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 boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1559 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1560 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1561 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1562 IS_JUST_CHANGING(x, y))
1564 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1566 // static variables for playfield scan mode (scanning forward or backward)
1567 static int playfield_scan_start_x = 0;
1568 static int playfield_scan_start_y = 0;
1569 static int playfield_scan_delta_x = 1;
1570 static int playfield_scan_delta_y = 1;
1572 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1573 (y) >= 0 && (y) <= lev_fieldy - 1; \
1574 (y) += playfield_scan_delta_y) \
1575 for ((x) = playfield_scan_start_x; \
1576 (x) >= 0 && (x) <= lev_fieldx - 1; \
1577 (x) += playfield_scan_delta_x)
1580 void DEBUG_SetMaximumDynamite(void)
1584 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1585 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1586 local_player->inventory_element[local_player->inventory_size++] =
1591 static void InitPlayfieldScanModeVars(void)
1593 if (game.use_reverse_scan_direction)
1595 playfield_scan_start_x = lev_fieldx - 1;
1596 playfield_scan_start_y = lev_fieldy - 1;
1598 playfield_scan_delta_x = -1;
1599 playfield_scan_delta_y = -1;
1603 playfield_scan_start_x = 0;
1604 playfield_scan_start_y = 0;
1606 playfield_scan_delta_x = 1;
1607 playfield_scan_delta_y = 1;
1611 static void InitPlayfieldScanMode(int mode)
1613 game.use_reverse_scan_direction =
1614 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1616 InitPlayfieldScanModeVars();
1619 static int get_move_delay_from_stepsize(int move_stepsize)
1622 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1624 // make sure that stepsize value is always a power of 2
1625 move_stepsize = (1 << log_2(move_stepsize));
1627 return TILEX / move_stepsize;
1630 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1633 int player_nr = player->index_nr;
1634 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1635 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1637 // do no immediately change move delay -- the player might just be moving
1638 player->move_delay_value_next = move_delay;
1640 // information if player can move must be set separately
1641 player->cannot_move = cannot_move;
1645 player->move_delay = game.initial_move_delay[player_nr];
1646 player->move_delay_value = game.initial_move_delay_value[player_nr];
1648 player->move_delay_value_next = -1;
1650 player->move_delay_reset_counter = 0;
1654 void GetPlayerConfig(void)
1656 GameFrameDelay = setup.game_frame_delay;
1658 if (!audio.sound_available)
1659 setup.sound_simple = FALSE;
1661 if (!audio.loops_available)
1662 setup.sound_loops = FALSE;
1664 if (!audio.music_available)
1665 setup.sound_music = FALSE;
1667 if (!video.fullscreen_available)
1668 setup.fullscreen = FALSE;
1670 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1672 SetAudioMode(setup.sound);
1675 int GetElementFromGroupElement(int element)
1677 if (IS_GROUP_ELEMENT(element))
1679 struct ElementGroupInfo *group = element_info[element].group;
1680 int last_anim_random_frame = gfx.anim_random_frame;
1683 if (group->choice_mode == ANIM_RANDOM)
1684 gfx.anim_random_frame = RND(group->num_elements_resolved);
1686 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1687 group->choice_mode, 0,
1690 if (group->choice_mode == ANIM_RANDOM)
1691 gfx.anim_random_frame = last_anim_random_frame;
1693 group->choice_pos++;
1695 element = group->element_resolved[element_pos];
1701 static void IncrementSokobanFieldsNeeded(void)
1703 if (level.sb_fields_needed)
1704 game.sokoban_fields_still_needed++;
1707 static void IncrementSokobanObjectsNeeded(void)
1709 if (level.sb_objects_needed)
1710 game.sokoban_objects_still_needed++;
1713 static void DecrementSokobanFieldsNeeded(void)
1715 if (game.sokoban_fields_still_needed > 0)
1716 game.sokoban_fields_still_needed--;
1719 static void DecrementSokobanObjectsNeeded(void)
1721 if (game.sokoban_objects_still_needed > 0)
1722 game.sokoban_objects_still_needed--;
1725 static void InitPlayerField(int x, int y, int element, boolean init_game)
1727 if (element == EL_SP_MURPHY)
1731 if (stored_player[0].present)
1733 Tile[x][y] = EL_SP_MURPHY_CLONE;
1739 stored_player[0].initial_element = element;
1740 stored_player[0].use_murphy = TRUE;
1742 if (!level.use_artwork_element[0])
1743 stored_player[0].artwork_element = EL_SP_MURPHY;
1746 Tile[x][y] = EL_PLAYER_1;
1752 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1753 int jx = player->jx, jy = player->jy;
1755 player->present = TRUE;
1757 player->block_last_field = (element == EL_SP_MURPHY ?
1758 level.sp_block_last_field :
1759 level.block_last_field);
1761 // ---------- initialize player's last field block delay ------------------
1763 // always start with reliable default value (no adjustment needed)
1764 player->block_delay_adjustment = 0;
1766 // special case 1: in Supaplex, Murphy blocks last field one more frame
1767 if (player->block_last_field && element == EL_SP_MURPHY)
1768 player->block_delay_adjustment = 1;
1770 // special case 2: in game engines before 3.1.1, blocking was different
1771 if (game.use_block_last_field_bug)
1772 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1774 if (!network.enabled || player->connected_network)
1776 player->active = TRUE;
1778 // remove potentially duplicate players
1779 if (StorePlayer[jx][jy] == Tile[x][y])
1780 StorePlayer[jx][jy] = 0;
1782 StorePlayer[x][y] = Tile[x][y];
1784 #if DEBUG_INIT_PLAYER
1785 Debug("game:init:player", "- player element %d activated",
1786 player->element_nr);
1787 Debug("game:init:player", " (local player is %d and currently %s)",
1788 local_player->element_nr,
1789 local_player->active ? "active" : "not active");
1793 Tile[x][y] = EL_EMPTY;
1795 player->jx = player->last_jx = x;
1796 player->jy = player->last_jy = y;
1799 // always check if player was just killed and should be reanimated
1801 int player_nr = GET_PLAYER_NR(element);
1802 struct PlayerInfo *player = &stored_player[player_nr];
1804 if (player->active && player->killed)
1805 player->reanimated = TRUE; // if player was just killed, reanimate him
1809 static void InitField(int x, int y, boolean init_game)
1811 int element = Tile[x][y];
1820 InitPlayerField(x, y, element, init_game);
1823 case EL_SOKOBAN_FIELD_PLAYER:
1824 element = Tile[x][y] = EL_PLAYER_1;
1825 InitField(x, y, init_game);
1827 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1828 InitField(x, y, init_game);
1831 case EL_SOKOBAN_FIELD_EMPTY:
1832 IncrementSokobanFieldsNeeded();
1835 case EL_SOKOBAN_OBJECT:
1836 IncrementSokobanObjectsNeeded();
1840 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1841 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1842 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1843 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1844 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1845 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1846 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1847 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1848 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1849 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1858 case EL_SPACESHIP_RIGHT:
1859 case EL_SPACESHIP_UP:
1860 case EL_SPACESHIP_LEFT:
1861 case EL_SPACESHIP_DOWN:
1862 case EL_BD_BUTTERFLY:
1863 case EL_BD_BUTTERFLY_RIGHT:
1864 case EL_BD_BUTTERFLY_UP:
1865 case EL_BD_BUTTERFLY_LEFT:
1866 case EL_BD_BUTTERFLY_DOWN:
1868 case EL_BD_FIREFLY_RIGHT:
1869 case EL_BD_FIREFLY_UP:
1870 case EL_BD_FIREFLY_LEFT:
1871 case EL_BD_FIREFLY_DOWN:
1872 case EL_PACMAN_RIGHT:
1874 case EL_PACMAN_LEFT:
1875 case EL_PACMAN_DOWN:
1877 case EL_YAMYAM_LEFT:
1878 case EL_YAMYAM_RIGHT:
1880 case EL_YAMYAM_DOWN:
1881 case EL_DARK_YAMYAM:
1884 case EL_SP_SNIKSNAK:
1885 case EL_SP_ELECTRON:
1891 case EL_SPRING_LEFT:
1892 case EL_SPRING_RIGHT:
1896 case EL_AMOEBA_FULL:
1901 case EL_AMOEBA_DROP:
1902 if (y == lev_fieldy - 1)
1904 Tile[x][y] = EL_AMOEBA_GROWING;
1905 Store[x][y] = EL_AMOEBA_WET;
1909 case EL_DYNAMITE_ACTIVE:
1910 case EL_SP_DISK_RED_ACTIVE:
1911 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1912 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1913 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1914 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1915 MovDelay[x][y] = 96;
1918 case EL_EM_DYNAMITE_ACTIVE:
1919 MovDelay[x][y] = 32;
1923 game.lights_still_needed++;
1927 game.friends_still_needed++;
1932 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1935 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1936 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1937 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1938 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1939 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1940 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1941 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1942 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1943 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1944 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1945 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1946 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1949 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1950 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1951 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1953 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1955 game.belt_dir[belt_nr] = belt_dir;
1956 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1958 else // more than one switch -- set it like the first switch
1960 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1965 case EL_LIGHT_SWITCH_ACTIVE:
1967 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1970 case EL_INVISIBLE_STEELWALL:
1971 case EL_INVISIBLE_WALL:
1972 case EL_INVISIBLE_SAND:
1973 if (game.light_time_left > 0 ||
1974 game.lenses_time_left > 0)
1975 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1978 case EL_EMC_MAGIC_BALL:
1979 if (game.ball_active)
1980 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1983 case EL_EMC_MAGIC_BALL_SWITCH:
1984 if (game.ball_active)
1985 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1988 case EL_TRIGGER_PLAYER:
1989 case EL_TRIGGER_ELEMENT:
1990 case EL_TRIGGER_CE_VALUE:
1991 case EL_TRIGGER_CE_SCORE:
1993 case EL_ANY_ELEMENT:
1994 case EL_CURRENT_CE_VALUE:
1995 case EL_CURRENT_CE_SCORE:
2012 // reference elements should not be used on the playfield
2013 Tile[x][y] = EL_EMPTY;
2017 if (IS_CUSTOM_ELEMENT(element))
2019 if (CAN_MOVE(element))
2022 if (!element_info[element].use_last_ce_value || init_game)
2023 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2025 else if (IS_GROUP_ELEMENT(element))
2027 Tile[x][y] = GetElementFromGroupElement(element);
2029 InitField(x, y, init_game);
2031 else if (IS_EMPTY_ELEMENT(element))
2033 GfxElementEmpty[x][y] = element;
2034 Tile[x][y] = EL_EMPTY;
2036 if (element_info[element].use_gfx_element)
2037 game.use_masked_elements = TRUE;
2044 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2047 static void InitField_WithBug1(int x, int y, boolean init_game)
2049 InitField(x, y, init_game);
2051 // not needed to call InitMovDir() -- already done by InitField()!
2052 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2053 CAN_MOVE(Tile[x][y]))
2057 static void InitField_WithBug2(int x, int y, boolean init_game)
2059 int old_element = Tile[x][y];
2061 InitField(x, y, init_game);
2063 // not needed to call InitMovDir() -- already done by InitField()!
2064 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2065 CAN_MOVE(old_element) &&
2066 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2069 /* this case is in fact a combination of not less than three bugs:
2070 first, it calls InitMovDir() for elements that can move, although this is
2071 already done by InitField(); then, it checks the element that was at this
2072 field _before_ the call to InitField() (which can change it); lastly, it
2073 was not called for "mole with direction" elements, which were treated as
2074 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2078 static int get_key_element_from_nr(int key_nr)
2080 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2081 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2082 EL_EM_KEY_1 : EL_KEY_1);
2084 return key_base_element + key_nr;
2087 static int get_next_dropped_element(struct PlayerInfo *player)
2089 return (player->inventory_size > 0 ?
2090 player->inventory_element[player->inventory_size - 1] :
2091 player->inventory_infinite_element != EL_UNDEFINED ?
2092 player->inventory_infinite_element :
2093 player->dynabombs_left > 0 ?
2094 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2098 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2100 // pos >= 0: get element from bottom of the stack;
2101 // pos < 0: get element from top of the stack
2105 int min_inventory_size = -pos;
2106 int inventory_pos = player->inventory_size - min_inventory_size;
2107 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2109 return (player->inventory_size >= min_inventory_size ?
2110 player->inventory_element[inventory_pos] :
2111 player->inventory_infinite_element != EL_UNDEFINED ?
2112 player->inventory_infinite_element :
2113 player->dynabombs_left >= min_dynabombs_left ?
2114 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2119 int min_dynabombs_left = pos + 1;
2120 int min_inventory_size = pos + 1 - player->dynabombs_left;
2121 int inventory_pos = pos - player->dynabombs_left;
2123 return (player->inventory_infinite_element != EL_UNDEFINED ?
2124 player->inventory_infinite_element :
2125 player->dynabombs_left >= min_dynabombs_left ?
2126 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2127 player->inventory_size >= min_inventory_size ?
2128 player->inventory_element[inventory_pos] :
2133 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2135 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2136 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2139 if (gpo1->sort_priority != gpo2->sort_priority)
2140 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2142 compare_result = gpo1->nr - gpo2->nr;
2144 return compare_result;
2147 int getPlayerInventorySize(int player_nr)
2149 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2150 return game_em.ply[player_nr]->dynamite;
2151 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2152 return game_sp.red_disk_count;
2154 return stored_player[player_nr].inventory_size;
2157 static void InitGameControlValues(void)
2161 for (i = 0; game_panel_controls[i].nr != -1; i++)
2163 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2164 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2165 struct TextPosInfo *pos = gpc->pos;
2167 int type = gpc->type;
2171 Error("'game_panel_controls' structure corrupted at %d", i);
2173 Fail("this should not happen -- please debug");
2176 // force update of game controls after initialization
2177 gpc->value = gpc->last_value = -1;
2178 gpc->frame = gpc->last_frame = -1;
2179 gpc->gfx_frame = -1;
2181 // determine panel value width for later calculation of alignment
2182 if (type == TYPE_INTEGER || type == TYPE_STRING)
2184 pos->width = pos->size * getFontWidth(pos->font);
2185 pos->height = getFontHeight(pos->font);
2187 else if (type == TYPE_ELEMENT)
2189 pos->width = pos->size;
2190 pos->height = pos->size;
2193 // fill structure for game panel draw order
2195 gpo->sort_priority = pos->sort_priority;
2198 // sort game panel controls according to sort_priority and control number
2199 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2200 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2203 static void UpdatePlayfieldElementCount(void)
2205 boolean use_element_count = FALSE;
2208 // first check if it is needed at all to calculate playfield element count
2209 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2210 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2211 use_element_count = TRUE;
2213 if (!use_element_count)
2216 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2217 element_info[i].element_count = 0;
2219 SCAN_PLAYFIELD(x, y)
2221 element_info[Tile[x][y]].element_count++;
2224 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2225 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2226 if (IS_IN_GROUP(j, i))
2227 element_info[EL_GROUP_START + i].element_count +=
2228 element_info[j].element_count;
2231 static void UpdateGameControlValues(void)
2234 int time = (game.LevelSolved ?
2235 game.LevelSolved_CountingTime :
2236 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2238 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2239 game_sp.time_played :
2240 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2241 game_mm.energy_left :
2242 game.no_time_limit ? TimePlayed : TimeLeft);
2243 int score = (game.LevelSolved ?
2244 game.LevelSolved_CountingScore :
2245 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2246 game_em.lev->score :
2247 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2249 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2252 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2253 game_em.lev->gems_needed :
2254 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2255 game_sp.infotrons_still_needed :
2256 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2257 game_mm.kettles_still_needed :
2258 game.gems_still_needed);
2259 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2260 game_em.lev->gems_needed > 0 :
2261 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2262 game_sp.infotrons_still_needed > 0 :
2263 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2264 game_mm.kettles_still_needed > 0 ||
2265 game_mm.lights_still_needed > 0 :
2266 game.gems_still_needed > 0 ||
2267 game.sokoban_fields_still_needed > 0 ||
2268 game.sokoban_objects_still_needed > 0 ||
2269 game.lights_still_needed > 0);
2270 int health = (game.LevelSolved ?
2271 game.LevelSolved_CountingHealth :
2272 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2273 MM_HEALTH(game_mm.laser_overload_value) :
2275 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2277 UpdatePlayfieldElementCount();
2279 // update game panel control values
2281 // used instead of "level_nr" (for network games)
2282 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2283 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2285 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2286 for (i = 0; i < MAX_NUM_KEYS; i++)
2287 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2288 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2289 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2291 if (game.centered_player_nr == -1)
2293 for (i = 0; i < MAX_PLAYERS; i++)
2295 // only one player in Supaplex game engine
2296 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2299 for (k = 0; k < MAX_NUM_KEYS; k++)
2301 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2303 if (game_em.ply[i]->keys & (1 << k))
2304 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2305 get_key_element_from_nr(k);
2307 else if (stored_player[i].key[k])
2308 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2309 get_key_element_from_nr(k);
2312 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2313 getPlayerInventorySize(i);
2315 if (stored_player[i].num_white_keys > 0)
2316 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2319 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2320 stored_player[i].num_white_keys;
2325 int player_nr = game.centered_player_nr;
2327 for (k = 0; k < MAX_NUM_KEYS; k++)
2329 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2331 if (game_em.ply[player_nr]->keys & (1 << k))
2332 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2333 get_key_element_from_nr(k);
2335 else if (stored_player[player_nr].key[k])
2336 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2337 get_key_element_from_nr(k);
2340 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2341 getPlayerInventorySize(player_nr);
2343 if (stored_player[player_nr].num_white_keys > 0)
2344 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2346 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2347 stored_player[player_nr].num_white_keys;
2350 // re-arrange keys on game panel, if needed or if defined by style settings
2351 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2353 int nr = GAME_PANEL_KEY_1 + i;
2354 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2355 struct TextPosInfo *pos = gpc->pos;
2357 // skip check if key is not in the player's inventory
2358 if (gpc->value == EL_EMPTY)
2361 // check if keys should be arranged on panel from left to right
2362 if (pos->style == STYLE_LEFTMOST_POSITION)
2364 // check previous key positions (left from current key)
2365 for (k = 0; k < i; k++)
2367 int nr_new = GAME_PANEL_KEY_1 + k;
2369 if (game_panel_controls[nr_new].value == EL_EMPTY)
2371 game_panel_controls[nr_new].value = gpc->value;
2372 gpc->value = EL_EMPTY;
2379 // check if "undefined" keys can be placed at some other position
2380 if (pos->x == -1 && pos->y == -1)
2382 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2384 // 1st try: display key at the same position as normal or EM keys
2385 if (game_panel_controls[nr_new].value == EL_EMPTY)
2387 game_panel_controls[nr_new].value = gpc->value;
2391 // 2nd try: display key at the next free position in the key panel
2392 for (k = 0; k < STD_NUM_KEYS; k++)
2394 nr_new = GAME_PANEL_KEY_1 + k;
2396 if (game_panel_controls[nr_new].value == EL_EMPTY)
2398 game_panel_controls[nr_new].value = gpc->value;
2407 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2409 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2410 get_inventory_element_from_pos(local_player, i);
2411 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2412 get_inventory_element_from_pos(local_player, -i - 1);
2415 game_panel_controls[GAME_PANEL_SCORE].value = score;
2416 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2418 game_panel_controls[GAME_PANEL_TIME].value = time;
2420 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2421 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2422 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2424 if (level.time == 0)
2425 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2427 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2429 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2430 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2432 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2434 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2435 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2437 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2438 local_player->shield_normal_time_left;
2439 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2440 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2442 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2443 local_player->shield_deadly_time_left;
2445 game_panel_controls[GAME_PANEL_EXIT].value =
2446 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2448 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2449 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2450 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2451 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2452 EL_EMC_MAGIC_BALL_SWITCH);
2454 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2455 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2456 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2457 game.light_time_left;
2459 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2460 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2461 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2462 game.timegate_time_left;
2464 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2465 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2467 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2468 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2469 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2470 game.lenses_time_left;
2472 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2473 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2474 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2475 game.magnify_time_left;
2477 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2478 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2479 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2480 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2481 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2482 EL_BALLOON_SWITCH_NONE);
2484 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2485 local_player->dynabomb_count;
2486 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2487 local_player->dynabomb_size;
2488 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2489 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2491 game_panel_controls[GAME_PANEL_PENGUINS].value =
2492 game.friends_still_needed;
2494 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2495 game.sokoban_objects_still_needed;
2496 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2497 game.sokoban_fields_still_needed;
2499 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2500 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2502 for (i = 0; i < NUM_BELTS; i++)
2504 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2505 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2506 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2507 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2508 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2511 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2512 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2513 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2514 game.magic_wall_time_left;
2516 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2517 local_player->gravity;
2519 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2520 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2522 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2523 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2524 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2525 game.panel.element[i].id : EL_UNDEFINED);
2527 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2528 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2529 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2530 element_info[game.panel.element_count[i].id].element_count : 0);
2532 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2533 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2534 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2535 element_info[game.panel.ce_score[i].id].collect_score : 0);
2537 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2538 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2539 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2540 element_info[game.panel.ce_score_element[i].id].collect_score :
2543 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2544 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2545 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2547 // update game panel control frames
2549 for (i = 0; game_panel_controls[i].nr != -1; i++)
2551 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2553 if (gpc->type == TYPE_ELEMENT)
2555 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2557 int last_anim_random_frame = gfx.anim_random_frame;
2558 int element = gpc->value;
2559 int graphic = el2panelimg(element);
2560 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2561 sync_random_frame : INIT_GFX_RANDOM());
2563 if (gpc->value != gpc->last_value)
2566 gpc->gfx_random = init_gfx_random;
2572 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2573 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2574 gpc->gfx_random = init_gfx_random;
2577 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2578 gfx.anim_random_frame = gpc->gfx_random;
2580 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2581 gpc->gfx_frame = element_info[element].collect_score;
2583 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2585 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2586 gfx.anim_random_frame = last_anim_random_frame;
2589 else if (gpc->type == TYPE_GRAPHIC)
2591 if (gpc->graphic != IMG_UNDEFINED)
2593 int last_anim_random_frame = gfx.anim_random_frame;
2594 int graphic = gpc->graphic;
2595 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2596 sync_random_frame : INIT_GFX_RANDOM());
2598 if (gpc->value != gpc->last_value)
2601 gpc->gfx_random = init_gfx_random;
2607 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2608 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2609 gpc->gfx_random = init_gfx_random;
2612 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2613 gfx.anim_random_frame = gpc->gfx_random;
2615 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2617 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2618 gfx.anim_random_frame = last_anim_random_frame;
2624 static void DisplayGameControlValues(void)
2626 boolean redraw_panel = FALSE;
2629 for (i = 0; game_panel_controls[i].nr != -1; i++)
2631 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2633 if (PANEL_DEACTIVATED(gpc->pos))
2636 if (gpc->value == gpc->last_value &&
2637 gpc->frame == gpc->last_frame)
2640 redraw_panel = TRUE;
2646 // copy default game door content to main double buffer
2648 // !!! CHECK AGAIN !!!
2649 SetPanelBackground();
2650 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2651 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2653 // redraw game control buttons
2654 RedrawGameButtons();
2656 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2658 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2660 int nr = game_panel_order[i].nr;
2661 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2662 struct TextPosInfo *pos = gpc->pos;
2663 int type = gpc->type;
2664 int value = gpc->value;
2665 int frame = gpc->frame;
2666 int size = pos->size;
2667 int font = pos->font;
2668 boolean draw_masked = pos->draw_masked;
2669 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2671 if (PANEL_DEACTIVATED(pos))
2674 if (pos->class == get_hash_from_key("extra_panel_items") &&
2675 !setup.prefer_extra_panel_items)
2678 gpc->last_value = value;
2679 gpc->last_frame = frame;
2681 if (type == TYPE_INTEGER)
2683 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2684 nr == GAME_PANEL_TIME)
2686 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2688 if (use_dynamic_size) // use dynamic number of digits
2690 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2691 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2692 int size2 = size1 + 1;
2693 int font1 = pos->font;
2694 int font2 = pos->font_alt;
2696 size = (value < value_change ? size1 : size2);
2697 font = (value < value_change ? font1 : font2);
2701 // correct text size if "digits" is zero or less
2703 size = strlen(int2str(value, size));
2705 // dynamically correct text alignment
2706 pos->width = size * getFontWidth(font);
2708 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2709 int2str(value, size), font, mask_mode);
2711 else if (type == TYPE_ELEMENT)
2713 int element, graphic;
2717 int dst_x = PANEL_XPOS(pos);
2718 int dst_y = PANEL_YPOS(pos);
2720 if (value != EL_UNDEFINED && value != EL_EMPTY)
2723 graphic = el2panelimg(value);
2726 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2727 element, EL_NAME(element), size);
2730 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2733 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2736 width = graphic_info[graphic].width * size / TILESIZE;
2737 height = graphic_info[graphic].height * size / TILESIZE;
2740 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2743 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2747 else if (type == TYPE_GRAPHIC)
2749 int graphic = gpc->graphic;
2750 int graphic_active = gpc->graphic_active;
2754 int dst_x = PANEL_XPOS(pos);
2755 int dst_y = PANEL_YPOS(pos);
2756 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2757 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2759 if (graphic != IMG_UNDEFINED && !skip)
2761 if (pos->style == STYLE_REVERSE)
2762 value = 100 - value;
2764 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2766 if (pos->direction & MV_HORIZONTAL)
2768 width = graphic_info[graphic_active].width * value / 100;
2769 height = graphic_info[graphic_active].height;
2771 if (pos->direction == MV_LEFT)
2773 src_x += graphic_info[graphic_active].width - width;
2774 dst_x += graphic_info[graphic_active].width - width;
2779 width = graphic_info[graphic_active].width;
2780 height = graphic_info[graphic_active].height * value / 100;
2782 if (pos->direction == MV_UP)
2784 src_y += graphic_info[graphic_active].height - height;
2785 dst_y += graphic_info[graphic_active].height - height;
2790 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2793 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2796 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2798 if (pos->direction & MV_HORIZONTAL)
2800 if (pos->direction == MV_RIGHT)
2807 dst_x = PANEL_XPOS(pos);
2810 width = graphic_info[graphic].width - width;
2814 if (pos->direction == MV_DOWN)
2821 dst_y = PANEL_YPOS(pos);
2824 height = graphic_info[graphic].height - height;
2828 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2831 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2835 else if (type == TYPE_STRING)
2837 boolean active = (value != 0);
2838 char *state_normal = "off";
2839 char *state_active = "on";
2840 char *state = (active ? state_active : state_normal);
2841 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2842 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2843 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2844 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2846 if (nr == GAME_PANEL_GRAVITY_STATE)
2848 int font1 = pos->font; // (used for normal state)
2849 int font2 = pos->font_alt; // (used for active state)
2851 font = (active ? font2 : font1);
2860 // don't truncate output if "chars" is zero or less
2863 // dynamically correct text alignment
2864 pos->width = size * getFontWidth(font);
2867 s_cut = getStringCopyN(s, size);
2869 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2870 s_cut, font, mask_mode);
2876 redraw_mask |= REDRAW_DOOR_1;
2879 SetGameStatus(GAME_MODE_PLAYING);
2882 void UpdateAndDisplayGameControlValues(void)
2884 if (tape.deactivate_display)
2887 UpdateGameControlValues();
2888 DisplayGameControlValues();
2891 void UpdateGameDoorValues(void)
2893 UpdateGameControlValues();
2896 void DrawGameDoorValues(void)
2898 DisplayGameControlValues();
2902 // ============================================================================
2904 // ----------------------------------------------------------------------------
2905 // initialize game engine due to level / tape version number
2906 // ============================================================================
2908 static void InitGameEngine(void)
2910 int i, j, k, l, x, y;
2912 // set game engine from tape file when re-playing, else from level file
2913 game.engine_version = (tape.playing ? tape.engine_version :
2914 level.game_version);
2916 // set single or multi-player game mode (needed for re-playing tapes)
2917 game.team_mode = setup.team_mode;
2921 int num_players = 0;
2923 for (i = 0; i < MAX_PLAYERS; i++)
2924 if (tape.player_participates[i])
2927 // multi-player tapes contain input data for more than one player
2928 game.team_mode = (num_players > 1);
2932 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2933 level.game_version);
2934 Debug("game:init:level", " tape.file_version == %06d",
2936 Debug("game:init:level", " tape.game_version == %06d",
2938 Debug("game:init:level", " tape.engine_version == %06d",
2939 tape.engine_version);
2940 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2941 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2944 // --------------------------------------------------------------------------
2945 // set flags for bugs and changes according to active game engine version
2946 // --------------------------------------------------------------------------
2950 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2952 Bug was introduced in version:
2955 Bug was fixed in version:
2959 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2960 but the property "can fall" was missing, which caused some levels to be
2961 unsolvable. This was fixed in version 4.2.0.0.
2963 Affected levels/tapes:
2964 An example for a tape that was fixed by this bugfix is tape 029 from the
2965 level set "rnd_sam_bateman".
2966 The wrong behaviour will still be used for all levels or tapes that were
2967 created/recorded with it. An example for this is tape 023 from the level
2968 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2971 boolean use_amoeba_dropping_cannot_fall_bug =
2972 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2973 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2975 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2976 tape.game_version < VERSION_IDENT(4,2,0,0)));
2979 Summary of bugfix/change:
2980 Fixed move speed of elements entering or leaving magic wall.
2982 Fixed/changed in version:
2986 Before 2.0.1, move speed of elements entering or leaving magic wall was
2987 twice as fast as it is now.
2988 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2990 Affected levels/tapes:
2991 The first condition is generally needed for all levels/tapes before version
2992 2.0.1, which might use the old behaviour before it was changed; known tapes
2993 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2994 The second condition is an exception from the above case and is needed for
2995 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2996 above, but before it was known that this change would break tapes like the
2997 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2998 although the engine version while recording maybe was before 2.0.1. There
2999 are a lot of tapes that are affected by this exception, like tape 006 from
3000 the level set "rnd_conor_mancone".
3003 boolean use_old_move_stepsize_for_magic_wall =
3004 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3006 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3007 tape.game_version < VERSION_IDENT(4,2,0,0)));
3010 Summary of bugfix/change:
3011 Fixed handling for custom elements that change when pushed by the player.
3013 Fixed/changed in version:
3017 Before 3.1.0, custom elements that "change when pushing" changed directly
3018 after the player started pushing them (until then handled in "DigField()").
3019 Since 3.1.0, these custom elements are not changed until the "pushing"
3020 move of the element is finished (now handled in "ContinueMoving()").
3022 Affected levels/tapes:
3023 The first condition is generally needed for all levels/tapes before version
3024 3.1.0, which might use the old behaviour before it was changed; known tapes
3025 that are affected are some tapes from the level set "Walpurgis Gardens" by
3027 The second condition is an exception from the above case and is needed for
3028 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3029 above (including some development versions of 3.1.0), but before it was
3030 known that this change would break tapes like the above and was fixed in
3031 3.1.1, so that the changed behaviour was active although the engine version
3032 while recording maybe was before 3.1.0. There is at least one tape that is
3033 affected by this exception, which is the tape for the one-level set "Bug
3034 Machine" by Juergen Bonhagen.
3037 game.use_change_when_pushing_bug =
3038 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3040 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3041 tape.game_version < VERSION_IDENT(3,1,1,0)));
3044 Summary of bugfix/change:
3045 Fixed handling for blocking the field the player leaves when moving.
3047 Fixed/changed in version:
3051 Before 3.1.1, when "block last field when moving" was enabled, the field
3052 the player is leaving when moving was blocked for the time of the move,
3053 and was directly unblocked afterwards. This resulted in the last field
3054 being blocked for exactly one less than the number of frames of one player
3055 move. Additionally, even when blocking was disabled, the last field was
3056 blocked for exactly one frame.
3057 Since 3.1.1, due to changes in player movement handling, the last field
3058 is not blocked at all when blocking is disabled. When blocking is enabled,
3059 the last field is blocked for exactly the number of frames of one player
3060 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3061 last field is blocked for exactly one more than the number of frames of
3064 Affected levels/tapes:
3065 (!!! yet to be determined -- probably many !!!)
3068 game.use_block_last_field_bug =
3069 (game.engine_version < VERSION_IDENT(3,1,1,0));
3071 /* various special flags and settings for native Emerald Mine game engine */
3073 game_em.use_single_button =
3074 (game.engine_version > VERSION_IDENT(4,0,0,2));
3076 game_em.use_snap_key_bug =
3077 (game.engine_version < VERSION_IDENT(4,0,1,0));
3079 game_em.use_random_bug =
3080 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3082 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3084 game_em.use_old_explosions = use_old_em_engine;
3085 game_em.use_old_android = use_old_em_engine;
3086 game_em.use_old_push_elements = use_old_em_engine;
3087 game_em.use_old_push_into_acid = use_old_em_engine;
3089 game_em.use_wrap_around = !use_old_em_engine;
3091 // --------------------------------------------------------------------------
3093 // set maximal allowed number of custom element changes per game frame
3094 game.max_num_changes_per_frame = 1;
3096 // default scan direction: scan playfield from top/left to bottom/right
3097 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3099 // dynamically adjust element properties according to game engine version
3100 InitElementPropertiesEngine(game.engine_version);
3102 // ---------- initialize special element properties -------------------------
3104 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3105 if (use_amoeba_dropping_cannot_fall_bug)
3106 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3108 // ---------- initialize player's initial move delay ------------------------
3110 // dynamically adjust player properties according to level information
3111 for (i = 0; i < MAX_PLAYERS; i++)
3112 game.initial_move_delay_value[i] =
3113 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3115 // dynamically adjust player properties according to game engine version
3116 for (i = 0; i < MAX_PLAYERS; i++)
3117 game.initial_move_delay[i] =
3118 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3119 game.initial_move_delay_value[i] : 0);
3121 // ---------- initialize player's initial push delay ------------------------
3123 // dynamically adjust player properties according to game engine version
3124 game.initial_push_delay_value =
3125 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3127 // ---------- initialize changing elements ----------------------------------
3129 // initialize changing elements information
3130 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3132 struct ElementInfo *ei = &element_info[i];
3134 // this pointer might have been changed in the level editor
3135 ei->change = &ei->change_page[0];
3137 if (!IS_CUSTOM_ELEMENT(i))
3139 ei->change->target_element = EL_EMPTY_SPACE;
3140 ei->change->delay_fixed = 0;
3141 ei->change->delay_random = 0;
3142 ei->change->delay_frames = 1;
3145 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3147 ei->has_change_event[j] = FALSE;
3149 ei->event_page_nr[j] = 0;
3150 ei->event_page[j] = &ei->change_page[0];
3154 // add changing elements from pre-defined list
3155 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3157 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3158 struct ElementInfo *ei = &element_info[ch_delay->element];
3160 ei->change->target_element = ch_delay->target_element;
3161 ei->change->delay_fixed = ch_delay->change_delay;
3163 ei->change->pre_change_function = ch_delay->pre_change_function;
3164 ei->change->change_function = ch_delay->change_function;
3165 ei->change->post_change_function = ch_delay->post_change_function;
3167 ei->change->can_change = TRUE;
3168 ei->change->can_change_or_has_action = TRUE;
3170 ei->has_change_event[CE_DELAY] = TRUE;
3172 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3173 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3176 // ---------- initialize internal run-time variables ------------------------
3178 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3180 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3182 for (j = 0; j < ei->num_change_pages; j++)
3184 ei->change_page[j].can_change_or_has_action =
3185 (ei->change_page[j].can_change |
3186 ei->change_page[j].has_action);
3190 // add change events from custom element configuration
3191 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3193 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3195 for (j = 0; j < ei->num_change_pages; j++)
3197 if (!ei->change_page[j].can_change_or_has_action)
3200 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3202 // only add event page for the first page found with this event
3203 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3205 ei->has_change_event[k] = TRUE;
3207 ei->event_page_nr[k] = j;
3208 ei->event_page[k] = &ei->change_page[j];
3214 // ---------- initialize reference elements in change conditions ------------
3216 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3218 int element = EL_CUSTOM_START + i;
3219 struct ElementInfo *ei = &element_info[element];
3221 for (j = 0; j < ei->num_change_pages; j++)
3223 int trigger_element = ei->change_page[j].initial_trigger_element;
3225 if (trigger_element >= EL_PREV_CE_8 &&
3226 trigger_element <= EL_NEXT_CE_8)
3227 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3229 ei->change_page[j].trigger_element = trigger_element;
3233 // ---------- initialize run-time trigger player and element ----------------
3235 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3237 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3239 for (j = 0; j < ei->num_change_pages; j++)
3241 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3242 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3243 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3244 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3245 ei->change_page[j].actual_trigger_ce_value = 0;
3246 ei->change_page[j].actual_trigger_ce_score = 0;
3250 // ---------- initialize trigger events -------------------------------------
3252 // initialize trigger events information
3253 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3254 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3255 trigger_events[i][j] = FALSE;
3257 // add trigger events from element change event properties
3258 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3260 struct ElementInfo *ei = &element_info[i];
3262 for (j = 0; j < ei->num_change_pages; j++)
3264 if (!ei->change_page[j].can_change_or_has_action)
3267 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3269 int trigger_element = ei->change_page[j].trigger_element;
3271 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3273 if (ei->change_page[j].has_event[k])
3275 if (IS_GROUP_ELEMENT(trigger_element))
3277 struct ElementGroupInfo *group =
3278 element_info[trigger_element].group;
3280 for (l = 0; l < group->num_elements_resolved; l++)
3281 trigger_events[group->element_resolved[l]][k] = TRUE;
3283 else if (trigger_element == EL_ANY_ELEMENT)
3284 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3285 trigger_events[l][k] = TRUE;
3287 trigger_events[trigger_element][k] = TRUE;
3294 // ---------- initialize push delay -----------------------------------------
3296 // initialize push delay values to default
3297 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3299 if (!IS_CUSTOM_ELEMENT(i))
3301 // set default push delay values (corrected since version 3.0.7-1)
3302 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3304 element_info[i].push_delay_fixed = 2;
3305 element_info[i].push_delay_random = 8;
3309 element_info[i].push_delay_fixed = 8;
3310 element_info[i].push_delay_random = 8;
3315 // set push delay value for certain elements from pre-defined list
3316 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3318 int e = push_delay_list[i].element;
3320 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3321 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3324 // set push delay value for Supaplex elements for newer engine versions
3325 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3327 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3329 if (IS_SP_ELEMENT(i))
3331 // set SP push delay to just enough to push under a falling zonk
3332 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3334 element_info[i].push_delay_fixed = delay;
3335 element_info[i].push_delay_random = 0;
3340 // ---------- initialize move stepsize --------------------------------------
3342 // initialize move stepsize values to default
3343 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3344 if (!IS_CUSTOM_ELEMENT(i))
3345 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3347 // set move stepsize value for certain elements from pre-defined list
3348 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3350 int e = move_stepsize_list[i].element;
3352 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3354 // set move stepsize value for certain elements for older engine versions
3355 if (use_old_move_stepsize_for_magic_wall)
3357 if (e == EL_MAGIC_WALL_FILLING ||
3358 e == EL_MAGIC_WALL_EMPTYING ||
3359 e == EL_BD_MAGIC_WALL_FILLING ||
3360 e == EL_BD_MAGIC_WALL_EMPTYING)
3361 element_info[e].move_stepsize *= 2;
3365 // ---------- initialize collect score --------------------------------------
3367 // initialize collect score values for custom elements from initial value
3368 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3369 if (IS_CUSTOM_ELEMENT(i))
3370 element_info[i].collect_score = element_info[i].collect_score_initial;
3372 // ---------- initialize collect count --------------------------------------
3374 // initialize collect count values for non-custom elements
3375 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3376 if (!IS_CUSTOM_ELEMENT(i))
3377 element_info[i].collect_count_initial = 0;
3379 // add collect count values for all elements from pre-defined list
3380 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3381 element_info[collect_count_list[i].element].collect_count_initial =
3382 collect_count_list[i].count;
3384 // ---------- initialize access direction -----------------------------------
3386 // initialize access direction values to default (access from every side)
3387 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3388 if (!IS_CUSTOM_ELEMENT(i))
3389 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3391 // set access direction value for certain elements from pre-defined list
3392 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3393 element_info[access_direction_list[i].element].access_direction =
3394 access_direction_list[i].direction;
3396 // ---------- initialize explosion content ----------------------------------
3397 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3399 if (IS_CUSTOM_ELEMENT(i))
3402 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3404 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3406 element_info[i].content.e[x][y] =
3407 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3408 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3409 i == EL_PLAYER_3 ? EL_EMERALD :
3410 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3411 i == EL_MOLE ? EL_EMERALD_RED :
3412 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3413 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3414 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3415 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3416 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3417 i == EL_WALL_EMERALD ? EL_EMERALD :
3418 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3419 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3420 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3421 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3422 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3423 i == EL_WALL_PEARL ? EL_PEARL :
3424 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3429 // ---------- initialize recursion detection --------------------------------
3430 recursion_loop_depth = 0;
3431 recursion_loop_detected = FALSE;
3432 recursion_loop_element = EL_UNDEFINED;
3434 // ---------- initialize graphics engine ------------------------------------
3435 game.scroll_delay_value =
3436 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3437 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3438 !setup.forced_scroll_delay ? 0 :
3439 setup.scroll_delay ? setup.scroll_delay_value : 0);
3440 game.scroll_delay_value =
3441 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3443 // ---------- initialize game engine snapshots ------------------------------
3444 for (i = 0; i < MAX_PLAYERS; i++)
3445 game.snapshot.last_action[i] = 0;
3446 game.snapshot.changed_action = FALSE;
3447 game.snapshot.collected_item = FALSE;
3448 game.snapshot.mode =
3449 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3450 SNAPSHOT_MODE_EVERY_STEP :
3451 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3452 SNAPSHOT_MODE_EVERY_MOVE :
3453 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3454 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3455 game.snapshot.save_snapshot = FALSE;
3457 // ---------- initialize level time for Supaplex engine ---------------------
3458 // Supaplex levels with time limit currently unsupported -- should be added
3459 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3462 // ---------- initialize flags for handling game actions --------------------
3464 // set flags for game actions to default values
3465 game.use_key_actions = TRUE;
3466 game.use_mouse_actions = FALSE;
3468 // when using Mirror Magic game engine, handle mouse events only
3469 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3471 game.use_key_actions = FALSE;
3472 game.use_mouse_actions = TRUE;
3475 // check for custom elements with mouse click events
3476 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3478 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3480 int element = EL_CUSTOM_START + i;
3482 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3483 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3484 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3485 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3486 game.use_mouse_actions = TRUE;
3491 static int get_num_special_action(int element, int action_first,
3494 int num_special_action = 0;
3497 for (i = action_first; i <= action_last; i++)
3499 boolean found = FALSE;
3501 for (j = 0; j < NUM_DIRECTIONS; j++)
3502 if (el_act_dir2img(element, i, j) !=
3503 el_act_dir2img(element, ACTION_DEFAULT, j))
3507 num_special_action++;
3512 return num_special_action;
3516 // ============================================================================
3518 // ----------------------------------------------------------------------------
3519 // initialize and start new game
3520 // ============================================================================
3522 #if DEBUG_INIT_PLAYER
3523 static void DebugPrintPlayerStatus(char *message)
3530 Debug("game:init:player", "%s:", message);
3532 for (i = 0; i < MAX_PLAYERS; i++)
3534 struct PlayerInfo *player = &stored_player[i];
3536 Debug("game:init:player",
3537 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3541 player->connected_locally,
3542 player->connected_network,
3544 (local_player == player ? " (local player)" : ""));
3551 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3552 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3553 int fade_mask = REDRAW_FIELD;
3555 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3556 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3557 int initial_move_dir = MV_DOWN;
3560 // required here to update video display before fading (FIX THIS)
3561 DrawMaskedBorder(REDRAW_DOOR_2);
3563 if (!game.restart_level)
3564 CloseDoor(DOOR_CLOSE_1);
3566 SetGameStatus(GAME_MODE_PLAYING);
3568 if (level_editor_test_game)
3569 FadeSkipNextFadeOut();
3571 FadeSetEnterScreen();
3574 fade_mask = REDRAW_ALL;
3576 FadeLevelSoundsAndMusic();
3578 ExpireSoundLoops(TRUE);
3582 if (level_editor_test_game)
3583 FadeSkipNextFadeIn();
3585 // needed if different viewport properties defined for playing
3586 ChangeViewportPropertiesIfNeeded();
3590 DrawCompleteVideoDisplay();
3592 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3595 InitGameControlValues();
3599 // initialize tape actions from game when recording tape
3600 tape.use_key_actions = game.use_key_actions;
3601 tape.use_mouse_actions = game.use_mouse_actions;
3603 // initialize visible playfield size when recording tape (for team mode)
3604 tape.scr_fieldx = SCR_FIELDX;
3605 tape.scr_fieldy = SCR_FIELDY;
3608 // don't play tapes over network
3609 network_playing = (network.enabled && !tape.playing);
3611 for (i = 0; i < MAX_PLAYERS; i++)
3613 struct PlayerInfo *player = &stored_player[i];
3615 player->index_nr = i;
3616 player->index_bit = (1 << i);
3617 player->element_nr = EL_PLAYER_1 + i;
3619 player->present = FALSE;
3620 player->active = FALSE;
3621 player->mapped = FALSE;
3623 player->killed = FALSE;
3624 player->reanimated = FALSE;
3625 player->buried = FALSE;
3628 player->effective_action = 0;
3629 player->programmed_action = 0;
3630 player->snap_action = 0;
3632 player->mouse_action.lx = 0;
3633 player->mouse_action.ly = 0;
3634 player->mouse_action.button = 0;
3635 player->mouse_action.button_hint = 0;
3637 player->effective_mouse_action.lx = 0;
3638 player->effective_mouse_action.ly = 0;
3639 player->effective_mouse_action.button = 0;
3640 player->effective_mouse_action.button_hint = 0;
3642 for (j = 0; j < MAX_NUM_KEYS; j++)
3643 player->key[j] = FALSE;
3645 player->num_white_keys = 0;
3647 player->dynabomb_count = 0;
3648 player->dynabomb_size = 1;
3649 player->dynabombs_left = 0;
3650 player->dynabomb_xl = FALSE;
3652 player->MovDir = initial_move_dir;
3655 player->GfxDir = initial_move_dir;
3656 player->GfxAction = ACTION_DEFAULT;
3658 player->StepFrame = 0;
3660 player->initial_element = player->element_nr;
3661 player->artwork_element =
3662 (level.use_artwork_element[i] ? level.artwork_element[i] :
3663 player->element_nr);
3664 player->use_murphy = FALSE;
3666 player->block_last_field = FALSE; // initialized in InitPlayerField()
3667 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3669 player->gravity = level.initial_player_gravity[i];
3671 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3673 player->actual_frame_counter = 0;
3675 player->step_counter = 0;
3677 player->last_move_dir = initial_move_dir;
3679 player->is_active = FALSE;
3681 player->is_waiting = FALSE;
3682 player->is_moving = FALSE;
3683 player->is_auto_moving = FALSE;
3684 player->is_digging = FALSE;
3685 player->is_snapping = FALSE;
3686 player->is_collecting = FALSE;
3687 player->is_pushing = FALSE;
3688 player->is_switching = FALSE;
3689 player->is_dropping = FALSE;
3690 player->is_dropping_pressed = FALSE;
3692 player->is_bored = FALSE;
3693 player->is_sleeping = FALSE;
3695 player->was_waiting = TRUE;
3696 player->was_moving = FALSE;
3697 player->was_snapping = FALSE;
3698 player->was_dropping = FALSE;
3700 player->force_dropping = FALSE;
3702 player->frame_counter_bored = -1;
3703 player->frame_counter_sleeping = -1;
3705 player->anim_delay_counter = 0;
3706 player->post_delay_counter = 0;
3708 player->dir_waiting = initial_move_dir;
3709 player->action_waiting = ACTION_DEFAULT;
3710 player->last_action_waiting = ACTION_DEFAULT;
3711 player->special_action_bored = ACTION_DEFAULT;
3712 player->special_action_sleeping = ACTION_DEFAULT;
3714 player->switch_x = -1;
3715 player->switch_y = -1;
3717 player->drop_x = -1;
3718 player->drop_y = -1;
3720 player->show_envelope = 0;
3722 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3724 player->push_delay = -1; // initialized when pushing starts
3725 player->push_delay_value = game.initial_push_delay_value;
3727 player->drop_delay = 0;
3728 player->drop_pressed_delay = 0;
3730 player->last_jx = -1;
3731 player->last_jy = -1;
3735 player->shield_normal_time_left = 0;
3736 player->shield_deadly_time_left = 0;
3738 player->last_removed_element = EL_UNDEFINED;
3740 player->inventory_infinite_element = EL_UNDEFINED;
3741 player->inventory_size = 0;
3743 if (level.use_initial_inventory[i])
3745 for (j = 0; j < level.initial_inventory_size[i]; j++)
3747 int element = level.initial_inventory_content[i][j];
3748 int collect_count = element_info[element].collect_count_initial;
3751 if (!IS_CUSTOM_ELEMENT(element))
3754 if (collect_count == 0)
3755 player->inventory_infinite_element = element;
3757 for (k = 0; k < collect_count; k++)
3758 if (player->inventory_size < MAX_INVENTORY_SIZE)
3759 player->inventory_element[player->inventory_size++] = element;
3763 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3764 SnapField(player, 0, 0);
3766 map_player_action[i] = i;
3769 network_player_action_received = FALSE;
3771 // initial null action
3772 if (network_playing)
3773 SendToServer_MovePlayer(MV_NONE);
3778 TimeLeft = level.time;
3781 ScreenMovDir = MV_NONE;
3785 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3787 game.robot_wheel_x = -1;
3788 game.robot_wheel_y = -1;
3793 game.all_players_gone = FALSE;
3795 game.LevelSolved = FALSE;
3796 game.GameOver = FALSE;
3798 game.GamePlayed = !tape.playing;
3800 game.LevelSolved_GameWon = FALSE;
3801 game.LevelSolved_GameEnd = FALSE;
3802 game.LevelSolved_SaveTape = FALSE;
3803 game.LevelSolved_SaveScore = FALSE;
3805 game.LevelSolved_CountingTime = 0;
3806 game.LevelSolved_CountingScore = 0;
3807 game.LevelSolved_CountingHealth = 0;
3809 game.panel.active = TRUE;
3811 game.no_time_limit = (level.time == 0);
3813 game.yamyam_content_nr = 0;
3814 game.robot_wheel_active = FALSE;
3815 game.magic_wall_active = FALSE;
3816 game.magic_wall_time_left = 0;
3817 game.light_time_left = 0;
3818 game.timegate_time_left = 0;
3819 game.switchgate_pos = 0;
3820 game.wind_direction = level.wind_direction_initial;
3822 game.time_final = 0;
3823 game.score_time_final = 0;
3826 game.score_final = 0;
3828 game.health = MAX_HEALTH;
3829 game.health_final = MAX_HEALTH;
3831 game.gems_still_needed = level.gems_needed;
3832 game.sokoban_fields_still_needed = 0;
3833 game.sokoban_objects_still_needed = 0;
3834 game.lights_still_needed = 0;
3835 game.players_still_needed = 0;
3836 game.friends_still_needed = 0;
3838 game.lenses_time_left = 0;
3839 game.magnify_time_left = 0;
3841 game.ball_active = level.ball_active_initial;
3842 game.ball_content_nr = 0;
3844 game.explosions_delayed = TRUE;
3846 game.envelope_active = FALSE;
3848 // special case: set custom artwork setting to initial value
3849 game.use_masked_elements = game.use_masked_elements_initial;
3851 for (i = 0; i < NUM_BELTS; i++)
3853 game.belt_dir[i] = MV_NONE;
3854 game.belt_dir_nr[i] = 3; // not moving, next moving left
3857 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3858 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3860 #if DEBUG_INIT_PLAYER
3861 DebugPrintPlayerStatus("Player status at level initialization");
3864 SCAN_PLAYFIELD(x, y)
3866 Tile[x][y] = Last[x][y] = level.field[x][y];
3867 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3868 ChangeDelay[x][y] = 0;
3869 ChangePage[x][y] = -1;
3870 CustomValue[x][y] = 0; // initialized in InitField()
3871 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3873 WasJustMoving[x][y] = 0;
3874 WasJustFalling[x][y] = 0;
3875 CheckCollision[x][y] = 0;
3876 CheckImpact[x][y] = 0;
3878 Pushed[x][y] = FALSE;
3880 ChangeCount[x][y] = 0;
3881 ChangeEvent[x][y] = -1;
3883 ExplodePhase[x][y] = 0;
3884 ExplodeDelay[x][y] = 0;
3885 ExplodeField[x][y] = EX_TYPE_NONE;
3887 RunnerVisit[x][y] = 0;
3888 PlayerVisit[x][y] = 0;
3891 GfxRandom[x][y] = INIT_GFX_RANDOM();
3892 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
3893 GfxElement[x][y] = EL_UNDEFINED;
3894 GfxElementEmpty[x][y] = EL_EMPTY;
3895 GfxAction[x][y] = ACTION_DEFAULT;
3896 GfxDir[x][y] = MV_NONE;
3897 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3900 SCAN_PLAYFIELD(x, y)
3902 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3904 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3907 InitField(x, y, TRUE);
3909 ResetGfxAnimation(x, y);
3914 for (i = 0; i < MAX_PLAYERS; i++)
3916 struct PlayerInfo *player = &stored_player[i];
3918 // set number of special actions for bored and sleeping animation
3919 player->num_special_action_bored =
3920 get_num_special_action(player->artwork_element,
3921 ACTION_BORING_1, ACTION_BORING_LAST);
3922 player->num_special_action_sleeping =
3923 get_num_special_action(player->artwork_element,
3924 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3927 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3928 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3930 // initialize type of slippery elements
3931 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3933 if (!IS_CUSTOM_ELEMENT(i))
3935 // default: elements slip down either to the left or right randomly
3936 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3938 // SP style elements prefer to slip down on the left side
3939 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3940 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3942 // BD style elements prefer to slip down on the left side
3943 if (game.emulation == EMU_BOULDERDASH)
3944 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3948 // initialize explosion and ignition delay
3949 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3951 if (!IS_CUSTOM_ELEMENT(i))
3954 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3955 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3956 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3957 int last_phase = (num_phase + 1) * delay;
3958 int half_phase = (num_phase / 2) * delay;
3960 element_info[i].explosion_delay = last_phase - 1;
3961 element_info[i].ignition_delay = half_phase;
3963 if (i == EL_BLACK_ORB)
3964 element_info[i].ignition_delay = 1;
3968 // correct non-moving belts to start moving left
3969 for (i = 0; i < NUM_BELTS; i++)
3970 if (game.belt_dir[i] == MV_NONE)
3971 game.belt_dir_nr[i] = 3; // not moving, next moving left
3973 #if USE_NEW_PLAYER_ASSIGNMENTS
3974 // use preferred player also in local single-player mode
3975 if (!network.enabled && !game.team_mode)
3977 int new_index_nr = setup.network_player_nr;
3979 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3981 for (i = 0; i < MAX_PLAYERS; i++)
3982 stored_player[i].connected_locally = FALSE;
3984 stored_player[new_index_nr].connected_locally = TRUE;
3988 for (i = 0; i < MAX_PLAYERS; i++)
3990 stored_player[i].connected = FALSE;
3992 // in network game mode, the local player might not be the first player
3993 if (stored_player[i].connected_locally)
3994 local_player = &stored_player[i];
3997 if (!network.enabled)
3998 local_player->connected = TRUE;
4002 for (i = 0; i < MAX_PLAYERS; i++)
4003 stored_player[i].connected = tape.player_participates[i];
4005 else if (network.enabled)
4007 // add team mode players connected over the network (needed for correct
4008 // assignment of player figures from level to locally playing players)
4010 for (i = 0; i < MAX_PLAYERS; i++)
4011 if (stored_player[i].connected_network)
4012 stored_player[i].connected = TRUE;
4014 else if (game.team_mode)
4016 // try to guess locally connected team mode players (needed for correct
4017 // assignment of player figures from level to locally playing players)
4019 for (i = 0; i < MAX_PLAYERS; i++)
4020 if (setup.input[i].use_joystick ||
4021 setup.input[i].key.left != KSYM_UNDEFINED)
4022 stored_player[i].connected = TRUE;
4025 #if DEBUG_INIT_PLAYER
4026 DebugPrintPlayerStatus("Player status after level initialization");
4029 #if DEBUG_INIT_PLAYER
4030 Debug("game:init:player", "Reassigning players ...");
4033 // check if any connected player was not found in playfield
4034 for (i = 0; i < MAX_PLAYERS; i++)
4036 struct PlayerInfo *player = &stored_player[i];
4038 if (player->connected && !player->present)
4040 struct PlayerInfo *field_player = NULL;
4042 #if DEBUG_INIT_PLAYER
4043 Debug("game:init:player",
4044 "- looking for field player for player %d ...", i + 1);
4047 // assign first free player found that is present in the playfield
4049 // first try: look for unmapped playfield player that is not connected
4050 for (j = 0; j < MAX_PLAYERS; j++)
4051 if (field_player == NULL &&
4052 stored_player[j].present &&
4053 !stored_player[j].mapped &&
4054 !stored_player[j].connected)
4055 field_player = &stored_player[j];
4057 // second try: look for *any* unmapped playfield player
4058 for (j = 0; j < MAX_PLAYERS; j++)
4059 if (field_player == NULL &&
4060 stored_player[j].present &&
4061 !stored_player[j].mapped)
4062 field_player = &stored_player[j];
4064 if (field_player != NULL)
4066 int jx = field_player->jx, jy = field_player->jy;
4068 #if DEBUG_INIT_PLAYER
4069 Debug("game:init:player", "- found player %d",
4070 field_player->index_nr + 1);
4073 player->present = FALSE;
4074 player->active = FALSE;
4076 field_player->present = TRUE;
4077 field_player->active = TRUE;
4080 player->initial_element = field_player->initial_element;
4081 player->artwork_element = field_player->artwork_element;
4083 player->block_last_field = field_player->block_last_field;
4084 player->block_delay_adjustment = field_player->block_delay_adjustment;
4087 StorePlayer[jx][jy] = field_player->element_nr;
4089 field_player->jx = field_player->last_jx = jx;
4090 field_player->jy = field_player->last_jy = jy;
4092 if (local_player == player)
4093 local_player = field_player;
4095 map_player_action[field_player->index_nr] = i;
4097 field_player->mapped = TRUE;
4099 #if DEBUG_INIT_PLAYER
4100 Debug("game:init:player", "- map_player_action[%d] == %d",
4101 field_player->index_nr + 1, i + 1);
4106 if (player->connected && player->present)
4107 player->mapped = TRUE;
4110 #if DEBUG_INIT_PLAYER
4111 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4116 // check if any connected player was not found in playfield
4117 for (i = 0; i < MAX_PLAYERS; i++)
4119 struct PlayerInfo *player = &stored_player[i];
4121 if (player->connected && !player->present)
4123 for (j = 0; j < MAX_PLAYERS; j++)
4125 struct PlayerInfo *field_player = &stored_player[j];
4126 int jx = field_player->jx, jy = field_player->jy;
4128 // assign first free player found that is present in the playfield
4129 if (field_player->present && !field_player->connected)
4131 player->present = TRUE;
4132 player->active = TRUE;
4134 field_player->present = FALSE;
4135 field_player->active = FALSE;
4137 player->initial_element = field_player->initial_element;
4138 player->artwork_element = field_player->artwork_element;
4140 player->block_last_field = field_player->block_last_field;
4141 player->block_delay_adjustment = field_player->block_delay_adjustment;
4143 StorePlayer[jx][jy] = player->element_nr;
4145 player->jx = player->last_jx = jx;
4146 player->jy = player->last_jy = jy;
4156 Debug("game:init:player", "local_player->present == %d",
4157 local_player->present);
4160 // set focus to local player for network games, else to all players
4161 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4162 game.centered_player_nr_next = game.centered_player_nr;
4163 game.set_centered_player = FALSE;
4164 game.set_centered_player_wrap = FALSE;
4166 if (network_playing && tape.recording)
4168 // store client dependent player focus when recording network games
4169 tape.centered_player_nr_next = game.centered_player_nr_next;
4170 tape.set_centered_player = TRUE;
4175 // when playing a tape, eliminate all players who do not participate
4177 #if USE_NEW_PLAYER_ASSIGNMENTS
4179 if (!game.team_mode)
4181 for (i = 0; i < MAX_PLAYERS; i++)
4183 if (stored_player[i].active &&
4184 !tape.player_participates[map_player_action[i]])
4186 struct PlayerInfo *player = &stored_player[i];
4187 int jx = player->jx, jy = player->jy;
4189 #if DEBUG_INIT_PLAYER
4190 Debug("game:init:player", "Removing player %d at (%d, %d)",
4194 player->active = FALSE;
4195 StorePlayer[jx][jy] = 0;
4196 Tile[jx][jy] = EL_EMPTY;
4203 for (i = 0; i < MAX_PLAYERS; i++)
4205 if (stored_player[i].active &&
4206 !tape.player_participates[i])
4208 struct PlayerInfo *player = &stored_player[i];
4209 int jx = player->jx, jy = player->jy;
4211 player->active = FALSE;
4212 StorePlayer[jx][jy] = 0;
4213 Tile[jx][jy] = EL_EMPTY;
4218 else if (!network.enabled && !game.team_mode) // && !tape.playing
4220 // when in single player mode, eliminate all but the local player
4222 for (i = 0; i < MAX_PLAYERS; i++)
4224 struct PlayerInfo *player = &stored_player[i];
4226 if (player->active && player != local_player)
4228 int jx = player->jx, jy = player->jy;
4230 player->active = FALSE;
4231 player->present = FALSE;
4233 StorePlayer[jx][jy] = 0;
4234 Tile[jx][jy] = EL_EMPTY;
4239 for (i = 0; i < MAX_PLAYERS; i++)
4240 if (stored_player[i].active)
4241 game.players_still_needed++;
4243 if (level.solved_by_one_player)
4244 game.players_still_needed = 1;
4246 // when recording the game, store which players take part in the game
4249 #if USE_NEW_PLAYER_ASSIGNMENTS
4250 for (i = 0; i < MAX_PLAYERS; i++)
4251 if (stored_player[i].connected)
4252 tape.player_participates[i] = TRUE;
4254 for (i = 0; i < MAX_PLAYERS; i++)
4255 if (stored_player[i].active)
4256 tape.player_participates[i] = TRUE;
4260 #if DEBUG_INIT_PLAYER
4261 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4264 if (BorderElement == EL_EMPTY)
4267 SBX_Right = lev_fieldx - SCR_FIELDX;
4269 SBY_Lower = lev_fieldy - SCR_FIELDY;
4274 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4276 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4279 if (full_lev_fieldx <= SCR_FIELDX)
4280 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4281 if (full_lev_fieldy <= SCR_FIELDY)
4282 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4284 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4286 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4289 // if local player not found, look for custom element that might create
4290 // the player (make some assumptions about the right custom element)
4291 if (!local_player->present)
4293 int start_x = 0, start_y = 0;
4294 int found_rating = 0;
4295 int found_element = EL_UNDEFINED;
4296 int player_nr = local_player->index_nr;
4298 SCAN_PLAYFIELD(x, y)
4300 int element = Tile[x][y];
4305 if (level.use_start_element[player_nr] &&
4306 level.start_element[player_nr] == element &&
4313 found_element = element;
4316 if (!IS_CUSTOM_ELEMENT(element))
4319 if (CAN_CHANGE(element))
4321 for (i = 0; i < element_info[element].num_change_pages; i++)
4323 // check for player created from custom element as single target
4324 content = element_info[element].change_page[i].target_element;
4325 is_player = IS_PLAYER_ELEMENT(content);
4327 if (is_player && (found_rating < 3 ||
4328 (found_rating == 3 && element < found_element)))
4334 found_element = element;
4339 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4341 // check for player created from custom element as explosion content
4342 content = element_info[element].content.e[xx][yy];
4343 is_player = IS_PLAYER_ELEMENT(content);
4345 if (is_player && (found_rating < 2 ||
4346 (found_rating == 2 && element < found_element)))
4348 start_x = x + xx - 1;
4349 start_y = y + yy - 1;
4352 found_element = element;
4355 if (!CAN_CHANGE(element))
4358 for (i = 0; i < element_info[element].num_change_pages; i++)
4360 // check for player created from custom element as extended target
4362 element_info[element].change_page[i].target_content.e[xx][yy];
4364 is_player = IS_PLAYER_ELEMENT(content);
4366 if (is_player && (found_rating < 1 ||
4367 (found_rating == 1 && element < found_element)))
4369 start_x = x + xx - 1;
4370 start_y = y + yy - 1;
4373 found_element = element;
4379 scroll_x = SCROLL_POSITION_X(start_x);
4380 scroll_y = SCROLL_POSITION_Y(start_y);
4384 scroll_x = SCROLL_POSITION_X(local_player->jx);
4385 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4388 // !!! FIX THIS (START) !!!
4389 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4391 InitGameEngine_EM();
4393 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4395 InitGameEngine_SP();
4397 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4399 InitGameEngine_MM();
4403 DrawLevel(REDRAW_FIELD);
4406 // after drawing the level, correct some elements
4407 if (game.timegate_time_left == 0)
4408 CloseAllOpenTimegates();
4411 // blit playfield from scroll buffer to normal back buffer for fading in
4412 BlitScreenToBitmap(backbuffer);
4413 // !!! FIX THIS (END) !!!
4415 DrawMaskedBorder(fade_mask);
4420 // full screen redraw is required at this point in the following cases:
4421 // - special editor door undrawn when game was started from level editor
4422 // - drawing area (playfield) was changed and has to be removed completely
4423 redraw_mask = REDRAW_ALL;
4427 if (!game.restart_level)
4429 // copy default game door content to main double buffer
4431 // !!! CHECK AGAIN !!!
4432 SetPanelBackground();
4433 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4434 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4437 SetPanelBackground();
4438 SetDrawBackgroundMask(REDRAW_DOOR_1);
4440 UpdateAndDisplayGameControlValues();
4442 if (!game.restart_level)
4448 CreateGameButtons();
4453 // copy actual game door content to door double buffer for OpenDoor()
4454 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4456 OpenDoor(DOOR_OPEN_ALL);
4458 KeyboardAutoRepeatOffUnlessAutoplay();
4460 #if DEBUG_INIT_PLAYER
4461 DebugPrintPlayerStatus("Player status (final)");
4470 if (!game.restart_level && !tape.playing)
4472 LevelStats_incPlayed(level_nr);
4474 SaveLevelSetup_SeriesInfo();
4477 game.restart_level = FALSE;
4478 game.restart_game_message = NULL;
4480 game.request_active = FALSE;
4481 game.request_active_or_moving = FALSE;
4483 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4484 InitGameActions_MM();
4486 SaveEngineSnapshotToListInitial();
4488 if (!game.restart_level)
4490 PlaySound(SND_GAME_STARTING);
4492 if (setup.sound_music)
4496 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4499 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4500 int actual_player_x, int actual_player_y)
4502 // this is used for non-R'n'D game engines to update certain engine values
4504 // needed to determine if sounds are played within the visible screen area
4505 scroll_x = actual_scroll_x;
4506 scroll_y = actual_scroll_y;
4508 // needed to get player position for "follow finger" playing input method
4509 local_player->jx = actual_player_x;
4510 local_player->jy = actual_player_y;
4513 void InitMovDir(int x, int y)
4515 int i, element = Tile[x][y];
4516 static int xy[4][2] =
4523 static int direction[3][4] =
4525 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4526 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4527 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4536 Tile[x][y] = EL_BUG;
4537 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4540 case EL_SPACESHIP_RIGHT:
4541 case EL_SPACESHIP_UP:
4542 case EL_SPACESHIP_LEFT:
4543 case EL_SPACESHIP_DOWN:
4544 Tile[x][y] = EL_SPACESHIP;
4545 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4548 case EL_BD_BUTTERFLY_RIGHT:
4549 case EL_BD_BUTTERFLY_UP:
4550 case EL_BD_BUTTERFLY_LEFT:
4551 case EL_BD_BUTTERFLY_DOWN:
4552 Tile[x][y] = EL_BD_BUTTERFLY;
4553 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4556 case EL_BD_FIREFLY_RIGHT:
4557 case EL_BD_FIREFLY_UP:
4558 case EL_BD_FIREFLY_LEFT:
4559 case EL_BD_FIREFLY_DOWN:
4560 Tile[x][y] = EL_BD_FIREFLY;
4561 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4564 case EL_PACMAN_RIGHT:
4566 case EL_PACMAN_LEFT:
4567 case EL_PACMAN_DOWN:
4568 Tile[x][y] = EL_PACMAN;
4569 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4572 case EL_YAMYAM_LEFT:
4573 case EL_YAMYAM_RIGHT:
4575 case EL_YAMYAM_DOWN:
4576 Tile[x][y] = EL_YAMYAM;
4577 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4580 case EL_SP_SNIKSNAK:
4581 MovDir[x][y] = MV_UP;
4584 case EL_SP_ELECTRON:
4585 MovDir[x][y] = MV_LEFT;
4592 Tile[x][y] = EL_MOLE;
4593 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4596 case EL_SPRING_LEFT:
4597 case EL_SPRING_RIGHT:
4598 Tile[x][y] = EL_SPRING;
4599 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4603 if (IS_CUSTOM_ELEMENT(element))
4605 struct ElementInfo *ei = &element_info[element];
4606 int move_direction_initial = ei->move_direction_initial;
4607 int move_pattern = ei->move_pattern;
4609 if (move_direction_initial == MV_START_PREVIOUS)
4611 if (MovDir[x][y] != MV_NONE)
4614 move_direction_initial = MV_START_AUTOMATIC;
4617 if (move_direction_initial == MV_START_RANDOM)
4618 MovDir[x][y] = 1 << RND(4);
4619 else if (move_direction_initial & MV_ANY_DIRECTION)
4620 MovDir[x][y] = move_direction_initial;
4621 else if (move_pattern == MV_ALL_DIRECTIONS ||
4622 move_pattern == MV_TURNING_LEFT ||
4623 move_pattern == MV_TURNING_RIGHT ||
4624 move_pattern == MV_TURNING_LEFT_RIGHT ||
4625 move_pattern == MV_TURNING_RIGHT_LEFT ||
4626 move_pattern == MV_TURNING_RANDOM)
4627 MovDir[x][y] = 1 << RND(4);
4628 else if (move_pattern == MV_HORIZONTAL)
4629 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4630 else if (move_pattern == MV_VERTICAL)
4631 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4632 else if (move_pattern & MV_ANY_DIRECTION)
4633 MovDir[x][y] = element_info[element].move_pattern;
4634 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4635 move_pattern == MV_ALONG_RIGHT_SIDE)
4637 // use random direction as default start direction
4638 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4639 MovDir[x][y] = 1 << RND(4);
4641 for (i = 0; i < NUM_DIRECTIONS; i++)
4643 int x1 = x + xy[i][0];
4644 int y1 = y + xy[i][1];
4646 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4648 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4649 MovDir[x][y] = direction[0][i];
4651 MovDir[x][y] = direction[1][i];
4660 MovDir[x][y] = 1 << RND(4);
4662 if (element != EL_BUG &&
4663 element != EL_SPACESHIP &&
4664 element != EL_BD_BUTTERFLY &&
4665 element != EL_BD_FIREFLY)
4668 for (i = 0; i < NUM_DIRECTIONS; i++)
4670 int x1 = x + xy[i][0];
4671 int y1 = y + xy[i][1];
4673 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4675 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4677 MovDir[x][y] = direction[0][i];
4680 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4681 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4683 MovDir[x][y] = direction[1][i];
4692 GfxDir[x][y] = MovDir[x][y];
4695 void InitAmoebaNr(int x, int y)
4698 int group_nr = AmoebaNeighbourNr(x, y);
4702 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4704 if (AmoebaCnt[i] == 0)
4712 AmoebaNr[x][y] = group_nr;
4713 AmoebaCnt[group_nr]++;
4714 AmoebaCnt2[group_nr]++;
4717 static void LevelSolved_SetFinalGameValues(void)
4719 game.time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4720 game.score_time_final = (level.use_step_counter ? TimePlayed :
4721 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4723 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4724 game_em.lev->score :
4725 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4729 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4730 MM_HEALTH(game_mm.laser_overload_value) :
4733 game.LevelSolved_CountingTime = game.time_final;
4734 game.LevelSolved_CountingScore = game.score_final;
4735 game.LevelSolved_CountingHealth = game.health_final;
4738 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4740 game.LevelSolved_CountingTime = time;
4741 game.LevelSolved_CountingScore = score;
4742 game.LevelSolved_CountingHealth = health;
4744 game_panel_controls[GAME_PANEL_TIME].value = time;
4745 game_panel_controls[GAME_PANEL_SCORE].value = score;
4746 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4748 DisplayGameControlValues();
4751 static void LevelSolved(void)
4753 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4754 game.players_still_needed > 0)
4757 game.LevelSolved = TRUE;
4758 game.GameOver = TRUE;
4760 // needed here to display correct panel values while player walks into exit
4761 LevelSolved_SetFinalGameValues();
4766 static int time_count_steps;
4767 static int time, time_final;
4768 static float score, score_final; // needed for time score < 10 for 10 seconds
4769 static int health, health_final;
4770 static int game_over_delay_1 = 0;
4771 static int game_over_delay_2 = 0;
4772 static int game_over_delay_3 = 0;
4773 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4774 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4776 if (!game.LevelSolved_GameWon)
4780 // do not start end game actions before the player stops moving (to exit)
4781 if (local_player->active && local_player->MovPos)
4784 // calculate final game values after player finished walking into exit
4785 LevelSolved_SetFinalGameValues();
4787 game.LevelSolved_GameWon = TRUE;
4788 game.LevelSolved_SaveTape = tape.recording;
4789 game.LevelSolved_SaveScore = !tape.playing;
4793 LevelStats_incSolved(level_nr);
4795 SaveLevelSetup_SeriesInfo();
4798 if (tape.auto_play) // tape might already be stopped here
4799 tape.auto_play_level_solved = TRUE;
4803 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4804 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4805 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4807 time = time_final = game.time_final;
4808 score = score_final = game.score_final;
4809 health = health_final = game.health_final;
4811 // update game panel values before (delayed) counting of score (if any)
4812 LevelSolved_DisplayFinalGameValues(time, score, health);
4814 // if level has time score defined, calculate new final game values
4817 int time_final_max = 999;
4818 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4819 int time_frames = 0;
4820 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4821 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4826 time_frames = time_frames_left;
4828 else if (game.no_time_limit && TimePlayed < time_final_max)
4830 time_final = time_final_max;
4831 time_frames = time_frames_final_max - time_frames_played;
4834 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4836 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4838 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4841 score_final += health * time_score;
4844 game.score_final = score_final;
4845 game.health_final = health_final;
4848 // if not counting score after game, immediately update game panel values
4849 if (level_editor_test_game || !setup.count_score_after_game)
4852 score = score_final;
4854 LevelSolved_DisplayFinalGameValues(time, score, health);
4857 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4859 // check if last player has left the level
4860 if (game.exit_x >= 0 &&
4863 int x = game.exit_x;
4864 int y = game.exit_y;
4865 int element = Tile[x][y];
4867 // close exit door after last player
4868 if ((game.all_players_gone &&
4869 (element == EL_EXIT_OPEN ||
4870 element == EL_SP_EXIT_OPEN ||
4871 element == EL_STEEL_EXIT_OPEN)) ||
4872 element == EL_EM_EXIT_OPEN ||
4873 element == EL_EM_STEEL_EXIT_OPEN)
4877 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4878 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4879 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4880 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4881 EL_EM_STEEL_EXIT_CLOSING);
4883 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4886 // player disappears
4887 DrawLevelField(x, y);
4890 for (i = 0; i < MAX_PLAYERS; i++)
4892 struct PlayerInfo *player = &stored_player[i];
4894 if (player->present)
4896 RemovePlayer(player);
4898 // player disappears
4899 DrawLevelField(player->jx, player->jy);
4904 PlaySound(SND_GAME_WINNING);
4907 if (setup.count_score_after_game)
4909 if (time != time_final)
4911 if (game_over_delay_1 > 0)
4913 game_over_delay_1--;
4918 int time_to_go = ABS(time_final - time);
4919 int time_count_dir = (time < time_final ? +1 : -1);
4921 if (time_to_go < time_count_steps)
4922 time_count_steps = 1;
4924 time += time_count_steps * time_count_dir;
4925 score += time_count_steps * time_score;
4927 // set final score to correct rounding differences after counting score
4928 if (time == time_final)
4929 score = score_final;
4931 LevelSolved_DisplayFinalGameValues(time, score, health);
4933 if (time == time_final)
4934 StopSound(SND_GAME_LEVELTIME_BONUS);
4935 else if (setup.sound_loops)
4936 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4938 PlaySound(SND_GAME_LEVELTIME_BONUS);
4943 if (health != health_final)
4945 if (game_over_delay_2 > 0)
4947 game_over_delay_2--;
4952 int health_count_dir = (health < health_final ? +1 : -1);
4954 health += health_count_dir;
4955 score += time_score;
4957 LevelSolved_DisplayFinalGameValues(time, score, health);
4959 if (health == health_final)
4960 StopSound(SND_GAME_LEVELTIME_BONUS);
4961 else if (setup.sound_loops)
4962 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4964 PlaySound(SND_GAME_LEVELTIME_BONUS);
4970 game.panel.active = FALSE;
4972 if (game_over_delay_3 > 0)
4974 game_over_delay_3--;
4984 // used instead of "level_nr" (needed for network games)
4985 int last_level_nr = levelset.level_nr;
4986 boolean tape_saved = FALSE;
4988 game.LevelSolved_GameEnd = TRUE;
4990 if (game.LevelSolved_SaveTape)
4992 // make sure that request dialog to save tape does not open door again
4993 if (!global.use_envelope_request)
4994 CloseDoor(DOOR_CLOSE_1);
4997 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
4999 // set unique basename for score tape (also saved in high score table)
5000 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5003 // if no tape is to be saved, close both doors simultaneously
5004 CloseDoor(DOOR_CLOSE_ALL);
5006 if (level_editor_test_game)
5008 SetGameStatus(GAME_MODE_MAIN);
5015 if (!game.LevelSolved_SaveScore)
5017 SetGameStatus(GAME_MODE_MAIN);
5024 if (level_nr == leveldir_current->handicap_level)
5026 leveldir_current->handicap_level++;
5028 SaveLevelSetup_SeriesInfo();
5031 // save score and score tape before potentially erasing tape below
5032 NewHighScore(last_level_nr, tape_saved);
5034 if (setup.increment_levels &&
5035 level_nr < leveldir_current->last_level &&
5038 level_nr++; // advance to next level
5039 TapeErase(); // start with empty tape
5041 if (setup.auto_play_next_level)
5043 LoadLevel(level_nr);
5045 SaveLevelSetup_SeriesInfo();
5049 if (scores.last_added >= 0 && setup.show_scores_after_game)
5051 SetGameStatus(GAME_MODE_SCORES);
5053 DrawHallOfFame(last_level_nr);
5055 else if (setup.auto_play_next_level && setup.increment_levels &&
5056 last_level_nr < leveldir_current->last_level &&
5059 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5063 SetGameStatus(GAME_MODE_MAIN);
5069 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5070 boolean one_score_entry_per_name)
5074 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5077 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5079 struct ScoreEntry *entry = &list->entry[i];
5080 boolean score_is_better = (new_entry->score > entry->score);
5081 boolean score_is_equal = (new_entry->score == entry->score);
5082 boolean time_is_better = (new_entry->time < entry->time);
5083 boolean time_is_equal = (new_entry->time == entry->time);
5084 boolean better_by_score = (score_is_better ||
5085 (score_is_equal && time_is_better));
5086 boolean better_by_time = (time_is_better ||
5087 (time_is_equal && score_is_better));
5088 boolean is_better = (level.rate_time_over_score ? better_by_time :
5090 boolean entry_is_empty = (entry->score == 0 &&
5093 // prevent adding server score entries if also existing in local score file
5094 // (special case: historic score entries have an empty tape basename entry)
5095 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5096 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5099 if (is_better || entry_is_empty)
5101 // player has made it to the hall of fame
5103 if (i < MAX_SCORE_ENTRIES - 1)
5105 int m = MAX_SCORE_ENTRIES - 1;
5108 if (one_score_entry_per_name)
5110 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5111 if (strEqual(list->entry[l].name, new_entry->name))
5114 if (m == i) // player's new highscore overwrites his old one
5118 for (l = m; l > i; l--)
5119 list->entry[l] = list->entry[l - 1];
5124 *entry = *new_entry;
5128 else if (one_score_entry_per_name &&
5129 strEqual(entry->name, new_entry->name))
5131 // player already in high score list with better score or time
5140 void NewHighScore(int level_nr, boolean tape_saved)
5142 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5143 boolean one_per_name = FALSE;
5145 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5146 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5148 new_entry.score = game.score_final;
5149 new_entry.time = game.score_time_final;
5151 LoadScore(level_nr);
5153 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5155 if (scores.last_added < 0)
5158 SaveScore(level_nr);
5160 // store last added local score entry (before merging server scores)
5161 scores.last_added_local = scores.last_added;
5163 if (!game.LevelSolved_SaveTape)
5166 SaveScoreTape(level_nr);
5168 if (setup.ask_for_using_api_server)
5170 setup.use_api_server =
5171 Request("Upload your score and tape to the high score server?", REQ_ASK);
5173 if (!setup.use_api_server)
5174 Request("Not using high score server! Use setup menu to enable again!",
5177 runtime.use_api_server = setup.use_api_server;
5179 // after asking for using API server once, do not ask again
5180 setup.ask_for_using_api_server = FALSE;
5182 SaveSetup_ServerSetup();
5185 SaveServerScore(level_nr, tape_saved);
5188 void MergeServerScore(void)
5190 struct ScoreEntry last_added_entry;
5191 boolean one_per_name = FALSE;
5194 if (scores.last_added >= 0)
5195 last_added_entry = scores.entry[scores.last_added];
5197 for (i = 0; i < server_scores.num_entries; i++)
5199 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5201 if (pos >= 0 && pos <= scores.last_added)
5202 scores.last_added++;
5205 if (scores.last_added >= MAX_SCORE_ENTRIES)
5207 scores.last_added = MAX_SCORE_ENTRIES - 1;
5208 scores.force_last_added = TRUE;
5210 scores.entry[scores.last_added] = last_added_entry;
5214 static int getElementMoveStepsizeExt(int x, int y, int direction)
5216 int element = Tile[x][y];
5217 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5218 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5219 int horiz_move = (dx != 0);
5220 int sign = (horiz_move ? dx : dy);
5221 int step = sign * element_info[element].move_stepsize;
5223 // special values for move stepsize for spring and things on conveyor belt
5226 if (CAN_FALL(element) &&
5227 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5228 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5229 else if (element == EL_SPRING)
5230 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5236 static int getElementMoveStepsize(int x, int y)
5238 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5241 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5243 if (player->GfxAction != action || player->GfxDir != dir)
5245 player->GfxAction = action;
5246 player->GfxDir = dir;
5248 player->StepFrame = 0;
5252 static void ResetGfxFrame(int x, int y)
5254 // profiling showed that "autotest" spends 10~20% of its time in this function
5255 if (DrawingDeactivatedField())
5258 int element = Tile[x][y];
5259 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5261 if (graphic_info[graphic].anim_global_sync)
5262 GfxFrame[x][y] = FrameCounter;
5263 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5264 GfxFrame[x][y] = CustomValue[x][y];
5265 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5266 GfxFrame[x][y] = element_info[element].collect_score;
5267 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5268 GfxFrame[x][y] = ChangeDelay[x][y];
5271 static void ResetGfxAnimation(int x, int y)
5273 GfxAction[x][y] = ACTION_DEFAULT;
5274 GfxDir[x][y] = MovDir[x][y];
5277 ResetGfxFrame(x, y);
5280 static void ResetRandomAnimationValue(int x, int y)
5282 GfxRandom[x][y] = INIT_GFX_RANDOM();
5285 static void InitMovingField(int x, int y, int direction)
5287 int element = Tile[x][y];
5288 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5289 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5292 boolean is_moving_before, is_moving_after;
5294 // check if element was/is moving or being moved before/after mode change
5295 is_moving_before = (WasJustMoving[x][y] != 0);
5296 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5298 // reset animation only for moving elements which change direction of moving
5299 // or which just started or stopped moving
5300 // (else CEs with property "can move" / "not moving" are reset each frame)
5301 if (is_moving_before != is_moving_after ||
5302 direction != MovDir[x][y])
5303 ResetGfxAnimation(x, y);
5305 MovDir[x][y] = direction;
5306 GfxDir[x][y] = direction;
5308 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5309 direction == MV_DOWN && CAN_FALL(element) ?
5310 ACTION_FALLING : ACTION_MOVING);
5312 // this is needed for CEs with property "can move" / "not moving"
5314 if (is_moving_after)
5316 if (Tile[newx][newy] == EL_EMPTY)
5317 Tile[newx][newy] = EL_BLOCKED;
5319 MovDir[newx][newy] = MovDir[x][y];
5321 CustomValue[newx][newy] = CustomValue[x][y];
5323 GfxFrame[newx][newy] = GfxFrame[x][y];
5324 GfxRandom[newx][newy] = GfxRandom[x][y];
5325 GfxAction[newx][newy] = GfxAction[x][y];
5326 GfxDir[newx][newy] = GfxDir[x][y];
5330 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5332 int direction = MovDir[x][y];
5333 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5334 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5340 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5342 int oldx = x, oldy = y;
5343 int direction = MovDir[x][y];
5345 if (direction == MV_LEFT)
5347 else if (direction == MV_RIGHT)
5349 else if (direction == MV_UP)
5351 else if (direction == MV_DOWN)
5354 *comes_from_x = oldx;
5355 *comes_from_y = oldy;
5358 static int MovingOrBlocked2Element(int x, int y)
5360 int element = Tile[x][y];
5362 if (element == EL_BLOCKED)
5366 Blocked2Moving(x, y, &oldx, &oldy);
5367 return Tile[oldx][oldy];
5373 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5375 // like MovingOrBlocked2Element(), but if element is moving
5376 // and (x,y) is the field the moving element is just leaving,
5377 // return EL_BLOCKED instead of the element value
5378 int element = Tile[x][y];
5380 if (IS_MOVING(x, y))
5382 if (element == EL_BLOCKED)
5386 Blocked2Moving(x, y, &oldx, &oldy);
5387 return Tile[oldx][oldy];
5396 static void RemoveField(int x, int y)
5398 Tile[x][y] = EL_EMPTY;
5404 CustomValue[x][y] = 0;
5407 ChangeDelay[x][y] = 0;
5408 ChangePage[x][y] = -1;
5409 Pushed[x][y] = FALSE;
5411 GfxElement[x][y] = EL_UNDEFINED;
5412 GfxAction[x][y] = ACTION_DEFAULT;
5413 GfxDir[x][y] = MV_NONE;
5416 static void RemoveMovingField(int x, int y)
5418 int oldx = x, oldy = y, newx = x, newy = y;
5419 int element = Tile[x][y];
5420 int next_element = EL_UNDEFINED;
5422 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5425 if (IS_MOVING(x, y))
5427 Moving2Blocked(x, y, &newx, &newy);
5429 if (Tile[newx][newy] != EL_BLOCKED)
5431 // element is moving, but target field is not free (blocked), but
5432 // already occupied by something different (example: acid pool);
5433 // in this case, only remove the moving field, but not the target
5435 RemoveField(oldx, oldy);
5437 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5439 TEST_DrawLevelField(oldx, oldy);
5444 else if (element == EL_BLOCKED)
5446 Blocked2Moving(x, y, &oldx, &oldy);
5447 if (!IS_MOVING(oldx, oldy))
5451 if (element == EL_BLOCKED &&
5452 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5453 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5454 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5455 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5456 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5457 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5458 next_element = get_next_element(Tile[oldx][oldy]);
5460 RemoveField(oldx, oldy);
5461 RemoveField(newx, newy);
5463 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5465 if (next_element != EL_UNDEFINED)
5466 Tile[oldx][oldy] = next_element;
5468 TEST_DrawLevelField(oldx, oldy);
5469 TEST_DrawLevelField(newx, newy);
5472 void DrawDynamite(int x, int y)
5474 int sx = SCREENX(x), sy = SCREENY(y);
5475 int graphic = el2img(Tile[x][y]);
5478 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5481 if (IS_WALKABLE_INSIDE(Back[x][y]))
5485 DrawLevelElement(x, y, Back[x][y]);
5486 else if (Store[x][y])
5487 DrawLevelElement(x, y, Store[x][y]);
5488 else if (game.use_masked_elements)
5489 DrawLevelElement(x, y, EL_EMPTY);
5491 frame = getGraphicAnimationFrameXY(graphic, x, y);
5493 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5494 DrawGraphicThruMask(sx, sy, graphic, frame);
5496 DrawGraphic(sx, sy, graphic, frame);
5499 static void CheckDynamite(int x, int y)
5501 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5505 if (MovDelay[x][y] != 0)
5508 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5514 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5519 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5521 boolean num_checked_players = 0;
5524 for (i = 0; i < MAX_PLAYERS; i++)
5526 if (stored_player[i].active)
5528 int sx = stored_player[i].jx;
5529 int sy = stored_player[i].jy;
5531 if (num_checked_players == 0)
5538 *sx1 = MIN(*sx1, sx);
5539 *sy1 = MIN(*sy1, sy);
5540 *sx2 = MAX(*sx2, sx);
5541 *sy2 = MAX(*sy2, sy);
5544 num_checked_players++;
5549 static boolean checkIfAllPlayersFitToScreen_RND(void)
5551 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5553 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5555 return (sx2 - sx1 < SCR_FIELDX &&
5556 sy2 - sy1 < SCR_FIELDY);
5559 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5561 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5563 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5565 *sx = (sx1 + sx2) / 2;
5566 *sy = (sy1 + sy2) / 2;
5569 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5570 boolean center_screen, boolean quick_relocation)
5572 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5573 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5574 boolean no_delay = (tape.warp_forward);
5575 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5576 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5577 int new_scroll_x, new_scroll_y;
5579 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5581 // case 1: quick relocation inside visible screen (without scrolling)
5588 if (!level.shifted_relocation || center_screen)
5590 // relocation _with_ centering of screen
5592 new_scroll_x = SCROLL_POSITION_X(x);
5593 new_scroll_y = SCROLL_POSITION_Y(y);
5597 // relocation _without_ centering of screen
5599 int center_scroll_x = SCROLL_POSITION_X(old_x);
5600 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5601 int offset_x = x + (scroll_x - center_scroll_x);
5602 int offset_y = y + (scroll_y - center_scroll_y);
5604 // for new screen position, apply previous offset to center position
5605 new_scroll_x = SCROLL_POSITION_X(offset_x);
5606 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5609 if (quick_relocation)
5611 // case 2: quick relocation (redraw without visible scrolling)
5613 scroll_x = new_scroll_x;
5614 scroll_y = new_scroll_y;
5621 // case 3: visible relocation (with scrolling to new position)
5623 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5625 SetVideoFrameDelay(wait_delay_value);
5627 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5629 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5630 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5632 if (dx == 0 && dy == 0) // no scrolling needed at all
5638 // set values for horizontal/vertical screen scrolling (half tile size)
5639 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5640 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5641 int pos_x = dx * TILEX / 2;
5642 int pos_y = dy * TILEY / 2;
5643 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5644 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5646 ScrollLevel(dx, dy);
5649 // scroll in two steps of half tile size to make things smoother
5650 BlitScreenToBitmapExt_RND(window, fx, fy);
5652 // scroll second step to align at full tile size
5653 BlitScreenToBitmap(window);
5659 SetVideoFrameDelay(frame_delay_value_old);
5662 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5664 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5665 int player_nr = GET_PLAYER_NR(el_player);
5666 struct PlayerInfo *player = &stored_player[player_nr];
5667 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5668 boolean no_delay = (tape.warp_forward);
5669 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5670 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5671 int old_jx = player->jx;
5672 int old_jy = player->jy;
5673 int old_element = Tile[old_jx][old_jy];
5674 int element = Tile[jx][jy];
5675 boolean player_relocated = (old_jx != jx || old_jy != jy);
5677 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5678 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5679 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5680 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5681 int leave_side_horiz = move_dir_horiz;
5682 int leave_side_vert = move_dir_vert;
5683 int enter_side = enter_side_horiz | enter_side_vert;
5684 int leave_side = leave_side_horiz | leave_side_vert;
5686 if (player->buried) // do not reanimate dead player
5689 if (!player_relocated) // no need to relocate the player
5692 if (IS_PLAYER(jx, jy)) // player already placed at new position
5694 RemoveField(jx, jy); // temporarily remove newly placed player
5695 DrawLevelField(jx, jy);
5698 if (player->present)
5700 while (player->MovPos)
5702 ScrollPlayer(player, SCROLL_GO_ON);
5703 ScrollScreen(NULL, SCROLL_GO_ON);
5705 AdvanceFrameAndPlayerCounters(player->index_nr);
5709 BackToFront_WithFrameDelay(wait_delay_value);
5712 DrawPlayer(player); // needed here only to cleanup last field
5713 DrawLevelField(player->jx, player->jy); // remove player graphic
5715 player->is_moving = FALSE;
5718 if (IS_CUSTOM_ELEMENT(old_element))
5719 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5721 player->index_bit, leave_side);
5723 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5725 player->index_bit, leave_side);
5727 Tile[jx][jy] = el_player;
5728 InitPlayerField(jx, jy, el_player, TRUE);
5730 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5731 possible that the relocation target field did not contain a player element,
5732 but a walkable element, to which the new player was relocated -- in this
5733 case, restore that (already initialized!) element on the player field */
5734 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5736 Tile[jx][jy] = element; // restore previously existing element
5739 // only visually relocate centered player
5740 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5741 FALSE, level.instant_relocation);
5743 TestIfPlayerTouchesBadThing(jx, jy);
5744 TestIfPlayerTouchesCustomElement(jx, jy);
5746 if (IS_CUSTOM_ELEMENT(element))
5747 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5748 player->index_bit, enter_side);
5750 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5751 player->index_bit, enter_side);
5753 if (player->is_switching)
5755 /* ensure that relocation while still switching an element does not cause
5756 a new element to be treated as also switched directly after relocation
5757 (this is important for teleporter switches that teleport the player to
5758 a place where another teleporter switch is in the same direction, which
5759 would then incorrectly be treated as immediately switched before the
5760 direction key that caused the switch was released) */
5762 player->switch_x += jx - old_jx;
5763 player->switch_y += jy - old_jy;
5767 static void Explode(int ex, int ey, int phase, int mode)
5773 // !!! eliminate this variable !!!
5774 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5776 if (game.explosions_delayed)
5778 ExplodeField[ex][ey] = mode;
5782 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5784 int center_element = Tile[ex][ey];
5785 int artwork_element, explosion_element; // set these values later
5787 // remove things displayed in background while burning dynamite
5788 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5791 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5793 // put moving element to center field (and let it explode there)
5794 center_element = MovingOrBlocked2Element(ex, ey);
5795 RemoveMovingField(ex, ey);
5796 Tile[ex][ey] = center_element;
5799 // now "center_element" is finally determined -- set related values now
5800 artwork_element = center_element; // for custom player artwork
5801 explosion_element = center_element; // for custom player artwork
5803 if (IS_PLAYER(ex, ey))
5805 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5807 artwork_element = stored_player[player_nr].artwork_element;
5809 if (level.use_explosion_element[player_nr])
5811 explosion_element = level.explosion_element[player_nr];
5812 artwork_element = explosion_element;
5816 if (mode == EX_TYPE_NORMAL ||
5817 mode == EX_TYPE_CENTER ||
5818 mode == EX_TYPE_CROSS)
5819 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5821 last_phase = element_info[explosion_element].explosion_delay + 1;
5823 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5825 int xx = x - ex + 1;
5826 int yy = y - ey + 1;
5829 if (!IN_LEV_FIELD(x, y) ||
5830 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5831 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5834 element = Tile[x][y];
5836 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5838 element = MovingOrBlocked2Element(x, y);
5840 if (!IS_EXPLOSION_PROOF(element))
5841 RemoveMovingField(x, y);
5844 // indestructible elements can only explode in center (but not flames)
5845 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5846 mode == EX_TYPE_BORDER)) ||
5847 element == EL_FLAMES)
5850 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5851 behaviour, for example when touching a yamyam that explodes to rocks
5852 with active deadly shield, a rock is created under the player !!! */
5853 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5855 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5856 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5857 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5859 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5862 if (IS_ACTIVE_BOMB(element))
5864 // re-activate things under the bomb like gate or penguin
5865 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5872 // save walkable background elements while explosion on same tile
5873 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5874 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5875 Back[x][y] = element;
5877 // ignite explodable elements reached by other explosion
5878 if (element == EL_EXPLOSION)
5879 element = Store2[x][y];
5881 if (AmoebaNr[x][y] &&
5882 (element == EL_AMOEBA_FULL ||
5883 element == EL_BD_AMOEBA ||
5884 element == EL_AMOEBA_GROWING))
5886 AmoebaCnt[AmoebaNr[x][y]]--;
5887 AmoebaCnt2[AmoebaNr[x][y]]--;
5892 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5894 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5896 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5898 if (PLAYERINFO(ex, ey)->use_murphy)
5899 Store[x][y] = EL_EMPTY;
5902 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5903 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5904 else if (IS_PLAYER_ELEMENT(center_element))
5905 Store[x][y] = EL_EMPTY;
5906 else if (center_element == EL_YAMYAM)
5907 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5908 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5909 Store[x][y] = element_info[center_element].content.e[xx][yy];
5911 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5912 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5913 // otherwise) -- FIX THIS !!!
5914 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5915 Store[x][y] = element_info[element].content.e[1][1];
5917 else if (!CAN_EXPLODE(element))
5918 Store[x][y] = element_info[element].content.e[1][1];
5921 Store[x][y] = EL_EMPTY;
5923 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5924 center_element == EL_AMOEBA_TO_DIAMOND)
5925 Store2[x][y] = element;
5927 Tile[x][y] = EL_EXPLOSION;
5928 GfxElement[x][y] = artwork_element;
5930 ExplodePhase[x][y] = 1;
5931 ExplodeDelay[x][y] = last_phase;
5936 if (center_element == EL_YAMYAM)
5937 game.yamyam_content_nr =
5938 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5950 GfxFrame[x][y] = 0; // restart explosion animation
5952 last_phase = ExplodeDelay[x][y];
5954 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5956 // this can happen if the player leaves an explosion just in time
5957 if (GfxElement[x][y] == EL_UNDEFINED)
5958 GfxElement[x][y] = EL_EMPTY;
5960 border_element = Store2[x][y];
5961 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5962 border_element = StorePlayer[x][y];
5964 if (phase == element_info[border_element].ignition_delay ||
5965 phase == last_phase)
5967 boolean border_explosion = FALSE;
5969 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5970 !PLAYER_EXPLOSION_PROTECTED(x, y))
5972 KillPlayerUnlessExplosionProtected(x, y);
5973 border_explosion = TRUE;
5975 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5977 Tile[x][y] = Store2[x][y];
5980 border_explosion = TRUE;
5982 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5984 AmoebaToDiamond(x, y);
5986 border_explosion = TRUE;
5989 // if an element just explodes due to another explosion (chain-reaction),
5990 // do not immediately end the new explosion when it was the last frame of
5991 // the explosion (as it would be done in the following "if"-statement!)
5992 if (border_explosion && phase == last_phase)
5996 if (phase == last_phase)
6000 element = Tile[x][y] = Store[x][y];
6001 Store[x][y] = Store2[x][y] = 0;
6002 GfxElement[x][y] = EL_UNDEFINED;
6004 // player can escape from explosions and might therefore be still alive
6005 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6006 element <= EL_PLAYER_IS_EXPLODING_4)
6008 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6009 int explosion_element = EL_PLAYER_1 + player_nr;
6010 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6011 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6013 if (level.use_explosion_element[player_nr])
6014 explosion_element = level.explosion_element[player_nr];
6016 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6017 element_info[explosion_element].content.e[xx][yy]);
6020 // restore probably existing indestructible background element
6021 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6022 element = Tile[x][y] = Back[x][y];
6025 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6026 GfxDir[x][y] = MV_NONE;
6027 ChangeDelay[x][y] = 0;
6028 ChangePage[x][y] = -1;
6030 CustomValue[x][y] = 0;
6032 InitField_WithBug2(x, y, FALSE);
6034 TEST_DrawLevelField(x, y);
6036 TestIfElementTouchesCustomElement(x, y);
6038 if (GFX_CRUMBLED(element))
6039 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6041 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6042 StorePlayer[x][y] = 0;
6044 if (IS_PLAYER_ELEMENT(element))
6045 RelocatePlayer(x, y, element);
6047 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6049 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6050 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6053 TEST_DrawLevelFieldCrumbled(x, y);
6055 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6057 DrawLevelElement(x, y, Back[x][y]);
6058 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6060 else if (IS_WALKABLE_UNDER(Back[x][y]))
6062 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6063 DrawLevelElementThruMask(x, y, Back[x][y]);
6065 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6066 DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6070 static void DynaExplode(int ex, int ey)
6073 int dynabomb_element = Tile[ex][ey];
6074 int dynabomb_size = 1;
6075 boolean dynabomb_xl = FALSE;
6076 struct PlayerInfo *player;
6077 static int xy[4][2] =
6085 if (IS_ACTIVE_BOMB(dynabomb_element))
6087 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6088 dynabomb_size = player->dynabomb_size;
6089 dynabomb_xl = player->dynabomb_xl;
6090 player->dynabombs_left++;
6093 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6095 for (i = 0; i < NUM_DIRECTIONS; i++)
6097 for (j = 1; j <= dynabomb_size; j++)
6099 int x = ex + j * xy[i][0];
6100 int y = ey + j * xy[i][1];
6103 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6106 element = Tile[x][y];
6108 // do not restart explosions of fields with active bombs
6109 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6112 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6114 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6115 !IS_DIGGABLE(element) && !dynabomb_xl)
6121 void Bang(int x, int y)
6123 int element = MovingOrBlocked2Element(x, y);
6124 int explosion_type = EX_TYPE_NORMAL;
6126 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6128 struct PlayerInfo *player = PLAYERINFO(x, y);
6130 element = Tile[x][y] = player->initial_element;
6132 if (level.use_explosion_element[player->index_nr])
6134 int explosion_element = level.explosion_element[player->index_nr];
6136 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6137 explosion_type = EX_TYPE_CROSS;
6138 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6139 explosion_type = EX_TYPE_CENTER;
6147 case EL_BD_BUTTERFLY:
6150 case EL_DARK_YAMYAM:
6154 RaiseScoreElement(element);
6157 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6158 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6159 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6160 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6161 case EL_DYNABOMB_INCREASE_NUMBER:
6162 case EL_DYNABOMB_INCREASE_SIZE:
6163 case EL_DYNABOMB_INCREASE_POWER:
6164 explosion_type = EX_TYPE_DYNA;
6167 case EL_DC_LANDMINE:
6168 explosion_type = EX_TYPE_CENTER;
6173 case EL_LAMP_ACTIVE:
6174 case EL_AMOEBA_TO_DIAMOND:
6175 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6176 explosion_type = EX_TYPE_CENTER;
6180 if (element_info[element].explosion_type == EXPLODES_CROSS)
6181 explosion_type = EX_TYPE_CROSS;
6182 else if (element_info[element].explosion_type == EXPLODES_1X1)
6183 explosion_type = EX_TYPE_CENTER;
6187 if (explosion_type == EX_TYPE_DYNA)
6190 Explode(x, y, EX_PHASE_START, explosion_type);
6192 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6195 static void SplashAcid(int x, int y)
6197 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6198 (!IN_LEV_FIELD(x - 1, y - 2) ||
6199 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6200 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6202 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6203 (!IN_LEV_FIELD(x + 1, y - 2) ||
6204 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6205 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6207 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6210 static void InitBeltMovement(void)
6212 static int belt_base_element[4] =
6214 EL_CONVEYOR_BELT_1_LEFT,
6215 EL_CONVEYOR_BELT_2_LEFT,
6216 EL_CONVEYOR_BELT_3_LEFT,
6217 EL_CONVEYOR_BELT_4_LEFT
6219 static int belt_base_active_element[4] =
6221 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6222 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6223 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6224 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6229 // set frame order for belt animation graphic according to belt direction
6230 for (i = 0; i < NUM_BELTS; i++)
6234 for (j = 0; j < NUM_BELT_PARTS; j++)
6236 int element = belt_base_active_element[belt_nr] + j;
6237 int graphic_1 = el2img(element);
6238 int graphic_2 = el2panelimg(element);
6240 if (game.belt_dir[i] == MV_LEFT)
6242 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6243 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6247 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6248 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6253 SCAN_PLAYFIELD(x, y)
6255 int element = Tile[x][y];
6257 for (i = 0; i < NUM_BELTS; i++)
6259 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6261 int e_belt_nr = getBeltNrFromBeltElement(element);
6264 if (e_belt_nr == belt_nr)
6266 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6268 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6275 static void ToggleBeltSwitch(int x, int y)
6277 static int belt_base_element[4] =
6279 EL_CONVEYOR_BELT_1_LEFT,
6280 EL_CONVEYOR_BELT_2_LEFT,
6281 EL_CONVEYOR_BELT_3_LEFT,
6282 EL_CONVEYOR_BELT_4_LEFT
6284 static int belt_base_active_element[4] =
6286 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6287 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6288 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6289 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6291 static int belt_base_switch_element[4] =
6293 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6294 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6295 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6296 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6298 static int belt_move_dir[4] =
6306 int element = Tile[x][y];
6307 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6308 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6309 int belt_dir = belt_move_dir[belt_dir_nr];
6312 if (!IS_BELT_SWITCH(element))
6315 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6316 game.belt_dir[belt_nr] = belt_dir;
6318 if (belt_dir_nr == 3)
6321 // set frame order for belt animation graphic according to belt direction
6322 for (i = 0; i < NUM_BELT_PARTS; i++)
6324 int element = belt_base_active_element[belt_nr] + i;
6325 int graphic_1 = el2img(element);
6326 int graphic_2 = el2panelimg(element);
6328 if (belt_dir == 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;
6340 SCAN_PLAYFIELD(xx, yy)
6342 int element = Tile[xx][yy];
6344 if (IS_BELT_SWITCH(element))
6346 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6348 if (e_belt_nr == belt_nr)
6350 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6351 TEST_DrawLevelField(xx, yy);
6354 else if (IS_BELT(element) && belt_dir != MV_NONE)
6356 int e_belt_nr = getBeltNrFromBeltElement(element);
6358 if (e_belt_nr == belt_nr)
6360 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6362 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6363 TEST_DrawLevelField(xx, yy);
6366 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6368 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6370 if (e_belt_nr == belt_nr)
6372 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6374 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6375 TEST_DrawLevelField(xx, yy);
6381 static void ToggleSwitchgateSwitch(int x, int y)
6385 game.switchgate_pos = !game.switchgate_pos;
6387 SCAN_PLAYFIELD(xx, yy)
6389 int element = Tile[xx][yy];
6391 if (element == EL_SWITCHGATE_SWITCH_UP)
6393 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6394 TEST_DrawLevelField(xx, yy);
6396 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6398 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6399 TEST_DrawLevelField(xx, yy);
6401 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6403 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6404 TEST_DrawLevelField(xx, yy);
6406 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6408 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6409 TEST_DrawLevelField(xx, yy);
6411 else if (element == EL_SWITCHGATE_OPEN ||
6412 element == EL_SWITCHGATE_OPENING)
6414 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6416 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6418 else if (element == EL_SWITCHGATE_CLOSED ||
6419 element == EL_SWITCHGATE_CLOSING)
6421 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6423 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6428 static int getInvisibleActiveFromInvisibleElement(int element)
6430 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6431 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6432 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6436 static int getInvisibleFromInvisibleActiveElement(int element)
6438 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6439 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6440 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6444 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6448 SCAN_PLAYFIELD(x, y)
6450 int element = Tile[x][y];
6452 if (element == EL_LIGHT_SWITCH &&
6453 game.light_time_left > 0)
6455 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6456 TEST_DrawLevelField(x, y);
6458 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6459 game.light_time_left == 0)
6461 Tile[x][y] = EL_LIGHT_SWITCH;
6462 TEST_DrawLevelField(x, y);
6464 else if (element == EL_EMC_DRIPPER &&
6465 game.light_time_left > 0)
6467 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6468 TEST_DrawLevelField(x, y);
6470 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6471 game.light_time_left == 0)
6473 Tile[x][y] = EL_EMC_DRIPPER;
6474 TEST_DrawLevelField(x, y);
6476 else if (element == EL_INVISIBLE_STEELWALL ||
6477 element == EL_INVISIBLE_WALL ||
6478 element == EL_INVISIBLE_SAND)
6480 if (game.light_time_left > 0)
6481 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6483 TEST_DrawLevelField(x, y);
6485 // uncrumble neighbour fields, if needed
6486 if (element == EL_INVISIBLE_SAND)
6487 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6489 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6490 element == EL_INVISIBLE_WALL_ACTIVE ||
6491 element == EL_INVISIBLE_SAND_ACTIVE)
6493 if (game.light_time_left == 0)
6494 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6496 TEST_DrawLevelField(x, y);
6498 // re-crumble neighbour fields, if needed
6499 if (element == EL_INVISIBLE_SAND)
6500 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6505 static void RedrawAllInvisibleElementsForLenses(void)
6509 SCAN_PLAYFIELD(x, y)
6511 int element = Tile[x][y];
6513 if (element == EL_EMC_DRIPPER &&
6514 game.lenses_time_left > 0)
6516 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6517 TEST_DrawLevelField(x, y);
6519 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6520 game.lenses_time_left == 0)
6522 Tile[x][y] = EL_EMC_DRIPPER;
6523 TEST_DrawLevelField(x, y);
6525 else if (element == EL_INVISIBLE_STEELWALL ||
6526 element == EL_INVISIBLE_WALL ||
6527 element == EL_INVISIBLE_SAND)
6529 if (game.lenses_time_left > 0)
6530 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6532 TEST_DrawLevelField(x, y);
6534 // uncrumble neighbour fields, if needed
6535 if (element == EL_INVISIBLE_SAND)
6536 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6538 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6539 element == EL_INVISIBLE_WALL_ACTIVE ||
6540 element == EL_INVISIBLE_SAND_ACTIVE)
6542 if (game.lenses_time_left == 0)
6543 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6545 TEST_DrawLevelField(x, y);
6547 // re-crumble neighbour fields, if needed
6548 if (element == EL_INVISIBLE_SAND)
6549 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6554 static void RedrawAllInvisibleElementsForMagnifier(void)
6558 SCAN_PLAYFIELD(x, y)
6560 int element = Tile[x][y];
6562 if (element == EL_EMC_FAKE_GRASS &&
6563 game.magnify_time_left > 0)
6565 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6566 TEST_DrawLevelField(x, y);
6568 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6569 game.magnify_time_left == 0)
6571 Tile[x][y] = EL_EMC_FAKE_GRASS;
6572 TEST_DrawLevelField(x, y);
6574 else if (IS_GATE_GRAY(element) &&
6575 game.magnify_time_left > 0)
6577 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6578 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6579 IS_EM_GATE_GRAY(element) ?
6580 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6581 IS_EMC_GATE_GRAY(element) ?
6582 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6583 IS_DC_GATE_GRAY(element) ?
6584 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6586 TEST_DrawLevelField(x, y);
6588 else if (IS_GATE_GRAY_ACTIVE(element) &&
6589 game.magnify_time_left == 0)
6591 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6592 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6593 IS_EM_GATE_GRAY_ACTIVE(element) ?
6594 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6595 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6596 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6597 IS_DC_GATE_GRAY_ACTIVE(element) ?
6598 EL_DC_GATE_WHITE_GRAY :
6600 TEST_DrawLevelField(x, y);
6605 static void ToggleLightSwitch(int x, int y)
6607 int element = Tile[x][y];
6609 game.light_time_left =
6610 (element == EL_LIGHT_SWITCH ?
6611 level.time_light * FRAMES_PER_SECOND : 0);
6613 RedrawAllLightSwitchesAndInvisibleElements();
6616 static void ActivateTimegateSwitch(int x, int y)
6620 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6622 SCAN_PLAYFIELD(xx, yy)
6624 int element = Tile[xx][yy];
6626 if (element == EL_TIMEGATE_CLOSED ||
6627 element == EL_TIMEGATE_CLOSING)
6629 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6630 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6634 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6636 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6637 TEST_DrawLevelField(xx, yy);
6643 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6644 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6647 static void Impact(int x, int y)
6649 boolean last_line = (y == lev_fieldy - 1);
6650 boolean object_hit = FALSE;
6651 boolean impact = (last_line || object_hit);
6652 int element = Tile[x][y];
6653 int smashed = EL_STEELWALL;
6655 if (!last_line) // check if element below was hit
6657 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6660 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6661 MovDir[x][y + 1] != MV_DOWN ||
6662 MovPos[x][y + 1] <= TILEY / 2));
6664 // do not smash moving elements that left the smashed field in time
6665 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6666 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6669 #if USE_QUICKSAND_IMPACT_BUGFIX
6670 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6672 RemoveMovingField(x, y + 1);
6673 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6674 Tile[x][y + 2] = EL_ROCK;
6675 TEST_DrawLevelField(x, y + 2);
6680 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6682 RemoveMovingField(x, y + 1);
6683 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6684 Tile[x][y + 2] = EL_ROCK;
6685 TEST_DrawLevelField(x, y + 2);
6692 smashed = MovingOrBlocked2Element(x, y + 1);
6694 impact = (last_line || object_hit);
6697 if (!last_line && smashed == EL_ACID) // element falls into acid
6699 SplashAcid(x, y + 1);
6703 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6704 // only reset graphic animation if graphic really changes after impact
6706 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6708 ResetGfxAnimation(x, y);
6709 TEST_DrawLevelField(x, y);
6712 if (impact && CAN_EXPLODE_IMPACT(element))
6717 else if (impact && element == EL_PEARL &&
6718 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6720 ResetGfxAnimation(x, y);
6722 Tile[x][y] = EL_PEARL_BREAKING;
6723 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6726 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6728 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6733 if (impact && element == EL_AMOEBA_DROP)
6735 if (object_hit && IS_PLAYER(x, y + 1))
6736 KillPlayerUnlessEnemyProtected(x, y + 1);
6737 else if (object_hit && smashed == EL_PENGUIN)
6741 Tile[x][y] = EL_AMOEBA_GROWING;
6742 Store[x][y] = EL_AMOEBA_WET;
6744 ResetRandomAnimationValue(x, y);
6749 if (object_hit) // check which object was hit
6751 if ((CAN_PASS_MAGIC_WALL(element) &&
6752 (smashed == EL_MAGIC_WALL ||
6753 smashed == EL_BD_MAGIC_WALL)) ||
6754 (CAN_PASS_DC_MAGIC_WALL(element) &&
6755 smashed == EL_DC_MAGIC_WALL))
6758 int activated_magic_wall =
6759 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6760 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6761 EL_DC_MAGIC_WALL_ACTIVE);
6763 // activate magic wall / mill
6764 SCAN_PLAYFIELD(xx, yy)
6766 if (Tile[xx][yy] == smashed)
6767 Tile[xx][yy] = activated_magic_wall;
6770 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6771 game.magic_wall_active = TRUE;
6773 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6774 SND_MAGIC_WALL_ACTIVATING :
6775 smashed == EL_BD_MAGIC_WALL ?
6776 SND_BD_MAGIC_WALL_ACTIVATING :
6777 SND_DC_MAGIC_WALL_ACTIVATING));
6780 if (IS_PLAYER(x, y + 1))
6782 if (CAN_SMASH_PLAYER(element))
6784 KillPlayerUnlessEnemyProtected(x, y + 1);
6788 else if (smashed == EL_PENGUIN)
6790 if (CAN_SMASH_PLAYER(element))
6796 else if (element == EL_BD_DIAMOND)
6798 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6804 else if (((element == EL_SP_INFOTRON ||
6805 element == EL_SP_ZONK) &&
6806 (smashed == EL_SP_SNIKSNAK ||
6807 smashed == EL_SP_ELECTRON ||
6808 smashed == EL_SP_DISK_ORANGE)) ||
6809 (element == EL_SP_INFOTRON &&
6810 smashed == EL_SP_DISK_YELLOW))
6815 else if (CAN_SMASH_EVERYTHING(element))
6817 if (IS_CLASSIC_ENEMY(smashed) ||
6818 CAN_EXPLODE_SMASHED(smashed))
6823 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6825 if (smashed == EL_LAMP ||
6826 smashed == EL_LAMP_ACTIVE)
6831 else if (smashed == EL_NUT)
6833 Tile[x][y + 1] = EL_NUT_BREAKING;
6834 PlayLevelSound(x, y, SND_NUT_BREAKING);
6835 RaiseScoreElement(EL_NUT);
6838 else if (smashed == EL_PEARL)
6840 ResetGfxAnimation(x, y);
6842 Tile[x][y + 1] = EL_PEARL_BREAKING;
6843 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6846 else if (smashed == EL_DIAMOND)
6848 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6849 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6852 else if (IS_BELT_SWITCH(smashed))
6854 ToggleBeltSwitch(x, y + 1);
6856 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6857 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6858 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6859 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6861 ToggleSwitchgateSwitch(x, y + 1);
6863 else if (smashed == EL_LIGHT_SWITCH ||
6864 smashed == EL_LIGHT_SWITCH_ACTIVE)
6866 ToggleLightSwitch(x, y + 1);
6870 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6872 CheckElementChangeBySide(x, y + 1, smashed, element,
6873 CE_SWITCHED, CH_SIDE_TOP);
6874 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6880 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6885 // play sound of magic wall / mill
6887 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6888 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6889 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6891 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6892 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6893 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6894 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6895 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6896 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6901 // play sound of object that hits the ground
6902 if (last_line || object_hit)
6903 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6906 static void TurnRoundExt(int x, int y)
6918 { 0, 0 }, { 0, 0 }, { 0, 0 },
6923 int left, right, back;
6927 { MV_DOWN, MV_UP, MV_RIGHT },
6928 { MV_UP, MV_DOWN, MV_LEFT },
6930 { MV_LEFT, MV_RIGHT, MV_DOWN },
6934 { MV_RIGHT, MV_LEFT, MV_UP }
6937 int element = Tile[x][y];
6938 int move_pattern = element_info[element].move_pattern;
6940 int old_move_dir = MovDir[x][y];
6941 int left_dir = turn[old_move_dir].left;
6942 int right_dir = turn[old_move_dir].right;
6943 int back_dir = turn[old_move_dir].back;
6945 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6946 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6947 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6948 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6950 int left_x = x + left_dx, left_y = y + left_dy;
6951 int right_x = x + right_dx, right_y = y + right_dy;
6952 int move_x = x + move_dx, move_y = y + move_dy;
6956 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6958 TestIfBadThingTouchesOtherBadThing(x, y);
6960 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6961 MovDir[x][y] = right_dir;
6962 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6963 MovDir[x][y] = left_dir;
6965 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6967 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6970 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6972 TestIfBadThingTouchesOtherBadThing(x, y);
6974 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6975 MovDir[x][y] = left_dir;
6976 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6977 MovDir[x][y] = right_dir;
6979 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6981 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6984 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6986 TestIfBadThingTouchesOtherBadThing(x, y);
6988 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6989 MovDir[x][y] = left_dir;
6990 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6991 MovDir[x][y] = right_dir;
6993 if (MovDir[x][y] != old_move_dir)
6996 else if (element == EL_YAMYAM)
6998 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6999 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7001 if (can_turn_left && can_turn_right)
7002 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7003 else if (can_turn_left)
7004 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7005 else if (can_turn_right)
7006 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7008 MovDir[x][y] = back_dir;
7010 MovDelay[x][y] = 16 + 16 * RND(3);
7012 else if (element == EL_DARK_YAMYAM)
7014 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7016 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7019 if (can_turn_left && can_turn_right)
7020 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7021 else if (can_turn_left)
7022 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7023 else if (can_turn_right)
7024 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7026 MovDir[x][y] = back_dir;
7028 MovDelay[x][y] = 16 + 16 * RND(3);
7030 else if (element == EL_PACMAN)
7032 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7033 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7035 if (can_turn_left && can_turn_right)
7036 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7037 else if (can_turn_left)
7038 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7039 else if (can_turn_right)
7040 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7042 MovDir[x][y] = back_dir;
7044 MovDelay[x][y] = 6 + RND(40);
7046 else if (element == EL_PIG)
7048 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7049 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7050 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7051 boolean should_turn_left, should_turn_right, should_move_on;
7053 int rnd = RND(rnd_value);
7055 should_turn_left = (can_turn_left &&
7057 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7058 y + back_dy + left_dy)));
7059 should_turn_right = (can_turn_right &&
7061 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7062 y + back_dy + right_dy)));
7063 should_move_on = (can_move_on &&
7066 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7067 y + move_dy + left_dy) ||
7068 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7069 y + move_dy + right_dy)));
7071 if (should_turn_left || should_turn_right || should_move_on)
7073 if (should_turn_left && should_turn_right && should_move_on)
7074 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7075 rnd < 2 * rnd_value / 3 ? right_dir :
7077 else if (should_turn_left && should_turn_right)
7078 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7079 else if (should_turn_left && should_move_on)
7080 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7081 else if (should_turn_right && should_move_on)
7082 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7083 else if (should_turn_left)
7084 MovDir[x][y] = left_dir;
7085 else if (should_turn_right)
7086 MovDir[x][y] = right_dir;
7087 else if (should_move_on)
7088 MovDir[x][y] = old_move_dir;
7090 else if (can_move_on && rnd > rnd_value / 8)
7091 MovDir[x][y] = old_move_dir;
7092 else if (can_turn_left && can_turn_right)
7093 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7094 else if (can_turn_left && rnd > rnd_value / 8)
7095 MovDir[x][y] = left_dir;
7096 else if (can_turn_right && rnd > rnd_value/8)
7097 MovDir[x][y] = right_dir;
7099 MovDir[x][y] = back_dir;
7101 xx = x + move_xy[MovDir[x][y]].dx;
7102 yy = y + move_xy[MovDir[x][y]].dy;
7104 if (!IN_LEV_FIELD(xx, yy) ||
7105 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7106 MovDir[x][y] = old_move_dir;
7110 else if (element == EL_DRAGON)
7112 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7113 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7114 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7116 int rnd = RND(rnd_value);
7118 if (can_move_on && rnd > rnd_value / 8)
7119 MovDir[x][y] = old_move_dir;
7120 else if (can_turn_left && can_turn_right)
7121 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7122 else if (can_turn_left && rnd > rnd_value / 8)
7123 MovDir[x][y] = left_dir;
7124 else if (can_turn_right && rnd > rnd_value / 8)
7125 MovDir[x][y] = right_dir;
7127 MovDir[x][y] = back_dir;
7129 xx = x + move_xy[MovDir[x][y]].dx;
7130 yy = y + move_xy[MovDir[x][y]].dy;
7132 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7133 MovDir[x][y] = old_move_dir;
7137 else if (element == EL_MOLE)
7139 boolean can_move_on =
7140 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7141 IS_AMOEBOID(Tile[move_x][move_y]) ||
7142 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7145 boolean can_turn_left =
7146 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7147 IS_AMOEBOID(Tile[left_x][left_y])));
7149 boolean can_turn_right =
7150 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7151 IS_AMOEBOID(Tile[right_x][right_y])));
7153 if (can_turn_left && can_turn_right)
7154 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7155 else if (can_turn_left)
7156 MovDir[x][y] = left_dir;
7158 MovDir[x][y] = right_dir;
7161 if (MovDir[x][y] != old_move_dir)
7164 else if (element == EL_BALLOON)
7166 MovDir[x][y] = game.wind_direction;
7169 else if (element == EL_SPRING)
7171 if (MovDir[x][y] & MV_HORIZONTAL)
7173 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7174 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7176 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7177 ResetGfxAnimation(move_x, move_y);
7178 TEST_DrawLevelField(move_x, move_y);
7180 MovDir[x][y] = back_dir;
7182 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7183 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7184 MovDir[x][y] = MV_NONE;
7189 else if (element == EL_ROBOT ||
7190 element == EL_SATELLITE ||
7191 element == EL_PENGUIN ||
7192 element == EL_EMC_ANDROID)
7194 int attr_x = -1, attr_y = -1;
7196 if (game.all_players_gone)
7198 attr_x = game.exit_x;
7199 attr_y = game.exit_y;
7205 for (i = 0; i < MAX_PLAYERS; i++)
7207 struct PlayerInfo *player = &stored_player[i];
7208 int jx = player->jx, jy = player->jy;
7210 if (!player->active)
7214 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7222 if (element == EL_ROBOT &&
7223 game.robot_wheel_x >= 0 &&
7224 game.robot_wheel_y >= 0 &&
7225 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7226 game.engine_version < VERSION_IDENT(3,1,0,0)))
7228 attr_x = game.robot_wheel_x;
7229 attr_y = game.robot_wheel_y;
7232 if (element == EL_PENGUIN)
7235 static int xy[4][2] =
7243 for (i = 0; i < NUM_DIRECTIONS; i++)
7245 int ex = x + xy[i][0];
7246 int ey = y + xy[i][1];
7248 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7249 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7250 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7251 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7260 MovDir[x][y] = MV_NONE;
7262 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7263 else if (attr_x > x)
7264 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7266 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7267 else if (attr_y > y)
7268 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7270 if (element == EL_ROBOT)
7274 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7275 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7276 Moving2Blocked(x, y, &newx, &newy);
7278 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7279 MovDelay[x][y] = 8 + 8 * !RND(3);
7281 MovDelay[x][y] = 16;
7283 else if (element == EL_PENGUIN)
7289 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7291 boolean first_horiz = RND(2);
7292 int new_move_dir = MovDir[x][y];
7295 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7296 Moving2Blocked(x, y, &newx, &newy);
7298 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7302 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7303 Moving2Blocked(x, y, &newx, &newy);
7305 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7308 MovDir[x][y] = old_move_dir;
7312 else if (element == EL_SATELLITE)
7318 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7320 boolean first_horiz = RND(2);
7321 int new_move_dir = MovDir[x][y];
7324 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7325 Moving2Blocked(x, y, &newx, &newy);
7327 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7331 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7332 Moving2Blocked(x, y, &newx, &newy);
7334 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7337 MovDir[x][y] = old_move_dir;
7341 else if (element == EL_EMC_ANDROID)
7343 static int check_pos[16] =
7345 -1, // 0 => (invalid)
7348 -1, // 3 => (invalid)
7350 0, // 5 => MV_LEFT | MV_UP
7351 2, // 6 => MV_RIGHT | MV_UP
7352 -1, // 7 => (invalid)
7354 6, // 9 => MV_LEFT | MV_DOWN
7355 4, // 10 => MV_RIGHT | MV_DOWN
7356 -1, // 11 => (invalid)
7357 -1, // 12 => (invalid)
7358 -1, // 13 => (invalid)
7359 -1, // 14 => (invalid)
7360 -1, // 15 => (invalid)
7368 { -1, -1, MV_LEFT | MV_UP },
7370 { +1, -1, MV_RIGHT | MV_UP },
7371 { +1, 0, MV_RIGHT },
7372 { +1, +1, MV_RIGHT | MV_DOWN },
7374 { -1, +1, MV_LEFT | MV_DOWN },
7377 int start_pos, check_order;
7378 boolean can_clone = FALSE;
7381 // check if there is any free field around current position
7382 for (i = 0; i < 8; i++)
7384 int newx = x + check_xy[i].dx;
7385 int newy = y + check_xy[i].dy;
7387 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7395 if (can_clone) // randomly find an element to clone
7399 start_pos = check_pos[RND(8)];
7400 check_order = (RND(2) ? -1 : +1);
7402 for (i = 0; i < 8; i++)
7404 int pos_raw = start_pos + i * check_order;
7405 int pos = (pos_raw + 8) % 8;
7406 int newx = x + check_xy[pos].dx;
7407 int newy = y + check_xy[pos].dy;
7409 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7411 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7412 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7414 Store[x][y] = Tile[newx][newy];
7423 if (can_clone) // randomly find a direction to move
7427 start_pos = check_pos[RND(8)];
7428 check_order = (RND(2) ? -1 : +1);
7430 for (i = 0; i < 8; i++)
7432 int pos_raw = start_pos + i * check_order;
7433 int pos = (pos_raw + 8) % 8;
7434 int newx = x + check_xy[pos].dx;
7435 int newy = y + check_xy[pos].dy;
7436 int new_move_dir = check_xy[pos].dir;
7438 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7440 MovDir[x][y] = new_move_dir;
7441 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7450 if (can_clone) // cloning and moving successful
7453 // cannot clone -- try to move towards player
7455 start_pos = check_pos[MovDir[x][y] & 0x0f];
7456 check_order = (RND(2) ? -1 : +1);
7458 for (i = 0; i < 3; i++)
7460 // first check start_pos, then previous/next or (next/previous) pos
7461 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7462 int pos = (pos_raw + 8) % 8;
7463 int newx = x + check_xy[pos].dx;
7464 int newy = y + check_xy[pos].dy;
7465 int new_move_dir = check_xy[pos].dir;
7467 if (IS_PLAYER(newx, newy))
7470 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7472 MovDir[x][y] = new_move_dir;
7473 MovDelay[x][y] = level.android_move_time * 8 + 1;
7480 else if (move_pattern == MV_TURNING_LEFT ||
7481 move_pattern == MV_TURNING_RIGHT ||
7482 move_pattern == MV_TURNING_LEFT_RIGHT ||
7483 move_pattern == MV_TURNING_RIGHT_LEFT ||
7484 move_pattern == MV_TURNING_RANDOM ||
7485 move_pattern == MV_ALL_DIRECTIONS)
7487 boolean can_turn_left =
7488 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7489 boolean can_turn_right =
7490 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7492 if (element_info[element].move_stepsize == 0) // "not moving"
7495 if (move_pattern == MV_TURNING_LEFT)
7496 MovDir[x][y] = left_dir;
7497 else if (move_pattern == MV_TURNING_RIGHT)
7498 MovDir[x][y] = right_dir;
7499 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7500 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7501 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7502 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7503 else if (move_pattern == MV_TURNING_RANDOM)
7504 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7505 can_turn_right && !can_turn_left ? right_dir :
7506 RND(2) ? left_dir : right_dir);
7507 else if (can_turn_left && can_turn_right)
7508 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7509 else if (can_turn_left)
7510 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7511 else if (can_turn_right)
7512 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7514 MovDir[x][y] = back_dir;
7516 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7518 else if (move_pattern == MV_HORIZONTAL ||
7519 move_pattern == MV_VERTICAL)
7521 if (move_pattern & old_move_dir)
7522 MovDir[x][y] = back_dir;
7523 else if (move_pattern == MV_HORIZONTAL)
7524 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7525 else if (move_pattern == MV_VERTICAL)
7526 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7528 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7530 else if (move_pattern & MV_ANY_DIRECTION)
7532 MovDir[x][y] = move_pattern;
7533 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7535 else if (move_pattern & MV_WIND_DIRECTION)
7537 MovDir[x][y] = game.wind_direction;
7538 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7540 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7542 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7543 MovDir[x][y] = left_dir;
7544 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7545 MovDir[x][y] = right_dir;
7547 if (MovDir[x][y] != old_move_dir)
7548 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7550 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7552 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7553 MovDir[x][y] = right_dir;
7554 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7555 MovDir[x][y] = left_dir;
7557 if (MovDir[x][y] != old_move_dir)
7558 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7560 else if (move_pattern == MV_TOWARDS_PLAYER ||
7561 move_pattern == MV_AWAY_FROM_PLAYER)
7563 int attr_x = -1, attr_y = -1;
7565 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7567 if (game.all_players_gone)
7569 attr_x = game.exit_x;
7570 attr_y = game.exit_y;
7576 for (i = 0; i < MAX_PLAYERS; i++)
7578 struct PlayerInfo *player = &stored_player[i];
7579 int jx = player->jx, jy = player->jy;
7581 if (!player->active)
7585 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7593 MovDir[x][y] = MV_NONE;
7595 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7596 else if (attr_x > x)
7597 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7599 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7600 else if (attr_y > y)
7601 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7603 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7605 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7607 boolean first_horiz = RND(2);
7608 int new_move_dir = MovDir[x][y];
7610 if (element_info[element].move_stepsize == 0) // "not moving"
7612 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7613 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7619 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7620 Moving2Blocked(x, y, &newx, &newy);
7622 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7626 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7627 Moving2Blocked(x, y, &newx, &newy);
7629 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7632 MovDir[x][y] = old_move_dir;
7635 else if (move_pattern == MV_WHEN_PUSHED ||
7636 move_pattern == MV_WHEN_DROPPED)
7638 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7639 MovDir[x][y] = MV_NONE;
7643 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7645 static int test_xy[7][2] =
7655 static int test_dir[7] =
7665 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7666 int move_preference = -1000000; // start with very low preference
7667 int new_move_dir = MV_NONE;
7668 int start_test = RND(4);
7671 for (i = 0; i < NUM_DIRECTIONS; i++)
7673 int move_dir = test_dir[start_test + i];
7674 int move_dir_preference;
7676 xx = x + test_xy[start_test + i][0];
7677 yy = y + test_xy[start_test + i][1];
7679 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7680 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7682 new_move_dir = move_dir;
7687 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7690 move_dir_preference = -1 * RunnerVisit[xx][yy];
7691 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7692 move_dir_preference = PlayerVisit[xx][yy];
7694 if (move_dir_preference > move_preference)
7696 // prefer field that has not been visited for the longest time
7697 move_preference = move_dir_preference;
7698 new_move_dir = move_dir;
7700 else if (move_dir_preference == move_preference &&
7701 move_dir == old_move_dir)
7703 // prefer last direction when all directions are preferred equally
7704 move_preference = move_dir_preference;
7705 new_move_dir = move_dir;
7709 MovDir[x][y] = new_move_dir;
7710 if (old_move_dir != new_move_dir)
7711 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7715 static void TurnRound(int x, int y)
7717 int direction = MovDir[x][y];
7721 GfxDir[x][y] = MovDir[x][y];
7723 if (direction != MovDir[x][y])
7727 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7729 ResetGfxFrame(x, y);
7732 static boolean JustBeingPushed(int x, int y)
7736 for (i = 0; i < MAX_PLAYERS; i++)
7738 struct PlayerInfo *player = &stored_player[i];
7740 if (player->active && player->is_pushing && player->MovPos)
7742 int next_jx = player->jx + (player->jx - player->last_jx);
7743 int next_jy = player->jy + (player->jy - player->last_jy);
7745 if (x == next_jx && y == next_jy)
7753 static void StartMoving(int x, int y)
7755 boolean started_moving = FALSE; // some elements can fall _and_ move
7756 int element = Tile[x][y];
7761 if (MovDelay[x][y] == 0)
7762 GfxAction[x][y] = ACTION_DEFAULT;
7764 if (CAN_FALL(element) && y < lev_fieldy - 1)
7766 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7767 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7768 if (JustBeingPushed(x, y))
7771 if (element == EL_QUICKSAND_FULL)
7773 if (IS_FREE(x, y + 1))
7775 InitMovingField(x, y, MV_DOWN);
7776 started_moving = TRUE;
7778 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7779 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7780 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7781 Store[x][y] = EL_ROCK;
7783 Store[x][y] = EL_ROCK;
7786 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7788 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7790 if (!MovDelay[x][y])
7792 MovDelay[x][y] = TILEY + 1;
7794 ResetGfxAnimation(x, y);
7795 ResetGfxAnimation(x, y + 1);
7800 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7801 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7808 Tile[x][y] = EL_QUICKSAND_EMPTY;
7809 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7810 Store[x][y + 1] = Store[x][y];
7813 PlayLevelSoundAction(x, y, ACTION_FILLING);
7815 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7817 if (!MovDelay[x][y])
7819 MovDelay[x][y] = TILEY + 1;
7821 ResetGfxAnimation(x, y);
7822 ResetGfxAnimation(x, y + 1);
7827 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7828 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7835 Tile[x][y] = EL_QUICKSAND_EMPTY;
7836 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7837 Store[x][y + 1] = Store[x][y];
7840 PlayLevelSoundAction(x, y, ACTION_FILLING);
7843 else if (element == EL_QUICKSAND_FAST_FULL)
7845 if (IS_FREE(x, y + 1))
7847 InitMovingField(x, y, MV_DOWN);
7848 started_moving = TRUE;
7850 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7851 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7852 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7853 Store[x][y] = EL_ROCK;
7855 Store[x][y] = EL_ROCK;
7858 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7860 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7862 if (!MovDelay[x][y])
7864 MovDelay[x][y] = TILEY + 1;
7866 ResetGfxAnimation(x, y);
7867 ResetGfxAnimation(x, y + 1);
7872 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7873 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7880 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7881 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7882 Store[x][y + 1] = Store[x][y];
7885 PlayLevelSoundAction(x, y, ACTION_FILLING);
7887 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7889 if (!MovDelay[x][y])
7891 MovDelay[x][y] = TILEY + 1;
7893 ResetGfxAnimation(x, y);
7894 ResetGfxAnimation(x, y + 1);
7899 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7900 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7907 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7908 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7909 Store[x][y + 1] = Store[x][y];
7912 PlayLevelSoundAction(x, y, ACTION_FILLING);
7915 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7916 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7918 InitMovingField(x, y, MV_DOWN);
7919 started_moving = TRUE;
7921 Tile[x][y] = EL_QUICKSAND_FILLING;
7922 Store[x][y] = element;
7924 PlayLevelSoundAction(x, y, ACTION_FILLING);
7926 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7927 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7929 InitMovingField(x, y, MV_DOWN);
7930 started_moving = TRUE;
7932 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7933 Store[x][y] = element;
7935 PlayLevelSoundAction(x, y, ACTION_FILLING);
7937 else if (element == EL_MAGIC_WALL_FULL)
7939 if (IS_FREE(x, y + 1))
7941 InitMovingField(x, y, MV_DOWN);
7942 started_moving = TRUE;
7944 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7945 Store[x][y] = EL_CHANGED(Store[x][y]);
7947 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7949 if (!MovDelay[x][y])
7950 MovDelay[x][y] = TILEY / 4 + 1;
7959 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7960 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7961 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7965 else if (element == EL_BD_MAGIC_WALL_FULL)
7967 if (IS_FREE(x, y + 1))
7969 InitMovingField(x, y, MV_DOWN);
7970 started_moving = TRUE;
7972 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7973 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7975 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7977 if (!MovDelay[x][y])
7978 MovDelay[x][y] = TILEY / 4 + 1;
7987 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7988 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7989 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7993 else if (element == EL_DC_MAGIC_WALL_FULL)
7995 if (IS_FREE(x, y + 1))
7997 InitMovingField(x, y, MV_DOWN);
7998 started_moving = TRUE;
8000 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8001 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8003 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8005 if (!MovDelay[x][y])
8006 MovDelay[x][y] = TILEY / 4 + 1;
8015 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8016 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8017 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8021 else if ((CAN_PASS_MAGIC_WALL(element) &&
8022 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8023 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8024 (CAN_PASS_DC_MAGIC_WALL(element) &&
8025 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8028 InitMovingField(x, y, MV_DOWN);
8029 started_moving = TRUE;
8032 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8033 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8034 EL_DC_MAGIC_WALL_FILLING);
8035 Store[x][y] = element;
8037 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8039 SplashAcid(x, y + 1);
8041 InitMovingField(x, y, MV_DOWN);
8042 started_moving = TRUE;
8044 Store[x][y] = EL_ACID;
8047 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8048 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8049 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8050 CAN_FALL(element) && WasJustFalling[x][y] &&
8051 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8053 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8054 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8055 (Tile[x][y + 1] == EL_BLOCKED)))
8057 /* this is needed for a special case not covered by calling "Impact()"
8058 from "ContinueMoving()": if an element moves to a tile directly below
8059 another element which was just falling on that tile (which was empty
8060 in the previous frame), the falling element above would just stop
8061 instead of smashing the element below (in previous version, the above
8062 element was just checked for "moving" instead of "falling", resulting
8063 in incorrect smashes caused by horizontal movement of the above
8064 element; also, the case of the player being the element to smash was
8065 simply not covered here... :-/ ) */
8067 CheckCollision[x][y] = 0;
8068 CheckImpact[x][y] = 0;
8072 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8074 if (MovDir[x][y] == MV_NONE)
8076 InitMovingField(x, y, MV_DOWN);
8077 started_moving = TRUE;
8080 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8082 if (WasJustFalling[x][y]) // prevent animation from being restarted
8083 MovDir[x][y] = MV_DOWN;
8085 InitMovingField(x, y, MV_DOWN);
8086 started_moving = TRUE;
8088 else if (element == EL_AMOEBA_DROP)
8090 Tile[x][y] = EL_AMOEBA_GROWING;
8091 Store[x][y] = EL_AMOEBA_WET;
8093 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8094 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8095 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8096 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8098 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8099 (IS_FREE(x - 1, y + 1) ||
8100 Tile[x - 1][y + 1] == EL_ACID));
8101 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8102 (IS_FREE(x + 1, y + 1) ||
8103 Tile[x + 1][y + 1] == EL_ACID));
8104 boolean can_fall_any = (can_fall_left || can_fall_right);
8105 boolean can_fall_both = (can_fall_left && can_fall_right);
8106 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8108 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8110 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8111 can_fall_right = FALSE;
8112 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8113 can_fall_left = FALSE;
8114 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8115 can_fall_right = FALSE;
8116 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8117 can_fall_left = FALSE;
8119 can_fall_any = (can_fall_left || can_fall_right);
8120 can_fall_both = FALSE;
8125 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8126 can_fall_right = FALSE; // slip down on left side
8128 can_fall_left = !(can_fall_right = RND(2));
8130 can_fall_both = FALSE;
8135 // if not determined otherwise, prefer left side for slipping down
8136 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8137 started_moving = TRUE;
8140 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8142 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8143 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8144 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8145 int belt_dir = game.belt_dir[belt_nr];
8147 if ((belt_dir == MV_LEFT && left_is_free) ||
8148 (belt_dir == MV_RIGHT && right_is_free))
8150 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8152 InitMovingField(x, y, belt_dir);
8153 started_moving = TRUE;
8155 Pushed[x][y] = TRUE;
8156 Pushed[nextx][y] = TRUE;
8158 GfxAction[x][y] = ACTION_DEFAULT;
8162 MovDir[x][y] = 0; // if element was moving, stop it
8167 // not "else if" because of elements that can fall and move (EL_SPRING)
8168 if (CAN_MOVE(element) && !started_moving)
8170 int move_pattern = element_info[element].move_pattern;
8173 Moving2Blocked(x, y, &newx, &newy);
8175 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8178 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8179 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8181 WasJustMoving[x][y] = 0;
8182 CheckCollision[x][y] = 0;
8184 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8186 if (Tile[x][y] != element) // element has changed
8190 if (!MovDelay[x][y]) // start new movement phase
8192 // all objects that can change their move direction after each step
8193 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8195 if (element != EL_YAMYAM &&
8196 element != EL_DARK_YAMYAM &&
8197 element != EL_PACMAN &&
8198 !(move_pattern & MV_ANY_DIRECTION) &&
8199 move_pattern != MV_TURNING_LEFT &&
8200 move_pattern != MV_TURNING_RIGHT &&
8201 move_pattern != MV_TURNING_LEFT_RIGHT &&
8202 move_pattern != MV_TURNING_RIGHT_LEFT &&
8203 move_pattern != MV_TURNING_RANDOM)
8207 if (MovDelay[x][y] && (element == EL_BUG ||
8208 element == EL_SPACESHIP ||
8209 element == EL_SP_SNIKSNAK ||
8210 element == EL_SP_ELECTRON ||
8211 element == EL_MOLE))
8212 TEST_DrawLevelField(x, y);
8216 if (MovDelay[x][y]) // wait some time before next movement
8220 if (element == EL_ROBOT ||
8221 element == EL_YAMYAM ||
8222 element == EL_DARK_YAMYAM)
8224 DrawLevelElementAnimationIfNeeded(x, y, element);
8225 PlayLevelSoundAction(x, y, ACTION_WAITING);
8227 else if (element == EL_SP_ELECTRON)
8228 DrawLevelElementAnimationIfNeeded(x, y, element);
8229 else if (element == EL_DRAGON)
8232 int dir = MovDir[x][y];
8233 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8234 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8235 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8236 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8237 dir == MV_UP ? IMG_FLAMES_1_UP :
8238 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8239 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8241 GfxAction[x][y] = ACTION_ATTACKING;
8243 if (IS_PLAYER(x, y))
8244 DrawPlayerField(x, y);
8246 TEST_DrawLevelField(x, y);
8248 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8250 for (i = 1; i <= 3; i++)
8252 int xx = x + i * dx;
8253 int yy = y + i * dy;
8254 int sx = SCREENX(xx);
8255 int sy = SCREENY(yy);
8256 int flame_graphic = graphic + (i - 1);
8258 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8263 int flamed = MovingOrBlocked2Element(xx, yy);
8265 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8268 RemoveMovingField(xx, yy);
8270 ChangeDelay[xx][yy] = 0;
8272 Tile[xx][yy] = EL_FLAMES;
8274 if (IN_SCR_FIELD(sx, sy))
8276 TEST_DrawLevelFieldCrumbled(xx, yy);
8277 DrawGraphic(sx, sy, flame_graphic, frame);
8282 if (Tile[xx][yy] == EL_FLAMES)
8283 Tile[xx][yy] = EL_EMPTY;
8284 TEST_DrawLevelField(xx, yy);
8289 if (MovDelay[x][y]) // element still has to wait some time
8291 PlayLevelSoundAction(x, y, ACTION_WAITING);
8297 // now make next step
8299 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8301 if (DONT_COLLIDE_WITH(element) &&
8302 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8303 !PLAYER_ENEMY_PROTECTED(newx, newy))
8305 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8310 else if (CAN_MOVE_INTO_ACID(element) &&
8311 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8312 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8313 (MovDir[x][y] == MV_DOWN ||
8314 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8316 SplashAcid(newx, newy);
8317 Store[x][y] = EL_ACID;
8319 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8321 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8322 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8323 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8324 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8327 TEST_DrawLevelField(x, y);
8329 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8330 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8331 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8333 game.friends_still_needed--;
8334 if (!game.friends_still_needed &&
8336 game.all_players_gone)
8341 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8343 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8344 TEST_DrawLevelField(newx, newy);
8346 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8348 else if (!IS_FREE(newx, newy))
8350 GfxAction[x][y] = ACTION_WAITING;
8352 if (IS_PLAYER(x, y))
8353 DrawPlayerField(x, y);
8355 TEST_DrawLevelField(x, y);
8360 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8362 if (IS_FOOD_PIG(Tile[newx][newy]))
8364 if (IS_MOVING(newx, newy))
8365 RemoveMovingField(newx, newy);
8368 Tile[newx][newy] = EL_EMPTY;
8369 TEST_DrawLevelField(newx, newy);
8372 PlayLevelSound(x, y, SND_PIG_DIGGING);
8374 else if (!IS_FREE(newx, newy))
8376 if (IS_PLAYER(x, y))
8377 DrawPlayerField(x, y);
8379 TEST_DrawLevelField(x, y);
8384 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8386 if (Store[x][y] != EL_EMPTY)
8388 boolean can_clone = FALSE;
8391 // check if element to clone is still there
8392 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8394 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8402 // cannot clone or target field not free anymore -- do not clone
8403 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8404 Store[x][y] = EL_EMPTY;
8407 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8409 if (IS_MV_DIAGONAL(MovDir[x][y]))
8411 int diagonal_move_dir = MovDir[x][y];
8412 int stored = Store[x][y];
8413 int change_delay = 8;
8416 // android is moving diagonally
8418 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8420 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8421 GfxElement[x][y] = EL_EMC_ANDROID;
8422 GfxAction[x][y] = ACTION_SHRINKING;
8423 GfxDir[x][y] = diagonal_move_dir;
8424 ChangeDelay[x][y] = change_delay;
8426 if (Store[x][y] == EL_EMPTY)
8427 Store[x][y] = GfxElementEmpty[x][y];
8429 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8432 DrawLevelGraphicAnimation(x, y, graphic);
8433 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8435 if (Tile[newx][newy] == EL_ACID)
8437 SplashAcid(newx, newy);
8442 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8444 Store[newx][newy] = EL_EMC_ANDROID;
8445 GfxElement[newx][newy] = EL_EMC_ANDROID;
8446 GfxAction[newx][newy] = ACTION_GROWING;
8447 GfxDir[newx][newy] = diagonal_move_dir;
8448 ChangeDelay[newx][newy] = change_delay;
8450 graphic = el_act_dir2img(GfxElement[newx][newy],
8451 GfxAction[newx][newy], GfxDir[newx][newy]);
8453 DrawLevelGraphicAnimation(newx, newy, graphic);
8454 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8460 Tile[newx][newy] = EL_EMPTY;
8461 TEST_DrawLevelField(newx, newy);
8463 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8466 else if (!IS_FREE(newx, newy))
8471 else if (IS_CUSTOM_ELEMENT(element) &&
8472 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8474 if (!DigFieldByCE(newx, newy, element))
8477 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8479 RunnerVisit[x][y] = FrameCounter;
8480 PlayerVisit[x][y] /= 8; // expire player visit path
8483 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8485 if (!IS_FREE(newx, newy))
8487 if (IS_PLAYER(x, y))
8488 DrawPlayerField(x, y);
8490 TEST_DrawLevelField(x, y);
8496 boolean wanna_flame = !RND(10);
8497 int dx = newx - x, dy = newy - y;
8498 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8499 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8500 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8501 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8502 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8503 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8506 IS_CLASSIC_ENEMY(element1) ||
8507 IS_CLASSIC_ENEMY(element2)) &&
8508 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8509 element1 != EL_FLAMES && element2 != EL_FLAMES)
8511 ResetGfxAnimation(x, y);
8512 GfxAction[x][y] = ACTION_ATTACKING;
8514 if (IS_PLAYER(x, y))
8515 DrawPlayerField(x, y);
8517 TEST_DrawLevelField(x, y);
8519 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8521 MovDelay[x][y] = 50;
8523 Tile[newx][newy] = EL_FLAMES;
8524 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8525 Tile[newx1][newy1] = EL_FLAMES;
8526 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8527 Tile[newx2][newy2] = EL_FLAMES;
8533 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8534 Tile[newx][newy] == EL_DIAMOND)
8536 if (IS_MOVING(newx, newy))
8537 RemoveMovingField(newx, newy);
8540 Tile[newx][newy] = EL_EMPTY;
8541 TEST_DrawLevelField(newx, newy);
8544 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8546 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8547 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8549 if (AmoebaNr[newx][newy])
8551 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8552 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8553 Tile[newx][newy] == EL_BD_AMOEBA)
8554 AmoebaCnt[AmoebaNr[newx][newy]]--;
8557 if (IS_MOVING(newx, newy))
8559 RemoveMovingField(newx, newy);
8563 Tile[newx][newy] = EL_EMPTY;
8564 TEST_DrawLevelField(newx, newy);
8567 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8569 else if ((element == EL_PACMAN || element == EL_MOLE)
8570 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8572 if (AmoebaNr[newx][newy])
8574 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8575 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8576 Tile[newx][newy] == EL_BD_AMOEBA)
8577 AmoebaCnt[AmoebaNr[newx][newy]]--;
8580 if (element == EL_MOLE)
8582 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8583 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8585 ResetGfxAnimation(x, y);
8586 GfxAction[x][y] = ACTION_DIGGING;
8587 TEST_DrawLevelField(x, y);
8589 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8591 return; // wait for shrinking amoeba
8593 else // element == EL_PACMAN
8595 Tile[newx][newy] = EL_EMPTY;
8596 TEST_DrawLevelField(newx, newy);
8597 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8600 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8601 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8602 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8604 // wait for shrinking amoeba to completely disappear
8607 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8609 // object was running against a wall
8613 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8614 DrawLevelElementAnimation(x, y, element);
8616 if (DONT_TOUCH(element))
8617 TestIfBadThingTouchesPlayer(x, y);
8622 InitMovingField(x, y, MovDir[x][y]);
8624 PlayLevelSoundAction(x, y, ACTION_MOVING);
8628 ContinueMoving(x, y);
8631 void ContinueMoving(int x, int y)
8633 int element = Tile[x][y];
8634 struct ElementInfo *ei = &element_info[element];
8635 int direction = MovDir[x][y];
8636 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8637 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8638 int newx = x + dx, newy = y + dy;
8639 int stored = Store[x][y];
8640 int stored_new = Store[newx][newy];
8641 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8642 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8643 boolean last_line = (newy == lev_fieldy - 1);
8644 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8646 if (pushed_by_player) // special case: moving object pushed by player
8648 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8650 else if (use_step_delay) // special case: moving object has step delay
8652 if (!MovDelay[x][y])
8653 MovPos[x][y] += getElementMoveStepsize(x, y);
8658 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8662 TEST_DrawLevelField(x, y);
8664 return; // element is still waiting
8667 else // normal case: generically moving object
8669 MovPos[x][y] += getElementMoveStepsize(x, y);
8672 if (ABS(MovPos[x][y]) < TILEX)
8674 TEST_DrawLevelField(x, y);
8676 return; // element is still moving
8679 // element reached destination field
8681 Tile[x][y] = EL_EMPTY;
8682 Tile[newx][newy] = element;
8683 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8685 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8687 element = Tile[newx][newy] = EL_ACID;
8689 else if (element == EL_MOLE)
8691 Tile[x][y] = EL_SAND;
8693 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8695 else if (element == EL_QUICKSAND_FILLING)
8697 element = Tile[newx][newy] = get_next_element(element);
8698 Store[newx][newy] = Store[x][y];
8700 else if (element == EL_QUICKSAND_EMPTYING)
8702 Tile[x][y] = get_next_element(element);
8703 element = Tile[newx][newy] = Store[x][y];
8705 else if (element == EL_QUICKSAND_FAST_FILLING)
8707 element = Tile[newx][newy] = get_next_element(element);
8708 Store[newx][newy] = Store[x][y];
8710 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8712 Tile[x][y] = get_next_element(element);
8713 element = Tile[newx][newy] = Store[x][y];
8715 else if (element == EL_MAGIC_WALL_FILLING)
8717 element = Tile[newx][newy] = get_next_element(element);
8718 if (!game.magic_wall_active)
8719 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8720 Store[newx][newy] = Store[x][y];
8722 else if (element == EL_MAGIC_WALL_EMPTYING)
8724 Tile[x][y] = get_next_element(element);
8725 if (!game.magic_wall_active)
8726 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8727 element = Tile[newx][newy] = Store[x][y];
8729 InitField(newx, newy, FALSE);
8731 else if (element == EL_BD_MAGIC_WALL_FILLING)
8733 element = Tile[newx][newy] = get_next_element(element);
8734 if (!game.magic_wall_active)
8735 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8736 Store[newx][newy] = Store[x][y];
8738 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8740 Tile[x][y] = get_next_element(element);
8741 if (!game.magic_wall_active)
8742 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8743 element = Tile[newx][newy] = Store[x][y];
8745 InitField(newx, newy, FALSE);
8747 else if (element == EL_DC_MAGIC_WALL_FILLING)
8749 element = Tile[newx][newy] = get_next_element(element);
8750 if (!game.magic_wall_active)
8751 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8752 Store[newx][newy] = Store[x][y];
8754 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8756 Tile[x][y] = get_next_element(element);
8757 if (!game.magic_wall_active)
8758 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8759 element = Tile[newx][newy] = Store[x][y];
8761 InitField(newx, newy, FALSE);
8763 else if (element == EL_AMOEBA_DROPPING)
8765 Tile[x][y] = get_next_element(element);
8766 element = Tile[newx][newy] = Store[x][y];
8768 else if (element == EL_SOKOBAN_OBJECT)
8771 Tile[x][y] = Back[x][y];
8773 if (Back[newx][newy])
8774 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8776 Back[x][y] = Back[newx][newy] = 0;
8779 Store[x][y] = EL_EMPTY;
8784 MovDelay[newx][newy] = 0;
8786 if (CAN_CHANGE_OR_HAS_ACTION(element))
8788 // copy element change control values to new field
8789 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8790 ChangePage[newx][newy] = ChangePage[x][y];
8791 ChangeCount[newx][newy] = ChangeCount[x][y];
8792 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8795 CustomValue[newx][newy] = CustomValue[x][y];
8797 ChangeDelay[x][y] = 0;
8798 ChangePage[x][y] = -1;
8799 ChangeCount[x][y] = 0;
8800 ChangeEvent[x][y] = -1;
8802 CustomValue[x][y] = 0;
8804 // copy animation control values to new field
8805 GfxFrame[newx][newy] = GfxFrame[x][y];
8806 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8807 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8808 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8810 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8812 // some elements can leave other elements behind after moving
8813 if (ei->move_leave_element != EL_EMPTY &&
8814 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8815 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8817 int move_leave_element = ei->move_leave_element;
8819 // this makes it possible to leave the removed element again
8820 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8821 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8823 Tile[x][y] = move_leave_element;
8825 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8826 MovDir[x][y] = direction;
8828 InitField(x, y, FALSE);
8830 if (GFX_CRUMBLED(Tile[x][y]))
8831 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8833 if (IS_PLAYER_ELEMENT(move_leave_element))
8834 RelocatePlayer(x, y, move_leave_element);
8837 // do this after checking for left-behind element
8838 ResetGfxAnimation(x, y); // reset animation values for old field
8840 if (!CAN_MOVE(element) ||
8841 (CAN_FALL(element) && direction == MV_DOWN &&
8842 (element == EL_SPRING ||
8843 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8844 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8845 GfxDir[x][y] = MovDir[newx][newy] = 0;
8847 TEST_DrawLevelField(x, y);
8848 TEST_DrawLevelField(newx, newy);
8850 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8852 // prevent pushed element from moving on in pushed direction
8853 if (pushed_by_player && CAN_MOVE(element) &&
8854 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8855 !(element_info[element].move_pattern & direction))
8856 TurnRound(newx, newy);
8858 // prevent elements on conveyor belt from moving on in last direction
8859 if (pushed_by_conveyor && CAN_FALL(element) &&
8860 direction & MV_HORIZONTAL)
8861 MovDir[newx][newy] = 0;
8863 if (!pushed_by_player)
8865 int nextx = newx + dx, nexty = newy + dy;
8866 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8868 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8870 if (CAN_FALL(element) && direction == MV_DOWN)
8871 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8873 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8874 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8876 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8877 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8880 if (DONT_TOUCH(element)) // object may be nasty to player or others
8882 TestIfBadThingTouchesPlayer(newx, newy);
8883 TestIfBadThingTouchesFriend(newx, newy);
8885 if (!IS_CUSTOM_ELEMENT(element))
8886 TestIfBadThingTouchesOtherBadThing(newx, newy);
8888 else if (element == EL_PENGUIN)
8889 TestIfFriendTouchesBadThing(newx, newy);
8891 if (DONT_GET_HIT_BY(element))
8893 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8896 // give the player one last chance (one more frame) to move away
8897 if (CAN_FALL(element) && direction == MV_DOWN &&
8898 (last_line || (!IS_FREE(x, newy + 1) &&
8899 (!IS_PLAYER(x, newy + 1) ||
8900 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8903 if (pushed_by_player && !game.use_change_when_pushing_bug)
8905 int push_side = MV_DIR_OPPOSITE(direction);
8906 struct PlayerInfo *player = PLAYERINFO(x, y);
8908 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8909 player->index_bit, push_side);
8910 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8911 player->index_bit, push_side);
8914 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8915 MovDelay[newx][newy] = 1;
8917 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8919 TestIfElementTouchesCustomElement(x, y); // empty or new element
8920 TestIfElementHitsCustomElement(newx, newy, direction);
8921 TestIfPlayerTouchesCustomElement(newx, newy);
8922 TestIfElementTouchesCustomElement(newx, newy);
8924 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8925 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8926 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8927 MV_DIR_OPPOSITE(direction));
8930 int AmoebaNeighbourNr(int ax, int ay)
8933 int element = Tile[ax][ay];
8935 static int xy[4][2] =
8943 for (i = 0; i < NUM_DIRECTIONS; i++)
8945 int x = ax + xy[i][0];
8946 int y = ay + xy[i][1];
8948 if (!IN_LEV_FIELD(x, y))
8951 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8952 group_nr = AmoebaNr[x][y];
8958 static void AmoebaMerge(int ax, int ay)
8960 int i, x, y, xx, yy;
8961 int new_group_nr = AmoebaNr[ax][ay];
8962 static int xy[4][2] =
8970 if (new_group_nr == 0)
8973 for (i = 0; i < NUM_DIRECTIONS; i++)
8978 if (!IN_LEV_FIELD(x, y))
8981 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8982 Tile[x][y] == EL_BD_AMOEBA ||
8983 Tile[x][y] == EL_AMOEBA_DEAD) &&
8984 AmoebaNr[x][y] != new_group_nr)
8986 int old_group_nr = AmoebaNr[x][y];
8988 if (old_group_nr == 0)
8991 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8992 AmoebaCnt[old_group_nr] = 0;
8993 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8994 AmoebaCnt2[old_group_nr] = 0;
8996 SCAN_PLAYFIELD(xx, yy)
8998 if (AmoebaNr[xx][yy] == old_group_nr)
8999 AmoebaNr[xx][yy] = new_group_nr;
9005 void AmoebaToDiamond(int ax, int ay)
9009 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9011 int group_nr = AmoebaNr[ax][ay];
9016 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9017 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9023 SCAN_PLAYFIELD(x, y)
9025 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9028 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9032 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9033 SND_AMOEBA_TURNING_TO_GEM :
9034 SND_AMOEBA_TURNING_TO_ROCK));
9039 static int xy[4][2] =
9047 for (i = 0; i < NUM_DIRECTIONS; i++)
9052 if (!IN_LEV_FIELD(x, y))
9055 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9057 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9058 SND_AMOEBA_TURNING_TO_GEM :
9059 SND_AMOEBA_TURNING_TO_ROCK));
9066 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9069 int group_nr = AmoebaNr[ax][ay];
9070 boolean done = FALSE;
9075 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9076 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9082 SCAN_PLAYFIELD(x, y)
9084 if (AmoebaNr[x][y] == group_nr &&
9085 (Tile[x][y] == EL_AMOEBA_DEAD ||
9086 Tile[x][y] == EL_BD_AMOEBA ||
9087 Tile[x][y] == EL_AMOEBA_GROWING))
9090 Tile[x][y] = new_element;
9091 InitField(x, y, FALSE);
9092 TEST_DrawLevelField(x, y);
9098 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9099 SND_BD_AMOEBA_TURNING_TO_ROCK :
9100 SND_BD_AMOEBA_TURNING_TO_GEM));
9103 static void AmoebaGrowing(int x, int y)
9105 static unsigned int sound_delay = 0;
9106 static unsigned int sound_delay_value = 0;
9108 if (!MovDelay[x][y]) // start new growing cycle
9112 if (DelayReached(&sound_delay, sound_delay_value))
9114 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9115 sound_delay_value = 30;
9119 if (MovDelay[x][y]) // wait some time before growing bigger
9122 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9124 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9125 6 - MovDelay[x][y]);
9127 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
9130 if (!MovDelay[x][y])
9132 Tile[x][y] = Store[x][y];
9134 TEST_DrawLevelField(x, y);
9139 static void AmoebaShrinking(int x, int y)
9141 static unsigned int sound_delay = 0;
9142 static unsigned int sound_delay_value = 0;
9144 if (!MovDelay[x][y]) // start new shrinking cycle
9148 if (DelayReached(&sound_delay, sound_delay_value))
9149 sound_delay_value = 30;
9152 if (MovDelay[x][y]) // wait some time before shrinking
9155 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9157 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9158 6 - MovDelay[x][y]);
9160 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9163 if (!MovDelay[x][y])
9165 Tile[x][y] = EL_EMPTY;
9166 TEST_DrawLevelField(x, y);
9168 // don't let mole enter this field in this cycle;
9169 // (give priority to objects falling to this field from above)
9175 static void AmoebaReproduce(int ax, int ay)
9178 int element = Tile[ax][ay];
9179 int graphic = el2img(element);
9180 int newax = ax, neway = ay;
9181 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9182 static int xy[4][2] =
9190 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9192 Tile[ax][ay] = EL_AMOEBA_DEAD;
9193 TEST_DrawLevelField(ax, ay);
9197 if (IS_ANIMATED(graphic))
9198 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9200 if (!MovDelay[ax][ay]) // start making new amoeba field
9201 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9203 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9206 if (MovDelay[ax][ay])
9210 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9213 int x = ax + xy[start][0];
9214 int y = ay + xy[start][1];
9216 if (!IN_LEV_FIELD(x, y))
9219 if (IS_FREE(x, y) ||
9220 CAN_GROW_INTO(Tile[x][y]) ||
9221 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9222 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9228 if (newax == ax && neway == ay)
9231 else // normal or "filled" (BD style) amoeba
9234 boolean waiting_for_player = FALSE;
9236 for (i = 0; i < NUM_DIRECTIONS; i++)
9238 int j = (start + i) % 4;
9239 int x = ax + xy[j][0];
9240 int y = ay + xy[j][1];
9242 if (!IN_LEV_FIELD(x, y))
9245 if (IS_FREE(x, y) ||
9246 CAN_GROW_INTO(Tile[x][y]) ||
9247 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9248 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9254 else if (IS_PLAYER(x, y))
9255 waiting_for_player = TRUE;
9258 if (newax == ax && neway == ay) // amoeba cannot grow
9260 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9262 Tile[ax][ay] = EL_AMOEBA_DEAD;
9263 TEST_DrawLevelField(ax, ay);
9264 AmoebaCnt[AmoebaNr[ax][ay]]--;
9266 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9268 if (element == EL_AMOEBA_FULL)
9269 AmoebaToDiamond(ax, ay);
9270 else if (element == EL_BD_AMOEBA)
9271 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9276 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9278 // amoeba gets larger by growing in some direction
9280 int new_group_nr = AmoebaNr[ax][ay];
9283 if (new_group_nr == 0)
9285 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9287 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9293 AmoebaNr[newax][neway] = new_group_nr;
9294 AmoebaCnt[new_group_nr]++;
9295 AmoebaCnt2[new_group_nr]++;
9297 // if amoeba touches other amoeba(s) after growing, unify them
9298 AmoebaMerge(newax, neway);
9300 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9302 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9308 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9309 (neway == lev_fieldy - 1 && newax != ax))
9311 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9312 Store[newax][neway] = element;
9314 else if (neway == ay || element == EL_EMC_DRIPPER)
9316 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9318 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9322 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9323 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9324 Store[ax][ay] = EL_AMOEBA_DROP;
9325 ContinueMoving(ax, ay);
9329 TEST_DrawLevelField(newax, neway);
9332 static void Life(int ax, int ay)
9336 int element = Tile[ax][ay];
9337 int graphic = el2img(element);
9338 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9340 boolean changed = FALSE;
9342 if (IS_ANIMATED(graphic))
9343 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9348 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9349 MovDelay[ax][ay] = life_time;
9351 if (MovDelay[ax][ay]) // wait some time before next cycle
9354 if (MovDelay[ax][ay])
9358 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9360 int xx = ax+x1, yy = ay+y1;
9361 int old_element = Tile[xx][yy];
9362 int num_neighbours = 0;
9364 if (!IN_LEV_FIELD(xx, yy))
9367 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9369 int x = xx+x2, y = yy+y2;
9371 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9374 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9375 boolean is_neighbour = FALSE;
9377 if (level.use_life_bugs)
9379 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9380 (IS_FREE(x, y) && Stop[x][y]));
9383 (Last[x][y] == element || is_player_cell);
9389 boolean is_free = FALSE;
9391 if (level.use_life_bugs)
9392 is_free = (IS_FREE(xx, yy));
9394 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9396 if (xx == ax && yy == ay) // field in the middle
9398 if (num_neighbours < life_parameter[0] ||
9399 num_neighbours > life_parameter[1])
9401 Tile[xx][yy] = EL_EMPTY;
9402 if (Tile[xx][yy] != old_element)
9403 TEST_DrawLevelField(xx, yy);
9404 Stop[xx][yy] = TRUE;
9408 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9409 { // free border field
9410 if (num_neighbours >= life_parameter[2] &&
9411 num_neighbours <= life_parameter[3])
9413 Tile[xx][yy] = element;
9414 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9415 if (Tile[xx][yy] != old_element)
9416 TEST_DrawLevelField(xx, yy);
9417 Stop[xx][yy] = TRUE;
9424 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9425 SND_GAME_OF_LIFE_GROWING);
9428 static void InitRobotWheel(int x, int y)
9430 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9433 static void RunRobotWheel(int x, int y)
9435 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9438 static void StopRobotWheel(int x, int y)
9440 if (game.robot_wheel_x == x &&
9441 game.robot_wheel_y == y)
9443 game.robot_wheel_x = -1;
9444 game.robot_wheel_y = -1;
9445 game.robot_wheel_active = FALSE;
9449 static void InitTimegateWheel(int x, int y)
9451 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9454 static void RunTimegateWheel(int x, int y)
9456 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9459 static void InitMagicBallDelay(int x, int y)
9461 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9464 static void ActivateMagicBall(int bx, int by)
9468 if (level.ball_random)
9470 int pos_border = RND(8); // select one of the eight border elements
9471 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9472 int xx = pos_content % 3;
9473 int yy = pos_content / 3;
9478 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9479 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9483 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9485 int xx = x - bx + 1;
9486 int yy = y - by + 1;
9488 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9489 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9493 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9496 static void CheckExit(int x, int y)
9498 if (game.gems_still_needed > 0 ||
9499 game.sokoban_fields_still_needed > 0 ||
9500 game.sokoban_objects_still_needed > 0 ||
9501 game.lights_still_needed > 0)
9503 int element = Tile[x][y];
9504 int graphic = el2img(element);
9506 if (IS_ANIMATED(graphic))
9507 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9512 // do not re-open exit door closed after last player
9513 if (game.all_players_gone)
9516 Tile[x][y] = EL_EXIT_OPENING;
9518 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9521 static void CheckExitEM(int x, int y)
9523 if (game.gems_still_needed > 0 ||
9524 game.sokoban_fields_still_needed > 0 ||
9525 game.sokoban_objects_still_needed > 0 ||
9526 game.lights_still_needed > 0)
9528 int element = Tile[x][y];
9529 int graphic = el2img(element);
9531 if (IS_ANIMATED(graphic))
9532 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9537 // do not re-open exit door closed after last player
9538 if (game.all_players_gone)
9541 Tile[x][y] = EL_EM_EXIT_OPENING;
9543 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9546 static void CheckExitSteel(int x, int y)
9548 if (game.gems_still_needed > 0 ||
9549 game.sokoban_fields_still_needed > 0 ||
9550 game.sokoban_objects_still_needed > 0 ||
9551 game.lights_still_needed > 0)
9553 int element = Tile[x][y];
9554 int graphic = el2img(element);
9556 if (IS_ANIMATED(graphic))
9557 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9562 // do not re-open exit door closed after last player
9563 if (game.all_players_gone)
9566 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9568 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9571 static void CheckExitSteelEM(int x, int y)
9573 if (game.gems_still_needed > 0 ||
9574 game.sokoban_fields_still_needed > 0 ||
9575 game.sokoban_objects_still_needed > 0 ||
9576 game.lights_still_needed > 0)
9578 int element = Tile[x][y];
9579 int graphic = el2img(element);
9581 if (IS_ANIMATED(graphic))
9582 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9587 // do not re-open exit door closed after last player
9588 if (game.all_players_gone)
9591 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9593 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9596 static void CheckExitSP(int x, int y)
9598 if (game.gems_still_needed > 0)
9600 int element = Tile[x][y];
9601 int graphic = el2img(element);
9603 if (IS_ANIMATED(graphic))
9604 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9609 // do not re-open exit door closed after last player
9610 if (game.all_players_gone)
9613 Tile[x][y] = EL_SP_EXIT_OPENING;
9615 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9618 static void CloseAllOpenTimegates(void)
9622 SCAN_PLAYFIELD(x, y)
9624 int element = Tile[x][y];
9626 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9628 Tile[x][y] = EL_TIMEGATE_CLOSING;
9630 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9635 static void DrawTwinkleOnField(int x, int y)
9637 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9640 if (Tile[x][y] == EL_BD_DIAMOND)
9643 if (MovDelay[x][y] == 0) // next animation frame
9644 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9646 if (MovDelay[x][y] != 0) // wait some time before next frame
9650 DrawLevelElementAnimation(x, y, Tile[x][y]);
9652 if (MovDelay[x][y] != 0)
9654 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9655 10 - MovDelay[x][y]);
9657 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9662 static void MauerWaechst(int x, int y)
9666 if (!MovDelay[x][y]) // next animation frame
9667 MovDelay[x][y] = 3 * delay;
9669 if (MovDelay[x][y]) // wait some time before next frame
9673 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9675 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9676 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9678 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9681 if (!MovDelay[x][y])
9683 if (MovDir[x][y] == MV_LEFT)
9685 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9686 TEST_DrawLevelField(x - 1, y);
9688 else if (MovDir[x][y] == MV_RIGHT)
9690 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9691 TEST_DrawLevelField(x + 1, y);
9693 else if (MovDir[x][y] == MV_UP)
9695 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9696 TEST_DrawLevelField(x, y - 1);
9700 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9701 TEST_DrawLevelField(x, y + 1);
9704 Tile[x][y] = Store[x][y];
9706 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9707 TEST_DrawLevelField(x, y);
9712 static void MauerAbleger(int ax, int ay)
9714 int element = Tile[ax][ay];
9715 int graphic = el2img(element);
9716 boolean oben_frei = FALSE, unten_frei = FALSE;
9717 boolean links_frei = FALSE, rechts_frei = FALSE;
9718 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9719 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9720 boolean new_wall = FALSE;
9722 if (IS_ANIMATED(graphic))
9723 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9725 if (!MovDelay[ax][ay]) // start building new wall
9726 MovDelay[ax][ay] = 6;
9728 if (MovDelay[ax][ay]) // wait some time before building new wall
9731 if (MovDelay[ax][ay])
9735 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9737 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9739 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9741 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9744 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9745 element == EL_EXPANDABLE_WALL_ANY)
9749 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9750 Store[ax][ay-1] = element;
9751 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9752 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9753 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9754 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9759 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9760 Store[ax][ay+1] = element;
9761 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9762 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9763 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9764 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9769 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9770 element == EL_EXPANDABLE_WALL_ANY ||
9771 element == EL_EXPANDABLE_WALL ||
9772 element == EL_BD_EXPANDABLE_WALL)
9776 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9777 Store[ax-1][ay] = element;
9778 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9779 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9780 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9781 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9787 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9788 Store[ax+1][ay] = element;
9789 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9790 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9791 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9792 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9797 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9798 TEST_DrawLevelField(ax, ay);
9800 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9802 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9803 unten_massiv = TRUE;
9804 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9805 links_massiv = TRUE;
9806 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9807 rechts_massiv = TRUE;
9809 if (((oben_massiv && unten_massiv) ||
9810 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9811 element == EL_EXPANDABLE_WALL) &&
9812 ((links_massiv && rechts_massiv) ||
9813 element == EL_EXPANDABLE_WALL_VERTICAL))
9814 Tile[ax][ay] = EL_WALL;
9817 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9820 static void MauerAblegerStahl(int ax, int ay)
9822 int element = Tile[ax][ay];
9823 int graphic = el2img(element);
9824 boolean oben_frei = FALSE, unten_frei = FALSE;
9825 boolean links_frei = FALSE, rechts_frei = FALSE;
9826 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9827 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9828 boolean new_wall = FALSE;
9830 if (IS_ANIMATED(graphic))
9831 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9833 if (!MovDelay[ax][ay]) // start building new wall
9834 MovDelay[ax][ay] = 6;
9836 if (MovDelay[ax][ay]) // wait some time before building new wall
9839 if (MovDelay[ax][ay])
9843 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9845 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9847 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9849 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9852 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9853 element == EL_EXPANDABLE_STEELWALL_ANY)
9857 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9858 Store[ax][ay-1] = element;
9859 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9860 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9861 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9862 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9867 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9868 Store[ax][ay+1] = element;
9869 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9870 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9871 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9872 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9877 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9878 element == EL_EXPANDABLE_STEELWALL_ANY)
9882 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9883 Store[ax-1][ay] = element;
9884 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9885 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9886 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9887 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9893 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9894 Store[ax+1][ay] = element;
9895 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9896 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9897 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9898 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9903 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9905 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9906 unten_massiv = TRUE;
9907 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9908 links_massiv = TRUE;
9909 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9910 rechts_massiv = TRUE;
9912 if (((oben_massiv && unten_massiv) ||
9913 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9914 ((links_massiv && rechts_massiv) ||
9915 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9916 Tile[ax][ay] = EL_STEELWALL;
9919 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9922 static void CheckForDragon(int x, int y)
9925 boolean dragon_found = FALSE;
9926 static int xy[4][2] =
9934 for (i = 0; i < NUM_DIRECTIONS; i++)
9936 for (j = 0; j < 4; j++)
9938 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9940 if (IN_LEV_FIELD(xx, yy) &&
9941 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9943 if (Tile[xx][yy] == EL_DRAGON)
9944 dragon_found = TRUE;
9953 for (i = 0; i < NUM_DIRECTIONS; i++)
9955 for (j = 0; j < 3; j++)
9957 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9959 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9961 Tile[xx][yy] = EL_EMPTY;
9962 TEST_DrawLevelField(xx, yy);
9971 static void InitBuggyBase(int x, int y)
9973 int element = Tile[x][y];
9974 int activating_delay = FRAMES_PER_SECOND / 4;
9977 (element == EL_SP_BUGGY_BASE ?
9978 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9979 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9981 element == EL_SP_BUGGY_BASE_ACTIVE ?
9982 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9985 static void WarnBuggyBase(int x, int y)
9988 static int xy[4][2] =
9996 for (i = 0; i < NUM_DIRECTIONS; i++)
9998 int xx = x + xy[i][0];
9999 int yy = y + xy[i][1];
10001 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10003 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10010 static void InitTrap(int x, int y)
10012 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10015 static void ActivateTrap(int x, int y)
10017 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10020 static void ChangeActiveTrap(int x, int y)
10022 int graphic = IMG_TRAP_ACTIVE;
10024 // if new animation frame was drawn, correct crumbled sand border
10025 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10026 TEST_DrawLevelFieldCrumbled(x, y);
10029 static int getSpecialActionElement(int element, int number, int base_element)
10031 return (element != EL_EMPTY ? element :
10032 number != -1 ? base_element + number - 1 :
10036 static int getModifiedActionNumber(int value_old, int operator, int operand,
10037 int value_min, int value_max)
10039 int value_new = (operator == CA_MODE_SET ? operand :
10040 operator == CA_MODE_ADD ? value_old + operand :
10041 operator == CA_MODE_SUBTRACT ? value_old - operand :
10042 operator == CA_MODE_MULTIPLY ? value_old * operand :
10043 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10044 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10047 return (value_new < value_min ? value_min :
10048 value_new > value_max ? value_max :
10052 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10054 struct ElementInfo *ei = &element_info[element];
10055 struct ElementChangeInfo *change = &ei->change_page[page];
10056 int target_element = change->target_element;
10057 int action_type = change->action_type;
10058 int action_mode = change->action_mode;
10059 int action_arg = change->action_arg;
10060 int action_element = change->action_element;
10063 if (!change->has_action)
10066 // ---------- determine action paramater values -----------------------------
10068 int level_time_value =
10069 (level.time > 0 ? TimeLeft :
10072 int action_arg_element_raw =
10073 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10074 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10075 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10076 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10077 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10078 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10079 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10081 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10083 int action_arg_direction =
10084 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10085 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10086 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10087 change->actual_trigger_side :
10088 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10089 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10092 int action_arg_number_min =
10093 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10096 int action_arg_number_max =
10097 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10098 action_type == CA_SET_LEVEL_GEMS ? 999 :
10099 action_type == CA_SET_LEVEL_TIME ? 9999 :
10100 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10101 action_type == CA_SET_CE_VALUE ? 9999 :
10102 action_type == CA_SET_CE_SCORE ? 9999 :
10105 int action_arg_number_reset =
10106 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10107 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10108 action_type == CA_SET_LEVEL_TIME ? level.time :
10109 action_type == CA_SET_LEVEL_SCORE ? 0 :
10110 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10111 action_type == CA_SET_CE_SCORE ? 0 :
10114 int action_arg_number =
10115 (action_arg <= CA_ARG_MAX ? action_arg :
10116 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10117 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10118 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10119 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10120 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10121 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10122 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10123 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10124 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10125 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10126 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10127 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10128 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10129 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10130 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10131 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10132 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10133 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10134 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10135 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10136 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10139 int action_arg_number_old =
10140 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10141 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10142 action_type == CA_SET_LEVEL_SCORE ? game.score :
10143 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10144 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10147 int action_arg_number_new =
10148 getModifiedActionNumber(action_arg_number_old,
10149 action_mode, action_arg_number,
10150 action_arg_number_min, action_arg_number_max);
10152 int trigger_player_bits =
10153 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10154 change->actual_trigger_player_bits : change->trigger_player);
10156 int action_arg_player_bits =
10157 (action_arg >= CA_ARG_PLAYER_1 &&
10158 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10159 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10160 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10163 // ---------- execute action -----------------------------------------------
10165 switch (action_type)
10172 // ---------- level actions ----------------------------------------------
10174 case CA_RESTART_LEVEL:
10176 game.restart_level = TRUE;
10181 case CA_SHOW_ENVELOPE:
10183 int element = getSpecialActionElement(action_arg_element,
10184 action_arg_number, EL_ENVELOPE_1);
10186 if (IS_ENVELOPE(element))
10187 local_player->show_envelope = element;
10192 case CA_SET_LEVEL_TIME:
10194 if (level.time > 0) // only modify limited time value
10196 TimeLeft = action_arg_number_new;
10198 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10200 DisplayGameControlValues();
10202 if (!TimeLeft && setup.time_limit)
10203 for (i = 0; i < MAX_PLAYERS; i++)
10204 KillPlayer(&stored_player[i]);
10210 case CA_SET_LEVEL_SCORE:
10212 game.score = action_arg_number_new;
10214 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10216 DisplayGameControlValues();
10221 case CA_SET_LEVEL_GEMS:
10223 game.gems_still_needed = action_arg_number_new;
10225 game.snapshot.collected_item = TRUE;
10227 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10229 DisplayGameControlValues();
10234 case CA_SET_LEVEL_WIND:
10236 game.wind_direction = action_arg_direction;
10241 case CA_SET_LEVEL_RANDOM_SEED:
10243 // ensure that setting a new random seed while playing is predictable
10244 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10249 // ---------- player actions ---------------------------------------------
10251 case CA_MOVE_PLAYER:
10252 case CA_MOVE_PLAYER_NEW:
10254 // automatically move to the next field in specified direction
10255 for (i = 0; i < MAX_PLAYERS; i++)
10256 if (trigger_player_bits & (1 << i))
10257 if (action_type == CA_MOVE_PLAYER ||
10258 stored_player[i].MovPos == 0)
10259 stored_player[i].programmed_action = action_arg_direction;
10264 case CA_EXIT_PLAYER:
10266 for (i = 0; i < MAX_PLAYERS; i++)
10267 if (action_arg_player_bits & (1 << i))
10268 ExitPlayer(&stored_player[i]);
10270 if (game.players_still_needed == 0)
10276 case CA_KILL_PLAYER:
10278 for (i = 0; i < MAX_PLAYERS; i++)
10279 if (action_arg_player_bits & (1 << i))
10280 KillPlayer(&stored_player[i]);
10285 case CA_SET_PLAYER_KEYS:
10287 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10288 int element = getSpecialActionElement(action_arg_element,
10289 action_arg_number, EL_KEY_1);
10291 if (IS_KEY(element))
10293 for (i = 0; i < MAX_PLAYERS; i++)
10295 if (trigger_player_bits & (1 << i))
10297 stored_player[i].key[KEY_NR(element)] = key_state;
10299 DrawGameDoorValues();
10307 case CA_SET_PLAYER_SPEED:
10309 for (i = 0; i < MAX_PLAYERS; i++)
10311 if (trigger_player_bits & (1 << i))
10313 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10315 if (action_arg == CA_ARG_SPEED_FASTER &&
10316 stored_player[i].cannot_move)
10318 action_arg_number = STEPSIZE_VERY_SLOW;
10320 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10321 action_arg == CA_ARG_SPEED_FASTER)
10323 action_arg_number = 2;
10324 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10327 else if (action_arg == CA_ARG_NUMBER_RESET)
10329 action_arg_number = level.initial_player_stepsize[i];
10333 getModifiedActionNumber(move_stepsize,
10336 action_arg_number_min,
10337 action_arg_number_max);
10339 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10346 case CA_SET_PLAYER_SHIELD:
10348 for (i = 0; i < MAX_PLAYERS; i++)
10350 if (trigger_player_bits & (1 << i))
10352 if (action_arg == CA_ARG_SHIELD_OFF)
10354 stored_player[i].shield_normal_time_left = 0;
10355 stored_player[i].shield_deadly_time_left = 0;
10357 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10359 stored_player[i].shield_normal_time_left = 999999;
10361 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10363 stored_player[i].shield_normal_time_left = 999999;
10364 stored_player[i].shield_deadly_time_left = 999999;
10372 case CA_SET_PLAYER_GRAVITY:
10374 for (i = 0; i < MAX_PLAYERS; i++)
10376 if (trigger_player_bits & (1 << i))
10378 stored_player[i].gravity =
10379 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10380 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10381 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10382 stored_player[i].gravity);
10389 case CA_SET_PLAYER_ARTWORK:
10391 for (i = 0; i < MAX_PLAYERS; i++)
10393 if (trigger_player_bits & (1 << i))
10395 int artwork_element = action_arg_element;
10397 if (action_arg == CA_ARG_ELEMENT_RESET)
10399 (level.use_artwork_element[i] ? level.artwork_element[i] :
10400 stored_player[i].element_nr);
10402 if (stored_player[i].artwork_element != artwork_element)
10403 stored_player[i].Frame = 0;
10405 stored_player[i].artwork_element = artwork_element;
10407 SetPlayerWaiting(&stored_player[i], FALSE);
10409 // set number of special actions for bored and sleeping animation
10410 stored_player[i].num_special_action_bored =
10411 get_num_special_action(artwork_element,
10412 ACTION_BORING_1, ACTION_BORING_LAST);
10413 stored_player[i].num_special_action_sleeping =
10414 get_num_special_action(artwork_element,
10415 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10422 case CA_SET_PLAYER_INVENTORY:
10424 for (i = 0; i < MAX_PLAYERS; i++)
10426 struct PlayerInfo *player = &stored_player[i];
10429 if (trigger_player_bits & (1 << i))
10431 int inventory_element = action_arg_element;
10433 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10434 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10435 action_arg == CA_ARG_ELEMENT_ACTION)
10437 int element = inventory_element;
10438 int collect_count = element_info[element].collect_count_initial;
10440 if (!IS_CUSTOM_ELEMENT(element))
10443 if (collect_count == 0)
10444 player->inventory_infinite_element = element;
10446 for (k = 0; k < collect_count; k++)
10447 if (player->inventory_size < MAX_INVENTORY_SIZE)
10448 player->inventory_element[player->inventory_size++] =
10451 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10452 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10453 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10455 if (player->inventory_infinite_element != EL_UNDEFINED &&
10456 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10457 action_arg_element_raw))
10458 player->inventory_infinite_element = EL_UNDEFINED;
10460 for (k = 0, j = 0; j < player->inventory_size; j++)
10462 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10463 action_arg_element_raw))
10464 player->inventory_element[k++] = player->inventory_element[j];
10467 player->inventory_size = k;
10469 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10471 if (player->inventory_size > 0)
10473 for (j = 0; j < player->inventory_size - 1; j++)
10474 player->inventory_element[j] = player->inventory_element[j + 1];
10476 player->inventory_size--;
10479 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10481 if (player->inventory_size > 0)
10482 player->inventory_size--;
10484 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10486 player->inventory_infinite_element = EL_UNDEFINED;
10487 player->inventory_size = 0;
10489 else if (action_arg == CA_ARG_INVENTORY_RESET)
10491 player->inventory_infinite_element = EL_UNDEFINED;
10492 player->inventory_size = 0;
10494 if (level.use_initial_inventory[i])
10496 for (j = 0; j < level.initial_inventory_size[i]; j++)
10498 int element = level.initial_inventory_content[i][j];
10499 int collect_count = element_info[element].collect_count_initial;
10501 if (!IS_CUSTOM_ELEMENT(element))
10504 if (collect_count == 0)
10505 player->inventory_infinite_element = element;
10507 for (k = 0; k < collect_count; k++)
10508 if (player->inventory_size < MAX_INVENTORY_SIZE)
10509 player->inventory_element[player->inventory_size++] =
10520 // ---------- CE actions -------------------------------------------------
10522 case CA_SET_CE_VALUE:
10524 int last_ce_value = CustomValue[x][y];
10526 CustomValue[x][y] = action_arg_number_new;
10528 if (CustomValue[x][y] != last_ce_value)
10530 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10531 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10533 if (CustomValue[x][y] == 0)
10535 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10536 ChangeCount[x][y] = 0; // allow at least one more change
10538 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10539 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10546 case CA_SET_CE_SCORE:
10548 int last_ce_score = ei->collect_score;
10550 ei->collect_score = action_arg_number_new;
10552 if (ei->collect_score != last_ce_score)
10554 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10555 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10557 if (ei->collect_score == 0)
10561 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10562 ChangeCount[x][y] = 0; // allow at least one more change
10564 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10565 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10568 This is a very special case that seems to be a mixture between
10569 CheckElementChange() and CheckTriggeredElementChange(): while
10570 the first one only affects single elements that are triggered
10571 directly, the second one affects multiple elements in the playfield
10572 that are triggered indirectly by another element. This is a third
10573 case: Changing the CE score always affects multiple identical CEs,
10574 so every affected CE must be checked, not only the single CE for
10575 which the CE score was changed in the first place (as every instance
10576 of that CE shares the same CE score, and therefore also can change)!
10578 SCAN_PLAYFIELD(xx, yy)
10580 if (Tile[xx][yy] == element)
10581 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10582 CE_SCORE_GETS_ZERO);
10590 case CA_SET_CE_ARTWORK:
10592 int artwork_element = action_arg_element;
10593 boolean reset_frame = FALSE;
10596 if (action_arg == CA_ARG_ELEMENT_RESET)
10597 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10600 if (ei->gfx_element != artwork_element)
10601 reset_frame = TRUE;
10603 ei->gfx_element = artwork_element;
10605 SCAN_PLAYFIELD(xx, yy)
10607 if (Tile[xx][yy] == element)
10611 ResetGfxAnimation(xx, yy);
10612 ResetRandomAnimationValue(xx, yy);
10615 TEST_DrawLevelField(xx, yy);
10622 // ---------- engine actions ---------------------------------------------
10624 case CA_SET_ENGINE_SCAN_MODE:
10626 InitPlayfieldScanMode(action_arg);
10636 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10638 int old_element = Tile[x][y];
10639 int new_element = GetElementFromGroupElement(element);
10640 int previous_move_direction = MovDir[x][y];
10641 int last_ce_value = CustomValue[x][y];
10642 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10643 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10644 boolean add_player_onto_element = (new_element_is_player &&
10645 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10646 IS_WALKABLE(old_element));
10648 if (!add_player_onto_element)
10650 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10651 RemoveMovingField(x, y);
10655 Tile[x][y] = new_element;
10657 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10658 MovDir[x][y] = previous_move_direction;
10660 if (element_info[new_element].use_last_ce_value)
10661 CustomValue[x][y] = last_ce_value;
10663 InitField_WithBug1(x, y, FALSE);
10665 new_element = Tile[x][y]; // element may have changed
10667 ResetGfxAnimation(x, y);
10668 ResetRandomAnimationValue(x, y);
10670 TEST_DrawLevelField(x, y);
10672 if (GFX_CRUMBLED(new_element))
10673 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10676 // check if element under the player changes from accessible to unaccessible
10677 // (needed for special case of dropping element which then changes)
10678 // (must be checked after creating new element for walkable group elements)
10679 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10680 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10687 // "ChangeCount" not set yet to allow "entered by player" change one time
10688 if (new_element_is_player)
10689 RelocatePlayer(x, y, new_element);
10692 ChangeCount[x][y]++; // count number of changes in the same frame
10694 TestIfBadThingTouchesPlayer(x, y);
10695 TestIfPlayerTouchesCustomElement(x, y);
10696 TestIfElementTouchesCustomElement(x, y);
10699 static void CreateField(int x, int y, int element)
10701 CreateFieldExt(x, y, element, FALSE);
10704 static void CreateElementFromChange(int x, int y, int element)
10706 element = GET_VALID_RUNTIME_ELEMENT(element);
10708 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10710 int old_element = Tile[x][y];
10712 // prevent changed element from moving in same engine frame
10713 // unless both old and new element can either fall or move
10714 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10715 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10719 CreateFieldExt(x, y, element, TRUE);
10722 static boolean ChangeElement(int x, int y, int element, int page)
10724 struct ElementInfo *ei = &element_info[element];
10725 struct ElementChangeInfo *change = &ei->change_page[page];
10726 int ce_value = CustomValue[x][y];
10727 int ce_score = ei->collect_score;
10728 int target_element;
10729 int old_element = Tile[x][y];
10731 // always use default change event to prevent running into a loop
10732 if (ChangeEvent[x][y] == -1)
10733 ChangeEvent[x][y] = CE_DELAY;
10735 if (ChangeEvent[x][y] == CE_DELAY)
10737 // reset actual trigger element, trigger player and action element
10738 change->actual_trigger_element = EL_EMPTY;
10739 change->actual_trigger_player = EL_EMPTY;
10740 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10741 change->actual_trigger_side = CH_SIDE_NONE;
10742 change->actual_trigger_ce_value = 0;
10743 change->actual_trigger_ce_score = 0;
10746 // do not change elements more than a specified maximum number of changes
10747 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10750 ChangeCount[x][y]++; // count number of changes in the same frame
10752 if (change->explode)
10759 if (change->use_target_content)
10761 boolean complete_replace = TRUE;
10762 boolean can_replace[3][3];
10765 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10768 boolean is_walkable;
10769 boolean is_diggable;
10770 boolean is_collectible;
10771 boolean is_removable;
10772 boolean is_destructible;
10773 int ex = x + xx - 1;
10774 int ey = y + yy - 1;
10775 int content_element = change->target_content.e[xx][yy];
10778 can_replace[xx][yy] = TRUE;
10780 if (ex == x && ey == y) // do not check changing element itself
10783 if (content_element == EL_EMPTY_SPACE)
10785 can_replace[xx][yy] = FALSE; // do not replace border with space
10790 if (!IN_LEV_FIELD(ex, ey))
10792 can_replace[xx][yy] = FALSE;
10793 complete_replace = FALSE;
10800 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10801 e = MovingOrBlocked2Element(ex, ey);
10803 is_empty = (IS_FREE(ex, ey) ||
10804 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10806 is_walkable = (is_empty || IS_WALKABLE(e));
10807 is_diggable = (is_empty || IS_DIGGABLE(e));
10808 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10809 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10810 is_removable = (is_diggable || is_collectible);
10812 can_replace[xx][yy] =
10813 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10814 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10815 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10816 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10817 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10818 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10819 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10821 if (!can_replace[xx][yy])
10822 complete_replace = FALSE;
10825 if (!change->only_if_complete || complete_replace)
10827 boolean something_has_changed = FALSE;
10829 if (change->only_if_complete && change->use_random_replace &&
10830 RND(100) < change->random_percentage)
10833 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10835 int ex = x + xx - 1;
10836 int ey = y + yy - 1;
10837 int content_element;
10839 if (can_replace[xx][yy] && (!change->use_random_replace ||
10840 RND(100) < change->random_percentage))
10842 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10843 RemoveMovingField(ex, ey);
10845 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10847 content_element = change->target_content.e[xx][yy];
10848 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10849 ce_value, ce_score);
10851 CreateElementFromChange(ex, ey, target_element);
10853 something_has_changed = TRUE;
10855 // for symmetry reasons, freeze newly created border elements
10856 if (ex != x || ey != y)
10857 Stop[ex][ey] = TRUE; // no more moving in this frame
10861 if (something_has_changed)
10863 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10864 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10870 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10871 ce_value, ce_score);
10873 if (element == EL_DIAGONAL_GROWING ||
10874 element == EL_DIAGONAL_SHRINKING)
10876 target_element = Store[x][y];
10878 Store[x][y] = EL_EMPTY;
10881 // special case: element changes to player (and may be kept if walkable)
10882 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10883 CreateElementFromChange(x, y, EL_EMPTY);
10885 CreateElementFromChange(x, y, target_element);
10887 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10888 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10891 // this uses direct change before indirect change
10892 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10897 static void HandleElementChange(int x, int y, int page)
10899 int element = MovingOrBlocked2Element(x, y);
10900 struct ElementInfo *ei = &element_info[element];
10901 struct ElementChangeInfo *change = &ei->change_page[page];
10902 boolean handle_action_before_change = FALSE;
10905 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10906 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10908 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10909 x, y, element, element_info[element].token_name);
10910 Debug("game:playing:HandleElementChange", "This should never happen!");
10914 // this can happen with classic bombs on walkable, changing elements
10915 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10920 if (ChangeDelay[x][y] == 0) // initialize element change
10922 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10924 if (change->can_change)
10926 // !!! not clear why graphic animation should be reset at all here !!!
10927 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10928 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10931 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10933 When using an animation frame delay of 1 (this only happens with
10934 "sp_zonk.moving.left/right" in the classic graphics), the default
10935 (non-moving) animation shows wrong animation frames (while the
10936 moving animation, like "sp_zonk.moving.left/right", is correct,
10937 so this graphical bug never shows up with the classic graphics).
10938 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10939 be drawn instead of the correct frames 0,1,2,3. This is caused by
10940 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10941 an element change: First when the change delay ("ChangeDelay[][]")
10942 counter has reached zero after decrementing, then a second time in
10943 the next frame (after "GfxFrame[][]" was already incremented) when
10944 "ChangeDelay[][]" is reset to the initial delay value again.
10946 This causes frame 0 to be drawn twice, while the last frame won't
10947 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10949 As some animations may already be cleverly designed around this bug
10950 (at least the "Snake Bite" snake tail animation does this), it cannot
10951 simply be fixed here without breaking such existing animations.
10952 Unfortunately, it cannot easily be detected if a graphics set was
10953 designed "before" or "after" the bug was fixed. As a workaround,
10954 a new graphics set option "game.graphics_engine_version" was added
10955 to be able to specify the game's major release version for which the
10956 graphics set was designed, which can then be used to decide if the
10957 bugfix should be used (version 4 and above) or not (version 3 or
10958 below, or if no version was specified at all, as with old sets).
10960 (The wrong/fixed animation frames can be tested with the test level set
10961 "test_gfxframe" and level "000", which contains a specially prepared
10962 custom element at level position (x/y) == (11/9) which uses the zonk
10963 animation mentioned above. Using "game.graphics_engine_version: 4"
10964 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10965 This can also be seen from the debug output for this test element.)
10968 // when a custom element is about to change (for example by change delay),
10969 // do not reset graphic animation when the custom element is moving
10970 if (game.graphics_engine_version < 4 &&
10973 ResetGfxAnimation(x, y);
10974 ResetRandomAnimationValue(x, y);
10977 if (change->pre_change_function)
10978 change->pre_change_function(x, y);
10982 ChangeDelay[x][y]--;
10984 if (ChangeDelay[x][y] != 0) // continue element change
10986 if (change->can_change)
10988 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10990 if (IS_ANIMATED(graphic))
10991 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10993 if (change->change_function)
10994 change->change_function(x, y);
10997 else // finish element change
10999 if (ChangePage[x][y] != -1) // remember page from delayed change
11001 page = ChangePage[x][y];
11002 ChangePage[x][y] = -1;
11004 change = &ei->change_page[page];
11007 if (IS_MOVING(x, y)) // never change a running system ;-)
11009 ChangeDelay[x][y] = 1; // try change after next move step
11010 ChangePage[x][y] = page; // remember page to use for change
11015 // special case: set new level random seed before changing element
11016 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11017 handle_action_before_change = TRUE;
11019 if (change->has_action && handle_action_before_change)
11020 ExecuteCustomElementAction(x, y, element, page);
11022 if (change->can_change)
11024 if (ChangeElement(x, y, element, page))
11026 if (change->post_change_function)
11027 change->post_change_function(x, y);
11031 if (change->has_action && !handle_action_before_change)
11032 ExecuteCustomElementAction(x, y, element, page);
11036 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11037 int trigger_element,
11039 int trigger_player,
11043 boolean change_done_any = FALSE;
11044 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11047 if (!(trigger_events[trigger_element][trigger_event]))
11050 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11052 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11054 int element = EL_CUSTOM_START + i;
11055 boolean change_done = FALSE;
11058 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11059 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11062 for (p = 0; p < element_info[element].num_change_pages; p++)
11064 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11066 if (change->can_change_or_has_action &&
11067 change->has_event[trigger_event] &&
11068 change->trigger_side & trigger_side &&
11069 change->trigger_player & trigger_player &&
11070 change->trigger_page & trigger_page_bits &&
11071 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11073 change->actual_trigger_element = trigger_element;
11074 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11075 change->actual_trigger_player_bits = trigger_player;
11076 change->actual_trigger_side = trigger_side;
11077 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11078 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11080 if ((change->can_change && !change_done) || change->has_action)
11084 SCAN_PLAYFIELD(x, y)
11086 if (Tile[x][y] == element)
11088 if (change->can_change && !change_done)
11090 // if element already changed in this frame, not only prevent
11091 // another element change (checked in ChangeElement()), but
11092 // also prevent additional element actions for this element
11094 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11095 !level.use_action_after_change_bug)
11098 ChangeDelay[x][y] = 1;
11099 ChangeEvent[x][y] = trigger_event;
11101 HandleElementChange(x, y, p);
11103 else if (change->has_action)
11105 // if element already changed in this frame, not only prevent
11106 // another element change (checked in ChangeElement()), but
11107 // also prevent additional element actions for this element
11109 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11110 !level.use_action_after_change_bug)
11113 ExecuteCustomElementAction(x, y, element, p);
11114 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11119 if (change->can_change)
11121 change_done = TRUE;
11122 change_done_any = TRUE;
11129 RECURSION_LOOP_DETECTION_END();
11131 return change_done_any;
11134 static boolean CheckElementChangeExt(int x, int y,
11136 int trigger_element,
11138 int trigger_player,
11141 boolean change_done = FALSE;
11144 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11145 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11148 if (Tile[x][y] == EL_BLOCKED)
11150 Blocked2Moving(x, y, &x, &y);
11151 element = Tile[x][y];
11154 // check if element has already changed or is about to change after moving
11155 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11156 Tile[x][y] != element) ||
11158 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11159 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11160 ChangePage[x][y] != -1)))
11163 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11165 for (p = 0; p < element_info[element].num_change_pages; p++)
11167 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11169 /* check trigger element for all events where the element that is checked
11170 for changing interacts with a directly adjacent element -- this is
11171 different to element changes that affect other elements to change on the
11172 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11173 boolean check_trigger_element =
11174 (trigger_event == CE_NEXT_TO_X ||
11175 trigger_event == CE_TOUCHING_X ||
11176 trigger_event == CE_HITTING_X ||
11177 trigger_event == CE_HIT_BY_X ||
11178 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11180 if (change->can_change_or_has_action &&
11181 change->has_event[trigger_event] &&
11182 change->trigger_side & trigger_side &&
11183 change->trigger_player & trigger_player &&
11184 (!check_trigger_element ||
11185 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11187 change->actual_trigger_element = trigger_element;
11188 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11189 change->actual_trigger_player_bits = trigger_player;
11190 change->actual_trigger_side = trigger_side;
11191 change->actual_trigger_ce_value = CustomValue[x][y];
11192 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11194 // special case: trigger element not at (x,y) position for some events
11195 if (check_trigger_element)
11207 { 0, 0 }, { 0, 0 }, { 0, 0 },
11211 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11212 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11214 change->actual_trigger_ce_value = CustomValue[xx][yy];
11215 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11218 if (change->can_change && !change_done)
11220 ChangeDelay[x][y] = 1;
11221 ChangeEvent[x][y] = trigger_event;
11223 HandleElementChange(x, y, p);
11225 change_done = TRUE;
11227 else if (change->has_action)
11229 ExecuteCustomElementAction(x, y, element, p);
11230 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11235 RECURSION_LOOP_DETECTION_END();
11237 return change_done;
11240 static void PlayPlayerSound(struct PlayerInfo *player)
11242 int jx = player->jx, jy = player->jy;
11243 int sound_element = player->artwork_element;
11244 int last_action = player->last_action_waiting;
11245 int action = player->action_waiting;
11247 if (player->is_waiting)
11249 if (action != last_action)
11250 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11252 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11256 if (action != last_action)
11257 StopSound(element_info[sound_element].sound[last_action]);
11259 if (last_action == ACTION_SLEEPING)
11260 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11264 static void PlayAllPlayersSound(void)
11268 for (i = 0; i < MAX_PLAYERS; i++)
11269 if (stored_player[i].active)
11270 PlayPlayerSound(&stored_player[i]);
11273 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11275 boolean last_waiting = player->is_waiting;
11276 int move_dir = player->MovDir;
11278 player->dir_waiting = move_dir;
11279 player->last_action_waiting = player->action_waiting;
11283 if (!last_waiting) // not waiting -> waiting
11285 player->is_waiting = TRUE;
11287 player->frame_counter_bored =
11289 game.player_boring_delay_fixed +
11290 GetSimpleRandom(game.player_boring_delay_random);
11291 player->frame_counter_sleeping =
11293 game.player_sleeping_delay_fixed +
11294 GetSimpleRandom(game.player_sleeping_delay_random);
11296 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11299 if (game.player_sleeping_delay_fixed +
11300 game.player_sleeping_delay_random > 0 &&
11301 player->anim_delay_counter == 0 &&
11302 player->post_delay_counter == 0 &&
11303 FrameCounter >= player->frame_counter_sleeping)
11304 player->is_sleeping = TRUE;
11305 else if (game.player_boring_delay_fixed +
11306 game.player_boring_delay_random > 0 &&
11307 FrameCounter >= player->frame_counter_bored)
11308 player->is_bored = TRUE;
11310 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11311 player->is_bored ? ACTION_BORING :
11314 if (player->is_sleeping && player->use_murphy)
11316 // special case for sleeping Murphy when leaning against non-free tile
11318 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11319 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11320 !IS_MOVING(player->jx - 1, player->jy)))
11321 move_dir = MV_LEFT;
11322 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11323 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11324 !IS_MOVING(player->jx + 1, player->jy)))
11325 move_dir = MV_RIGHT;
11327 player->is_sleeping = FALSE;
11329 player->dir_waiting = move_dir;
11332 if (player->is_sleeping)
11334 if (player->num_special_action_sleeping > 0)
11336 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11338 int last_special_action = player->special_action_sleeping;
11339 int num_special_action = player->num_special_action_sleeping;
11340 int special_action =
11341 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11342 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11343 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11344 last_special_action + 1 : ACTION_SLEEPING);
11345 int special_graphic =
11346 el_act_dir2img(player->artwork_element, special_action, move_dir);
11348 player->anim_delay_counter =
11349 graphic_info[special_graphic].anim_delay_fixed +
11350 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11351 player->post_delay_counter =
11352 graphic_info[special_graphic].post_delay_fixed +
11353 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11355 player->special_action_sleeping = special_action;
11358 if (player->anim_delay_counter > 0)
11360 player->action_waiting = player->special_action_sleeping;
11361 player->anim_delay_counter--;
11363 else if (player->post_delay_counter > 0)
11365 player->post_delay_counter--;
11369 else if (player->is_bored)
11371 if (player->num_special_action_bored > 0)
11373 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11375 int special_action =
11376 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11377 int special_graphic =
11378 el_act_dir2img(player->artwork_element, special_action, move_dir);
11380 player->anim_delay_counter =
11381 graphic_info[special_graphic].anim_delay_fixed +
11382 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11383 player->post_delay_counter =
11384 graphic_info[special_graphic].post_delay_fixed +
11385 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11387 player->special_action_bored = special_action;
11390 if (player->anim_delay_counter > 0)
11392 player->action_waiting = player->special_action_bored;
11393 player->anim_delay_counter--;
11395 else if (player->post_delay_counter > 0)
11397 player->post_delay_counter--;
11402 else if (last_waiting) // waiting -> not waiting
11404 player->is_waiting = FALSE;
11405 player->is_bored = FALSE;
11406 player->is_sleeping = FALSE;
11408 player->frame_counter_bored = -1;
11409 player->frame_counter_sleeping = -1;
11411 player->anim_delay_counter = 0;
11412 player->post_delay_counter = 0;
11414 player->dir_waiting = player->MovDir;
11415 player->action_waiting = ACTION_DEFAULT;
11417 player->special_action_bored = ACTION_DEFAULT;
11418 player->special_action_sleeping = ACTION_DEFAULT;
11422 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11424 if ((!player->is_moving && player->was_moving) ||
11425 (player->MovPos == 0 && player->was_moving) ||
11426 (player->is_snapping && !player->was_snapping) ||
11427 (player->is_dropping && !player->was_dropping))
11429 if (!CheckSaveEngineSnapshotToList())
11432 player->was_moving = FALSE;
11433 player->was_snapping = TRUE;
11434 player->was_dropping = TRUE;
11438 if (player->is_moving)
11439 player->was_moving = TRUE;
11441 if (!player->is_snapping)
11442 player->was_snapping = FALSE;
11444 if (!player->is_dropping)
11445 player->was_dropping = FALSE;
11448 static struct MouseActionInfo mouse_action_last = { 0 };
11449 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11450 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11453 CheckSaveEngineSnapshotToList();
11455 mouse_action_last = mouse_action;
11458 static void CheckSingleStepMode(struct PlayerInfo *player)
11460 if (tape.single_step && tape.recording && !tape.pausing)
11462 // as it is called "single step mode", just return to pause mode when the
11463 // player stopped moving after one tile (or never starts moving at all)
11464 // (reverse logic needed here in case single step mode used in team mode)
11465 if (player->is_moving ||
11466 player->is_pushing ||
11467 player->is_dropping_pressed ||
11468 player->effective_mouse_action.button)
11469 game.enter_single_step_mode = FALSE;
11472 CheckSaveEngineSnapshot(player);
11475 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11477 int left = player_action & JOY_LEFT;
11478 int right = player_action & JOY_RIGHT;
11479 int up = player_action & JOY_UP;
11480 int down = player_action & JOY_DOWN;
11481 int button1 = player_action & JOY_BUTTON_1;
11482 int button2 = player_action & JOY_BUTTON_2;
11483 int dx = (left ? -1 : right ? 1 : 0);
11484 int dy = (up ? -1 : down ? 1 : 0);
11486 if (!player->active || tape.pausing)
11492 SnapField(player, dx, dy);
11496 DropElement(player);
11498 MovePlayer(player, dx, dy);
11501 CheckSingleStepMode(player);
11503 SetPlayerWaiting(player, FALSE);
11505 return player_action;
11509 // no actions for this player (no input at player's configured device)
11511 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11512 SnapField(player, 0, 0);
11513 CheckGravityMovementWhenNotMoving(player);
11515 if (player->MovPos == 0)
11516 SetPlayerWaiting(player, TRUE);
11518 if (player->MovPos == 0) // needed for tape.playing
11519 player->is_moving = FALSE;
11521 player->is_dropping = FALSE;
11522 player->is_dropping_pressed = FALSE;
11523 player->drop_pressed_delay = 0;
11525 CheckSingleStepMode(player);
11531 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11534 if (!tape.use_mouse_actions)
11537 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11538 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11539 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11542 static void SetTapeActionFromMouseAction(byte *tape_action,
11543 struct MouseActionInfo *mouse_action)
11545 if (!tape.use_mouse_actions)
11548 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11549 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11550 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11553 static void CheckLevelSolved(void)
11555 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11557 if (game_em.level_solved &&
11558 !game_em.game_over) // game won
11562 game_em.game_over = TRUE;
11564 game.all_players_gone = TRUE;
11567 if (game_em.game_over) // game lost
11568 game.all_players_gone = TRUE;
11570 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11572 if (game_sp.level_solved &&
11573 !game_sp.game_over) // game won
11577 game_sp.game_over = TRUE;
11579 game.all_players_gone = TRUE;
11582 if (game_sp.game_over) // game lost
11583 game.all_players_gone = TRUE;
11585 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11587 if (game_mm.level_solved &&
11588 !game_mm.game_over) // game won
11592 game_mm.game_over = TRUE;
11594 game.all_players_gone = TRUE;
11597 if (game_mm.game_over) // game lost
11598 game.all_players_gone = TRUE;
11602 static void CheckLevelTime_StepCounter(void)
11612 if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved)
11613 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11615 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11617 DisplayGameControlValues();
11619 if (!TimeLeft && setup.time_limit && !game.LevelSolved)
11620 for (i = 0; i < MAX_PLAYERS; i++)
11621 KillPlayer(&stored_player[i]);
11623 else if (game.no_time_limit && !game.all_players_gone)
11625 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11627 DisplayGameControlValues();
11631 static void CheckLevelTime(void)
11635 if (TimeFrames >= FRAMES_PER_SECOND)
11640 for (i = 0; i < MAX_PLAYERS; i++)
11642 struct PlayerInfo *player = &stored_player[i];
11644 if (SHIELD_ON(player))
11646 player->shield_normal_time_left--;
11648 if (player->shield_deadly_time_left > 0)
11649 player->shield_deadly_time_left--;
11653 if (!game.LevelSolved && !level.use_step_counter)
11661 if (TimeLeft <= 10 && setup.time_limit)
11662 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11664 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11665 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11667 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11669 if (!TimeLeft && setup.time_limit)
11671 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11672 game_em.lev->killed_out_of_time = TRUE;
11674 for (i = 0; i < MAX_PLAYERS; i++)
11675 KillPlayer(&stored_player[i]);
11678 else if (game.no_time_limit && !game.all_players_gone)
11680 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11683 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11686 if (tape.recording || tape.playing)
11687 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11690 if (tape.recording || tape.playing)
11691 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11693 UpdateAndDisplayGameControlValues();
11696 void AdvanceFrameAndPlayerCounters(int player_nr)
11700 // advance frame counters (global frame counter and time frame counter)
11704 // advance player counters (counters for move delay, move animation etc.)
11705 for (i = 0; i < MAX_PLAYERS; i++)
11707 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11708 int move_delay_value = stored_player[i].move_delay_value;
11709 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11711 if (!advance_player_counters) // not all players may be affected
11714 if (move_frames == 0) // less than one move per game frame
11716 int stepsize = TILEX / move_delay_value;
11717 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11718 int count = (stored_player[i].is_moving ?
11719 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11721 if (count % delay == 0)
11725 stored_player[i].Frame += move_frames;
11727 if (stored_player[i].MovPos != 0)
11728 stored_player[i].StepFrame += move_frames;
11730 if (stored_player[i].move_delay > 0)
11731 stored_player[i].move_delay--;
11733 // due to bugs in previous versions, counter must count up, not down
11734 if (stored_player[i].push_delay != -1)
11735 stored_player[i].push_delay++;
11737 if (stored_player[i].drop_delay > 0)
11738 stored_player[i].drop_delay--;
11740 if (stored_player[i].is_dropping_pressed)
11741 stored_player[i].drop_pressed_delay++;
11745 void StartGameActions(boolean init_network_game, boolean record_tape,
11748 unsigned int new_random_seed = InitRND(random_seed);
11751 TapeStartRecording(new_random_seed);
11753 if (init_network_game)
11755 SendToServer_LevelFile();
11756 SendToServer_StartPlaying();
11764 static void GameActionsExt(void)
11767 static unsigned int game_frame_delay = 0;
11769 unsigned int game_frame_delay_value;
11770 byte *recorded_player_action;
11771 byte summarized_player_action = 0;
11772 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11775 // detect endless loops, caused by custom element programming
11776 if (recursion_loop_detected && recursion_loop_depth == 0)
11778 char *message = getStringCat3("Internal Error! Element ",
11779 EL_NAME(recursion_loop_element),
11780 " caused endless loop! Quit the game?");
11782 Warn("element '%s' caused endless loop in game engine",
11783 EL_NAME(recursion_loop_element));
11785 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11787 recursion_loop_detected = FALSE; // if game should be continued
11794 if (game.restart_level)
11795 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11797 CheckLevelSolved();
11799 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11802 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11805 if (game_status != GAME_MODE_PLAYING) // status might have changed
11808 game_frame_delay_value =
11809 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11811 if (tape.playing && tape.warp_forward && !tape.pausing)
11812 game_frame_delay_value = 0;
11814 SetVideoFrameDelay(game_frame_delay_value);
11816 // (de)activate virtual buttons depending on current game status
11817 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11819 if (game.all_players_gone) // if no players there to be controlled anymore
11820 SetOverlayActive(FALSE);
11821 else if (!tape.playing) // if game continues after tape stopped playing
11822 SetOverlayActive(TRUE);
11827 // ---------- main game synchronization point ----------
11829 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11831 Debug("game:playing:skip", "skip == %d", skip);
11834 // ---------- main game synchronization point ----------
11836 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11840 if (network_playing && !network_player_action_received)
11842 // try to get network player actions in time
11844 // last chance to get network player actions without main loop delay
11845 HandleNetworking();
11847 // game was quit by network peer
11848 if (game_status != GAME_MODE_PLAYING)
11851 // check if network player actions still missing and game still running
11852 if (!network_player_action_received && !checkGameEnded())
11853 return; // failed to get network player actions in time
11855 // do not yet reset "network_player_action_received" (for tape.pausing)
11861 // at this point we know that we really continue executing the game
11863 network_player_action_received = FALSE;
11865 // when playing tape, read previously recorded player input from tape data
11866 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11868 local_player->effective_mouse_action = local_player->mouse_action;
11870 if (recorded_player_action != NULL)
11871 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11872 recorded_player_action);
11874 // TapePlayAction() may return NULL when toggling to "pause before death"
11878 if (tape.set_centered_player)
11880 game.centered_player_nr_next = tape.centered_player_nr_next;
11881 game.set_centered_player = TRUE;
11884 for (i = 0; i < MAX_PLAYERS; i++)
11886 summarized_player_action |= stored_player[i].action;
11888 if (!network_playing && (game.team_mode || tape.playing))
11889 stored_player[i].effective_action = stored_player[i].action;
11892 if (network_playing && !checkGameEnded())
11893 SendToServer_MovePlayer(summarized_player_action);
11895 // summarize all actions at local players mapped input device position
11896 // (this allows using different input devices in single player mode)
11897 if (!network.enabled && !game.team_mode)
11898 stored_player[map_player_action[local_player->index_nr]].effective_action =
11899 summarized_player_action;
11901 // summarize all actions at centered player in local team mode
11902 if (tape.recording &&
11903 setup.team_mode && !network.enabled &&
11904 setup.input_on_focus &&
11905 game.centered_player_nr != -1)
11907 for (i = 0; i < MAX_PLAYERS; i++)
11908 stored_player[map_player_action[i]].effective_action =
11909 (i == game.centered_player_nr ? summarized_player_action : 0);
11912 if (recorded_player_action != NULL)
11913 for (i = 0; i < MAX_PLAYERS; i++)
11914 stored_player[i].effective_action = recorded_player_action[i];
11916 for (i = 0; i < MAX_PLAYERS; i++)
11918 tape_action[i] = stored_player[i].effective_action;
11920 /* (this may happen in the RND game engine if a player was not present on
11921 the playfield on level start, but appeared later from a custom element */
11922 if (setup.team_mode &&
11925 !tape.player_participates[i])
11926 tape.player_participates[i] = TRUE;
11929 SetTapeActionFromMouseAction(tape_action,
11930 &local_player->effective_mouse_action);
11932 // only record actions from input devices, but not programmed actions
11933 if (tape.recording)
11934 TapeRecordAction(tape_action);
11936 // remember if game was played (especially after tape stopped playing)
11937 if (!tape.playing && summarized_player_action)
11938 game.GamePlayed = TRUE;
11940 #if USE_NEW_PLAYER_ASSIGNMENTS
11941 // !!! also map player actions in single player mode !!!
11942 // if (game.team_mode)
11945 byte mapped_action[MAX_PLAYERS];
11947 #if DEBUG_PLAYER_ACTIONS
11948 for (i = 0; i < MAX_PLAYERS; i++)
11949 DebugContinued("", "%d, ", stored_player[i].effective_action);
11952 for (i = 0; i < MAX_PLAYERS; i++)
11953 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11955 for (i = 0; i < MAX_PLAYERS; i++)
11956 stored_player[i].effective_action = mapped_action[i];
11958 #if DEBUG_PLAYER_ACTIONS
11959 DebugContinued("", "=> ");
11960 for (i = 0; i < MAX_PLAYERS; i++)
11961 DebugContinued("", "%d, ", stored_player[i].effective_action);
11962 DebugContinued("game:playing:player", "\n");
11965 #if DEBUG_PLAYER_ACTIONS
11968 for (i = 0; i < MAX_PLAYERS; i++)
11969 DebugContinued("", "%d, ", stored_player[i].effective_action);
11970 DebugContinued("game:playing:player", "\n");
11975 for (i = 0; i < MAX_PLAYERS; i++)
11977 // allow engine snapshot in case of changed movement attempt
11978 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11979 (stored_player[i].effective_action & KEY_MOTION))
11980 game.snapshot.changed_action = TRUE;
11982 // allow engine snapshot in case of snapping/dropping attempt
11983 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11984 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11985 game.snapshot.changed_action = TRUE;
11987 game.snapshot.last_action[i] = stored_player[i].effective_action;
11990 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11992 GameActions_EM_Main();
11994 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11996 GameActions_SP_Main();
11998 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12000 GameActions_MM_Main();
12004 GameActions_RND_Main();
12007 BlitScreenToBitmap(backbuffer);
12009 CheckLevelSolved();
12012 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12014 if (global.show_frames_per_second)
12016 static unsigned int fps_counter = 0;
12017 static int fps_frames = 0;
12018 unsigned int fps_delay_ms = Counter() - fps_counter;
12022 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12024 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12027 fps_counter = Counter();
12029 // always draw FPS to screen after FPS value was updated
12030 redraw_mask |= REDRAW_FPS;
12033 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12034 if (GetDrawDeactivationMask() == REDRAW_NONE)
12035 redraw_mask |= REDRAW_FPS;
12039 static void GameActions_CheckSaveEngineSnapshot(void)
12041 if (!game.snapshot.save_snapshot)
12044 // clear flag for saving snapshot _before_ saving snapshot
12045 game.snapshot.save_snapshot = FALSE;
12047 SaveEngineSnapshotToList();
12050 void GameActions(void)
12054 GameActions_CheckSaveEngineSnapshot();
12057 void GameActions_EM_Main(void)
12059 byte effective_action[MAX_PLAYERS];
12060 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12063 for (i = 0; i < MAX_PLAYERS; i++)
12064 effective_action[i] = stored_player[i].effective_action;
12066 GameActions_EM(effective_action, warp_mode);
12069 void GameActions_SP_Main(void)
12071 byte effective_action[MAX_PLAYERS];
12072 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12075 for (i = 0; i < MAX_PLAYERS; i++)
12076 effective_action[i] = stored_player[i].effective_action;
12078 GameActions_SP(effective_action, warp_mode);
12080 for (i = 0; i < MAX_PLAYERS; i++)
12082 if (stored_player[i].force_dropping)
12083 stored_player[i].action |= KEY_BUTTON_DROP;
12085 stored_player[i].force_dropping = FALSE;
12089 void GameActions_MM_Main(void)
12091 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12093 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12096 void GameActions_RND_Main(void)
12101 void GameActions_RND(void)
12103 static struct MouseActionInfo mouse_action_last = { 0 };
12104 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12105 int magic_wall_x = 0, magic_wall_y = 0;
12106 int i, x, y, element, graphic, last_gfx_frame;
12108 InitPlayfieldScanModeVars();
12110 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12112 SCAN_PLAYFIELD(x, y)
12114 ChangeCount[x][y] = 0;
12115 ChangeEvent[x][y] = -1;
12119 if (game.set_centered_player)
12121 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12123 // switching to "all players" only possible if all players fit to screen
12124 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12126 game.centered_player_nr_next = game.centered_player_nr;
12127 game.set_centered_player = FALSE;
12130 // do not switch focus to non-existing (or non-active) player
12131 if (game.centered_player_nr_next >= 0 &&
12132 !stored_player[game.centered_player_nr_next].active)
12134 game.centered_player_nr_next = game.centered_player_nr;
12135 game.set_centered_player = FALSE;
12139 if (game.set_centered_player &&
12140 ScreenMovPos == 0) // screen currently aligned at tile position
12144 if (game.centered_player_nr_next == -1)
12146 setScreenCenteredToAllPlayers(&sx, &sy);
12150 sx = stored_player[game.centered_player_nr_next].jx;
12151 sy = stored_player[game.centered_player_nr_next].jy;
12154 game.centered_player_nr = game.centered_player_nr_next;
12155 game.set_centered_player = FALSE;
12157 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12158 DrawGameDoorValues();
12161 // check single step mode (set flag and clear again if any player is active)
12162 game.enter_single_step_mode =
12163 (tape.single_step && tape.recording && !tape.pausing);
12165 for (i = 0; i < MAX_PLAYERS; i++)
12167 int actual_player_action = stored_player[i].effective_action;
12170 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12171 - rnd_equinox_tetrachloride 048
12172 - rnd_equinox_tetrachloride_ii 096
12173 - rnd_emanuel_schmieg 002
12174 - doctor_sloan_ww 001, 020
12176 if (stored_player[i].MovPos == 0)
12177 CheckGravityMovement(&stored_player[i]);
12180 // overwrite programmed action with tape action
12181 if (stored_player[i].programmed_action)
12182 actual_player_action = stored_player[i].programmed_action;
12184 PlayerActions(&stored_player[i], actual_player_action);
12186 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12189 // single step pause mode may already have been toggled by "ScrollPlayer()"
12190 if (game.enter_single_step_mode && !tape.pausing)
12191 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12193 ScrollScreen(NULL, SCROLL_GO_ON);
12195 /* for backwards compatibility, the following code emulates a fixed bug that
12196 occured when pushing elements (causing elements that just made their last
12197 pushing step to already (if possible) make their first falling step in the
12198 same game frame, which is bad); this code is also needed to use the famous
12199 "spring push bug" which is used in older levels and might be wanted to be
12200 used also in newer levels, but in this case the buggy pushing code is only
12201 affecting the "spring" element and no other elements */
12203 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12205 for (i = 0; i < MAX_PLAYERS; i++)
12207 struct PlayerInfo *player = &stored_player[i];
12208 int x = player->jx;
12209 int y = player->jy;
12211 if (player->active && player->is_pushing && player->is_moving &&
12213 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12214 Tile[x][y] == EL_SPRING))
12216 ContinueMoving(x, y);
12218 // continue moving after pushing (this is actually a bug)
12219 if (!IS_MOVING(x, y))
12220 Stop[x][y] = FALSE;
12225 SCAN_PLAYFIELD(x, y)
12227 Last[x][y] = Tile[x][y];
12229 ChangeCount[x][y] = 0;
12230 ChangeEvent[x][y] = -1;
12232 // this must be handled before main playfield loop
12233 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12236 if (MovDelay[x][y] <= 0)
12240 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12243 if (MovDelay[x][y] <= 0)
12245 int element = Store[x][y];
12246 int move_direction = MovDir[x][y];
12247 int player_index_bit = Store2[x][y];
12253 TEST_DrawLevelField(x, y);
12255 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12257 if (IS_ENVELOPE(element))
12258 local_player->show_envelope = element;
12263 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12265 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12267 Debug("game:playing:GameActions_RND", "This should never happen!");
12269 ChangePage[x][y] = -1;
12273 Stop[x][y] = FALSE;
12274 if (WasJustMoving[x][y] > 0)
12275 WasJustMoving[x][y]--;
12276 if (WasJustFalling[x][y] > 0)
12277 WasJustFalling[x][y]--;
12278 if (CheckCollision[x][y] > 0)
12279 CheckCollision[x][y]--;
12280 if (CheckImpact[x][y] > 0)
12281 CheckImpact[x][y]--;
12285 /* reset finished pushing action (not done in ContinueMoving() to allow
12286 continuous pushing animation for elements with zero push delay) */
12287 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12289 ResetGfxAnimation(x, y);
12290 TEST_DrawLevelField(x, y);
12294 if (IS_BLOCKED(x, y))
12298 Blocked2Moving(x, y, &oldx, &oldy);
12299 if (!IS_MOVING(oldx, oldy))
12301 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12302 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12303 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12304 Debug("game:playing:GameActions_RND", "This should never happen!");
12310 if (mouse_action.button)
12312 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12313 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12315 x = mouse_action.lx;
12316 y = mouse_action.ly;
12317 element = Tile[x][y];
12321 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12322 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12326 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12327 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12331 SCAN_PLAYFIELD(x, y)
12333 element = Tile[x][y];
12334 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12335 last_gfx_frame = GfxFrame[x][y];
12337 if (element == EL_EMPTY)
12338 graphic = el2img(GfxElementEmpty[x][y]);
12340 ResetGfxFrame(x, y);
12342 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12343 DrawLevelGraphicAnimation(x, y, graphic);
12345 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12346 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12347 ResetRandomAnimationValue(x, y);
12349 SetRandomAnimationValue(x, y);
12351 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12353 if (IS_INACTIVE(element))
12355 if (IS_ANIMATED(graphic))
12356 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12361 // this may take place after moving, so 'element' may have changed
12362 if (IS_CHANGING(x, y) &&
12363 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12365 int page = element_info[element].event_page_nr[CE_DELAY];
12367 HandleElementChange(x, y, page);
12369 element = Tile[x][y];
12370 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12373 CheckNextToConditions(x, y);
12375 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12379 element = Tile[x][y];
12380 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12382 if (IS_ANIMATED(graphic) &&
12383 !IS_MOVING(x, y) &&
12385 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12387 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12388 TEST_DrawTwinkleOnField(x, y);
12390 else if (element == EL_ACID)
12393 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12395 else if ((element == EL_EXIT_OPEN ||
12396 element == EL_EM_EXIT_OPEN ||
12397 element == EL_SP_EXIT_OPEN ||
12398 element == EL_STEEL_EXIT_OPEN ||
12399 element == EL_EM_STEEL_EXIT_OPEN ||
12400 element == EL_SP_TERMINAL ||
12401 element == EL_SP_TERMINAL_ACTIVE ||
12402 element == EL_EXTRA_TIME ||
12403 element == EL_SHIELD_NORMAL ||
12404 element == EL_SHIELD_DEADLY) &&
12405 IS_ANIMATED(graphic))
12406 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12407 else if (IS_MOVING(x, y))
12408 ContinueMoving(x, y);
12409 else if (IS_ACTIVE_BOMB(element))
12410 CheckDynamite(x, y);
12411 else if (element == EL_AMOEBA_GROWING)
12412 AmoebaGrowing(x, y);
12413 else if (element == EL_AMOEBA_SHRINKING)
12414 AmoebaShrinking(x, y);
12416 #if !USE_NEW_AMOEBA_CODE
12417 else if (IS_AMOEBALIVE(element))
12418 AmoebaReproduce(x, y);
12421 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12423 else if (element == EL_EXIT_CLOSED)
12425 else if (element == EL_EM_EXIT_CLOSED)
12427 else if (element == EL_STEEL_EXIT_CLOSED)
12428 CheckExitSteel(x, y);
12429 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12430 CheckExitSteelEM(x, y);
12431 else if (element == EL_SP_EXIT_CLOSED)
12433 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12434 element == EL_EXPANDABLE_STEELWALL_GROWING)
12435 MauerWaechst(x, y);
12436 else if (element == EL_EXPANDABLE_WALL ||
12437 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12438 element == EL_EXPANDABLE_WALL_VERTICAL ||
12439 element == EL_EXPANDABLE_WALL_ANY ||
12440 element == EL_BD_EXPANDABLE_WALL)
12441 MauerAbleger(x, y);
12442 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12443 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12444 element == EL_EXPANDABLE_STEELWALL_ANY)
12445 MauerAblegerStahl(x, y);
12446 else if (element == EL_FLAMES)
12447 CheckForDragon(x, y);
12448 else if (element == EL_EXPLOSION)
12449 ; // drawing of correct explosion animation is handled separately
12450 else if (element == EL_ELEMENT_SNAPPING ||
12451 element == EL_DIAGONAL_SHRINKING ||
12452 element == EL_DIAGONAL_GROWING)
12454 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12456 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12458 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12459 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12461 if (IS_BELT_ACTIVE(element))
12462 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12464 if (game.magic_wall_active)
12466 int jx = local_player->jx, jy = local_player->jy;
12468 // play the element sound at the position nearest to the player
12469 if ((element == EL_MAGIC_WALL_FULL ||
12470 element == EL_MAGIC_WALL_ACTIVE ||
12471 element == EL_MAGIC_WALL_EMPTYING ||
12472 element == EL_BD_MAGIC_WALL_FULL ||
12473 element == EL_BD_MAGIC_WALL_ACTIVE ||
12474 element == EL_BD_MAGIC_WALL_EMPTYING ||
12475 element == EL_DC_MAGIC_WALL_FULL ||
12476 element == EL_DC_MAGIC_WALL_ACTIVE ||
12477 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12478 ABS(x - jx) + ABS(y - jy) <
12479 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12487 #if USE_NEW_AMOEBA_CODE
12488 // new experimental amoeba growth stuff
12489 if (!(FrameCounter % 8))
12491 static unsigned int random = 1684108901;
12493 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12495 x = RND(lev_fieldx);
12496 y = RND(lev_fieldy);
12497 element = Tile[x][y];
12499 if (!IS_PLAYER(x,y) &&
12500 (element == EL_EMPTY ||
12501 CAN_GROW_INTO(element) ||
12502 element == EL_QUICKSAND_EMPTY ||
12503 element == EL_QUICKSAND_FAST_EMPTY ||
12504 element == EL_ACID_SPLASH_LEFT ||
12505 element == EL_ACID_SPLASH_RIGHT))
12507 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12508 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12509 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12510 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12511 Tile[x][y] = EL_AMOEBA_DROP;
12514 random = random * 129 + 1;
12519 game.explosions_delayed = FALSE;
12521 SCAN_PLAYFIELD(x, y)
12523 element = Tile[x][y];
12525 if (ExplodeField[x][y])
12526 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12527 else if (element == EL_EXPLOSION)
12528 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12530 ExplodeField[x][y] = EX_TYPE_NONE;
12533 game.explosions_delayed = TRUE;
12535 if (game.magic_wall_active)
12537 if (!(game.magic_wall_time_left % 4))
12539 int element = Tile[magic_wall_x][magic_wall_y];
12541 if (element == EL_BD_MAGIC_WALL_FULL ||
12542 element == EL_BD_MAGIC_WALL_ACTIVE ||
12543 element == EL_BD_MAGIC_WALL_EMPTYING)
12544 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12545 else if (element == EL_DC_MAGIC_WALL_FULL ||
12546 element == EL_DC_MAGIC_WALL_ACTIVE ||
12547 element == EL_DC_MAGIC_WALL_EMPTYING)
12548 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12550 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12553 if (game.magic_wall_time_left > 0)
12555 game.magic_wall_time_left--;
12557 if (!game.magic_wall_time_left)
12559 SCAN_PLAYFIELD(x, y)
12561 element = Tile[x][y];
12563 if (element == EL_MAGIC_WALL_ACTIVE ||
12564 element == EL_MAGIC_WALL_FULL)
12566 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12567 TEST_DrawLevelField(x, y);
12569 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12570 element == EL_BD_MAGIC_WALL_FULL)
12572 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12573 TEST_DrawLevelField(x, y);
12575 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12576 element == EL_DC_MAGIC_WALL_FULL)
12578 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12579 TEST_DrawLevelField(x, y);
12583 game.magic_wall_active = FALSE;
12588 if (game.light_time_left > 0)
12590 game.light_time_left--;
12592 if (game.light_time_left == 0)
12593 RedrawAllLightSwitchesAndInvisibleElements();
12596 if (game.timegate_time_left > 0)
12598 game.timegate_time_left--;
12600 if (game.timegate_time_left == 0)
12601 CloseAllOpenTimegates();
12604 if (game.lenses_time_left > 0)
12606 game.lenses_time_left--;
12608 if (game.lenses_time_left == 0)
12609 RedrawAllInvisibleElementsForLenses();
12612 if (game.magnify_time_left > 0)
12614 game.magnify_time_left--;
12616 if (game.magnify_time_left == 0)
12617 RedrawAllInvisibleElementsForMagnifier();
12620 for (i = 0; i < MAX_PLAYERS; i++)
12622 struct PlayerInfo *player = &stored_player[i];
12624 if (SHIELD_ON(player))
12626 if (player->shield_deadly_time_left)
12627 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12628 else if (player->shield_normal_time_left)
12629 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12633 #if USE_DELAYED_GFX_REDRAW
12634 SCAN_PLAYFIELD(x, y)
12636 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12638 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12639 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12641 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12642 DrawLevelField(x, y);
12644 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12645 DrawLevelFieldCrumbled(x, y);
12647 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12648 DrawLevelFieldCrumbledNeighbours(x, y);
12650 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12651 DrawTwinkleOnField(x, y);
12654 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12659 PlayAllPlayersSound();
12661 for (i = 0; i < MAX_PLAYERS; i++)
12663 struct PlayerInfo *player = &stored_player[i];
12665 if (player->show_envelope != 0 && (!player->active ||
12666 player->MovPos == 0))
12668 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12670 player->show_envelope = 0;
12674 // use random number generator in every frame to make it less predictable
12675 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12678 mouse_action_last = mouse_action;
12681 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12683 int min_x = x, min_y = y, max_x = x, max_y = y;
12684 int scr_fieldx = getScreenFieldSizeX();
12685 int scr_fieldy = getScreenFieldSizeY();
12688 for (i = 0; i < MAX_PLAYERS; i++)
12690 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12692 if (!stored_player[i].active || &stored_player[i] == player)
12695 min_x = MIN(min_x, jx);
12696 min_y = MIN(min_y, jy);
12697 max_x = MAX(max_x, jx);
12698 max_y = MAX(max_y, jy);
12701 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12704 static boolean AllPlayersInVisibleScreen(void)
12708 for (i = 0; i < MAX_PLAYERS; i++)
12710 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12712 if (!stored_player[i].active)
12715 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12722 void ScrollLevel(int dx, int dy)
12724 int scroll_offset = 2 * TILEX_VAR;
12727 BlitBitmap(drawto_field, drawto_field,
12728 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12729 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12730 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12731 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12732 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12733 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12737 x = (dx == 1 ? BX1 : BX2);
12738 for (y = BY1; y <= BY2; y++)
12739 DrawScreenField(x, y);
12744 y = (dy == 1 ? BY1 : BY2);
12745 for (x = BX1; x <= BX2; x++)
12746 DrawScreenField(x, y);
12749 redraw_mask |= REDRAW_FIELD;
12752 static boolean canFallDown(struct PlayerInfo *player)
12754 int jx = player->jx, jy = player->jy;
12756 return (IN_LEV_FIELD(jx, jy + 1) &&
12757 (IS_FREE(jx, jy + 1) ||
12758 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12759 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12760 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12763 static boolean canPassField(int x, int y, int move_dir)
12765 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12766 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12767 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12768 int nextx = x + dx;
12769 int nexty = y + dy;
12770 int element = Tile[x][y];
12772 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12773 !CAN_MOVE(element) &&
12774 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12775 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12776 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12779 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12781 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12782 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12783 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12787 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12788 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12789 (IS_DIGGABLE(Tile[newx][newy]) ||
12790 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12791 canPassField(newx, newy, move_dir)));
12794 static void CheckGravityMovement(struct PlayerInfo *player)
12796 if (player->gravity && !player->programmed_action)
12798 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12799 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12800 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12801 int jx = player->jx, jy = player->jy;
12802 boolean player_is_moving_to_valid_field =
12803 (!player_is_snapping &&
12804 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12805 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12806 boolean player_can_fall_down = canFallDown(player);
12808 if (player_can_fall_down &&
12809 !player_is_moving_to_valid_field)
12810 player->programmed_action = MV_DOWN;
12814 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12816 return CheckGravityMovement(player);
12818 if (player->gravity && !player->programmed_action)
12820 int jx = player->jx, jy = player->jy;
12821 boolean field_under_player_is_free =
12822 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12823 boolean player_is_standing_on_valid_field =
12824 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12825 (IS_WALKABLE(Tile[jx][jy]) &&
12826 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12828 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12829 player->programmed_action = MV_DOWN;
12834 MovePlayerOneStep()
12835 -----------------------------------------------------------------------------
12836 dx, dy: direction (non-diagonal) to try to move the player to
12837 real_dx, real_dy: direction as read from input device (can be diagonal)
12840 boolean MovePlayerOneStep(struct PlayerInfo *player,
12841 int dx, int dy, int real_dx, int real_dy)
12843 int jx = player->jx, jy = player->jy;
12844 int new_jx = jx + dx, new_jy = jy + dy;
12846 boolean player_can_move = !player->cannot_move;
12848 if (!player->active || (!dx && !dy))
12849 return MP_NO_ACTION;
12851 player->MovDir = (dx < 0 ? MV_LEFT :
12852 dx > 0 ? MV_RIGHT :
12854 dy > 0 ? MV_DOWN : MV_NONE);
12856 if (!IN_LEV_FIELD(new_jx, new_jy))
12857 return MP_NO_ACTION;
12859 if (!player_can_move)
12861 if (player->MovPos == 0)
12863 player->is_moving = FALSE;
12864 player->is_digging = FALSE;
12865 player->is_collecting = FALSE;
12866 player->is_snapping = FALSE;
12867 player->is_pushing = FALSE;
12871 if (!network.enabled && game.centered_player_nr == -1 &&
12872 !AllPlayersInSight(player, new_jx, new_jy))
12873 return MP_NO_ACTION;
12875 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12876 if (can_move != MP_MOVING)
12879 // check if DigField() has caused relocation of the player
12880 if (player->jx != jx || player->jy != jy)
12881 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12883 StorePlayer[jx][jy] = 0;
12884 player->last_jx = jx;
12885 player->last_jy = jy;
12886 player->jx = new_jx;
12887 player->jy = new_jy;
12888 StorePlayer[new_jx][new_jy] = player->element_nr;
12890 if (player->move_delay_value_next != -1)
12892 player->move_delay_value = player->move_delay_value_next;
12893 player->move_delay_value_next = -1;
12897 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12899 player->step_counter++;
12901 PlayerVisit[jx][jy] = FrameCounter;
12903 player->is_moving = TRUE;
12906 // should better be called in MovePlayer(), but this breaks some tapes
12907 ScrollPlayer(player, SCROLL_INIT);
12913 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12915 int jx = player->jx, jy = player->jy;
12916 int old_jx = jx, old_jy = jy;
12917 int moved = MP_NO_ACTION;
12919 if (!player->active)
12924 if (player->MovPos == 0)
12926 player->is_moving = FALSE;
12927 player->is_digging = FALSE;
12928 player->is_collecting = FALSE;
12929 player->is_snapping = FALSE;
12930 player->is_pushing = FALSE;
12936 if (player->move_delay > 0)
12939 player->move_delay = -1; // set to "uninitialized" value
12941 // store if player is automatically moved to next field
12942 player->is_auto_moving = (player->programmed_action != MV_NONE);
12944 // remove the last programmed player action
12945 player->programmed_action = 0;
12947 if (player->MovPos)
12949 // should only happen if pre-1.2 tape recordings are played
12950 // this is only for backward compatibility
12952 int original_move_delay_value = player->move_delay_value;
12955 Debug("game:playing:MovePlayer",
12956 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12960 // scroll remaining steps with finest movement resolution
12961 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12963 while (player->MovPos)
12965 ScrollPlayer(player, SCROLL_GO_ON);
12966 ScrollScreen(NULL, SCROLL_GO_ON);
12968 AdvanceFrameAndPlayerCounters(player->index_nr);
12971 BackToFront_WithFrameDelay(0);
12974 player->move_delay_value = original_move_delay_value;
12977 player->is_active = FALSE;
12979 if (player->last_move_dir & MV_HORIZONTAL)
12981 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12982 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12986 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12987 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12990 if (!moved && !player->is_active)
12992 player->is_moving = FALSE;
12993 player->is_digging = FALSE;
12994 player->is_collecting = FALSE;
12995 player->is_snapping = FALSE;
12996 player->is_pushing = FALSE;
13002 if (moved & MP_MOVING && !ScreenMovPos &&
13003 (player->index_nr == game.centered_player_nr ||
13004 game.centered_player_nr == -1))
13006 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13008 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13010 // actual player has left the screen -- scroll in that direction
13011 if (jx != old_jx) // player has moved horizontally
13012 scroll_x += (jx - old_jx);
13013 else // player has moved vertically
13014 scroll_y += (jy - old_jy);
13018 int offset_raw = game.scroll_delay_value;
13020 if (jx != old_jx) // player has moved horizontally
13022 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13023 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13024 int new_scroll_x = jx - MIDPOSX + offset_x;
13026 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13027 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13028 scroll_x = new_scroll_x;
13030 // don't scroll over playfield boundaries
13031 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13033 // don't scroll more than one field at a time
13034 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13036 // don't scroll against the player's moving direction
13037 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13038 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13039 scroll_x = old_scroll_x;
13041 else // player has moved vertically
13043 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13044 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13045 int new_scroll_y = jy - MIDPOSY + offset_y;
13047 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13048 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13049 scroll_y = new_scroll_y;
13051 // don't scroll over playfield boundaries
13052 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13054 // don't scroll more than one field at a time
13055 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13057 // don't scroll against the player's moving direction
13058 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13059 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13060 scroll_y = old_scroll_y;
13064 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13066 if (!network.enabled && game.centered_player_nr == -1 &&
13067 !AllPlayersInVisibleScreen())
13069 scroll_x = old_scroll_x;
13070 scroll_y = old_scroll_y;
13074 ScrollScreen(player, SCROLL_INIT);
13075 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13080 player->StepFrame = 0;
13082 if (moved & MP_MOVING)
13084 if (old_jx != jx && old_jy == jy)
13085 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13086 else if (old_jx == jx && old_jy != jy)
13087 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13089 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13091 player->last_move_dir = player->MovDir;
13092 player->is_moving = TRUE;
13093 player->is_snapping = FALSE;
13094 player->is_switching = FALSE;
13095 player->is_dropping = FALSE;
13096 player->is_dropping_pressed = FALSE;
13097 player->drop_pressed_delay = 0;
13100 // should better be called here than above, but this breaks some tapes
13101 ScrollPlayer(player, SCROLL_INIT);
13106 CheckGravityMovementWhenNotMoving(player);
13108 player->is_moving = FALSE;
13110 /* at this point, the player is allowed to move, but cannot move right now
13111 (e.g. because of something blocking the way) -- ensure that the player
13112 is also allowed to move in the next frame (in old versions before 3.1.1,
13113 the player was forced to wait again for eight frames before next try) */
13115 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13116 player->move_delay = 0; // allow direct movement in the next frame
13119 if (player->move_delay == -1) // not yet initialized by DigField()
13120 player->move_delay = player->move_delay_value;
13122 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13124 TestIfPlayerTouchesBadThing(jx, jy);
13125 TestIfPlayerTouchesCustomElement(jx, jy);
13128 if (!player->active)
13129 RemovePlayer(player);
13134 void ScrollPlayer(struct PlayerInfo *player, int mode)
13136 int jx = player->jx, jy = player->jy;
13137 int last_jx = player->last_jx, last_jy = player->last_jy;
13138 int move_stepsize = TILEX / player->move_delay_value;
13140 if (!player->active)
13143 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13146 if (mode == SCROLL_INIT)
13148 player->actual_frame_counter = FrameCounter;
13149 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13151 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13152 Tile[last_jx][last_jy] == EL_EMPTY)
13154 int last_field_block_delay = 0; // start with no blocking at all
13155 int block_delay_adjustment = player->block_delay_adjustment;
13157 // if player blocks last field, add delay for exactly one move
13158 if (player->block_last_field)
13160 last_field_block_delay += player->move_delay_value;
13162 // when blocking enabled, prevent moving up despite gravity
13163 if (player->gravity && player->MovDir == MV_UP)
13164 block_delay_adjustment = -1;
13167 // add block delay adjustment (also possible when not blocking)
13168 last_field_block_delay += block_delay_adjustment;
13170 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13171 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13174 if (player->MovPos != 0) // player has not yet reached destination
13177 else if (!FrameReached(&player->actual_frame_counter, 1))
13180 if (player->MovPos != 0)
13182 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13183 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13185 // before DrawPlayer() to draw correct player graphic for this case
13186 if (player->MovPos == 0)
13187 CheckGravityMovement(player);
13190 if (player->MovPos == 0) // player reached destination field
13192 if (player->move_delay_reset_counter > 0)
13194 player->move_delay_reset_counter--;
13196 if (player->move_delay_reset_counter == 0)
13198 // continue with normal speed after quickly moving through gate
13199 HALVE_PLAYER_SPEED(player);
13201 // be able to make the next move without delay
13202 player->move_delay = 0;
13206 player->last_jx = jx;
13207 player->last_jy = jy;
13209 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13210 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13211 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13212 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13213 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13214 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13215 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13216 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13218 ExitPlayer(player);
13220 if (game.players_still_needed == 0 &&
13221 (game.friends_still_needed == 0 ||
13222 IS_SP_ELEMENT(Tile[jx][jy])))
13226 // this breaks one level: "machine", level 000
13228 int move_direction = player->MovDir;
13229 int enter_side = MV_DIR_OPPOSITE(move_direction);
13230 int leave_side = move_direction;
13231 int old_jx = last_jx;
13232 int old_jy = last_jy;
13233 int old_element = Tile[old_jx][old_jy];
13234 int new_element = Tile[jx][jy];
13236 if (IS_CUSTOM_ELEMENT(old_element))
13237 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13239 player->index_bit, leave_side);
13241 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13242 CE_PLAYER_LEAVES_X,
13243 player->index_bit, leave_side);
13245 if (IS_CUSTOM_ELEMENT(new_element))
13246 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13247 player->index_bit, enter_side);
13249 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13250 CE_PLAYER_ENTERS_X,
13251 player->index_bit, enter_side);
13253 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13254 CE_MOVE_OF_X, move_direction);
13257 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13259 TestIfPlayerTouchesBadThing(jx, jy);
13260 TestIfPlayerTouchesCustomElement(jx, jy);
13262 /* needed because pushed element has not yet reached its destination,
13263 so it would trigger a change event at its previous field location */
13264 if (!player->is_pushing)
13265 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13267 if (level.finish_dig_collect &&
13268 (player->is_digging || player->is_collecting))
13270 int last_element = player->last_removed_element;
13271 int move_direction = player->MovDir;
13272 int enter_side = MV_DIR_OPPOSITE(move_direction);
13273 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13274 CE_PLAYER_COLLECTS_X);
13276 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13277 player->index_bit, enter_side);
13279 player->last_removed_element = EL_UNDEFINED;
13282 if (!player->active)
13283 RemovePlayer(player);
13286 if (level.use_step_counter)
13287 CheckLevelTime_StepCounter();
13289 if (tape.single_step && tape.recording && !tape.pausing &&
13290 !player->programmed_action)
13291 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13293 if (!player->programmed_action)
13294 CheckSaveEngineSnapshot(player);
13298 void ScrollScreen(struct PlayerInfo *player, int mode)
13300 static unsigned int screen_frame_counter = 0;
13302 if (mode == SCROLL_INIT)
13304 // set scrolling step size according to actual player's moving speed
13305 ScrollStepSize = TILEX / player->move_delay_value;
13307 screen_frame_counter = FrameCounter;
13308 ScreenMovDir = player->MovDir;
13309 ScreenMovPos = player->MovPos;
13310 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13313 else if (!FrameReached(&screen_frame_counter, 1))
13318 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13319 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13320 redraw_mask |= REDRAW_FIELD;
13323 ScreenMovDir = MV_NONE;
13326 void CheckNextToConditions(int x, int y)
13328 int element = Tile[x][y];
13330 if (IS_PLAYER(x, y))
13331 TestIfPlayerNextToCustomElement(x, y);
13333 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13334 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13335 TestIfElementNextToCustomElement(x, y);
13338 void TestIfPlayerNextToCustomElement(int x, int y)
13340 static int xy[4][2] =
13347 static int trigger_sides[4][2] =
13349 // center side border side
13350 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13351 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13352 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13353 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13357 if (!IS_PLAYER(x, y))
13360 struct PlayerInfo *player = PLAYERINFO(x, y);
13362 if (player->is_moving)
13365 for (i = 0; i < NUM_DIRECTIONS; i++)
13367 int xx = x + xy[i][0];
13368 int yy = y + xy[i][1];
13369 int border_side = trigger_sides[i][1];
13370 int border_element;
13372 if (!IN_LEV_FIELD(xx, yy))
13375 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13376 continue; // center and border element not connected
13378 border_element = Tile[xx][yy];
13380 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13381 player->index_bit, border_side);
13382 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13383 CE_PLAYER_NEXT_TO_X,
13384 player->index_bit, border_side);
13386 /* use player element that is initially defined in the level playfield,
13387 not the player element that corresponds to the runtime player number
13388 (example: a level that contains EL_PLAYER_3 as the only player would
13389 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13391 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13392 CE_NEXT_TO_X, border_side);
13396 void TestIfPlayerTouchesCustomElement(int x, int y)
13398 static int xy[4][2] =
13405 static int trigger_sides[4][2] =
13407 // center side border side
13408 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13409 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13410 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13411 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13413 static int touch_dir[4] =
13415 MV_LEFT | MV_RIGHT,
13420 int center_element = Tile[x][y]; // should always be non-moving!
13423 for (i = 0; i < NUM_DIRECTIONS; i++)
13425 int xx = x + xy[i][0];
13426 int yy = y + xy[i][1];
13427 int center_side = trigger_sides[i][0];
13428 int border_side = trigger_sides[i][1];
13429 int border_element;
13431 if (!IN_LEV_FIELD(xx, yy))
13434 if (IS_PLAYER(x, y)) // player found at center element
13436 struct PlayerInfo *player = PLAYERINFO(x, y);
13438 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13439 border_element = Tile[xx][yy]; // may be moving!
13440 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13441 border_element = Tile[xx][yy];
13442 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13443 border_element = MovingOrBlocked2Element(xx, yy);
13445 continue; // center and border element do not touch
13447 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13448 player->index_bit, border_side);
13449 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13450 CE_PLAYER_TOUCHES_X,
13451 player->index_bit, border_side);
13454 /* use player element that is initially defined in the level playfield,
13455 not the player element that corresponds to the runtime player number
13456 (example: a level that contains EL_PLAYER_3 as the only player would
13457 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13458 int player_element = PLAYERINFO(x, y)->initial_element;
13460 CheckElementChangeBySide(xx, yy, border_element, player_element,
13461 CE_TOUCHING_X, border_side);
13464 else if (IS_PLAYER(xx, yy)) // player found at border element
13466 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13468 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13470 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13471 continue; // center and border element do not touch
13474 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13475 player->index_bit, center_side);
13476 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13477 CE_PLAYER_TOUCHES_X,
13478 player->index_bit, center_side);
13481 /* use player element that is initially defined in the level playfield,
13482 not the player element that corresponds to the runtime player number
13483 (example: a level that contains EL_PLAYER_3 as the only player would
13484 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13485 int player_element = PLAYERINFO(xx, yy)->initial_element;
13487 CheckElementChangeBySide(x, y, center_element, player_element,
13488 CE_TOUCHING_X, center_side);
13496 void TestIfElementNextToCustomElement(int x, int y)
13498 static int xy[4][2] =
13505 static int trigger_sides[4][2] =
13507 // center side border side
13508 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13509 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13510 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13511 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13513 int center_element = Tile[x][y]; // should always be non-moving!
13516 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13519 for (i = 0; i < NUM_DIRECTIONS; i++)
13521 int xx = x + xy[i][0];
13522 int yy = y + xy[i][1];
13523 int border_side = trigger_sides[i][1];
13524 int border_element;
13526 if (!IN_LEV_FIELD(xx, yy))
13529 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13530 continue; // center and border element not connected
13532 border_element = Tile[xx][yy];
13534 // check for change of center element (but change it only once)
13535 if (CheckElementChangeBySide(x, y, center_element, border_element,
13536 CE_NEXT_TO_X, border_side))
13541 void TestIfElementTouchesCustomElement(int x, int y)
13543 static int xy[4][2] =
13550 static int trigger_sides[4][2] =
13552 // center side border side
13553 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13554 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13555 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13556 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13558 static int touch_dir[4] =
13560 MV_LEFT | MV_RIGHT,
13565 boolean change_center_element = FALSE;
13566 int center_element = Tile[x][y]; // should always be non-moving!
13567 int border_element_old[NUM_DIRECTIONS];
13570 for (i = 0; i < NUM_DIRECTIONS; i++)
13572 int xx = x + xy[i][0];
13573 int yy = y + xy[i][1];
13574 int border_element;
13576 border_element_old[i] = -1;
13578 if (!IN_LEV_FIELD(xx, yy))
13581 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13582 border_element = Tile[xx][yy]; // may be moving!
13583 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13584 border_element = Tile[xx][yy];
13585 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13586 border_element = MovingOrBlocked2Element(xx, yy);
13588 continue; // center and border element do not touch
13590 border_element_old[i] = border_element;
13593 for (i = 0; i < NUM_DIRECTIONS; i++)
13595 int xx = x + xy[i][0];
13596 int yy = y + xy[i][1];
13597 int center_side = trigger_sides[i][0];
13598 int border_element = border_element_old[i];
13600 if (border_element == -1)
13603 // check for change of border element
13604 CheckElementChangeBySide(xx, yy, border_element, center_element,
13605 CE_TOUCHING_X, center_side);
13607 // (center element cannot be player, so we dont have to check this here)
13610 for (i = 0; i < NUM_DIRECTIONS; i++)
13612 int xx = x + xy[i][0];
13613 int yy = y + xy[i][1];
13614 int border_side = trigger_sides[i][1];
13615 int border_element = border_element_old[i];
13617 if (border_element == -1)
13620 // check for change of center element (but change it only once)
13621 if (!change_center_element)
13622 change_center_element =
13623 CheckElementChangeBySide(x, y, center_element, border_element,
13624 CE_TOUCHING_X, border_side);
13626 if (IS_PLAYER(xx, yy))
13628 /* use player element that is initially defined in the level playfield,
13629 not the player element that corresponds to the runtime player number
13630 (example: a level that contains EL_PLAYER_3 as the only player would
13631 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13632 int player_element = PLAYERINFO(xx, yy)->initial_element;
13634 CheckElementChangeBySide(x, y, center_element, player_element,
13635 CE_TOUCHING_X, border_side);
13640 void TestIfElementHitsCustomElement(int x, int y, int direction)
13642 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13643 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13644 int hitx = x + dx, hity = y + dy;
13645 int hitting_element = Tile[x][y];
13646 int touched_element;
13648 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13651 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13652 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13654 if (IN_LEV_FIELD(hitx, hity))
13656 int opposite_direction = MV_DIR_OPPOSITE(direction);
13657 int hitting_side = direction;
13658 int touched_side = opposite_direction;
13659 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13660 MovDir[hitx][hity] != direction ||
13661 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13667 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13668 CE_HITTING_X, touched_side);
13670 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13671 CE_HIT_BY_X, hitting_side);
13673 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13674 CE_HIT_BY_SOMETHING, opposite_direction);
13676 if (IS_PLAYER(hitx, hity))
13678 /* use player element that is initially defined in the level playfield,
13679 not the player element that corresponds to the runtime player number
13680 (example: a level that contains EL_PLAYER_3 as the only player would
13681 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13682 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13684 CheckElementChangeBySide(x, y, hitting_element, player_element,
13685 CE_HITTING_X, touched_side);
13690 // "hitting something" is also true when hitting the playfield border
13691 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13692 CE_HITTING_SOMETHING, direction);
13695 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13697 int i, kill_x = -1, kill_y = -1;
13699 int bad_element = -1;
13700 static int test_xy[4][2] =
13707 static int test_dir[4] =
13715 for (i = 0; i < NUM_DIRECTIONS; i++)
13717 int test_x, test_y, test_move_dir, test_element;
13719 test_x = good_x + test_xy[i][0];
13720 test_y = good_y + test_xy[i][1];
13722 if (!IN_LEV_FIELD(test_x, test_y))
13726 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13728 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13730 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13731 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13733 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13734 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13738 bad_element = test_element;
13744 if (kill_x != -1 || kill_y != -1)
13746 if (IS_PLAYER(good_x, good_y))
13748 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13750 if (player->shield_deadly_time_left > 0 &&
13751 !IS_INDESTRUCTIBLE(bad_element))
13752 Bang(kill_x, kill_y);
13753 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13754 KillPlayer(player);
13757 Bang(good_x, good_y);
13761 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13763 int i, kill_x = -1, kill_y = -1;
13764 int bad_element = Tile[bad_x][bad_y];
13765 static int test_xy[4][2] =
13772 static int touch_dir[4] =
13774 MV_LEFT | MV_RIGHT,
13779 static int test_dir[4] =
13787 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13790 for (i = 0; i < NUM_DIRECTIONS; i++)
13792 int test_x, test_y, test_move_dir, test_element;
13794 test_x = bad_x + test_xy[i][0];
13795 test_y = bad_y + test_xy[i][1];
13797 if (!IN_LEV_FIELD(test_x, test_y))
13801 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13803 test_element = Tile[test_x][test_y];
13805 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13806 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13808 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13809 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13811 // good thing is player or penguin that does not move away
13812 if (IS_PLAYER(test_x, test_y))
13814 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13816 if (bad_element == EL_ROBOT && player->is_moving)
13817 continue; // robot does not kill player if he is moving
13819 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13821 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13822 continue; // center and border element do not touch
13830 else if (test_element == EL_PENGUIN)
13840 if (kill_x != -1 || kill_y != -1)
13842 if (IS_PLAYER(kill_x, kill_y))
13844 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13846 if (player->shield_deadly_time_left > 0 &&
13847 !IS_INDESTRUCTIBLE(bad_element))
13848 Bang(bad_x, bad_y);
13849 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13850 KillPlayer(player);
13853 Bang(kill_x, kill_y);
13857 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13859 int bad_element = Tile[bad_x][bad_y];
13860 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13861 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13862 int test_x = bad_x + dx, test_y = bad_y + dy;
13863 int test_move_dir, test_element;
13864 int kill_x = -1, kill_y = -1;
13866 if (!IN_LEV_FIELD(test_x, test_y))
13870 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13872 test_element = Tile[test_x][test_y];
13874 if (test_move_dir != bad_move_dir)
13876 // good thing can be player or penguin that does not move away
13877 if (IS_PLAYER(test_x, test_y))
13879 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13881 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13882 player as being hit when he is moving towards the bad thing, because
13883 the "get hit by" condition would be lost after the player stops) */
13884 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13885 return; // player moves away from bad thing
13890 else if (test_element == EL_PENGUIN)
13897 if (kill_x != -1 || kill_y != -1)
13899 if (IS_PLAYER(kill_x, kill_y))
13901 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13903 if (player->shield_deadly_time_left > 0 &&
13904 !IS_INDESTRUCTIBLE(bad_element))
13905 Bang(bad_x, bad_y);
13906 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13907 KillPlayer(player);
13910 Bang(kill_x, kill_y);
13914 void TestIfPlayerTouchesBadThing(int x, int y)
13916 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13919 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13921 TestIfGoodThingHitsBadThing(x, y, move_dir);
13924 void TestIfBadThingTouchesPlayer(int x, int y)
13926 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13929 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13931 TestIfBadThingHitsGoodThing(x, y, move_dir);
13934 void TestIfFriendTouchesBadThing(int x, int y)
13936 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13939 void TestIfBadThingTouchesFriend(int x, int y)
13941 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13944 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13946 int i, kill_x = bad_x, kill_y = bad_y;
13947 static int xy[4][2] =
13955 for (i = 0; i < NUM_DIRECTIONS; i++)
13959 x = bad_x + xy[i][0];
13960 y = bad_y + xy[i][1];
13961 if (!IN_LEV_FIELD(x, y))
13964 element = Tile[x][y];
13965 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13966 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13974 if (kill_x != bad_x || kill_y != bad_y)
13975 Bang(bad_x, bad_y);
13978 void KillPlayer(struct PlayerInfo *player)
13980 int jx = player->jx, jy = player->jy;
13982 if (!player->active)
13986 Debug("game:playing:KillPlayer",
13987 "0: killed == %d, active == %d, reanimated == %d",
13988 player->killed, player->active, player->reanimated);
13991 /* the following code was introduced to prevent an infinite loop when calling
13993 -> CheckTriggeredElementChangeExt()
13994 -> ExecuteCustomElementAction()
13996 -> (infinitely repeating the above sequence of function calls)
13997 which occurs when killing the player while having a CE with the setting
13998 "kill player X when explosion of <player X>"; the solution using a new
13999 field "player->killed" was chosen for backwards compatibility, although
14000 clever use of the fields "player->active" etc. would probably also work */
14002 if (player->killed)
14006 player->killed = TRUE;
14008 // remove accessible field at the player's position
14009 Tile[jx][jy] = EL_EMPTY;
14011 // deactivate shield (else Bang()/Explode() would not work right)
14012 player->shield_normal_time_left = 0;
14013 player->shield_deadly_time_left = 0;
14016 Debug("game:playing:KillPlayer",
14017 "1: killed == %d, active == %d, reanimated == %d",
14018 player->killed, player->active, player->reanimated);
14024 Debug("game:playing:KillPlayer",
14025 "2: killed == %d, active == %d, reanimated == %d",
14026 player->killed, player->active, player->reanimated);
14029 if (player->reanimated) // killed player may have been reanimated
14030 player->killed = player->reanimated = FALSE;
14032 BuryPlayer(player);
14035 static void KillPlayerUnlessEnemyProtected(int x, int y)
14037 if (!PLAYER_ENEMY_PROTECTED(x, y))
14038 KillPlayer(PLAYERINFO(x, y));
14041 static void KillPlayerUnlessExplosionProtected(int x, int y)
14043 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14044 KillPlayer(PLAYERINFO(x, y));
14047 void BuryPlayer(struct PlayerInfo *player)
14049 int jx = player->jx, jy = player->jy;
14051 if (!player->active)
14054 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14055 PlayLevelSound(jx, jy, SND_GAME_LOSING);
14057 RemovePlayer(player);
14059 player->buried = TRUE;
14061 if (game.all_players_gone)
14062 game.GameOver = TRUE;
14065 void RemovePlayer(struct PlayerInfo *player)
14067 int jx = player->jx, jy = player->jy;
14068 int i, found = FALSE;
14070 player->present = FALSE;
14071 player->active = FALSE;
14073 // required for some CE actions (even if the player is not active anymore)
14074 player->MovPos = 0;
14076 if (!ExplodeField[jx][jy])
14077 StorePlayer[jx][jy] = 0;
14079 if (player->is_moving)
14080 TEST_DrawLevelField(player->last_jx, player->last_jy);
14082 for (i = 0; i < MAX_PLAYERS; i++)
14083 if (stored_player[i].active)
14088 game.all_players_gone = TRUE;
14089 game.GameOver = TRUE;
14092 game.exit_x = game.robot_wheel_x = jx;
14093 game.exit_y = game.robot_wheel_y = jy;
14096 void ExitPlayer(struct PlayerInfo *player)
14098 DrawPlayer(player); // needed here only to cleanup last field
14099 RemovePlayer(player);
14101 if (game.players_still_needed > 0)
14102 game.players_still_needed--;
14105 static void SetFieldForSnapping(int x, int y, int element, int direction,
14106 int player_index_bit)
14108 struct ElementInfo *ei = &element_info[element];
14109 int direction_bit = MV_DIR_TO_BIT(direction);
14110 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14111 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14112 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14114 Tile[x][y] = EL_ELEMENT_SNAPPING;
14115 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14116 MovDir[x][y] = direction;
14117 Store[x][y] = element;
14118 Store2[x][y] = player_index_bit;
14120 ResetGfxAnimation(x, y);
14122 GfxElement[x][y] = element;
14123 GfxAction[x][y] = action;
14124 GfxDir[x][y] = direction;
14125 GfxFrame[x][y] = -1;
14128 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14129 int player_index_bit)
14131 TestIfElementTouchesCustomElement(x, y); // for empty space
14133 if (level.finish_dig_collect)
14135 int dig_side = MV_DIR_OPPOSITE(direction);
14136 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14137 CE_PLAYER_COLLECTS_X);
14139 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14140 player_index_bit, dig_side);
14141 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14142 player_index_bit, dig_side);
14147 =============================================================================
14148 checkDiagonalPushing()
14149 -----------------------------------------------------------------------------
14150 check if diagonal input device direction results in pushing of object
14151 (by checking if the alternative direction is walkable, diggable, ...)
14152 =============================================================================
14155 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14156 int x, int y, int real_dx, int real_dy)
14158 int jx, jy, dx, dy, xx, yy;
14160 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14163 // diagonal direction: check alternative direction
14168 xx = jx + (dx == 0 ? real_dx : 0);
14169 yy = jy + (dy == 0 ? real_dy : 0);
14171 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14175 =============================================================================
14177 -----------------------------------------------------------------------------
14178 x, y: field next to player (non-diagonal) to try to dig to
14179 real_dx, real_dy: direction as read from input device (can be diagonal)
14180 =============================================================================
14183 static int DigField(struct PlayerInfo *player,
14184 int oldx, int oldy, int x, int y,
14185 int real_dx, int real_dy, int mode)
14187 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14188 boolean player_was_pushing = player->is_pushing;
14189 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14190 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14191 int jx = oldx, jy = oldy;
14192 int dx = x - jx, dy = y - jy;
14193 int nextx = x + dx, nexty = y + dy;
14194 int move_direction = (dx == -1 ? MV_LEFT :
14195 dx == +1 ? MV_RIGHT :
14197 dy == +1 ? MV_DOWN : MV_NONE);
14198 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14199 int dig_side = MV_DIR_OPPOSITE(move_direction);
14200 int old_element = Tile[jx][jy];
14201 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14204 if (is_player) // function can also be called by EL_PENGUIN
14206 if (player->MovPos == 0)
14208 player->is_digging = FALSE;
14209 player->is_collecting = FALSE;
14212 if (player->MovPos == 0) // last pushing move finished
14213 player->is_pushing = FALSE;
14215 if (mode == DF_NO_PUSH) // player just stopped pushing
14217 player->is_switching = FALSE;
14218 player->push_delay = -1;
14220 return MP_NO_ACTION;
14223 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14224 old_element = Back[jx][jy];
14226 // in case of element dropped at player position, check background
14227 else if (Back[jx][jy] != EL_EMPTY &&
14228 game.engine_version >= VERSION_IDENT(2,2,0,0))
14229 old_element = Back[jx][jy];
14231 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14232 return MP_NO_ACTION; // field has no opening in this direction
14234 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14235 return MP_NO_ACTION; // field has no opening in this direction
14237 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14241 Tile[jx][jy] = player->artwork_element;
14242 InitMovingField(jx, jy, MV_DOWN);
14243 Store[jx][jy] = EL_ACID;
14244 ContinueMoving(jx, jy);
14245 BuryPlayer(player);
14247 return MP_DONT_RUN_INTO;
14250 if (player_can_move && DONT_RUN_INTO(element))
14252 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14254 return MP_DONT_RUN_INTO;
14257 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14258 return MP_NO_ACTION;
14260 collect_count = element_info[element].collect_count_initial;
14262 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14263 return MP_NO_ACTION;
14265 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14266 player_can_move = player_can_move_or_snap;
14268 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14269 game.engine_version >= VERSION_IDENT(2,2,0,0))
14271 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14272 player->index_bit, dig_side);
14273 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14274 player->index_bit, dig_side);
14276 if (element == EL_DC_LANDMINE)
14279 if (Tile[x][y] != element) // field changed by snapping
14282 return MP_NO_ACTION;
14285 if (player->gravity && is_player && !player->is_auto_moving &&
14286 canFallDown(player) && move_direction != MV_DOWN &&
14287 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14288 return MP_NO_ACTION; // player cannot walk here due to gravity
14290 if (player_can_move &&
14291 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14293 int sound_element = SND_ELEMENT(element);
14294 int sound_action = ACTION_WALKING;
14296 if (IS_RND_GATE(element))
14298 if (!player->key[RND_GATE_NR(element)])
14299 return MP_NO_ACTION;
14301 else if (IS_RND_GATE_GRAY(element))
14303 if (!player->key[RND_GATE_GRAY_NR(element)])
14304 return MP_NO_ACTION;
14306 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14308 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14309 return MP_NO_ACTION;
14311 else if (element == EL_EXIT_OPEN ||
14312 element == EL_EM_EXIT_OPEN ||
14313 element == EL_EM_EXIT_OPENING ||
14314 element == EL_STEEL_EXIT_OPEN ||
14315 element == EL_EM_STEEL_EXIT_OPEN ||
14316 element == EL_EM_STEEL_EXIT_OPENING ||
14317 element == EL_SP_EXIT_OPEN ||
14318 element == EL_SP_EXIT_OPENING)
14320 sound_action = ACTION_PASSING; // player is passing exit
14322 else if (element == EL_EMPTY)
14324 sound_action = ACTION_MOVING; // nothing to walk on
14327 // play sound from background or player, whatever is available
14328 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14329 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14331 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14333 else if (player_can_move &&
14334 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14336 if (!ACCESS_FROM(element, opposite_direction))
14337 return MP_NO_ACTION; // field not accessible from this direction
14339 if (CAN_MOVE(element)) // only fixed elements can be passed!
14340 return MP_NO_ACTION;
14342 if (IS_EM_GATE(element))
14344 if (!player->key[EM_GATE_NR(element)])
14345 return MP_NO_ACTION;
14347 else if (IS_EM_GATE_GRAY(element))
14349 if (!player->key[EM_GATE_GRAY_NR(element)])
14350 return MP_NO_ACTION;
14352 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14354 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14355 return MP_NO_ACTION;
14357 else if (IS_EMC_GATE(element))
14359 if (!player->key[EMC_GATE_NR(element)])
14360 return MP_NO_ACTION;
14362 else if (IS_EMC_GATE_GRAY(element))
14364 if (!player->key[EMC_GATE_GRAY_NR(element)])
14365 return MP_NO_ACTION;
14367 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14369 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14370 return MP_NO_ACTION;
14372 else if (element == EL_DC_GATE_WHITE ||
14373 element == EL_DC_GATE_WHITE_GRAY ||
14374 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14376 if (player->num_white_keys == 0)
14377 return MP_NO_ACTION;
14379 player->num_white_keys--;
14381 else if (IS_SP_PORT(element))
14383 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14384 element == EL_SP_GRAVITY_PORT_RIGHT ||
14385 element == EL_SP_GRAVITY_PORT_UP ||
14386 element == EL_SP_GRAVITY_PORT_DOWN)
14387 player->gravity = !player->gravity;
14388 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14389 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14390 element == EL_SP_GRAVITY_ON_PORT_UP ||
14391 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14392 player->gravity = TRUE;
14393 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14394 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14395 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14396 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14397 player->gravity = FALSE;
14400 // automatically move to the next field with double speed
14401 player->programmed_action = move_direction;
14403 if (player->move_delay_reset_counter == 0)
14405 player->move_delay_reset_counter = 2; // two double speed steps
14407 DOUBLE_PLAYER_SPEED(player);
14410 PlayLevelSoundAction(x, y, ACTION_PASSING);
14412 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14416 if (mode != DF_SNAP)
14418 GfxElement[x][y] = GFX_ELEMENT(element);
14419 player->is_digging = TRUE;
14422 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14424 // use old behaviour for old levels (digging)
14425 if (!level.finish_dig_collect)
14427 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14428 player->index_bit, dig_side);
14430 // if digging triggered player relocation, finish digging tile
14431 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14432 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14435 if (mode == DF_SNAP)
14437 if (level.block_snap_field)
14438 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14440 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14442 // use old behaviour for old levels (snapping)
14443 if (!level.finish_dig_collect)
14444 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14445 player->index_bit, dig_side);
14448 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14452 if (is_player && mode != DF_SNAP)
14454 GfxElement[x][y] = element;
14455 player->is_collecting = TRUE;
14458 if (element == EL_SPEED_PILL)
14460 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14462 else if (element == EL_EXTRA_TIME && level.time > 0)
14464 TimeLeft += level.extra_time;
14466 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14468 DisplayGameControlValues();
14470 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14472 player->shield_normal_time_left += level.shield_normal_time;
14473 if (element == EL_SHIELD_DEADLY)
14474 player->shield_deadly_time_left += level.shield_deadly_time;
14476 else if (element == EL_DYNAMITE ||
14477 element == EL_EM_DYNAMITE ||
14478 element == EL_SP_DISK_RED)
14480 if (player->inventory_size < MAX_INVENTORY_SIZE)
14481 player->inventory_element[player->inventory_size++] = element;
14483 DrawGameDoorValues();
14485 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14487 player->dynabomb_count++;
14488 player->dynabombs_left++;
14490 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14492 player->dynabomb_size++;
14494 else if (element == EL_DYNABOMB_INCREASE_POWER)
14496 player->dynabomb_xl = TRUE;
14498 else if (IS_KEY(element))
14500 player->key[KEY_NR(element)] = TRUE;
14502 DrawGameDoorValues();
14504 else if (element == EL_DC_KEY_WHITE)
14506 player->num_white_keys++;
14508 // display white keys?
14509 // DrawGameDoorValues();
14511 else if (IS_ENVELOPE(element))
14513 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14515 if (!wait_for_snapping)
14516 player->show_envelope = element;
14518 else if (element == EL_EMC_LENSES)
14520 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14522 RedrawAllInvisibleElementsForLenses();
14524 else if (element == EL_EMC_MAGNIFIER)
14526 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14528 RedrawAllInvisibleElementsForMagnifier();
14530 else if (IS_DROPPABLE(element) ||
14531 IS_THROWABLE(element)) // can be collected and dropped
14535 if (collect_count == 0)
14536 player->inventory_infinite_element = element;
14538 for (i = 0; i < collect_count; i++)
14539 if (player->inventory_size < MAX_INVENTORY_SIZE)
14540 player->inventory_element[player->inventory_size++] = element;
14542 DrawGameDoorValues();
14544 else if (collect_count > 0)
14546 game.gems_still_needed -= collect_count;
14547 if (game.gems_still_needed < 0)
14548 game.gems_still_needed = 0;
14550 game.snapshot.collected_item = TRUE;
14552 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14554 DisplayGameControlValues();
14557 RaiseScoreElement(element);
14558 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14560 // use old behaviour for old levels (collecting)
14561 if (!level.finish_dig_collect && is_player)
14563 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14564 player->index_bit, dig_side);
14566 // if collecting triggered player relocation, finish collecting tile
14567 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14568 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14571 if (mode == DF_SNAP)
14573 if (level.block_snap_field)
14574 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14576 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14578 // use old behaviour for old levels (snapping)
14579 if (!level.finish_dig_collect)
14580 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14581 player->index_bit, dig_side);
14584 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14586 if (mode == DF_SNAP && element != EL_BD_ROCK)
14587 return MP_NO_ACTION;
14589 if (CAN_FALL(element) && dy)
14590 return MP_NO_ACTION;
14592 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14593 !(element == EL_SPRING && level.use_spring_bug))
14594 return MP_NO_ACTION;
14596 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14597 ((move_direction & MV_VERTICAL &&
14598 ((element_info[element].move_pattern & MV_LEFT &&
14599 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14600 (element_info[element].move_pattern & MV_RIGHT &&
14601 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14602 (move_direction & MV_HORIZONTAL &&
14603 ((element_info[element].move_pattern & MV_UP &&
14604 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14605 (element_info[element].move_pattern & MV_DOWN &&
14606 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14607 return MP_NO_ACTION;
14609 // do not push elements already moving away faster than player
14610 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14611 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14612 return MP_NO_ACTION;
14614 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14616 if (player->push_delay_value == -1 || !player_was_pushing)
14617 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14619 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14621 if (player->push_delay_value == -1)
14622 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14624 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14626 if (!player->is_pushing)
14627 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14630 player->is_pushing = TRUE;
14631 player->is_active = TRUE;
14633 if (!(IN_LEV_FIELD(nextx, nexty) &&
14634 (IS_FREE(nextx, nexty) ||
14635 (IS_SB_ELEMENT(element) &&
14636 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14637 (IS_CUSTOM_ELEMENT(element) &&
14638 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14639 return MP_NO_ACTION;
14641 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14642 return MP_NO_ACTION;
14644 if (player->push_delay == -1) // new pushing; restart delay
14645 player->push_delay = 0;
14647 if (player->push_delay < player->push_delay_value &&
14648 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14649 element != EL_SPRING && element != EL_BALLOON)
14651 // make sure that there is no move delay before next try to push
14652 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14653 player->move_delay = 0;
14655 return MP_NO_ACTION;
14658 if (IS_CUSTOM_ELEMENT(element) &&
14659 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14661 if (!DigFieldByCE(nextx, nexty, element))
14662 return MP_NO_ACTION;
14665 if (IS_SB_ELEMENT(element))
14667 boolean sokoban_task_solved = FALSE;
14669 if (element == EL_SOKOBAN_FIELD_FULL)
14671 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14673 IncrementSokobanFieldsNeeded();
14674 IncrementSokobanObjectsNeeded();
14677 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14679 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14681 DecrementSokobanFieldsNeeded();
14682 DecrementSokobanObjectsNeeded();
14684 // sokoban object was pushed from empty field to sokoban field
14685 if (Back[x][y] == EL_EMPTY)
14686 sokoban_task_solved = TRUE;
14689 Tile[x][y] = EL_SOKOBAN_OBJECT;
14691 if (Back[x][y] == Back[nextx][nexty])
14692 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14693 else if (Back[x][y] != 0)
14694 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14697 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14700 if (sokoban_task_solved &&
14701 game.sokoban_fields_still_needed == 0 &&
14702 game.sokoban_objects_still_needed == 0 &&
14703 level.auto_exit_sokoban)
14705 game.players_still_needed = 0;
14709 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14713 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14715 InitMovingField(x, y, move_direction);
14716 GfxAction[x][y] = ACTION_PUSHING;
14718 if (mode == DF_SNAP)
14719 ContinueMoving(x, y);
14721 MovPos[x][y] = (dx != 0 ? dx : dy);
14723 Pushed[x][y] = TRUE;
14724 Pushed[nextx][nexty] = TRUE;
14726 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14727 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14729 player->push_delay_value = -1; // get new value later
14731 // check for element change _after_ element has been pushed
14732 if (game.use_change_when_pushing_bug)
14734 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14735 player->index_bit, dig_side);
14736 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14737 player->index_bit, dig_side);
14740 else if (IS_SWITCHABLE(element))
14742 if (PLAYER_SWITCHING(player, x, y))
14744 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14745 player->index_bit, dig_side);
14750 player->is_switching = TRUE;
14751 player->switch_x = x;
14752 player->switch_y = y;
14754 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14756 if (element == EL_ROBOT_WHEEL)
14758 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14760 game.robot_wheel_x = x;
14761 game.robot_wheel_y = y;
14762 game.robot_wheel_active = TRUE;
14764 TEST_DrawLevelField(x, y);
14766 else if (element == EL_SP_TERMINAL)
14770 SCAN_PLAYFIELD(xx, yy)
14772 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14776 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14778 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14780 ResetGfxAnimation(xx, yy);
14781 TEST_DrawLevelField(xx, yy);
14785 else if (IS_BELT_SWITCH(element))
14787 ToggleBeltSwitch(x, y);
14789 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14790 element == EL_SWITCHGATE_SWITCH_DOWN ||
14791 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14792 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14794 ToggleSwitchgateSwitch(x, y);
14796 else if (element == EL_LIGHT_SWITCH ||
14797 element == EL_LIGHT_SWITCH_ACTIVE)
14799 ToggleLightSwitch(x, y);
14801 else if (element == EL_TIMEGATE_SWITCH ||
14802 element == EL_DC_TIMEGATE_SWITCH)
14804 ActivateTimegateSwitch(x, y);
14806 else if (element == EL_BALLOON_SWITCH_LEFT ||
14807 element == EL_BALLOON_SWITCH_RIGHT ||
14808 element == EL_BALLOON_SWITCH_UP ||
14809 element == EL_BALLOON_SWITCH_DOWN ||
14810 element == EL_BALLOON_SWITCH_NONE ||
14811 element == EL_BALLOON_SWITCH_ANY)
14813 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14814 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14815 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14816 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14817 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14820 else if (element == EL_LAMP)
14822 Tile[x][y] = EL_LAMP_ACTIVE;
14823 game.lights_still_needed--;
14825 ResetGfxAnimation(x, y);
14826 TEST_DrawLevelField(x, y);
14828 else if (element == EL_TIME_ORB_FULL)
14830 Tile[x][y] = EL_TIME_ORB_EMPTY;
14832 if (level.time > 0 || level.use_time_orb_bug)
14834 TimeLeft += level.time_orb_time;
14835 game.no_time_limit = FALSE;
14837 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14839 DisplayGameControlValues();
14842 ResetGfxAnimation(x, y);
14843 TEST_DrawLevelField(x, y);
14845 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14846 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14850 game.ball_active = !game.ball_active;
14852 SCAN_PLAYFIELD(xx, yy)
14854 int e = Tile[xx][yy];
14856 if (game.ball_active)
14858 if (e == EL_EMC_MAGIC_BALL)
14859 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14860 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14861 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14865 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14866 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14867 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14868 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14873 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14874 player->index_bit, dig_side);
14876 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14877 player->index_bit, dig_side);
14879 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14880 player->index_bit, dig_side);
14886 if (!PLAYER_SWITCHING(player, x, y))
14888 player->is_switching = TRUE;
14889 player->switch_x = x;
14890 player->switch_y = y;
14892 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14893 player->index_bit, dig_side);
14894 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14895 player->index_bit, dig_side);
14897 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14898 player->index_bit, dig_side);
14899 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14900 player->index_bit, dig_side);
14903 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14904 player->index_bit, dig_side);
14905 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14906 player->index_bit, dig_side);
14908 return MP_NO_ACTION;
14911 player->push_delay = -1;
14913 if (is_player) // function can also be called by EL_PENGUIN
14915 if (Tile[x][y] != element) // really digged/collected something
14917 player->is_collecting = !player->is_digging;
14918 player->is_active = TRUE;
14920 player->last_removed_element = element;
14927 static boolean DigFieldByCE(int x, int y, int digging_element)
14929 int element = Tile[x][y];
14931 if (!IS_FREE(x, y))
14933 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14934 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14937 // no element can dig solid indestructible elements
14938 if (IS_INDESTRUCTIBLE(element) &&
14939 !IS_DIGGABLE(element) &&
14940 !IS_COLLECTIBLE(element))
14943 if (AmoebaNr[x][y] &&
14944 (element == EL_AMOEBA_FULL ||
14945 element == EL_BD_AMOEBA ||
14946 element == EL_AMOEBA_GROWING))
14948 AmoebaCnt[AmoebaNr[x][y]]--;
14949 AmoebaCnt2[AmoebaNr[x][y]]--;
14952 if (IS_MOVING(x, y))
14953 RemoveMovingField(x, y);
14957 TEST_DrawLevelField(x, y);
14960 // if digged element was about to explode, prevent the explosion
14961 ExplodeField[x][y] = EX_TYPE_NONE;
14963 PlayLevelSoundAction(x, y, action);
14966 Store[x][y] = EL_EMPTY;
14968 // this makes it possible to leave the removed element again
14969 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14970 Store[x][y] = element;
14975 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14977 int jx = player->jx, jy = player->jy;
14978 int x = jx + dx, y = jy + dy;
14979 int snap_direction = (dx == -1 ? MV_LEFT :
14980 dx == +1 ? MV_RIGHT :
14982 dy == +1 ? MV_DOWN : MV_NONE);
14983 boolean can_continue_snapping = (level.continuous_snapping &&
14984 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14986 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14989 if (!player->active || !IN_LEV_FIELD(x, y))
14997 if (player->MovPos == 0)
14998 player->is_pushing = FALSE;
15000 player->is_snapping = FALSE;
15002 if (player->MovPos == 0)
15004 player->is_moving = FALSE;
15005 player->is_digging = FALSE;
15006 player->is_collecting = FALSE;
15012 // prevent snapping with already pressed snap key when not allowed
15013 if (player->is_snapping && !can_continue_snapping)
15016 player->MovDir = snap_direction;
15018 if (player->MovPos == 0)
15020 player->is_moving = FALSE;
15021 player->is_digging = FALSE;
15022 player->is_collecting = FALSE;
15025 player->is_dropping = FALSE;
15026 player->is_dropping_pressed = FALSE;
15027 player->drop_pressed_delay = 0;
15029 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15032 player->is_snapping = TRUE;
15033 player->is_active = TRUE;
15035 if (player->MovPos == 0)
15037 player->is_moving = FALSE;
15038 player->is_digging = FALSE;
15039 player->is_collecting = FALSE;
15042 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15043 TEST_DrawLevelField(player->last_jx, player->last_jy);
15045 TEST_DrawLevelField(x, y);
15050 static boolean DropElement(struct PlayerInfo *player)
15052 int old_element, new_element;
15053 int dropx = player->jx, dropy = player->jy;
15054 int drop_direction = player->MovDir;
15055 int drop_side = drop_direction;
15056 int drop_element = get_next_dropped_element(player);
15058 /* do not drop an element on top of another element; when holding drop key
15059 pressed without moving, dropped element must move away before the next
15060 element can be dropped (this is especially important if the next element
15061 is dynamite, which can be placed on background for historical reasons) */
15062 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15065 if (IS_THROWABLE(drop_element))
15067 dropx += GET_DX_FROM_DIR(drop_direction);
15068 dropy += GET_DY_FROM_DIR(drop_direction);
15070 if (!IN_LEV_FIELD(dropx, dropy))
15074 old_element = Tile[dropx][dropy]; // old element at dropping position
15075 new_element = drop_element; // default: no change when dropping
15077 // check if player is active, not moving and ready to drop
15078 if (!player->active || player->MovPos || player->drop_delay > 0)
15081 // check if player has anything that can be dropped
15082 if (new_element == EL_UNDEFINED)
15085 // only set if player has anything that can be dropped
15086 player->is_dropping_pressed = TRUE;
15088 // check if drop key was pressed long enough for EM style dynamite
15089 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15092 // check if anything can be dropped at the current position
15093 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15096 // collected custom elements can only be dropped on empty fields
15097 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15100 if (old_element != EL_EMPTY)
15101 Back[dropx][dropy] = old_element; // store old element on this field
15103 ResetGfxAnimation(dropx, dropy);
15104 ResetRandomAnimationValue(dropx, dropy);
15106 if (player->inventory_size > 0 ||
15107 player->inventory_infinite_element != EL_UNDEFINED)
15109 if (player->inventory_size > 0)
15111 player->inventory_size--;
15113 DrawGameDoorValues();
15115 if (new_element == EL_DYNAMITE)
15116 new_element = EL_DYNAMITE_ACTIVE;
15117 else if (new_element == EL_EM_DYNAMITE)
15118 new_element = EL_EM_DYNAMITE_ACTIVE;
15119 else if (new_element == EL_SP_DISK_RED)
15120 new_element = EL_SP_DISK_RED_ACTIVE;
15123 Tile[dropx][dropy] = new_element;
15125 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15126 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15127 el2img(Tile[dropx][dropy]), 0);
15129 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15131 // needed if previous element just changed to "empty" in the last frame
15132 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15134 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15135 player->index_bit, drop_side);
15136 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15138 player->index_bit, drop_side);
15140 TestIfElementTouchesCustomElement(dropx, dropy);
15142 else // player is dropping a dyna bomb
15144 player->dynabombs_left--;
15146 Tile[dropx][dropy] = new_element;
15148 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15149 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15150 el2img(Tile[dropx][dropy]), 0);
15152 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15155 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15156 InitField_WithBug1(dropx, dropy, FALSE);
15158 new_element = Tile[dropx][dropy]; // element might have changed
15160 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15161 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15163 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15164 MovDir[dropx][dropy] = drop_direction;
15166 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15168 // do not cause impact style collision by dropping elements that can fall
15169 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15172 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15173 player->is_dropping = TRUE;
15175 player->drop_pressed_delay = 0;
15176 player->is_dropping_pressed = FALSE;
15178 player->drop_x = dropx;
15179 player->drop_y = dropy;
15184 // ----------------------------------------------------------------------------
15185 // game sound playing functions
15186 // ----------------------------------------------------------------------------
15188 static int *loop_sound_frame = NULL;
15189 static int *loop_sound_volume = NULL;
15191 void InitPlayLevelSound(void)
15193 int num_sounds = getSoundListSize();
15195 checked_free(loop_sound_frame);
15196 checked_free(loop_sound_volume);
15198 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15199 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15202 static void PlayLevelSound(int x, int y, int nr)
15204 int sx = SCREENX(x), sy = SCREENY(y);
15205 int volume, stereo_position;
15206 int max_distance = 8;
15207 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15209 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15210 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15213 if (!IN_LEV_FIELD(x, y) ||
15214 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15215 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15218 volume = SOUND_MAX_VOLUME;
15220 if (!IN_SCR_FIELD(sx, sy))
15222 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15223 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15225 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15228 stereo_position = (SOUND_MAX_LEFT +
15229 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15230 (SCR_FIELDX + 2 * max_distance));
15232 if (IS_LOOP_SOUND(nr))
15234 /* This assures that quieter loop sounds do not overwrite louder ones,
15235 while restarting sound volume comparison with each new game frame. */
15237 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15240 loop_sound_volume[nr] = volume;
15241 loop_sound_frame[nr] = FrameCounter;
15244 PlaySoundExt(nr, volume, stereo_position, type);
15247 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15249 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15250 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15251 y < LEVELY(BY1) ? LEVELY(BY1) :
15252 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15256 static void PlayLevelSoundAction(int x, int y, int action)
15258 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15261 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15263 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15265 if (sound_effect != SND_UNDEFINED)
15266 PlayLevelSound(x, y, sound_effect);
15269 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15272 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15274 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15275 PlayLevelSound(x, y, sound_effect);
15278 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15280 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15282 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15283 PlayLevelSound(x, y, sound_effect);
15286 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15288 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15290 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15291 StopSound(sound_effect);
15294 static int getLevelMusicNr(void)
15296 if (levelset.music[level_nr] != MUS_UNDEFINED)
15297 return levelset.music[level_nr]; // from config file
15299 return MAP_NOCONF_MUSIC(level_nr); // 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);
15646 // door may still be open due to skipped or envelope style request
15647 CloseDoor(DOOR_CLOSE_1);
15650 if (network.enabled)
15651 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15655 FadeSkipNextFadeIn();
15657 SetGameStatus(GAME_MODE_MAIN);
15662 else // continue playing the game
15664 if (tape.playing && tape.deactivate_display)
15665 TapeDeactivateDisplayOff(TRUE);
15667 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15669 if (tape.playing && tape.deactivate_display)
15670 TapeDeactivateDisplayOn();
15674 void RequestQuitGame(boolean escape_key_pressed)
15676 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15677 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15678 level_editor_test_game);
15679 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15682 RequestQuitGameExt(skip_request, quick_quit,
15683 "Do you really want to quit the game?");
15686 void RequestRestartGame(char *message)
15688 game.restart_game_message = NULL;
15690 boolean has_started_game = hasStartedNetworkGame();
15691 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15693 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15695 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15699 // needed in case of envelope request to close game panel
15700 CloseDoor(DOOR_CLOSE_1);
15702 SetGameStatus(GAME_MODE_MAIN);
15708 void CheckGameOver(void)
15710 static boolean last_game_over = FALSE;
15711 static int game_over_delay = 0;
15712 int game_over_delay_value = 50;
15713 boolean game_over = checkGameFailed();
15715 // do not handle game over if request dialog is already active
15716 if (game.request_active)
15719 // do not ask to play again if game was never actually played
15720 if (!game.GamePlayed)
15725 last_game_over = FALSE;
15726 game_over_delay = game_over_delay_value;
15731 if (game_over_delay > 0)
15738 if (last_game_over != game_over)
15739 game.restart_game_message = (hasStartedNetworkGame() ?
15740 "Game over! Play it again?" :
15743 last_game_over = game_over;
15746 boolean checkGameSolved(void)
15748 // set for all game engines if level was solved
15749 return game.LevelSolved_GameEnd;
15752 boolean checkGameFailed(void)
15754 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15755 return (game_em.game_over && !game_em.level_solved);
15756 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15757 return (game_sp.game_over && !game_sp.level_solved);
15758 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15759 return (game_mm.game_over && !game_mm.level_solved);
15760 else // GAME_ENGINE_TYPE_RND
15761 return (game.GameOver && !game.LevelSolved);
15764 boolean checkGameEnded(void)
15766 return (checkGameSolved() || checkGameFailed());
15770 // ----------------------------------------------------------------------------
15771 // random generator functions
15772 // ----------------------------------------------------------------------------
15774 unsigned int InitEngineRandom_RND(int seed)
15776 game.num_random_calls = 0;
15778 return InitEngineRandom(seed);
15781 unsigned int RND(int max)
15785 game.num_random_calls++;
15787 return GetEngineRandom(max);
15794 // ----------------------------------------------------------------------------
15795 // game engine snapshot handling functions
15796 // ----------------------------------------------------------------------------
15798 struct EngineSnapshotInfo
15800 // runtime values for custom element collect score
15801 int collect_score[NUM_CUSTOM_ELEMENTS];
15803 // runtime values for group element choice position
15804 int choice_pos[NUM_GROUP_ELEMENTS];
15806 // runtime values for belt position animations
15807 int belt_graphic[4][NUM_BELT_PARTS];
15808 int belt_anim_mode[4][NUM_BELT_PARTS];
15811 static struct EngineSnapshotInfo engine_snapshot_rnd;
15812 static char *snapshot_level_identifier = NULL;
15813 static int snapshot_level_nr = -1;
15815 static void SaveEngineSnapshotValues_RND(void)
15817 static int belt_base_active_element[4] =
15819 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15820 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15821 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15822 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15826 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15828 int element = EL_CUSTOM_START + i;
15830 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15833 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15835 int element = EL_GROUP_START + i;
15837 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15840 for (i = 0; i < 4; i++)
15842 for (j = 0; j < NUM_BELT_PARTS; j++)
15844 int element = belt_base_active_element[i] + j;
15845 int graphic = el2img(element);
15846 int anim_mode = graphic_info[graphic].anim_mode;
15848 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15849 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15854 static void LoadEngineSnapshotValues_RND(void)
15856 unsigned int num_random_calls = game.num_random_calls;
15859 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15861 int element = EL_CUSTOM_START + i;
15863 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15866 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15868 int element = EL_GROUP_START + i;
15870 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15873 for (i = 0; i < 4; i++)
15875 for (j = 0; j < NUM_BELT_PARTS; j++)
15877 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15878 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15880 graphic_info[graphic].anim_mode = anim_mode;
15884 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15886 InitRND(tape.random_seed);
15887 for (i = 0; i < num_random_calls; i++)
15891 if (game.num_random_calls != num_random_calls)
15893 Error("number of random calls out of sync");
15894 Error("number of random calls should be %d", num_random_calls);
15895 Error("number of random calls is %d", game.num_random_calls);
15897 Fail("this should not happen -- please debug");
15901 void FreeEngineSnapshotSingle(void)
15903 FreeSnapshotSingle();
15905 setString(&snapshot_level_identifier, NULL);
15906 snapshot_level_nr = -1;
15909 void FreeEngineSnapshotList(void)
15911 FreeSnapshotList();
15914 static ListNode *SaveEngineSnapshotBuffers(void)
15916 ListNode *buffers = NULL;
15918 // copy some special values to a structure better suited for the snapshot
15920 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15921 SaveEngineSnapshotValues_RND();
15922 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15923 SaveEngineSnapshotValues_EM();
15924 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15925 SaveEngineSnapshotValues_SP(&buffers);
15926 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15927 SaveEngineSnapshotValues_MM(&buffers);
15929 // save values stored in special snapshot structure
15931 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15932 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15933 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15934 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15935 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15936 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15937 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15938 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15940 // save further RND engine values
15942 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15943 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15944 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15946 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15947 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15948 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15949 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15950 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15952 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15953 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15954 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15956 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15958 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15959 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15961 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15962 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15963 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15964 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15965 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15966 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15967 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15968 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15969 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15970 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15971 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15972 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15973 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15974 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15975 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15976 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15977 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15978 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15980 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15981 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15983 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15984 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15985 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15987 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15988 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15990 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15991 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15992 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
15993 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15994 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15995 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15997 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15998 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16001 ListNode *node = engine_snapshot_list_rnd;
16004 while (node != NULL)
16006 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16011 Debug("game:playing:SaveEngineSnapshotBuffers",
16012 "size of engine snapshot: %d bytes", num_bytes);
16018 void SaveEngineSnapshotSingle(void)
16020 ListNode *buffers = SaveEngineSnapshotBuffers();
16022 // finally save all snapshot buffers to single snapshot
16023 SaveSnapshotSingle(buffers);
16025 // save level identification information
16026 setString(&snapshot_level_identifier, leveldir_current->identifier);
16027 snapshot_level_nr = level_nr;
16030 boolean CheckSaveEngineSnapshotToList(void)
16032 boolean save_snapshot =
16033 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16034 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16035 game.snapshot.changed_action) ||
16036 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16037 game.snapshot.collected_item));
16039 game.snapshot.changed_action = FALSE;
16040 game.snapshot.collected_item = FALSE;
16041 game.snapshot.save_snapshot = save_snapshot;
16043 return save_snapshot;
16046 void SaveEngineSnapshotToList(void)
16048 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16052 ListNode *buffers = SaveEngineSnapshotBuffers();
16054 // finally save all snapshot buffers to snapshot list
16055 SaveSnapshotToList(buffers);
16058 void SaveEngineSnapshotToListInitial(void)
16060 FreeEngineSnapshotList();
16062 SaveEngineSnapshotToList();
16065 static void LoadEngineSnapshotValues(void)
16067 // restore special values from snapshot structure
16069 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16070 LoadEngineSnapshotValues_RND();
16071 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16072 LoadEngineSnapshotValues_EM();
16073 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16074 LoadEngineSnapshotValues_SP();
16075 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16076 LoadEngineSnapshotValues_MM();
16079 void LoadEngineSnapshotSingle(void)
16081 LoadSnapshotSingle();
16083 LoadEngineSnapshotValues();
16086 static void LoadEngineSnapshot_Undo(int steps)
16088 LoadSnapshotFromList_Older(steps);
16090 LoadEngineSnapshotValues();
16093 static void LoadEngineSnapshot_Redo(int steps)
16095 LoadSnapshotFromList_Newer(steps);
16097 LoadEngineSnapshotValues();
16100 boolean CheckEngineSnapshotSingle(void)
16102 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16103 snapshot_level_nr == level_nr);
16106 boolean CheckEngineSnapshotList(void)
16108 return CheckSnapshotList();
16112 // ---------- new game button stuff -------------------------------------------
16119 boolean *setup_value;
16120 boolean allowed_on_tape;
16121 boolean is_touch_button;
16123 } gamebutton_info[NUM_GAME_BUTTONS] =
16126 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16127 GAME_CTRL_ID_STOP, NULL,
16128 TRUE, FALSE, "stop game"
16131 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16132 GAME_CTRL_ID_PAUSE, NULL,
16133 TRUE, FALSE, "pause game"
16136 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16137 GAME_CTRL_ID_PLAY, NULL,
16138 TRUE, FALSE, "play game"
16141 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16142 GAME_CTRL_ID_UNDO, NULL,
16143 TRUE, FALSE, "undo step"
16146 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16147 GAME_CTRL_ID_REDO, NULL,
16148 TRUE, FALSE, "redo step"
16151 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16152 GAME_CTRL_ID_SAVE, NULL,
16153 TRUE, FALSE, "save game"
16156 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16157 GAME_CTRL_ID_PAUSE2, NULL,
16158 TRUE, FALSE, "pause game"
16161 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16162 GAME_CTRL_ID_LOAD, NULL,
16163 TRUE, FALSE, "load game"
16166 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16167 GAME_CTRL_ID_PANEL_STOP, NULL,
16168 FALSE, FALSE, "stop game"
16171 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16172 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16173 FALSE, FALSE, "pause game"
16176 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16177 GAME_CTRL_ID_PANEL_PLAY, NULL,
16178 FALSE, FALSE, "play game"
16181 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16182 GAME_CTRL_ID_TOUCH_STOP, NULL,
16183 FALSE, TRUE, "stop game"
16186 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16187 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16188 FALSE, TRUE, "pause game"
16191 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16192 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16193 TRUE, FALSE, "background music on/off"
16196 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16197 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16198 TRUE, FALSE, "sound loops on/off"
16201 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16202 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16203 TRUE, FALSE, "normal sounds on/off"
16206 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16207 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16208 FALSE, FALSE, "background music on/off"
16211 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16212 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16213 FALSE, FALSE, "sound loops on/off"
16216 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16217 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16218 FALSE, FALSE, "normal sounds on/off"
16222 void CreateGameButtons(void)
16226 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16228 int graphic = gamebutton_info[i].graphic;
16229 struct GraphicInfo *gfx = &graphic_info[graphic];
16230 struct XY *pos = gamebutton_info[i].pos;
16231 struct GadgetInfo *gi;
16234 unsigned int event_mask;
16235 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16236 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16237 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16238 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16239 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16240 int gd_x = gfx->src_x;
16241 int gd_y = gfx->src_y;
16242 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16243 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16244 int gd_xa = gfx->src_x + gfx->active_xoffset;
16245 int gd_ya = gfx->src_y + gfx->active_yoffset;
16246 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16247 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16248 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16249 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16252 if (gfx->bitmap == NULL)
16254 game_gadget[id] = NULL;
16259 if (id == GAME_CTRL_ID_STOP ||
16260 id == GAME_CTRL_ID_PANEL_STOP ||
16261 id == GAME_CTRL_ID_TOUCH_STOP ||
16262 id == GAME_CTRL_ID_PLAY ||
16263 id == GAME_CTRL_ID_PANEL_PLAY ||
16264 id == GAME_CTRL_ID_SAVE ||
16265 id == GAME_CTRL_ID_LOAD)
16267 button_type = GD_TYPE_NORMAL_BUTTON;
16269 event_mask = GD_EVENT_RELEASED;
16271 else if (id == GAME_CTRL_ID_UNDO ||
16272 id == GAME_CTRL_ID_REDO)
16274 button_type = GD_TYPE_NORMAL_BUTTON;
16276 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16280 button_type = GD_TYPE_CHECK_BUTTON;
16281 checked = (gamebutton_info[i].setup_value != NULL ?
16282 *gamebutton_info[i].setup_value : FALSE);
16283 event_mask = GD_EVENT_PRESSED;
16286 gi = CreateGadget(GDI_CUSTOM_ID, id,
16287 GDI_IMAGE_ID, graphic,
16288 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16291 GDI_WIDTH, gfx->width,
16292 GDI_HEIGHT, gfx->height,
16293 GDI_TYPE, button_type,
16294 GDI_STATE, GD_BUTTON_UNPRESSED,
16295 GDI_CHECKED, checked,
16296 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16297 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16298 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16299 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16300 GDI_DIRECT_DRAW, FALSE,
16301 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16302 GDI_EVENT_MASK, event_mask,
16303 GDI_CALLBACK_ACTION, HandleGameButtons,
16307 Fail("cannot create gadget");
16309 game_gadget[id] = gi;
16313 void FreeGameButtons(void)
16317 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16318 FreeGadget(game_gadget[i]);
16321 static void UnmapGameButtonsAtSamePosition(int id)
16325 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16327 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16328 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16329 UnmapGadget(game_gadget[i]);
16332 static void UnmapGameButtonsAtSamePosition_All(void)
16334 if (setup.show_load_save_buttons)
16336 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16337 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16338 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16340 else if (setup.show_undo_redo_buttons)
16342 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16343 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16344 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16348 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16349 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16350 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16352 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16353 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16354 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16358 void MapLoadSaveButtons(void)
16360 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16361 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16363 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16364 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16367 void MapUndoRedoButtons(void)
16369 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16370 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16372 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16373 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16376 void ModifyPauseButtons(void)
16380 GAME_CTRL_ID_PAUSE,
16381 GAME_CTRL_ID_PAUSE2,
16382 GAME_CTRL_ID_PANEL_PAUSE,
16383 GAME_CTRL_ID_TOUCH_PAUSE,
16388 for (i = 0; ids[i] > -1; i++)
16389 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16392 static void MapGameButtonsExt(boolean on_tape)
16396 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16397 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16398 MapGadget(game_gadget[i]);
16400 UnmapGameButtonsAtSamePosition_All();
16402 RedrawGameButtons();
16405 static void UnmapGameButtonsExt(boolean on_tape)
16409 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16410 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16411 UnmapGadget(game_gadget[i]);
16414 static void RedrawGameButtonsExt(boolean on_tape)
16418 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16419 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16420 RedrawGadget(game_gadget[i]);
16423 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16428 gi->checked = state;
16431 static void RedrawSoundButtonGadget(int id)
16433 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16434 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16435 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16436 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16437 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16438 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16441 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16442 RedrawGadget(game_gadget[id2]);
16445 void MapGameButtons(void)
16447 MapGameButtonsExt(FALSE);
16450 void UnmapGameButtons(void)
16452 UnmapGameButtonsExt(FALSE);
16455 void RedrawGameButtons(void)
16457 RedrawGameButtonsExt(FALSE);
16460 void MapGameButtonsOnTape(void)
16462 MapGameButtonsExt(TRUE);
16465 void UnmapGameButtonsOnTape(void)
16467 UnmapGameButtonsExt(TRUE);
16470 void RedrawGameButtonsOnTape(void)
16472 RedrawGameButtonsExt(TRUE);
16475 static void GameUndoRedoExt(void)
16477 ClearPlayerAction();
16479 tape.pausing = TRUE;
16482 UpdateAndDisplayGameControlValues();
16484 DrawCompleteVideoDisplay();
16485 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16486 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16487 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16489 ModifyPauseButtons();
16494 static void GameUndo(int steps)
16496 if (!CheckEngineSnapshotList())
16499 int tape_property_bits = tape.property_bits;
16501 LoadEngineSnapshot_Undo(steps);
16503 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16508 static void GameRedo(int steps)
16510 if (!CheckEngineSnapshotList())
16513 int tape_property_bits = tape.property_bits;
16515 LoadEngineSnapshot_Redo(steps);
16517 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16522 static void HandleGameButtonsExt(int id, int button)
16524 static boolean game_undo_executed = FALSE;
16525 int steps = BUTTON_STEPSIZE(button);
16526 boolean handle_game_buttons =
16527 (game_status == GAME_MODE_PLAYING ||
16528 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16530 if (!handle_game_buttons)
16535 case GAME_CTRL_ID_STOP:
16536 case GAME_CTRL_ID_PANEL_STOP:
16537 case GAME_CTRL_ID_TOUCH_STOP:
16538 if (game_status == GAME_MODE_MAIN)
16544 RequestQuitGame(FALSE);
16548 case GAME_CTRL_ID_PAUSE:
16549 case GAME_CTRL_ID_PAUSE2:
16550 case GAME_CTRL_ID_PANEL_PAUSE:
16551 case GAME_CTRL_ID_TOUCH_PAUSE:
16552 if (network.enabled && game_status == GAME_MODE_PLAYING)
16555 SendToServer_ContinuePlaying();
16557 SendToServer_PausePlaying();
16560 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16562 game_undo_executed = FALSE;
16566 case GAME_CTRL_ID_PLAY:
16567 case GAME_CTRL_ID_PANEL_PLAY:
16568 if (game_status == GAME_MODE_MAIN)
16570 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16572 else if (tape.pausing)
16574 if (network.enabled)
16575 SendToServer_ContinuePlaying();
16577 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16581 case GAME_CTRL_ID_UNDO:
16582 // Important: When using "save snapshot when collecting an item" mode,
16583 // load last (current) snapshot for first "undo" after pressing "pause"
16584 // (else the last-but-one snapshot would be loaded, because the snapshot
16585 // pointer already points to the last snapshot when pressing "pause",
16586 // which is fine for "every step/move" mode, but not for "every collect")
16587 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16588 !game_undo_executed)
16591 game_undo_executed = TRUE;
16596 case GAME_CTRL_ID_REDO:
16600 case GAME_CTRL_ID_SAVE:
16604 case GAME_CTRL_ID_LOAD:
16608 case SOUND_CTRL_ID_MUSIC:
16609 case SOUND_CTRL_ID_PANEL_MUSIC:
16610 if (setup.sound_music)
16612 setup.sound_music = FALSE;
16616 else if (audio.music_available)
16618 setup.sound = setup.sound_music = TRUE;
16620 SetAudioMode(setup.sound);
16622 if (game_status == GAME_MODE_PLAYING)
16626 RedrawSoundButtonGadget(id);
16630 case SOUND_CTRL_ID_LOOPS:
16631 case SOUND_CTRL_ID_PANEL_LOOPS:
16632 if (setup.sound_loops)
16633 setup.sound_loops = FALSE;
16634 else if (audio.loops_available)
16636 setup.sound = setup.sound_loops = TRUE;
16638 SetAudioMode(setup.sound);
16641 RedrawSoundButtonGadget(id);
16645 case SOUND_CTRL_ID_SIMPLE:
16646 case SOUND_CTRL_ID_PANEL_SIMPLE:
16647 if (setup.sound_simple)
16648 setup.sound_simple = FALSE;
16649 else if (audio.sound_available)
16651 setup.sound = setup.sound_simple = TRUE;
16653 SetAudioMode(setup.sound);
16656 RedrawSoundButtonGadget(id);
16665 static void HandleGameButtons(struct GadgetInfo *gi)
16667 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16670 void HandleSoundButtonKeys(Key key)
16672 if (key == setup.shortcut.sound_simple)
16673 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16674 else if (key == setup.shortcut.sound_loops)
16675 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16676 else if (key == setup.shortcut.sound_music)
16677 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);