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;
3285 // ---------- initialize trigger events -------------------------------------
3287 // initialize trigger events information
3288 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3289 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3290 trigger_events[i][j] = FALSE;
3292 // add trigger events from element change event properties
3293 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3295 struct ElementInfo *ei = &element_info[i];
3297 for (j = 0; j < ei->num_change_pages; j++)
3299 struct ElementChangeInfo *change = &ei->change_page[j];
3301 if (!change->can_change_or_has_action)
3304 if (change->has_event[CE_BY_OTHER_ACTION])
3306 int trigger_element = change->trigger_element;
3308 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3310 if (change->has_event[k])
3312 if (IS_GROUP_ELEMENT(trigger_element))
3314 struct ElementGroupInfo *group =
3315 element_info[trigger_element].group;
3317 for (l = 0; l < group->num_elements_resolved; l++)
3318 trigger_events[group->element_resolved[l]][k] = TRUE;
3320 else if (trigger_element == EL_ANY_ELEMENT)
3321 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3322 trigger_events[l][k] = TRUE;
3324 trigger_events[trigger_element][k] = TRUE;
3331 // ---------- initialize push delay -----------------------------------------
3333 // initialize push delay values to default
3334 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3336 if (!IS_CUSTOM_ELEMENT(i))
3338 // set default push delay values (corrected since version 3.0.7-1)
3339 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3341 element_info[i].push_delay_fixed = 2;
3342 element_info[i].push_delay_random = 8;
3346 element_info[i].push_delay_fixed = 8;
3347 element_info[i].push_delay_random = 8;
3352 // set push delay value for certain elements from pre-defined list
3353 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3355 int e = push_delay_list[i].element;
3357 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3358 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3361 // set push delay value for Supaplex elements for newer engine versions
3362 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3364 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3366 if (IS_SP_ELEMENT(i))
3368 // set SP push delay to just enough to push under a falling zonk
3369 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3371 element_info[i].push_delay_fixed = delay;
3372 element_info[i].push_delay_random = 0;
3377 // ---------- initialize move stepsize --------------------------------------
3379 // initialize move stepsize values to default
3380 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3381 if (!IS_CUSTOM_ELEMENT(i))
3382 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3384 // set move stepsize value for certain elements from pre-defined list
3385 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3387 int e = move_stepsize_list[i].element;
3389 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3391 // set move stepsize value for certain elements for older engine versions
3392 if (use_old_move_stepsize_for_magic_wall)
3394 if (e == EL_MAGIC_WALL_FILLING ||
3395 e == EL_MAGIC_WALL_EMPTYING ||
3396 e == EL_BD_MAGIC_WALL_FILLING ||
3397 e == EL_BD_MAGIC_WALL_EMPTYING)
3398 element_info[e].move_stepsize *= 2;
3402 // ---------- initialize collect score --------------------------------------
3404 // initialize collect score values for custom elements from initial value
3405 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3406 if (IS_CUSTOM_ELEMENT(i))
3407 element_info[i].collect_score = element_info[i].collect_score_initial;
3409 // ---------- initialize collect count --------------------------------------
3411 // initialize collect count values for non-custom elements
3412 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3413 if (!IS_CUSTOM_ELEMENT(i))
3414 element_info[i].collect_count_initial = 0;
3416 // add collect count values for all elements from pre-defined list
3417 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3418 element_info[collect_count_list[i].element].collect_count_initial =
3419 collect_count_list[i].count;
3421 // ---------- initialize access direction -----------------------------------
3423 // initialize access direction values to default (access from every side)
3424 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3425 if (!IS_CUSTOM_ELEMENT(i))
3426 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3428 // set access direction value for certain elements from pre-defined list
3429 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3430 element_info[access_direction_list[i].element].access_direction =
3431 access_direction_list[i].direction;
3433 // ---------- initialize explosion content ----------------------------------
3434 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3436 if (IS_CUSTOM_ELEMENT(i))
3439 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3441 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3443 element_info[i].content.e[x][y] =
3444 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3445 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3446 i == EL_PLAYER_3 ? EL_EMERALD :
3447 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3448 i == EL_MOLE ? EL_EMERALD_RED :
3449 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3450 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3451 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3452 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3453 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3454 i == EL_WALL_EMERALD ? EL_EMERALD :
3455 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3456 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3457 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3458 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3459 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3460 i == EL_WALL_PEARL ? EL_PEARL :
3461 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3466 // ---------- initialize recursion detection --------------------------------
3467 recursion_loop_depth = 0;
3468 recursion_loop_detected = FALSE;
3469 recursion_loop_element = EL_UNDEFINED;
3471 // ---------- initialize graphics engine ------------------------------------
3472 game.scroll_delay_value =
3473 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3474 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3475 !setup.forced_scroll_delay ? 0 :
3476 setup.scroll_delay ? setup.scroll_delay_value : 0);
3477 if (game.forced_scroll_delay_value == -1)
3478 game.scroll_delay_value =
3479 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3481 // ---------- initialize game engine snapshots ------------------------------
3482 for (i = 0; i < MAX_PLAYERS; i++)
3483 game.snapshot.last_action[i] = 0;
3484 game.snapshot.changed_action = FALSE;
3485 game.snapshot.collected_item = FALSE;
3486 game.snapshot.mode =
3487 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3488 SNAPSHOT_MODE_EVERY_STEP :
3489 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3490 SNAPSHOT_MODE_EVERY_MOVE :
3491 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3492 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3493 game.snapshot.save_snapshot = FALSE;
3495 // ---------- initialize level time for Supaplex engine ---------------------
3496 // Supaplex levels with time limit currently unsupported -- should be added
3497 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3500 // ---------- initialize flags for handling game actions --------------------
3502 // set flags for game actions to default values
3503 game.use_key_actions = TRUE;
3504 game.use_mouse_actions = FALSE;
3506 // when using Mirror Magic game engine, handle mouse events only
3507 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3509 game.use_key_actions = FALSE;
3510 game.use_mouse_actions = TRUE;
3513 // check for custom elements with mouse click events
3514 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3516 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3518 int element = EL_CUSTOM_START + i;
3520 if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3521 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3522 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3523 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3524 game.use_mouse_actions = TRUE;
3529 static int get_num_special_action(int element, int action_first,
3532 int num_special_action = 0;
3535 for (i = action_first; i <= action_last; i++)
3537 boolean found = FALSE;
3539 for (j = 0; j < NUM_DIRECTIONS; j++)
3540 if (el_act_dir2img(element, i, j) !=
3541 el_act_dir2img(element, ACTION_DEFAULT, j))
3545 num_special_action++;
3550 return num_special_action;
3554 // ============================================================================
3556 // ----------------------------------------------------------------------------
3557 // initialize and start new game
3558 // ============================================================================
3560 #if DEBUG_INIT_PLAYER
3561 static void DebugPrintPlayerStatus(char *message)
3568 Debug("game:init:player", "%s:", message);
3570 for (i = 0; i < MAX_PLAYERS; i++)
3572 struct PlayerInfo *player = &stored_player[i];
3574 Debug("game:init:player",
3575 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3579 player->connected_locally,
3580 player->connected_network,
3582 (local_player == player ? " (local player)" : ""));
3589 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3590 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3591 int fade_mask = REDRAW_FIELD;
3592 boolean restarting = (game_status == GAME_MODE_PLAYING);
3593 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3594 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3595 int initial_move_dir = MV_DOWN;
3598 // required here to update video display before fading (FIX THIS)
3599 DrawMaskedBorder(REDRAW_DOOR_2);
3601 if (!game.restart_level)
3602 CloseDoor(DOOR_CLOSE_1);
3606 // force fading out global animations displayed during game play
3607 SetGameStatus(GAME_MODE_PSEUDO_RESTARTING);
3611 SetGameStatus(GAME_MODE_PLAYING);
3614 if (level_editor_test_game)
3615 FadeSkipNextFadeOut();
3617 FadeSetEnterScreen();
3620 fade_mask = REDRAW_ALL;
3622 FadeLevelSoundsAndMusic();
3624 ExpireSoundLoops(TRUE);
3630 // force restarting global animations displayed during game play
3631 RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING);
3633 // this is required for "transforming" fade modes like cross-fading
3634 // (else global animations will be stopped, but not restarted here)
3635 SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING);
3637 SetGameStatus(GAME_MODE_PLAYING);
3640 if (level_editor_test_game)
3641 FadeSkipNextFadeIn();
3643 // needed if different viewport properties defined for playing
3644 ChangeViewportPropertiesIfNeeded();
3648 DrawCompleteVideoDisplay();
3650 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3653 InitGameControlValues();
3657 // initialize tape actions from game when recording tape
3658 tape.use_key_actions = game.use_key_actions;
3659 tape.use_mouse_actions = game.use_mouse_actions;
3661 // initialize visible playfield size when recording tape (for team mode)
3662 tape.scr_fieldx = SCR_FIELDX;
3663 tape.scr_fieldy = SCR_FIELDY;
3666 // don't play tapes over network
3667 network_playing = (network.enabled && !tape.playing);
3669 for (i = 0; i < MAX_PLAYERS; i++)
3671 struct PlayerInfo *player = &stored_player[i];
3673 player->index_nr = i;
3674 player->index_bit = (1 << i);
3675 player->element_nr = EL_PLAYER_1 + i;
3677 player->present = FALSE;
3678 player->active = FALSE;
3679 player->mapped = FALSE;
3681 player->killed = FALSE;
3682 player->reanimated = FALSE;
3683 player->buried = FALSE;
3686 player->effective_action = 0;
3687 player->programmed_action = 0;
3688 player->snap_action = 0;
3690 player->mouse_action.lx = 0;
3691 player->mouse_action.ly = 0;
3692 player->mouse_action.button = 0;
3693 player->mouse_action.button_hint = 0;
3695 player->effective_mouse_action.lx = 0;
3696 player->effective_mouse_action.ly = 0;
3697 player->effective_mouse_action.button = 0;
3698 player->effective_mouse_action.button_hint = 0;
3700 for (j = 0; j < MAX_NUM_KEYS; j++)
3701 player->key[j] = FALSE;
3703 player->num_white_keys = 0;
3705 player->dynabomb_count = 0;
3706 player->dynabomb_size = 1;
3707 player->dynabombs_left = 0;
3708 player->dynabomb_xl = FALSE;
3710 player->MovDir = initial_move_dir;
3713 player->GfxDir = initial_move_dir;
3714 player->GfxAction = ACTION_DEFAULT;
3716 player->StepFrame = 0;
3718 player->initial_element = player->element_nr;
3719 player->artwork_element =
3720 (level.use_artwork_element[i] ? level.artwork_element[i] :
3721 player->element_nr);
3722 player->use_murphy = FALSE;
3724 player->block_last_field = FALSE; // initialized in InitPlayerField()
3725 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3727 player->gravity = level.initial_player_gravity[i];
3729 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3731 player->actual_frame_counter.count = 0;
3732 player->actual_frame_counter.value = 1;
3734 player->step_counter = 0;
3736 player->last_move_dir = initial_move_dir;
3738 player->is_active = FALSE;
3740 player->is_waiting = FALSE;
3741 player->is_moving = FALSE;
3742 player->is_auto_moving = FALSE;
3743 player->is_digging = FALSE;
3744 player->is_snapping = FALSE;
3745 player->is_collecting = FALSE;
3746 player->is_pushing = FALSE;
3747 player->is_switching = FALSE;
3748 player->is_dropping = FALSE;
3749 player->is_dropping_pressed = FALSE;
3751 player->is_bored = FALSE;
3752 player->is_sleeping = FALSE;
3754 player->was_waiting = TRUE;
3755 player->was_moving = FALSE;
3756 player->was_snapping = FALSE;
3757 player->was_dropping = FALSE;
3759 player->force_dropping = FALSE;
3761 player->frame_counter_bored = -1;
3762 player->frame_counter_sleeping = -1;
3764 player->anim_delay_counter = 0;
3765 player->post_delay_counter = 0;
3767 player->dir_waiting = initial_move_dir;
3768 player->action_waiting = ACTION_DEFAULT;
3769 player->last_action_waiting = ACTION_DEFAULT;
3770 player->special_action_bored = ACTION_DEFAULT;
3771 player->special_action_sleeping = ACTION_DEFAULT;
3773 player->switch_x = -1;
3774 player->switch_y = -1;
3776 player->drop_x = -1;
3777 player->drop_y = -1;
3779 player->show_envelope = 0;
3781 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3783 player->push_delay = -1; // initialized when pushing starts
3784 player->push_delay_value = game.initial_push_delay_value;
3786 player->drop_delay = 0;
3787 player->drop_pressed_delay = 0;
3789 player->last_jx = -1;
3790 player->last_jy = -1;
3794 player->shield_normal_time_left = 0;
3795 player->shield_deadly_time_left = 0;
3797 player->last_removed_element = EL_UNDEFINED;
3799 player->inventory_infinite_element = EL_UNDEFINED;
3800 player->inventory_size = 0;
3802 if (level.use_initial_inventory[i])
3804 for (j = 0; j < level.initial_inventory_size[i]; j++)
3806 int element = level.initial_inventory_content[i][j];
3807 int collect_count = element_info[element].collect_count_initial;
3810 if (!IS_CUSTOM_ELEMENT(element))
3813 if (collect_count == 0)
3814 player->inventory_infinite_element = element;
3816 for (k = 0; k < collect_count; k++)
3817 if (player->inventory_size < MAX_INVENTORY_SIZE)
3818 player->inventory_element[player->inventory_size++] = element;
3822 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3823 SnapField(player, 0, 0);
3825 map_player_action[i] = i;
3828 network_player_action_received = FALSE;
3830 // initial null action
3831 if (network_playing)
3832 SendToServer_MovePlayer(MV_NONE);
3837 TimeLeft = level.time;
3840 ScreenMovDir = MV_NONE;
3844 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3846 game.robot_wheel_x = -1;
3847 game.robot_wheel_y = -1;
3852 game.all_players_gone = FALSE;
3854 game.LevelSolved = FALSE;
3855 game.GameOver = FALSE;
3857 game.GamePlayed = !tape.playing;
3859 game.LevelSolved_GameWon = FALSE;
3860 game.LevelSolved_GameEnd = FALSE;
3861 game.LevelSolved_SaveTape = FALSE;
3862 game.LevelSolved_SaveScore = FALSE;
3864 game.LevelSolved_CountingTime = 0;
3865 game.LevelSolved_CountingScore = 0;
3866 game.LevelSolved_CountingHealth = 0;
3868 game.panel.active = TRUE;
3870 game.no_level_time_limit = (level.time == 0);
3871 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3873 game.yamyam_content_nr = 0;
3874 game.robot_wheel_active = FALSE;
3875 game.magic_wall_active = FALSE;
3876 game.magic_wall_time_left = 0;
3877 game.light_time_left = 0;
3878 game.timegate_time_left = 0;
3879 game.switchgate_pos = 0;
3880 game.wind_direction = level.wind_direction_initial;
3882 game.time_final = 0;
3883 game.score_time_final = 0;
3886 game.score_final = 0;
3888 game.health = MAX_HEALTH;
3889 game.health_final = MAX_HEALTH;
3891 game.gems_still_needed = level.gems_needed;
3892 game.sokoban_fields_still_needed = 0;
3893 game.sokoban_objects_still_needed = 0;
3894 game.lights_still_needed = 0;
3895 game.players_still_needed = 0;
3896 game.friends_still_needed = 0;
3898 game.lenses_time_left = 0;
3899 game.magnify_time_left = 0;
3901 game.ball_active = level.ball_active_initial;
3902 game.ball_content_nr = 0;
3904 game.explosions_delayed = TRUE;
3906 game.envelope_active = FALSE;
3908 // special case: set custom artwork setting to initial value
3909 game.use_masked_elements = game.use_masked_elements_initial;
3911 for (i = 0; i < NUM_BELTS; i++)
3913 game.belt_dir[i] = MV_NONE;
3914 game.belt_dir_nr[i] = 3; // not moving, next moving left
3917 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3918 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3920 #if DEBUG_INIT_PLAYER
3921 DebugPrintPlayerStatus("Player status at level initialization");
3924 SCAN_PLAYFIELD(x, y)
3926 Tile[x][y] = Last[x][y] = level.field[x][y];
3927 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3928 ChangeDelay[x][y] = 0;
3929 ChangePage[x][y] = -1;
3930 CustomValue[x][y] = 0; // initialized in InitField()
3931 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3933 WasJustMoving[x][y] = 0;
3934 WasJustFalling[x][y] = 0;
3935 CheckCollision[x][y] = 0;
3936 CheckImpact[x][y] = 0;
3938 Pushed[x][y] = FALSE;
3940 ChangeCount[x][y] = 0;
3941 ChangeEvent[x][y] = -1;
3943 ExplodePhase[x][y] = 0;
3944 ExplodeDelay[x][y] = 0;
3945 ExplodeField[x][y] = EX_TYPE_NONE;
3947 RunnerVisit[x][y] = 0;
3948 PlayerVisit[x][y] = 0;
3951 GfxRandom[x][y] = INIT_GFX_RANDOM();
3952 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
3953 GfxElement[x][y] = EL_UNDEFINED;
3954 GfxElementEmpty[x][y] = EL_EMPTY;
3955 GfxAction[x][y] = ACTION_DEFAULT;
3956 GfxDir[x][y] = MV_NONE;
3957 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3960 SCAN_PLAYFIELD(x, y)
3962 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3964 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3967 InitField(x, y, TRUE);
3969 ResetGfxAnimation(x, y);
3974 for (i = 0; i < MAX_PLAYERS; i++)
3976 struct PlayerInfo *player = &stored_player[i];
3978 // set number of special actions for bored and sleeping animation
3979 player->num_special_action_bored =
3980 get_num_special_action(player->artwork_element,
3981 ACTION_BORING_1, ACTION_BORING_LAST);
3982 player->num_special_action_sleeping =
3983 get_num_special_action(player->artwork_element,
3984 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3987 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3988 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3990 // initialize type of slippery elements
3991 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3993 if (!IS_CUSTOM_ELEMENT(i))
3995 // default: elements slip down either to the left or right randomly
3996 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3998 // SP style elements prefer to slip down on the left side
3999 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4000 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4002 // BD style elements prefer to slip down on the left side
4003 if (game.emulation == EMU_BOULDERDASH)
4004 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4008 // initialize explosion and ignition delay
4009 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4011 if (!IS_CUSTOM_ELEMENT(i))
4014 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4015 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4016 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4017 int last_phase = (num_phase + 1) * delay;
4018 int half_phase = (num_phase / 2) * delay;
4020 element_info[i].explosion_delay = last_phase - 1;
4021 element_info[i].ignition_delay = half_phase;
4023 if (i == EL_BLACK_ORB)
4024 element_info[i].ignition_delay = 1;
4028 // correct non-moving belts to start moving left
4029 for (i = 0; i < NUM_BELTS; i++)
4030 if (game.belt_dir[i] == MV_NONE)
4031 game.belt_dir_nr[i] = 3; // not moving, next moving left
4033 #if USE_NEW_PLAYER_ASSIGNMENTS
4034 // use preferred player also in local single-player mode
4035 if (!network.enabled && !game.team_mode)
4037 int new_index_nr = setup.network_player_nr;
4039 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4041 for (i = 0; i < MAX_PLAYERS; i++)
4042 stored_player[i].connected_locally = FALSE;
4044 stored_player[new_index_nr].connected_locally = TRUE;
4048 for (i = 0; i < MAX_PLAYERS; i++)
4050 stored_player[i].connected = FALSE;
4052 // in network game mode, the local player might not be the first player
4053 if (stored_player[i].connected_locally)
4054 local_player = &stored_player[i];
4057 if (!network.enabled)
4058 local_player->connected = TRUE;
4062 for (i = 0; i < MAX_PLAYERS; i++)
4063 stored_player[i].connected = tape.player_participates[i];
4065 else if (network.enabled)
4067 // add team mode players connected over the network (needed for correct
4068 // assignment of player figures from level to locally playing players)
4070 for (i = 0; i < MAX_PLAYERS; i++)
4071 if (stored_player[i].connected_network)
4072 stored_player[i].connected = TRUE;
4074 else if (game.team_mode)
4076 // try to guess locally connected team mode players (needed for correct
4077 // assignment of player figures from level to locally playing players)
4079 for (i = 0; i < MAX_PLAYERS; i++)
4080 if (setup.input[i].use_joystick ||
4081 setup.input[i].key.left != KSYM_UNDEFINED)
4082 stored_player[i].connected = TRUE;
4085 #if DEBUG_INIT_PLAYER
4086 DebugPrintPlayerStatus("Player status after level initialization");
4089 #if DEBUG_INIT_PLAYER
4090 Debug("game:init:player", "Reassigning players ...");
4093 // check if any connected player was not found in playfield
4094 for (i = 0; i < MAX_PLAYERS; i++)
4096 struct PlayerInfo *player = &stored_player[i];
4098 if (player->connected && !player->present)
4100 struct PlayerInfo *field_player = NULL;
4102 #if DEBUG_INIT_PLAYER
4103 Debug("game:init:player",
4104 "- looking for field player for player %d ...", i + 1);
4107 // assign first free player found that is present in the playfield
4109 // first try: look for unmapped playfield player that is not connected
4110 for (j = 0; j < MAX_PLAYERS; j++)
4111 if (field_player == NULL &&
4112 stored_player[j].present &&
4113 !stored_player[j].mapped &&
4114 !stored_player[j].connected)
4115 field_player = &stored_player[j];
4117 // second try: look for *any* unmapped playfield player
4118 for (j = 0; j < MAX_PLAYERS; j++)
4119 if (field_player == NULL &&
4120 stored_player[j].present &&
4121 !stored_player[j].mapped)
4122 field_player = &stored_player[j];
4124 if (field_player != NULL)
4126 int jx = field_player->jx, jy = field_player->jy;
4128 #if DEBUG_INIT_PLAYER
4129 Debug("game:init:player", "- found player %d",
4130 field_player->index_nr + 1);
4133 player->present = FALSE;
4134 player->active = FALSE;
4136 field_player->present = TRUE;
4137 field_player->active = TRUE;
4140 player->initial_element = field_player->initial_element;
4141 player->artwork_element = field_player->artwork_element;
4143 player->block_last_field = field_player->block_last_field;
4144 player->block_delay_adjustment = field_player->block_delay_adjustment;
4147 StorePlayer[jx][jy] = field_player->element_nr;
4149 field_player->jx = field_player->last_jx = jx;
4150 field_player->jy = field_player->last_jy = jy;
4152 if (local_player == player)
4153 local_player = field_player;
4155 map_player_action[field_player->index_nr] = i;
4157 field_player->mapped = TRUE;
4159 #if DEBUG_INIT_PLAYER
4160 Debug("game:init:player", "- map_player_action[%d] == %d",
4161 field_player->index_nr + 1, i + 1);
4166 if (player->connected && player->present)
4167 player->mapped = TRUE;
4170 #if DEBUG_INIT_PLAYER
4171 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4176 // check if any connected player was not found in playfield
4177 for (i = 0; i < MAX_PLAYERS; i++)
4179 struct PlayerInfo *player = &stored_player[i];
4181 if (player->connected && !player->present)
4183 for (j = 0; j < MAX_PLAYERS; j++)
4185 struct PlayerInfo *field_player = &stored_player[j];
4186 int jx = field_player->jx, jy = field_player->jy;
4188 // assign first free player found that is present in the playfield
4189 if (field_player->present && !field_player->connected)
4191 player->present = TRUE;
4192 player->active = TRUE;
4194 field_player->present = FALSE;
4195 field_player->active = FALSE;
4197 player->initial_element = field_player->initial_element;
4198 player->artwork_element = field_player->artwork_element;
4200 player->block_last_field = field_player->block_last_field;
4201 player->block_delay_adjustment = field_player->block_delay_adjustment;
4203 StorePlayer[jx][jy] = player->element_nr;
4205 player->jx = player->last_jx = jx;
4206 player->jy = player->last_jy = jy;
4216 Debug("game:init:player", "local_player->present == %d",
4217 local_player->present);
4220 // set focus to local player for network games, else to all players
4221 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4222 game.centered_player_nr_next = game.centered_player_nr;
4223 game.set_centered_player = FALSE;
4224 game.set_centered_player_wrap = FALSE;
4226 if (network_playing && tape.recording)
4228 // store client dependent player focus when recording network games
4229 tape.centered_player_nr_next = game.centered_player_nr_next;
4230 tape.set_centered_player = TRUE;
4235 // when playing a tape, eliminate all players who do not participate
4237 #if USE_NEW_PLAYER_ASSIGNMENTS
4239 if (!game.team_mode)
4241 for (i = 0; i < MAX_PLAYERS; i++)
4243 if (stored_player[i].active &&
4244 !tape.player_participates[map_player_action[i]])
4246 struct PlayerInfo *player = &stored_player[i];
4247 int jx = player->jx, jy = player->jy;
4249 #if DEBUG_INIT_PLAYER
4250 Debug("game:init:player", "Removing player %d at (%d, %d)",
4254 player->active = FALSE;
4255 StorePlayer[jx][jy] = 0;
4256 Tile[jx][jy] = EL_EMPTY;
4263 for (i = 0; i < MAX_PLAYERS; i++)
4265 if (stored_player[i].active &&
4266 !tape.player_participates[i])
4268 struct PlayerInfo *player = &stored_player[i];
4269 int jx = player->jx, jy = player->jy;
4271 player->active = FALSE;
4272 StorePlayer[jx][jy] = 0;
4273 Tile[jx][jy] = EL_EMPTY;
4278 else if (!network.enabled && !game.team_mode) // && !tape.playing
4280 // when in single player mode, eliminate all but the local player
4282 for (i = 0; i < MAX_PLAYERS; i++)
4284 struct PlayerInfo *player = &stored_player[i];
4286 if (player->active && player != local_player)
4288 int jx = player->jx, jy = player->jy;
4290 player->active = FALSE;
4291 player->present = FALSE;
4293 StorePlayer[jx][jy] = 0;
4294 Tile[jx][jy] = EL_EMPTY;
4299 for (i = 0; i < MAX_PLAYERS; i++)
4300 if (stored_player[i].active)
4301 game.players_still_needed++;
4303 if (level.solved_by_one_player)
4304 game.players_still_needed = 1;
4306 // when recording the game, store which players take part in the game
4309 #if USE_NEW_PLAYER_ASSIGNMENTS
4310 for (i = 0; i < MAX_PLAYERS; i++)
4311 if (stored_player[i].connected)
4312 tape.player_participates[i] = TRUE;
4314 for (i = 0; i < MAX_PLAYERS; i++)
4315 if (stored_player[i].active)
4316 tape.player_participates[i] = TRUE;
4320 #if DEBUG_INIT_PLAYER
4321 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4324 if (BorderElement == EL_EMPTY)
4327 SBX_Right = lev_fieldx - SCR_FIELDX;
4329 SBY_Lower = lev_fieldy - SCR_FIELDY;
4334 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4336 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4339 if (full_lev_fieldx <= SCR_FIELDX)
4340 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4341 if (full_lev_fieldy <= SCR_FIELDY)
4342 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4344 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4346 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4349 // if local player not found, look for custom element that might create
4350 // the player (make some assumptions about the right custom element)
4351 if (!local_player->present)
4353 int start_x = 0, start_y = 0;
4354 int found_rating = 0;
4355 int found_element = EL_UNDEFINED;
4356 int player_nr = local_player->index_nr;
4358 SCAN_PLAYFIELD(x, y)
4360 int element = Tile[x][y];
4365 if (level.use_start_element[player_nr] &&
4366 level.start_element[player_nr] == element &&
4373 found_element = element;
4376 if (!IS_CUSTOM_ELEMENT(element))
4379 if (CAN_CHANGE(element))
4381 for (i = 0; i < element_info[element].num_change_pages; i++)
4383 // check for player created from custom element as single target
4384 content = element_info[element].change_page[i].target_element;
4385 is_player = IS_PLAYER_ELEMENT(content);
4387 if (is_player && (found_rating < 3 ||
4388 (found_rating == 3 && element < found_element)))
4394 found_element = element;
4399 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4401 // check for player created from custom element as explosion content
4402 content = element_info[element].content.e[xx][yy];
4403 is_player = IS_PLAYER_ELEMENT(content);
4405 if (is_player && (found_rating < 2 ||
4406 (found_rating == 2 && element < found_element)))
4408 start_x = x + xx - 1;
4409 start_y = y + yy - 1;
4412 found_element = element;
4415 if (!CAN_CHANGE(element))
4418 for (i = 0; i < element_info[element].num_change_pages; i++)
4420 // check for player created from custom element as extended target
4422 element_info[element].change_page[i].target_content.e[xx][yy];
4424 is_player = IS_PLAYER_ELEMENT(content);
4426 if (is_player && (found_rating < 1 ||
4427 (found_rating == 1 && element < found_element)))
4429 start_x = x + xx - 1;
4430 start_y = y + yy - 1;
4433 found_element = element;
4439 scroll_x = SCROLL_POSITION_X(start_x);
4440 scroll_y = SCROLL_POSITION_Y(start_y);
4444 scroll_x = SCROLL_POSITION_X(local_player->jx);
4445 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4448 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4449 scroll_x = game.forced_scroll_x;
4450 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4451 scroll_y = game.forced_scroll_y;
4453 // !!! FIX THIS (START) !!!
4454 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4456 InitGameEngine_EM();
4458 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4460 InitGameEngine_SP();
4462 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4464 InitGameEngine_MM();
4468 DrawLevel(REDRAW_FIELD);
4471 // after drawing the level, correct some elements
4472 if (game.timegate_time_left == 0)
4473 CloseAllOpenTimegates();
4476 // blit playfield from scroll buffer to normal back buffer for fading in
4477 BlitScreenToBitmap(backbuffer);
4478 // !!! FIX THIS (END) !!!
4480 DrawMaskedBorder(fade_mask);
4485 // full screen redraw is required at this point in the following cases:
4486 // - special editor door undrawn when game was started from level editor
4487 // - drawing area (playfield) was changed and has to be removed completely
4488 redraw_mask = REDRAW_ALL;
4492 if (!game.restart_level)
4494 // copy default game door content to main double buffer
4496 // !!! CHECK AGAIN !!!
4497 SetPanelBackground();
4498 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4499 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4502 SetPanelBackground();
4503 SetDrawBackgroundMask(REDRAW_DOOR_1);
4505 UpdateAndDisplayGameControlValues();
4507 if (!game.restart_level)
4513 CreateGameButtons();
4518 // copy actual game door content to door double buffer for OpenDoor()
4519 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4521 OpenDoor(DOOR_OPEN_ALL);
4523 KeyboardAutoRepeatOffUnlessAutoplay();
4525 #if DEBUG_INIT_PLAYER
4526 DebugPrintPlayerStatus("Player status (final)");
4535 if (!game.restart_level && !tape.playing)
4537 LevelStats_incPlayed(level_nr);
4539 SaveLevelSetup_SeriesInfo();
4542 game.restart_level = FALSE;
4544 game.request_active = FALSE;
4545 game.request_active_or_moving = FALSE;
4547 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4548 InitGameActions_MM();
4550 SaveEngineSnapshotToListInitial();
4552 if (!game.restart_level)
4554 PlaySound(SND_GAME_STARTING);
4556 if (setup.sound_music)
4560 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4563 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4564 int actual_player_x, int actual_player_y)
4566 // this is used for non-R'n'D game engines to update certain engine values
4568 // needed to determine if sounds are played within the visible screen area
4569 scroll_x = actual_scroll_x;
4570 scroll_y = actual_scroll_y;
4572 // needed to get player position for "follow finger" playing input method
4573 local_player->jx = actual_player_x;
4574 local_player->jy = actual_player_y;
4577 void InitMovDir(int x, int y)
4579 int i, element = Tile[x][y];
4580 static int xy[4][2] =
4587 static int direction[3][4] =
4589 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4590 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4591 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4600 Tile[x][y] = EL_BUG;
4601 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4604 case EL_SPACESHIP_RIGHT:
4605 case EL_SPACESHIP_UP:
4606 case EL_SPACESHIP_LEFT:
4607 case EL_SPACESHIP_DOWN:
4608 Tile[x][y] = EL_SPACESHIP;
4609 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4612 case EL_BD_BUTTERFLY_RIGHT:
4613 case EL_BD_BUTTERFLY_UP:
4614 case EL_BD_BUTTERFLY_LEFT:
4615 case EL_BD_BUTTERFLY_DOWN:
4616 Tile[x][y] = EL_BD_BUTTERFLY;
4617 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4620 case EL_BD_FIREFLY_RIGHT:
4621 case EL_BD_FIREFLY_UP:
4622 case EL_BD_FIREFLY_LEFT:
4623 case EL_BD_FIREFLY_DOWN:
4624 Tile[x][y] = EL_BD_FIREFLY;
4625 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4628 case EL_PACMAN_RIGHT:
4630 case EL_PACMAN_LEFT:
4631 case EL_PACMAN_DOWN:
4632 Tile[x][y] = EL_PACMAN;
4633 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4636 case EL_YAMYAM_LEFT:
4637 case EL_YAMYAM_RIGHT:
4639 case EL_YAMYAM_DOWN:
4640 Tile[x][y] = EL_YAMYAM;
4641 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4644 case EL_SP_SNIKSNAK:
4645 MovDir[x][y] = MV_UP;
4648 case EL_SP_ELECTRON:
4649 MovDir[x][y] = MV_LEFT;
4656 Tile[x][y] = EL_MOLE;
4657 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4660 case EL_SPRING_LEFT:
4661 case EL_SPRING_RIGHT:
4662 Tile[x][y] = EL_SPRING;
4663 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4667 if (IS_CUSTOM_ELEMENT(element))
4669 struct ElementInfo *ei = &element_info[element];
4670 int move_direction_initial = ei->move_direction_initial;
4671 int move_pattern = ei->move_pattern;
4673 if (move_direction_initial == MV_START_PREVIOUS)
4675 if (MovDir[x][y] != MV_NONE)
4678 move_direction_initial = MV_START_AUTOMATIC;
4681 if (move_direction_initial == MV_START_RANDOM)
4682 MovDir[x][y] = 1 << RND(4);
4683 else if (move_direction_initial & MV_ANY_DIRECTION)
4684 MovDir[x][y] = move_direction_initial;
4685 else if (move_pattern == MV_ALL_DIRECTIONS ||
4686 move_pattern == MV_TURNING_LEFT ||
4687 move_pattern == MV_TURNING_RIGHT ||
4688 move_pattern == MV_TURNING_LEFT_RIGHT ||
4689 move_pattern == MV_TURNING_RIGHT_LEFT ||
4690 move_pattern == MV_TURNING_RANDOM)
4691 MovDir[x][y] = 1 << RND(4);
4692 else if (move_pattern == MV_HORIZONTAL)
4693 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4694 else if (move_pattern == MV_VERTICAL)
4695 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4696 else if (move_pattern & MV_ANY_DIRECTION)
4697 MovDir[x][y] = element_info[element].move_pattern;
4698 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4699 move_pattern == MV_ALONG_RIGHT_SIDE)
4701 // use random direction as default start direction
4702 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4703 MovDir[x][y] = 1 << RND(4);
4705 for (i = 0; i < NUM_DIRECTIONS; i++)
4707 int x1 = x + xy[i][0];
4708 int y1 = y + xy[i][1];
4710 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4712 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4713 MovDir[x][y] = direction[0][i];
4715 MovDir[x][y] = direction[1][i];
4724 MovDir[x][y] = 1 << RND(4);
4726 if (element != EL_BUG &&
4727 element != EL_SPACESHIP &&
4728 element != EL_BD_BUTTERFLY &&
4729 element != EL_BD_FIREFLY)
4732 for (i = 0; i < NUM_DIRECTIONS; i++)
4734 int x1 = x + xy[i][0];
4735 int y1 = y + xy[i][1];
4737 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4739 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4741 MovDir[x][y] = direction[0][i];
4744 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4745 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4747 MovDir[x][y] = direction[1][i];
4756 GfxDir[x][y] = MovDir[x][y];
4759 void InitAmoebaNr(int x, int y)
4762 int group_nr = AmoebaNeighbourNr(x, y);
4766 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4768 if (AmoebaCnt[i] == 0)
4776 AmoebaNr[x][y] = group_nr;
4777 AmoebaCnt[group_nr]++;
4778 AmoebaCnt2[group_nr]++;
4781 static void LevelSolved_SetFinalGameValues(void)
4783 game.time_final = (game.no_level_time_limit ? TimePlayed : TimeLeft);
4784 game.score_time_final = (level.use_step_counter ? TimePlayed :
4785 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4787 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4788 game_em.lev->score :
4789 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4793 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4794 MM_HEALTH(game_mm.laser_overload_value) :
4797 game.LevelSolved_CountingTime = game.time_final;
4798 game.LevelSolved_CountingScore = game.score_final;
4799 game.LevelSolved_CountingHealth = game.health_final;
4802 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4804 game.LevelSolved_CountingTime = time;
4805 game.LevelSolved_CountingScore = score;
4806 game.LevelSolved_CountingHealth = health;
4808 game_panel_controls[GAME_PANEL_TIME].value = time;
4809 game_panel_controls[GAME_PANEL_SCORE].value = score;
4810 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4812 DisplayGameControlValues();
4815 static void LevelSolved(void)
4817 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4818 game.players_still_needed > 0)
4821 game.LevelSolved = TRUE;
4822 game.GameOver = TRUE;
4826 // needed here to display correct panel values while player walks into exit
4827 LevelSolved_SetFinalGameValues();
4832 static int time_count_steps;
4833 static int time, time_final;
4834 static float score, score_final; // needed for time score < 10 for 10 seconds
4835 static int health, health_final;
4836 static int game_over_delay_1 = 0;
4837 static int game_over_delay_2 = 0;
4838 static int game_over_delay_3 = 0;
4839 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4840 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4842 if (!game.LevelSolved_GameWon)
4846 // do not start end game actions before the player stops moving (to exit)
4847 if (local_player->active && local_player->MovPos)
4850 // calculate final game values after player finished walking into exit
4851 LevelSolved_SetFinalGameValues();
4853 game.LevelSolved_GameWon = TRUE;
4854 game.LevelSolved_SaveTape = tape.recording;
4855 game.LevelSolved_SaveScore = !tape.playing;
4859 LevelStats_incSolved(level_nr);
4861 SaveLevelSetup_SeriesInfo();
4864 if (tape.auto_play) // tape might already be stopped here
4865 tape.auto_play_level_solved = TRUE;
4869 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4870 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4871 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4873 time = time_final = game.time_final;
4874 score = score_final = game.score_final;
4875 health = health_final = game.health_final;
4877 // update game panel values before (delayed) counting of score (if any)
4878 LevelSolved_DisplayFinalGameValues(time, score, health);
4880 // if level has time score defined, calculate new final game values
4883 int time_final_max = 999;
4884 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4885 int time_frames = 0;
4886 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4887 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4892 time_frames = time_frames_left;
4894 else if (game.no_level_time_limit && TimePlayed < time_final_max)
4896 time_final = time_final_max;
4897 time_frames = time_frames_final_max - time_frames_played;
4900 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4902 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4904 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4907 score_final += health * time_score;
4910 game.score_final = score_final;
4911 game.health_final = health_final;
4914 // if not counting score after game, immediately update game panel values
4915 if (level_editor_test_game || !setup.count_score_after_game)
4918 score = score_final;
4920 LevelSolved_DisplayFinalGameValues(time, score, health);
4923 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4925 // check if last player has left the level
4926 if (game.exit_x >= 0 &&
4929 int x = game.exit_x;
4930 int y = game.exit_y;
4931 int element = Tile[x][y];
4933 // close exit door after last player
4934 if ((game.all_players_gone &&
4935 (element == EL_EXIT_OPEN ||
4936 element == EL_SP_EXIT_OPEN ||
4937 element == EL_STEEL_EXIT_OPEN)) ||
4938 element == EL_EM_EXIT_OPEN ||
4939 element == EL_EM_STEEL_EXIT_OPEN)
4943 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4944 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4945 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4946 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4947 EL_EM_STEEL_EXIT_CLOSING);
4949 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4952 // player disappears
4953 DrawLevelField(x, y);
4956 for (i = 0; i < MAX_PLAYERS; i++)
4958 struct PlayerInfo *player = &stored_player[i];
4960 if (player->present)
4962 RemovePlayer(player);
4964 // player disappears
4965 DrawLevelField(player->jx, player->jy);
4970 PlaySound(SND_GAME_WINNING);
4973 if (setup.count_score_after_game)
4975 if (time != time_final)
4977 if (game_over_delay_1 > 0)
4979 game_over_delay_1--;
4984 int time_to_go = ABS(time_final - time);
4985 int time_count_dir = (time < time_final ? +1 : -1);
4987 if (time_to_go < time_count_steps)
4988 time_count_steps = 1;
4990 time += time_count_steps * time_count_dir;
4991 score += time_count_steps * time_score;
4993 // set final score to correct rounding differences after counting score
4994 if (time == time_final)
4995 score = score_final;
4997 LevelSolved_DisplayFinalGameValues(time, score, health);
4999 if (time == time_final)
5000 StopSound(SND_GAME_LEVELTIME_BONUS);
5001 else if (setup.sound_loops)
5002 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5004 PlaySound(SND_GAME_LEVELTIME_BONUS);
5009 if (health != health_final)
5011 if (game_over_delay_2 > 0)
5013 game_over_delay_2--;
5018 int health_count_dir = (health < health_final ? +1 : -1);
5020 health += health_count_dir;
5021 score += time_score;
5023 LevelSolved_DisplayFinalGameValues(time, score, health);
5025 if (health == health_final)
5026 StopSound(SND_GAME_LEVELTIME_BONUS);
5027 else if (setup.sound_loops)
5028 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5030 PlaySound(SND_GAME_LEVELTIME_BONUS);
5036 game.panel.active = FALSE;
5038 if (game_over_delay_3 > 0)
5040 game_over_delay_3--;
5050 // used instead of "level_nr" (needed for network games)
5051 int last_level_nr = levelset.level_nr;
5052 boolean tape_saved = FALSE;
5054 game.LevelSolved_GameEnd = TRUE;
5056 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5058 // make sure that request dialog to save tape does not open door again
5059 if (!global.use_envelope_request)
5060 CloseDoor(DOOR_CLOSE_1);
5063 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5065 // set unique basename for score tape (also saved in high score table)
5066 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5069 // if no tape is to be saved, close both doors simultaneously
5070 CloseDoor(DOOR_CLOSE_ALL);
5072 if (level_editor_test_game || score_info_tape_play)
5074 SetGameStatus(GAME_MODE_MAIN);
5081 if (!game.LevelSolved_SaveScore)
5083 SetGameStatus(GAME_MODE_MAIN);
5090 if (level_nr == leveldir_current->handicap_level)
5092 leveldir_current->handicap_level++;
5094 SaveLevelSetup_SeriesInfo();
5097 // save score and score tape before potentially erasing tape below
5098 NewHighScore(last_level_nr, tape_saved);
5100 if (setup.increment_levels &&
5101 level_nr < leveldir_current->last_level &&
5104 level_nr++; // advance to next level
5105 TapeErase(); // start with empty tape
5107 if (setup.auto_play_next_level)
5109 scores.continue_playing = TRUE;
5110 scores.next_level_nr = level_nr;
5112 LoadLevel(level_nr);
5114 SaveLevelSetup_SeriesInfo();
5118 if (scores.last_added >= 0 && setup.show_scores_after_game)
5120 SetGameStatus(GAME_MODE_SCORES);
5122 DrawHallOfFame(last_level_nr);
5124 else if (scores.continue_playing)
5126 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5130 SetGameStatus(GAME_MODE_MAIN);
5136 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5137 boolean one_score_entry_per_name)
5141 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5144 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5146 struct ScoreEntry *entry = &list->entry[i];
5147 boolean score_is_better = (new_entry->score > entry->score);
5148 boolean score_is_equal = (new_entry->score == entry->score);
5149 boolean time_is_better = (new_entry->time < entry->time);
5150 boolean time_is_equal = (new_entry->time == entry->time);
5151 boolean better_by_score = (score_is_better ||
5152 (score_is_equal && time_is_better));
5153 boolean better_by_time = (time_is_better ||
5154 (time_is_equal && score_is_better));
5155 boolean is_better = (level.rate_time_over_score ? better_by_time :
5157 boolean entry_is_empty = (entry->score == 0 &&
5160 // prevent adding server score entries if also existing in local score file
5161 // (special case: historic score entries have an empty tape basename entry)
5162 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5163 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5165 // add fields from server score entry not stored in local score entry
5166 // (currently, this means setting platform, version and country fields;
5167 // in rare cases, this may also correct an invalid score value, as
5168 // historic scores might have been truncated to 16-bit values locally)
5169 *entry = *new_entry;
5174 if (is_better || entry_is_empty)
5176 // player has made it to the hall of fame
5178 if (i < MAX_SCORE_ENTRIES - 1)
5180 int m = MAX_SCORE_ENTRIES - 1;
5183 if (one_score_entry_per_name)
5185 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5186 if (strEqual(list->entry[l].name, new_entry->name))
5189 if (m == i) // player's new highscore overwrites his old one
5193 for (l = m; l > i; l--)
5194 list->entry[l] = list->entry[l - 1];
5199 *entry = *new_entry;
5203 else if (one_score_entry_per_name &&
5204 strEqual(entry->name, new_entry->name))
5206 // player already in high score list with better score or time
5212 // special case: new score is beyond the last high score list position
5213 return MAX_SCORE_ENTRIES;
5216 void NewHighScore(int level_nr, boolean tape_saved)
5218 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5219 boolean one_per_name = FALSE;
5221 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5222 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5224 new_entry.score = game.score_final;
5225 new_entry.time = game.score_time_final;
5227 LoadScore(level_nr);
5229 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5231 if (scores.last_added >= MAX_SCORE_ENTRIES)
5233 scores.last_added = MAX_SCORE_ENTRIES - 1;
5234 scores.force_last_added = TRUE;
5236 scores.entry[scores.last_added] = new_entry;
5238 // store last added local score entry (before merging server scores)
5239 scores.last_added_local = scores.last_added;
5244 if (scores.last_added < 0)
5247 SaveScore(level_nr);
5249 // store last added local score entry (before merging server scores)
5250 scores.last_added_local = scores.last_added;
5252 if (!game.LevelSolved_SaveTape)
5255 SaveScoreTape(level_nr);
5257 if (setup.ask_for_using_api_server)
5259 setup.use_api_server =
5260 Request("Upload your score and tape to the high score server?", REQ_ASK);
5262 if (!setup.use_api_server)
5263 Request("Not using high score server! Use setup menu to enable again!",
5266 runtime.use_api_server = setup.use_api_server;
5268 // after asking for using API server once, do not ask again
5269 setup.ask_for_using_api_server = FALSE;
5271 SaveSetup_ServerSetup();
5274 SaveServerScore(level_nr, tape_saved);
5277 void MergeServerScore(void)
5279 struct ScoreEntry last_added_entry;
5280 boolean one_per_name = FALSE;
5283 if (scores.last_added >= 0)
5284 last_added_entry = scores.entry[scores.last_added];
5286 for (i = 0; i < server_scores.num_entries; i++)
5288 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5290 if (pos >= 0 && pos <= scores.last_added)
5291 scores.last_added++;
5294 if (scores.last_added >= MAX_SCORE_ENTRIES)
5296 scores.last_added = MAX_SCORE_ENTRIES - 1;
5297 scores.force_last_added = TRUE;
5299 scores.entry[scores.last_added] = last_added_entry;
5303 static int getElementMoveStepsizeExt(int x, int y, int direction)
5305 int element = Tile[x][y];
5306 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5307 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5308 int horiz_move = (dx != 0);
5309 int sign = (horiz_move ? dx : dy);
5310 int step = sign * element_info[element].move_stepsize;
5312 // special values for move stepsize for spring and things on conveyor belt
5315 if (CAN_FALL(element) &&
5316 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5317 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5318 else if (element == EL_SPRING)
5319 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5325 static int getElementMoveStepsize(int x, int y)
5327 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5330 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5332 if (player->GfxAction != action || player->GfxDir != dir)
5334 player->GfxAction = action;
5335 player->GfxDir = dir;
5337 player->StepFrame = 0;
5341 static void ResetGfxFrame(int x, int y)
5343 // profiling showed that "autotest" spends 10~20% of its time in this function
5344 if (DrawingDeactivatedField())
5347 int element = Tile[x][y];
5348 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5350 if (graphic_info[graphic].anim_global_sync)
5351 GfxFrame[x][y] = FrameCounter;
5352 else if (graphic_info[graphic].anim_global_anim_sync)
5353 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5354 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5355 GfxFrame[x][y] = CustomValue[x][y];
5356 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5357 GfxFrame[x][y] = element_info[element].collect_score;
5358 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5359 GfxFrame[x][y] = ChangeDelay[x][y];
5362 static void ResetGfxAnimation(int x, int y)
5364 GfxAction[x][y] = ACTION_DEFAULT;
5365 GfxDir[x][y] = MovDir[x][y];
5368 ResetGfxFrame(x, y);
5371 static void ResetRandomAnimationValue(int x, int y)
5373 GfxRandom[x][y] = INIT_GFX_RANDOM();
5376 static void InitMovingField(int x, int y, int direction)
5378 int element = Tile[x][y];
5379 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5380 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5383 boolean is_moving_before, is_moving_after;
5385 // check if element was/is moving or being moved before/after mode change
5386 is_moving_before = (WasJustMoving[x][y] != 0);
5387 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5389 // reset animation only for moving elements which change direction of moving
5390 // or which just started or stopped moving
5391 // (else CEs with property "can move" / "not moving" are reset each frame)
5392 if (is_moving_before != is_moving_after ||
5393 direction != MovDir[x][y])
5394 ResetGfxAnimation(x, y);
5396 MovDir[x][y] = direction;
5397 GfxDir[x][y] = direction;
5399 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5400 direction == MV_DOWN && CAN_FALL(element) ?
5401 ACTION_FALLING : ACTION_MOVING);
5403 // this is needed for CEs with property "can move" / "not moving"
5405 if (is_moving_after)
5407 if (Tile[newx][newy] == EL_EMPTY)
5408 Tile[newx][newy] = EL_BLOCKED;
5410 MovDir[newx][newy] = MovDir[x][y];
5412 CustomValue[newx][newy] = CustomValue[x][y];
5414 GfxFrame[newx][newy] = GfxFrame[x][y];
5415 GfxRandom[newx][newy] = GfxRandom[x][y];
5416 GfxAction[newx][newy] = GfxAction[x][y];
5417 GfxDir[newx][newy] = GfxDir[x][y];
5421 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5423 int direction = MovDir[x][y];
5424 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5425 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5431 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5433 int direction = MovDir[x][y];
5434 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5435 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5437 *comes_from_x = oldx;
5438 *comes_from_y = oldy;
5441 static int MovingOrBlocked2Element(int x, int y)
5443 int element = Tile[x][y];
5445 if (element == EL_BLOCKED)
5449 Blocked2Moving(x, y, &oldx, &oldy);
5451 return Tile[oldx][oldy];
5457 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5459 // like MovingOrBlocked2Element(), but if element is moving
5460 // and (x, y) is the field the moving element is just leaving,
5461 // return EL_BLOCKED instead of the element value
5462 int element = Tile[x][y];
5464 if (IS_MOVING(x, y))
5466 if (element == EL_BLOCKED)
5470 Blocked2Moving(x, y, &oldx, &oldy);
5471 return Tile[oldx][oldy];
5480 static void RemoveField(int x, int y)
5482 Tile[x][y] = EL_EMPTY;
5488 CustomValue[x][y] = 0;
5491 ChangeDelay[x][y] = 0;
5492 ChangePage[x][y] = -1;
5493 Pushed[x][y] = FALSE;
5495 GfxElement[x][y] = EL_UNDEFINED;
5496 GfxAction[x][y] = ACTION_DEFAULT;
5497 GfxDir[x][y] = MV_NONE;
5500 static void RemoveMovingField(int x, int y)
5502 int oldx = x, oldy = y, newx = x, newy = y;
5503 int element = Tile[x][y];
5504 int next_element = EL_UNDEFINED;
5506 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5509 if (IS_MOVING(x, y))
5511 Moving2Blocked(x, y, &newx, &newy);
5513 if (Tile[newx][newy] != EL_BLOCKED)
5515 // element is moving, but target field is not free (blocked), but
5516 // already occupied by something different (example: acid pool);
5517 // in this case, only remove the moving field, but not the target
5519 RemoveField(oldx, oldy);
5521 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5523 TEST_DrawLevelField(oldx, oldy);
5528 else if (element == EL_BLOCKED)
5530 Blocked2Moving(x, y, &oldx, &oldy);
5531 if (!IS_MOVING(oldx, oldy))
5535 if (element == EL_BLOCKED &&
5536 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5537 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5538 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5539 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5540 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5541 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5542 next_element = get_next_element(Tile[oldx][oldy]);
5544 RemoveField(oldx, oldy);
5545 RemoveField(newx, newy);
5547 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5549 if (next_element != EL_UNDEFINED)
5550 Tile[oldx][oldy] = next_element;
5552 TEST_DrawLevelField(oldx, oldy);
5553 TEST_DrawLevelField(newx, newy);
5556 void DrawDynamite(int x, int y)
5558 int sx = SCREENX(x), sy = SCREENY(y);
5559 int graphic = el2img(Tile[x][y]);
5562 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5565 if (IS_WALKABLE_INSIDE(Back[x][y]))
5569 DrawLevelElement(x, y, Back[x][y]);
5570 else if (Store[x][y])
5571 DrawLevelElement(x, y, Store[x][y]);
5572 else if (game.use_masked_elements)
5573 DrawLevelElement(x, y, EL_EMPTY);
5575 frame = getGraphicAnimationFrameXY(graphic, x, y);
5577 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5578 DrawGraphicThruMask(sx, sy, graphic, frame);
5580 DrawGraphic(sx, sy, graphic, frame);
5583 static void CheckDynamite(int x, int y)
5585 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5589 if (MovDelay[x][y] != 0)
5592 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5598 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5603 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5605 boolean num_checked_players = 0;
5608 for (i = 0; i < MAX_PLAYERS; i++)
5610 if (stored_player[i].active)
5612 int sx = stored_player[i].jx;
5613 int sy = stored_player[i].jy;
5615 if (num_checked_players == 0)
5622 *sx1 = MIN(*sx1, sx);
5623 *sy1 = MIN(*sy1, sy);
5624 *sx2 = MAX(*sx2, sx);
5625 *sy2 = MAX(*sy2, sy);
5628 num_checked_players++;
5633 static boolean checkIfAllPlayersFitToScreen_RND(void)
5635 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5637 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5639 return (sx2 - sx1 < SCR_FIELDX &&
5640 sy2 - sy1 < SCR_FIELDY);
5643 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5645 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5647 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5649 *sx = (sx1 + sx2) / 2;
5650 *sy = (sy1 + sy2) / 2;
5653 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5654 boolean center_screen, boolean quick_relocation)
5656 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5657 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5658 boolean no_delay = (tape.warp_forward);
5659 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5660 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5661 int new_scroll_x, new_scroll_y;
5663 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5665 // case 1: quick relocation inside visible screen (without scrolling)
5672 if (!level.shifted_relocation || center_screen)
5674 // relocation _with_ centering of screen
5676 new_scroll_x = SCROLL_POSITION_X(x);
5677 new_scroll_y = SCROLL_POSITION_Y(y);
5681 // relocation _without_ centering of screen
5683 // apply distance between old and new player position to scroll position
5684 int shifted_scroll_x = scroll_x + (x - old_x);
5685 int shifted_scroll_y = scroll_y + (y - old_y);
5687 // make sure that shifted scroll position does not scroll beyond screen
5688 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5689 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5692 if (quick_relocation)
5694 // case 2: quick relocation (redraw without visible scrolling)
5696 scroll_x = new_scroll_x;
5697 scroll_y = new_scroll_y;
5704 // case 3: visible relocation (with scrolling to new position)
5706 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5708 SetVideoFrameDelay(wait_delay_value);
5710 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5712 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5713 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5715 if (dx == 0 && dy == 0) // no scrolling needed at all
5721 // set values for horizontal/vertical screen scrolling (half tile size)
5722 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5723 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5724 int pos_x = dx * TILEX / 2;
5725 int pos_y = dy * TILEY / 2;
5726 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5727 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5729 ScrollLevel(dx, dy);
5732 // scroll in two steps of half tile size to make things smoother
5733 BlitScreenToBitmapExt_RND(window, fx, fy);
5735 // scroll second step to align at full tile size
5736 BlitScreenToBitmap(window);
5742 SetVideoFrameDelay(frame_delay_value_old);
5745 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5747 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5748 int player_nr = GET_PLAYER_NR(el_player);
5749 struct PlayerInfo *player = &stored_player[player_nr];
5750 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5751 boolean no_delay = (tape.warp_forward);
5752 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5753 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5754 int old_jx = player->jx;
5755 int old_jy = player->jy;
5756 int old_element = Tile[old_jx][old_jy];
5757 int element = Tile[jx][jy];
5758 boolean player_relocated = (old_jx != jx || old_jy != jy);
5760 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5761 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5762 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5763 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5764 int leave_side_horiz = move_dir_horiz;
5765 int leave_side_vert = move_dir_vert;
5766 int enter_side = enter_side_horiz | enter_side_vert;
5767 int leave_side = leave_side_horiz | leave_side_vert;
5769 if (player->buried) // do not reanimate dead player
5772 if (!player_relocated) // no need to relocate the player
5775 if (IS_PLAYER(jx, jy)) // player already placed at new position
5777 RemoveField(jx, jy); // temporarily remove newly placed player
5778 DrawLevelField(jx, jy);
5781 if (player->present)
5783 while (player->MovPos)
5785 ScrollPlayer(player, SCROLL_GO_ON);
5786 ScrollScreen(NULL, SCROLL_GO_ON);
5788 AdvanceFrameAndPlayerCounters(player->index_nr);
5792 BackToFront_WithFrameDelay(wait_delay_value);
5795 DrawPlayer(player); // needed here only to cleanup last field
5796 DrawLevelField(player->jx, player->jy); // remove player graphic
5798 player->is_moving = FALSE;
5801 if (IS_CUSTOM_ELEMENT(old_element))
5802 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5804 player->index_bit, leave_side);
5806 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5808 player->index_bit, leave_side);
5810 Tile[jx][jy] = el_player;
5811 InitPlayerField(jx, jy, el_player, TRUE);
5813 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5814 possible that the relocation target field did not contain a player element,
5815 but a walkable element, to which the new player was relocated -- in this
5816 case, restore that (already initialized!) element on the player field */
5817 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5819 Tile[jx][jy] = element; // restore previously existing element
5822 // only visually relocate centered player
5823 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5824 FALSE, level.instant_relocation);
5826 TestIfPlayerTouchesBadThing(jx, jy);
5827 TestIfPlayerTouchesCustomElement(jx, jy);
5829 if (IS_CUSTOM_ELEMENT(element))
5830 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5831 player->index_bit, enter_side);
5833 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5834 player->index_bit, enter_side);
5836 if (player->is_switching)
5838 /* ensure that relocation while still switching an element does not cause
5839 a new element to be treated as also switched directly after relocation
5840 (this is important for teleporter switches that teleport the player to
5841 a place where another teleporter switch is in the same direction, which
5842 would then incorrectly be treated as immediately switched before the
5843 direction key that caused the switch was released) */
5845 player->switch_x += jx - old_jx;
5846 player->switch_y += jy - old_jy;
5850 static void Explode(int ex, int ey, int phase, int mode)
5856 if (game.explosions_delayed)
5858 ExplodeField[ex][ey] = mode;
5862 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5864 int center_element = Tile[ex][ey];
5865 int ce_value = CustomValue[ex][ey];
5866 int ce_score = element_info[center_element].collect_score;
5867 int artwork_element, explosion_element; // set these values later
5869 // remove things displayed in background while burning dynamite
5870 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5873 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5875 // put moving element to center field (and let it explode there)
5876 center_element = MovingOrBlocked2Element(ex, ey);
5877 RemoveMovingField(ex, ey);
5878 Tile[ex][ey] = center_element;
5881 // now "center_element" is finally determined -- set related values now
5882 artwork_element = center_element; // for custom player artwork
5883 explosion_element = center_element; // for custom player artwork
5885 if (IS_PLAYER(ex, ey))
5887 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5889 artwork_element = stored_player[player_nr].artwork_element;
5891 if (level.use_explosion_element[player_nr])
5893 explosion_element = level.explosion_element[player_nr];
5894 artwork_element = explosion_element;
5898 if (mode == EX_TYPE_NORMAL ||
5899 mode == EX_TYPE_CENTER ||
5900 mode == EX_TYPE_CROSS)
5901 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5903 last_phase = element_info[explosion_element].explosion_delay + 1;
5905 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5907 int xx = x - ex + 1;
5908 int yy = y - ey + 1;
5911 if (!IN_LEV_FIELD(x, y) ||
5912 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5913 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5916 element = Tile[x][y];
5918 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5920 element = MovingOrBlocked2Element(x, y);
5922 if (!IS_EXPLOSION_PROOF(element))
5923 RemoveMovingField(x, y);
5926 // indestructible elements can only explode in center (but not flames)
5927 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5928 mode == EX_TYPE_BORDER)) ||
5929 element == EL_FLAMES)
5932 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5933 behaviour, for example when touching a yamyam that explodes to rocks
5934 with active deadly shield, a rock is created under the player !!! */
5935 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5937 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5938 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5939 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5941 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5944 if (IS_ACTIVE_BOMB(element))
5946 // re-activate things under the bomb like gate or penguin
5947 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5954 // save walkable background elements while explosion on same tile
5955 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5956 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5957 Back[x][y] = element;
5959 // ignite explodable elements reached by other explosion
5960 if (element == EL_EXPLOSION)
5961 element = Store2[x][y];
5963 if (AmoebaNr[x][y] &&
5964 (element == EL_AMOEBA_FULL ||
5965 element == EL_BD_AMOEBA ||
5966 element == EL_AMOEBA_GROWING))
5968 AmoebaCnt[AmoebaNr[x][y]]--;
5969 AmoebaCnt2[AmoebaNr[x][y]]--;
5974 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5976 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5978 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5980 if (PLAYERINFO(ex, ey)->use_murphy)
5981 Store[x][y] = EL_EMPTY;
5984 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5985 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5986 else if (IS_PLAYER_ELEMENT(center_element))
5987 Store[x][y] = EL_EMPTY;
5988 else if (center_element == EL_YAMYAM)
5989 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5990 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5991 Store[x][y] = element_info[center_element].content.e[xx][yy];
5993 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5994 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5995 // otherwise) -- FIX THIS !!!
5996 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5997 Store[x][y] = element_info[element].content.e[1][1];
5999 else if (!CAN_EXPLODE(element))
6000 Store[x][y] = element_info[element].content.e[1][1];
6003 Store[x][y] = EL_EMPTY;
6005 if (IS_CUSTOM_ELEMENT(center_element))
6006 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6007 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6008 Store[x][y] >= EL_PREV_CE_8 &&
6009 Store[x][y] <= EL_NEXT_CE_8 ?
6010 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6013 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6014 center_element == EL_AMOEBA_TO_DIAMOND)
6015 Store2[x][y] = element;
6017 Tile[x][y] = EL_EXPLOSION;
6018 GfxElement[x][y] = artwork_element;
6020 ExplodePhase[x][y] = 1;
6021 ExplodeDelay[x][y] = last_phase;
6026 if (center_element == EL_YAMYAM)
6027 game.yamyam_content_nr =
6028 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6040 GfxFrame[x][y] = 0; // restart explosion animation
6042 last_phase = ExplodeDelay[x][y];
6044 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6046 // this can happen if the player leaves an explosion just in time
6047 if (GfxElement[x][y] == EL_UNDEFINED)
6048 GfxElement[x][y] = EL_EMPTY;
6050 border_element = Store2[x][y];
6051 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6052 border_element = StorePlayer[x][y];
6054 if (phase == element_info[border_element].ignition_delay ||
6055 phase == last_phase)
6057 boolean border_explosion = FALSE;
6059 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6060 !PLAYER_EXPLOSION_PROTECTED(x, y))
6062 KillPlayerUnlessExplosionProtected(x, y);
6063 border_explosion = TRUE;
6065 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6067 Tile[x][y] = Store2[x][y];
6070 border_explosion = TRUE;
6072 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6074 AmoebaToDiamond(x, y);
6076 border_explosion = TRUE;
6079 // if an element just explodes due to another explosion (chain-reaction),
6080 // do not immediately end the new explosion when it was the last frame of
6081 // the explosion (as it would be done in the following "if"-statement!)
6082 if (border_explosion && phase == last_phase)
6086 // this can happen if the player was just killed by an explosion
6087 if (GfxElement[x][y] == EL_UNDEFINED)
6088 GfxElement[x][y] = EL_EMPTY;
6090 if (phase == last_phase)
6094 element = Tile[x][y] = Store[x][y];
6095 Store[x][y] = Store2[x][y] = 0;
6096 GfxElement[x][y] = EL_UNDEFINED;
6098 // player can escape from explosions and might therefore be still alive
6099 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6100 element <= EL_PLAYER_IS_EXPLODING_4)
6102 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6103 int explosion_element = EL_PLAYER_1 + player_nr;
6104 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6105 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6107 if (level.use_explosion_element[player_nr])
6108 explosion_element = level.explosion_element[player_nr];
6110 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6111 element_info[explosion_element].content.e[xx][yy]);
6114 // restore probably existing indestructible background element
6115 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6116 element = Tile[x][y] = Back[x][y];
6119 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6120 GfxDir[x][y] = MV_NONE;
6121 ChangeDelay[x][y] = 0;
6122 ChangePage[x][y] = -1;
6124 CustomValue[x][y] = 0;
6126 InitField_WithBug2(x, y, FALSE);
6128 TEST_DrawLevelField(x, y);
6130 TestIfElementTouchesCustomElement(x, y);
6132 if (GFX_CRUMBLED(element))
6133 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6135 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6136 StorePlayer[x][y] = 0;
6138 if (IS_PLAYER_ELEMENT(element))
6139 RelocatePlayer(x, y, element);
6141 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6143 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6144 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6147 TEST_DrawLevelFieldCrumbled(x, y);
6149 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6151 DrawLevelElement(x, y, Back[x][y]);
6152 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6154 else if (IS_WALKABLE_UNDER(Back[x][y]))
6156 DrawLevelGraphic(x, y, graphic, frame);
6157 DrawLevelElementThruMask(x, y, Back[x][y]);
6159 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6160 DrawLevelGraphic(x, y, graphic, frame);
6164 static void DynaExplode(int ex, int ey)
6167 int dynabomb_element = Tile[ex][ey];
6168 int dynabomb_size = 1;
6169 boolean dynabomb_xl = FALSE;
6170 struct PlayerInfo *player;
6171 struct XY *xy = xy_topdown;
6173 if (IS_ACTIVE_BOMB(dynabomb_element))
6175 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6176 dynabomb_size = player->dynabomb_size;
6177 dynabomb_xl = player->dynabomb_xl;
6178 player->dynabombs_left++;
6181 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6183 for (i = 0; i < NUM_DIRECTIONS; i++)
6185 for (j = 1; j <= dynabomb_size; j++)
6187 int x = ex + j * xy[i].x;
6188 int y = ey + j * xy[i].y;
6191 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6194 element = Tile[x][y];
6196 // do not restart explosions of fields with active bombs
6197 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6200 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6202 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6203 !IS_DIGGABLE(element) && !dynabomb_xl)
6209 void Bang(int x, int y)
6211 int element = MovingOrBlocked2Element(x, y);
6212 int explosion_type = EX_TYPE_NORMAL;
6214 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6216 struct PlayerInfo *player = PLAYERINFO(x, y);
6218 element = Tile[x][y] = player->initial_element;
6220 if (level.use_explosion_element[player->index_nr])
6222 int explosion_element = level.explosion_element[player->index_nr];
6224 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6225 explosion_type = EX_TYPE_CROSS;
6226 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6227 explosion_type = EX_TYPE_CENTER;
6235 case EL_BD_BUTTERFLY:
6238 case EL_DARK_YAMYAM:
6242 RaiseScoreElement(element);
6245 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6246 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6247 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6248 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6249 case EL_DYNABOMB_INCREASE_NUMBER:
6250 case EL_DYNABOMB_INCREASE_SIZE:
6251 case EL_DYNABOMB_INCREASE_POWER:
6252 explosion_type = EX_TYPE_DYNA;
6255 case EL_DC_LANDMINE:
6256 explosion_type = EX_TYPE_CENTER;
6261 case EL_LAMP_ACTIVE:
6262 case EL_AMOEBA_TO_DIAMOND:
6263 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6264 explosion_type = EX_TYPE_CENTER;
6268 if (element_info[element].explosion_type == EXPLODES_CROSS)
6269 explosion_type = EX_TYPE_CROSS;
6270 else if (element_info[element].explosion_type == EXPLODES_1X1)
6271 explosion_type = EX_TYPE_CENTER;
6275 if (explosion_type == EX_TYPE_DYNA)
6278 Explode(x, y, EX_PHASE_START, explosion_type);
6280 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6283 static void SplashAcid(int x, int y)
6285 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6286 (!IN_LEV_FIELD(x - 1, y - 2) ||
6287 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6288 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6290 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6291 (!IN_LEV_FIELD(x + 1, y - 2) ||
6292 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6293 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6295 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6298 static void InitBeltMovement(void)
6300 static int belt_base_element[4] =
6302 EL_CONVEYOR_BELT_1_LEFT,
6303 EL_CONVEYOR_BELT_2_LEFT,
6304 EL_CONVEYOR_BELT_3_LEFT,
6305 EL_CONVEYOR_BELT_4_LEFT
6307 static int belt_base_active_element[4] =
6309 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6310 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6311 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6312 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6317 // set frame order for belt animation graphic according to belt direction
6318 for (i = 0; i < NUM_BELTS; i++)
6322 for (j = 0; j < NUM_BELT_PARTS; j++)
6324 int element = belt_base_active_element[belt_nr] + j;
6325 int graphic_1 = el2img(element);
6326 int graphic_2 = el2panelimg(element);
6328 if (game.belt_dir[i] == MV_LEFT)
6330 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6331 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6335 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6336 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6341 SCAN_PLAYFIELD(x, y)
6343 int element = Tile[x][y];
6345 for (i = 0; i < NUM_BELTS; i++)
6347 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6349 int e_belt_nr = getBeltNrFromBeltElement(element);
6352 if (e_belt_nr == belt_nr)
6354 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6356 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6363 static void ToggleBeltSwitch(int x, int y)
6365 static int belt_base_element[4] =
6367 EL_CONVEYOR_BELT_1_LEFT,
6368 EL_CONVEYOR_BELT_2_LEFT,
6369 EL_CONVEYOR_BELT_3_LEFT,
6370 EL_CONVEYOR_BELT_4_LEFT
6372 static int belt_base_active_element[4] =
6374 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6375 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6376 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6377 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6379 static int belt_base_switch_element[4] =
6381 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6382 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6383 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6384 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6386 static int belt_move_dir[4] =
6394 int element = Tile[x][y];
6395 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6396 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6397 int belt_dir = belt_move_dir[belt_dir_nr];
6400 if (!IS_BELT_SWITCH(element))
6403 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6404 game.belt_dir[belt_nr] = belt_dir;
6406 if (belt_dir_nr == 3)
6409 // set frame order for belt animation graphic according to belt direction
6410 for (i = 0; i < NUM_BELT_PARTS; i++)
6412 int element = belt_base_active_element[belt_nr] + i;
6413 int graphic_1 = el2img(element);
6414 int graphic_2 = el2panelimg(element);
6416 if (belt_dir == MV_LEFT)
6418 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6419 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6423 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6424 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6428 SCAN_PLAYFIELD(xx, yy)
6430 int element = Tile[xx][yy];
6432 if (IS_BELT_SWITCH(element))
6434 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6436 if (e_belt_nr == belt_nr)
6438 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6439 TEST_DrawLevelField(xx, yy);
6442 else if (IS_BELT(element) && belt_dir != MV_NONE)
6444 int e_belt_nr = getBeltNrFromBeltElement(element);
6446 if (e_belt_nr == belt_nr)
6448 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6450 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6451 TEST_DrawLevelField(xx, yy);
6454 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6456 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6458 if (e_belt_nr == belt_nr)
6460 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6462 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6463 TEST_DrawLevelField(xx, yy);
6469 static void ToggleSwitchgateSwitch(void)
6473 game.switchgate_pos = !game.switchgate_pos;
6475 SCAN_PLAYFIELD(xx, yy)
6477 int element = Tile[xx][yy];
6479 if (element == EL_SWITCHGATE_SWITCH_UP)
6481 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6482 TEST_DrawLevelField(xx, yy);
6484 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6486 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6487 TEST_DrawLevelField(xx, yy);
6489 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6491 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6492 TEST_DrawLevelField(xx, yy);
6494 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6496 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6497 TEST_DrawLevelField(xx, yy);
6499 else if (element == EL_SWITCHGATE_OPEN ||
6500 element == EL_SWITCHGATE_OPENING)
6502 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6504 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6506 else if (element == EL_SWITCHGATE_CLOSED ||
6507 element == EL_SWITCHGATE_CLOSING)
6509 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6511 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6516 static int getInvisibleActiveFromInvisibleElement(int element)
6518 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6519 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6520 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6524 static int getInvisibleFromInvisibleActiveElement(int element)
6526 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6527 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6528 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6532 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6536 SCAN_PLAYFIELD(x, y)
6538 int element = Tile[x][y];
6540 if (element == EL_LIGHT_SWITCH &&
6541 game.light_time_left > 0)
6543 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6544 TEST_DrawLevelField(x, y);
6546 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6547 game.light_time_left == 0)
6549 Tile[x][y] = EL_LIGHT_SWITCH;
6550 TEST_DrawLevelField(x, y);
6552 else if (element == EL_EMC_DRIPPER &&
6553 game.light_time_left > 0)
6555 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6556 TEST_DrawLevelField(x, y);
6558 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6559 game.light_time_left == 0)
6561 Tile[x][y] = EL_EMC_DRIPPER;
6562 TEST_DrawLevelField(x, y);
6564 else if (element == EL_INVISIBLE_STEELWALL ||
6565 element == EL_INVISIBLE_WALL ||
6566 element == EL_INVISIBLE_SAND)
6568 if (game.light_time_left > 0)
6569 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6571 TEST_DrawLevelField(x, y);
6573 // uncrumble neighbour fields, if needed
6574 if (element == EL_INVISIBLE_SAND)
6575 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6577 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6578 element == EL_INVISIBLE_WALL_ACTIVE ||
6579 element == EL_INVISIBLE_SAND_ACTIVE)
6581 if (game.light_time_left == 0)
6582 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6584 TEST_DrawLevelField(x, y);
6586 // re-crumble neighbour fields, if needed
6587 if (element == EL_INVISIBLE_SAND)
6588 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6593 static void RedrawAllInvisibleElementsForLenses(void)
6597 SCAN_PLAYFIELD(x, y)
6599 int element = Tile[x][y];
6601 if (element == EL_EMC_DRIPPER &&
6602 game.lenses_time_left > 0)
6604 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6605 TEST_DrawLevelField(x, y);
6607 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6608 game.lenses_time_left == 0)
6610 Tile[x][y] = EL_EMC_DRIPPER;
6611 TEST_DrawLevelField(x, y);
6613 else if (element == EL_INVISIBLE_STEELWALL ||
6614 element == EL_INVISIBLE_WALL ||
6615 element == EL_INVISIBLE_SAND)
6617 if (game.lenses_time_left > 0)
6618 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6620 TEST_DrawLevelField(x, y);
6622 // uncrumble neighbour fields, if needed
6623 if (element == EL_INVISIBLE_SAND)
6624 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6626 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6627 element == EL_INVISIBLE_WALL_ACTIVE ||
6628 element == EL_INVISIBLE_SAND_ACTIVE)
6630 if (game.lenses_time_left == 0)
6631 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6633 TEST_DrawLevelField(x, y);
6635 // re-crumble neighbour fields, if needed
6636 if (element == EL_INVISIBLE_SAND)
6637 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6642 static void RedrawAllInvisibleElementsForMagnifier(void)
6646 SCAN_PLAYFIELD(x, y)
6648 int element = Tile[x][y];
6650 if (element == EL_EMC_FAKE_GRASS &&
6651 game.magnify_time_left > 0)
6653 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6654 TEST_DrawLevelField(x, y);
6656 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6657 game.magnify_time_left == 0)
6659 Tile[x][y] = EL_EMC_FAKE_GRASS;
6660 TEST_DrawLevelField(x, y);
6662 else if (IS_GATE_GRAY(element) &&
6663 game.magnify_time_left > 0)
6665 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6666 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6667 IS_EM_GATE_GRAY(element) ?
6668 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6669 IS_EMC_GATE_GRAY(element) ?
6670 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6671 IS_DC_GATE_GRAY(element) ?
6672 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6674 TEST_DrawLevelField(x, y);
6676 else if (IS_GATE_GRAY_ACTIVE(element) &&
6677 game.magnify_time_left == 0)
6679 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6680 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6681 IS_EM_GATE_GRAY_ACTIVE(element) ?
6682 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6683 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6684 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6685 IS_DC_GATE_GRAY_ACTIVE(element) ?
6686 EL_DC_GATE_WHITE_GRAY :
6688 TEST_DrawLevelField(x, y);
6693 static void ToggleLightSwitch(int x, int y)
6695 int element = Tile[x][y];
6697 game.light_time_left =
6698 (element == EL_LIGHT_SWITCH ?
6699 level.time_light * FRAMES_PER_SECOND : 0);
6701 RedrawAllLightSwitchesAndInvisibleElements();
6704 static void ActivateTimegateSwitch(int x, int y)
6708 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6710 SCAN_PLAYFIELD(xx, yy)
6712 int element = Tile[xx][yy];
6714 if (element == EL_TIMEGATE_CLOSED ||
6715 element == EL_TIMEGATE_CLOSING)
6717 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6718 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6722 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6724 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6725 TEST_DrawLevelField(xx, yy);
6731 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6732 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6735 static void Impact(int x, int y)
6737 boolean last_line = (y == lev_fieldy - 1);
6738 boolean object_hit = FALSE;
6739 boolean impact = (last_line || object_hit);
6740 int element = Tile[x][y];
6741 int smashed = EL_STEELWALL;
6743 if (!last_line) // check if element below was hit
6745 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6748 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6749 MovDir[x][y + 1] != MV_DOWN ||
6750 MovPos[x][y + 1] <= TILEY / 2));
6752 // do not smash moving elements that left the smashed field in time
6753 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6754 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6757 #if USE_QUICKSAND_IMPACT_BUGFIX
6758 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6760 RemoveMovingField(x, y + 1);
6761 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6762 Tile[x][y + 2] = EL_ROCK;
6763 TEST_DrawLevelField(x, y + 2);
6768 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6770 RemoveMovingField(x, y + 1);
6771 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6772 Tile[x][y + 2] = EL_ROCK;
6773 TEST_DrawLevelField(x, y + 2);
6780 smashed = MovingOrBlocked2Element(x, y + 1);
6782 impact = (last_line || object_hit);
6785 if (!last_line && smashed == EL_ACID) // element falls into acid
6787 SplashAcid(x, y + 1);
6791 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6792 // only reset graphic animation if graphic really changes after impact
6794 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6796 ResetGfxAnimation(x, y);
6797 TEST_DrawLevelField(x, y);
6800 if (impact && CAN_EXPLODE_IMPACT(element))
6805 else if (impact && element == EL_PEARL &&
6806 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6808 ResetGfxAnimation(x, y);
6810 Tile[x][y] = EL_PEARL_BREAKING;
6811 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6814 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6816 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6821 if (impact && element == EL_AMOEBA_DROP)
6823 if (object_hit && IS_PLAYER(x, y + 1))
6824 KillPlayerUnlessEnemyProtected(x, y + 1);
6825 else if (object_hit && smashed == EL_PENGUIN)
6829 Tile[x][y] = EL_AMOEBA_GROWING;
6830 Store[x][y] = EL_AMOEBA_WET;
6832 ResetRandomAnimationValue(x, y);
6837 if (object_hit) // check which object was hit
6839 if ((CAN_PASS_MAGIC_WALL(element) &&
6840 (smashed == EL_MAGIC_WALL ||
6841 smashed == EL_BD_MAGIC_WALL)) ||
6842 (CAN_PASS_DC_MAGIC_WALL(element) &&
6843 smashed == EL_DC_MAGIC_WALL))
6846 int activated_magic_wall =
6847 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6848 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6849 EL_DC_MAGIC_WALL_ACTIVE);
6851 // activate magic wall / mill
6852 SCAN_PLAYFIELD(xx, yy)
6854 if (Tile[xx][yy] == smashed)
6855 Tile[xx][yy] = activated_magic_wall;
6858 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6859 game.magic_wall_active = TRUE;
6861 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6862 SND_MAGIC_WALL_ACTIVATING :
6863 smashed == EL_BD_MAGIC_WALL ?
6864 SND_BD_MAGIC_WALL_ACTIVATING :
6865 SND_DC_MAGIC_WALL_ACTIVATING));
6868 if (IS_PLAYER(x, y + 1))
6870 if (CAN_SMASH_PLAYER(element))
6872 KillPlayerUnlessEnemyProtected(x, y + 1);
6876 else if (smashed == EL_PENGUIN)
6878 if (CAN_SMASH_PLAYER(element))
6884 else if (element == EL_BD_DIAMOND)
6886 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6892 else if (((element == EL_SP_INFOTRON ||
6893 element == EL_SP_ZONK) &&
6894 (smashed == EL_SP_SNIKSNAK ||
6895 smashed == EL_SP_ELECTRON ||
6896 smashed == EL_SP_DISK_ORANGE)) ||
6897 (element == EL_SP_INFOTRON &&
6898 smashed == EL_SP_DISK_YELLOW))
6903 else if (CAN_SMASH_EVERYTHING(element))
6905 if (IS_CLASSIC_ENEMY(smashed) ||
6906 CAN_EXPLODE_SMASHED(smashed))
6911 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6913 if (smashed == EL_LAMP ||
6914 smashed == EL_LAMP_ACTIVE)
6919 else if (smashed == EL_NUT)
6921 Tile[x][y + 1] = EL_NUT_BREAKING;
6922 PlayLevelSound(x, y, SND_NUT_BREAKING);
6923 RaiseScoreElement(EL_NUT);
6926 else if (smashed == EL_PEARL)
6928 ResetGfxAnimation(x, y);
6930 Tile[x][y + 1] = EL_PEARL_BREAKING;
6931 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6934 else if (smashed == EL_DIAMOND)
6936 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6937 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6940 else if (IS_BELT_SWITCH(smashed))
6942 ToggleBeltSwitch(x, y + 1);
6944 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6945 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6946 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6947 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6949 ToggleSwitchgateSwitch();
6951 else if (smashed == EL_LIGHT_SWITCH ||
6952 smashed == EL_LIGHT_SWITCH_ACTIVE)
6954 ToggleLightSwitch(x, y + 1);
6958 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6960 CheckElementChangeBySide(x, y + 1, smashed, element,
6961 CE_SWITCHED, CH_SIDE_TOP);
6962 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6968 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6973 // play sound of magic wall / mill
6975 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6976 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6977 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6979 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6980 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6981 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6982 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6983 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6984 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6989 // play sound of object that hits the ground
6990 if (last_line || object_hit)
6991 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6994 static void TurnRoundExt(int x, int y)
7006 { 0, 0 }, { 0, 0 }, { 0, 0 },
7011 int left, right, back;
7015 { MV_DOWN, MV_UP, MV_RIGHT },
7016 { MV_UP, MV_DOWN, MV_LEFT },
7018 { MV_LEFT, MV_RIGHT, MV_DOWN },
7022 { MV_RIGHT, MV_LEFT, MV_UP }
7025 int element = Tile[x][y];
7026 int move_pattern = element_info[element].move_pattern;
7028 int old_move_dir = MovDir[x][y];
7029 int left_dir = turn[old_move_dir].left;
7030 int right_dir = turn[old_move_dir].right;
7031 int back_dir = turn[old_move_dir].back;
7033 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7034 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7035 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7036 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7038 int left_x = x + left_dx, left_y = y + left_dy;
7039 int right_x = x + right_dx, right_y = y + right_dy;
7040 int move_x = x + move_dx, move_y = y + move_dy;
7044 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7046 TestIfBadThingTouchesOtherBadThing(x, y);
7048 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7049 MovDir[x][y] = right_dir;
7050 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7051 MovDir[x][y] = left_dir;
7053 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7055 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7058 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7060 TestIfBadThingTouchesOtherBadThing(x, y);
7062 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7063 MovDir[x][y] = left_dir;
7064 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7065 MovDir[x][y] = right_dir;
7067 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7069 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7072 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7074 TestIfBadThingTouchesOtherBadThing(x, y);
7076 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7077 MovDir[x][y] = left_dir;
7078 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7079 MovDir[x][y] = right_dir;
7081 if (MovDir[x][y] != old_move_dir)
7084 else if (element == EL_YAMYAM)
7086 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7087 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7089 if (can_turn_left && can_turn_right)
7090 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7091 else if (can_turn_left)
7092 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7093 else if (can_turn_right)
7094 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7096 MovDir[x][y] = back_dir;
7098 MovDelay[x][y] = 16 + 16 * RND(3);
7100 else if (element == EL_DARK_YAMYAM)
7102 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7104 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7107 if (can_turn_left && can_turn_right)
7108 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7109 else if (can_turn_left)
7110 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7111 else if (can_turn_right)
7112 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7114 MovDir[x][y] = back_dir;
7116 MovDelay[x][y] = 16 + 16 * RND(3);
7118 else if (element == EL_PACMAN)
7120 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7121 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7123 if (can_turn_left && can_turn_right)
7124 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7125 else if (can_turn_left)
7126 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7127 else if (can_turn_right)
7128 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7130 MovDir[x][y] = back_dir;
7132 MovDelay[x][y] = 6 + RND(40);
7134 else if (element == EL_PIG)
7136 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7137 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7138 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7139 boolean should_turn_left, should_turn_right, should_move_on;
7141 int rnd = RND(rnd_value);
7143 should_turn_left = (can_turn_left &&
7145 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7146 y + back_dy + left_dy)));
7147 should_turn_right = (can_turn_right &&
7149 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7150 y + back_dy + right_dy)));
7151 should_move_on = (can_move_on &&
7154 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7155 y + move_dy + left_dy) ||
7156 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7157 y + move_dy + right_dy)));
7159 if (should_turn_left || should_turn_right || should_move_on)
7161 if (should_turn_left && should_turn_right && should_move_on)
7162 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7163 rnd < 2 * rnd_value / 3 ? right_dir :
7165 else if (should_turn_left && should_turn_right)
7166 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7167 else if (should_turn_left && should_move_on)
7168 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7169 else if (should_turn_right && should_move_on)
7170 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7171 else if (should_turn_left)
7172 MovDir[x][y] = left_dir;
7173 else if (should_turn_right)
7174 MovDir[x][y] = right_dir;
7175 else if (should_move_on)
7176 MovDir[x][y] = old_move_dir;
7178 else if (can_move_on && rnd > rnd_value / 8)
7179 MovDir[x][y] = old_move_dir;
7180 else if (can_turn_left && can_turn_right)
7181 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7182 else if (can_turn_left && rnd > rnd_value / 8)
7183 MovDir[x][y] = left_dir;
7184 else if (can_turn_right && rnd > rnd_value/8)
7185 MovDir[x][y] = right_dir;
7187 MovDir[x][y] = back_dir;
7189 xx = x + move_xy[MovDir[x][y]].dx;
7190 yy = y + move_xy[MovDir[x][y]].dy;
7192 if (!IN_LEV_FIELD(xx, yy) ||
7193 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7194 MovDir[x][y] = old_move_dir;
7198 else if (element == EL_DRAGON)
7200 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7201 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7202 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7204 int rnd = RND(rnd_value);
7206 if (can_move_on && rnd > rnd_value / 8)
7207 MovDir[x][y] = old_move_dir;
7208 else if (can_turn_left && can_turn_right)
7209 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7210 else if (can_turn_left && rnd > rnd_value / 8)
7211 MovDir[x][y] = left_dir;
7212 else if (can_turn_right && rnd > rnd_value / 8)
7213 MovDir[x][y] = right_dir;
7215 MovDir[x][y] = back_dir;
7217 xx = x + move_xy[MovDir[x][y]].dx;
7218 yy = y + move_xy[MovDir[x][y]].dy;
7220 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7221 MovDir[x][y] = old_move_dir;
7225 else if (element == EL_MOLE)
7227 boolean can_move_on =
7228 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7229 IS_AMOEBOID(Tile[move_x][move_y]) ||
7230 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7233 boolean can_turn_left =
7234 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7235 IS_AMOEBOID(Tile[left_x][left_y])));
7237 boolean can_turn_right =
7238 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7239 IS_AMOEBOID(Tile[right_x][right_y])));
7241 if (can_turn_left && can_turn_right)
7242 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7243 else if (can_turn_left)
7244 MovDir[x][y] = left_dir;
7246 MovDir[x][y] = right_dir;
7249 if (MovDir[x][y] != old_move_dir)
7252 else if (element == EL_BALLOON)
7254 MovDir[x][y] = game.wind_direction;
7257 else if (element == EL_SPRING)
7259 if (MovDir[x][y] & MV_HORIZONTAL)
7261 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7262 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7264 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7265 ResetGfxAnimation(move_x, move_y);
7266 TEST_DrawLevelField(move_x, move_y);
7268 MovDir[x][y] = back_dir;
7270 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7271 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7272 MovDir[x][y] = MV_NONE;
7277 else if (element == EL_ROBOT ||
7278 element == EL_SATELLITE ||
7279 element == EL_PENGUIN ||
7280 element == EL_EMC_ANDROID)
7282 int attr_x = -1, attr_y = -1;
7284 if (game.all_players_gone)
7286 attr_x = game.exit_x;
7287 attr_y = game.exit_y;
7293 for (i = 0; i < MAX_PLAYERS; i++)
7295 struct PlayerInfo *player = &stored_player[i];
7296 int jx = player->jx, jy = player->jy;
7298 if (!player->active)
7302 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7310 if (element == EL_ROBOT &&
7311 game.robot_wheel_x >= 0 &&
7312 game.robot_wheel_y >= 0 &&
7313 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7314 game.engine_version < VERSION_IDENT(3,1,0,0)))
7316 attr_x = game.robot_wheel_x;
7317 attr_y = game.robot_wheel_y;
7320 if (element == EL_PENGUIN)
7323 struct XY *xy = xy_topdown;
7325 for (i = 0; i < NUM_DIRECTIONS; i++)
7327 int ex = x + xy[i].x;
7328 int ey = y + xy[i].y;
7330 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7331 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7332 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7333 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7342 MovDir[x][y] = MV_NONE;
7344 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7345 else if (attr_x > x)
7346 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7348 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7349 else if (attr_y > y)
7350 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7352 if (element == EL_ROBOT)
7356 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7357 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7358 Moving2Blocked(x, y, &newx, &newy);
7360 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7361 MovDelay[x][y] = 8 + 8 * !RND(3);
7363 MovDelay[x][y] = 16;
7365 else if (element == EL_PENGUIN)
7371 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7373 boolean first_horiz = RND(2);
7374 int new_move_dir = MovDir[x][y];
7377 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7378 Moving2Blocked(x, y, &newx, &newy);
7380 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7384 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7385 Moving2Blocked(x, y, &newx, &newy);
7387 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7390 MovDir[x][y] = old_move_dir;
7394 else if (element == EL_SATELLITE)
7400 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7402 boolean first_horiz = RND(2);
7403 int new_move_dir = MovDir[x][y];
7406 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7407 Moving2Blocked(x, y, &newx, &newy);
7409 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7413 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7414 Moving2Blocked(x, y, &newx, &newy);
7416 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7419 MovDir[x][y] = old_move_dir;
7423 else if (element == EL_EMC_ANDROID)
7425 static int check_pos[16] =
7427 -1, // 0 => (invalid)
7430 -1, // 3 => (invalid)
7432 0, // 5 => MV_LEFT | MV_UP
7433 2, // 6 => MV_RIGHT | MV_UP
7434 -1, // 7 => (invalid)
7436 6, // 9 => MV_LEFT | MV_DOWN
7437 4, // 10 => MV_RIGHT | MV_DOWN
7438 -1, // 11 => (invalid)
7439 -1, // 12 => (invalid)
7440 -1, // 13 => (invalid)
7441 -1, // 14 => (invalid)
7442 -1, // 15 => (invalid)
7450 { -1, -1, MV_LEFT | MV_UP },
7452 { +1, -1, MV_RIGHT | MV_UP },
7453 { +1, 0, MV_RIGHT },
7454 { +1, +1, MV_RIGHT | MV_DOWN },
7456 { -1, +1, MV_LEFT | MV_DOWN },
7459 int start_pos, check_order;
7460 boolean can_clone = FALSE;
7463 // check if there is any free field around current position
7464 for (i = 0; i < 8; i++)
7466 int newx = x + check_xy[i].dx;
7467 int newy = y + check_xy[i].dy;
7469 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7477 if (can_clone) // randomly find an element to clone
7481 start_pos = check_pos[RND(8)];
7482 check_order = (RND(2) ? -1 : +1);
7484 for (i = 0; i < 8; i++)
7486 int pos_raw = start_pos + i * check_order;
7487 int pos = (pos_raw + 8) % 8;
7488 int newx = x + check_xy[pos].dx;
7489 int newy = y + check_xy[pos].dy;
7491 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7493 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7494 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7496 Store[x][y] = Tile[newx][newy];
7505 if (can_clone) // randomly find a direction to move
7509 start_pos = check_pos[RND(8)];
7510 check_order = (RND(2) ? -1 : +1);
7512 for (i = 0; i < 8; i++)
7514 int pos_raw = start_pos + i * check_order;
7515 int pos = (pos_raw + 8) % 8;
7516 int newx = x + check_xy[pos].dx;
7517 int newy = y + check_xy[pos].dy;
7518 int new_move_dir = check_xy[pos].dir;
7520 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7522 MovDir[x][y] = new_move_dir;
7523 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7532 if (can_clone) // cloning and moving successful
7535 // cannot clone -- try to move towards player
7537 start_pos = check_pos[MovDir[x][y] & 0x0f];
7538 check_order = (RND(2) ? -1 : +1);
7540 for (i = 0; i < 3; i++)
7542 // first check start_pos, then previous/next or (next/previous) pos
7543 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7544 int pos = (pos_raw + 8) % 8;
7545 int newx = x + check_xy[pos].dx;
7546 int newy = y + check_xy[pos].dy;
7547 int new_move_dir = check_xy[pos].dir;
7549 if (IS_PLAYER(newx, newy))
7552 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7554 MovDir[x][y] = new_move_dir;
7555 MovDelay[x][y] = level.android_move_time * 8 + 1;
7562 else if (move_pattern == MV_TURNING_LEFT ||
7563 move_pattern == MV_TURNING_RIGHT ||
7564 move_pattern == MV_TURNING_LEFT_RIGHT ||
7565 move_pattern == MV_TURNING_RIGHT_LEFT ||
7566 move_pattern == MV_TURNING_RANDOM ||
7567 move_pattern == MV_ALL_DIRECTIONS)
7569 boolean can_turn_left =
7570 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7571 boolean can_turn_right =
7572 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7574 if (element_info[element].move_stepsize == 0) // "not moving"
7577 if (move_pattern == MV_TURNING_LEFT)
7578 MovDir[x][y] = left_dir;
7579 else if (move_pattern == MV_TURNING_RIGHT)
7580 MovDir[x][y] = right_dir;
7581 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7582 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7583 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7584 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7585 else if (move_pattern == MV_TURNING_RANDOM)
7586 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7587 can_turn_right && !can_turn_left ? right_dir :
7588 RND(2) ? left_dir : right_dir);
7589 else if (can_turn_left && can_turn_right)
7590 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7591 else if (can_turn_left)
7592 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7593 else if (can_turn_right)
7594 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7596 MovDir[x][y] = back_dir;
7598 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7600 else if (move_pattern == MV_HORIZONTAL ||
7601 move_pattern == MV_VERTICAL)
7603 if (move_pattern & old_move_dir)
7604 MovDir[x][y] = back_dir;
7605 else if (move_pattern == MV_HORIZONTAL)
7606 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7607 else if (move_pattern == MV_VERTICAL)
7608 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7610 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7612 else if (move_pattern & MV_ANY_DIRECTION)
7614 MovDir[x][y] = move_pattern;
7615 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7617 else if (move_pattern & MV_WIND_DIRECTION)
7619 MovDir[x][y] = game.wind_direction;
7620 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7622 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7624 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7625 MovDir[x][y] = left_dir;
7626 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7627 MovDir[x][y] = right_dir;
7629 if (MovDir[x][y] != old_move_dir)
7630 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7632 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7634 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7635 MovDir[x][y] = right_dir;
7636 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7637 MovDir[x][y] = left_dir;
7639 if (MovDir[x][y] != old_move_dir)
7640 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7642 else if (move_pattern == MV_TOWARDS_PLAYER ||
7643 move_pattern == MV_AWAY_FROM_PLAYER)
7645 int attr_x = -1, attr_y = -1;
7647 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7649 if (game.all_players_gone)
7651 attr_x = game.exit_x;
7652 attr_y = game.exit_y;
7658 for (i = 0; i < MAX_PLAYERS; i++)
7660 struct PlayerInfo *player = &stored_player[i];
7661 int jx = player->jx, jy = player->jy;
7663 if (!player->active)
7667 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7675 MovDir[x][y] = MV_NONE;
7677 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7678 else if (attr_x > x)
7679 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7681 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7682 else if (attr_y > y)
7683 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7685 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7687 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7689 boolean first_horiz = RND(2);
7690 int new_move_dir = MovDir[x][y];
7692 if (element_info[element].move_stepsize == 0) // "not moving"
7694 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7695 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7701 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7702 Moving2Blocked(x, y, &newx, &newy);
7704 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7708 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7709 Moving2Blocked(x, y, &newx, &newy);
7711 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7714 MovDir[x][y] = old_move_dir;
7717 else if (move_pattern == MV_WHEN_PUSHED ||
7718 move_pattern == MV_WHEN_DROPPED)
7720 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7721 MovDir[x][y] = MV_NONE;
7725 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7727 struct XY *test_xy = xy_topdown;
7728 static int test_dir[4] =
7735 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7736 int move_preference = -1000000; // start with very low preference
7737 int new_move_dir = MV_NONE;
7738 int start_test = RND(4);
7741 for (i = 0; i < NUM_DIRECTIONS; i++)
7743 int j = (start_test + i) % 4;
7744 int move_dir = test_dir[j];
7745 int move_dir_preference;
7747 xx = x + test_xy[j].x;
7748 yy = y + test_xy[j].y;
7750 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7751 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7753 new_move_dir = move_dir;
7758 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7761 move_dir_preference = -1 * RunnerVisit[xx][yy];
7762 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7763 move_dir_preference = PlayerVisit[xx][yy];
7765 if (move_dir_preference > move_preference)
7767 // prefer field that has not been visited for the longest time
7768 move_preference = move_dir_preference;
7769 new_move_dir = move_dir;
7771 else if (move_dir_preference == move_preference &&
7772 move_dir == old_move_dir)
7774 // prefer last direction when all directions are preferred equally
7775 move_preference = move_dir_preference;
7776 new_move_dir = move_dir;
7780 MovDir[x][y] = new_move_dir;
7781 if (old_move_dir != new_move_dir)
7782 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7786 static void TurnRound(int x, int y)
7788 int direction = MovDir[x][y];
7792 GfxDir[x][y] = MovDir[x][y];
7794 if (direction != MovDir[x][y])
7798 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7800 ResetGfxFrame(x, y);
7803 static boolean JustBeingPushed(int x, int y)
7807 for (i = 0; i < MAX_PLAYERS; i++)
7809 struct PlayerInfo *player = &stored_player[i];
7811 if (player->active && player->is_pushing && player->MovPos)
7813 int next_jx = player->jx + (player->jx - player->last_jx);
7814 int next_jy = player->jy + (player->jy - player->last_jy);
7816 if (x == next_jx && y == next_jy)
7824 static void StartMoving(int x, int y)
7826 boolean started_moving = FALSE; // some elements can fall _and_ move
7827 int element = Tile[x][y];
7832 if (MovDelay[x][y] == 0)
7833 GfxAction[x][y] = ACTION_DEFAULT;
7835 if (CAN_FALL(element) && y < lev_fieldy - 1)
7837 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7838 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7839 if (JustBeingPushed(x, y))
7842 if (element == EL_QUICKSAND_FULL)
7844 if (IS_FREE(x, y + 1))
7846 InitMovingField(x, y, MV_DOWN);
7847 started_moving = TRUE;
7849 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7850 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7851 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7852 Store[x][y] = EL_ROCK;
7854 Store[x][y] = EL_ROCK;
7857 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7859 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7861 if (!MovDelay[x][y])
7863 MovDelay[x][y] = TILEY + 1;
7865 ResetGfxAnimation(x, y);
7866 ResetGfxAnimation(x, y + 1);
7871 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7872 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7879 Tile[x][y] = EL_QUICKSAND_EMPTY;
7880 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7881 Store[x][y + 1] = Store[x][y];
7884 PlayLevelSoundAction(x, y, ACTION_FILLING);
7886 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7888 if (!MovDelay[x][y])
7890 MovDelay[x][y] = TILEY + 1;
7892 ResetGfxAnimation(x, y);
7893 ResetGfxAnimation(x, y + 1);
7898 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7899 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7906 Tile[x][y] = EL_QUICKSAND_EMPTY;
7907 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7908 Store[x][y + 1] = Store[x][y];
7911 PlayLevelSoundAction(x, y, ACTION_FILLING);
7914 else if (element == EL_QUICKSAND_FAST_FULL)
7916 if (IS_FREE(x, y + 1))
7918 InitMovingField(x, y, MV_DOWN);
7919 started_moving = TRUE;
7921 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7922 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7923 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7924 Store[x][y] = EL_ROCK;
7926 Store[x][y] = EL_ROCK;
7929 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7931 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7933 if (!MovDelay[x][y])
7935 MovDelay[x][y] = TILEY + 1;
7937 ResetGfxAnimation(x, y);
7938 ResetGfxAnimation(x, y + 1);
7943 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7944 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7951 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7952 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7953 Store[x][y + 1] = Store[x][y];
7956 PlayLevelSoundAction(x, y, ACTION_FILLING);
7958 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7960 if (!MovDelay[x][y])
7962 MovDelay[x][y] = TILEY + 1;
7964 ResetGfxAnimation(x, y);
7965 ResetGfxAnimation(x, y + 1);
7970 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7971 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7978 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7979 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7980 Store[x][y + 1] = Store[x][y];
7983 PlayLevelSoundAction(x, y, ACTION_FILLING);
7986 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7987 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7989 InitMovingField(x, y, MV_DOWN);
7990 started_moving = TRUE;
7992 Tile[x][y] = EL_QUICKSAND_FILLING;
7993 Store[x][y] = element;
7995 PlayLevelSoundAction(x, y, ACTION_FILLING);
7997 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7998 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8000 InitMovingField(x, y, MV_DOWN);
8001 started_moving = TRUE;
8003 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8004 Store[x][y] = element;
8006 PlayLevelSoundAction(x, y, ACTION_FILLING);
8008 else if (element == EL_MAGIC_WALL_FULL)
8010 if (IS_FREE(x, y + 1))
8012 InitMovingField(x, y, MV_DOWN);
8013 started_moving = TRUE;
8015 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8016 Store[x][y] = EL_CHANGED(Store[x][y]);
8018 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8020 if (!MovDelay[x][y])
8021 MovDelay[x][y] = TILEY / 4 + 1;
8030 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8031 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8032 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8036 else if (element == EL_BD_MAGIC_WALL_FULL)
8038 if (IS_FREE(x, y + 1))
8040 InitMovingField(x, y, MV_DOWN);
8041 started_moving = TRUE;
8043 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8044 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8046 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8048 if (!MovDelay[x][y])
8049 MovDelay[x][y] = TILEY / 4 + 1;
8058 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8059 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8060 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8064 else if (element == EL_DC_MAGIC_WALL_FULL)
8066 if (IS_FREE(x, y + 1))
8068 InitMovingField(x, y, MV_DOWN);
8069 started_moving = TRUE;
8071 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8072 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8074 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8076 if (!MovDelay[x][y])
8077 MovDelay[x][y] = TILEY / 4 + 1;
8086 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8087 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8088 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8092 else if ((CAN_PASS_MAGIC_WALL(element) &&
8093 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8094 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8095 (CAN_PASS_DC_MAGIC_WALL(element) &&
8096 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8099 InitMovingField(x, y, MV_DOWN);
8100 started_moving = TRUE;
8103 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8104 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8105 EL_DC_MAGIC_WALL_FILLING);
8106 Store[x][y] = element;
8108 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8110 SplashAcid(x, y + 1);
8112 InitMovingField(x, y, MV_DOWN);
8113 started_moving = TRUE;
8115 Store[x][y] = EL_ACID;
8118 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8119 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8120 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8121 CAN_FALL(element) && WasJustFalling[x][y] &&
8122 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8124 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8125 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8126 (Tile[x][y + 1] == EL_BLOCKED)))
8128 /* this is needed for a special case not covered by calling "Impact()"
8129 from "ContinueMoving()": if an element moves to a tile directly below
8130 another element which was just falling on that tile (which was empty
8131 in the previous frame), the falling element above would just stop
8132 instead of smashing the element below (in previous version, the above
8133 element was just checked for "moving" instead of "falling", resulting
8134 in incorrect smashes caused by horizontal movement of the above
8135 element; also, the case of the player being the element to smash was
8136 simply not covered here... :-/ ) */
8138 CheckCollision[x][y] = 0;
8139 CheckImpact[x][y] = 0;
8143 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8145 if (MovDir[x][y] == MV_NONE)
8147 InitMovingField(x, y, MV_DOWN);
8148 started_moving = TRUE;
8151 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8153 if (WasJustFalling[x][y]) // prevent animation from being restarted
8154 MovDir[x][y] = MV_DOWN;
8156 InitMovingField(x, y, MV_DOWN);
8157 started_moving = TRUE;
8159 else if (element == EL_AMOEBA_DROP)
8161 Tile[x][y] = EL_AMOEBA_GROWING;
8162 Store[x][y] = EL_AMOEBA_WET;
8164 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8165 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8166 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8167 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8169 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8170 (IS_FREE(x - 1, y + 1) ||
8171 Tile[x - 1][y + 1] == EL_ACID));
8172 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8173 (IS_FREE(x + 1, y + 1) ||
8174 Tile[x + 1][y + 1] == EL_ACID));
8175 boolean can_fall_any = (can_fall_left || can_fall_right);
8176 boolean can_fall_both = (can_fall_left && can_fall_right);
8177 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8179 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8181 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8182 can_fall_right = FALSE;
8183 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8184 can_fall_left = FALSE;
8185 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8186 can_fall_right = FALSE;
8187 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8188 can_fall_left = FALSE;
8190 can_fall_any = (can_fall_left || can_fall_right);
8191 can_fall_both = FALSE;
8196 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8197 can_fall_right = FALSE; // slip down on left side
8199 can_fall_left = !(can_fall_right = RND(2));
8201 can_fall_both = FALSE;
8206 // if not determined otherwise, prefer left side for slipping down
8207 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8208 started_moving = TRUE;
8211 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8213 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8214 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8215 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8216 int belt_dir = game.belt_dir[belt_nr];
8218 if ((belt_dir == MV_LEFT && left_is_free) ||
8219 (belt_dir == MV_RIGHT && right_is_free))
8221 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8223 InitMovingField(x, y, belt_dir);
8224 started_moving = TRUE;
8226 Pushed[x][y] = TRUE;
8227 Pushed[nextx][y] = TRUE;
8229 GfxAction[x][y] = ACTION_DEFAULT;
8233 MovDir[x][y] = 0; // if element was moving, stop it
8238 // not "else if" because of elements that can fall and move (EL_SPRING)
8239 if (CAN_MOVE(element) && !started_moving)
8241 int move_pattern = element_info[element].move_pattern;
8244 Moving2Blocked(x, y, &newx, &newy);
8246 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8249 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8250 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8252 WasJustMoving[x][y] = 0;
8253 CheckCollision[x][y] = 0;
8255 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8257 if (Tile[x][y] != element) // element has changed
8261 if (!MovDelay[x][y]) // start new movement phase
8263 // all objects that can change their move direction after each step
8264 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8266 if (element != EL_YAMYAM &&
8267 element != EL_DARK_YAMYAM &&
8268 element != EL_PACMAN &&
8269 !(move_pattern & MV_ANY_DIRECTION) &&
8270 move_pattern != MV_TURNING_LEFT &&
8271 move_pattern != MV_TURNING_RIGHT &&
8272 move_pattern != MV_TURNING_LEFT_RIGHT &&
8273 move_pattern != MV_TURNING_RIGHT_LEFT &&
8274 move_pattern != MV_TURNING_RANDOM)
8278 if (MovDelay[x][y] && (element == EL_BUG ||
8279 element == EL_SPACESHIP ||
8280 element == EL_SP_SNIKSNAK ||
8281 element == EL_SP_ELECTRON ||
8282 element == EL_MOLE))
8283 TEST_DrawLevelField(x, y);
8287 if (MovDelay[x][y]) // wait some time before next movement
8291 if (element == EL_ROBOT ||
8292 element == EL_YAMYAM ||
8293 element == EL_DARK_YAMYAM)
8295 DrawLevelElementAnimationIfNeeded(x, y, element);
8296 PlayLevelSoundAction(x, y, ACTION_WAITING);
8298 else if (element == EL_SP_ELECTRON)
8299 DrawLevelElementAnimationIfNeeded(x, y, element);
8300 else if (element == EL_DRAGON)
8303 int dir = MovDir[x][y];
8304 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8305 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8306 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8307 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8308 dir == MV_UP ? IMG_FLAMES_1_UP :
8309 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8310 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8312 GfxAction[x][y] = ACTION_ATTACKING;
8314 if (IS_PLAYER(x, y))
8315 DrawPlayerField(x, y);
8317 TEST_DrawLevelField(x, y);
8319 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8321 for (i = 1; i <= 3; i++)
8323 int xx = x + i * dx;
8324 int yy = y + i * dy;
8325 int sx = SCREENX(xx);
8326 int sy = SCREENY(yy);
8327 int flame_graphic = graphic + (i - 1);
8329 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8334 int flamed = MovingOrBlocked2Element(xx, yy);
8336 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8339 RemoveMovingField(xx, yy);
8341 ChangeDelay[xx][yy] = 0;
8343 Tile[xx][yy] = EL_FLAMES;
8345 if (IN_SCR_FIELD(sx, sy))
8347 TEST_DrawLevelFieldCrumbled(xx, yy);
8348 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8353 if (Tile[xx][yy] == EL_FLAMES)
8354 Tile[xx][yy] = EL_EMPTY;
8355 TEST_DrawLevelField(xx, yy);
8360 if (MovDelay[x][y]) // element still has to wait some time
8362 PlayLevelSoundAction(x, y, ACTION_WAITING);
8368 // now make next step
8370 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8372 if (DONT_COLLIDE_WITH(element) &&
8373 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8374 !PLAYER_ENEMY_PROTECTED(newx, newy))
8376 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8381 else if (CAN_MOVE_INTO_ACID(element) &&
8382 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8383 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8384 (MovDir[x][y] == MV_DOWN ||
8385 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8387 SplashAcid(newx, newy);
8388 Store[x][y] = EL_ACID;
8390 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8392 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8393 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8394 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8395 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8398 TEST_DrawLevelField(x, y);
8400 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8401 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8402 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8404 game.friends_still_needed--;
8405 if (!game.friends_still_needed &&
8407 game.all_players_gone)
8412 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8414 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8415 TEST_DrawLevelField(newx, newy);
8417 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8419 else if (!IS_FREE(newx, newy))
8421 GfxAction[x][y] = ACTION_WAITING;
8423 if (IS_PLAYER(x, y))
8424 DrawPlayerField(x, y);
8426 TEST_DrawLevelField(x, y);
8431 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8433 if (IS_FOOD_PIG(Tile[newx][newy]))
8435 if (IS_MOVING(newx, newy))
8436 RemoveMovingField(newx, newy);
8439 Tile[newx][newy] = EL_EMPTY;
8440 TEST_DrawLevelField(newx, newy);
8443 PlayLevelSound(x, y, SND_PIG_DIGGING);
8445 else if (!IS_FREE(newx, newy))
8447 if (IS_PLAYER(x, y))
8448 DrawPlayerField(x, y);
8450 TEST_DrawLevelField(x, y);
8455 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8457 if (Store[x][y] != EL_EMPTY)
8459 boolean can_clone = FALSE;
8462 // check if element to clone is still there
8463 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8465 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8473 // cannot clone or target field not free anymore -- do not clone
8474 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8475 Store[x][y] = EL_EMPTY;
8478 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8480 if (IS_MV_DIAGONAL(MovDir[x][y]))
8482 int diagonal_move_dir = MovDir[x][y];
8483 int stored = Store[x][y];
8484 int change_delay = 8;
8487 // android is moving diagonally
8489 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8491 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8492 GfxElement[x][y] = EL_EMC_ANDROID;
8493 GfxAction[x][y] = ACTION_SHRINKING;
8494 GfxDir[x][y] = diagonal_move_dir;
8495 ChangeDelay[x][y] = change_delay;
8497 if (Store[x][y] == EL_EMPTY)
8498 Store[x][y] = GfxElementEmpty[x][y];
8500 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8503 DrawLevelGraphicAnimation(x, y, graphic);
8504 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8506 if (Tile[newx][newy] == EL_ACID)
8508 SplashAcid(newx, newy);
8513 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8515 Store[newx][newy] = EL_EMC_ANDROID;
8516 GfxElement[newx][newy] = EL_EMC_ANDROID;
8517 GfxAction[newx][newy] = ACTION_GROWING;
8518 GfxDir[newx][newy] = diagonal_move_dir;
8519 ChangeDelay[newx][newy] = change_delay;
8521 graphic = el_act_dir2img(GfxElement[newx][newy],
8522 GfxAction[newx][newy], GfxDir[newx][newy]);
8524 DrawLevelGraphicAnimation(newx, newy, graphic);
8525 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8531 Tile[newx][newy] = EL_EMPTY;
8532 TEST_DrawLevelField(newx, newy);
8534 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8537 else if (!IS_FREE(newx, newy))
8542 else if (IS_CUSTOM_ELEMENT(element) &&
8543 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8545 if (!DigFieldByCE(newx, newy, element))
8548 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8550 RunnerVisit[x][y] = FrameCounter;
8551 PlayerVisit[x][y] /= 8; // expire player visit path
8554 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8556 if (!IS_FREE(newx, newy))
8558 if (IS_PLAYER(x, y))
8559 DrawPlayerField(x, y);
8561 TEST_DrawLevelField(x, y);
8567 boolean wanna_flame = !RND(10);
8568 int dx = newx - x, dy = newy - y;
8569 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8570 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8571 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8572 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8573 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8574 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8577 IS_CLASSIC_ENEMY(element1) ||
8578 IS_CLASSIC_ENEMY(element2)) &&
8579 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8580 element1 != EL_FLAMES && element2 != EL_FLAMES)
8582 ResetGfxAnimation(x, y);
8583 GfxAction[x][y] = ACTION_ATTACKING;
8585 if (IS_PLAYER(x, y))
8586 DrawPlayerField(x, y);
8588 TEST_DrawLevelField(x, y);
8590 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8592 MovDelay[x][y] = 50;
8594 Tile[newx][newy] = EL_FLAMES;
8595 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8596 Tile[newx1][newy1] = EL_FLAMES;
8597 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8598 Tile[newx2][newy2] = EL_FLAMES;
8604 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8605 Tile[newx][newy] == EL_DIAMOND)
8607 if (IS_MOVING(newx, newy))
8608 RemoveMovingField(newx, newy);
8611 Tile[newx][newy] = EL_EMPTY;
8612 TEST_DrawLevelField(newx, newy);
8615 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8617 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8618 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8620 if (AmoebaNr[newx][newy])
8622 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8623 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8624 Tile[newx][newy] == EL_BD_AMOEBA)
8625 AmoebaCnt[AmoebaNr[newx][newy]]--;
8628 if (IS_MOVING(newx, newy))
8630 RemoveMovingField(newx, newy);
8634 Tile[newx][newy] = EL_EMPTY;
8635 TEST_DrawLevelField(newx, newy);
8638 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8640 else if ((element == EL_PACMAN || element == EL_MOLE)
8641 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8643 if (AmoebaNr[newx][newy])
8645 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8646 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8647 Tile[newx][newy] == EL_BD_AMOEBA)
8648 AmoebaCnt[AmoebaNr[newx][newy]]--;
8651 if (element == EL_MOLE)
8653 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8654 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8656 ResetGfxAnimation(x, y);
8657 GfxAction[x][y] = ACTION_DIGGING;
8658 TEST_DrawLevelField(x, y);
8660 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8662 return; // wait for shrinking amoeba
8664 else // element == EL_PACMAN
8666 Tile[newx][newy] = EL_EMPTY;
8667 TEST_DrawLevelField(newx, newy);
8668 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8671 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8672 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8673 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8675 // wait for shrinking amoeba to completely disappear
8678 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8680 // object was running against a wall
8684 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8685 DrawLevelElementAnimation(x, y, element);
8687 if (DONT_TOUCH(element))
8688 TestIfBadThingTouchesPlayer(x, y);
8693 InitMovingField(x, y, MovDir[x][y]);
8695 PlayLevelSoundAction(x, y, ACTION_MOVING);
8699 ContinueMoving(x, y);
8702 void ContinueMoving(int x, int y)
8704 int element = Tile[x][y];
8705 struct ElementInfo *ei = &element_info[element];
8706 int direction = MovDir[x][y];
8707 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8708 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8709 int newx = x + dx, newy = y + dy;
8710 int stored = Store[x][y];
8711 int stored_new = Store[newx][newy];
8712 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8713 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8714 boolean last_line = (newy == lev_fieldy - 1);
8715 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8717 if (pushed_by_player) // special case: moving object pushed by player
8719 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8721 else if (use_step_delay) // special case: moving object has step delay
8723 if (!MovDelay[x][y])
8724 MovPos[x][y] += getElementMoveStepsize(x, y);
8729 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8733 TEST_DrawLevelField(x, y);
8735 return; // element is still waiting
8738 else // normal case: generically moving object
8740 MovPos[x][y] += getElementMoveStepsize(x, y);
8743 if (ABS(MovPos[x][y]) < TILEX)
8745 TEST_DrawLevelField(x, y);
8747 return; // element is still moving
8750 // element reached destination field
8752 Tile[x][y] = EL_EMPTY;
8753 Tile[newx][newy] = element;
8754 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8756 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8758 element = Tile[newx][newy] = EL_ACID;
8760 else if (element == EL_MOLE)
8762 Tile[x][y] = EL_SAND;
8764 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8766 else if (element == EL_QUICKSAND_FILLING)
8768 element = Tile[newx][newy] = get_next_element(element);
8769 Store[newx][newy] = Store[x][y];
8771 else if (element == EL_QUICKSAND_EMPTYING)
8773 Tile[x][y] = get_next_element(element);
8774 element = Tile[newx][newy] = Store[x][y];
8776 else if (element == EL_QUICKSAND_FAST_FILLING)
8778 element = Tile[newx][newy] = get_next_element(element);
8779 Store[newx][newy] = Store[x][y];
8781 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8783 Tile[x][y] = get_next_element(element);
8784 element = Tile[newx][newy] = Store[x][y];
8786 else if (element == EL_MAGIC_WALL_FILLING)
8788 element = Tile[newx][newy] = get_next_element(element);
8789 if (!game.magic_wall_active)
8790 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8791 Store[newx][newy] = Store[x][y];
8793 else if (element == EL_MAGIC_WALL_EMPTYING)
8795 Tile[x][y] = get_next_element(element);
8796 if (!game.magic_wall_active)
8797 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8798 element = Tile[newx][newy] = Store[x][y];
8800 InitField(newx, newy, FALSE);
8802 else if (element == EL_BD_MAGIC_WALL_FILLING)
8804 element = Tile[newx][newy] = get_next_element(element);
8805 if (!game.magic_wall_active)
8806 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8807 Store[newx][newy] = Store[x][y];
8809 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8811 Tile[x][y] = get_next_element(element);
8812 if (!game.magic_wall_active)
8813 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8814 element = Tile[newx][newy] = Store[x][y];
8816 InitField(newx, newy, FALSE);
8818 else if (element == EL_DC_MAGIC_WALL_FILLING)
8820 element = Tile[newx][newy] = get_next_element(element);
8821 if (!game.magic_wall_active)
8822 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8823 Store[newx][newy] = Store[x][y];
8825 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8827 Tile[x][y] = get_next_element(element);
8828 if (!game.magic_wall_active)
8829 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8830 element = Tile[newx][newy] = Store[x][y];
8832 InitField(newx, newy, FALSE);
8834 else if (element == EL_AMOEBA_DROPPING)
8836 Tile[x][y] = get_next_element(element);
8837 element = Tile[newx][newy] = Store[x][y];
8839 else if (element == EL_SOKOBAN_OBJECT)
8842 Tile[x][y] = Back[x][y];
8844 if (Back[newx][newy])
8845 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8847 Back[x][y] = Back[newx][newy] = 0;
8850 Store[x][y] = EL_EMPTY;
8855 MovDelay[newx][newy] = 0;
8857 if (CAN_CHANGE_OR_HAS_ACTION(element))
8859 // copy element change control values to new field
8860 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8861 ChangePage[newx][newy] = ChangePage[x][y];
8862 ChangeCount[newx][newy] = ChangeCount[x][y];
8863 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8866 CustomValue[newx][newy] = CustomValue[x][y];
8868 ChangeDelay[x][y] = 0;
8869 ChangePage[x][y] = -1;
8870 ChangeCount[x][y] = 0;
8871 ChangeEvent[x][y] = -1;
8873 CustomValue[x][y] = 0;
8875 // copy animation control values to new field
8876 GfxFrame[newx][newy] = GfxFrame[x][y];
8877 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8878 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8879 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8881 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8883 // some elements can leave other elements behind after moving
8884 if (ei->move_leave_element != EL_EMPTY &&
8885 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8886 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8888 int move_leave_element = ei->move_leave_element;
8890 // this makes it possible to leave the removed element again
8891 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8892 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8894 Tile[x][y] = move_leave_element;
8896 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8897 MovDir[x][y] = direction;
8899 InitField(x, y, FALSE);
8901 if (GFX_CRUMBLED(Tile[x][y]))
8902 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8904 if (IS_PLAYER_ELEMENT(move_leave_element))
8905 RelocatePlayer(x, y, move_leave_element);
8908 // do this after checking for left-behind element
8909 ResetGfxAnimation(x, y); // reset animation values for old field
8911 if (!CAN_MOVE(element) ||
8912 (CAN_FALL(element) && direction == MV_DOWN &&
8913 (element == EL_SPRING ||
8914 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8915 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8916 GfxDir[x][y] = MovDir[newx][newy] = 0;
8918 TEST_DrawLevelField(x, y);
8919 TEST_DrawLevelField(newx, newy);
8921 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8923 // prevent pushed element from moving on in pushed direction
8924 if (pushed_by_player && CAN_MOVE(element) &&
8925 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8926 !(element_info[element].move_pattern & direction))
8927 TurnRound(newx, newy);
8929 // prevent elements on conveyor belt from moving on in last direction
8930 if (pushed_by_conveyor && CAN_FALL(element) &&
8931 direction & MV_HORIZONTAL)
8932 MovDir[newx][newy] = 0;
8934 if (!pushed_by_player)
8936 int nextx = newx + dx, nexty = newy + dy;
8937 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8939 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8941 if (CAN_FALL(element) && direction == MV_DOWN)
8942 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8944 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8945 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8947 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8948 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8951 if (DONT_TOUCH(element)) // object may be nasty to player or others
8953 TestIfBadThingTouchesPlayer(newx, newy);
8954 TestIfBadThingTouchesFriend(newx, newy);
8956 if (!IS_CUSTOM_ELEMENT(element))
8957 TestIfBadThingTouchesOtherBadThing(newx, newy);
8959 else if (element == EL_PENGUIN)
8960 TestIfFriendTouchesBadThing(newx, newy);
8962 if (DONT_GET_HIT_BY(element))
8964 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8967 // give the player one last chance (one more frame) to move away
8968 if (CAN_FALL(element) && direction == MV_DOWN &&
8969 (last_line || (!IS_FREE(x, newy + 1) &&
8970 (!IS_PLAYER(x, newy + 1) ||
8971 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8974 if (pushed_by_player && !game.use_change_when_pushing_bug)
8976 int push_side = MV_DIR_OPPOSITE(direction);
8977 struct PlayerInfo *player = PLAYERINFO(x, y);
8979 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8980 player->index_bit, push_side);
8981 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
8982 player->index_bit, push_side);
8985 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8986 MovDelay[newx][newy] = 1;
8988 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8990 TestIfElementTouchesCustomElement(x, y); // empty or new element
8991 TestIfElementHitsCustomElement(newx, newy, direction);
8992 TestIfPlayerTouchesCustomElement(newx, newy);
8993 TestIfElementTouchesCustomElement(newx, newy);
8995 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8996 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8997 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8998 MV_DIR_OPPOSITE(direction));
9001 int AmoebaNeighbourNr(int ax, int ay)
9004 int element = Tile[ax][ay];
9006 struct XY *xy = xy_topdown;
9008 for (i = 0; i < NUM_DIRECTIONS; i++)
9010 int x = ax + xy[i].x;
9011 int y = ay + xy[i].y;
9013 if (!IN_LEV_FIELD(x, y))
9016 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9017 group_nr = AmoebaNr[x][y];
9023 static void AmoebaMerge(int ax, int ay)
9025 int i, x, y, xx, yy;
9026 int new_group_nr = AmoebaNr[ax][ay];
9027 struct XY *xy = xy_topdown;
9029 if (new_group_nr == 0)
9032 for (i = 0; i < NUM_DIRECTIONS; i++)
9037 if (!IN_LEV_FIELD(x, y))
9040 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9041 Tile[x][y] == EL_BD_AMOEBA ||
9042 Tile[x][y] == EL_AMOEBA_DEAD) &&
9043 AmoebaNr[x][y] != new_group_nr)
9045 int old_group_nr = AmoebaNr[x][y];
9047 if (old_group_nr == 0)
9050 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9051 AmoebaCnt[old_group_nr] = 0;
9052 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9053 AmoebaCnt2[old_group_nr] = 0;
9055 SCAN_PLAYFIELD(xx, yy)
9057 if (AmoebaNr[xx][yy] == old_group_nr)
9058 AmoebaNr[xx][yy] = new_group_nr;
9064 void AmoebaToDiamond(int ax, int ay)
9068 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9070 int group_nr = AmoebaNr[ax][ay];
9075 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9076 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9082 SCAN_PLAYFIELD(x, y)
9084 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9087 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9091 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9092 SND_AMOEBA_TURNING_TO_GEM :
9093 SND_AMOEBA_TURNING_TO_ROCK));
9098 struct XY *xy = xy_topdown;
9100 for (i = 0; i < NUM_DIRECTIONS; i++)
9105 if (!IN_LEV_FIELD(x, y))
9108 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9110 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9111 SND_AMOEBA_TURNING_TO_GEM :
9112 SND_AMOEBA_TURNING_TO_ROCK));
9119 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9122 int group_nr = AmoebaNr[ax][ay];
9123 boolean done = FALSE;
9128 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9129 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9135 SCAN_PLAYFIELD(x, y)
9137 if (AmoebaNr[x][y] == group_nr &&
9138 (Tile[x][y] == EL_AMOEBA_DEAD ||
9139 Tile[x][y] == EL_BD_AMOEBA ||
9140 Tile[x][y] == EL_AMOEBA_GROWING))
9143 Tile[x][y] = new_element;
9144 InitField(x, y, FALSE);
9145 TEST_DrawLevelField(x, y);
9151 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9152 SND_BD_AMOEBA_TURNING_TO_ROCK :
9153 SND_BD_AMOEBA_TURNING_TO_GEM));
9156 static void AmoebaGrowing(int x, int y)
9158 static DelayCounter sound_delay = { 0 };
9160 if (!MovDelay[x][y]) // start new growing cycle
9164 if (DelayReached(&sound_delay))
9166 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9167 sound_delay.value = 30;
9171 if (MovDelay[x][y]) // wait some time before growing bigger
9174 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9176 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9177 6 - MovDelay[x][y]);
9179 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9182 if (!MovDelay[x][y])
9184 Tile[x][y] = Store[x][y];
9186 TEST_DrawLevelField(x, y);
9191 static void AmoebaShrinking(int x, int y)
9193 static DelayCounter sound_delay = { 0 };
9195 if (!MovDelay[x][y]) // start new shrinking cycle
9199 if (DelayReached(&sound_delay))
9200 sound_delay.value = 30;
9203 if (MovDelay[x][y]) // wait some time before shrinking
9206 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9208 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9209 6 - MovDelay[x][y]);
9211 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9214 if (!MovDelay[x][y])
9216 Tile[x][y] = EL_EMPTY;
9217 TEST_DrawLevelField(x, y);
9219 // don't let mole enter this field in this cycle;
9220 // (give priority to objects falling to this field from above)
9226 static void AmoebaReproduce(int ax, int ay)
9229 int element = Tile[ax][ay];
9230 int graphic = el2img(element);
9231 int newax = ax, neway = ay;
9232 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9233 struct XY *xy = xy_topdown;
9235 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9237 Tile[ax][ay] = EL_AMOEBA_DEAD;
9238 TEST_DrawLevelField(ax, ay);
9242 if (IS_ANIMATED(graphic))
9243 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9245 if (!MovDelay[ax][ay]) // start making new amoeba field
9246 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9248 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9251 if (MovDelay[ax][ay])
9255 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9258 int x = ax + xy[start].x;
9259 int y = ay + xy[start].y;
9261 if (!IN_LEV_FIELD(x, y))
9264 if (IS_FREE(x, y) ||
9265 CAN_GROW_INTO(Tile[x][y]) ||
9266 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9267 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9273 if (newax == ax && neway == ay)
9276 else // normal or "filled" (BD style) amoeba
9279 boolean waiting_for_player = FALSE;
9281 for (i = 0; i < NUM_DIRECTIONS; i++)
9283 int j = (start + i) % 4;
9284 int x = ax + xy[j].x;
9285 int y = ay + xy[j].y;
9287 if (!IN_LEV_FIELD(x, y))
9290 if (IS_FREE(x, y) ||
9291 CAN_GROW_INTO(Tile[x][y]) ||
9292 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9293 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9299 else if (IS_PLAYER(x, y))
9300 waiting_for_player = TRUE;
9303 if (newax == ax && neway == ay) // amoeba cannot grow
9305 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9307 Tile[ax][ay] = EL_AMOEBA_DEAD;
9308 TEST_DrawLevelField(ax, ay);
9309 AmoebaCnt[AmoebaNr[ax][ay]]--;
9311 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9313 if (element == EL_AMOEBA_FULL)
9314 AmoebaToDiamond(ax, ay);
9315 else if (element == EL_BD_AMOEBA)
9316 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9321 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9323 // amoeba gets larger by growing in some direction
9325 int new_group_nr = AmoebaNr[ax][ay];
9328 if (new_group_nr == 0)
9330 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9332 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9338 AmoebaNr[newax][neway] = new_group_nr;
9339 AmoebaCnt[new_group_nr]++;
9340 AmoebaCnt2[new_group_nr]++;
9342 // if amoeba touches other amoeba(s) after growing, unify them
9343 AmoebaMerge(newax, neway);
9345 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9347 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9353 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9354 (neway == lev_fieldy - 1 && newax != ax))
9356 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9357 Store[newax][neway] = element;
9359 else if (neway == ay || element == EL_EMC_DRIPPER)
9361 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9363 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9367 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9368 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9369 Store[ax][ay] = EL_AMOEBA_DROP;
9370 ContinueMoving(ax, ay);
9374 TEST_DrawLevelField(newax, neway);
9377 static void Life(int ax, int ay)
9381 int element = Tile[ax][ay];
9382 int graphic = el2img(element);
9383 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9385 boolean changed = FALSE;
9387 if (IS_ANIMATED(graphic))
9388 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9393 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9394 MovDelay[ax][ay] = life_time;
9396 if (MovDelay[ax][ay]) // wait some time before next cycle
9399 if (MovDelay[ax][ay])
9403 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9405 int xx = ax+x1, yy = ay+y1;
9406 int old_element = Tile[xx][yy];
9407 int num_neighbours = 0;
9409 if (!IN_LEV_FIELD(xx, yy))
9412 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9414 int x = xx+x2, y = yy+y2;
9416 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9419 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9420 boolean is_neighbour = FALSE;
9422 if (level.use_life_bugs)
9424 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9425 (IS_FREE(x, y) && Stop[x][y]));
9428 (Last[x][y] == element || is_player_cell);
9434 boolean is_free = FALSE;
9436 if (level.use_life_bugs)
9437 is_free = (IS_FREE(xx, yy));
9439 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9441 if (xx == ax && yy == ay) // field in the middle
9443 if (num_neighbours < life_parameter[0] ||
9444 num_neighbours > life_parameter[1])
9446 Tile[xx][yy] = EL_EMPTY;
9447 if (Tile[xx][yy] != old_element)
9448 TEST_DrawLevelField(xx, yy);
9449 Stop[xx][yy] = TRUE;
9453 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9454 { // free border field
9455 if (num_neighbours >= life_parameter[2] &&
9456 num_neighbours <= life_parameter[3])
9458 Tile[xx][yy] = element;
9459 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9460 if (Tile[xx][yy] != old_element)
9461 TEST_DrawLevelField(xx, yy);
9462 Stop[xx][yy] = TRUE;
9469 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9470 SND_GAME_OF_LIFE_GROWING);
9473 static void InitRobotWheel(int x, int y)
9475 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9478 static void RunRobotWheel(int x, int y)
9480 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9483 static void StopRobotWheel(int x, int y)
9485 if (game.robot_wheel_x == x &&
9486 game.robot_wheel_y == y)
9488 game.robot_wheel_x = -1;
9489 game.robot_wheel_y = -1;
9490 game.robot_wheel_active = FALSE;
9494 static void InitTimegateWheel(int x, int y)
9496 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9499 static void RunTimegateWheel(int x, int y)
9501 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9504 static void InitMagicBallDelay(int x, int y)
9506 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9509 static void ActivateMagicBall(int bx, int by)
9513 if (level.ball_random)
9515 int pos_border = RND(8); // select one of the eight border elements
9516 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9517 int xx = pos_content % 3;
9518 int yy = pos_content / 3;
9523 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9524 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9528 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9530 int xx = x - bx + 1;
9531 int yy = y - by + 1;
9533 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9534 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9538 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9541 static void CheckExit(int x, int y)
9543 if (game.gems_still_needed > 0 ||
9544 game.sokoban_fields_still_needed > 0 ||
9545 game.sokoban_objects_still_needed > 0 ||
9546 game.lights_still_needed > 0)
9548 int element = Tile[x][y];
9549 int graphic = el2img(element);
9551 if (IS_ANIMATED(graphic))
9552 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9557 // do not re-open exit door closed after last player
9558 if (game.all_players_gone)
9561 Tile[x][y] = EL_EXIT_OPENING;
9563 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9566 static void CheckExitEM(int x, int y)
9568 if (game.gems_still_needed > 0 ||
9569 game.sokoban_fields_still_needed > 0 ||
9570 game.sokoban_objects_still_needed > 0 ||
9571 game.lights_still_needed > 0)
9573 int element = Tile[x][y];
9574 int graphic = el2img(element);
9576 if (IS_ANIMATED(graphic))
9577 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9582 // do not re-open exit door closed after last player
9583 if (game.all_players_gone)
9586 Tile[x][y] = EL_EM_EXIT_OPENING;
9588 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9591 static void CheckExitSteel(int x, int y)
9593 if (game.gems_still_needed > 0 ||
9594 game.sokoban_fields_still_needed > 0 ||
9595 game.sokoban_objects_still_needed > 0 ||
9596 game.lights_still_needed > 0)
9598 int element = Tile[x][y];
9599 int graphic = el2img(element);
9601 if (IS_ANIMATED(graphic))
9602 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9607 // do not re-open exit door closed after last player
9608 if (game.all_players_gone)
9611 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9613 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9616 static void CheckExitSteelEM(int x, int y)
9618 if (game.gems_still_needed > 0 ||
9619 game.sokoban_fields_still_needed > 0 ||
9620 game.sokoban_objects_still_needed > 0 ||
9621 game.lights_still_needed > 0)
9623 int element = Tile[x][y];
9624 int graphic = el2img(element);
9626 if (IS_ANIMATED(graphic))
9627 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9632 // do not re-open exit door closed after last player
9633 if (game.all_players_gone)
9636 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9638 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9641 static void CheckExitSP(int x, int y)
9643 if (game.gems_still_needed > 0)
9645 int element = Tile[x][y];
9646 int graphic = el2img(element);
9648 if (IS_ANIMATED(graphic))
9649 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9654 // do not re-open exit door closed after last player
9655 if (game.all_players_gone)
9658 Tile[x][y] = EL_SP_EXIT_OPENING;
9660 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9663 static void CloseAllOpenTimegates(void)
9667 SCAN_PLAYFIELD(x, y)
9669 int element = Tile[x][y];
9671 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9673 Tile[x][y] = EL_TIMEGATE_CLOSING;
9675 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9680 static void DrawTwinkleOnField(int x, int y)
9682 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9685 if (Tile[x][y] == EL_BD_DIAMOND)
9688 if (MovDelay[x][y] == 0) // next animation frame
9689 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9691 if (MovDelay[x][y] != 0) // wait some time before next frame
9695 DrawLevelElementAnimation(x, y, Tile[x][y]);
9697 if (MovDelay[x][y] != 0)
9699 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9700 10 - MovDelay[x][y]);
9702 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9707 static void WallGrowing(int x, int y)
9711 if (!MovDelay[x][y]) // next animation frame
9712 MovDelay[x][y] = 3 * delay;
9714 if (MovDelay[x][y]) // wait some time before next frame
9718 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9720 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9721 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9723 DrawLevelGraphic(x, y, graphic, frame);
9726 if (!MovDelay[x][y])
9728 if (MovDir[x][y] == MV_LEFT)
9730 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9731 TEST_DrawLevelField(x - 1, y);
9733 else if (MovDir[x][y] == MV_RIGHT)
9735 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9736 TEST_DrawLevelField(x + 1, y);
9738 else if (MovDir[x][y] == MV_UP)
9740 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9741 TEST_DrawLevelField(x, y - 1);
9745 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9746 TEST_DrawLevelField(x, y + 1);
9749 Tile[x][y] = Store[x][y];
9751 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9752 TEST_DrawLevelField(x, y);
9757 static void CheckWallGrowing(int ax, int ay)
9759 int element = Tile[ax][ay];
9760 int graphic = el2img(element);
9761 boolean free_top = FALSE;
9762 boolean free_bottom = FALSE;
9763 boolean free_left = FALSE;
9764 boolean free_right = FALSE;
9765 boolean stop_top = FALSE;
9766 boolean stop_bottom = FALSE;
9767 boolean stop_left = FALSE;
9768 boolean stop_right = FALSE;
9769 boolean new_wall = FALSE;
9771 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9772 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9773 element == EL_EXPANDABLE_STEELWALL_ANY);
9775 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9776 element == EL_EXPANDABLE_WALL_ANY ||
9777 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9778 element == EL_EXPANDABLE_STEELWALL_ANY);
9780 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9781 element == EL_EXPANDABLE_WALL_ANY ||
9782 element == EL_EXPANDABLE_WALL ||
9783 element == EL_BD_EXPANDABLE_WALL ||
9784 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9785 element == EL_EXPANDABLE_STEELWALL_ANY);
9787 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9788 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9790 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9791 element == EL_EXPANDABLE_WALL ||
9792 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9794 int wall_growing = (is_steelwall ?
9795 EL_EXPANDABLE_STEELWALL_GROWING :
9796 EL_EXPANDABLE_WALL_GROWING);
9798 int gfx_wall_growing_up = (is_steelwall ?
9799 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9800 IMG_EXPANDABLE_WALL_GROWING_UP);
9801 int gfx_wall_growing_down = (is_steelwall ?
9802 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9803 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9804 int gfx_wall_growing_left = (is_steelwall ?
9805 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9806 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9807 int gfx_wall_growing_right = (is_steelwall ?
9808 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9809 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9811 if (IS_ANIMATED(graphic))
9812 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9814 if (!MovDelay[ax][ay]) // start building new wall
9815 MovDelay[ax][ay] = 6;
9817 if (MovDelay[ax][ay]) // wait some time before building new wall
9820 if (MovDelay[ax][ay])
9824 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9826 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9828 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9830 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9837 Tile[ax][ay - 1] = wall_growing;
9838 Store[ax][ay - 1] = element;
9839 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9841 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9842 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9849 Tile[ax][ay + 1] = wall_growing;
9850 Store[ax][ay + 1] = element;
9851 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9853 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9854 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
9860 if (grow_horizontal)
9864 Tile[ax - 1][ay] = wall_growing;
9865 Store[ax - 1][ay] = element;
9866 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9868 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9869 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
9876 Tile[ax + 1][ay] = wall_growing;
9877 Store[ax + 1][ay] = element;
9878 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9880 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9881 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
9887 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
9888 TEST_DrawLevelField(ax, ay);
9890 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9892 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9894 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9896 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9899 if (((stop_top && stop_bottom) || stop_horizontal) &&
9900 ((stop_left && stop_right) || stop_vertical))
9901 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
9904 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9907 static void CheckForDragon(int x, int y)
9910 boolean dragon_found = FALSE;
9911 struct XY *xy = xy_topdown;
9913 for (i = 0; i < NUM_DIRECTIONS; i++)
9915 for (j = 0; j < 4; j++)
9917 int xx = x + j * xy[i].x;
9918 int yy = y + j * xy[i].y;
9920 if (IN_LEV_FIELD(xx, yy) &&
9921 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9923 if (Tile[xx][yy] == EL_DRAGON)
9924 dragon_found = TRUE;
9933 for (i = 0; i < NUM_DIRECTIONS; i++)
9935 for (j = 0; j < 3; j++)
9937 int xx = x + j * xy[i].x;
9938 int yy = y + j * xy[i].y;
9940 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9942 Tile[xx][yy] = EL_EMPTY;
9943 TEST_DrawLevelField(xx, yy);
9952 static void InitBuggyBase(int x, int y)
9954 int element = Tile[x][y];
9955 int activating_delay = FRAMES_PER_SECOND / 4;
9958 (element == EL_SP_BUGGY_BASE ?
9959 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9960 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9962 element == EL_SP_BUGGY_BASE_ACTIVE ?
9963 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9966 static void WarnBuggyBase(int x, int y)
9969 struct XY *xy = xy_topdown;
9971 for (i = 0; i < NUM_DIRECTIONS; i++)
9973 int xx = x + xy[i].x;
9974 int yy = y + xy[i].y;
9976 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9978 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9985 static void InitTrap(int x, int y)
9987 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9990 static void ActivateTrap(int x, int y)
9992 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9995 static void ChangeActiveTrap(int x, int y)
9997 int graphic = IMG_TRAP_ACTIVE;
9999 // if new animation frame was drawn, correct crumbled sand border
10000 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10001 TEST_DrawLevelFieldCrumbled(x, y);
10004 static int getSpecialActionElement(int element, int number, int base_element)
10006 return (element != EL_EMPTY ? element :
10007 number != -1 ? base_element + number - 1 :
10011 static int getModifiedActionNumber(int value_old, int operator, int operand,
10012 int value_min, int value_max)
10014 int value_new = (operator == CA_MODE_SET ? operand :
10015 operator == CA_MODE_ADD ? value_old + operand :
10016 operator == CA_MODE_SUBTRACT ? value_old - operand :
10017 operator == CA_MODE_MULTIPLY ? value_old * operand :
10018 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10019 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10022 return (value_new < value_min ? value_min :
10023 value_new > value_max ? value_max :
10027 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10029 struct ElementInfo *ei = &element_info[element];
10030 struct ElementChangeInfo *change = &ei->change_page[page];
10031 int target_element = change->target_element;
10032 int action_type = change->action_type;
10033 int action_mode = change->action_mode;
10034 int action_arg = change->action_arg;
10035 int action_element = change->action_element;
10038 if (!change->has_action)
10041 // ---------- determine action paramater values -----------------------------
10043 int level_time_value =
10044 (level.time > 0 ? TimeLeft :
10047 int action_arg_element_raw =
10048 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10049 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10050 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10051 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10052 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10053 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10054 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10056 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10058 int action_arg_direction =
10059 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10060 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10061 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10062 change->actual_trigger_side :
10063 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10064 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10067 int action_arg_number_min =
10068 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10071 int action_arg_number_max =
10072 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10073 action_type == CA_SET_LEVEL_GEMS ? 999 :
10074 action_type == CA_SET_LEVEL_TIME ? 9999 :
10075 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10076 action_type == CA_SET_CE_VALUE ? 9999 :
10077 action_type == CA_SET_CE_SCORE ? 9999 :
10080 int action_arg_number_reset =
10081 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10082 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10083 action_type == CA_SET_LEVEL_TIME ? level.time :
10084 action_type == CA_SET_LEVEL_SCORE ? 0 :
10085 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10086 action_type == CA_SET_CE_SCORE ? 0 :
10089 int action_arg_number =
10090 (action_arg <= CA_ARG_MAX ? action_arg :
10091 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10092 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10093 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10094 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10095 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10096 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10097 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10098 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10099 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10100 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10101 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10102 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10103 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10104 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10105 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10106 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10107 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10108 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10109 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10110 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10111 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10114 int action_arg_number_old =
10115 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10116 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10117 action_type == CA_SET_LEVEL_SCORE ? game.score :
10118 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10119 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10122 int action_arg_number_new =
10123 getModifiedActionNumber(action_arg_number_old,
10124 action_mode, action_arg_number,
10125 action_arg_number_min, action_arg_number_max);
10127 int trigger_player_bits =
10128 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10129 change->actual_trigger_player_bits : change->trigger_player);
10131 int action_arg_player_bits =
10132 (action_arg >= CA_ARG_PLAYER_1 &&
10133 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10134 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10135 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10138 // ---------- execute action -----------------------------------------------
10140 switch (action_type)
10147 // ---------- level actions ----------------------------------------------
10149 case CA_RESTART_LEVEL:
10151 game.restart_level = TRUE;
10156 case CA_SHOW_ENVELOPE:
10158 int element = getSpecialActionElement(action_arg_element,
10159 action_arg_number, EL_ENVELOPE_1);
10161 if (IS_ENVELOPE(element))
10162 local_player->show_envelope = element;
10167 case CA_SET_LEVEL_TIME:
10169 if (level.time > 0) // only modify limited time value
10171 TimeLeft = action_arg_number_new;
10173 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10175 DisplayGameControlValues();
10177 if (!TimeLeft && game.time_limit)
10178 for (i = 0; i < MAX_PLAYERS; i++)
10179 KillPlayer(&stored_player[i]);
10185 case CA_SET_LEVEL_SCORE:
10187 game.score = action_arg_number_new;
10189 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10191 DisplayGameControlValues();
10196 case CA_SET_LEVEL_GEMS:
10198 game.gems_still_needed = action_arg_number_new;
10200 game.snapshot.collected_item = TRUE;
10202 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10204 DisplayGameControlValues();
10209 case CA_SET_LEVEL_WIND:
10211 game.wind_direction = action_arg_direction;
10216 case CA_SET_LEVEL_RANDOM_SEED:
10218 // ensure that setting a new random seed while playing is predictable
10219 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10224 // ---------- player actions ---------------------------------------------
10226 case CA_MOVE_PLAYER:
10227 case CA_MOVE_PLAYER_NEW:
10229 // automatically move to the next field in specified direction
10230 for (i = 0; i < MAX_PLAYERS; i++)
10231 if (trigger_player_bits & (1 << i))
10232 if (action_type == CA_MOVE_PLAYER ||
10233 stored_player[i].MovPos == 0)
10234 stored_player[i].programmed_action = action_arg_direction;
10239 case CA_EXIT_PLAYER:
10241 for (i = 0; i < MAX_PLAYERS; i++)
10242 if (action_arg_player_bits & (1 << i))
10243 ExitPlayer(&stored_player[i]);
10245 if (game.players_still_needed == 0)
10251 case CA_KILL_PLAYER:
10253 for (i = 0; i < MAX_PLAYERS; i++)
10254 if (action_arg_player_bits & (1 << i))
10255 KillPlayer(&stored_player[i]);
10260 case CA_SET_PLAYER_KEYS:
10262 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10263 int element = getSpecialActionElement(action_arg_element,
10264 action_arg_number, EL_KEY_1);
10266 if (IS_KEY(element))
10268 for (i = 0; i < MAX_PLAYERS; i++)
10270 if (trigger_player_bits & (1 << i))
10272 stored_player[i].key[KEY_NR(element)] = key_state;
10274 DrawGameDoorValues();
10282 case CA_SET_PLAYER_SPEED:
10284 for (i = 0; i < MAX_PLAYERS; i++)
10286 if (trigger_player_bits & (1 << i))
10288 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10290 if (action_arg == CA_ARG_SPEED_FASTER &&
10291 stored_player[i].cannot_move)
10293 action_arg_number = STEPSIZE_VERY_SLOW;
10295 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10296 action_arg == CA_ARG_SPEED_FASTER)
10298 action_arg_number = 2;
10299 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10302 else if (action_arg == CA_ARG_NUMBER_RESET)
10304 action_arg_number = level.initial_player_stepsize[i];
10308 getModifiedActionNumber(move_stepsize,
10311 action_arg_number_min,
10312 action_arg_number_max);
10314 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10321 case CA_SET_PLAYER_SHIELD:
10323 for (i = 0; i < MAX_PLAYERS; i++)
10325 if (trigger_player_bits & (1 << i))
10327 if (action_arg == CA_ARG_SHIELD_OFF)
10329 stored_player[i].shield_normal_time_left = 0;
10330 stored_player[i].shield_deadly_time_left = 0;
10332 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10334 stored_player[i].shield_normal_time_left = 999999;
10336 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10338 stored_player[i].shield_normal_time_left = 999999;
10339 stored_player[i].shield_deadly_time_left = 999999;
10347 case CA_SET_PLAYER_GRAVITY:
10349 for (i = 0; i < MAX_PLAYERS; i++)
10351 if (trigger_player_bits & (1 << i))
10353 stored_player[i].gravity =
10354 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10355 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10356 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10357 stored_player[i].gravity);
10364 case CA_SET_PLAYER_ARTWORK:
10366 for (i = 0; i < MAX_PLAYERS; i++)
10368 if (trigger_player_bits & (1 << i))
10370 int artwork_element = action_arg_element;
10372 if (action_arg == CA_ARG_ELEMENT_RESET)
10374 (level.use_artwork_element[i] ? level.artwork_element[i] :
10375 stored_player[i].element_nr);
10377 if (stored_player[i].artwork_element != artwork_element)
10378 stored_player[i].Frame = 0;
10380 stored_player[i].artwork_element = artwork_element;
10382 SetPlayerWaiting(&stored_player[i], FALSE);
10384 // set number of special actions for bored and sleeping animation
10385 stored_player[i].num_special_action_bored =
10386 get_num_special_action(artwork_element,
10387 ACTION_BORING_1, ACTION_BORING_LAST);
10388 stored_player[i].num_special_action_sleeping =
10389 get_num_special_action(artwork_element,
10390 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10397 case CA_SET_PLAYER_INVENTORY:
10399 for (i = 0; i < MAX_PLAYERS; i++)
10401 struct PlayerInfo *player = &stored_player[i];
10404 if (trigger_player_bits & (1 << i))
10406 int inventory_element = action_arg_element;
10408 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10409 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10410 action_arg == CA_ARG_ELEMENT_ACTION)
10412 int element = inventory_element;
10413 int collect_count = element_info[element].collect_count_initial;
10415 if (!IS_CUSTOM_ELEMENT(element))
10418 if (collect_count == 0)
10419 player->inventory_infinite_element = element;
10421 for (k = 0; k < collect_count; k++)
10422 if (player->inventory_size < MAX_INVENTORY_SIZE)
10423 player->inventory_element[player->inventory_size++] =
10426 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10427 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10428 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10430 if (player->inventory_infinite_element != EL_UNDEFINED &&
10431 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10432 action_arg_element_raw))
10433 player->inventory_infinite_element = EL_UNDEFINED;
10435 for (k = 0, j = 0; j < player->inventory_size; j++)
10437 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10438 action_arg_element_raw))
10439 player->inventory_element[k++] = player->inventory_element[j];
10442 player->inventory_size = k;
10444 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10446 if (player->inventory_size > 0)
10448 for (j = 0; j < player->inventory_size - 1; j++)
10449 player->inventory_element[j] = player->inventory_element[j + 1];
10451 player->inventory_size--;
10454 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10456 if (player->inventory_size > 0)
10457 player->inventory_size--;
10459 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10461 player->inventory_infinite_element = EL_UNDEFINED;
10462 player->inventory_size = 0;
10464 else if (action_arg == CA_ARG_INVENTORY_RESET)
10466 player->inventory_infinite_element = EL_UNDEFINED;
10467 player->inventory_size = 0;
10469 if (level.use_initial_inventory[i])
10471 for (j = 0; j < level.initial_inventory_size[i]; j++)
10473 int element = level.initial_inventory_content[i][j];
10474 int collect_count = element_info[element].collect_count_initial;
10476 if (!IS_CUSTOM_ELEMENT(element))
10479 if (collect_count == 0)
10480 player->inventory_infinite_element = element;
10482 for (k = 0; k < collect_count; k++)
10483 if (player->inventory_size < MAX_INVENTORY_SIZE)
10484 player->inventory_element[player->inventory_size++] =
10495 // ---------- CE actions -------------------------------------------------
10497 case CA_SET_CE_VALUE:
10499 int last_ce_value = CustomValue[x][y];
10501 CustomValue[x][y] = action_arg_number_new;
10503 if (CustomValue[x][y] != last_ce_value)
10505 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10506 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10508 if (CustomValue[x][y] == 0)
10510 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10511 ChangeCount[x][y] = 0; // allow at least one more change
10513 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10514 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10521 case CA_SET_CE_SCORE:
10523 int last_ce_score = ei->collect_score;
10525 ei->collect_score = action_arg_number_new;
10527 if (ei->collect_score != last_ce_score)
10529 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10530 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10532 if (ei->collect_score == 0)
10536 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10537 ChangeCount[x][y] = 0; // allow at least one more change
10539 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10540 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10543 This is a very special case that seems to be a mixture between
10544 CheckElementChange() and CheckTriggeredElementChange(): while
10545 the first one only affects single elements that are triggered
10546 directly, the second one affects multiple elements in the playfield
10547 that are triggered indirectly by another element. This is a third
10548 case: Changing the CE score always affects multiple identical CEs,
10549 so every affected CE must be checked, not only the single CE for
10550 which the CE score was changed in the first place (as every instance
10551 of that CE shares the same CE score, and therefore also can change)!
10553 SCAN_PLAYFIELD(xx, yy)
10555 if (Tile[xx][yy] == element)
10556 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10557 CE_SCORE_GETS_ZERO);
10565 case CA_SET_CE_ARTWORK:
10567 int artwork_element = action_arg_element;
10568 boolean reset_frame = FALSE;
10571 if (action_arg == CA_ARG_ELEMENT_RESET)
10572 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10575 if (ei->gfx_element != artwork_element)
10576 reset_frame = TRUE;
10578 ei->gfx_element = artwork_element;
10580 SCAN_PLAYFIELD(xx, yy)
10582 if (Tile[xx][yy] == element)
10586 ResetGfxAnimation(xx, yy);
10587 ResetRandomAnimationValue(xx, yy);
10590 TEST_DrawLevelField(xx, yy);
10597 // ---------- engine actions ---------------------------------------------
10599 case CA_SET_ENGINE_SCAN_MODE:
10601 InitPlayfieldScanMode(action_arg);
10611 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10613 int old_element = Tile[x][y];
10614 int new_element = GetElementFromGroupElement(element);
10615 int previous_move_direction = MovDir[x][y];
10616 int last_ce_value = CustomValue[x][y];
10617 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10618 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10619 boolean add_player_onto_element = (new_element_is_player &&
10620 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10621 IS_WALKABLE(old_element));
10623 if (!add_player_onto_element)
10625 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10626 RemoveMovingField(x, y);
10630 Tile[x][y] = new_element;
10632 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10633 MovDir[x][y] = previous_move_direction;
10635 if (element_info[new_element].use_last_ce_value)
10636 CustomValue[x][y] = last_ce_value;
10638 InitField_WithBug1(x, y, FALSE);
10640 new_element = Tile[x][y]; // element may have changed
10642 ResetGfxAnimation(x, y);
10643 ResetRandomAnimationValue(x, y);
10645 TEST_DrawLevelField(x, y);
10647 if (GFX_CRUMBLED(new_element))
10648 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10650 if (old_element == EL_EXPLOSION)
10652 Store[x][y] = Store2[x][y] = 0;
10654 // check if new element replaces an exploding player, requiring cleanup
10655 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10656 StorePlayer[x][y] = 0;
10659 // check if element under the player changes from accessible to unaccessible
10660 // (needed for special case of dropping element which then changes)
10661 // (must be checked after creating new element for walkable group elements)
10662 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10663 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10665 KillPlayer(PLAYERINFO(x, y));
10671 // "ChangeCount" not set yet to allow "entered by player" change one time
10672 if (new_element_is_player)
10673 RelocatePlayer(x, y, new_element);
10676 ChangeCount[x][y]++; // count number of changes in the same frame
10678 TestIfBadThingTouchesPlayer(x, y);
10679 TestIfPlayerTouchesCustomElement(x, y);
10680 TestIfElementTouchesCustomElement(x, y);
10683 static void CreateField(int x, int y, int element)
10685 CreateFieldExt(x, y, element, FALSE);
10688 static void CreateElementFromChange(int x, int y, int element)
10690 element = GET_VALID_RUNTIME_ELEMENT(element);
10692 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10694 int old_element = Tile[x][y];
10696 // prevent changed element from moving in same engine frame
10697 // unless both old and new element can either fall or move
10698 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10699 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10703 CreateFieldExt(x, y, element, TRUE);
10706 static boolean ChangeElement(int x, int y, int element, int page)
10708 struct ElementInfo *ei = &element_info[element];
10709 struct ElementChangeInfo *change = &ei->change_page[page];
10710 int ce_value = CustomValue[x][y];
10711 int ce_score = ei->collect_score;
10712 int target_element;
10713 int old_element = Tile[x][y];
10715 // always use default change event to prevent running into a loop
10716 if (ChangeEvent[x][y] == -1)
10717 ChangeEvent[x][y] = CE_DELAY;
10719 if (ChangeEvent[x][y] == CE_DELAY)
10721 // reset actual trigger element, trigger player and action element
10722 change->actual_trigger_element = EL_EMPTY;
10723 change->actual_trigger_player = EL_EMPTY;
10724 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10725 change->actual_trigger_side = CH_SIDE_NONE;
10726 change->actual_trigger_ce_value = 0;
10727 change->actual_trigger_ce_score = 0;
10730 // do not change elements more than a specified maximum number of changes
10731 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10734 ChangeCount[x][y]++; // count number of changes in the same frame
10736 if (ei->has_anim_event)
10737 HandleGlobalAnimEventByElementChange(element, page, x, y);
10739 if (change->explode)
10746 if (change->use_target_content)
10748 boolean complete_replace = TRUE;
10749 boolean can_replace[3][3];
10752 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10755 boolean is_walkable;
10756 boolean is_diggable;
10757 boolean is_collectible;
10758 boolean is_removable;
10759 boolean is_destructible;
10760 int ex = x + xx - 1;
10761 int ey = y + yy - 1;
10762 int content_element = change->target_content.e[xx][yy];
10765 can_replace[xx][yy] = TRUE;
10767 if (ex == x && ey == y) // do not check changing element itself
10770 if (content_element == EL_EMPTY_SPACE)
10772 can_replace[xx][yy] = FALSE; // do not replace border with space
10777 if (!IN_LEV_FIELD(ex, ey))
10779 can_replace[xx][yy] = FALSE;
10780 complete_replace = FALSE;
10787 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10788 e = MovingOrBlocked2Element(ex, ey);
10790 is_empty = (IS_FREE(ex, ey) ||
10791 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10793 is_walkable = (is_empty || IS_WALKABLE(e));
10794 is_diggable = (is_empty || IS_DIGGABLE(e));
10795 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10796 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10797 is_removable = (is_diggable || is_collectible);
10799 can_replace[xx][yy] =
10800 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10801 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10802 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10803 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10804 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10805 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10806 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10808 if (!can_replace[xx][yy])
10809 complete_replace = FALSE;
10812 if (!change->only_if_complete || complete_replace)
10814 boolean something_has_changed = FALSE;
10816 if (change->only_if_complete && change->use_random_replace &&
10817 RND(100) < change->random_percentage)
10820 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10822 int ex = x + xx - 1;
10823 int ey = y + yy - 1;
10824 int content_element;
10826 if (can_replace[xx][yy] && (!change->use_random_replace ||
10827 RND(100) < change->random_percentage))
10829 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10830 RemoveMovingField(ex, ey);
10832 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10834 content_element = change->target_content.e[xx][yy];
10835 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10836 ce_value, ce_score);
10838 CreateElementFromChange(ex, ey, target_element);
10840 something_has_changed = TRUE;
10842 // for symmetry reasons, freeze newly created border elements
10843 if (ex != x || ey != y)
10844 Stop[ex][ey] = TRUE; // no more moving in this frame
10848 if (something_has_changed)
10850 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10851 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10857 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10858 ce_value, ce_score);
10860 if (element == EL_DIAGONAL_GROWING ||
10861 element == EL_DIAGONAL_SHRINKING)
10863 target_element = Store[x][y];
10865 Store[x][y] = EL_EMPTY;
10868 // special case: element changes to player (and may be kept if walkable)
10869 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10870 CreateElementFromChange(x, y, EL_EMPTY);
10872 CreateElementFromChange(x, y, target_element);
10874 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10875 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10878 // this uses direct change before indirect change
10879 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10884 static void HandleElementChange(int x, int y, int page)
10886 int element = MovingOrBlocked2Element(x, y);
10887 struct ElementInfo *ei = &element_info[element];
10888 struct ElementChangeInfo *change = &ei->change_page[page];
10889 boolean handle_action_before_change = FALSE;
10892 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10893 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10895 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10896 x, y, element, element_info[element].token_name);
10897 Debug("game:playing:HandleElementChange", "This should never happen!");
10901 // this can happen with classic bombs on walkable, changing elements
10902 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10907 if (ChangeDelay[x][y] == 0) // initialize element change
10909 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10911 if (change->can_change)
10913 // !!! not clear why graphic animation should be reset at all here !!!
10914 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10915 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10918 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10920 When using an animation frame delay of 1 (this only happens with
10921 "sp_zonk.moving.left/right" in the classic graphics), the default
10922 (non-moving) animation shows wrong animation frames (while the
10923 moving animation, like "sp_zonk.moving.left/right", is correct,
10924 so this graphical bug never shows up with the classic graphics).
10925 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10926 be drawn instead of the correct frames 0,1,2,3. This is caused by
10927 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10928 an element change: First when the change delay ("ChangeDelay[][]")
10929 counter has reached zero after decrementing, then a second time in
10930 the next frame (after "GfxFrame[][]" was already incremented) when
10931 "ChangeDelay[][]" is reset to the initial delay value again.
10933 This causes frame 0 to be drawn twice, while the last frame won't
10934 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10936 As some animations may already be cleverly designed around this bug
10937 (at least the "Snake Bite" snake tail animation does this), it cannot
10938 simply be fixed here without breaking such existing animations.
10939 Unfortunately, it cannot easily be detected if a graphics set was
10940 designed "before" or "after" the bug was fixed. As a workaround,
10941 a new graphics set option "game.graphics_engine_version" was added
10942 to be able to specify the game's major release version for which the
10943 graphics set was designed, which can then be used to decide if the
10944 bugfix should be used (version 4 and above) or not (version 3 or
10945 below, or if no version was specified at all, as with old sets).
10947 (The wrong/fixed animation frames can be tested with the test level set
10948 "test_gfxframe" and level "000", which contains a specially prepared
10949 custom element at level position (x/y) == (11/9) which uses the zonk
10950 animation mentioned above. Using "game.graphics_engine_version: 4"
10951 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10952 This can also be seen from the debug output for this test element.)
10955 // when a custom element is about to change (for example by change delay),
10956 // do not reset graphic animation when the custom element is moving
10957 if (game.graphics_engine_version < 4 &&
10960 ResetGfxAnimation(x, y);
10961 ResetRandomAnimationValue(x, y);
10964 if (change->pre_change_function)
10965 change->pre_change_function(x, y);
10969 ChangeDelay[x][y]--;
10971 if (ChangeDelay[x][y] != 0) // continue element change
10973 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10975 // also needed if CE can not change, but has CE delay with CE action
10976 if (IS_ANIMATED(graphic))
10977 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10979 if (change->can_change)
10981 if (change->change_function)
10982 change->change_function(x, y);
10985 else // finish element change
10987 if (ChangePage[x][y] != -1) // remember page from delayed change
10989 page = ChangePage[x][y];
10990 ChangePage[x][y] = -1;
10992 change = &ei->change_page[page];
10995 if (IS_MOVING(x, y)) // never change a running system ;-)
10997 ChangeDelay[x][y] = 1; // try change after next move step
10998 ChangePage[x][y] = page; // remember page to use for change
11003 // special case: set new level random seed before changing element
11004 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11005 handle_action_before_change = TRUE;
11007 if (change->has_action && handle_action_before_change)
11008 ExecuteCustomElementAction(x, y, element, page);
11010 if (change->can_change)
11012 if (ChangeElement(x, y, element, page))
11014 if (change->post_change_function)
11015 change->post_change_function(x, y);
11019 if (change->has_action && !handle_action_before_change)
11020 ExecuteCustomElementAction(x, y, element, page);
11024 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11025 int trigger_element,
11027 int trigger_player,
11031 boolean change_done_any = FALSE;
11032 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11035 if (!(trigger_events[trigger_element][trigger_event]))
11038 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11040 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11042 int element = EL_CUSTOM_START + i;
11043 boolean change_done = FALSE;
11046 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11047 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11050 for (p = 0; p < element_info[element].num_change_pages; p++)
11052 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11054 if (change->can_change_or_has_action &&
11055 change->has_event[trigger_event] &&
11056 change->trigger_side & trigger_side &&
11057 change->trigger_player & trigger_player &&
11058 change->trigger_page & trigger_page_bits &&
11059 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11061 change->actual_trigger_element = trigger_element;
11062 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11063 change->actual_trigger_player_bits = trigger_player;
11064 change->actual_trigger_side = trigger_side;
11065 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11066 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11068 if ((change->can_change && !change_done) || change->has_action)
11072 SCAN_PLAYFIELD(x, y)
11074 if (Tile[x][y] == element)
11076 if (change->can_change && !change_done)
11078 // if element already changed in this frame, not only prevent
11079 // another element change (checked in ChangeElement()), but
11080 // also prevent additional element actions for this element
11082 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11083 !level.use_action_after_change_bug)
11086 ChangeDelay[x][y] = 1;
11087 ChangeEvent[x][y] = trigger_event;
11089 HandleElementChange(x, y, p);
11091 else if (change->has_action)
11093 // if element already changed in this frame, not only prevent
11094 // another element change (checked in ChangeElement()), but
11095 // also prevent additional element actions for this element
11097 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11098 !level.use_action_after_change_bug)
11101 ExecuteCustomElementAction(x, y, element, p);
11102 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11107 if (change->can_change)
11109 change_done = TRUE;
11110 change_done_any = TRUE;
11117 RECURSION_LOOP_DETECTION_END();
11119 return change_done_any;
11122 static boolean CheckElementChangeExt(int x, int y,
11124 int trigger_element,
11126 int trigger_player,
11129 boolean change_done = FALSE;
11132 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11133 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11136 if (Tile[x][y] == EL_BLOCKED)
11138 Blocked2Moving(x, y, &x, &y);
11139 element = Tile[x][y];
11142 // check if element has already changed or is about to change after moving
11143 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11144 Tile[x][y] != element) ||
11146 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11147 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11148 ChangePage[x][y] != -1)))
11151 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11153 for (p = 0; p < element_info[element].num_change_pages; p++)
11155 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11157 /* check trigger element for all events where the element that is checked
11158 for changing interacts with a directly adjacent element -- this is
11159 different to element changes that affect other elements to change on the
11160 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11161 boolean check_trigger_element =
11162 (trigger_event == CE_NEXT_TO_X ||
11163 trigger_event == CE_TOUCHING_X ||
11164 trigger_event == CE_HITTING_X ||
11165 trigger_event == CE_HIT_BY_X ||
11166 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11168 if (change->can_change_or_has_action &&
11169 change->has_event[trigger_event] &&
11170 change->trigger_side & trigger_side &&
11171 change->trigger_player & trigger_player &&
11172 (!check_trigger_element ||
11173 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11175 change->actual_trigger_element = trigger_element;
11176 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11177 change->actual_trigger_player_bits = trigger_player;
11178 change->actual_trigger_side = trigger_side;
11179 change->actual_trigger_ce_value = CustomValue[x][y];
11180 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11182 // special case: trigger element not at (x,y) position for some events
11183 if (check_trigger_element)
11195 { 0, 0 }, { 0, 0 }, { 0, 0 },
11199 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11200 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11202 change->actual_trigger_ce_value = CustomValue[xx][yy];
11203 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11206 if (change->can_change && !change_done)
11208 ChangeDelay[x][y] = 1;
11209 ChangeEvent[x][y] = trigger_event;
11211 HandleElementChange(x, y, p);
11213 change_done = TRUE;
11215 else if (change->has_action)
11217 ExecuteCustomElementAction(x, y, element, p);
11218 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11223 RECURSION_LOOP_DETECTION_END();
11225 return change_done;
11228 static void PlayPlayerSound(struct PlayerInfo *player)
11230 int jx = player->jx, jy = player->jy;
11231 int sound_element = player->artwork_element;
11232 int last_action = player->last_action_waiting;
11233 int action = player->action_waiting;
11235 if (player->is_waiting)
11237 if (action != last_action)
11238 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11240 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11244 if (action != last_action)
11245 StopSound(element_info[sound_element].sound[last_action]);
11247 if (last_action == ACTION_SLEEPING)
11248 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11252 static void PlayAllPlayersSound(void)
11256 for (i = 0; i < MAX_PLAYERS; i++)
11257 if (stored_player[i].active)
11258 PlayPlayerSound(&stored_player[i]);
11261 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11263 boolean last_waiting = player->is_waiting;
11264 int move_dir = player->MovDir;
11266 player->dir_waiting = move_dir;
11267 player->last_action_waiting = player->action_waiting;
11271 if (!last_waiting) // not waiting -> waiting
11273 player->is_waiting = TRUE;
11275 player->frame_counter_bored =
11277 game.player_boring_delay_fixed +
11278 GetSimpleRandom(game.player_boring_delay_random);
11279 player->frame_counter_sleeping =
11281 game.player_sleeping_delay_fixed +
11282 GetSimpleRandom(game.player_sleeping_delay_random);
11284 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11287 if (game.player_sleeping_delay_fixed +
11288 game.player_sleeping_delay_random > 0 &&
11289 player->anim_delay_counter == 0 &&
11290 player->post_delay_counter == 0 &&
11291 FrameCounter >= player->frame_counter_sleeping)
11292 player->is_sleeping = TRUE;
11293 else if (game.player_boring_delay_fixed +
11294 game.player_boring_delay_random > 0 &&
11295 FrameCounter >= player->frame_counter_bored)
11296 player->is_bored = TRUE;
11298 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11299 player->is_bored ? ACTION_BORING :
11302 if (player->is_sleeping && player->use_murphy)
11304 // special case for sleeping Murphy when leaning against non-free tile
11306 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11307 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11308 !IS_MOVING(player->jx - 1, player->jy)))
11309 move_dir = MV_LEFT;
11310 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11311 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11312 !IS_MOVING(player->jx + 1, player->jy)))
11313 move_dir = MV_RIGHT;
11315 player->is_sleeping = FALSE;
11317 player->dir_waiting = move_dir;
11320 if (player->is_sleeping)
11322 if (player->num_special_action_sleeping > 0)
11324 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11326 int last_special_action = player->special_action_sleeping;
11327 int num_special_action = player->num_special_action_sleeping;
11328 int special_action =
11329 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11330 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11331 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11332 last_special_action + 1 : ACTION_SLEEPING);
11333 int special_graphic =
11334 el_act_dir2img(player->artwork_element, special_action, move_dir);
11336 player->anim_delay_counter =
11337 graphic_info[special_graphic].anim_delay_fixed +
11338 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11339 player->post_delay_counter =
11340 graphic_info[special_graphic].post_delay_fixed +
11341 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11343 player->special_action_sleeping = special_action;
11346 if (player->anim_delay_counter > 0)
11348 player->action_waiting = player->special_action_sleeping;
11349 player->anim_delay_counter--;
11351 else if (player->post_delay_counter > 0)
11353 player->post_delay_counter--;
11357 else if (player->is_bored)
11359 if (player->num_special_action_bored > 0)
11361 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11363 int special_action =
11364 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11365 int special_graphic =
11366 el_act_dir2img(player->artwork_element, special_action, move_dir);
11368 player->anim_delay_counter =
11369 graphic_info[special_graphic].anim_delay_fixed +
11370 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11371 player->post_delay_counter =
11372 graphic_info[special_graphic].post_delay_fixed +
11373 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11375 player->special_action_bored = special_action;
11378 if (player->anim_delay_counter > 0)
11380 player->action_waiting = player->special_action_bored;
11381 player->anim_delay_counter--;
11383 else if (player->post_delay_counter > 0)
11385 player->post_delay_counter--;
11390 else if (last_waiting) // waiting -> not waiting
11392 player->is_waiting = FALSE;
11393 player->is_bored = FALSE;
11394 player->is_sleeping = FALSE;
11396 player->frame_counter_bored = -1;
11397 player->frame_counter_sleeping = -1;
11399 player->anim_delay_counter = 0;
11400 player->post_delay_counter = 0;
11402 player->dir_waiting = player->MovDir;
11403 player->action_waiting = ACTION_DEFAULT;
11405 player->special_action_bored = ACTION_DEFAULT;
11406 player->special_action_sleeping = ACTION_DEFAULT;
11410 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11412 if ((!player->is_moving && player->was_moving) ||
11413 (player->MovPos == 0 && player->was_moving) ||
11414 (player->is_snapping && !player->was_snapping) ||
11415 (player->is_dropping && !player->was_dropping))
11417 if (!CheckSaveEngineSnapshotToList())
11420 player->was_moving = FALSE;
11421 player->was_snapping = TRUE;
11422 player->was_dropping = TRUE;
11426 if (player->is_moving)
11427 player->was_moving = TRUE;
11429 if (!player->is_snapping)
11430 player->was_snapping = FALSE;
11432 if (!player->is_dropping)
11433 player->was_dropping = FALSE;
11436 static struct MouseActionInfo mouse_action_last = { 0 };
11437 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11438 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11441 CheckSaveEngineSnapshotToList();
11443 mouse_action_last = mouse_action;
11446 static void CheckSingleStepMode(struct PlayerInfo *player)
11448 if (tape.single_step && tape.recording && !tape.pausing)
11450 // as it is called "single step mode", just return to pause mode when the
11451 // player stopped moving after one tile (or never starts moving at all)
11452 // (reverse logic needed here in case single step mode used in team mode)
11453 if (player->is_moving ||
11454 player->is_pushing ||
11455 player->is_dropping_pressed ||
11456 player->effective_mouse_action.button)
11457 game.enter_single_step_mode = FALSE;
11460 CheckSaveEngineSnapshot(player);
11463 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11465 int left = player_action & JOY_LEFT;
11466 int right = player_action & JOY_RIGHT;
11467 int up = player_action & JOY_UP;
11468 int down = player_action & JOY_DOWN;
11469 int button1 = player_action & JOY_BUTTON_1;
11470 int button2 = player_action & JOY_BUTTON_2;
11471 int dx = (left ? -1 : right ? 1 : 0);
11472 int dy = (up ? -1 : down ? 1 : 0);
11474 if (!player->active || tape.pausing)
11480 SnapField(player, dx, dy);
11484 DropElement(player);
11486 MovePlayer(player, dx, dy);
11489 CheckSingleStepMode(player);
11491 SetPlayerWaiting(player, FALSE);
11493 return player_action;
11497 // no actions for this player (no input at player's configured device)
11499 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11500 SnapField(player, 0, 0);
11501 CheckGravityMovementWhenNotMoving(player);
11503 if (player->MovPos == 0)
11504 SetPlayerWaiting(player, TRUE);
11506 if (player->MovPos == 0) // needed for tape.playing
11507 player->is_moving = FALSE;
11509 player->is_dropping = FALSE;
11510 player->is_dropping_pressed = FALSE;
11511 player->drop_pressed_delay = 0;
11513 CheckSingleStepMode(player);
11519 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11522 if (!tape.use_mouse_actions)
11525 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11526 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11527 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11530 static void SetTapeActionFromMouseAction(byte *tape_action,
11531 struct MouseActionInfo *mouse_action)
11533 if (!tape.use_mouse_actions)
11536 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11537 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11538 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11541 static void CheckLevelSolved(void)
11543 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11545 if (game_em.level_solved &&
11546 !game_em.game_over) // game won
11550 game_em.game_over = TRUE;
11552 game.all_players_gone = TRUE;
11555 if (game_em.game_over) // game lost
11556 game.all_players_gone = TRUE;
11558 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11560 if (game_sp.level_solved &&
11561 !game_sp.game_over) // game won
11565 game_sp.game_over = TRUE;
11567 game.all_players_gone = TRUE;
11570 if (game_sp.game_over) // game lost
11571 game.all_players_gone = TRUE;
11573 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11575 if (game_mm.level_solved &&
11576 !game_mm.game_over) // game won
11580 game_mm.game_over = TRUE;
11582 game.all_players_gone = TRUE;
11585 if (game_mm.game_over) // game lost
11586 game.all_players_gone = TRUE;
11590 static void CheckLevelTime_StepCounter(void)
11600 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11601 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11603 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11605 DisplayGameControlValues();
11607 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11608 for (i = 0; i < MAX_PLAYERS; i++)
11609 KillPlayer(&stored_player[i]);
11611 else if (game.no_level_time_limit && !game.all_players_gone)
11613 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11615 DisplayGameControlValues();
11619 static void CheckLevelTime(void)
11623 if (TimeFrames >= FRAMES_PER_SECOND)
11628 for (i = 0; i < MAX_PLAYERS; i++)
11630 struct PlayerInfo *player = &stored_player[i];
11632 if (SHIELD_ON(player))
11634 player->shield_normal_time_left--;
11636 if (player->shield_deadly_time_left > 0)
11637 player->shield_deadly_time_left--;
11641 if (!game.LevelSolved && !level.use_step_counter)
11649 if (TimeLeft <= 10 && game.time_limit)
11650 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11652 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11653 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11655 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11657 if (!TimeLeft && game.time_limit)
11659 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11660 game_em.lev->killed_out_of_time = TRUE;
11662 for (i = 0; i < MAX_PLAYERS; i++)
11663 KillPlayer(&stored_player[i]);
11666 else if (game.no_level_time_limit && !game.all_players_gone)
11668 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11671 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11674 if (tape.recording || tape.playing)
11675 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11678 if (tape.recording || tape.playing)
11679 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11681 UpdateAndDisplayGameControlValues();
11684 void AdvanceFrameAndPlayerCounters(int player_nr)
11688 // advance frame counters (global frame counter and time frame counter)
11692 // advance player counters (counters for move delay, move animation etc.)
11693 for (i = 0; i < MAX_PLAYERS; i++)
11695 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11696 int move_delay_value = stored_player[i].move_delay_value;
11697 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11699 if (!advance_player_counters) // not all players may be affected
11702 if (move_frames == 0) // less than one move per game frame
11704 int stepsize = TILEX / move_delay_value;
11705 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11706 int count = (stored_player[i].is_moving ?
11707 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11709 if (count % delay == 0)
11713 stored_player[i].Frame += move_frames;
11715 if (stored_player[i].MovPos != 0)
11716 stored_player[i].StepFrame += move_frames;
11718 if (stored_player[i].move_delay > 0)
11719 stored_player[i].move_delay--;
11721 // due to bugs in previous versions, counter must count up, not down
11722 if (stored_player[i].push_delay != -1)
11723 stored_player[i].push_delay++;
11725 if (stored_player[i].drop_delay > 0)
11726 stored_player[i].drop_delay--;
11728 if (stored_player[i].is_dropping_pressed)
11729 stored_player[i].drop_pressed_delay++;
11733 void AdvanceFrameCounter(void)
11738 void AdvanceGfxFrame(void)
11742 SCAN_PLAYFIELD(x, y)
11748 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11749 struct MouseActionInfo *mouse_action_last)
11751 if (mouse_action->button)
11753 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11754 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11755 int x = mouse_action->lx;
11756 int y = mouse_action->ly;
11757 int element = Tile[x][y];
11761 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11762 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11766 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11767 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
11770 if (level.use_step_counter)
11772 boolean counted_click = FALSE;
11774 // element clicked that can change when clicked/pressed
11775 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
11776 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
11777 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
11778 counted_click = TRUE;
11780 // element clicked that can trigger change when clicked/pressed
11781 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
11782 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
11783 counted_click = TRUE;
11785 if (new_button && counted_click)
11786 CheckLevelTime_StepCounter();
11791 void StartGameActions(boolean init_network_game, boolean record_tape,
11794 unsigned int new_random_seed = InitRND(random_seed);
11797 TapeStartRecording(new_random_seed);
11799 if (setup.auto_pause_on_start && !tape.pausing)
11800 TapeTogglePause(TAPE_TOGGLE_MANUAL);
11802 if (init_network_game)
11804 SendToServer_LevelFile();
11805 SendToServer_StartPlaying();
11813 static void GameActionsExt(void)
11816 static unsigned int game_frame_delay = 0;
11818 unsigned int game_frame_delay_value;
11819 byte *recorded_player_action;
11820 byte summarized_player_action = 0;
11821 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11824 // detect endless loops, caused by custom element programming
11825 if (recursion_loop_detected && recursion_loop_depth == 0)
11827 char *message = getStringCat3("Internal Error! Element ",
11828 EL_NAME(recursion_loop_element),
11829 " caused endless loop! Quit the game?");
11831 Warn("element '%s' caused endless loop in game engine",
11832 EL_NAME(recursion_loop_element));
11834 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11836 recursion_loop_detected = FALSE; // if game should be continued
11843 if (game.restart_level)
11844 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11846 CheckLevelSolved();
11848 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11851 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11854 if (game_status != GAME_MODE_PLAYING) // status might have changed
11857 game_frame_delay_value =
11858 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11860 if (tape.playing && tape.warp_forward && !tape.pausing)
11861 game_frame_delay_value = 0;
11863 SetVideoFrameDelay(game_frame_delay_value);
11865 // (de)activate virtual buttons depending on current game status
11866 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11868 if (game.all_players_gone) // if no players there to be controlled anymore
11869 SetOverlayActive(FALSE);
11870 else if (!tape.playing) // if game continues after tape stopped playing
11871 SetOverlayActive(TRUE);
11876 // ---------- main game synchronization point ----------
11878 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11880 Debug("game:playing:skip", "skip == %d", skip);
11883 // ---------- main game synchronization point ----------
11885 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11889 if (network_playing && !network_player_action_received)
11891 // try to get network player actions in time
11893 // last chance to get network player actions without main loop delay
11894 HandleNetworking();
11896 // game was quit by network peer
11897 if (game_status != GAME_MODE_PLAYING)
11900 // check if network player actions still missing and game still running
11901 if (!network_player_action_received && !checkGameEnded())
11902 return; // failed to get network player actions in time
11904 // do not yet reset "network_player_action_received" (for tape.pausing)
11910 // at this point we know that we really continue executing the game
11912 network_player_action_received = FALSE;
11914 // when playing tape, read previously recorded player input from tape data
11915 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11917 local_player->effective_mouse_action = local_player->mouse_action;
11919 if (recorded_player_action != NULL)
11920 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11921 recorded_player_action);
11923 // TapePlayAction() may return NULL when toggling to "pause before death"
11927 if (tape.set_centered_player)
11929 game.centered_player_nr_next = tape.centered_player_nr_next;
11930 game.set_centered_player = TRUE;
11933 for (i = 0; i < MAX_PLAYERS; i++)
11935 summarized_player_action |= stored_player[i].action;
11937 if (!network_playing && (game.team_mode || tape.playing))
11938 stored_player[i].effective_action = stored_player[i].action;
11941 if (network_playing && !checkGameEnded())
11942 SendToServer_MovePlayer(summarized_player_action);
11944 // summarize all actions at local players mapped input device position
11945 // (this allows using different input devices in single player mode)
11946 if (!network.enabled && !game.team_mode)
11947 stored_player[map_player_action[local_player->index_nr]].effective_action =
11948 summarized_player_action;
11950 // summarize all actions at centered player in local team mode
11951 if (tape.recording &&
11952 setup.team_mode && !network.enabled &&
11953 setup.input_on_focus &&
11954 game.centered_player_nr != -1)
11956 for (i = 0; i < MAX_PLAYERS; i++)
11957 stored_player[map_player_action[i]].effective_action =
11958 (i == game.centered_player_nr ? summarized_player_action : 0);
11961 if (recorded_player_action != NULL)
11962 for (i = 0; i < MAX_PLAYERS; i++)
11963 stored_player[i].effective_action = recorded_player_action[i];
11965 for (i = 0; i < MAX_PLAYERS; i++)
11967 tape_action[i] = stored_player[i].effective_action;
11969 /* (this may happen in the RND game engine if a player was not present on
11970 the playfield on level start, but appeared later from a custom element */
11971 if (setup.team_mode &&
11974 !tape.player_participates[i])
11975 tape.player_participates[i] = TRUE;
11978 SetTapeActionFromMouseAction(tape_action,
11979 &local_player->effective_mouse_action);
11981 // only record actions from input devices, but not programmed actions
11982 if (tape.recording)
11983 TapeRecordAction(tape_action);
11985 // remember if game was played (especially after tape stopped playing)
11986 if (!tape.playing && summarized_player_action && !checkGameFailed())
11987 game.GamePlayed = TRUE;
11989 #if USE_NEW_PLAYER_ASSIGNMENTS
11990 // !!! also map player actions in single player mode !!!
11991 // if (game.team_mode)
11994 byte mapped_action[MAX_PLAYERS];
11996 #if DEBUG_PLAYER_ACTIONS
11997 for (i = 0; i < MAX_PLAYERS; i++)
11998 DebugContinued("", "%d, ", stored_player[i].effective_action);
12001 for (i = 0; i < MAX_PLAYERS; i++)
12002 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12004 for (i = 0; i < MAX_PLAYERS; i++)
12005 stored_player[i].effective_action = mapped_action[i];
12007 #if DEBUG_PLAYER_ACTIONS
12008 DebugContinued("", "=> ");
12009 for (i = 0; i < MAX_PLAYERS; i++)
12010 DebugContinued("", "%d, ", stored_player[i].effective_action);
12011 DebugContinued("game:playing:player", "\n");
12014 #if DEBUG_PLAYER_ACTIONS
12017 for (i = 0; i < MAX_PLAYERS; i++)
12018 DebugContinued("", "%d, ", stored_player[i].effective_action);
12019 DebugContinued("game:playing:player", "\n");
12024 for (i = 0; i < MAX_PLAYERS; i++)
12026 // allow engine snapshot in case of changed movement attempt
12027 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12028 (stored_player[i].effective_action & KEY_MOTION))
12029 game.snapshot.changed_action = TRUE;
12031 // allow engine snapshot in case of snapping/dropping attempt
12032 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12033 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12034 game.snapshot.changed_action = TRUE;
12036 game.snapshot.last_action[i] = stored_player[i].effective_action;
12039 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12041 GameActions_EM_Main();
12043 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12045 GameActions_SP_Main();
12047 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12049 GameActions_MM_Main();
12053 GameActions_RND_Main();
12056 BlitScreenToBitmap(backbuffer);
12058 CheckLevelSolved();
12061 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12063 if (global.show_frames_per_second)
12065 static unsigned int fps_counter = 0;
12066 static int fps_frames = 0;
12067 unsigned int fps_delay_ms = Counter() - fps_counter;
12071 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12073 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12076 fps_counter = Counter();
12078 // always draw FPS to screen after FPS value was updated
12079 redraw_mask |= REDRAW_FPS;
12082 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12083 if (GetDrawDeactivationMask() == REDRAW_NONE)
12084 redraw_mask |= REDRAW_FPS;
12088 static void GameActions_CheckSaveEngineSnapshot(void)
12090 if (!game.snapshot.save_snapshot)
12093 // clear flag for saving snapshot _before_ saving snapshot
12094 game.snapshot.save_snapshot = FALSE;
12096 SaveEngineSnapshotToList();
12099 void GameActions(void)
12103 GameActions_CheckSaveEngineSnapshot();
12106 void GameActions_EM_Main(void)
12108 byte effective_action[MAX_PLAYERS];
12111 for (i = 0; i < MAX_PLAYERS; i++)
12112 effective_action[i] = stored_player[i].effective_action;
12114 GameActions_EM(effective_action);
12117 void GameActions_SP_Main(void)
12119 byte effective_action[MAX_PLAYERS];
12122 for (i = 0; i < MAX_PLAYERS; i++)
12123 effective_action[i] = stored_player[i].effective_action;
12125 GameActions_SP(effective_action);
12127 for (i = 0; i < MAX_PLAYERS; i++)
12129 if (stored_player[i].force_dropping)
12130 stored_player[i].action |= KEY_BUTTON_DROP;
12132 stored_player[i].force_dropping = FALSE;
12136 void GameActions_MM_Main(void)
12140 GameActions_MM(local_player->effective_mouse_action);
12143 void GameActions_RND_Main(void)
12148 void GameActions_RND(void)
12150 static struct MouseActionInfo mouse_action_last = { 0 };
12151 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12152 int magic_wall_x = 0, magic_wall_y = 0;
12153 int i, x, y, element, graphic, last_gfx_frame;
12155 InitPlayfieldScanModeVars();
12157 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12159 SCAN_PLAYFIELD(x, y)
12161 ChangeCount[x][y] = 0;
12162 ChangeEvent[x][y] = -1;
12166 if (game.set_centered_player)
12168 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12170 // switching to "all players" only possible if all players fit to screen
12171 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12173 game.centered_player_nr_next = game.centered_player_nr;
12174 game.set_centered_player = FALSE;
12177 // do not switch focus to non-existing (or non-active) player
12178 if (game.centered_player_nr_next >= 0 &&
12179 !stored_player[game.centered_player_nr_next].active)
12181 game.centered_player_nr_next = game.centered_player_nr;
12182 game.set_centered_player = FALSE;
12186 if (game.set_centered_player &&
12187 ScreenMovPos == 0) // screen currently aligned at tile position
12191 if (game.centered_player_nr_next == -1)
12193 setScreenCenteredToAllPlayers(&sx, &sy);
12197 sx = stored_player[game.centered_player_nr_next].jx;
12198 sy = stored_player[game.centered_player_nr_next].jy;
12201 game.centered_player_nr = game.centered_player_nr_next;
12202 game.set_centered_player = FALSE;
12204 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12205 DrawGameDoorValues();
12208 // check single step mode (set flag and clear again if any player is active)
12209 game.enter_single_step_mode =
12210 (tape.single_step && tape.recording && !tape.pausing);
12212 for (i = 0; i < MAX_PLAYERS; i++)
12214 int actual_player_action = stored_player[i].effective_action;
12217 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12218 - rnd_equinox_tetrachloride 048
12219 - rnd_equinox_tetrachloride_ii 096
12220 - rnd_emanuel_schmieg 002
12221 - doctor_sloan_ww 001, 020
12223 if (stored_player[i].MovPos == 0)
12224 CheckGravityMovement(&stored_player[i]);
12227 // overwrite programmed action with tape action
12228 if (stored_player[i].programmed_action)
12229 actual_player_action = stored_player[i].programmed_action;
12231 PlayerActions(&stored_player[i], actual_player_action);
12233 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12236 // single step pause mode may already have been toggled by "ScrollPlayer()"
12237 if (game.enter_single_step_mode && !tape.pausing)
12238 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12240 ScrollScreen(NULL, SCROLL_GO_ON);
12242 /* for backwards compatibility, the following code emulates a fixed bug that
12243 occured when pushing elements (causing elements that just made their last
12244 pushing step to already (if possible) make their first falling step in the
12245 same game frame, which is bad); this code is also needed to use the famous
12246 "spring push bug" which is used in older levels and might be wanted to be
12247 used also in newer levels, but in this case the buggy pushing code is only
12248 affecting the "spring" element and no other elements */
12250 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12252 for (i = 0; i < MAX_PLAYERS; i++)
12254 struct PlayerInfo *player = &stored_player[i];
12255 int x = player->jx;
12256 int y = player->jy;
12258 if (player->active && player->is_pushing && player->is_moving &&
12260 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12261 Tile[x][y] == EL_SPRING))
12263 ContinueMoving(x, y);
12265 // continue moving after pushing (this is actually a bug)
12266 if (!IS_MOVING(x, y))
12267 Stop[x][y] = FALSE;
12272 SCAN_PLAYFIELD(x, y)
12274 Last[x][y] = Tile[x][y];
12276 ChangeCount[x][y] = 0;
12277 ChangeEvent[x][y] = -1;
12279 // this must be handled before main playfield loop
12280 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12283 if (MovDelay[x][y] <= 0)
12287 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12290 if (MovDelay[x][y] <= 0)
12292 int element = Store[x][y];
12293 int move_direction = MovDir[x][y];
12294 int player_index_bit = Store2[x][y];
12300 TEST_DrawLevelField(x, y);
12302 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12304 if (IS_ENVELOPE(element))
12305 local_player->show_envelope = element;
12310 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12312 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12314 Debug("game:playing:GameActions_RND", "This should never happen!");
12316 ChangePage[x][y] = -1;
12320 Stop[x][y] = FALSE;
12321 if (WasJustMoving[x][y] > 0)
12322 WasJustMoving[x][y]--;
12323 if (WasJustFalling[x][y] > 0)
12324 WasJustFalling[x][y]--;
12325 if (CheckCollision[x][y] > 0)
12326 CheckCollision[x][y]--;
12327 if (CheckImpact[x][y] > 0)
12328 CheckImpact[x][y]--;
12332 /* reset finished pushing action (not done in ContinueMoving() to allow
12333 continuous pushing animation for elements with zero push delay) */
12334 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12336 ResetGfxAnimation(x, y);
12337 TEST_DrawLevelField(x, y);
12341 if (IS_BLOCKED(x, y))
12345 Blocked2Moving(x, y, &oldx, &oldy);
12346 if (!IS_MOVING(oldx, oldy))
12348 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12349 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12350 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12351 Debug("game:playing:GameActions_RND", "This should never happen!");
12357 HandleMouseAction(&mouse_action, &mouse_action_last);
12359 SCAN_PLAYFIELD(x, y)
12361 element = Tile[x][y];
12362 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12363 last_gfx_frame = GfxFrame[x][y];
12365 if (element == EL_EMPTY)
12366 graphic = el2img(GfxElementEmpty[x][y]);
12368 ResetGfxFrame(x, y);
12370 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12371 DrawLevelGraphicAnimation(x, y, graphic);
12373 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12374 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12375 ResetRandomAnimationValue(x, y);
12377 SetRandomAnimationValue(x, y);
12379 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12381 if (IS_INACTIVE(element))
12383 if (IS_ANIMATED(graphic))
12384 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12389 // this may take place after moving, so 'element' may have changed
12390 if (IS_CHANGING(x, y) &&
12391 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12393 int page = element_info[element].event_page_nr[CE_DELAY];
12395 HandleElementChange(x, y, page);
12397 element = Tile[x][y];
12398 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12401 CheckNextToConditions(x, y);
12403 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12407 element = Tile[x][y];
12408 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12410 if (IS_ANIMATED(graphic) &&
12411 !IS_MOVING(x, y) &&
12413 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12415 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12416 TEST_DrawTwinkleOnField(x, y);
12418 else if (element == EL_ACID)
12421 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12423 else if ((element == EL_EXIT_OPEN ||
12424 element == EL_EM_EXIT_OPEN ||
12425 element == EL_SP_EXIT_OPEN ||
12426 element == EL_STEEL_EXIT_OPEN ||
12427 element == EL_EM_STEEL_EXIT_OPEN ||
12428 element == EL_SP_TERMINAL ||
12429 element == EL_SP_TERMINAL_ACTIVE ||
12430 element == EL_EXTRA_TIME ||
12431 element == EL_SHIELD_NORMAL ||
12432 element == EL_SHIELD_DEADLY) &&
12433 IS_ANIMATED(graphic))
12434 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12435 else if (IS_MOVING(x, y))
12436 ContinueMoving(x, y);
12437 else if (IS_ACTIVE_BOMB(element))
12438 CheckDynamite(x, y);
12439 else if (element == EL_AMOEBA_GROWING)
12440 AmoebaGrowing(x, y);
12441 else if (element == EL_AMOEBA_SHRINKING)
12442 AmoebaShrinking(x, y);
12444 #if !USE_NEW_AMOEBA_CODE
12445 else if (IS_AMOEBALIVE(element))
12446 AmoebaReproduce(x, y);
12449 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12451 else if (element == EL_EXIT_CLOSED)
12453 else if (element == EL_EM_EXIT_CLOSED)
12455 else if (element == EL_STEEL_EXIT_CLOSED)
12456 CheckExitSteel(x, y);
12457 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12458 CheckExitSteelEM(x, y);
12459 else if (element == EL_SP_EXIT_CLOSED)
12461 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12462 element == EL_EXPANDABLE_STEELWALL_GROWING)
12464 else if (element == EL_EXPANDABLE_WALL ||
12465 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12466 element == EL_EXPANDABLE_WALL_VERTICAL ||
12467 element == EL_EXPANDABLE_WALL_ANY ||
12468 element == EL_BD_EXPANDABLE_WALL ||
12469 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12470 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12471 element == EL_EXPANDABLE_STEELWALL_ANY)
12472 CheckWallGrowing(x, y);
12473 else if (element == EL_FLAMES)
12474 CheckForDragon(x, y);
12475 else if (element == EL_EXPLOSION)
12476 ; // drawing of correct explosion animation is handled separately
12477 else if (element == EL_ELEMENT_SNAPPING ||
12478 element == EL_DIAGONAL_SHRINKING ||
12479 element == EL_DIAGONAL_GROWING)
12481 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12483 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12485 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12486 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12488 if (IS_BELT_ACTIVE(element))
12489 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12491 if (game.magic_wall_active)
12493 int jx = local_player->jx, jy = local_player->jy;
12495 // play the element sound at the position nearest to the player
12496 if ((element == EL_MAGIC_WALL_FULL ||
12497 element == EL_MAGIC_WALL_ACTIVE ||
12498 element == EL_MAGIC_WALL_EMPTYING ||
12499 element == EL_BD_MAGIC_WALL_FULL ||
12500 element == EL_BD_MAGIC_WALL_ACTIVE ||
12501 element == EL_BD_MAGIC_WALL_EMPTYING ||
12502 element == EL_DC_MAGIC_WALL_FULL ||
12503 element == EL_DC_MAGIC_WALL_ACTIVE ||
12504 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12505 ABS(x - jx) + ABS(y - jy) <
12506 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12514 #if USE_NEW_AMOEBA_CODE
12515 // new experimental amoeba growth stuff
12516 if (!(FrameCounter % 8))
12518 static unsigned int random = 1684108901;
12520 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12522 x = RND(lev_fieldx);
12523 y = RND(lev_fieldy);
12524 element = Tile[x][y];
12526 if (!IS_PLAYER(x, y) &&
12527 (element == EL_EMPTY ||
12528 CAN_GROW_INTO(element) ||
12529 element == EL_QUICKSAND_EMPTY ||
12530 element == EL_QUICKSAND_FAST_EMPTY ||
12531 element == EL_ACID_SPLASH_LEFT ||
12532 element == EL_ACID_SPLASH_RIGHT))
12534 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12535 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12536 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12537 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12538 Tile[x][y] = EL_AMOEBA_DROP;
12541 random = random * 129 + 1;
12546 game.explosions_delayed = FALSE;
12548 SCAN_PLAYFIELD(x, y)
12550 element = Tile[x][y];
12552 if (ExplodeField[x][y])
12553 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12554 else if (element == EL_EXPLOSION)
12555 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12557 ExplodeField[x][y] = EX_TYPE_NONE;
12560 game.explosions_delayed = TRUE;
12562 if (game.magic_wall_active)
12564 if (!(game.magic_wall_time_left % 4))
12566 int element = Tile[magic_wall_x][magic_wall_y];
12568 if (element == EL_BD_MAGIC_WALL_FULL ||
12569 element == EL_BD_MAGIC_WALL_ACTIVE ||
12570 element == EL_BD_MAGIC_WALL_EMPTYING)
12571 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12572 else if (element == EL_DC_MAGIC_WALL_FULL ||
12573 element == EL_DC_MAGIC_WALL_ACTIVE ||
12574 element == EL_DC_MAGIC_WALL_EMPTYING)
12575 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12577 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12580 if (game.magic_wall_time_left > 0)
12582 game.magic_wall_time_left--;
12584 if (!game.magic_wall_time_left)
12586 SCAN_PLAYFIELD(x, y)
12588 element = Tile[x][y];
12590 if (element == EL_MAGIC_WALL_ACTIVE ||
12591 element == EL_MAGIC_WALL_FULL)
12593 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12594 TEST_DrawLevelField(x, y);
12596 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12597 element == EL_BD_MAGIC_WALL_FULL)
12599 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12600 TEST_DrawLevelField(x, y);
12602 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12603 element == EL_DC_MAGIC_WALL_FULL)
12605 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12606 TEST_DrawLevelField(x, y);
12610 game.magic_wall_active = FALSE;
12615 if (game.light_time_left > 0)
12617 game.light_time_left--;
12619 if (game.light_time_left == 0)
12620 RedrawAllLightSwitchesAndInvisibleElements();
12623 if (game.timegate_time_left > 0)
12625 game.timegate_time_left--;
12627 if (game.timegate_time_left == 0)
12628 CloseAllOpenTimegates();
12631 if (game.lenses_time_left > 0)
12633 game.lenses_time_left--;
12635 if (game.lenses_time_left == 0)
12636 RedrawAllInvisibleElementsForLenses();
12639 if (game.magnify_time_left > 0)
12641 game.magnify_time_left--;
12643 if (game.magnify_time_left == 0)
12644 RedrawAllInvisibleElementsForMagnifier();
12647 for (i = 0; i < MAX_PLAYERS; i++)
12649 struct PlayerInfo *player = &stored_player[i];
12651 if (SHIELD_ON(player))
12653 if (player->shield_deadly_time_left)
12654 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12655 else if (player->shield_normal_time_left)
12656 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12660 #if USE_DELAYED_GFX_REDRAW
12661 SCAN_PLAYFIELD(x, y)
12663 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12665 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12666 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12668 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12669 DrawLevelField(x, y);
12671 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12672 DrawLevelFieldCrumbled(x, y);
12674 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12675 DrawLevelFieldCrumbledNeighbours(x, y);
12677 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12678 DrawTwinkleOnField(x, y);
12681 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12686 PlayAllPlayersSound();
12688 for (i = 0; i < MAX_PLAYERS; i++)
12690 struct PlayerInfo *player = &stored_player[i];
12692 if (player->show_envelope != 0 && (!player->active ||
12693 player->MovPos == 0))
12695 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12697 player->show_envelope = 0;
12701 // use random number generator in every frame to make it less predictable
12702 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12705 mouse_action_last = mouse_action;
12708 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12710 int min_x = x, min_y = y, max_x = x, max_y = y;
12711 int scr_fieldx = getScreenFieldSizeX();
12712 int scr_fieldy = getScreenFieldSizeY();
12715 for (i = 0; i < MAX_PLAYERS; i++)
12717 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12719 if (!stored_player[i].active || &stored_player[i] == player)
12722 min_x = MIN(min_x, jx);
12723 min_y = MIN(min_y, jy);
12724 max_x = MAX(max_x, jx);
12725 max_y = MAX(max_y, jy);
12728 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12731 static boolean AllPlayersInVisibleScreen(void)
12735 for (i = 0; i < MAX_PLAYERS; i++)
12737 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12739 if (!stored_player[i].active)
12742 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12749 void ScrollLevel(int dx, int dy)
12751 int scroll_offset = 2 * TILEX_VAR;
12754 BlitBitmap(drawto_field, drawto_field,
12755 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12756 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12757 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12758 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12759 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12760 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12764 x = (dx == 1 ? BX1 : BX2);
12765 for (y = BY1; y <= BY2; y++)
12766 DrawScreenField(x, y);
12771 y = (dy == 1 ? BY1 : BY2);
12772 for (x = BX1; x <= BX2; x++)
12773 DrawScreenField(x, y);
12776 redraw_mask |= REDRAW_FIELD;
12779 static boolean canFallDown(struct PlayerInfo *player)
12781 int jx = player->jx, jy = player->jy;
12783 return (IN_LEV_FIELD(jx, jy + 1) &&
12784 (IS_FREE(jx, jy + 1) ||
12785 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12786 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12787 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12790 static boolean canPassField(int x, int y, int move_dir)
12792 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12793 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12794 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12795 int nextx = x + dx;
12796 int nexty = y + dy;
12797 int element = Tile[x][y];
12799 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12800 !CAN_MOVE(element) &&
12801 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12802 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12803 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12806 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12808 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12809 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12810 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12814 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12815 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12816 (IS_DIGGABLE(Tile[newx][newy]) ||
12817 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12818 canPassField(newx, newy, move_dir)));
12821 static void CheckGravityMovement(struct PlayerInfo *player)
12823 if (player->gravity && !player->programmed_action)
12825 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12826 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12827 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12828 int jx = player->jx, jy = player->jy;
12829 boolean player_is_moving_to_valid_field =
12830 (!player_is_snapping &&
12831 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12832 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12833 boolean player_can_fall_down = canFallDown(player);
12835 if (player_can_fall_down &&
12836 !player_is_moving_to_valid_field)
12837 player->programmed_action = MV_DOWN;
12841 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12843 return CheckGravityMovement(player);
12845 if (player->gravity && !player->programmed_action)
12847 int jx = player->jx, jy = player->jy;
12848 boolean field_under_player_is_free =
12849 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12850 boolean player_is_standing_on_valid_field =
12851 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12852 (IS_WALKABLE(Tile[jx][jy]) &&
12853 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12855 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12856 player->programmed_action = MV_DOWN;
12861 MovePlayerOneStep()
12862 -----------------------------------------------------------------------------
12863 dx, dy: direction (non-diagonal) to try to move the player to
12864 real_dx, real_dy: direction as read from input device (can be diagonal)
12867 boolean MovePlayerOneStep(struct PlayerInfo *player,
12868 int dx, int dy, int real_dx, int real_dy)
12870 int jx = player->jx, jy = player->jy;
12871 int new_jx = jx + dx, new_jy = jy + dy;
12873 boolean player_can_move = !player->cannot_move;
12875 if (!player->active || (!dx && !dy))
12876 return MP_NO_ACTION;
12878 player->MovDir = (dx < 0 ? MV_LEFT :
12879 dx > 0 ? MV_RIGHT :
12881 dy > 0 ? MV_DOWN : MV_NONE);
12883 if (!IN_LEV_FIELD(new_jx, new_jy))
12884 return MP_NO_ACTION;
12886 if (!player_can_move)
12888 if (player->MovPos == 0)
12890 player->is_moving = FALSE;
12891 player->is_digging = FALSE;
12892 player->is_collecting = FALSE;
12893 player->is_snapping = FALSE;
12894 player->is_pushing = FALSE;
12898 if (!network.enabled && game.centered_player_nr == -1 &&
12899 !AllPlayersInSight(player, new_jx, new_jy))
12900 return MP_NO_ACTION;
12902 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
12903 if (can_move != MP_MOVING)
12906 // check if DigField() has caused relocation of the player
12907 if (player->jx != jx || player->jy != jy)
12908 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12910 StorePlayer[jx][jy] = 0;
12911 player->last_jx = jx;
12912 player->last_jy = jy;
12913 player->jx = new_jx;
12914 player->jy = new_jy;
12915 StorePlayer[new_jx][new_jy] = player->element_nr;
12917 if (player->move_delay_value_next != -1)
12919 player->move_delay_value = player->move_delay_value_next;
12920 player->move_delay_value_next = -1;
12924 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12926 player->step_counter++;
12928 PlayerVisit[jx][jy] = FrameCounter;
12930 player->is_moving = TRUE;
12933 // should better be called in MovePlayer(), but this breaks some tapes
12934 ScrollPlayer(player, SCROLL_INIT);
12940 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12942 int jx = player->jx, jy = player->jy;
12943 int old_jx = jx, old_jy = jy;
12944 int moved = MP_NO_ACTION;
12946 if (!player->active)
12951 if (player->MovPos == 0)
12953 player->is_moving = FALSE;
12954 player->is_digging = FALSE;
12955 player->is_collecting = FALSE;
12956 player->is_snapping = FALSE;
12957 player->is_pushing = FALSE;
12963 if (player->move_delay > 0)
12966 player->move_delay = -1; // set to "uninitialized" value
12968 // store if player is automatically moved to next field
12969 player->is_auto_moving = (player->programmed_action != MV_NONE);
12971 // remove the last programmed player action
12972 player->programmed_action = 0;
12974 if (player->MovPos)
12976 // should only happen if pre-1.2 tape recordings are played
12977 // this is only for backward compatibility
12979 int original_move_delay_value = player->move_delay_value;
12982 Debug("game:playing:MovePlayer",
12983 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12987 // scroll remaining steps with finest movement resolution
12988 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12990 while (player->MovPos)
12992 ScrollPlayer(player, SCROLL_GO_ON);
12993 ScrollScreen(NULL, SCROLL_GO_ON);
12995 AdvanceFrameAndPlayerCounters(player->index_nr);
12998 BackToFront_WithFrameDelay(0);
13001 player->move_delay_value = original_move_delay_value;
13004 player->is_active = FALSE;
13006 if (player->last_move_dir & MV_HORIZONTAL)
13008 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13009 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13013 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13014 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13017 if (!moved && !player->is_active)
13019 player->is_moving = FALSE;
13020 player->is_digging = FALSE;
13021 player->is_collecting = FALSE;
13022 player->is_snapping = FALSE;
13023 player->is_pushing = FALSE;
13029 if (moved & MP_MOVING && !ScreenMovPos &&
13030 (player->index_nr == game.centered_player_nr ||
13031 game.centered_player_nr == -1))
13033 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13035 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13037 // actual player has left the screen -- scroll in that direction
13038 if (jx != old_jx) // player has moved horizontally
13039 scroll_x += (jx - old_jx);
13040 else // player has moved vertically
13041 scroll_y += (jy - old_jy);
13045 int offset_raw = game.scroll_delay_value;
13047 if (jx != old_jx) // player has moved horizontally
13049 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13050 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13051 int new_scroll_x = jx - MIDPOSX + offset_x;
13053 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13054 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13055 scroll_x = new_scroll_x;
13057 // don't scroll over playfield boundaries
13058 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13060 // don't scroll more than one field at a time
13061 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13063 // don't scroll against the player's moving direction
13064 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13065 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13066 scroll_x = old_scroll_x;
13068 else // player has moved vertically
13070 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13071 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13072 int new_scroll_y = jy - MIDPOSY + offset_y;
13074 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13075 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13076 scroll_y = new_scroll_y;
13078 // don't scroll over playfield boundaries
13079 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13081 // don't scroll more than one field at a time
13082 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13084 // don't scroll against the player's moving direction
13085 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13086 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13087 scroll_y = old_scroll_y;
13091 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13093 if (!network.enabled && game.centered_player_nr == -1 &&
13094 !AllPlayersInVisibleScreen())
13096 scroll_x = old_scroll_x;
13097 scroll_y = old_scroll_y;
13101 ScrollScreen(player, SCROLL_INIT);
13102 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13107 player->StepFrame = 0;
13109 if (moved & MP_MOVING)
13111 if (old_jx != jx && old_jy == jy)
13112 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13113 else if (old_jx == jx && old_jy != jy)
13114 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13116 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13118 player->last_move_dir = player->MovDir;
13119 player->is_moving = TRUE;
13120 player->is_snapping = FALSE;
13121 player->is_switching = FALSE;
13122 player->is_dropping = FALSE;
13123 player->is_dropping_pressed = FALSE;
13124 player->drop_pressed_delay = 0;
13127 // should better be called here than above, but this breaks some tapes
13128 ScrollPlayer(player, SCROLL_INIT);
13133 CheckGravityMovementWhenNotMoving(player);
13135 player->is_moving = FALSE;
13137 /* at this point, the player is allowed to move, but cannot move right now
13138 (e.g. because of something blocking the way) -- ensure that the player
13139 is also allowed to move in the next frame (in old versions before 3.1.1,
13140 the player was forced to wait again for eight frames before next try) */
13142 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13143 player->move_delay = 0; // allow direct movement in the next frame
13146 if (player->move_delay == -1) // not yet initialized by DigField()
13147 player->move_delay = player->move_delay_value;
13149 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13151 TestIfPlayerTouchesBadThing(jx, jy);
13152 TestIfPlayerTouchesCustomElement(jx, jy);
13155 if (!player->active)
13156 RemovePlayer(player);
13161 void ScrollPlayer(struct PlayerInfo *player, int mode)
13163 int jx = player->jx, jy = player->jy;
13164 int last_jx = player->last_jx, last_jy = player->last_jy;
13165 int move_stepsize = TILEX / player->move_delay_value;
13167 if (!player->active)
13170 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13173 if (mode == SCROLL_INIT)
13175 player->actual_frame_counter.count = FrameCounter;
13176 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13178 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13179 Tile[last_jx][last_jy] == EL_EMPTY)
13181 int last_field_block_delay = 0; // start with no blocking at all
13182 int block_delay_adjustment = player->block_delay_adjustment;
13184 // if player blocks last field, add delay for exactly one move
13185 if (player->block_last_field)
13187 last_field_block_delay += player->move_delay_value;
13189 // when blocking enabled, prevent moving up despite gravity
13190 if (player->gravity && player->MovDir == MV_UP)
13191 block_delay_adjustment = -1;
13194 // add block delay adjustment (also possible when not blocking)
13195 last_field_block_delay += block_delay_adjustment;
13197 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13198 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13201 if (player->MovPos != 0) // player has not yet reached destination
13204 else if (!FrameReached(&player->actual_frame_counter))
13207 if (player->MovPos != 0)
13209 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13210 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13212 // before DrawPlayer() to draw correct player graphic for this case
13213 if (player->MovPos == 0)
13214 CheckGravityMovement(player);
13217 if (player->MovPos == 0) // player reached destination field
13219 if (player->move_delay_reset_counter > 0)
13221 player->move_delay_reset_counter--;
13223 if (player->move_delay_reset_counter == 0)
13225 // continue with normal speed after quickly moving through gate
13226 HALVE_PLAYER_SPEED(player);
13228 // be able to make the next move without delay
13229 player->move_delay = 0;
13233 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13234 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13235 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13236 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13237 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13238 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13239 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13240 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13242 ExitPlayer(player);
13244 if (game.players_still_needed == 0 &&
13245 (game.friends_still_needed == 0 ||
13246 IS_SP_ELEMENT(Tile[jx][jy])))
13250 player->last_jx = jx;
13251 player->last_jy = jy;
13253 // this breaks one level: "machine", level 000
13255 int move_direction = player->MovDir;
13256 int enter_side = MV_DIR_OPPOSITE(move_direction);
13257 int leave_side = move_direction;
13258 int old_jx = last_jx;
13259 int old_jy = last_jy;
13260 int old_element = Tile[old_jx][old_jy];
13261 int new_element = Tile[jx][jy];
13263 if (IS_CUSTOM_ELEMENT(old_element))
13264 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13266 player->index_bit, leave_side);
13268 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13269 CE_PLAYER_LEAVES_X,
13270 player->index_bit, leave_side);
13272 // needed because pushed element has not yet reached its destination,
13273 // so it would trigger a change event at its previous field location
13274 if (!player->is_pushing)
13276 if (IS_CUSTOM_ELEMENT(new_element))
13277 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13278 player->index_bit, enter_side);
13280 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13281 CE_PLAYER_ENTERS_X,
13282 player->index_bit, enter_side);
13285 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13286 CE_MOVE_OF_X, move_direction);
13289 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13291 TestIfPlayerTouchesBadThing(jx, jy);
13292 TestIfPlayerTouchesCustomElement(jx, jy);
13294 // needed because pushed element has not yet reached its destination,
13295 // so it would trigger a change event at its previous field location
13296 if (!player->is_pushing)
13297 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13299 if (level.finish_dig_collect &&
13300 (player->is_digging || player->is_collecting))
13302 int last_element = player->last_removed_element;
13303 int move_direction = player->MovDir;
13304 int enter_side = MV_DIR_OPPOSITE(move_direction);
13305 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13306 CE_PLAYER_COLLECTS_X);
13308 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13309 player->index_bit, enter_side);
13311 player->last_removed_element = EL_UNDEFINED;
13314 if (!player->active)
13315 RemovePlayer(player);
13318 if (level.use_step_counter)
13319 CheckLevelTime_StepCounter();
13321 if (tape.single_step && tape.recording && !tape.pausing &&
13322 !player->programmed_action)
13323 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13325 if (!player->programmed_action)
13326 CheckSaveEngineSnapshot(player);
13330 void ScrollScreen(struct PlayerInfo *player, int mode)
13332 static DelayCounter screen_frame_counter = { 0 };
13334 if (mode == SCROLL_INIT)
13336 // set scrolling step size according to actual player's moving speed
13337 ScrollStepSize = TILEX / player->move_delay_value;
13339 screen_frame_counter.count = FrameCounter;
13340 screen_frame_counter.value = 1;
13342 ScreenMovDir = player->MovDir;
13343 ScreenMovPos = player->MovPos;
13344 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13347 else if (!FrameReached(&screen_frame_counter))
13352 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13353 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13354 redraw_mask |= REDRAW_FIELD;
13357 ScreenMovDir = MV_NONE;
13360 void CheckNextToConditions(int x, int y)
13362 int element = Tile[x][y];
13364 if (IS_PLAYER(x, y))
13365 TestIfPlayerNextToCustomElement(x, y);
13367 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13368 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13369 TestIfElementNextToCustomElement(x, y);
13372 void TestIfPlayerNextToCustomElement(int x, int y)
13374 struct XY *xy = xy_topdown;
13375 static int trigger_sides[4][2] =
13377 // center side border side
13378 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13379 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13380 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13381 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13385 if (!IS_PLAYER(x, y))
13388 struct PlayerInfo *player = PLAYERINFO(x, y);
13390 if (player->is_moving)
13393 for (i = 0; i < NUM_DIRECTIONS; i++)
13395 int xx = x + xy[i].x;
13396 int yy = y + xy[i].y;
13397 int border_side = trigger_sides[i][1];
13398 int border_element;
13400 if (!IN_LEV_FIELD(xx, yy))
13403 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13404 continue; // center and border element not connected
13406 border_element = Tile[xx][yy];
13408 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13409 player->index_bit, border_side);
13410 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13411 CE_PLAYER_NEXT_TO_X,
13412 player->index_bit, border_side);
13414 /* use player element that is initially defined in the level playfield,
13415 not the player element that corresponds to the runtime player number
13416 (example: a level that contains EL_PLAYER_3 as the only player would
13417 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13419 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13420 CE_NEXT_TO_X, border_side);
13424 void TestIfPlayerTouchesCustomElement(int x, int y)
13426 struct XY *xy = xy_topdown;
13427 static int trigger_sides[4][2] =
13429 // center side border side
13430 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13431 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13432 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13433 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13435 static int touch_dir[4] =
13437 MV_LEFT | MV_RIGHT,
13442 int center_element = Tile[x][y]; // should always be non-moving!
13445 for (i = 0; i < NUM_DIRECTIONS; i++)
13447 int xx = x + xy[i].x;
13448 int yy = y + xy[i].y;
13449 int center_side = trigger_sides[i][0];
13450 int border_side = trigger_sides[i][1];
13451 int border_element;
13453 if (!IN_LEV_FIELD(xx, yy))
13456 if (IS_PLAYER(x, y)) // player found at center element
13458 struct PlayerInfo *player = PLAYERINFO(x, y);
13460 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13461 border_element = Tile[xx][yy]; // may be moving!
13462 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13463 border_element = Tile[xx][yy];
13464 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13465 border_element = MovingOrBlocked2Element(xx, yy);
13467 continue; // center and border element do not touch
13469 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13470 player->index_bit, border_side);
13471 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13472 CE_PLAYER_TOUCHES_X,
13473 player->index_bit, border_side);
13476 /* use player element that is initially defined in the level playfield,
13477 not the player element that corresponds to the runtime player number
13478 (example: a level that contains EL_PLAYER_3 as the only player would
13479 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13480 int player_element = PLAYERINFO(x, y)->initial_element;
13482 // as element "X" is the player here, check opposite (center) side
13483 CheckElementChangeBySide(xx, yy, border_element, player_element,
13484 CE_TOUCHING_X, center_side);
13487 else if (IS_PLAYER(xx, yy)) // player found at border element
13489 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13491 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13493 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13494 continue; // center and border element do not touch
13497 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13498 player->index_bit, center_side);
13499 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13500 CE_PLAYER_TOUCHES_X,
13501 player->index_bit, center_side);
13504 /* use player element that is initially defined in the level playfield,
13505 not the player element that corresponds to the runtime player number
13506 (example: a level that contains EL_PLAYER_3 as the only player would
13507 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13508 int player_element = PLAYERINFO(xx, yy)->initial_element;
13510 // as element "X" is the player here, check opposite (border) side
13511 CheckElementChangeBySide(x, y, center_element, player_element,
13512 CE_TOUCHING_X, border_side);
13520 void TestIfElementNextToCustomElement(int x, int y)
13522 struct XY *xy = xy_topdown;
13523 static int trigger_sides[4][2] =
13525 // center side border side
13526 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13527 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13528 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13529 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13531 int center_element = Tile[x][y]; // should always be non-moving!
13534 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13537 for (i = 0; i < NUM_DIRECTIONS; i++)
13539 int xx = x + xy[i].x;
13540 int yy = y + xy[i].y;
13541 int border_side = trigger_sides[i][1];
13542 int border_element;
13544 if (!IN_LEV_FIELD(xx, yy))
13547 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13548 continue; // center and border element not connected
13550 border_element = Tile[xx][yy];
13552 // check for change of center element (but change it only once)
13553 if (CheckElementChangeBySide(x, y, center_element, border_element,
13554 CE_NEXT_TO_X, border_side))
13559 void TestIfElementTouchesCustomElement(int x, int y)
13561 struct XY *xy = xy_topdown;
13562 static int trigger_sides[4][2] =
13564 // center side border side
13565 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13566 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13567 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13568 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13570 static int touch_dir[4] =
13572 MV_LEFT | MV_RIGHT,
13577 boolean change_center_element = FALSE;
13578 int center_element = Tile[x][y]; // should always be non-moving!
13579 int border_element_old[NUM_DIRECTIONS];
13582 for (i = 0; i < NUM_DIRECTIONS; i++)
13584 int xx = x + xy[i].x;
13585 int yy = y + xy[i].y;
13586 int border_element;
13588 border_element_old[i] = -1;
13590 if (!IN_LEV_FIELD(xx, yy))
13593 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13594 border_element = Tile[xx][yy]; // may be moving!
13595 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13596 border_element = Tile[xx][yy];
13597 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13598 border_element = MovingOrBlocked2Element(xx, yy);
13600 continue; // center and border element do not touch
13602 border_element_old[i] = border_element;
13605 for (i = 0; i < NUM_DIRECTIONS; i++)
13607 int xx = x + xy[i].x;
13608 int yy = y + xy[i].y;
13609 int center_side = trigger_sides[i][0];
13610 int border_element = border_element_old[i];
13612 if (border_element == -1)
13615 // check for change of border element
13616 CheckElementChangeBySide(xx, yy, border_element, center_element,
13617 CE_TOUCHING_X, center_side);
13619 // (center element cannot be player, so we don't have to check this here)
13622 for (i = 0; i < NUM_DIRECTIONS; i++)
13624 int xx = x + xy[i].x;
13625 int yy = y + xy[i].y;
13626 int border_side = trigger_sides[i][1];
13627 int border_element = border_element_old[i];
13629 if (border_element == -1)
13632 // check for change of center element (but change it only once)
13633 if (!change_center_element)
13634 change_center_element =
13635 CheckElementChangeBySide(x, y, center_element, border_element,
13636 CE_TOUCHING_X, border_side);
13638 if (IS_PLAYER(xx, yy))
13640 /* use player element that is initially defined in the level playfield,
13641 not the player element that corresponds to the runtime player number
13642 (example: a level that contains EL_PLAYER_3 as the only player would
13643 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13644 int player_element = PLAYERINFO(xx, yy)->initial_element;
13646 // as element "X" is the player here, check opposite (border) side
13647 CheckElementChangeBySide(x, y, center_element, player_element,
13648 CE_TOUCHING_X, border_side);
13653 void TestIfElementHitsCustomElement(int x, int y, int direction)
13655 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13656 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13657 int hitx = x + dx, hity = y + dy;
13658 int hitting_element = Tile[x][y];
13659 int touched_element;
13661 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13664 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13665 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13667 if (IN_LEV_FIELD(hitx, hity))
13669 int opposite_direction = MV_DIR_OPPOSITE(direction);
13670 int hitting_side = direction;
13671 int touched_side = opposite_direction;
13672 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13673 MovDir[hitx][hity] != direction ||
13674 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13680 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13681 CE_HITTING_X, touched_side);
13683 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13684 CE_HIT_BY_X, hitting_side);
13686 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13687 CE_HIT_BY_SOMETHING, opposite_direction);
13689 if (IS_PLAYER(hitx, hity))
13691 /* use player element that is initially defined in the level playfield,
13692 not the player element that corresponds to the runtime player number
13693 (example: a level that contains EL_PLAYER_3 as the only player would
13694 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13695 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13697 CheckElementChangeBySide(x, y, hitting_element, player_element,
13698 CE_HITTING_X, touched_side);
13703 // "hitting something" is also true when hitting the playfield border
13704 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13705 CE_HITTING_SOMETHING, direction);
13708 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13710 int i, kill_x = -1, kill_y = -1;
13712 int bad_element = -1;
13713 struct XY *test_xy = xy_topdown;
13714 static int test_dir[4] =
13722 for (i = 0; i < NUM_DIRECTIONS; i++)
13724 int test_x, test_y, test_move_dir, test_element;
13726 test_x = good_x + test_xy[i].x;
13727 test_y = good_y + test_xy[i].y;
13729 if (!IN_LEV_FIELD(test_x, test_y))
13733 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13735 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13737 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13738 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13740 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13741 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13745 bad_element = test_element;
13751 if (kill_x != -1 || kill_y != -1)
13753 if (IS_PLAYER(good_x, good_y))
13755 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13757 if (player->shield_deadly_time_left > 0 &&
13758 !IS_INDESTRUCTIBLE(bad_element))
13759 Bang(kill_x, kill_y);
13760 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13761 KillPlayer(player);
13764 Bang(good_x, good_y);
13768 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13770 int i, kill_x = -1, kill_y = -1;
13771 int bad_element = Tile[bad_x][bad_y];
13772 struct XY *test_xy = xy_topdown;
13773 static int touch_dir[4] =
13775 MV_LEFT | MV_RIGHT,
13780 static int test_dir[4] =
13788 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13791 for (i = 0; i < NUM_DIRECTIONS; i++)
13793 int test_x, test_y, test_move_dir, test_element;
13795 test_x = bad_x + test_xy[i].x;
13796 test_y = bad_y + test_xy[i].y;
13798 if (!IN_LEV_FIELD(test_x, test_y))
13802 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13804 test_element = Tile[test_x][test_y];
13806 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13807 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13809 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13810 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13812 // good thing is player or penguin that does not move away
13813 if (IS_PLAYER(test_x, test_y))
13815 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13817 if (bad_element == EL_ROBOT && player->is_moving)
13818 continue; // robot does not kill player if he is moving
13820 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13822 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13823 continue; // center and border element do not touch
13831 else if (test_element == EL_PENGUIN)
13841 if (kill_x != -1 || kill_y != -1)
13843 if (IS_PLAYER(kill_x, kill_y))
13845 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13847 if (player->shield_deadly_time_left > 0 &&
13848 !IS_INDESTRUCTIBLE(bad_element))
13849 Bang(bad_x, bad_y);
13850 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13851 KillPlayer(player);
13854 Bang(kill_x, kill_y);
13858 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13860 int bad_element = Tile[bad_x][bad_y];
13861 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13862 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13863 int test_x = bad_x + dx, test_y = bad_y + dy;
13864 int test_move_dir, test_element;
13865 int kill_x = -1, kill_y = -1;
13867 if (!IN_LEV_FIELD(test_x, test_y))
13871 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13873 test_element = Tile[test_x][test_y];
13875 if (test_move_dir != bad_move_dir)
13877 // good thing can be player or penguin that does not move away
13878 if (IS_PLAYER(test_x, test_y))
13880 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13882 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13883 player as being hit when he is moving towards the bad thing, because
13884 the "get hit by" condition would be lost after the player stops) */
13885 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13886 return; // player moves away from bad thing
13891 else if (test_element == EL_PENGUIN)
13898 if (kill_x != -1 || kill_y != -1)
13900 if (IS_PLAYER(kill_x, kill_y))
13902 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13904 if (player->shield_deadly_time_left > 0 &&
13905 !IS_INDESTRUCTIBLE(bad_element))
13906 Bang(bad_x, bad_y);
13907 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13908 KillPlayer(player);
13911 Bang(kill_x, kill_y);
13915 void TestIfPlayerTouchesBadThing(int x, int y)
13917 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13920 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13922 TestIfGoodThingHitsBadThing(x, y, move_dir);
13925 void TestIfBadThingTouchesPlayer(int x, int y)
13927 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13930 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13932 TestIfBadThingHitsGoodThing(x, y, move_dir);
13935 void TestIfFriendTouchesBadThing(int x, int y)
13937 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13940 void TestIfBadThingTouchesFriend(int x, int y)
13942 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13945 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13947 int i, kill_x = bad_x, kill_y = bad_y;
13948 struct XY *xy = xy_topdown;
13950 for (i = 0; i < NUM_DIRECTIONS; i++)
13954 x = bad_x + xy[i].x;
13955 y = bad_y + xy[i].y;
13956 if (!IN_LEV_FIELD(x, y))
13959 element = Tile[x][y];
13960 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13961 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13969 if (kill_x != bad_x || kill_y != bad_y)
13970 Bang(bad_x, bad_y);
13973 void KillPlayer(struct PlayerInfo *player)
13975 int jx = player->jx, jy = player->jy;
13977 if (!player->active)
13981 Debug("game:playing:KillPlayer",
13982 "0: killed == %d, active == %d, reanimated == %d",
13983 player->killed, player->active, player->reanimated);
13986 /* the following code was introduced to prevent an infinite loop when calling
13988 -> CheckTriggeredElementChangeExt()
13989 -> ExecuteCustomElementAction()
13991 -> (infinitely repeating the above sequence of function calls)
13992 which occurs when killing the player while having a CE with the setting
13993 "kill player X when explosion of <player X>"; the solution using a new
13994 field "player->killed" was chosen for backwards compatibility, although
13995 clever use of the fields "player->active" etc. would probably also work */
13997 if (player->killed)
14001 player->killed = TRUE;
14003 // remove accessible field at the player's position
14004 RemoveField(jx, jy);
14006 // deactivate shield (else Bang()/Explode() would not work right)
14007 player->shield_normal_time_left = 0;
14008 player->shield_deadly_time_left = 0;
14011 Debug("game:playing:KillPlayer",
14012 "1: killed == %d, active == %d, reanimated == %d",
14013 player->killed, player->active, player->reanimated);
14019 Debug("game:playing:KillPlayer",
14020 "2: killed == %d, active == %d, reanimated == %d",
14021 player->killed, player->active, player->reanimated);
14024 if (player->reanimated) // killed player may have been reanimated
14025 player->killed = player->reanimated = FALSE;
14027 BuryPlayer(player);
14030 static void KillPlayerUnlessEnemyProtected(int x, int y)
14032 if (!PLAYER_ENEMY_PROTECTED(x, y))
14033 KillPlayer(PLAYERINFO(x, y));
14036 static void KillPlayerUnlessExplosionProtected(int x, int y)
14038 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14039 KillPlayer(PLAYERINFO(x, y));
14042 void BuryPlayer(struct PlayerInfo *player)
14044 int jx = player->jx, jy = player->jy;
14046 if (!player->active)
14049 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14051 RemovePlayer(player);
14053 player->buried = TRUE;
14055 if (game.all_players_gone)
14056 game.GameOver = TRUE;
14059 void RemovePlayer(struct PlayerInfo *player)
14061 int jx = player->jx, jy = player->jy;
14062 int i, found = FALSE;
14064 player->present = FALSE;
14065 player->active = FALSE;
14067 // required for some CE actions (even if the player is not active anymore)
14068 player->MovPos = 0;
14070 if (!ExplodeField[jx][jy])
14071 StorePlayer[jx][jy] = 0;
14073 if (player->is_moving)
14074 TEST_DrawLevelField(player->last_jx, player->last_jy);
14076 for (i = 0; i < MAX_PLAYERS; i++)
14077 if (stored_player[i].active)
14082 game.all_players_gone = TRUE;
14083 game.GameOver = TRUE;
14086 game.exit_x = game.robot_wheel_x = jx;
14087 game.exit_y = game.robot_wheel_y = jy;
14090 void ExitPlayer(struct PlayerInfo *player)
14092 DrawPlayer(player); // needed here only to cleanup last field
14093 RemovePlayer(player);
14095 if (game.players_still_needed > 0)
14096 game.players_still_needed--;
14099 static void SetFieldForSnapping(int x, int y, int element, int direction,
14100 int player_index_bit)
14102 struct ElementInfo *ei = &element_info[element];
14103 int direction_bit = MV_DIR_TO_BIT(direction);
14104 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14105 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14106 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14108 Tile[x][y] = EL_ELEMENT_SNAPPING;
14109 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14110 MovDir[x][y] = direction;
14111 Store[x][y] = element;
14112 Store2[x][y] = player_index_bit;
14114 ResetGfxAnimation(x, y);
14116 GfxElement[x][y] = element;
14117 GfxAction[x][y] = action;
14118 GfxDir[x][y] = direction;
14119 GfxFrame[x][y] = -1;
14122 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14123 int player_index_bit)
14125 TestIfElementTouchesCustomElement(x, y); // for empty space
14127 if (level.finish_dig_collect)
14129 int dig_side = MV_DIR_OPPOSITE(direction);
14130 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14131 CE_PLAYER_COLLECTS_X);
14133 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14134 player_index_bit, dig_side);
14135 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14136 player_index_bit, dig_side);
14141 =============================================================================
14142 checkDiagonalPushing()
14143 -----------------------------------------------------------------------------
14144 check if diagonal input device direction results in pushing of object
14145 (by checking if the alternative direction is walkable, diggable, ...)
14146 =============================================================================
14149 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14150 int x, int y, int real_dx, int real_dy)
14152 int jx, jy, dx, dy, xx, yy;
14154 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14157 // diagonal direction: check alternative direction
14162 xx = jx + (dx == 0 ? real_dx : 0);
14163 yy = jy + (dy == 0 ? real_dy : 0);
14165 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14169 =============================================================================
14171 -----------------------------------------------------------------------------
14172 x, y: field next to player (non-diagonal) to try to dig to
14173 real_dx, real_dy: direction as read from input device (can be diagonal)
14174 =============================================================================
14177 static int DigField(struct PlayerInfo *player,
14178 int oldx, int oldy, int x, int y,
14179 int real_dx, int real_dy, int mode)
14181 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14182 boolean player_was_pushing = player->is_pushing;
14183 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14184 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14185 int jx = oldx, jy = oldy;
14186 int dx = x - jx, dy = y - jy;
14187 int nextx = x + dx, nexty = y + dy;
14188 int move_direction = (dx == -1 ? MV_LEFT :
14189 dx == +1 ? MV_RIGHT :
14191 dy == +1 ? MV_DOWN : MV_NONE);
14192 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14193 int dig_side = MV_DIR_OPPOSITE(move_direction);
14194 int old_element = Tile[jx][jy];
14195 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14198 if (is_player) // function can also be called by EL_PENGUIN
14200 if (player->MovPos == 0)
14202 player->is_digging = FALSE;
14203 player->is_collecting = FALSE;
14206 if (player->MovPos == 0) // last pushing move finished
14207 player->is_pushing = FALSE;
14209 if (mode == DF_NO_PUSH) // player just stopped pushing
14211 player->is_switching = FALSE;
14212 player->push_delay = -1;
14214 return MP_NO_ACTION;
14217 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14218 old_element = Back[jx][jy];
14220 // in case of element dropped at player position, check background
14221 else if (Back[jx][jy] != EL_EMPTY &&
14222 game.engine_version >= VERSION_IDENT(2,2,0,0))
14223 old_element = Back[jx][jy];
14225 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14226 return MP_NO_ACTION; // field has no opening in this direction
14228 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14229 return MP_NO_ACTION; // field has no opening in this direction
14231 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14235 Tile[jx][jy] = player->artwork_element;
14236 InitMovingField(jx, jy, MV_DOWN);
14237 Store[jx][jy] = EL_ACID;
14238 ContinueMoving(jx, jy);
14239 BuryPlayer(player);
14241 return MP_DONT_RUN_INTO;
14244 if (player_can_move && DONT_RUN_INTO(element))
14246 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14248 return MP_DONT_RUN_INTO;
14251 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14252 return MP_NO_ACTION;
14254 collect_count = element_info[element].collect_count_initial;
14256 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14257 return MP_NO_ACTION;
14259 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14260 player_can_move = player_can_move_or_snap;
14262 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14263 game.engine_version >= VERSION_IDENT(2,2,0,0))
14265 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14266 player->index_bit, dig_side);
14267 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14268 player->index_bit, dig_side);
14270 if (element == EL_DC_LANDMINE)
14273 if (Tile[x][y] != element) // field changed by snapping
14276 return MP_NO_ACTION;
14279 if (player->gravity && is_player && !player->is_auto_moving &&
14280 canFallDown(player) && move_direction != MV_DOWN &&
14281 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14282 return MP_NO_ACTION; // player cannot walk here due to gravity
14284 if (player_can_move &&
14285 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14287 int sound_element = SND_ELEMENT(element);
14288 int sound_action = ACTION_WALKING;
14290 if (IS_RND_GATE(element))
14292 if (!player->key[RND_GATE_NR(element)])
14293 return MP_NO_ACTION;
14295 else if (IS_RND_GATE_GRAY(element))
14297 if (!player->key[RND_GATE_GRAY_NR(element)])
14298 return MP_NO_ACTION;
14300 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14302 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14303 return MP_NO_ACTION;
14305 else if (element == EL_EXIT_OPEN ||
14306 element == EL_EM_EXIT_OPEN ||
14307 element == EL_EM_EXIT_OPENING ||
14308 element == EL_STEEL_EXIT_OPEN ||
14309 element == EL_EM_STEEL_EXIT_OPEN ||
14310 element == EL_EM_STEEL_EXIT_OPENING ||
14311 element == EL_SP_EXIT_OPEN ||
14312 element == EL_SP_EXIT_OPENING)
14314 sound_action = ACTION_PASSING; // player is passing exit
14316 else if (element == EL_EMPTY)
14318 sound_action = ACTION_MOVING; // nothing to walk on
14321 // play sound from background or player, whatever is available
14322 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14323 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14325 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14327 else if (player_can_move &&
14328 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14330 if (!ACCESS_FROM(element, opposite_direction))
14331 return MP_NO_ACTION; // field not accessible from this direction
14333 if (CAN_MOVE(element)) // only fixed elements can be passed!
14334 return MP_NO_ACTION;
14336 if (IS_EM_GATE(element))
14338 if (!player->key[EM_GATE_NR(element)])
14339 return MP_NO_ACTION;
14341 else if (IS_EM_GATE_GRAY(element))
14343 if (!player->key[EM_GATE_GRAY_NR(element)])
14344 return MP_NO_ACTION;
14346 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14348 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14349 return MP_NO_ACTION;
14351 else if (IS_EMC_GATE(element))
14353 if (!player->key[EMC_GATE_NR(element)])
14354 return MP_NO_ACTION;
14356 else if (IS_EMC_GATE_GRAY(element))
14358 if (!player->key[EMC_GATE_GRAY_NR(element)])
14359 return MP_NO_ACTION;
14361 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14363 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14364 return MP_NO_ACTION;
14366 else if (element == EL_DC_GATE_WHITE ||
14367 element == EL_DC_GATE_WHITE_GRAY ||
14368 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14370 if (player->num_white_keys == 0)
14371 return MP_NO_ACTION;
14373 player->num_white_keys--;
14375 else if (IS_SP_PORT(element))
14377 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14378 element == EL_SP_GRAVITY_PORT_RIGHT ||
14379 element == EL_SP_GRAVITY_PORT_UP ||
14380 element == EL_SP_GRAVITY_PORT_DOWN)
14381 player->gravity = !player->gravity;
14382 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14383 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14384 element == EL_SP_GRAVITY_ON_PORT_UP ||
14385 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14386 player->gravity = TRUE;
14387 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14388 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14389 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14390 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14391 player->gravity = FALSE;
14394 // automatically move to the next field with double speed
14395 player->programmed_action = move_direction;
14397 if (player->move_delay_reset_counter == 0)
14399 player->move_delay_reset_counter = 2; // two double speed steps
14401 DOUBLE_PLAYER_SPEED(player);
14404 PlayLevelSoundAction(x, y, ACTION_PASSING);
14406 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14410 if (mode != DF_SNAP)
14412 GfxElement[x][y] = GFX_ELEMENT(element);
14413 player->is_digging = TRUE;
14416 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14418 // use old behaviour for old levels (digging)
14419 if (!level.finish_dig_collect)
14421 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14422 player->index_bit, dig_side);
14424 // if digging triggered player relocation, finish digging tile
14425 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14426 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14429 if (mode == DF_SNAP)
14431 if (level.block_snap_field)
14432 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14434 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14436 // use old behaviour for old levels (snapping)
14437 if (!level.finish_dig_collect)
14438 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14439 player->index_bit, dig_side);
14442 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14446 if (is_player && mode != DF_SNAP)
14448 GfxElement[x][y] = element;
14449 player->is_collecting = TRUE;
14452 if (element == EL_SPEED_PILL)
14454 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14456 else if (element == EL_EXTRA_TIME && level.time > 0)
14458 TimeLeft += level.extra_time;
14460 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14462 DisplayGameControlValues();
14464 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14466 int shield_time = (element == EL_SHIELD_DEADLY ?
14467 level.shield_deadly_time :
14468 level.shield_normal_time);
14470 player->shield_normal_time_left += shield_time;
14471 if (element == EL_SHIELD_DEADLY)
14472 player->shield_deadly_time_left += shield_time;
14474 else if (element == EL_DYNAMITE ||
14475 element == EL_EM_DYNAMITE ||
14476 element == EL_SP_DISK_RED)
14478 if (player->inventory_size < MAX_INVENTORY_SIZE)
14479 player->inventory_element[player->inventory_size++] = element;
14481 DrawGameDoorValues();
14483 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14485 player->dynabomb_count++;
14486 player->dynabombs_left++;
14488 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14490 player->dynabomb_size++;
14492 else if (element == EL_DYNABOMB_INCREASE_POWER)
14494 player->dynabomb_xl = TRUE;
14496 else if (IS_KEY(element))
14498 player->key[KEY_NR(element)] = TRUE;
14500 DrawGameDoorValues();
14502 else if (element == EL_DC_KEY_WHITE)
14504 player->num_white_keys++;
14506 // display white keys?
14507 // DrawGameDoorValues();
14509 else if (IS_ENVELOPE(element))
14511 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14513 if (!wait_for_snapping)
14514 player->show_envelope = element;
14516 else if (element == EL_EMC_LENSES)
14518 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14520 RedrawAllInvisibleElementsForLenses();
14522 else if (element == EL_EMC_MAGNIFIER)
14524 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14526 RedrawAllInvisibleElementsForMagnifier();
14528 else if (IS_DROPPABLE(element) ||
14529 IS_THROWABLE(element)) // can be collected and dropped
14533 if (collect_count == 0)
14534 player->inventory_infinite_element = element;
14536 for (i = 0; i < collect_count; i++)
14537 if (player->inventory_size < MAX_INVENTORY_SIZE)
14538 player->inventory_element[player->inventory_size++] = element;
14540 DrawGameDoorValues();
14542 else if (collect_count > 0)
14544 game.gems_still_needed -= collect_count;
14545 if (game.gems_still_needed < 0)
14546 game.gems_still_needed = 0;
14548 game.snapshot.collected_item = TRUE;
14550 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14552 DisplayGameControlValues();
14555 RaiseScoreElement(element);
14556 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14558 // use old behaviour for old levels (collecting)
14559 if (!level.finish_dig_collect && is_player)
14561 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14562 player->index_bit, dig_side);
14564 // if collecting triggered player relocation, finish collecting tile
14565 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14566 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14569 if (mode == DF_SNAP)
14571 if (level.block_snap_field)
14572 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14574 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14576 // use old behaviour for old levels (snapping)
14577 if (!level.finish_dig_collect)
14578 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14579 player->index_bit, dig_side);
14582 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14584 if (mode == DF_SNAP && element != EL_BD_ROCK)
14585 return MP_NO_ACTION;
14587 if (CAN_FALL(element) && dy)
14588 return MP_NO_ACTION;
14590 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14591 !(element == EL_SPRING && level.use_spring_bug))
14592 return MP_NO_ACTION;
14594 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14595 ((move_direction & MV_VERTICAL &&
14596 ((element_info[element].move_pattern & MV_LEFT &&
14597 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14598 (element_info[element].move_pattern & MV_RIGHT &&
14599 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14600 (move_direction & MV_HORIZONTAL &&
14601 ((element_info[element].move_pattern & MV_UP &&
14602 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14603 (element_info[element].move_pattern & MV_DOWN &&
14604 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14605 return MP_NO_ACTION;
14607 // do not push elements already moving away faster than player
14608 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14609 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14610 return MP_NO_ACTION;
14612 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14614 if (player->push_delay_value == -1 || !player_was_pushing)
14615 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14617 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14619 if (player->push_delay_value == -1)
14620 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14622 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14624 if (!player->is_pushing)
14625 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14628 player->is_pushing = TRUE;
14629 player->is_active = TRUE;
14631 if (!(IN_LEV_FIELD(nextx, nexty) &&
14632 (IS_FREE(nextx, nexty) ||
14633 (IS_SB_ELEMENT(element) &&
14634 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14635 (IS_CUSTOM_ELEMENT(element) &&
14636 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14637 return MP_NO_ACTION;
14639 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14640 return MP_NO_ACTION;
14642 if (player->push_delay == -1) // new pushing; restart delay
14643 player->push_delay = 0;
14645 if (player->push_delay < player->push_delay_value &&
14646 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14647 element != EL_SPRING && element != EL_BALLOON)
14649 // make sure that there is no move delay before next try to push
14650 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14651 player->move_delay = 0;
14653 return MP_NO_ACTION;
14656 if (IS_CUSTOM_ELEMENT(element) &&
14657 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14659 if (!DigFieldByCE(nextx, nexty, element))
14660 return MP_NO_ACTION;
14663 if (IS_SB_ELEMENT(element))
14665 boolean sokoban_task_solved = FALSE;
14667 if (element == EL_SOKOBAN_FIELD_FULL)
14669 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14671 IncrementSokobanFieldsNeeded();
14672 IncrementSokobanObjectsNeeded();
14675 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14677 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14679 DecrementSokobanFieldsNeeded();
14680 DecrementSokobanObjectsNeeded();
14682 // sokoban object was pushed from empty field to sokoban field
14683 if (Back[x][y] == EL_EMPTY)
14684 sokoban_task_solved = TRUE;
14687 Tile[x][y] = EL_SOKOBAN_OBJECT;
14689 if (Back[x][y] == Back[nextx][nexty])
14690 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14691 else if (Back[x][y] != 0)
14692 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14695 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14698 if (sokoban_task_solved &&
14699 game.sokoban_fields_still_needed == 0 &&
14700 game.sokoban_objects_still_needed == 0 &&
14701 level.auto_exit_sokoban)
14703 game.players_still_needed = 0;
14707 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14711 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14713 InitMovingField(x, y, move_direction);
14714 GfxAction[x][y] = ACTION_PUSHING;
14716 if (mode == DF_SNAP)
14717 ContinueMoving(x, y);
14719 MovPos[x][y] = (dx != 0 ? dx : dy);
14721 Pushed[x][y] = TRUE;
14722 Pushed[nextx][nexty] = TRUE;
14724 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14725 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14727 player->push_delay_value = -1; // get new value later
14729 // check for element change _after_ element has been pushed
14730 if (game.use_change_when_pushing_bug)
14732 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14733 player->index_bit, dig_side);
14734 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14735 player->index_bit, dig_side);
14738 else if (IS_SWITCHABLE(element))
14740 if (PLAYER_SWITCHING(player, x, y))
14742 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14743 player->index_bit, dig_side);
14748 player->is_switching = TRUE;
14749 player->switch_x = x;
14750 player->switch_y = y;
14752 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14754 if (element == EL_ROBOT_WHEEL)
14756 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14758 game.robot_wheel_x = x;
14759 game.robot_wheel_y = y;
14760 game.robot_wheel_active = TRUE;
14762 TEST_DrawLevelField(x, y);
14764 else if (element == EL_SP_TERMINAL)
14768 SCAN_PLAYFIELD(xx, yy)
14770 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14774 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14776 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14778 ResetGfxAnimation(xx, yy);
14779 TEST_DrawLevelField(xx, yy);
14783 else if (IS_BELT_SWITCH(element))
14785 ToggleBeltSwitch(x, y);
14787 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14788 element == EL_SWITCHGATE_SWITCH_DOWN ||
14789 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14790 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14792 ToggleSwitchgateSwitch();
14794 else if (element == EL_LIGHT_SWITCH ||
14795 element == EL_LIGHT_SWITCH_ACTIVE)
14797 ToggleLightSwitch(x, y);
14799 else if (element == EL_TIMEGATE_SWITCH ||
14800 element == EL_DC_TIMEGATE_SWITCH)
14802 ActivateTimegateSwitch(x, y);
14804 else if (element == EL_BALLOON_SWITCH_LEFT ||
14805 element == EL_BALLOON_SWITCH_RIGHT ||
14806 element == EL_BALLOON_SWITCH_UP ||
14807 element == EL_BALLOON_SWITCH_DOWN ||
14808 element == EL_BALLOON_SWITCH_NONE ||
14809 element == EL_BALLOON_SWITCH_ANY)
14811 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14812 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14813 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14814 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14815 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14818 else if (element == EL_LAMP)
14820 Tile[x][y] = EL_LAMP_ACTIVE;
14821 game.lights_still_needed--;
14823 ResetGfxAnimation(x, y);
14824 TEST_DrawLevelField(x, y);
14826 else if (element == EL_TIME_ORB_FULL)
14828 Tile[x][y] = EL_TIME_ORB_EMPTY;
14830 if (level.time > 0 || level.use_time_orb_bug)
14832 TimeLeft += level.time_orb_time;
14833 game.no_level_time_limit = FALSE;
14835 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14837 DisplayGameControlValues();
14840 ResetGfxAnimation(x, y);
14841 TEST_DrawLevelField(x, y);
14843 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14844 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14848 game.ball_active = !game.ball_active;
14850 SCAN_PLAYFIELD(xx, yy)
14852 int e = Tile[xx][yy];
14854 if (game.ball_active)
14856 if (e == EL_EMC_MAGIC_BALL)
14857 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14858 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14859 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14863 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14864 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14865 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14866 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14871 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14872 player->index_bit, dig_side);
14874 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14875 player->index_bit, dig_side);
14877 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14878 player->index_bit, dig_side);
14884 if (!PLAYER_SWITCHING(player, x, y))
14886 player->is_switching = TRUE;
14887 player->switch_x = x;
14888 player->switch_y = y;
14890 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14891 player->index_bit, dig_side);
14892 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14893 player->index_bit, dig_side);
14895 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14896 player->index_bit, dig_side);
14897 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14898 player->index_bit, dig_side);
14901 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14902 player->index_bit, dig_side);
14903 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14904 player->index_bit, dig_side);
14906 return MP_NO_ACTION;
14909 player->push_delay = -1;
14911 if (is_player) // function can also be called by EL_PENGUIN
14913 if (Tile[x][y] != element) // really digged/collected something
14915 player->is_collecting = !player->is_digging;
14916 player->is_active = TRUE;
14918 player->last_removed_element = element;
14925 static boolean DigFieldByCE(int x, int y, int digging_element)
14927 int element = Tile[x][y];
14929 if (!IS_FREE(x, y))
14931 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14932 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14935 // no element can dig solid indestructible elements
14936 if (IS_INDESTRUCTIBLE(element) &&
14937 !IS_DIGGABLE(element) &&
14938 !IS_COLLECTIBLE(element))
14941 if (AmoebaNr[x][y] &&
14942 (element == EL_AMOEBA_FULL ||
14943 element == EL_BD_AMOEBA ||
14944 element == EL_AMOEBA_GROWING))
14946 AmoebaCnt[AmoebaNr[x][y]]--;
14947 AmoebaCnt2[AmoebaNr[x][y]]--;
14950 if (IS_MOVING(x, y))
14951 RemoveMovingField(x, y);
14955 TEST_DrawLevelField(x, y);
14958 // if digged element was about to explode, prevent the explosion
14959 ExplodeField[x][y] = EX_TYPE_NONE;
14961 PlayLevelSoundAction(x, y, action);
14964 Store[x][y] = EL_EMPTY;
14966 // this makes it possible to leave the removed element again
14967 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14968 Store[x][y] = element;
14973 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14975 int jx = player->jx, jy = player->jy;
14976 int x = jx + dx, y = jy + dy;
14977 int snap_direction = (dx == -1 ? MV_LEFT :
14978 dx == +1 ? MV_RIGHT :
14980 dy == +1 ? MV_DOWN : MV_NONE);
14981 boolean can_continue_snapping = (level.continuous_snapping &&
14982 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14984 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14987 if (!player->active || !IN_LEV_FIELD(x, y))
14995 if (player->MovPos == 0)
14996 player->is_pushing = FALSE;
14998 player->is_snapping = FALSE;
15000 if (player->MovPos == 0)
15002 player->is_moving = FALSE;
15003 player->is_digging = FALSE;
15004 player->is_collecting = FALSE;
15010 // prevent snapping with already pressed snap key when not allowed
15011 if (player->is_snapping && !can_continue_snapping)
15014 player->MovDir = snap_direction;
15016 if (player->MovPos == 0)
15018 player->is_moving = FALSE;
15019 player->is_digging = FALSE;
15020 player->is_collecting = FALSE;
15023 player->is_dropping = FALSE;
15024 player->is_dropping_pressed = FALSE;
15025 player->drop_pressed_delay = 0;
15027 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15030 player->is_snapping = TRUE;
15031 player->is_active = TRUE;
15033 if (player->MovPos == 0)
15035 player->is_moving = FALSE;
15036 player->is_digging = FALSE;
15037 player->is_collecting = FALSE;
15040 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15041 TEST_DrawLevelField(player->last_jx, player->last_jy);
15043 TEST_DrawLevelField(x, y);
15048 static boolean DropElement(struct PlayerInfo *player)
15050 int old_element, new_element;
15051 int dropx = player->jx, dropy = player->jy;
15052 int drop_direction = player->MovDir;
15053 int drop_side = drop_direction;
15054 int drop_element = get_next_dropped_element(player);
15056 /* do not drop an element on top of another element; when holding drop key
15057 pressed without moving, dropped element must move away before the next
15058 element can be dropped (this is especially important if the next element
15059 is dynamite, which can be placed on background for historical reasons) */
15060 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15063 if (IS_THROWABLE(drop_element))
15065 dropx += GET_DX_FROM_DIR(drop_direction);
15066 dropy += GET_DY_FROM_DIR(drop_direction);
15068 if (!IN_LEV_FIELD(dropx, dropy))
15072 old_element = Tile[dropx][dropy]; // old element at dropping position
15073 new_element = drop_element; // default: no change when dropping
15075 // check if player is active, not moving and ready to drop
15076 if (!player->active || player->MovPos || player->drop_delay > 0)
15079 // check if player has anything that can be dropped
15080 if (new_element == EL_UNDEFINED)
15083 // only set if player has anything that can be dropped
15084 player->is_dropping_pressed = TRUE;
15086 // check if drop key was pressed long enough for EM style dynamite
15087 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15090 // check if anything can be dropped at the current position
15091 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15094 // collected custom elements can only be dropped on empty fields
15095 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15098 if (old_element != EL_EMPTY)
15099 Back[dropx][dropy] = old_element; // store old element on this field
15101 ResetGfxAnimation(dropx, dropy);
15102 ResetRandomAnimationValue(dropx, dropy);
15104 if (player->inventory_size > 0 ||
15105 player->inventory_infinite_element != EL_UNDEFINED)
15107 if (player->inventory_size > 0)
15109 player->inventory_size--;
15111 DrawGameDoorValues();
15113 if (new_element == EL_DYNAMITE)
15114 new_element = EL_DYNAMITE_ACTIVE;
15115 else if (new_element == EL_EM_DYNAMITE)
15116 new_element = EL_EM_DYNAMITE_ACTIVE;
15117 else if (new_element == EL_SP_DISK_RED)
15118 new_element = EL_SP_DISK_RED_ACTIVE;
15121 Tile[dropx][dropy] = new_element;
15123 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15124 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15125 el2img(Tile[dropx][dropy]), 0);
15127 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15129 // needed if previous element just changed to "empty" in the last frame
15130 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15132 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15133 player->index_bit, drop_side);
15134 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15136 player->index_bit, drop_side);
15138 TestIfElementTouchesCustomElement(dropx, dropy);
15140 else // player is dropping a dyna bomb
15142 player->dynabombs_left--;
15144 Tile[dropx][dropy] = new_element;
15146 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15147 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15148 el2img(Tile[dropx][dropy]), 0);
15150 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15153 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15154 InitField_WithBug1(dropx, dropy, FALSE);
15156 new_element = Tile[dropx][dropy]; // element might have changed
15158 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15159 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15161 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15162 MovDir[dropx][dropy] = drop_direction;
15164 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15166 // do not cause impact style collision by dropping elements that can fall
15167 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15170 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15171 player->is_dropping = TRUE;
15173 player->drop_pressed_delay = 0;
15174 player->is_dropping_pressed = FALSE;
15176 player->drop_x = dropx;
15177 player->drop_y = dropy;
15182 // ----------------------------------------------------------------------------
15183 // game sound playing functions
15184 // ----------------------------------------------------------------------------
15186 static int *loop_sound_frame = NULL;
15187 static int *loop_sound_volume = NULL;
15189 void InitPlayLevelSound(void)
15191 int num_sounds = getSoundListSize();
15193 checked_free(loop_sound_frame);
15194 checked_free(loop_sound_volume);
15196 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15197 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15200 static void PlayLevelSound(int x, int y, int nr)
15202 int sx = SCREENX(x), sy = SCREENY(y);
15203 int volume, stereo_position;
15204 int max_distance = 8;
15205 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15207 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15208 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15211 if (!IN_LEV_FIELD(x, y) ||
15212 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15213 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15216 volume = SOUND_MAX_VOLUME;
15218 if (!IN_SCR_FIELD(sx, sy))
15220 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15221 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15223 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15226 stereo_position = (SOUND_MAX_LEFT +
15227 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15228 (SCR_FIELDX + 2 * max_distance));
15230 if (IS_LOOP_SOUND(nr))
15232 /* This assures that quieter loop sounds do not overwrite louder ones,
15233 while restarting sound volume comparison with each new game frame. */
15235 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15238 loop_sound_volume[nr] = volume;
15239 loop_sound_frame[nr] = FrameCounter;
15242 PlaySoundExt(nr, volume, stereo_position, type);
15245 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15247 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15248 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15249 y < LEVELY(BY1) ? LEVELY(BY1) :
15250 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15254 static void PlayLevelSoundAction(int x, int y, int action)
15256 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15259 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15261 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15263 if (sound_effect != SND_UNDEFINED)
15264 PlayLevelSound(x, y, sound_effect);
15267 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15270 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15272 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15273 PlayLevelSound(x, y, sound_effect);
15276 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15278 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15280 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15281 PlayLevelSound(x, y, sound_effect);
15284 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15286 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15288 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15289 StopSound(sound_effect);
15292 static int getLevelMusicNr(void)
15294 int level_pos = level_nr - leveldir_current->first_level;
15296 if (levelset.music[level_nr] != MUS_UNDEFINED)
15297 return levelset.music[level_nr]; // from config file
15299 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15302 static void FadeLevelSounds(void)
15307 static void FadeLevelMusic(void)
15309 int music_nr = getLevelMusicNr();
15310 char *curr_music = getCurrentlyPlayingMusicFilename();
15311 char *next_music = getMusicInfoEntryFilename(music_nr);
15313 if (!strEqual(curr_music, next_music))
15317 void FadeLevelSoundsAndMusic(void)
15323 static void PlayLevelMusic(void)
15325 int music_nr = getLevelMusicNr();
15326 char *curr_music = getCurrentlyPlayingMusicFilename();
15327 char *next_music = getMusicInfoEntryFilename(music_nr);
15329 if (!strEqual(curr_music, next_music))
15330 PlayMusicLoop(music_nr);
15333 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15335 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15337 int x = xx - offset;
15338 int y = yy - offset;
15343 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15347 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15351 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15355 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15359 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15363 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15367 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15370 case SOUND_android_clone:
15371 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15374 case SOUND_android_move:
15375 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15379 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15383 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15387 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15390 case SOUND_eater_eat:
15391 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15395 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15398 case SOUND_collect:
15399 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15402 case SOUND_diamond:
15403 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15407 // !!! CHECK THIS !!!
15409 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15411 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15415 case SOUND_wonderfall:
15416 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15420 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15424 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15428 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15432 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15436 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15440 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15444 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15448 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15451 case SOUND_exit_open:
15452 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15455 case SOUND_exit_leave:
15456 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15459 case SOUND_dynamite:
15460 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15464 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15468 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15472 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15476 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15480 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15484 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15488 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15493 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15495 int element = map_element_SP_to_RND(element_sp);
15496 int action = map_action_SP_to_RND(action_sp);
15497 int offset = (setup.sp_show_border_elements ? 0 : 1);
15498 int x = xx - offset;
15499 int y = yy - offset;
15501 PlayLevelSoundElementAction(x, y, element, action);
15504 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15506 int element = map_element_MM_to_RND(element_mm);
15507 int action = map_action_MM_to_RND(action_mm);
15509 int x = xx - offset;
15510 int y = yy - offset;
15512 if (!IS_MM_ELEMENT(element))
15513 element = EL_MM_DEFAULT;
15515 PlayLevelSoundElementAction(x, y, element, action);
15518 void PlaySound_MM(int sound_mm)
15520 int sound = map_sound_MM_to_RND(sound_mm);
15522 if (sound == SND_UNDEFINED)
15528 void PlaySoundLoop_MM(int sound_mm)
15530 int sound = map_sound_MM_to_RND(sound_mm);
15532 if (sound == SND_UNDEFINED)
15535 PlaySoundLoop(sound);
15538 void StopSound_MM(int sound_mm)
15540 int sound = map_sound_MM_to_RND(sound_mm);
15542 if (sound == SND_UNDEFINED)
15548 void RaiseScore(int value)
15550 game.score += value;
15552 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15554 DisplayGameControlValues();
15557 void RaiseScoreElement(int element)
15562 case EL_BD_DIAMOND:
15563 case EL_EMERALD_YELLOW:
15564 case EL_EMERALD_RED:
15565 case EL_EMERALD_PURPLE:
15566 case EL_SP_INFOTRON:
15567 RaiseScore(level.score[SC_EMERALD]);
15570 RaiseScore(level.score[SC_DIAMOND]);
15573 RaiseScore(level.score[SC_CRYSTAL]);
15576 RaiseScore(level.score[SC_PEARL]);
15579 case EL_BD_BUTTERFLY:
15580 case EL_SP_ELECTRON:
15581 RaiseScore(level.score[SC_BUG]);
15584 case EL_BD_FIREFLY:
15585 case EL_SP_SNIKSNAK:
15586 RaiseScore(level.score[SC_SPACESHIP]);
15589 case EL_DARK_YAMYAM:
15590 RaiseScore(level.score[SC_YAMYAM]);
15593 RaiseScore(level.score[SC_ROBOT]);
15596 RaiseScore(level.score[SC_PACMAN]);
15599 RaiseScore(level.score[SC_NUT]);
15602 case EL_EM_DYNAMITE:
15603 case EL_SP_DISK_RED:
15604 case EL_DYNABOMB_INCREASE_NUMBER:
15605 case EL_DYNABOMB_INCREASE_SIZE:
15606 case EL_DYNABOMB_INCREASE_POWER:
15607 RaiseScore(level.score[SC_DYNAMITE]);
15609 case EL_SHIELD_NORMAL:
15610 case EL_SHIELD_DEADLY:
15611 RaiseScore(level.score[SC_SHIELD]);
15613 case EL_EXTRA_TIME:
15614 RaiseScore(level.extra_time_score);
15628 case EL_DC_KEY_WHITE:
15629 RaiseScore(level.score[SC_KEY]);
15632 RaiseScore(element_info[element].collect_score);
15637 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15639 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15643 // prevent short reactivation of overlay buttons while closing door
15644 SetOverlayActive(FALSE);
15645 UnmapGameButtons();
15647 // door may still be open due to skipped or envelope style request
15648 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
15651 if (network.enabled)
15652 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15656 FadeSkipNextFadeIn();
15658 SetGameStatus(GAME_MODE_MAIN);
15663 else // continue playing the game
15665 if (tape.playing && tape.deactivate_display)
15666 TapeDeactivateDisplayOff(TRUE);
15668 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15670 if (tape.playing && tape.deactivate_display)
15671 TapeDeactivateDisplayOn();
15675 void RequestQuitGame(boolean escape_key_pressed)
15677 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15678 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15679 level_editor_test_game);
15680 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15681 quick_quit || score_info_tape_play);
15683 RequestQuitGameExt(skip_request, quick_quit,
15684 "Do you really want to quit the game?");
15687 static char *getRestartGameMessage(void)
15689 boolean play_again = hasStartedNetworkGame();
15690 static char message[MAX_OUTPUT_LINESIZE];
15691 char *game_over_text = "Game over!";
15692 char *play_again_text = " Play it again?";
15694 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
15695 game_mm.game_over_message != NULL)
15696 game_over_text = game_mm.game_over_message;
15698 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
15699 (play_again ? play_again_text : ""));
15704 static void RequestRestartGame(void)
15706 char *message = getRestartGameMessage();
15707 boolean has_started_game = hasStartedNetworkGame();
15708 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15709 int door_state = DOOR_CLOSE_1;
15711 if (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game)
15713 CloseDoor(door_state);
15715 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15719 // if game was invoked from level editor, also close tape recorder door
15720 if (level_editor_test_game)
15721 door_state = DOOR_CLOSE_ALL;
15723 CloseDoor(door_state);
15725 SetGameStatus(GAME_MODE_MAIN);
15731 boolean CheckRestartGame(void)
15733 static int game_over_delay = 0;
15734 int game_over_delay_value = 50;
15735 boolean game_over = checkGameFailed();
15739 game_over_delay = game_over_delay_value;
15744 if (game_over_delay > 0)
15746 if (game_over_delay == game_over_delay_value / 2)
15747 PlaySound(SND_GAME_LOSING);
15754 // do not handle game over if request dialog is already active
15755 if (game.request_active)
15758 // do not ask to play again if game was never actually played
15759 if (!game.GamePlayed)
15762 // do not ask to play again if this was disabled in setup menu
15763 if (!setup.ask_on_game_over)
15766 RequestRestartGame();
15771 boolean checkGameSolved(void)
15773 // set for all game engines if level was solved
15774 return game.LevelSolved_GameEnd;
15777 boolean checkGameFailed(void)
15779 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15780 return (game_em.game_over && !game_em.level_solved);
15781 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15782 return (game_sp.game_over && !game_sp.level_solved);
15783 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15784 return (game_mm.game_over && !game_mm.level_solved);
15785 else // GAME_ENGINE_TYPE_RND
15786 return (game.GameOver && !game.LevelSolved);
15789 boolean checkGameEnded(void)
15791 return (checkGameSolved() || checkGameFailed());
15795 // ----------------------------------------------------------------------------
15796 // random generator functions
15797 // ----------------------------------------------------------------------------
15799 unsigned int InitEngineRandom_RND(int seed)
15801 game.num_random_calls = 0;
15803 return InitEngineRandom(seed);
15806 unsigned int RND(int max)
15810 game.num_random_calls++;
15812 return GetEngineRandom(max);
15819 // ----------------------------------------------------------------------------
15820 // game engine snapshot handling functions
15821 // ----------------------------------------------------------------------------
15823 struct EngineSnapshotInfo
15825 // runtime values for custom element collect score
15826 int collect_score[NUM_CUSTOM_ELEMENTS];
15828 // runtime values for group element choice position
15829 int choice_pos[NUM_GROUP_ELEMENTS];
15831 // runtime values for belt position animations
15832 int belt_graphic[4][NUM_BELT_PARTS];
15833 int belt_anim_mode[4][NUM_BELT_PARTS];
15836 static struct EngineSnapshotInfo engine_snapshot_rnd;
15837 static char *snapshot_level_identifier = NULL;
15838 static int snapshot_level_nr = -1;
15840 static void SaveEngineSnapshotValues_RND(void)
15842 static int belt_base_active_element[4] =
15844 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15845 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15846 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15847 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15851 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15853 int element = EL_CUSTOM_START + i;
15855 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15858 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15860 int element = EL_GROUP_START + i;
15862 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15865 for (i = 0; i < 4; i++)
15867 for (j = 0; j < NUM_BELT_PARTS; j++)
15869 int element = belt_base_active_element[i] + j;
15870 int graphic = el2img(element);
15871 int anim_mode = graphic_info[graphic].anim_mode;
15873 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15874 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15879 static void LoadEngineSnapshotValues_RND(void)
15881 unsigned int num_random_calls = game.num_random_calls;
15884 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15886 int element = EL_CUSTOM_START + i;
15888 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15891 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15893 int element = EL_GROUP_START + i;
15895 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15898 for (i = 0; i < 4; i++)
15900 for (j = 0; j < NUM_BELT_PARTS; j++)
15902 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15903 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15905 graphic_info[graphic].anim_mode = anim_mode;
15909 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15911 InitRND(tape.random_seed);
15912 for (i = 0; i < num_random_calls; i++)
15916 if (game.num_random_calls != num_random_calls)
15918 Error("number of random calls out of sync");
15919 Error("number of random calls should be %d", num_random_calls);
15920 Error("number of random calls is %d", game.num_random_calls);
15922 Fail("this should not happen -- please debug");
15926 void FreeEngineSnapshotSingle(void)
15928 FreeSnapshotSingle();
15930 setString(&snapshot_level_identifier, NULL);
15931 snapshot_level_nr = -1;
15934 void FreeEngineSnapshotList(void)
15936 FreeSnapshotList();
15939 static ListNode *SaveEngineSnapshotBuffers(void)
15941 ListNode *buffers = NULL;
15943 // copy some special values to a structure better suited for the snapshot
15945 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15946 SaveEngineSnapshotValues_RND();
15947 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15948 SaveEngineSnapshotValues_EM();
15949 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15950 SaveEngineSnapshotValues_SP(&buffers);
15951 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15952 SaveEngineSnapshotValues_MM();
15954 // save values stored in special snapshot structure
15956 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15957 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15958 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15959 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15960 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15961 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15962 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15963 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15965 // save further RND engine values
15967 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15968 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15969 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15971 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15972 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15973 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15974 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15975 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15977 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15978 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15979 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15981 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15983 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15984 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15986 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15987 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15988 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15989 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15990 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15991 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15992 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15993 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15994 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15995 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15996 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15997 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15998 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15999 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16000 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16001 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16002 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16003 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16005 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16006 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16008 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16009 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16010 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16012 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16013 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16015 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16016 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16017 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16018 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16019 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16020 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16022 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16023 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16026 ListNode *node = engine_snapshot_list_rnd;
16029 while (node != NULL)
16031 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16036 Debug("game:playing:SaveEngineSnapshotBuffers",
16037 "size of engine snapshot: %d bytes", num_bytes);
16043 void SaveEngineSnapshotSingle(void)
16045 ListNode *buffers = SaveEngineSnapshotBuffers();
16047 // finally save all snapshot buffers to single snapshot
16048 SaveSnapshotSingle(buffers);
16050 // save level identification information
16051 setString(&snapshot_level_identifier, leveldir_current->identifier);
16052 snapshot_level_nr = level_nr;
16055 boolean CheckSaveEngineSnapshotToList(void)
16057 boolean save_snapshot =
16058 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16059 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16060 game.snapshot.changed_action) ||
16061 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16062 game.snapshot.collected_item));
16064 game.snapshot.changed_action = FALSE;
16065 game.snapshot.collected_item = FALSE;
16066 game.snapshot.save_snapshot = save_snapshot;
16068 return save_snapshot;
16071 void SaveEngineSnapshotToList(void)
16073 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16077 ListNode *buffers = SaveEngineSnapshotBuffers();
16079 // finally save all snapshot buffers to snapshot list
16080 SaveSnapshotToList(buffers);
16083 void SaveEngineSnapshotToListInitial(void)
16085 FreeEngineSnapshotList();
16087 SaveEngineSnapshotToList();
16090 static void LoadEngineSnapshotValues(void)
16092 // restore special values from snapshot structure
16094 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16095 LoadEngineSnapshotValues_RND();
16096 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16097 LoadEngineSnapshotValues_EM();
16098 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16099 LoadEngineSnapshotValues_SP();
16100 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16101 LoadEngineSnapshotValues_MM();
16104 void LoadEngineSnapshotSingle(void)
16106 LoadSnapshotSingle();
16108 LoadEngineSnapshotValues();
16111 static void LoadEngineSnapshot_Undo(int steps)
16113 LoadSnapshotFromList_Older(steps);
16115 LoadEngineSnapshotValues();
16118 static void LoadEngineSnapshot_Redo(int steps)
16120 LoadSnapshotFromList_Newer(steps);
16122 LoadEngineSnapshotValues();
16125 boolean CheckEngineSnapshotSingle(void)
16127 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16128 snapshot_level_nr == level_nr);
16131 boolean CheckEngineSnapshotList(void)
16133 return CheckSnapshotList();
16137 // ---------- new game button stuff -------------------------------------------
16144 boolean *setup_value;
16145 boolean allowed_on_tape;
16146 boolean is_touch_button;
16148 } gamebutton_info[NUM_GAME_BUTTONS] =
16151 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16152 GAME_CTRL_ID_STOP, NULL,
16153 TRUE, FALSE, "stop game"
16156 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16157 GAME_CTRL_ID_PAUSE, NULL,
16158 TRUE, FALSE, "pause game"
16161 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16162 GAME_CTRL_ID_PLAY, NULL,
16163 TRUE, FALSE, "play game"
16166 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16167 GAME_CTRL_ID_UNDO, NULL,
16168 TRUE, FALSE, "undo step"
16171 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16172 GAME_CTRL_ID_REDO, NULL,
16173 TRUE, FALSE, "redo step"
16176 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16177 GAME_CTRL_ID_SAVE, NULL,
16178 TRUE, FALSE, "save game"
16181 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16182 GAME_CTRL_ID_PAUSE2, NULL,
16183 TRUE, FALSE, "pause game"
16186 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16187 GAME_CTRL_ID_LOAD, NULL,
16188 TRUE, FALSE, "load game"
16191 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16192 GAME_CTRL_ID_PANEL_STOP, NULL,
16193 FALSE, FALSE, "stop game"
16196 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16197 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16198 FALSE, FALSE, "pause game"
16201 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16202 GAME_CTRL_ID_PANEL_PLAY, NULL,
16203 FALSE, FALSE, "play game"
16206 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16207 GAME_CTRL_ID_TOUCH_STOP, NULL,
16208 FALSE, TRUE, "stop game"
16211 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16212 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16213 FALSE, TRUE, "pause game"
16216 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16217 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16218 TRUE, FALSE, "background music on/off"
16221 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16222 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16223 TRUE, FALSE, "sound loops on/off"
16226 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16227 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16228 TRUE, FALSE, "normal sounds on/off"
16231 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16232 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16233 FALSE, FALSE, "background music on/off"
16236 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16237 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16238 FALSE, FALSE, "sound loops on/off"
16241 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16242 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16243 FALSE, FALSE, "normal sounds on/off"
16247 void CreateGameButtons(void)
16251 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16253 int graphic = gamebutton_info[i].graphic;
16254 struct GraphicInfo *gfx = &graphic_info[graphic];
16255 struct XY *pos = gamebutton_info[i].pos;
16256 struct GadgetInfo *gi;
16259 unsigned int event_mask;
16260 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16261 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16262 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16263 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16264 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16265 int gd_x = gfx->src_x;
16266 int gd_y = gfx->src_y;
16267 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16268 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16269 int gd_xa = gfx->src_x + gfx->active_xoffset;
16270 int gd_ya = gfx->src_y + gfx->active_yoffset;
16271 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16272 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16273 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16274 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16277 // do not use touch buttons if overlay touch buttons are disabled
16278 if (is_touch_button && !setup.touch.overlay_buttons)
16281 if (gfx->bitmap == NULL)
16283 game_gadget[id] = NULL;
16288 if (id == GAME_CTRL_ID_STOP ||
16289 id == GAME_CTRL_ID_PANEL_STOP ||
16290 id == GAME_CTRL_ID_TOUCH_STOP ||
16291 id == GAME_CTRL_ID_PLAY ||
16292 id == GAME_CTRL_ID_PANEL_PLAY ||
16293 id == GAME_CTRL_ID_SAVE ||
16294 id == GAME_CTRL_ID_LOAD)
16296 button_type = GD_TYPE_NORMAL_BUTTON;
16298 event_mask = GD_EVENT_RELEASED;
16300 else if (id == GAME_CTRL_ID_UNDO ||
16301 id == GAME_CTRL_ID_REDO)
16303 button_type = GD_TYPE_NORMAL_BUTTON;
16305 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16309 button_type = GD_TYPE_CHECK_BUTTON;
16310 checked = (gamebutton_info[i].setup_value != NULL ?
16311 *gamebutton_info[i].setup_value : FALSE);
16312 event_mask = GD_EVENT_PRESSED;
16315 gi = CreateGadget(GDI_CUSTOM_ID, id,
16316 GDI_IMAGE_ID, graphic,
16317 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16320 GDI_WIDTH, gfx->width,
16321 GDI_HEIGHT, gfx->height,
16322 GDI_TYPE, button_type,
16323 GDI_STATE, GD_BUTTON_UNPRESSED,
16324 GDI_CHECKED, checked,
16325 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16326 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16327 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16328 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16329 GDI_DIRECT_DRAW, FALSE,
16330 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16331 GDI_EVENT_MASK, event_mask,
16332 GDI_CALLBACK_ACTION, HandleGameButtons,
16336 Fail("cannot create gadget");
16338 game_gadget[id] = gi;
16342 void FreeGameButtons(void)
16346 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16347 FreeGadget(game_gadget[i]);
16350 static void UnmapGameButtonsAtSamePosition(int id)
16354 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16356 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16357 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16358 UnmapGadget(game_gadget[i]);
16361 static void UnmapGameButtonsAtSamePosition_All(void)
16363 if (setup.show_load_save_buttons)
16365 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16366 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16367 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16369 else if (setup.show_undo_redo_buttons)
16371 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16372 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16373 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16377 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16378 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16379 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16381 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16382 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16383 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16387 void MapLoadSaveButtons(void)
16389 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16390 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16392 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16393 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16396 void MapUndoRedoButtons(void)
16398 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16399 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16401 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16402 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16405 void ModifyPauseButtons(void)
16409 GAME_CTRL_ID_PAUSE,
16410 GAME_CTRL_ID_PAUSE2,
16411 GAME_CTRL_ID_PANEL_PAUSE,
16412 GAME_CTRL_ID_TOUCH_PAUSE,
16417 for (i = 0; ids[i] > -1; i++)
16418 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16421 static void MapGameButtonsExt(boolean on_tape)
16425 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16427 if ((i == GAME_CTRL_ID_UNDO ||
16428 i == GAME_CTRL_ID_REDO) &&
16429 game_status != GAME_MODE_PLAYING)
16432 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16433 MapGadget(game_gadget[i]);
16436 UnmapGameButtonsAtSamePosition_All();
16438 RedrawGameButtons();
16441 static void UnmapGameButtonsExt(boolean on_tape)
16445 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16446 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16447 UnmapGadget(game_gadget[i]);
16450 static void RedrawGameButtonsExt(boolean on_tape)
16454 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16455 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16456 RedrawGadget(game_gadget[i]);
16459 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16464 gi->checked = state;
16467 static void RedrawSoundButtonGadget(int id)
16469 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16470 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16471 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16472 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16473 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16474 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16477 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16478 RedrawGadget(game_gadget[id2]);
16481 void MapGameButtons(void)
16483 MapGameButtonsExt(FALSE);
16486 void UnmapGameButtons(void)
16488 UnmapGameButtonsExt(FALSE);
16491 void RedrawGameButtons(void)
16493 RedrawGameButtonsExt(FALSE);
16496 void MapGameButtonsOnTape(void)
16498 MapGameButtonsExt(TRUE);
16501 void UnmapGameButtonsOnTape(void)
16503 UnmapGameButtonsExt(TRUE);
16506 void RedrawGameButtonsOnTape(void)
16508 RedrawGameButtonsExt(TRUE);
16511 static void GameUndoRedoExt(void)
16513 ClearPlayerAction();
16515 tape.pausing = TRUE;
16518 UpdateAndDisplayGameControlValues();
16520 DrawCompleteVideoDisplay();
16521 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16522 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16523 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16525 ModifyPauseButtons();
16530 static void GameUndo(int steps)
16532 if (!CheckEngineSnapshotList())
16535 int tape_property_bits = tape.property_bits;
16537 LoadEngineSnapshot_Undo(steps);
16539 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16544 static void GameRedo(int steps)
16546 if (!CheckEngineSnapshotList())
16549 int tape_property_bits = tape.property_bits;
16551 LoadEngineSnapshot_Redo(steps);
16553 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16558 static void HandleGameButtonsExt(int id, int button)
16560 static boolean game_undo_executed = FALSE;
16561 int steps = BUTTON_STEPSIZE(button);
16562 boolean handle_game_buttons =
16563 (game_status == GAME_MODE_PLAYING ||
16564 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16566 if (!handle_game_buttons)
16571 case GAME_CTRL_ID_STOP:
16572 case GAME_CTRL_ID_PANEL_STOP:
16573 case GAME_CTRL_ID_TOUCH_STOP:
16578 case GAME_CTRL_ID_PAUSE:
16579 case GAME_CTRL_ID_PAUSE2:
16580 case GAME_CTRL_ID_PANEL_PAUSE:
16581 case GAME_CTRL_ID_TOUCH_PAUSE:
16582 if (network.enabled && game_status == GAME_MODE_PLAYING)
16585 SendToServer_ContinuePlaying();
16587 SendToServer_PausePlaying();
16590 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16592 game_undo_executed = FALSE;
16596 case GAME_CTRL_ID_PLAY:
16597 case GAME_CTRL_ID_PANEL_PLAY:
16598 if (game_status == GAME_MODE_MAIN)
16600 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16602 else if (tape.pausing)
16604 if (network.enabled)
16605 SendToServer_ContinuePlaying();
16607 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16611 case GAME_CTRL_ID_UNDO:
16612 // Important: When using "save snapshot when collecting an item" mode,
16613 // load last (current) snapshot for first "undo" after pressing "pause"
16614 // (else the last-but-one snapshot would be loaded, because the snapshot
16615 // pointer already points to the last snapshot when pressing "pause",
16616 // which is fine for "every step/move" mode, but not for "every collect")
16617 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16618 !game_undo_executed)
16621 game_undo_executed = TRUE;
16626 case GAME_CTRL_ID_REDO:
16630 case GAME_CTRL_ID_SAVE:
16634 case GAME_CTRL_ID_LOAD:
16638 case SOUND_CTRL_ID_MUSIC:
16639 case SOUND_CTRL_ID_PANEL_MUSIC:
16640 if (setup.sound_music)
16642 setup.sound_music = FALSE;
16646 else if (audio.music_available)
16648 setup.sound = setup.sound_music = TRUE;
16650 SetAudioMode(setup.sound);
16652 if (game_status == GAME_MODE_PLAYING)
16656 RedrawSoundButtonGadget(id);
16660 case SOUND_CTRL_ID_LOOPS:
16661 case SOUND_CTRL_ID_PANEL_LOOPS:
16662 if (setup.sound_loops)
16663 setup.sound_loops = FALSE;
16664 else if (audio.loops_available)
16666 setup.sound = setup.sound_loops = TRUE;
16668 SetAudioMode(setup.sound);
16671 RedrawSoundButtonGadget(id);
16675 case SOUND_CTRL_ID_SIMPLE:
16676 case SOUND_CTRL_ID_PANEL_SIMPLE:
16677 if (setup.sound_simple)
16678 setup.sound_simple = FALSE;
16679 else if (audio.sound_available)
16681 setup.sound = setup.sound_simple = TRUE;
16683 SetAudioMode(setup.sound);
16686 RedrawSoundButtonGadget(id);
16695 static void HandleGameButtons(struct GadgetInfo *gi)
16697 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16700 void HandleSoundButtonKeys(Key key)
16702 if (key == setup.shortcut.sound_simple)
16703 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16704 else if (key == setup.shortcut.sound_loops)
16705 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16706 else if (key == setup.shortcut.sound_music)
16707 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);