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_RESTART 8
1021 #define GAME_CTRL_ID_PANEL_STOP 9
1022 #define GAME_CTRL_ID_PANEL_PAUSE 10
1023 #define GAME_CTRL_ID_PANEL_PLAY 11
1024 #define GAME_CTRL_ID_PANEL_RESTART 12
1025 #define GAME_CTRL_ID_TOUCH_STOP 13
1026 #define GAME_CTRL_ID_TOUCH_PAUSE 14
1027 #define GAME_CTRL_ID_TOUCH_RESTART 15
1028 #define SOUND_CTRL_ID_MUSIC 16
1029 #define SOUND_CTRL_ID_LOOPS 17
1030 #define SOUND_CTRL_ID_SIMPLE 18
1031 #define SOUND_CTRL_ID_PANEL_MUSIC 19
1032 #define SOUND_CTRL_ID_PANEL_LOOPS 20
1033 #define SOUND_CTRL_ID_PANEL_SIMPLE 21
1035 #define NUM_GAME_BUTTONS 22
1038 // forward declaration for internal use
1040 static void CreateField(int, int, int);
1042 static void ResetGfxAnimation(int, int);
1044 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1045 static void AdvanceFrameAndPlayerCounters(int);
1047 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1048 static boolean MovePlayer(struct PlayerInfo *, int, int);
1049 static void ScrollPlayer(struct PlayerInfo *, int);
1050 static void ScrollScreen(struct PlayerInfo *, int);
1052 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1053 static boolean DigFieldByCE(int, int, int);
1054 static boolean SnapField(struct PlayerInfo *, int, int);
1055 static boolean DropElement(struct PlayerInfo *);
1057 static void InitBeltMovement(void);
1058 static void CloseAllOpenTimegates(void);
1059 static void CheckGravityMovement(struct PlayerInfo *);
1060 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1061 static void KillPlayerUnlessEnemyProtected(int, int);
1062 static void KillPlayerUnlessExplosionProtected(int, int);
1064 static void CheckNextToConditions(int, int);
1065 static void TestIfPlayerNextToCustomElement(int, int);
1066 static void TestIfPlayerTouchesCustomElement(int, int);
1067 static void TestIfElementNextToCustomElement(int, int);
1068 static void TestIfElementTouchesCustomElement(int, int);
1069 static void TestIfElementHitsCustomElement(int, int, int);
1071 static void HandleElementChange(int, int, int);
1072 static void ExecuteCustomElementAction(int, int, int, int);
1073 static boolean ChangeElement(int, int, int, int);
1075 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int, int, int);
1076 #define CheckTriggeredElementChange(x, y, e, ev) \
1077 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, -1)
1078 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1079 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1080 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1081 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1082 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1083 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1084 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1085 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1087 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1088 #define CheckElementChange(x, y, e, te, ev) \
1089 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1090 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1091 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1092 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1093 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1094 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1095 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1097 static void PlayLevelSound(int, int, int);
1098 static void PlayLevelSoundNearest(int, int, int);
1099 static void PlayLevelSoundAction(int, int, int);
1100 static void PlayLevelSoundElementAction(int, int, int, int);
1101 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1102 static void PlayLevelSoundActionIfLoop(int, int, int);
1103 static void StopLevelSoundActionIfLoop(int, int, int);
1104 static void PlayLevelMusic(void);
1105 static void FadeLevelSoundsAndMusic(void);
1107 static void HandleGameButtons(struct GadgetInfo *);
1109 int AmoebaNeighbourNr(int, int);
1110 void AmoebaToDiamond(int, int);
1111 void ContinueMoving(int, int);
1112 void Bang(int, int);
1113 void InitMovDir(int, int);
1114 void InitAmoebaNr(int, int);
1115 void NewHighScore(int, boolean);
1117 void TestIfGoodThingHitsBadThing(int, int, int);
1118 void TestIfBadThingHitsGoodThing(int, int, int);
1119 void TestIfPlayerTouchesBadThing(int, int);
1120 void TestIfPlayerRunsIntoBadThing(int, int, int);
1121 void TestIfBadThingTouchesPlayer(int, int);
1122 void TestIfBadThingRunsIntoPlayer(int, int, int);
1123 void TestIfFriendTouchesBadThing(int, int);
1124 void TestIfBadThingTouchesFriend(int, int);
1125 void TestIfBadThingTouchesOtherBadThing(int, int);
1126 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1128 void KillPlayer(struct PlayerInfo *);
1129 void BuryPlayer(struct PlayerInfo *);
1130 void RemovePlayer(struct PlayerInfo *);
1131 void ExitPlayer(struct PlayerInfo *);
1133 static int getInvisibleActiveFromInvisibleElement(int);
1134 static int getInvisibleFromInvisibleActiveElement(int);
1136 static void TestFieldAfterSnapping(int, int, int, int, int);
1138 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1140 // for detection of endless loops, caused by custom element programming
1141 // (using maximal playfield width x 10 is just a rough approximation)
1142 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1144 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1146 if (recursion_loop_detected) \
1149 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1151 recursion_loop_detected = TRUE; \
1152 recursion_loop_element = (e); \
1155 recursion_loop_depth++; \
1158 #define RECURSION_LOOP_DETECTION_END() \
1160 recursion_loop_depth--; \
1163 static int recursion_loop_depth;
1164 static boolean recursion_loop_detected;
1165 static boolean recursion_loop_element;
1167 static int map_player_action[MAX_PLAYERS];
1170 // ----------------------------------------------------------------------------
1171 // definition of elements that automatically change to other elements after
1172 // a specified time, eventually calling a function when changing
1173 // ----------------------------------------------------------------------------
1175 // forward declaration for changer functions
1176 static void InitBuggyBase(int, int);
1177 static void WarnBuggyBase(int, int);
1179 static void InitTrap(int, int);
1180 static void ActivateTrap(int, int);
1181 static void ChangeActiveTrap(int, int);
1183 static void InitRobotWheel(int, int);
1184 static void RunRobotWheel(int, int);
1185 static void StopRobotWheel(int, int);
1187 static void InitTimegateWheel(int, int);
1188 static void RunTimegateWheel(int, int);
1190 static void InitMagicBallDelay(int, int);
1191 static void ActivateMagicBall(int, int);
1193 struct ChangingElementInfo
1198 void (*pre_change_function)(int x, int y);
1199 void (*change_function)(int x, int y);
1200 void (*post_change_function)(int x, int y);
1203 static struct ChangingElementInfo change_delay_list[] =
1238 EL_STEEL_EXIT_OPENING,
1246 EL_STEEL_EXIT_CLOSING,
1247 EL_STEEL_EXIT_CLOSED,
1270 EL_EM_STEEL_EXIT_OPENING,
1271 EL_EM_STEEL_EXIT_OPEN,
1278 EL_EM_STEEL_EXIT_CLOSING,
1302 EL_SWITCHGATE_OPENING,
1310 EL_SWITCHGATE_CLOSING,
1311 EL_SWITCHGATE_CLOSED,
1318 EL_TIMEGATE_OPENING,
1326 EL_TIMEGATE_CLOSING,
1335 EL_ACID_SPLASH_LEFT,
1343 EL_ACID_SPLASH_RIGHT,
1352 EL_SP_BUGGY_BASE_ACTIVATING,
1359 EL_SP_BUGGY_BASE_ACTIVATING,
1360 EL_SP_BUGGY_BASE_ACTIVE,
1367 EL_SP_BUGGY_BASE_ACTIVE,
1391 EL_ROBOT_WHEEL_ACTIVE,
1399 EL_TIMEGATE_SWITCH_ACTIVE,
1407 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1408 EL_DC_TIMEGATE_SWITCH,
1415 EL_EMC_MAGIC_BALL_ACTIVE,
1416 EL_EMC_MAGIC_BALL_ACTIVE,
1423 EL_EMC_SPRING_BUMPER_ACTIVE,
1424 EL_EMC_SPRING_BUMPER,
1431 EL_DIAGONAL_SHRINKING,
1439 EL_DIAGONAL_GROWING,
1460 int push_delay_fixed, push_delay_random;
1464 { EL_SPRING, 0, 0 },
1465 { EL_BALLOON, 0, 0 },
1467 { EL_SOKOBAN_OBJECT, 2, 0 },
1468 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1469 { EL_SATELLITE, 2, 0 },
1470 { EL_SP_DISK_YELLOW, 2, 0 },
1472 { EL_UNDEFINED, 0, 0 },
1480 move_stepsize_list[] =
1482 { EL_AMOEBA_DROP, 2 },
1483 { EL_AMOEBA_DROPPING, 2 },
1484 { EL_QUICKSAND_FILLING, 1 },
1485 { EL_QUICKSAND_EMPTYING, 1 },
1486 { EL_QUICKSAND_FAST_FILLING, 2 },
1487 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1488 { EL_MAGIC_WALL_FILLING, 2 },
1489 { EL_MAGIC_WALL_EMPTYING, 2 },
1490 { EL_BD_MAGIC_WALL_FILLING, 2 },
1491 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1492 { EL_DC_MAGIC_WALL_FILLING, 2 },
1493 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1495 { EL_UNDEFINED, 0 },
1503 collect_count_list[] =
1506 { EL_BD_DIAMOND, 1 },
1507 { EL_EMERALD_YELLOW, 1 },
1508 { EL_EMERALD_RED, 1 },
1509 { EL_EMERALD_PURPLE, 1 },
1511 { EL_SP_INFOTRON, 1 },
1515 { EL_UNDEFINED, 0 },
1523 access_direction_list[] =
1525 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1526 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1527 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1528 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1529 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1530 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1531 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1532 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1533 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1534 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1535 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1537 { EL_SP_PORT_LEFT, MV_RIGHT },
1538 { EL_SP_PORT_RIGHT, MV_LEFT },
1539 { EL_SP_PORT_UP, MV_DOWN },
1540 { EL_SP_PORT_DOWN, MV_UP },
1541 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1542 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1543 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1544 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1545 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1546 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1547 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1548 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1549 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1550 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1551 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1552 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1553 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1554 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1555 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1557 { EL_UNDEFINED, MV_NONE }
1560 static struct XY xy_topdown[] =
1568 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1570 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1571 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1572 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1573 IS_JUST_CHANGING(x, y))
1575 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1577 // static variables for playfield scan mode (scanning forward or backward)
1578 static int playfield_scan_start_x = 0;
1579 static int playfield_scan_start_y = 0;
1580 static int playfield_scan_delta_x = 1;
1581 static int playfield_scan_delta_y = 1;
1583 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1584 (y) >= 0 && (y) <= lev_fieldy - 1; \
1585 (y) += playfield_scan_delta_y) \
1586 for ((x) = playfield_scan_start_x; \
1587 (x) >= 0 && (x) <= lev_fieldx - 1; \
1588 (x) += playfield_scan_delta_x)
1591 void DEBUG_SetMaximumDynamite(void)
1595 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1596 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1597 local_player->inventory_element[local_player->inventory_size++] =
1602 static void InitPlayfieldScanModeVars(void)
1604 if (game.use_reverse_scan_direction)
1606 playfield_scan_start_x = lev_fieldx - 1;
1607 playfield_scan_start_y = lev_fieldy - 1;
1609 playfield_scan_delta_x = -1;
1610 playfield_scan_delta_y = -1;
1614 playfield_scan_start_x = 0;
1615 playfield_scan_start_y = 0;
1617 playfield_scan_delta_x = 1;
1618 playfield_scan_delta_y = 1;
1622 static void InitPlayfieldScanMode(int mode)
1624 game.use_reverse_scan_direction =
1625 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1627 InitPlayfieldScanModeVars();
1630 static int get_move_delay_from_stepsize(int move_stepsize)
1633 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1635 // make sure that stepsize value is always a power of 2
1636 move_stepsize = (1 << log_2(move_stepsize));
1638 return TILEX / move_stepsize;
1641 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1644 int player_nr = player->index_nr;
1645 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1646 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1648 // do no immediately change move delay -- the player might just be moving
1649 player->move_delay_value_next = move_delay;
1651 // information if player can move must be set separately
1652 player->cannot_move = cannot_move;
1656 player->move_delay = game.initial_move_delay[player_nr];
1657 player->move_delay_value = game.initial_move_delay_value[player_nr];
1659 player->move_delay_value_next = -1;
1661 player->move_delay_reset_counter = 0;
1665 void GetPlayerConfig(void)
1667 GameFrameDelay = setup.game_frame_delay;
1669 if (!audio.sound_available)
1670 setup.sound_simple = FALSE;
1672 if (!audio.loops_available)
1673 setup.sound_loops = FALSE;
1675 if (!audio.music_available)
1676 setup.sound_music = FALSE;
1678 if (!video.fullscreen_available)
1679 setup.fullscreen = FALSE;
1681 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1683 SetAudioMode(setup.sound);
1686 int GetElementFromGroupElement(int element)
1688 if (IS_GROUP_ELEMENT(element))
1690 struct ElementGroupInfo *group = element_info[element].group;
1691 int last_anim_random_frame = gfx.anim_random_frame;
1694 if (group->choice_mode == ANIM_RANDOM)
1695 gfx.anim_random_frame = RND(group->num_elements_resolved);
1697 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1698 group->choice_mode, 0,
1701 if (group->choice_mode == ANIM_RANDOM)
1702 gfx.anim_random_frame = last_anim_random_frame;
1704 group->choice_pos++;
1706 element = group->element_resolved[element_pos];
1712 static void IncrementSokobanFieldsNeeded(void)
1714 if (level.sb_fields_needed)
1715 game.sokoban_fields_still_needed++;
1718 static void IncrementSokobanObjectsNeeded(void)
1720 if (level.sb_objects_needed)
1721 game.sokoban_objects_still_needed++;
1724 static void DecrementSokobanFieldsNeeded(void)
1726 if (game.sokoban_fields_still_needed > 0)
1727 game.sokoban_fields_still_needed--;
1730 static void DecrementSokobanObjectsNeeded(void)
1732 if (game.sokoban_objects_still_needed > 0)
1733 game.sokoban_objects_still_needed--;
1736 static void InitPlayerField(int x, int y, int element, boolean init_game)
1738 if (element == EL_SP_MURPHY)
1742 if (stored_player[0].present)
1744 Tile[x][y] = EL_SP_MURPHY_CLONE;
1750 stored_player[0].initial_element = element;
1751 stored_player[0].use_murphy = TRUE;
1753 if (!level.use_artwork_element[0])
1754 stored_player[0].artwork_element = EL_SP_MURPHY;
1757 Tile[x][y] = EL_PLAYER_1;
1763 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1764 int jx = player->jx, jy = player->jy;
1766 player->present = TRUE;
1768 player->block_last_field = (element == EL_SP_MURPHY ?
1769 level.sp_block_last_field :
1770 level.block_last_field);
1772 // ---------- initialize player's last field block delay ------------------
1774 // always start with reliable default value (no adjustment needed)
1775 player->block_delay_adjustment = 0;
1777 // special case 1: in Supaplex, Murphy blocks last field one more frame
1778 if (player->block_last_field && element == EL_SP_MURPHY)
1779 player->block_delay_adjustment = 1;
1781 // special case 2: in game engines before 3.1.1, blocking was different
1782 if (game.use_block_last_field_bug)
1783 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1785 if (!network.enabled || player->connected_network)
1787 player->active = TRUE;
1789 // remove potentially duplicate players
1790 if (IN_LEV_FIELD(jx, jy) && StorePlayer[jx][jy] == Tile[x][y])
1791 StorePlayer[jx][jy] = 0;
1793 StorePlayer[x][y] = Tile[x][y];
1795 #if DEBUG_INIT_PLAYER
1796 Debug("game:init:player", "- player element %d activated",
1797 player->element_nr);
1798 Debug("game:init:player", " (local player is %d and currently %s)",
1799 local_player->element_nr,
1800 local_player->active ? "active" : "not active");
1804 Tile[x][y] = EL_EMPTY;
1806 player->jx = player->last_jx = x;
1807 player->jy = player->last_jy = y;
1810 // always check if player was just killed and should be reanimated
1812 int player_nr = GET_PLAYER_NR(element);
1813 struct PlayerInfo *player = &stored_player[player_nr];
1815 if (player->active && player->killed)
1816 player->reanimated = TRUE; // if player was just killed, reanimate him
1820 static void InitField(int x, int y, boolean init_game)
1822 int element = Tile[x][y];
1831 InitPlayerField(x, y, element, init_game);
1834 case EL_SOKOBAN_FIELD_PLAYER:
1835 element = Tile[x][y] = EL_PLAYER_1;
1836 InitField(x, y, init_game);
1838 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1839 InitField(x, y, init_game);
1842 case EL_SOKOBAN_FIELD_EMPTY:
1843 IncrementSokobanFieldsNeeded();
1846 case EL_SOKOBAN_OBJECT:
1847 IncrementSokobanObjectsNeeded();
1851 if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID)
1852 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1853 else if (x > 0 && Tile[x - 1][y] == EL_ACID)
1854 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1855 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT)
1856 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1857 else if (y > 0 && Tile[x][y - 1] == EL_ACID)
1858 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1859 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT)
1860 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1869 case EL_SPACESHIP_RIGHT:
1870 case EL_SPACESHIP_UP:
1871 case EL_SPACESHIP_LEFT:
1872 case EL_SPACESHIP_DOWN:
1873 case EL_BD_BUTTERFLY:
1874 case EL_BD_BUTTERFLY_RIGHT:
1875 case EL_BD_BUTTERFLY_UP:
1876 case EL_BD_BUTTERFLY_LEFT:
1877 case EL_BD_BUTTERFLY_DOWN:
1879 case EL_BD_FIREFLY_RIGHT:
1880 case EL_BD_FIREFLY_UP:
1881 case EL_BD_FIREFLY_LEFT:
1882 case EL_BD_FIREFLY_DOWN:
1883 case EL_PACMAN_RIGHT:
1885 case EL_PACMAN_LEFT:
1886 case EL_PACMAN_DOWN:
1888 case EL_YAMYAM_LEFT:
1889 case EL_YAMYAM_RIGHT:
1891 case EL_YAMYAM_DOWN:
1892 case EL_DARK_YAMYAM:
1895 case EL_SP_SNIKSNAK:
1896 case EL_SP_ELECTRON:
1902 case EL_SPRING_LEFT:
1903 case EL_SPRING_RIGHT:
1907 case EL_AMOEBA_FULL:
1912 case EL_AMOEBA_DROP:
1913 if (y == lev_fieldy - 1)
1915 Tile[x][y] = EL_AMOEBA_GROWING;
1916 Store[x][y] = EL_AMOEBA_WET;
1920 case EL_DYNAMITE_ACTIVE:
1921 case EL_SP_DISK_RED_ACTIVE:
1922 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1923 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1924 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1925 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1926 MovDelay[x][y] = 96;
1929 case EL_EM_DYNAMITE_ACTIVE:
1930 MovDelay[x][y] = 32;
1934 game.lights_still_needed++;
1938 game.friends_still_needed++;
1943 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1946 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1947 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1948 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1949 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1950 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1951 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1952 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1953 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1954 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1955 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1956 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1957 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1960 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1961 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1962 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1964 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1966 game.belt_dir[belt_nr] = belt_dir;
1967 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1969 else // more than one switch -- set it like the first switch
1971 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1976 case EL_LIGHT_SWITCH_ACTIVE:
1978 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1981 case EL_INVISIBLE_STEELWALL:
1982 case EL_INVISIBLE_WALL:
1983 case EL_INVISIBLE_SAND:
1984 if (game.light_time_left > 0 ||
1985 game.lenses_time_left > 0)
1986 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1989 case EL_EMC_MAGIC_BALL:
1990 if (game.ball_active)
1991 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1994 case EL_EMC_MAGIC_BALL_SWITCH:
1995 if (game.ball_active)
1996 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1999 case EL_TRIGGER_PLAYER:
2000 case EL_TRIGGER_ELEMENT:
2001 case EL_TRIGGER_CE_VALUE:
2002 case EL_TRIGGER_CE_SCORE:
2004 case EL_ANY_ELEMENT:
2005 case EL_CURRENT_CE_VALUE:
2006 case EL_CURRENT_CE_SCORE:
2023 // reference elements should not be used on the playfield
2024 Tile[x][y] = EL_EMPTY;
2028 if (IS_CUSTOM_ELEMENT(element))
2030 if (CAN_MOVE(element))
2033 if (!element_info[element].use_last_ce_value || init_game)
2034 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2036 else if (IS_GROUP_ELEMENT(element))
2038 Tile[x][y] = GetElementFromGroupElement(element);
2040 InitField(x, y, init_game);
2042 else if (IS_EMPTY_ELEMENT(element))
2044 GfxElementEmpty[x][y] = element;
2045 Tile[x][y] = EL_EMPTY;
2047 if (element_info[element].use_gfx_element)
2048 game.use_masked_elements = TRUE;
2055 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2058 static void InitField_WithBug1(int x, int y, boolean init_game)
2060 InitField(x, y, init_game);
2062 // not needed to call InitMovDir() -- already done by InitField()!
2063 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2064 CAN_MOVE(Tile[x][y]))
2068 static void InitField_WithBug2(int x, int y, boolean init_game)
2070 int old_element = Tile[x][y];
2072 InitField(x, y, init_game);
2074 // not needed to call InitMovDir() -- already done by InitField()!
2075 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2076 CAN_MOVE(old_element) &&
2077 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2080 /* this case is in fact a combination of not less than three bugs:
2081 first, it calls InitMovDir() for elements that can move, although this is
2082 already done by InitField(); then, it checks the element that was at this
2083 field _before_ the call to InitField() (which can change it); lastly, it
2084 was not called for "mole with direction" elements, which were treated as
2085 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2089 static int get_key_element_from_nr(int key_nr)
2091 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2092 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2093 EL_EM_KEY_1 : EL_KEY_1);
2095 return key_base_element + key_nr;
2098 static int get_next_dropped_element(struct PlayerInfo *player)
2100 return (player->inventory_size > 0 ?
2101 player->inventory_element[player->inventory_size - 1] :
2102 player->inventory_infinite_element != EL_UNDEFINED ?
2103 player->inventory_infinite_element :
2104 player->dynabombs_left > 0 ?
2105 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2109 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2111 // pos >= 0: get element from bottom of the stack;
2112 // pos < 0: get element from top of the stack
2116 int min_inventory_size = -pos;
2117 int inventory_pos = player->inventory_size - min_inventory_size;
2118 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2120 return (player->inventory_size >= min_inventory_size ?
2121 player->inventory_element[inventory_pos] :
2122 player->inventory_infinite_element != EL_UNDEFINED ?
2123 player->inventory_infinite_element :
2124 player->dynabombs_left >= min_dynabombs_left ?
2125 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2130 int min_dynabombs_left = pos + 1;
2131 int min_inventory_size = pos + 1 - player->dynabombs_left;
2132 int inventory_pos = pos - player->dynabombs_left;
2134 return (player->inventory_infinite_element != EL_UNDEFINED ?
2135 player->inventory_infinite_element :
2136 player->dynabombs_left >= min_dynabombs_left ?
2137 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2138 player->inventory_size >= min_inventory_size ?
2139 player->inventory_element[inventory_pos] :
2144 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2146 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2147 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2150 if (gpo1->sort_priority != gpo2->sort_priority)
2151 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2153 compare_result = gpo1->nr - gpo2->nr;
2155 return compare_result;
2158 int getPlayerInventorySize(int player_nr)
2160 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2161 return game_em.ply[player_nr]->dynamite;
2162 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2163 return game_sp.red_disk_count;
2165 return stored_player[player_nr].inventory_size;
2168 static void InitGameControlValues(void)
2172 for (i = 0; game_panel_controls[i].nr != -1; i++)
2174 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2175 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2176 struct TextPosInfo *pos = gpc->pos;
2178 int type = gpc->type;
2182 Error("'game_panel_controls' structure corrupted at %d", i);
2184 Fail("this should not happen -- please debug");
2187 // force update of game controls after initialization
2188 gpc->value = gpc->last_value = -1;
2189 gpc->frame = gpc->last_frame = -1;
2190 gpc->gfx_frame = -1;
2192 // determine panel value width for later calculation of alignment
2193 if (type == TYPE_INTEGER || type == TYPE_STRING)
2195 pos->width = pos->size * getFontWidth(pos->font);
2196 pos->height = getFontHeight(pos->font);
2198 else if (type == TYPE_ELEMENT)
2200 pos->width = pos->size;
2201 pos->height = pos->size;
2204 // fill structure for game panel draw order
2206 gpo->sort_priority = pos->sort_priority;
2209 // sort game panel controls according to sort_priority and control number
2210 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2211 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2214 static void UpdatePlayfieldElementCount(void)
2216 boolean use_element_count = FALSE;
2219 // first check if it is needed at all to calculate playfield element count
2220 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2221 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2222 use_element_count = TRUE;
2224 if (!use_element_count)
2227 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2228 element_info[i].element_count = 0;
2230 SCAN_PLAYFIELD(x, y)
2232 element_info[Tile[x][y]].element_count++;
2235 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2236 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2237 if (IS_IN_GROUP(j, i))
2238 element_info[EL_GROUP_START + i].element_count +=
2239 element_info[j].element_count;
2242 static void UpdateGameControlValues(void)
2245 int time = (game.LevelSolved ?
2246 game.LevelSolved_CountingTime :
2247 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2249 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2250 game_sp.time_played :
2251 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2252 game_mm.energy_left :
2253 game.no_level_time_limit ? TimePlayed : TimeLeft);
2254 int score = (game.LevelSolved ?
2255 game.LevelSolved_CountingScore :
2256 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2257 game_em.lev->score :
2258 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2260 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2263 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2264 game_em.lev->gems_needed :
2265 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2266 game_sp.infotrons_still_needed :
2267 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2268 game_mm.kettles_still_needed :
2269 game.gems_still_needed);
2270 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2271 game_em.lev->gems_needed > 0 :
2272 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2273 game_sp.infotrons_still_needed > 0 :
2274 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2275 game_mm.kettles_still_needed > 0 ||
2276 game_mm.lights_still_needed > 0 :
2277 game.gems_still_needed > 0 ||
2278 game.sokoban_fields_still_needed > 0 ||
2279 game.sokoban_objects_still_needed > 0 ||
2280 game.lights_still_needed > 0);
2281 int health = (game.LevelSolved ?
2282 game.LevelSolved_CountingHealth :
2283 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2284 MM_HEALTH(game_mm.laser_overload_value) :
2286 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2288 UpdatePlayfieldElementCount();
2290 // update game panel control values
2292 // used instead of "level_nr" (for network games)
2293 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2294 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2296 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2297 for (i = 0; i < MAX_NUM_KEYS; i++)
2298 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2299 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2300 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2302 if (game.centered_player_nr == -1)
2304 for (i = 0; i < MAX_PLAYERS; i++)
2306 // only one player in Supaplex game engine
2307 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2310 for (k = 0; k < MAX_NUM_KEYS; k++)
2312 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2314 if (game_em.ply[i]->keys & (1 << k))
2315 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2316 get_key_element_from_nr(k);
2318 else if (stored_player[i].key[k])
2319 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2320 get_key_element_from_nr(k);
2323 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2324 getPlayerInventorySize(i);
2326 if (stored_player[i].num_white_keys > 0)
2327 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2330 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2331 stored_player[i].num_white_keys;
2336 int player_nr = game.centered_player_nr;
2338 for (k = 0; k < MAX_NUM_KEYS; k++)
2340 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2342 if (game_em.ply[player_nr]->keys & (1 << k))
2343 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2344 get_key_element_from_nr(k);
2346 else if (stored_player[player_nr].key[k])
2347 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2348 get_key_element_from_nr(k);
2351 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2352 getPlayerInventorySize(player_nr);
2354 if (stored_player[player_nr].num_white_keys > 0)
2355 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2357 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2358 stored_player[player_nr].num_white_keys;
2361 // re-arrange keys on game panel, if needed or if defined by style settings
2362 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2364 int nr = GAME_PANEL_KEY_1 + i;
2365 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2366 struct TextPosInfo *pos = gpc->pos;
2368 // skip check if key is not in the player's inventory
2369 if (gpc->value == EL_EMPTY)
2372 // check if keys should be arranged on panel from left to right
2373 if (pos->style == STYLE_LEFTMOST_POSITION)
2375 // check previous key positions (left from current key)
2376 for (k = 0; k < i; k++)
2378 int nr_new = GAME_PANEL_KEY_1 + k;
2380 if (game_panel_controls[nr_new].value == EL_EMPTY)
2382 game_panel_controls[nr_new].value = gpc->value;
2383 gpc->value = EL_EMPTY;
2390 // check if "undefined" keys can be placed at some other position
2391 if (pos->x == -1 && pos->y == -1)
2393 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2395 // 1st try: display key at the same position as normal or EM keys
2396 if (game_panel_controls[nr_new].value == EL_EMPTY)
2398 game_panel_controls[nr_new].value = gpc->value;
2402 // 2nd try: display key at the next free position in the key panel
2403 for (k = 0; k < STD_NUM_KEYS; k++)
2405 nr_new = GAME_PANEL_KEY_1 + k;
2407 if (game_panel_controls[nr_new].value == EL_EMPTY)
2409 game_panel_controls[nr_new].value = gpc->value;
2418 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2420 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2421 get_inventory_element_from_pos(local_player, i);
2422 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2423 get_inventory_element_from_pos(local_player, -i - 1);
2426 game_panel_controls[GAME_PANEL_SCORE].value = score;
2427 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2429 game_panel_controls[GAME_PANEL_TIME].value = time;
2431 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2432 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2433 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2435 if (level.time == 0)
2436 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2438 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2440 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2441 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2443 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2445 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2446 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2448 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2449 local_player->shield_normal_time_left;
2450 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2451 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2453 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2454 local_player->shield_deadly_time_left;
2456 game_panel_controls[GAME_PANEL_EXIT].value =
2457 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2459 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2460 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2461 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2462 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2463 EL_EMC_MAGIC_BALL_SWITCH);
2465 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2466 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2467 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2468 game.light_time_left;
2470 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2471 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2472 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2473 game.timegate_time_left;
2475 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2476 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2478 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2479 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2480 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2481 game.lenses_time_left;
2483 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2484 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2485 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2486 game.magnify_time_left;
2488 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2489 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2490 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2491 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2492 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2493 EL_BALLOON_SWITCH_NONE);
2495 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2496 local_player->dynabomb_count;
2497 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2498 local_player->dynabomb_size;
2499 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2500 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2502 game_panel_controls[GAME_PANEL_PENGUINS].value =
2503 game.friends_still_needed;
2505 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2506 game.sokoban_objects_still_needed;
2507 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2508 game.sokoban_fields_still_needed;
2510 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2511 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2513 for (i = 0; i < NUM_BELTS; i++)
2515 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2516 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2517 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2518 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2519 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2522 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2523 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2524 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2525 game.magic_wall_time_left;
2527 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2528 local_player->gravity;
2530 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2531 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2533 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2534 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2535 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2536 game.panel.element[i].id : EL_UNDEFINED);
2538 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2539 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2540 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2541 element_info[game.panel.element_count[i].id].element_count : 0);
2543 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2544 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2545 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2546 element_info[game.panel.ce_score[i].id].collect_score : 0);
2548 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2549 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2550 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2551 element_info[game.panel.ce_score_element[i].id].collect_score :
2554 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2555 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2556 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2558 // update game panel control frames
2560 for (i = 0; game_panel_controls[i].nr != -1; i++)
2562 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2564 if (gpc->type == TYPE_ELEMENT)
2566 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2568 int last_anim_random_frame = gfx.anim_random_frame;
2569 int element = gpc->value;
2570 int graphic = el2panelimg(element);
2571 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2573 graphic_info[graphic].anim_global_anim_sync ?
2574 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2576 if (gpc->value != gpc->last_value)
2579 gpc->gfx_random = init_gfx_random;
2585 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2586 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2587 gpc->gfx_random = init_gfx_random;
2590 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2591 gfx.anim_random_frame = gpc->gfx_random;
2593 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2594 gpc->gfx_frame = element_info[element].collect_score;
2596 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2598 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2599 gfx.anim_random_frame = last_anim_random_frame;
2602 else if (gpc->type == TYPE_GRAPHIC)
2604 if (gpc->graphic != IMG_UNDEFINED)
2606 int last_anim_random_frame = gfx.anim_random_frame;
2607 int graphic = gpc->graphic;
2608 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2610 graphic_info[graphic].anim_global_anim_sync ?
2611 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2613 if (gpc->value != gpc->last_value)
2616 gpc->gfx_random = init_gfx_random;
2622 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2623 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2624 gpc->gfx_random = init_gfx_random;
2627 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2628 gfx.anim_random_frame = gpc->gfx_random;
2630 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2632 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2633 gfx.anim_random_frame = last_anim_random_frame;
2639 static void DisplayGameControlValues(void)
2641 boolean redraw_panel = FALSE;
2644 for (i = 0; game_panel_controls[i].nr != -1; i++)
2646 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2648 if (PANEL_DEACTIVATED(gpc->pos))
2651 if (gpc->value == gpc->last_value &&
2652 gpc->frame == gpc->last_frame)
2655 redraw_panel = TRUE;
2661 // copy default game door content to main double buffer
2663 // !!! CHECK AGAIN !!!
2664 SetPanelBackground();
2665 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2666 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2668 // redraw game control buttons
2669 RedrawGameButtons();
2671 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2673 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2675 int nr = game_panel_order[i].nr;
2676 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2677 struct TextPosInfo *pos = gpc->pos;
2678 int type = gpc->type;
2679 int value = gpc->value;
2680 int frame = gpc->frame;
2681 int size = pos->size;
2682 int font = pos->font;
2683 boolean draw_masked = pos->draw_masked;
2684 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2686 if (PANEL_DEACTIVATED(pos))
2689 if (pos->class == get_hash_from_key("extra_panel_items") &&
2690 !setup.prefer_extra_panel_items)
2693 gpc->last_value = value;
2694 gpc->last_frame = frame;
2696 if (type == TYPE_INTEGER)
2698 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2699 nr == GAME_PANEL_INVENTORY_COUNT ||
2700 nr == GAME_PANEL_SCORE ||
2701 nr == GAME_PANEL_HIGHSCORE ||
2702 nr == GAME_PANEL_TIME)
2704 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2706 if (use_dynamic_size) // use dynamic number of digits
2708 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 :
2709 nr == GAME_PANEL_INVENTORY_COUNT ||
2710 nr == GAME_PANEL_TIME ? 1000 : 100000);
2711 int size_add = (nr == GAME_PANEL_LEVEL_NUMBER ||
2712 nr == GAME_PANEL_INVENTORY_COUNT ||
2713 nr == GAME_PANEL_TIME ? 1 : 2);
2714 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 :
2715 nr == GAME_PANEL_INVENTORY_COUNT ||
2716 nr == GAME_PANEL_TIME ? 3 : 5);
2717 int size2 = size1 + size_add;
2718 int font1 = pos->font;
2719 int font2 = pos->font_alt;
2721 size = (value < value_change ? size1 : size2);
2722 font = (value < value_change ? font1 : font2);
2726 // correct text size if "digits" is zero or less
2728 size = strlen(int2str(value, size));
2730 // dynamically correct text alignment
2731 pos->width = size * getFontWidth(font);
2733 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2734 int2str(value, size), font, mask_mode);
2736 else if (type == TYPE_ELEMENT)
2738 int element, graphic;
2742 int dst_x = PANEL_XPOS(pos);
2743 int dst_y = PANEL_YPOS(pos);
2745 if (value != EL_UNDEFINED && value != EL_EMPTY)
2748 graphic = el2panelimg(value);
2751 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2752 element, EL_NAME(element), size);
2755 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2758 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2761 width = graphic_info[graphic].width * size / TILESIZE;
2762 height = graphic_info[graphic].height * size / TILESIZE;
2765 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2768 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2772 else if (type == TYPE_GRAPHIC)
2774 int graphic = gpc->graphic;
2775 int graphic_active = gpc->graphic_active;
2779 int dst_x = PANEL_XPOS(pos);
2780 int dst_y = PANEL_YPOS(pos);
2781 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2782 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2784 if (graphic != IMG_UNDEFINED && !skip)
2786 if (pos->style == STYLE_REVERSE)
2787 value = 100 - value;
2789 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2791 if (pos->direction & MV_HORIZONTAL)
2793 width = graphic_info[graphic_active].width * value / 100;
2794 height = graphic_info[graphic_active].height;
2796 if (pos->direction == MV_LEFT)
2798 src_x += graphic_info[graphic_active].width - width;
2799 dst_x += graphic_info[graphic_active].width - width;
2804 width = graphic_info[graphic_active].width;
2805 height = graphic_info[graphic_active].height * value / 100;
2807 if (pos->direction == MV_UP)
2809 src_y += graphic_info[graphic_active].height - height;
2810 dst_y += graphic_info[graphic_active].height - height;
2815 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2818 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2821 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2823 if (pos->direction & MV_HORIZONTAL)
2825 if (pos->direction == MV_RIGHT)
2832 dst_x = PANEL_XPOS(pos);
2835 width = graphic_info[graphic].width - width;
2839 if (pos->direction == MV_DOWN)
2846 dst_y = PANEL_YPOS(pos);
2849 height = graphic_info[graphic].height - height;
2853 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2856 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2860 else if (type == TYPE_STRING)
2862 boolean active = (value != 0);
2863 char *state_normal = "off";
2864 char *state_active = "on";
2865 char *state = (active ? state_active : state_normal);
2866 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2867 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2868 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2869 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2871 if (nr == GAME_PANEL_GRAVITY_STATE)
2873 int font1 = pos->font; // (used for normal state)
2874 int font2 = pos->font_alt; // (used for active state)
2876 font = (active ? font2 : font1);
2885 // don't truncate output if "chars" is zero or less
2888 // dynamically correct text alignment
2889 pos->width = size * getFontWidth(font);
2892 s_cut = getStringCopyN(s, size);
2894 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2895 s_cut, font, mask_mode);
2901 redraw_mask |= REDRAW_DOOR_1;
2904 SetGameStatus(GAME_MODE_PLAYING);
2907 void UpdateAndDisplayGameControlValues(void)
2909 if (tape.deactivate_display)
2912 UpdateGameControlValues();
2913 DisplayGameControlValues();
2916 void UpdateGameDoorValues(void)
2918 UpdateGameControlValues();
2921 void DrawGameDoorValues(void)
2923 DisplayGameControlValues();
2927 // ============================================================================
2929 // ----------------------------------------------------------------------------
2930 // initialize game engine due to level / tape version number
2931 // ============================================================================
2933 static void InitGameEngine(void)
2935 int i, j, k, l, x, y;
2937 // set game engine from tape file when re-playing, else from level file
2938 game.engine_version = (tape.playing ? tape.engine_version :
2939 level.game_version);
2941 // set single or multi-player game mode (needed for re-playing tapes)
2942 game.team_mode = setup.team_mode;
2946 int num_players = 0;
2948 for (i = 0; i < MAX_PLAYERS; i++)
2949 if (tape.player_participates[i])
2952 // multi-player tapes contain input data for more than one player
2953 game.team_mode = (num_players > 1);
2957 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2958 level.game_version);
2959 Debug("game:init:level", " tape.file_version == %06d",
2961 Debug("game:init:level", " tape.game_version == %06d",
2963 Debug("game:init:level", " tape.engine_version == %06d",
2964 tape.engine_version);
2965 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2966 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2969 // --------------------------------------------------------------------------
2970 // set flags for bugs and changes according to active game engine version
2971 // --------------------------------------------------------------------------
2975 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2977 Bug was introduced in version:
2980 Bug was fixed in version:
2984 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2985 but the property "can fall" was missing, which caused some levels to be
2986 unsolvable. This was fixed in version 4.2.0.0.
2988 Affected levels/tapes:
2989 An example for a tape that was fixed by this bugfix is tape 029 from the
2990 level set "rnd_sam_bateman".
2991 The wrong behaviour will still be used for all levels or tapes that were
2992 created/recorded with it. An example for this is tape 023 from the level
2993 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2996 boolean use_amoeba_dropping_cannot_fall_bug =
2997 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2998 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
3000 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3001 tape.game_version < VERSION_IDENT(4,2,0,0)));
3004 Summary of bugfix/change:
3005 Fixed move speed of elements entering or leaving magic wall.
3007 Fixed/changed in version:
3011 Before 2.0.1, move speed of elements entering or leaving magic wall was
3012 twice as fast as it is now.
3013 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
3015 Affected levels/tapes:
3016 The first condition is generally needed for all levels/tapes before version
3017 2.0.1, which might use the old behaviour before it was changed; known tapes
3018 that are affected: Tape 014 from the level set "rnd_conor_mancone".
3019 The second condition is an exception from the above case and is needed for
3020 the special case of tapes recorded with game (not engine!) version 2.0.1 or
3021 above, but before it was known that this change would break tapes like the
3022 above and was fixed in 4.2.0.0, so that the changed behaviour was active
3023 although the engine version while recording maybe was before 2.0.1. There
3024 are a lot of tapes that are affected by this exception, like tape 006 from
3025 the level set "rnd_conor_mancone".
3028 boolean use_old_move_stepsize_for_magic_wall =
3029 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3031 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3032 tape.game_version < VERSION_IDENT(4,2,0,0)));
3035 Summary of bugfix/change:
3036 Fixed handling for custom elements that change when pushed by the player.
3038 Fixed/changed in version:
3042 Before 3.1.0, custom elements that "change when pushing" changed directly
3043 after the player started pushing them (until then handled in "DigField()").
3044 Since 3.1.0, these custom elements are not changed until the "pushing"
3045 move of the element is finished (now handled in "ContinueMoving()").
3047 Affected levels/tapes:
3048 The first condition is generally needed for all levels/tapes before version
3049 3.1.0, which might use the old behaviour before it was changed; known tapes
3050 that are affected are some tapes from the level set "Walpurgis Gardens" by
3052 The second condition is an exception from the above case and is needed for
3053 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3054 above (including some development versions of 3.1.0), but before it was
3055 known that this change would break tapes like the above and was fixed in
3056 3.1.1, so that the changed behaviour was active although the engine version
3057 while recording maybe was before 3.1.0. There is at least one tape that is
3058 affected by this exception, which is the tape for the one-level set "Bug
3059 Machine" by Juergen Bonhagen.
3062 game.use_change_when_pushing_bug =
3063 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3065 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3066 tape.game_version < VERSION_IDENT(3,1,1,0)));
3069 Summary of bugfix/change:
3070 Fixed handling for blocking the field the player leaves when moving.
3072 Fixed/changed in version:
3076 Before 3.1.1, when "block last field when moving" was enabled, the field
3077 the player is leaving when moving was blocked for the time of the move,
3078 and was directly unblocked afterwards. This resulted in the last field
3079 being blocked for exactly one less than the number of frames of one player
3080 move. Additionally, even when blocking was disabled, the last field was
3081 blocked for exactly one frame.
3082 Since 3.1.1, due to changes in player movement handling, the last field
3083 is not blocked at all when blocking is disabled. When blocking is enabled,
3084 the last field is blocked for exactly the number of frames of one player
3085 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3086 last field is blocked for exactly one more than the number of frames of
3089 Affected levels/tapes:
3090 (!!! yet to be determined -- probably many !!!)
3093 game.use_block_last_field_bug =
3094 (game.engine_version < VERSION_IDENT(3,1,1,0));
3096 /* various special flags and settings for native Emerald Mine game engine */
3098 game_em.use_single_button =
3099 (game.engine_version > VERSION_IDENT(4,0,0,2));
3101 game_em.use_push_delay =
3102 (game.engine_version > VERSION_IDENT(4,3,7,1));
3104 game_em.use_snap_key_bug =
3105 (game.engine_version < VERSION_IDENT(4,0,1,0));
3107 game_em.use_random_bug =
3108 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3110 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3112 game_em.use_old_explosions = use_old_em_engine;
3113 game_em.use_old_android = use_old_em_engine;
3114 game_em.use_old_push_elements = use_old_em_engine;
3115 game_em.use_old_push_into_acid = use_old_em_engine;
3117 game_em.use_wrap_around = !use_old_em_engine;
3119 // --------------------------------------------------------------------------
3121 // set maximal allowed number of custom element changes per game frame
3122 game.max_num_changes_per_frame = 1;
3124 // default scan direction: scan playfield from top/left to bottom/right
3125 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3127 // dynamically adjust element properties according to game engine version
3128 InitElementPropertiesEngine(game.engine_version);
3130 // ---------- initialize special element properties -------------------------
3132 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3133 if (use_amoeba_dropping_cannot_fall_bug)
3134 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3136 // ---------- initialize player's initial move delay ------------------------
3138 // dynamically adjust player properties according to level information
3139 for (i = 0; i < MAX_PLAYERS; i++)
3140 game.initial_move_delay_value[i] =
3141 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3143 // dynamically adjust player properties according to game engine version
3144 for (i = 0; i < MAX_PLAYERS; i++)
3145 game.initial_move_delay[i] =
3146 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3147 game.initial_move_delay_value[i] : 0);
3149 // ---------- initialize player's initial push delay ------------------------
3151 // dynamically adjust player properties according to game engine version
3152 game.initial_push_delay_value =
3153 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3155 // ---------- initialize changing elements ----------------------------------
3157 // initialize changing elements information
3158 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3160 struct ElementInfo *ei = &element_info[i];
3162 // this pointer might have been changed in the level editor
3163 ei->change = &ei->change_page[0];
3165 if (!IS_CUSTOM_ELEMENT(i))
3167 ei->change->target_element = EL_EMPTY_SPACE;
3168 ei->change->delay_fixed = 0;
3169 ei->change->delay_random = 0;
3170 ei->change->delay_frames = 1;
3173 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3175 ei->has_change_event[j] = FALSE;
3177 ei->event_page_nr[j] = 0;
3178 ei->event_page[j] = &ei->change_page[0];
3182 // add changing elements from pre-defined list
3183 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3185 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3186 struct ElementInfo *ei = &element_info[ch_delay->element];
3188 ei->change->target_element = ch_delay->target_element;
3189 ei->change->delay_fixed = ch_delay->change_delay;
3191 ei->change->pre_change_function = ch_delay->pre_change_function;
3192 ei->change->change_function = ch_delay->change_function;
3193 ei->change->post_change_function = ch_delay->post_change_function;
3195 ei->change->can_change = TRUE;
3196 ei->change->can_change_or_has_action = TRUE;
3198 ei->has_change_event[CE_DELAY] = TRUE;
3200 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3201 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3204 // ---------- initialize if element can trigger global animations -----------
3206 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3208 struct ElementInfo *ei = &element_info[i];
3210 ei->has_anim_event = FALSE;
3213 InitGlobalAnimEventsForCustomElements();
3215 // ---------- initialize internal run-time variables ------------------------
3217 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3219 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3221 for (j = 0; j < ei->num_change_pages; j++)
3223 ei->change_page[j].can_change_or_has_action =
3224 (ei->change_page[j].can_change |
3225 ei->change_page[j].has_action);
3229 // add change events from custom element configuration
3230 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3232 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3234 for (j = 0; j < ei->num_change_pages; j++)
3236 if (!ei->change_page[j].can_change_or_has_action)
3239 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3241 // only add event page for the first page found with this event
3242 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3244 ei->has_change_event[k] = TRUE;
3246 ei->event_page_nr[k] = j;
3247 ei->event_page[k] = &ei->change_page[j];
3253 // ---------- initialize reference elements in change conditions ------------
3255 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3257 int element = EL_CUSTOM_START + i;
3258 struct ElementInfo *ei = &element_info[element];
3260 for (j = 0; j < ei->num_change_pages; j++)
3262 int trigger_element = ei->change_page[j].initial_trigger_element;
3264 if (trigger_element >= EL_PREV_CE_8 &&
3265 trigger_element <= EL_NEXT_CE_8)
3266 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3268 ei->change_page[j].trigger_element = trigger_element;
3272 // ---------- initialize run-time trigger player and element ----------------
3274 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3276 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3278 for (j = 0; j < ei->num_change_pages; j++)
3280 struct ElementChangeInfo *change = &ei->change_page[j];
3282 change->actual_trigger_element = EL_EMPTY;
3283 change->actual_trigger_player = EL_EMPTY;
3284 change->actual_trigger_player_bits = CH_PLAYER_NONE;
3285 change->actual_trigger_side = CH_SIDE_NONE;
3286 change->actual_trigger_ce_value = 0;
3287 change->actual_trigger_ce_score = 0;
3288 change->actual_trigger_x = -1;
3289 change->actual_trigger_y = -1;
3293 // ---------- initialize trigger events -------------------------------------
3295 // initialize trigger events information
3296 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3297 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3298 trigger_events[i][j] = FALSE;
3300 // add trigger events from element change event properties
3301 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3303 struct ElementInfo *ei = &element_info[i];
3305 for (j = 0; j < ei->num_change_pages; j++)
3307 struct ElementChangeInfo *change = &ei->change_page[j];
3309 if (!change->can_change_or_has_action)
3312 if (change->has_event[CE_BY_OTHER_ACTION])
3314 int trigger_element = change->trigger_element;
3316 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3318 if (change->has_event[k])
3320 if (IS_GROUP_ELEMENT(trigger_element))
3322 struct ElementGroupInfo *group =
3323 element_info[trigger_element].group;
3325 for (l = 0; l < group->num_elements_resolved; l++)
3326 trigger_events[group->element_resolved[l]][k] = TRUE;
3328 else if (trigger_element == EL_ANY_ELEMENT)
3329 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3330 trigger_events[l][k] = TRUE;
3332 trigger_events[trigger_element][k] = TRUE;
3339 // ---------- initialize push delay -----------------------------------------
3341 // initialize push delay values to default
3342 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3344 if (!IS_CUSTOM_ELEMENT(i))
3346 // set default push delay values (corrected since version 3.0.7-1)
3347 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3349 element_info[i].push_delay_fixed = 2;
3350 element_info[i].push_delay_random = 8;
3354 element_info[i].push_delay_fixed = 8;
3355 element_info[i].push_delay_random = 8;
3360 // set push delay value for certain elements from pre-defined list
3361 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3363 int e = push_delay_list[i].element;
3365 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3366 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3369 // set push delay value for Supaplex elements for newer engine versions
3370 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3372 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3374 if (IS_SP_ELEMENT(i))
3376 // set SP push delay to just enough to push under a falling zonk
3377 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3379 element_info[i].push_delay_fixed = delay;
3380 element_info[i].push_delay_random = 0;
3385 // ---------- initialize move stepsize --------------------------------------
3387 // initialize move stepsize values to default
3388 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3389 if (!IS_CUSTOM_ELEMENT(i))
3390 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3392 // set move stepsize value for certain elements from pre-defined list
3393 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3395 int e = move_stepsize_list[i].element;
3397 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3399 // set move stepsize value for certain elements for older engine versions
3400 if (use_old_move_stepsize_for_magic_wall)
3402 if (e == EL_MAGIC_WALL_FILLING ||
3403 e == EL_MAGIC_WALL_EMPTYING ||
3404 e == EL_BD_MAGIC_WALL_FILLING ||
3405 e == EL_BD_MAGIC_WALL_EMPTYING)
3406 element_info[e].move_stepsize *= 2;
3410 // ---------- initialize collect score --------------------------------------
3412 // initialize collect score values for custom elements from initial value
3413 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3414 if (IS_CUSTOM_ELEMENT(i))
3415 element_info[i].collect_score = element_info[i].collect_score_initial;
3417 // ---------- initialize collect count --------------------------------------
3419 // initialize collect count values for non-custom elements
3420 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3421 if (!IS_CUSTOM_ELEMENT(i))
3422 element_info[i].collect_count_initial = 0;
3424 // add collect count values for all elements from pre-defined list
3425 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3426 element_info[collect_count_list[i].element].collect_count_initial =
3427 collect_count_list[i].count;
3429 // ---------- initialize access direction -----------------------------------
3431 // initialize access direction values to default (access from every side)
3432 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3433 if (!IS_CUSTOM_ELEMENT(i))
3434 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3436 // set access direction value for certain elements from pre-defined list
3437 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3438 element_info[access_direction_list[i].element].access_direction =
3439 access_direction_list[i].direction;
3441 // ---------- initialize explosion content ----------------------------------
3442 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3444 if (IS_CUSTOM_ELEMENT(i))
3447 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3449 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3451 element_info[i].content.e[x][y] =
3452 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3453 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3454 i == EL_PLAYER_3 ? EL_EMERALD :
3455 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3456 i == EL_MOLE ? EL_EMERALD_RED :
3457 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3458 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3459 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3460 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3461 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3462 i == EL_WALL_EMERALD ? EL_EMERALD :
3463 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3464 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3465 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3466 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3467 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3468 i == EL_WALL_PEARL ? EL_PEARL :
3469 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3474 // ---------- initialize recursion detection --------------------------------
3475 recursion_loop_depth = 0;
3476 recursion_loop_detected = FALSE;
3477 recursion_loop_element = EL_UNDEFINED;
3479 // ---------- initialize graphics engine ------------------------------------
3480 game.scroll_delay_value =
3481 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3482 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3483 !setup.forced_scroll_delay ? 0 :
3484 setup.scroll_delay ? setup.scroll_delay_value : 0);
3485 if (game.forced_scroll_delay_value == -1)
3486 game.scroll_delay_value =
3487 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3489 // ---------- initialize game engine snapshots ------------------------------
3490 for (i = 0; i < MAX_PLAYERS; i++)
3491 game.snapshot.last_action[i] = 0;
3492 game.snapshot.changed_action = FALSE;
3493 game.snapshot.collected_item = FALSE;
3494 game.snapshot.mode =
3495 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3496 SNAPSHOT_MODE_EVERY_STEP :
3497 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3498 SNAPSHOT_MODE_EVERY_MOVE :
3499 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3500 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3501 game.snapshot.save_snapshot = FALSE;
3503 // ---------- initialize level time for Supaplex engine ---------------------
3504 // Supaplex levels with time limit currently unsupported -- should be added
3505 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3508 // ---------- initialize flags for handling game actions --------------------
3510 // set flags for game actions to default values
3511 game.use_key_actions = TRUE;
3512 game.use_mouse_actions = FALSE;
3514 // when using Mirror Magic game engine, handle mouse events only
3515 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3517 game.use_key_actions = FALSE;
3518 game.use_mouse_actions = TRUE;
3521 // check for custom elements with mouse click events
3522 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3524 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3526 int element = EL_CUSTOM_START + i;
3528 if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3529 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3530 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3531 HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3532 game.use_mouse_actions = TRUE;
3537 static int get_num_special_action(int element, int action_first,
3540 int num_special_action = 0;
3543 for (i = action_first; i <= action_last; i++)
3545 boolean found = FALSE;
3547 for (j = 0; j < NUM_DIRECTIONS; j++)
3548 if (el_act_dir2img(element, i, j) !=
3549 el_act_dir2img(element, ACTION_DEFAULT, j))
3553 num_special_action++;
3558 return num_special_action;
3562 // ============================================================================
3564 // ----------------------------------------------------------------------------
3565 // initialize and start new game
3566 // ============================================================================
3568 #if DEBUG_INIT_PLAYER
3569 static void DebugPrintPlayerStatus(char *message)
3576 Debug("game:init:player", "%s:", message);
3578 for (i = 0; i < MAX_PLAYERS; i++)
3580 struct PlayerInfo *player = &stored_player[i];
3582 Debug("game:init:player",
3583 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3587 player->connected_locally,
3588 player->connected_network,
3590 (local_player == player ? " (local player)" : ""));
3597 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3598 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3599 int fade_mask = REDRAW_FIELD;
3600 boolean restarting = (game_status == GAME_MODE_PLAYING);
3601 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3602 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3603 int initial_move_dir = MV_DOWN;
3606 // required here to update video display before fading (FIX THIS)
3607 DrawMaskedBorder(REDRAW_DOOR_2);
3609 if (!game.restart_level)
3610 CloseDoor(DOOR_CLOSE_1);
3614 // force fading out global animations displayed during game play
3615 SetGameStatus(GAME_MODE_PSEUDO_RESTARTING);
3619 SetGameStatus(GAME_MODE_PLAYING);
3622 if (level_editor_test_game)
3623 FadeSkipNextFadeOut();
3625 FadeSetEnterScreen();
3628 fade_mask = REDRAW_ALL;
3630 FadeLevelSoundsAndMusic();
3632 ExpireSoundLoops(TRUE);
3638 // force restarting global animations displayed during game play
3639 RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING);
3641 // this is required for "transforming" fade modes like cross-fading
3642 // (else global animations will be stopped, but not restarted here)
3643 SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING);
3645 SetGameStatus(GAME_MODE_PLAYING);
3648 if (level_editor_test_game)
3649 FadeSkipNextFadeIn();
3651 // needed if different viewport properties defined for playing
3652 ChangeViewportPropertiesIfNeeded();
3656 DrawCompleteVideoDisplay();
3658 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3661 InitGameControlValues();
3665 // initialize tape actions from game when recording tape
3666 tape.use_key_actions = game.use_key_actions;
3667 tape.use_mouse_actions = game.use_mouse_actions;
3669 // initialize visible playfield size when recording tape (for team mode)
3670 tape.scr_fieldx = SCR_FIELDX;
3671 tape.scr_fieldy = SCR_FIELDY;
3674 // don't play tapes over network
3675 network_playing = (network.enabled && !tape.playing);
3677 for (i = 0; i < MAX_PLAYERS; i++)
3679 struct PlayerInfo *player = &stored_player[i];
3681 player->index_nr = i;
3682 player->index_bit = (1 << i);
3683 player->element_nr = EL_PLAYER_1 + i;
3685 player->present = FALSE;
3686 player->active = FALSE;
3687 player->mapped = FALSE;
3689 player->killed = FALSE;
3690 player->reanimated = FALSE;
3691 player->buried = FALSE;
3694 player->effective_action = 0;
3695 player->programmed_action = 0;
3696 player->snap_action = 0;
3698 player->mouse_action.lx = 0;
3699 player->mouse_action.ly = 0;
3700 player->mouse_action.button = 0;
3701 player->mouse_action.button_hint = 0;
3703 player->effective_mouse_action.lx = 0;
3704 player->effective_mouse_action.ly = 0;
3705 player->effective_mouse_action.button = 0;
3706 player->effective_mouse_action.button_hint = 0;
3708 for (j = 0; j < MAX_NUM_KEYS; j++)
3709 player->key[j] = FALSE;
3711 player->num_white_keys = 0;
3713 player->dynabomb_count = 0;
3714 player->dynabomb_size = 1;
3715 player->dynabombs_left = 0;
3716 player->dynabomb_xl = FALSE;
3718 player->MovDir = initial_move_dir;
3721 player->GfxDir = initial_move_dir;
3722 player->GfxAction = ACTION_DEFAULT;
3724 player->StepFrame = 0;
3726 player->initial_element = player->element_nr;
3727 player->artwork_element =
3728 (level.use_artwork_element[i] ? level.artwork_element[i] :
3729 player->element_nr);
3730 player->use_murphy = FALSE;
3732 player->block_last_field = FALSE; // initialized in InitPlayerField()
3733 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3735 player->gravity = level.initial_player_gravity[i];
3737 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3739 player->actual_frame_counter.count = 0;
3740 player->actual_frame_counter.value = 1;
3742 player->step_counter = 0;
3744 player->last_move_dir = initial_move_dir;
3746 player->is_active = FALSE;
3748 player->is_waiting = FALSE;
3749 player->is_moving = FALSE;
3750 player->is_auto_moving = FALSE;
3751 player->is_digging = FALSE;
3752 player->is_snapping = FALSE;
3753 player->is_collecting = FALSE;
3754 player->is_pushing = FALSE;
3755 player->is_switching = FALSE;
3756 player->is_dropping = FALSE;
3757 player->is_dropping_pressed = FALSE;
3759 player->is_bored = FALSE;
3760 player->is_sleeping = FALSE;
3762 player->was_waiting = TRUE;
3763 player->was_moving = FALSE;
3764 player->was_snapping = FALSE;
3765 player->was_dropping = FALSE;
3767 player->force_dropping = FALSE;
3769 player->frame_counter_bored = -1;
3770 player->frame_counter_sleeping = -1;
3772 player->anim_delay_counter = 0;
3773 player->post_delay_counter = 0;
3775 player->dir_waiting = initial_move_dir;
3776 player->action_waiting = ACTION_DEFAULT;
3777 player->last_action_waiting = ACTION_DEFAULT;
3778 player->special_action_bored = ACTION_DEFAULT;
3779 player->special_action_sleeping = ACTION_DEFAULT;
3781 player->switch_x = -1;
3782 player->switch_y = -1;
3784 player->drop_x = -1;
3785 player->drop_y = -1;
3787 player->show_envelope = 0;
3789 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3791 player->push_delay = -1; // initialized when pushing starts
3792 player->push_delay_value = game.initial_push_delay_value;
3794 player->drop_delay = 0;
3795 player->drop_pressed_delay = 0;
3797 player->last_jx = -1;
3798 player->last_jy = -1;
3802 player->shield_normal_time_left = 0;
3803 player->shield_deadly_time_left = 0;
3805 player->last_removed_element = EL_UNDEFINED;
3807 player->inventory_infinite_element = EL_UNDEFINED;
3808 player->inventory_size = 0;
3810 if (level.use_initial_inventory[i])
3812 for (j = 0; j < level.initial_inventory_size[i]; j++)
3814 int element = level.initial_inventory_content[i][j];
3815 int collect_count = element_info[element].collect_count_initial;
3818 if (!IS_CUSTOM_ELEMENT(element))
3821 if (collect_count == 0)
3822 player->inventory_infinite_element = element;
3824 for (k = 0; k < collect_count; k++)
3825 if (player->inventory_size < MAX_INVENTORY_SIZE)
3826 player->inventory_element[player->inventory_size++] = element;
3830 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3831 SnapField(player, 0, 0);
3833 map_player_action[i] = i;
3836 network_player_action_received = FALSE;
3838 // initial null action
3839 if (network_playing)
3840 SendToServer_MovePlayer(MV_NONE);
3845 TimeLeft = level.time;
3848 ScreenMovDir = MV_NONE;
3852 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3854 game.robot_wheel_x = -1;
3855 game.robot_wheel_y = -1;
3860 game.all_players_gone = FALSE;
3862 game.LevelSolved = FALSE;
3863 game.GameOver = FALSE;
3865 game.GamePlayed = !tape.playing;
3867 game.LevelSolved_GameWon = FALSE;
3868 game.LevelSolved_GameEnd = FALSE;
3869 game.LevelSolved_SaveTape = FALSE;
3870 game.LevelSolved_SaveScore = FALSE;
3872 game.LevelSolved_CountingTime = 0;
3873 game.LevelSolved_CountingScore = 0;
3874 game.LevelSolved_CountingHealth = 0;
3876 game.RestartGameRequested = FALSE;
3878 game.panel.active = TRUE;
3880 game.no_level_time_limit = (level.time == 0);
3881 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3883 game.yamyam_content_nr = 0;
3884 game.robot_wheel_active = FALSE;
3885 game.magic_wall_active = FALSE;
3886 game.magic_wall_time_left = 0;
3887 game.light_time_left = 0;
3888 game.timegate_time_left = 0;
3889 game.switchgate_pos = 0;
3890 game.wind_direction = level.wind_direction_initial;
3892 game.time_final = 0;
3893 game.score_time_final = 0;
3896 game.score_final = 0;
3898 game.health = MAX_HEALTH;
3899 game.health_final = MAX_HEALTH;
3901 game.gems_still_needed = level.gems_needed;
3902 game.sokoban_fields_still_needed = 0;
3903 game.sokoban_objects_still_needed = 0;
3904 game.lights_still_needed = 0;
3905 game.players_still_needed = 0;
3906 game.friends_still_needed = 0;
3908 game.lenses_time_left = 0;
3909 game.magnify_time_left = 0;
3911 game.ball_active = level.ball_active_initial;
3912 game.ball_content_nr = 0;
3914 game.explosions_delayed = TRUE;
3916 game.envelope_active = FALSE;
3918 // special case: set custom artwork setting to initial value
3919 game.use_masked_elements = game.use_masked_elements_initial;
3921 for (i = 0; i < NUM_BELTS; i++)
3923 game.belt_dir[i] = MV_NONE;
3924 game.belt_dir_nr[i] = 3; // not moving, next moving left
3927 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3928 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3930 #if DEBUG_INIT_PLAYER
3931 DebugPrintPlayerStatus("Player status at level initialization");
3934 SCAN_PLAYFIELD(x, y)
3936 Tile[x][y] = Last[x][y] = level.field[x][y];
3937 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3938 ChangeDelay[x][y] = 0;
3939 ChangePage[x][y] = -1;
3940 CustomValue[x][y] = 0; // initialized in InitField()
3941 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3943 WasJustMoving[x][y] = 0;
3944 WasJustFalling[x][y] = 0;
3945 CheckCollision[x][y] = 0;
3946 CheckImpact[x][y] = 0;
3948 Pushed[x][y] = FALSE;
3950 ChangeCount[x][y] = 0;
3951 ChangeEvent[x][y] = -1;
3953 ExplodePhase[x][y] = 0;
3954 ExplodeDelay[x][y] = 0;
3955 ExplodeField[x][y] = EX_TYPE_NONE;
3957 RunnerVisit[x][y] = 0;
3958 PlayerVisit[x][y] = 0;
3961 GfxRandom[x][y] = INIT_GFX_RANDOM();
3962 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
3963 GfxElement[x][y] = EL_UNDEFINED;
3964 GfxElementEmpty[x][y] = EL_EMPTY;
3965 GfxAction[x][y] = ACTION_DEFAULT;
3966 GfxDir[x][y] = MV_NONE;
3967 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3970 SCAN_PLAYFIELD(x, y)
3972 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3974 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3977 InitField(x, y, TRUE);
3979 ResetGfxAnimation(x, y);
3984 // required if level does not contain any "empty space" element
3985 if (element_info[EL_EMPTY].use_gfx_element)
3986 game.use_masked_elements = TRUE;
3988 for (i = 0; i < MAX_PLAYERS; i++)
3990 struct PlayerInfo *player = &stored_player[i];
3992 // set number of special actions for bored and sleeping animation
3993 player->num_special_action_bored =
3994 get_num_special_action(player->artwork_element,
3995 ACTION_BORING_1, ACTION_BORING_LAST);
3996 player->num_special_action_sleeping =
3997 get_num_special_action(player->artwork_element,
3998 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
4001 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
4002 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
4004 // initialize type of slippery elements
4005 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4007 if (!IS_CUSTOM_ELEMENT(i))
4009 // default: elements slip down either to the left or right randomly
4010 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
4012 // SP style elements prefer to slip down on the left side
4013 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4014 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4016 // BD style elements prefer to slip down on the left side
4017 if (game.emulation == EMU_BOULDERDASH)
4018 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4022 // initialize explosion and ignition delay
4023 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4025 if (!IS_CUSTOM_ELEMENT(i))
4028 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4029 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4030 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4031 int last_phase = (num_phase + 1) * delay;
4032 int half_phase = (num_phase / 2) * delay;
4034 element_info[i].explosion_delay = last_phase - 1;
4035 element_info[i].ignition_delay = half_phase;
4037 if (i == EL_BLACK_ORB)
4038 element_info[i].ignition_delay = 1;
4042 // correct non-moving belts to start moving left
4043 for (i = 0; i < NUM_BELTS; i++)
4044 if (game.belt_dir[i] == MV_NONE)
4045 game.belt_dir_nr[i] = 3; // not moving, next moving left
4047 #if USE_NEW_PLAYER_ASSIGNMENTS
4048 // use preferred player also in local single-player mode
4049 if (!network.enabled && !game.team_mode)
4051 int new_index_nr = setup.network_player_nr;
4053 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4055 for (i = 0; i < MAX_PLAYERS; i++)
4056 stored_player[i].connected_locally = FALSE;
4058 stored_player[new_index_nr].connected_locally = TRUE;
4062 for (i = 0; i < MAX_PLAYERS; i++)
4064 stored_player[i].connected = FALSE;
4066 // in network game mode, the local player might not be the first player
4067 if (stored_player[i].connected_locally)
4068 local_player = &stored_player[i];
4071 if (!network.enabled)
4072 local_player->connected = TRUE;
4076 for (i = 0; i < MAX_PLAYERS; i++)
4077 stored_player[i].connected = tape.player_participates[i];
4079 else if (network.enabled)
4081 // add team mode players connected over the network (needed for correct
4082 // assignment of player figures from level to locally playing players)
4084 for (i = 0; i < MAX_PLAYERS; i++)
4085 if (stored_player[i].connected_network)
4086 stored_player[i].connected = TRUE;
4088 else if (game.team_mode)
4090 // try to guess locally connected team mode players (needed for correct
4091 // assignment of player figures from level to locally playing players)
4093 for (i = 0; i < MAX_PLAYERS; i++)
4094 if (setup.input[i].use_joystick ||
4095 setup.input[i].key.left != KSYM_UNDEFINED)
4096 stored_player[i].connected = TRUE;
4099 #if DEBUG_INIT_PLAYER
4100 DebugPrintPlayerStatus("Player status after level initialization");
4103 #if DEBUG_INIT_PLAYER
4104 Debug("game:init:player", "Reassigning players ...");
4107 // check if any connected player was not found in playfield
4108 for (i = 0; i < MAX_PLAYERS; i++)
4110 struct PlayerInfo *player = &stored_player[i];
4112 if (player->connected && !player->present)
4114 struct PlayerInfo *field_player = NULL;
4116 #if DEBUG_INIT_PLAYER
4117 Debug("game:init:player",
4118 "- looking for field player for player %d ...", i + 1);
4121 // assign first free player found that is present in the playfield
4123 // first try: look for unmapped playfield player that is not connected
4124 for (j = 0; j < MAX_PLAYERS; j++)
4125 if (field_player == NULL &&
4126 stored_player[j].present &&
4127 !stored_player[j].mapped &&
4128 !stored_player[j].connected)
4129 field_player = &stored_player[j];
4131 // second try: look for *any* unmapped playfield player
4132 for (j = 0; j < MAX_PLAYERS; j++)
4133 if (field_player == NULL &&
4134 stored_player[j].present &&
4135 !stored_player[j].mapped)
4136 field_player = &stored_player[j];
4138 if (field_player != NULL)
4140 int jx = field_player->jx, jy = field_player->jy;
4142 #if DEBUG_INIT_PLAYER
4143 Debug("game:init:player", "- found player %d",
4144 field_player->index_nr + 1);
4147 player->present = FALSE;
4148 player->active = FALSE;
4150 field_player->present = TRUE;
4151 field_player->active = TRUE;
4154 player->initial_element = field_player->initial_element;
4155 player->artwork_element = field_player->artwork_element;
4157 player->block_last_field = field_player->block_last_field;
4158 player->block_delay_adjustment = field_player->block_delay_adjustment;
4161 StorePlayer[jx][jy] = field_player->element_nr;
4163 field_player->jx = field_player->last_jx = jx;
4164 field_player->jy = field_player->last_jy = jy;
4166 if (local_player == player)
4167 local_player = field_player;
4169 map_player_action[field_player->index_nr] = i;
4171 field_player->mapped = TRUE;
4173 #if DEBUG_INIT_PLAYER
4174 Debug("game:init:player", "- map_player_action[%d] == %d",
4175 field_player->index_nr + 1, i + 1);
4180 if (player->connected && player->present)
4181 player->mapped = TRUE;
4184 #if DEBUG_INIT_PLAYER
4185 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4190 // check if any connected player was not found in playfield
4191 for (i = 0; i < MAX_PLAYERS; i++)
4193 struct PlayerInfo *player = &stored_player[i];
4195 if (player->connected && !player->present)
4197 for (j = 0; j < MAX_PLAYERS; j++)
4199 struct PlayerInfo *field_player = &stored_player[j];
4200 int jx = field_player->jx, jy = field_player->jy;
4202 // assign first free player found that is present in the playfield
4203 if (field_player->present && !field_player->connected)
4205 player->present = TRUE;
4206 player->active = TRUE;
4208 field_player->present = FALSE;
4209 field_player->active = FALSE;
4211 player->initial_element = field_player->initial_element;
4212 player->artwork_element = field_player->artwork_element;
4214 player->block_last_field = field_player->block_last_field;
4215 player->block_delay_adjustment = field_player->block_delay_adjustment;
4217 StorePlayer[jx][jy] = player->element_nr;
4219 player->jx = player->last_jx = jx;
4220 player->jy = player->last_jy = jy;
4230 Debug("game:init:player", "local_player->present == %d",
4231 local_player->present);
4234 // set focus to local player for network games, else to all players
4235 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4236 game.centered_player_nr_next = game.centered_player_nr;
4237 game.set_centered_player = FALSE;
4238 game.set_centered_player_wrap = FALSE;
4240 if (network_playing && tape.recording)
4242 // store client dependent player focus when recording network games
4243 tape.centered_player_nr_next = game.centered_player_nr_next;
4244 tape.set_centered_player = TRUE;
4249 // when playing a tape, eliminate all players who do not participate
4251 #if USE_NEW_PLAYER_ASSIGNMENTS
4253 if (!game.team_mode)
4255 for (i = 0; i < MAX_PLAYERS; i++)
4257 if (stored_player[i].active &&
4258 !tape.player_participates[map_player_action[i]])
4260 struct PlayerInfo *player = &stored_player[i];
4261 int jx = player->jx, jy = player->jy;
4263 #if DEBUG_INIT_PLAYER
4264 Debug("game:init:player", "Removing player %d at (%d, %d)",
4268 player->active = FALSE;
4269 StorePlayer[jx][jy] = 0;
4270 Tile[jx][jy] = EL_EMPTY;
4277 for (i = 0; i < MAX_PLAYERS; i++)
4279 if (stored_player[i].active &&
4280 !tape.player_participates[i])
4282 struct PlayerInfo *player = &stored_player[i];
4283 int jx = player->jx, jy = player->jy;
4285 player->active = FALSE;
4286 StorePlayer[jx][jy] = 0;
4287 Tile[jx][jy] = EL_EMPTY;
4292 else if (!network.enabled && !game.team_mode) // && !tape.playing
4294 // when in single player mode, eliminate all but the local player
4296 for (i = 0; i < MAX_PLAYERS; i++)
4298 struct PlayerInfo *player = &stored_player[i];
4300 if (player->active && player != local_player)
4302 int jx = player->jx, jy = player->jy;
4304 player->active = FALSE;
4305 player->present = FALSE;
4307 StorePlayer[jx][jy] = 0;
4308 Tile[jx][jy] = EL_EMPTY;
4313 for (i = 0; i < MAX_PLAYERS; i++)
4314 if (stored_player[i].active)
4315 game.players_still_needed++;
4317 if (level.solved_by_one_player)
4318 game.players_still_needed = 1;
4320 // when recording the game, store which players take part in the game
4323 #if USE_NEW_PLAYER_ASSIGNMENTS
4324 for (i = 0; i < MAX_PLAYERS; i++)
4325 if (stored_player[i].connected)
4326 tape.player_participates[i] = TRUE;
4328 for (i = 0; i < MAX_PLAYERS; i++)
4329 if (stored_player[i].active)
4330 tape.player_participates[i] = TRUE;
4334 #if DEBUG_INIT_PLAYER
4335 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4338 if (BorderElement == EL_EMPTY)
4341 SBX_Right = lev_fieldx - SCR_FIELDX;
4343 SBY_Lower = lev_fieldy - SCR_FIELDY;
4348 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4350 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4353 if (full_lev_fieldx <= SCR_FIELDX)
4354 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4355 if (full_lev_fieldy <= SCR_FIELDY)
4356 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4358 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4360 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4363 // if local player not found, look for custom element that might create
4364 // the player (make some assumptions about the right custom element)
4365 if (!local_player->present)
4367 int start_x = 0, start_y = 0;
4368 int found_rating = 0;
4369 int found_element = EL_UNDEFINED;
4370 int player_nr = local_player->index_nr;
4372 SCAN_PLAYFIELD(x, y)
4374 int element = Tile[x][y];
4379 if (level.use_start_element[player_nr] &&
4380 level.start_element[player_nr] == element &&
4387 found_element = element;
4390 if (!IS_CUSTOM_ELEMENT(element))
4393 if (CAN_CHANGE(element))
4395 for (i = 0; i < element_info[element].num_change_pages; i++)
4397 // check for player created from custom element as single target
4398 content = element_info[element].change_page[i].target_element;
4399 is_player = IS_PLAYER_ELEMENT(content);
4401 if (is_player && (found_rating < 3 ||
4402 (found_rating == 3 && element < found_element)))
4408 found_element = element;
4413 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4415 // check for player created from custom element as explosion content
4416 content = element_info[element].content.e[xx][yy];
4417 is_player = IS_PLAYER_ELEMENT(content);
4419 if (is_player && (found_rating < 2 ||
4420 (found_rating == 2 && element < found_element)))
4422 start_x = x + xx - 1;
4423 start_y = y + yy - 1;
4426 found_element = element;
4429 if (!CAN_CHANGE(element))
4432 for (i = 0; i < element_info[element].num_change_pages; i++)
4434 // check for player created from custom element as extended target
4436 element_info[element].change_page[i].target_content.e[xx][yy];
4438 is_player = IS_PLAYER_ELEMENT(content);
4440 if (is_player && (found_rating < 1 ||
4441 (found_rating == 1 && element < found_element)))
4443 start_x = x + xx - 1;
4444 start_y = y + yy - 1;
4447 found_element = element;
4453 scroll_x = SCROLL_POSITION_X(start_x);
4454 scroll_y = SCROLL_POSITION_Y(start_y);
4458 scroll_x = SCROLL_POSITION_X(local_player->jx);
4459 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4462 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4463 scroll_x = game.forced_scroll_x;
4464 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4465 scroll_y = game.forced_scroll_y;
4467 // !!! FIX THIS (START) !!!
4468 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4470 InitGameEngine_EM();
4472 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4474 InitGameEngine_SP();
4476 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4478 InitGameEngine_MM();
4482 DrawLevel(REDRAW_FIELD);
4485 // after drawing the level, correct some elements
4486 if (game.timegate_time_left == 0)
4487 CloseAllOpenTimegates();
4490 // blit playfield from scroll buffer to normal back buffer for fading in
4491 BlitScreenToBitmap(backbuffer);
4492 // !!! FIX THIS (END) !!!
4494 DrawMaskedBorder(fade_mask);
4499 // full screen redraw is required at this point in the following cases:
4500 // - special editor door undrawn when game was started from level editor
4501 // - drawing area (playfield) was changed and has to be removed completely
4502 redraw_mask = REDRAW_ALL;
4506 if (!game.restart_level)
4508 // copy default game door content to main double buffer
4510 // !!! CHECK AGAIN !!!
4511 SetPanelBackground();
4512 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4513 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4516 SetPanelBackground();
4517 SetDrawBackgroundMask(REDRAW_DOOR_1);
4519 UpdateAndDisplayGameControlValues();
4521 if (!game.restart_level)
4527 CreateGameButtons();
4532 // copy actual game door content to door double buffer for OpenDoor()
4533 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4535 OpenDoor(DOOR_OPEN_ALL);
4537 KeyboardAutoRepeatOffUnlessAutoplay();
4539 #if DEBUG_INIT_PLAYER
4540 DebugPrintPlayerStatus("Player status (final)");
4549 if (!game.restart_level && !tape.playing)
4551 LevelStats_incPlayed(level_nr);
4553 SaveLevelSetup_SeriesInfo();
4556 game.restart_level = FALSE;
4557 game.request_active = FALSE;
4559 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4560 InitGameActions_MM();
4562 SaveEngineSnapshotToListInitial();
4564 if (!game.restart_level)
4566 PlaySound(SND_GAME_STARTING);
4568 if (setup.sound_music)
4572 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4575 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4576 int actual_player_x, int actual_player_y)
4578 // this is used for non-R'n'D game engines to update certain engine values
4580 // needed to determine if sounds are played within the visible screen area
4581 scroll_x = actual_scroll_x;
4582 scroll_y = actual_scroll_y;
4584 // needed to get player position for "follow finger" playing input method
4585 local_player->jx = actual_player_x;
4586 local_player->jy = actual_player_y;
4589 void InitMovDir(int x, int y)
4591 int i, element = Tile[x][y];
4592 static int xy[4][2] =
4599 static int direction[3][4] =
4601 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4602 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4603 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4612 Tile[x][y] = EL_BUG;
4613 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4616 case EL_SPACESHIP_RIGHT:
4617 case EL_SPACESHIP_UP:
4618 case EL_SPACESHIP_LEFT:
4619 case EL_SPACESHIP_DOWN:
4620 Tile[x][y] = EL_SPACESHIP;
4621 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4624 case EL_BD_BUTTERFLY_RIGHT:
4625 case EL_BD_BUTTERFLY_UP:
4626 case EL_BD_BUTTERFLY_LEFT:
4627 case EL_BD_BUTTERFLY_DOWN:
4628 Tile[x][y] = EL_BD_BUTTERFLY;
4629 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4632 case EL_BD_FIREFLY_RIGHT:
4633 case EL_BD_FIREFLY_UP:
4634 case EL_BD_FIREFLY_LEFT:
4635 case EL_BD_FIREFLY_DOWN:
4636 Tile[x][y] = EL_BD_FIREFLY;
4637 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4640 case EL_PACMAN_RIGHT:
4642 case EL_PACMAN_LEFT:
4643 case EL_PACMAN_DOWN:
4644 Tile[x][y] = EL_PACMAN;
4645 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4648 case EL_YAMYAM_LEFT:
4649 case EL_YAMYAM_RIGHT:
4651 case EL_YAMYAM_DOWN:
4652 Tile[x][y] = EL_YAMYAM;
4653 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4656 case EL_SP_SNIKSNAK:
4657 MovDir[x][y] = MV_UP;
4660 case EL_SP_ELECTRON:
4661 MovDir[x][y] = MV_LEFT;
4668 Tile[x][y] = EL_MOLE;
4669 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4672 case EL_SPRING_LEFT:
4673 case EL_SPRING_RIGHT:
4674 Tile[x][y] = EL_SPRING;
4675 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4679 if (IS_CUSTOM_ELEMENT(element))
4681 struct ElementInfo *ei = &element_info[element];
4682 int move_direction_initial = ei->move_direction_initial;
4683 int move_pattern = ei->move_pattern;
4685 if (move_direction_initial == MV_START_PREVIOUS)
4687 if (MovDir[x][y] != MV_NONE)
4690 move_direction_initial = MV_START_AUTOMATIC;
4693 if (move_direction_initial == MV_START_RANDOM)
4694 MovDir[x][y] = 1 << RND(4);
4695 else if (move_direction_initial & MV_ANY_DIRECTION)
4696 MovDir[x][y] = move_direction_initial;
4697 else if (move_pattern == MV_ALL_DIRECTIONS ||
4698 move_pattern == MV_TURNING_LEFT ||
4699 move_pattern == MV_TURNING_RIGHT ||
4700 move_pattern == MV_TURNING_LEFT_RIGHT ||
4701 move_pattern == MV_TURNING_RIGHT_LEFT ||
4702 move_pattern == MV_TURNING_RANDOM)
4703 MovDir[x][y] = 1 << RND(4);
4704 else if (move_pattern == MV_HORIZONTAL)
4705 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4706 else if (move_pattern == MV_VERTICAL)
4707 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4708 else if (move_pattern & MV_ANY_DIRECTION)
4709 MovDir[x][y] = element_info[element].move_pattern;
4710 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4711 move_pattern == MV_ALONG_RIGHT_SIDE)
4713 // use random direction as default start direction
4714 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4715 MovDir[x][y] = 1 << RND(4);
4717 for (i = 0; i < NUM_DIRECTIONS; i++)
4719 int x1 = x + xy[i][0];
4720 int y1 = y + xy[i][1];
4722 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4724 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4725 MovDir[x][y] = direction[0][i];
4727 MovDir[x][y] = direction[1][i];
4736 MovDir[x][y] = 1 << RND(4);
4738 if (element != EL_BUG &&
4739 element != EL_SPACESHIP &&
4740 element != EL_BD_BUTTERFLY &&
4741 element != EL_BD_FIREFLY)
4744 for (i = 0; i < NUM_DIRECTIONS; i++)
4746 int x1 = x + xy[i][0];
4747 int y1 = y + xy[i][1];
4749 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4751 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4753 MovDir[x][y] = direction[0][i];
4756 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4757 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4759 MovDir[x][y] = direction[1][i];
4768 GfxDir[x][y] = MovDir[x][y];
4771 void InitAmoebaNr(int x, int y)
4774 int group_nr = AmoebaNeighbourNr(x, y);
4778 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4780 if (AmoebaCnt[i] == 0)
4788 AmoebaNr[x][y] = group_nr;
4789 AmoebaCnt[group_nr]++;
4790 AmoebaCnt2[group_nr]++;
4793 static void LevelSolved_SetFinalGameValues(void)
4795 game.time_final = (game.no_level_time_limit ? TimePlayed : TimeLeft);
4796 game.score_time_final = (level.use_step_counter ? TimePlayed :
4797 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4799 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4800 game_em.lev->score :
4801 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4805 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4806 MM_HEALTH(game_mm.laser_overload_value) :
4809 game.LevelSolved_CountingTime = game.time_final;
4810 game.LevelSolved_CountingScore = game.score_final;
4811 game.LevelSolved_CountingHealth = game.health_final;
4814 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4816 game.LevelSolved_CountingTime = time;
4817 game.LevelSolved_CountingScore = score;
4818 game.LevelSolved_CountingHealth = health;
4820 game_panel_controls[GAME_PANEL_TIME].value = time;
4821 game_panel_controls[GAME_PANEL_SCORE].value = score;
4822 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4824 DisplayGameControlValues();
4827 static void LevelSolved(void)
4829 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4830 game.players_still_needed > 0)
4833 game.LevelSolved = TRUE;
4834 game.GameOver = TRUE;
4838 // needed here to display correct panel values while player walks into exit
4839 LevelSolved_SetFinalGameValues();
4844 static int time_count_steps;
4845 static int time, time_final;
4846 static float score, score_final; // needed for time score < 10 for 10 seconds
4847 static int health, health_final;
4848 static int game_over_delay_1 = 0;
4849 static int game_over_delay_2 = 0;
4850 static int game_over_delay_3 = 0;
4851 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4852 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4854 if (!game.LevelSolved_GameWon)
4858 // do not start end game actions before the player stops moving (to exit)
4859 if (local_player->active && local_player->MovPos)
4862 // calculate final game values after player finished walking into exit
4863 LevelSolved_SetFinalGameValues();
4865 game.LevelSolved_GameWon = TRUE;
4866 game.LevelSolved_SaveTape = tape.recording;
4867 game.LevelSolved_SaveScore = !tape.playing;
4871 LevelStats_incSolved(level_nr);
4873 SaveLevelSetup_SeriesInfo();
4876 if (tape.auto_play) // tape might already be stopped here
4877 tape.auto_play_level_solved = TRUE;
4881 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4882 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4883 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4885 time = time_final = game.time_final;
4886 score = score_final = game.score_final;
4887 health = health_final = game.health_final;
4889 // update game panel values before (delayed) counting of score (if any)
4890 LevelSolved_DisplayFinalGameValues(time, score, health);
4892 // if level has time score defined, calculate new final game values
4895 int time_final_max = 999;
4896 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4897 int time_frames = 0;
4898 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4899 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4904 time_frames = time_frames_left;
4906 else if (game.no_level_time_limit && TimePlayed < time_final_max)
4908 time_final = time_final_max;
4909 time_frames = time_frames_final_max - time_frames_played;
4912 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4914 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4916 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4919 score_final += health * time_score;
4922 game.score_final = score_final;
4923 game.health_final = health_final;
4926 // if not counting score after game, immediately update game panel values
4927 if (level_editor_test_game || !setup.count_score_after_game)
4930 score = score_final;
4932 LevelSolved_DisplayFinalGameValues(time, score, health);
4935 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4937 // check if last player has left the level
4938 if (game.exit_x >= 0 &&
4941 int x = game.exit_x;
4942 int y = game.exit_y;
4943 int element = Tile[x][y];
4945 // close exit door after last player
4946 if ((game.all_players_gone &&
4947 (element == EL_EXIT_OPEN ||
4948 element == EL_SP_EXIT_OPEN ||
4949 element == EL_STEEL_EXIT_OPEN)) ||
4950 element == EL_EM_EXIT_OPEN ||
4951 element == EL_EM_STEEL_EXIT_OPEN)
4955 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4956 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4957 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4958 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4959 EL_EM_STEEL_EXIT_CLOSING);
4961 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4964 // player disappears
4965 DrawLevelField(x, y);
4968 for (i = 0; i < MAX_PLAYERS; i++)
4970 struct PlayerInfo *player = &stored_player[i];
4972 if (player->present)
4974 RemovePlayer(player);
4976 // player disappears
4977 DrawLevelField(player->jx, player->jy);
4982 PlaySound(SND_GAME_WINNING);
4985 if (setup.count_score_after_game)
4987 if (time != time_final)
4989 if (game_over_delay_1 > 0)
4991 game_over_delay_1--;
4996 int time_to_go = ABS(time_final - time);
4997 int time_count_dir = (time < time_final ? +1 : -1);
4999 if (time_to_go < time_count_steps)
5000 time_count_steps = 1;
5002 time += time_count_steps * time_count_dir;
5003 score += time_count_steps * time_score;
5005 // set final score to correct rounding differences after counting score
5006 if (time == time_final)
5007 score = score_final;
5009 LevelSolved_DisplayFinalGameValues(time, score, health);
5011 if (time == time_final)
5012 StopSound(SND_GAME_LEVELTIME_BONUS);
5013 else if (setup.sound_loops)
5014 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5016 PlaySound(SND_GAME_LEVELTIME_BONUS);
5021 if (health != health_final)
5023 if (game_over_delay_2 > 0)
5025 game_over_delay_2--;
5030 int health_count_dir = (health < health_final ? +1 : -1);
5032 health += health_count_dir;
5033 score += time_score;
5035 LevelSolved_DisplayFinalGameValues(time, score, health);
5037 if (health == health_final)
5038 StopSound(SND_GAME_LEVELTIME_BONUS);
5039 else if (setup.sound_loops)
5040 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5042 PlaySound(SND_GAME_LEVELTIME_BONUS);
5048 game.panel.active = FALSE;
5050 if (game_over_delay_3 > 0)
5052 game_over_delay_3--;
5062 // used instead of "level_nr" (needed for network games)
5063 int last_level_nr = levelset.level_nr;
5064 boolean tape_saved = FALSE;
5066 game.LevelSolved_GameEnd = TRUE;
5068 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5070 // make sure that request dialog to save tape does not open door again
5071 if (!global.use_envelope_request)
5072 CloseDoor(DOOR_CLOSE_1);
5075 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5077 // set unique basename for score tape (also saved in high score table)
5078 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5081 // if no tape is to be saved, close both doors simultaneously
5082 CloseDoor(DOOR_CLOSE_ALL);
5084 if (level_editor_test_game || score_info_tape_play)
5086 SetGameStatus(GAME_MODE_MAIN);
5093 if (!game.LevelSolved_SaveScore)
5095 SetGameStatus(GAME_MODE_MAIN);
5102 if (level_nr == leveldir_current->handicap_level)
5104 leveldir_current->handicap_level++;
5106 SaveLevelSetup_SeriesInfo();
5109 // save score and score tape before potentially erasing tape below
5110 NewHighScore(last_level_nr, tape_saved);
5112 if (setup.increment_levels &&
5113 level_nr < leveldir_current->last_level &&
5116 level_nr++; // advance to next level
5117 TapeErase(); // start with empty tape
5119 if (setup.auto_play_next_level)
5121 scores.continue_playing = TRUE;
5122 scores.next_level_nr = level_nr;
5124 LoadLevel(level_nr);
5126 SaveLevelSetup_SeriesInfo();
5130 if (scores.last_added >= 0 && setup.show_scores_after_game)
5132 SetGameStatus(GAME_MODE_SCORES);
5134 DrawHallOfFame(last_level_nr);
5136 else if (scores.continue_playing)
5138 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5142 SetGameStatus(GAME_MODE_MAIN);
5148 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5149 boolean one_score_entry_per_name)
5153 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5156 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5158 struct ScoreEntry *entry = &list->entry[i];
5159 boolean score_is_better = (new_entry->score > entry->score);
5160 boolean score_is_equal = (new_entry->score == entry->score);
5161 boolean time_is_better = (new_entry->time < entry->time);
5162 boolean time_is_equal = (new_entry->time == entry->time);
5163 boolean better_by_score = (score_is_better ||
5164 (score_is_equal && time_is_better));
5165 boolean better_by_time = (time_is_better ||
5166 (time_is_equal && score_is_better));
5167 boolean is_better = (level.rate_time_over_score ? better_by_time :
5169 boolean entry_is_empty = (entry->score == 0 &&
5172 // prevent adding server score entries if also existing in local score file
5173 // (special case: historic score entries have an empty tape basename entry)
5174 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5175 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5177 // add fields from server score entry not stored in local score entry
5178 // (currently, this means setting platform, version and country fields;
5179 // in rare cases, this may also correct an invalid score value, as
5180 // historic scores might have been truncated to 16-bit values locally)
5181 *entry = *new_entry;
5186 if (is_better || entry_is_empty)
5188 // player has made it to the hall of fame
5190 if (i < MAX_SCORE_ENTRIES - 1)
5192 int m = MAX_SCORE_ENTRIES - 1;
5195 if (one_score_entry_per_name)
5197 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5198 if (strEqual(list->entry[l].name, new_entry->name))
5201 if (m == i) // player's new highscore overwrites his old one
5205 for (l = m; l > i; l--)
5206 list->entry[l] = list->entry[l - 1];
5211 *entry = *new_entry;
5215 else if (one_score_entry_per_name &&
5216 strEqual(entry->name, new_entry->name))
5218 // player already in high score list with better score or time
5224 // special case: new score is beyond the last high score list position
5225 return MAX_SCORE_ENTRIES;
5228 void NewHighScore(int level_nr, boolean tape_saved)
5230 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5231 boolean one_per_name = FALSE;
5233 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5234 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5236 new_entry.score = game.score_final;
5237 new_entry.time = game.score_time_final;
5239 LoadScore(level_nr);
5241 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5243 if (scores.last_added >= MAX_SCORE_ENTRIES)
5245 scores.last_added = MAX_SCORE_ENTRIES - 1;
5246 scores.force_last_added = TRUE;
5248 scores.entry[scores.last_added] = new_entry;
5250 // store last added local score entry (before merging server scores)
5251 scores.last_added_local = scores.last_added;
5256 if (scores.last_added < 0)
5259 SaveScore(level_nr);
5261 // store last added local score entry (before merging server scores)
5262 scores.last_added_local = scores.last_added;
5264 if (!game.LevelSolved_SaveTape)
5267 SaveScoreTape(level_nr);
5269 if (setup.ask_for_using_api_server)
5271 setup.use_api_server =
5272 Request("Upload your score and tape to the high score server?", REQ_ASK);
5274 if (!setup.use_api_server)
5275 Request("Not using high score server! Use setup menu to enable again!",
5278 runtime.use_api_server = setup.use_api_server;
5280 // after asking for using API server once, do not ask again
5281 setup.ask_for_using_api_server = FALSE;
5283 SaveSetup_ServerSetup();
5286 SaveServerScore(level_nr, tape_saved);
5289 void MergeServerScore(void)
5291 struct ScoreEntry last_added_entry;
5292 boolean one_per_name = FALSE;
5295 if (scores.last_added >= 0)
5296 last_added_entry = scores.entry[scores.last_added];
5298 for (i = 0; i < server_scores.num_entries; i++)
5300 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5302 if (pos >= 0 && pos <= scores.last_added)
5303 scores.last_added++;
5306 if (scores.last_added >= MAX_SCORE_ENTRIES)
5308 scores.last_added = MAX_SCORE_ENTRIES - 1;
5309 scores.force_last_added = TRUE;
5311 scores.entry[scores.last_added] = last_added_entry;
5315 static int getElementMoveStepsizeExt(int x, int y, int direction)
5317 int element = Tile[x][y];
5318 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5319 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5320 int horiz_move = (dx != 0);
5321 int sign = (horiz_move ? dx : dy);
5322 int step = sign * element_info[element].move_stepsize;
5324 // special values for move stepsize for spring and things on conveyor belt
5327 if (CAN_FALL(element) &&
5328 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5329 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5330 else if (element == EL_SPRING)
5331 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5337 static int getElementMoveStepsize(int x, int y)
5339 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5342 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5344 if (player->GfxAction != action || player->GfxDir != dir)
5346 player->GfxAction = action;
5347 player->GfxDir = dir;
5349 player->StepFrame = 0;
5353 static void ResetGfxFrame(int x, int y)
5355 // profiling showed that "autotest" spends 10~20% of its time in this function
5356 if (DrawingDeactivatedField())
5359 int element = Tile[x][y];
5360 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5362 if (graphic_info[graphic].anim_global_sync)
5363 GfxFrame[x][y] = FrameCounter;
5364 else if (graphic_info[graphic].anim_global_anim_sync)
5365 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5366 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5367 GfxFrame[x][y] = CustomValue[x][y];
5368 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5369 GfxFrame[x][y] = element_info[element].collect_score;
5370 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5371 GfxFrame[x][y] = ChangeDelay[x][y];
5374 static void ResetGfxAnimation(int x, int y)
5376 GfxAction[x][y] = ACTION_DEFAULT;
5377 GfxDir[x][y] = MovDir[x][y];
5380 ResetGfxFrame(x, y);
5383 static void ResetRandomAnimationValue(int x, int y)
5385 GfxRandom[x][y] = INIT_GFX_RANDOM();
5388 static void InitMovingField(int x, int y, int direction)
5390 int element = Tile[x][y];
5391 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5392 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5395 boolean is_moving_before, is_moving_after;
5397 // check if element was/is moving or being moved before/after mode change
5398 is_moving_before = (WasJustMoving[x][y] != 0);
5399 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5401 // reset animation only for moving elements which change direction of moving
5402 // or which just started or stopped moving
5403 // (else CEs with property "can move" / "not moving" are reset each frame)
5404 if (is_moving_before != is_moving_after ||
5405 direction != MovDir[x][y])
5406 ResetGfxAnimation(x, y);
5408 MovDir[x][y] = direction;
5409 GfxDir[x][y] = direction;
5411 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5412 direction == MV_DOWN && CAN_FALL(element) ?
5413 ACTION_FALLING : ACTION_MOVING);
5415 // this is needed for CEs with property "can move" / "not moving"
5417 if (is_moving_after)
5419 if (Tile[newx][newy] == EL_EMPTY)
5420 Tile[newx][newy] = EL_BLOCKED;
5422 MovDir[newx][newy] = MovDir[x][y];
5424 CustomValue[newx][newy] = CustomValue[x][y];
5426 GfxFrame[newx][newy] = GfxFrame[x][y];
5427 GfxRandom[newx][newy] = GfxRandom[x][y];
5428 GfxAction[newx][newy] = GfxAction[x][y];
5429 GfxDir[newx][newy] = GfxDir[x][y];
5433 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5435 int direction = MovDir[x][y];
5436 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5437 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5443 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5445 int direction = MovDir[x][y];
5446 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5447 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5449 *comes_from_x = oldx;
5450 *comes_from_y = oldy;
5453 static int MovingOrBlocked2Element(int x, int y)
5455 int element = Tile[x][y];
5457 if (element == EL_BLOCKED)
5461 Blocked2Moving(x, y, &oldx, &oldy);
5463 return Tile[oldx][oldy];
5469 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5471 // like MovingOrBlocked2Element(), but if element is moving
5472 // and (x, y) is the field the moving element is just leaving,
5473 // return EL_BLOCKED instead of the element value
5474 int element = Tile[x][y];
5476 if (IS_MOVING(x, y))
5478 if (element == EL_BLOCKED)
5482 Blocked2Moving(x, y, &oldx, &oldy);
5483 return Tile[oldx][oldy];
5492 static void RemoveField(int x, int y)
5494 Tile[x][y] = EL_EMPTY;
5500 CustomValue[x][y] = 0;
5503 ChangeDelay[x][y] = 0;
5504 ChangePage[x][y] = -1;
5505 Pushed[x][y] = FALSE;
5507 GfxElement[x][y] = EL_UNDEFINED;
5508 GfxAction[x][y] = ACTION_DEFAULT;
5509 GfxDir[x][y] = MV_NONE;
5512 static void RemoveMovingField(int x, int y)
5514 int oldx = x, oldy = y, newx = x, newy = y;
5515 int element = Tile[x][y];
5516 int next_element = EL_UNDEFINED;
5518 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5521 if (IS_MOVING(x, y))
5523 Moving2Blocked(x, y, &newx, &newy);
5525 if (Tile[newx][newy] != EL_BLOCKED)
5527 // element is moving, but target field is not free (blocked), but
5528 // already occupied by something different (example: acid pool);
5529 // in this case, only remove the moving field, but not the target
5531 RemoveField(oldx, oldy);
5533 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5535 TEST_DrawLevelField(oldx, oldy);
5540 else if (element == EL_BLOCKED)
5542 Blocked2Moving(x, y, &oldx, &oldy);
5543 if (!IS_MOVING(oldx, oldy))
5547 if (element == EL_BLOCKED &&
5548 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5549 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5550 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5551 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5552 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5553 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5554 next_element = get_next_element(Tile[oldx][oldy]);
5556 RemoveField(oldx, oldy);
5557 RemoveField(newx, newy);
5559 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5561 if (next_element != EL_UNDEFINED)
5562 Tile[oldx][oldy] = next_element;
5564 TEST_DrawLevelField(oldx, oldy);
5565 TEST_DrawLevelField(newx, newy);
5568 void DrawDynamite(int x, int y)
5570 int sx = SCREENX(x), sy = SCREENY(y);
5571 int graphic = el2img(Tile[x][y]);
5574 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5577 if (IS_WALKABLE_INSIDE(Back[x][y]))
5581 DrawLevelElement(x, y, Back[x][y]);
5582 else if (Store[x][y])
5583 DrawLevelElement(x, y, Store[x][y]);
5584 else if (game.use_masked_elements)
5585 DrawLevelElement(x, y, EL_EMPTY);
5587 frame = getGraphicAnimationFrameXY(graphic, x, y);
5589 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5590 DrawGraphicThruMask(sx, sy, graphic, frame);
5592 DrawGraphic(sx, sy, graphic, frame);
5595 static void CheckDynamite(int x, int y)
5597 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5601 if (MovDelay[x][y] != 0)
5604 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5610 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5615 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5617 boolean num_checked_players = 0;
5620 for (i = 0; i < MAX_PLAYERS; i++)
5622 if (stored_player[i].active)
5624 int sx = stored_player[i].jx;
5625 int sy = stored_player[i].jy;
5627 if (num_checked_players == 0)
5634 *sx1 = MIN(*sx1, sx);
5635 *sy1 = MIN(*sy1, sy);
5636 *sx2 = MAX(*sx2, sx);
5637 *sy2 = MAX(*sy2, sy);
5640 num_checked_players++;
5645 static boolean checkIfAllPlayersFitToScreen_RND(void)
5647 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5649 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5651 return (sx2 - sx1 < SCR_FIELDX &&
5652 sy2 - sy1 < SCR_FIELDY);
5655 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5657 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5659 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5661 *sx = (sx1 + sx2) / 2;
5662 *sy = (sy1 + sy2) / 2;
5665 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5666 boolean center_screen, boolean quick_relocation)
5668 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5669 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5670 boolean no_delay = (tape.warp_forward);
5671 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5672 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5673 int new_scroll_x, new_scroll_y;
5675 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5677 // case 1: quick relocation inside visible screen (without scrolling)
5684 if (!level.shifted_relocation || center_screen)
5686 // relocation _with_ centering of screen
5688 new_scroll_x = SCROLL_POSITION_X(x);
5689 new_scroll_y = SCROLL_POSITION_Y(y);
5693 // relocation _without_ centering of screen
5695 // apply distance between old and new player position to scroll position
5696 int shifted_scroll_x = scroll_x + (x - old_x);
5697 int shifted_scroll_y = scroll_y + (y - old_y);
5699 // make sure that shifted scroll position does not scroll beyond screen
5700 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5701 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5703 // special case for teleporting from one end of the playfield to the other
5704 // (this kludge prevents the destination area to be shifted by half a tile
5705 // against the source destination for even screen width or screen height;
5706 // probably most useful when used with high "game.forced_scroll_delay_value"
5707 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5708 if (quick_relocation)
5710 if (EVEN(SCR_FIELDX))
5712 // relocate (teleport) between left and right border (half or full)
5713 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5714 new_scroll_x = SBX_Right;
5715 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5716 new_scroll_x = SBX_Right - 1;
5717 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5718 new_scroll_x = SBX_Left;
5719 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5720 new_scroll_x = SBX_Left + 1;
5723 if (EVEN(SCR_FIELDY))
5725 // relocate (teleport) between top and bottom border (half or full)
5726 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5727 new_scroll_y = SBY_Lower;
5728 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5729 new_scroll_y = SBY_Lower - 1;
5730 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5731 new_scroll_y = SBY_Upper;
5732 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5733 new_scroll_y = SBY_Upper + 1;
5738 if (quick_relocation)
5740 // case 2: quick relocation (redraw without visible scrolling)
5742 scroll_x = new_scroll_x;
5743 scroll_y = new_scroll_y;
5750 // case 3: visible relocation (with scrolling to new position)
5752 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5754 SetVideoFrameDelay(wait_delay_value);
5756 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5758 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5759 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5761 if (dx == 0 && dy == 0) // no scrolling needed at all
5767 // set values for horizontal/vertical screen scrolling (half tile size)
5768 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5769 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5770 int pos_x = dx * TILEX / 2;
5771 int pos_y = dy * TILEY / 2;
5772 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5773 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5775 ScrollLevel(dx, dy);
5778 // scroll in two steps of half tile size to make things smoother
5779 BlitScreenToBitmapExt_RND(window, fx, fy);
5781 // scroll second step to align at full tile size
5782 BlitScreenToBitmap(window);
5788 SetVideoFrameDelay(frame_delay_value_old);
5791 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5793 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5794 int player_nr = GET_PLAYER_NR(el_player);
5795 struct PlayerInfo *player = &stored_player[player_nr];
5796 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5797 boolean no_delay = (tape.warp_forward);
5798 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5799 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5800 int old_jx = player->jx;
5801 int old_jy = player->jy;
5802 int old_element = Tile[old_jx][old_jy];
5803 int element = Tile[jx][jy];
5804 boolean player_relocated = (old_jx != jx || old_jy != jy);
5806 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5807 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5808 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5809 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5810 int leave_side_horiz = move_dir_horiz;
5811 int leave_side_vert = move_dir_vert;
5812 int enter_side = enter_side_horiz | enter_side_vert;
5813 int leave_side = leave_side_horiz | leave_side_vert;
5815 if (player->buried) // do not reanimate dead player
5818 if (!player_relocated) // no need to relocate the player
5821 if (IS_PLAYER(jx, jy)) // player already placed at new position
5823 RemoveField(jx, jy); // temporarily remove newly placed player
5824 DrawLevelField(jx, jy);
5827 if (player->present)
5829 while (player->MovPos)
5831 ScrollPlayer(player, SCROLL_GO_ON);
5832 ScrollScreen(NULL, SCROLL_GO_ON);
5834 AdvanceFrameAndPlayerCounters(player->index_nr);
5838 BackToFront_WithFrameDelay(wait_delay_value);
5841 DrawPlayer(player); // needed here only to cleanup last field
5842 DrawLevelField(player->jx, player->jy); // remove player graphic
5844 player->is_moving = FALSE;
5847 if (IS_CUSTOM_ELEMENT(old_element))
5848 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5850 player->index_bit, leave_side);
5852 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5854 player->index_bit, leave_side);
5856 Tile[jx][jy] = el_player;
5857 InitPlayerField(jx, jy, el_player, TRUE);
5859 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5860 possible that the relocation target field did not contain a player element,
5861 but a walkable element, to which the new player was relocated -- in this
5862 case, restore that (already initialized!) element on the player field */
5863 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5865 Tile[jx][jy] = element; // restore previously existing element
5868 // only visually relocate centered player
5869 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5870 FALSE, level.instant_relocation);
5872 TestIfPlayerTouchesBadThing(jx, jy);
5873 TestIfPlayerTouchesCustomElement(jx, jy);
5875 if (IS_CUSTOM_ELEMENT(element))
5876 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5877 player->index_bit, enter_side);
5879 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5880 player->index_bit, enter_side);
5882 if (player->is_switching)
5884 /* ensure that relocation while still switching an element does not cause
5885 a new element to be treated as also switched directly after relocation
5886 (this is important for teleporter switches that teleport the player to
5887 a place where another teleporter switch is in the same direction, which
5888 would then incorrectly be treated as immediately switched before the
5889 direction key that caused the switch was released) */
5891 player->switch_x += jx - old_jx;
5892 player->switch_y += jy - old_jy;
5896 static void Explode(int ex, int ey, int phase, int mode)
5902 if (game.explosions_delayed)
5904 ExplodeField[ex][ey] = mode;
5908 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5910 int center_element = Tile[ex][ey];
5911 int ce_value = CustomValue[ex][ey];
5912 int ce_score = element_info[center_element].collect_score;
5913 int artwork_element, explosion_element; // set these values later
5915 // remove things displayed in background while burning dynamite
5916 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5919 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5921 // put moving element to center field (and let it explode there)
5922 center_element = MovingOrBlocked2Element(ex, ey);
5923 RemoveMovingField(ex, ey);
5924 Tile[ex][ey] = center_element;
5927 // now "center_element" is finally determined -- set related values now
5928 artwork_element = center_element; // for custom player artwork
5929 explosion_element = center_element; // for custom player artwork
5931 if (IS_PLAYER(ex, ey))
5933 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5935 artwork_element = stored_player[player_nr].artwork_element;
5937 if (level.use_explosion_element[player_nr])
5939 explosion_element = level.explosion_element[player_nr];
5940 artwork_element = explosion_element;
5944 if (mode == EX_TYPE_NORMAL ||
5945 mode == EX_TYPE_CENTER ||
5946 mode == EX_TYPE_CROSS)
5947 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5949 last_phase = element_info[explosion_element].explosion_delay + 1;
5951 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5953 int xx = x - ex + 1;
5954 int yy = y - ey + 1;
5957 if (!IN_LEV_FIELD(x, y) ||
5958 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5959 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5962 element = Tile[x][y];
5964 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5966 element = MovingOrBlocked2Element(x, y);
5968 if (!IS_EXPLOSION_PROOF(element))
5969 RemoveMovingField(x, y);
5972 // indestructible elements can only explode in center (but not flames)
5973 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5974 mode == EX_TYPE_BORDER)) ||
5975 element == EL_FLAMES)
5978 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5979 behaviour, for example when touching a yamyam that explodes to rocks
5980 with active deadly shield, a rock is created under the player !!! */
5981 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5983 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5984 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5985 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5987 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5990 if (IS_ACTIVE_BOMB(element))
5992 // re-activate things under the bomb like gate or penguin
5993 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6000 // save walkable background elements while explosion on same tile
6001 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6002 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6003 Back[x][y] = element;
6005 // ignite explodable elements reached by other explosion
6006 if (element == EL_EXPLOSION)
6007 element = Store2[x][y];
6009 if (AmoebaNr[x][y] &&
6010 (element == EL_AMOEBA_FULL ||
6011 element == EL_BD_AMOEBA ||
6012 element == EL_AMOEBA_GROWING))
6014 AmoebaCnt[AmoebaNr[x][y]]--;
6015 AmoebaCnt2[AmoebaNr[x][y]]--;
6020 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6022 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6024 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6026 if (PLAYERINFO(ex, ey)->use_murphy)
6027 Store[x][y] = EL_EMPTY;
6030 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6031 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6032 else if (IS_PLAYER_ELEMENT(center_element))
6033 Store[x][y] = EL_EMPTY;
6034 else if (center_element == EL_YAMYAM)
6035 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6036 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6037 Store[x][y] = element_info[center_element].content.e[xx][yy];
6039 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6040 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6041 // otherwise) -- FIX THIS !!!
6042 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6043 Store[x][y] = element_info[element].content.e[1][1];
6045 else if (!CAN_EXPLODE(element))
6046 Store[x][y] = element_info[element].content.e[1][1];
6049 Store[x][y] = EL_EMPTY;
6051 if (IS_CUSTOM_ELEMENT(center_element))
6052 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6053 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6054 Store[x][y] >= EL_PREV_CE_8 &&
6055 Store[x][y] <= EL_NEXT_CE_8 ?
6056 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6059 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6060 center_element == EL_AMOEBA_TO_DIAMOND)
6061 Store2[x][y] = element;
6063 Tile[x][y] = EL_EXPLOSION;
6064 GfxElement[x][y] = artwork_element;
6066 ExplodePhase[x][y] = 1;
6067 ExplodeDelay[x][y] = last_phase;
6072 if (center_element == EL_YAMYAM)
6073 game.yamyam_content_nr =
6074 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6086 GfxFrame[x][y] = 0; // restart explosion animation
6088 last_phase = ExplodeDelay[x][y];
6090 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6092 // this can happen if the player leaves an explosion just in time
6093 if (GfxElement[x][y] == EL_UNDEFINED)
6094 GfxElement[x][y] = EL_EMPTY;
6096 border_element = Store2[x][y];
6097 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6098 border_element = StorePlayer[x][y];
6100 if (phase == element_info[border_element].ignition_delay ||
6101 phase == last_phase)
6103 boolean border_explosion = FALSE;
6105 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6106 !PLAYER_EXPLOSION_PROTECTED(x, y))
6108 KillPlayerUnlessExplosionProtected(x, y);
6109 border_explosion = TRUE;
6111 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6113 Tile[x][y] = Store2[x][y];
6116 border_explosion = TRUE;
6118 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6120 AmoebaToDiamond(x, y);
6122 border_explosion = TRUE;
6125 // if an element just explodes due to another explosion (chain-reaction),
6126 // do not immediately end the new explosion when it was the last frame of
6127 // the explosion (as it would be done in the following "if"-statement!)
6128 if (border_explosion && phase == last_phase)
6132 // this can happen if the player was just killed by an explosion
6133 if (GfxElement[x][y] == EL_UNDEFINED)
6134 GfxElement[x][y] = EL_EMPTY;
6136 if (phase == last_phase)
6140 element = Tile[x][y] = Store[x][y];
6141 Store[x][y] = Store2[x][y] = 0;
6142 GfxElement[x][y] = EL_UNDEFINED;
6144 // player can escape from explosions and might therefore be still alive
6145 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6146 element <= EL_PLAYER_IS_EXPLODING_4)
6148 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6149 int explosion_element = EL_PLAYER_1 + player_nr;
6150 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6151 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6153 if (level.use_explosion_element[player_nr])
6154 explosion_element = level.explosion_element[player_nr];
6156 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6157 element_info[explosion_element].content.e[xx][yy]);
6160 // restore probably existing indestructible background element
6161 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6162 element = Tile[x][y] = Back[x][y];
6165 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6166 GfxDir[x][y] = MV_NONE;
6167 ChangeDelay[x][y] = 0;
6168 ChangePage[x][y] = -1;
6170 CustomValue[x][y] = 0;
6172 InitField_WithBug2(x, y, FALSE);
6174 TEST_DrawLevelField(x, y);
6176 TestIfElementTouchesCustomElement(x, y);
6178 if (GFX_CRUMBLED(element))
6179 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6181 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6182 StorePlayer[x][y] = 0;
6184 if (IS_PLAYER_ELEMENT(element))
6185 RelocatePlayer(x, y, element);
6187 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6189 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6190 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6193 TEST_DrawLevelFieldCrumbled(x, y);
6195 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6197 DrawLevelElement(x, y, Back[x][y]);
6198 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6200 else if (IS_WALKABLE_UNDER(Back[x][y]))
6202 DrawLevelGraphic(x, y, graphic, frame);
6203 DrawLevelElementThruMask(x, y, Back[x][y]);
6205 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6206 DrawLevelGraphic(x, y, graphic, frame);
6210 static void DynaExplode(int ex, int ey)
6213 int dynabomb_element = Tile[ex][ey];
6214 int dynabomb_size = 1;
6215 boolean dynabomb_xl = FALSE;
6216 struct PlayerInfo *player;
6217 struct XY *xy = xy_topdown;
6219 if (IS_ACTIVE_BOMB(dynabomb_element))
6221 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6222 dynabomb_size = player->dynabomb_size;
6223 dynabomb_xl = player->dynabomb_xl;
6224 player->dynabombs_left++;
6227 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6229 for (i = 0; i < NUM_DIRECTIONS; i++)
6231 for (j = 1; j <= dynabomb_size; j++)
6233 int x = ex + j * xy[i].x;
6234 int y = ey + j * xy[i].y;
6237 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6240 element = Tile[x][y];
6242 // do not restart explosions of fields with active bombs
6243 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6246 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6248 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6249 !IS_DIGGABLE(element) && !dynabomb_xl)
6255 void Bang(int x, int y)
6257 int element = MovingOrBlocked2Element(x, y);
6258 int explosion_type = EX_TYPE_NORMAL;
6260 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6262 struct PlayerInfo *player = PLAYERINFO(x, y);
6264 element = Tile[x][y] = player->initial_element;
6266 if (level.use_explosion_element[player->index_nr])
6268 int explosion_element = level.explosion_element[player->index_nr];
6270 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6271 explosion_type = EX_TYPE_CROSS;
6272 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6273 explosion_type = EX_TYPE_CENTER;
6281 case EL_BD_BUTTERFLY:
6284 case EL_DARK_YAMYAM:
6288 RaiseScoreElement(element);
6291 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6292 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6293 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6294 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6295 case EL_DYNABOMB_INCREASE_NUMBER:
6296 case EL_DYNABOMB_INCREASE_SIZE:
6297 case EL_DYNABOMB_INCREASE_POWER:
6298 explosion_type = EX_TYPE_DYNA;
6301 case EL_DC_LANDMINE:
6302 explosion_type = EX_TYPE_CENTER;
6307 case EL_LAMP_ACTIVE:
6308 case EL_AMOEBA_TO_DIAMOND:
6309 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6310 explosion_type = EX_TYPE_CENTER;
6314 if (element_info[element].explosion_type == EXPLODES_CROSS)
6315 explosion_type = EX_TYPE_CROSS;
6316 else if (element_info[element].explosion_type == EXPLODES_1X1)
6317 explosion_type = EX_TYPE_CENTER;
6321 if (explosion_type == EX_TYPE_DYNA)
6324 Explode(x, y, EX_PHASE_START, explosion_type);
6326 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6329 static void SplashAcid(int x, int y)
6331 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6332 (!IN_LEV_FIELD(x - 1, y - 2) ||
6333 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6334 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6336 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6337 (!IN_LEV_FIELD(x + 1, y - 2) ||
6338 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6339 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6341 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6344 static void InitBeltMovement(void)
6346 static int belt_base_element[4] =
6348 EL_CONVEYOR_BELT_1_LEFT,
6349 EL_CONVEYOR_BELT_2_LEFT,
6350 EL_CONVEYOR_BELT_3_LEFT,
6351 EL_CONVEYOR_BELT_4_LEFT
6353 static int belt_base_active_element[4] =
6355 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6356 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6357 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6358 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6363 // set frame order for belt animation graphic according to belt direction
6364 for (i = 0; i < NUM_BELTS; i++)
6368 for (j = 0; j < NUM_BELT_PARTS; j++)
6370 int element = belt_base_active_element[belt_nr] + j;
6371 int graphic_1 = el2img(element);
6372 int graphic_2 = el2panelimg(element);
6374 if (game.belt_dir[i] == MV_LEFT)
6376 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6377 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6381 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6382 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6387 SCAN_PLAYFIELD(x, y)
6389 int element = Tile[x][y];
6391 for (i = 0; i < NUM_BELTS; i++)
6393 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6395 int e_belt_nr = getBeltNrFromBeltElement(element);
6398 if (e_belt_nr == belt_nr)
6400 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6402 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6409 static void ToggleBeltSwitch(int x, int y)
6411 static int belt_base_element[4] =
6413 EL_CONVEYOR_BELT_1_LEFT,
6414 EL_CONVEYOR_BELT_2_LEFT,
6415 EL_CONVEYOR_BELT_3_LEFT,
6416 EL_CONVEYOR_BELT_4_LEFT
6418 static int belt_base_active_element[4] =
6420 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6421 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6422 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6423 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6425 static int belt_base_switch_element[4] =
6427 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6428 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6429 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6430 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6432 static int belt_move_dir[4] =
6440 int element = Tile[x][y];
6441 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6442 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6443 int belt_dir = belt_move_dir[belt_dir_nr];
6446 if (!IS_BELT_SWITCH(element))
6449 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6450 game.belt_dir[belt_nr] = belt_dir;
6452 if (belt_dir_nr == 3)
6455 // set frame order for belt animation graphic according to belt direction
6456 for (i = 0; i < NUM_BELT_PARTS; i++)
6458 int element = belt_base_active_element[belt_nr] + i;
6459 int graphic_1 = el2img(element);
6460 int graphic_2 = el2panelimg(element);
6462 if (belt_dir == MV_LEFT)
6464 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6465 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6469 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6470 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6474 SCAN_PLAYFIELD(xx, yy)
6476 int element = Tile[xx][yy];
6478 if (IS_BELT_SWITCH(element))
6480 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6482 if (e_belt_nr == belt_nr)
6484 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6485 TEST_DrawLevelField(xx, yy);
6488 else if (IS_BELT(element) && belt_dir != MV_NONE)
6490 int e_belt_nr = getBeltNrFromBeltElement(element);
6492 if (e_belt_nr == belt_nr)
6494 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6496 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6497 TEST_DrawLevelField(xx, yy);
6500 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6502 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6504 if (e_belt_nr == belt_nr)
6506 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6508 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6509 TEST_DrawLevelField(xx, yy);
6515 static void ToggleSwitchgateSwitch(void)
6519 game.switchgate_pos = !game.switchgate_pos;
6521 SCAN_PLAYFIELD(xx, yy)
6523 int element = Tile[xx][yy];
6525 if (element == EL_SWITCHGATE_SWITCH_UP)
6527 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6528 TEST_DrawLevelField(xx, yy);
6530 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6532 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6533 TEST_DrawLevelField(xx, yy);
6535 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6537 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6538 TEST_DrawLevelField(xx, yy);
6540 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6542 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6543 TEST_DrawLevelField(xx, yy);
6545 else if (element == EL_SWITCHGATE_OPEN ||
6546 element == EL_SWITCHGATE_OPENING)
6548 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6550 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6552 else if (element == EL_SWITCHGATE_CLOSED ||
6553 element == EL_SWITCHGATE_CLOSING)
6555 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6557 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6562 static int getInvisibleActiveFromInvisibleElement(int element)
6564 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6565 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6566 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6570 static int getInvisibleFromInvisibleActiveElement(int element)
6572 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6573 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6574 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6578 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6582 SCAN_PLAYFIELD(x, y)
6584 int element = Tile[x][y];
6586 if (element == EL_LIGHT_SWITCH &&
6587 game.light_time_left > 0)
6589 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6590 TEST_DrawLevelField(x, y);
6592 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6593 game.light_time_left == 0)
6595 Tile[x][y] = EL_LIGHT_SWITCH;
6596 TEST_DrawLevelField(x, y);
6598 else if (element == EL_EMC_DRIPPER &&
6599 game.light_time_left > 0)
6601 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6602 TEST_DrawLevelField(x, y);
6604 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6605 game.light_time_left == 0)
6607 Tile[x][y] = EL_EMC_DRIPPER;
6608 TEST_DrawLevelField(x, y);
6610 else if (element == EL_INVISIBLE_STEELWALL ||
6611 element == EL_INVISIBLE_WALL ||
6612 element == EL_INVISIBLE_SAND)
6614 if (game.light_time_left > 0)
6615 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6617 TEST_DrawLevelField(x, y);
6619 // uncrumble neighbour fields, if needed
6620 if (element == EL_INVISIBLE_SAND)
6621 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6623 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6624 element == EL_INVISIBLE_WALL_ACTIVE ||
6625 element == EL_INVISIBLE_SAND_ACTIVE)
6627 if (game.light_time_left == 0)
6628 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6630 TEST_DrawLevelField(x, y);
6632 // re-crumble neighbour fields, if needed
6633 if (element == EL_INVISIBLE_SAND)
6634 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6639 static void RedrawAllInvisibleElementsForLenses(void)
6643 SCAN_PLAYFIELD(x, y)
6645 int element = Tile[x][y];
6647 if (element == EL_EMC_DRIPPER &&
6648 game.lenses_time_left > 0)
6650 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6651 TEST_DrawLevelField(x, y);
6653 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6654 game.lenses_time_left == 0)
6656 Tile[x][y] = EL_EMC_DRIPPER;
6657 TEST_DrawLevelField(x, y);
6659 else if (element == EL_INVISIBLE_STEELWALL ||
6660 element == EL_INVISIBLE_WALL ||
6661 element == EL_INVISIBLE_SAND)
6663 if (game.lenses_time_left > 0)
6664 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6666 TEST_DrawLevelField(x, y);
6668 // uncrumble neighbour fields, if needed
6669 if (element == EL_INVISIBLE_SAND)
6670 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6672 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6673 element == EL_INVISIBLE_WALL_ACTIVE ||
6674 element == EL_INVISIBLE_SAND_ACTIVE)
6676 if (game.lenses_time_left == 0)
6677 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6679 TEST_DrawLevelField(x, y);
6681 // re-crumble neighbour fields, if needed
6682 if (element == EL_INVISIBLE_SAND)
6683 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6688 static void RedrawAllInvisibleElementsForMagnifier(void)
6692 SCAN_PLAYFIELD(x, y)
6694 int element = Tile[x][y];
6696 if (element == EL_EMC_FAKE_GRASS &&
6697 game.magnify_time_left > 0)
6699 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6700 TEST_DrawLevelField(x, y);
6702 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6703 game.magnify_time_left == 0)
6705 Tile[x][y] = EL_EMC_FAKE_GRASS;
6706 TEST_DrawLevelField(x, y);
6708 else if (IS_GATE_GRAY(element) &&
6709 game.magnify_time_left > 0)
6711 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6712 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6713 IS_EM_GATE_GRAY(element) ?
6714 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6715 IS_EMC_GATE_GRAY(element) ?
6716 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6717 IS_DC_GATE_GRAY(element) ?
6718 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6720 TEST_DrawLevelField(x, y);
6722 else if (IS_GATE_GRAY_ACTIVE(element) &&
6723 game.magnify_time_left == 0)
6725 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6726 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6727 IS_EM_GATE_GRAY_ACTIVE(element) ?
6728 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6729 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6730 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6731 IS_DC_GATE_GRAY_ACTIVE(element) ?
6732 EL_DC_GATE_WHITE_GRAY :
6734 TEST_DrawLevelField(x, y);
6739 static void ToggleLightSwitch(int x, int y)
6741 int element = Tile[x][y];
6743 game.light_time_left =
6744 (element == EL_LIGHT_SWITCH ?
6745 level.time_light * FRAMES_PER_SECOND : 0);
6747 RedrawAllLightSwitchesAndInvisibleElements();
6750 static void ActivateTimegateSwitch(int x, int y)
6754 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6756 SCAN_PLAYFIELD(xx, yy)
6758 int element = Tile[xx][yy];
6760 if (element == EL_TIMEGATE_CLOSED ||
6761 element == EL_TIMEGATE_CLOSING)
6763 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6764 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6768 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6770 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6771 TEST_DrawLevelField(xx, yy);
6777 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6778 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6781 static void Impact(int x, int y)
6783 boolean last_line = (y == lev_fieldy - 1);
6784 boolean object_hit = FALSE;
6785 boolean impact = (last_line || object_hit);
6786 int element = Tile[x][y];
6787 int smashed = EL_STEELWALL;
6789 if (!last_line) // check if element below was hit
6791 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6794 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6795 MovDir[x][y + 1] != MV_DOWN ||
6796 MovPos[x][y + 1] <= TILEY / 2));
6798 // do not smash moving elements that left the smashed field in time
6799 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6800 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6803 #if USE_QUICKSAND_IMPACT_BUGFIX
6804 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6806 RemoveMovingField(x, y + 1);
6807 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6808 Tile[x][y + 2] = EL_ROCK;
6809 TEST_DrawLevelField(x, y + 2);
6814 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6816 RemoveMovingField(x, y + 1);
6817 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6818 Tile[x][y + 2] = EL_ROCK;
6819 TEST_DrawLevelField(x, y + 2);
6826 smashed = MovingOrBlocked2Element(x, y + 1);
6828 impact = (last_line || object_hit);
6831 if (!last_line && smashed == EL_ACID) // element falls into acid
6833 SplashAcid(x, y + 1);
6837 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6838 // only reset graphic animation if graphic really changes after impact
6840 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6842 ResetGfxAnimation(x, y);
6843 TEST_DrawLevelField(x, y);
6846 if (impact && CAN_EXPLODE_IMPACT(element))
6851 else if (impact && element == EL_PEARL &&
6852 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6854 ResetGfxAnimation(x, y);
6856 Tile[x][y] = EL_PEARL_BREAKING;
6857 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6860 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6862 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6867 if (impact && element == EL_AMOEBA_DROP)
6869 if (object_hit && IS_PLAYER(x, y + 1))
6870 KillPlayerUnlessEnemyProtected(x, y + 1);
6871 else if (object_hit && smashed == EL_PENGUIN)
6875 Tile[x][y] = EL_AMOEBA_GROWING;
6876 Store[x][y] = EL_AMOEBA_WET;
6878 ResetRandomAnimationValue(x, y);
6883 if (object_hit) // check which object was hit
6885 if ((CAN_PASS_MAGIC_WALL(element) &&
6886 (smashed == EL_MAGIC_WALL ||
6887 smashed == EL_BD_MAGIC_WALL)) ||
6888 (CAN_PASS_DC_MAGIC_WALL(element) &&
6889 smashed == EL_DC_MAGIC_WALL))
6892 int activated_magic_wall =
6893 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6894 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6895 EL_DC_MAGIC_WALL_ACTIVE);
6897 // activate magic wall / mill
6898 SCAN_PLAYFIELD(xx, yy)
6900 if (Tile[xx][yy] == smashed)
6901 Tile[xx][yy] = activated_magic_wall;
6904 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6905 game.magic_wall_active = TRUE;
6907 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6908 SND_MAGIC_WALL_ACTIVATING :
6909 smashed == EL_BD_MAGIC_WALL ?
6910 SND_BD_MAGIC_WALL_ACTIVATING :
6911 SND_DC_MAGIC_WALL_ACTIVATING));
6914 if (IS_PLAYER(x, y + 1))
6916 if (CAN_SMASH_PLAYER(element))
6918 KillPlayerUnlessEnemyProtected(x, y + 1);
6922 else if (smashed == EL_PENGUIN)
6924 if (CAN_SMASH_PLAYER(element))
6930 else if (element == EL_BD_DIAMOND)
6932 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6938 else if (((element == EL_SP_INFOTRON ||
6939 element == EL_SP_ZONK) &&
6940 (smashed == EL_SP_SNIKSNAK ||
6941 smashed == EL_SP_ELECTRON ||
6942 smashed == EL_SP_DISK_ORANGE)) ||
6943 (element == EL_SP_INFOTRON &&
6944 smashed == EL_SP_DISK_YELLOW))
6949 else if (CAN_SMASH_EVERYTHING(element))
6951 if (IS_CLASSIC_ENEMY(smashed) ||
6952 CAN_EXPLODE_SMASHED(smashed))
6957 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6959 if (smashed == EL_LAMP ||
6960 smashed == EL_LAMP_ACTIVE)
6965 else if (smashed == EL_NUT)
6967 Tile[x][y + 1] = EL_NUT_BREAKING;
6968 PlayLevelSound(x, y, SND_NUT_BREAKING);
6969 RaiseScoreElement(EL_NUT);
6972 else if (smashed == EL_PEARL)
6974 ResetGfxAnimation(x, y);
6976 Tile[x][y + 1] = EL_PEARL_BREAKING;
6977 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6980 else if (smashed == EL_DIAMOND)
6982 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6983 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6986 else if (IS_BELT_SWITCH(smashed))
6988 ToggleBeltSwitch(x, y + 1);
6990 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6991 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6992 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6993 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6995 ToggleSwitchgateSwitch();
6997 else if (smashed == EL_LIGHT_SWITCH ||
6998 smashed == EL_LIGHT_SWITCH_ACTIVE)
7000 ToggleLightSwitch(x, y + 1);
7004 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7006 CheckElementChangeBySide(x, y + 1, smashed, element,
7007 CE_SWITCHED, CH_SIDE_TOP);
7008 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7014 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7019 // play sound of magic wall / mill
7021 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7022 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7023 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7025 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7026 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7027 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7028 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7029 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7030 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7035 // play sound of object that hits the ground
7036 if (last_line || object_hit)
7037 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7040 static void TurnRoundExt(int x, int y)
7052 { 0, 0 }, { 0, 0 }, { 0, 0 },
7057 int left, right, back;
7061 { MV_DOWN, MV_UP, MV_RIGHT },
7062 { MV_UP, MV_DOWN, MV_LEFT },
7064 { MV_LEFT, MV_RIGHT, MV_DOWN },
7068 { MV_RIGHT, MV_LEFT, MV_UP }
7071 int element = Tile[x][y];
7072 int move_pattern = element_info[element].move_pattern;
7074 int old_move_dir = MovDir[x][y];
7075 int left_dir = turn[old_move_dir].left;
7076 int right_dir = turn[old_move_dir].right;
7077 int back_dir = turn[old_move_dir].back;
7079 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7080 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7081 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7082 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7084 int left_x = x + left_dx, left_y = y + left_dy;
7085 int right_x = x + right_dx, right_y = y + right_dy;
7086 int move_x = x + move_dx, move_y = y + move_dy;
7090 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7092 TestIfBadThingTouchesOtherBadThing(x, y);
7094 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7095 MovDir[x][y] = right_dir;
7096 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7097 MovDir[x][y] = left_dir;
7099 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7101 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7104 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7106 TestIfBadThingTouchesOtherBadThing(x, y);
7108 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7109 MovDir[x][y] = left_dir;
7110 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7111 MovDir[x][y] = right_dir;
7113 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7115 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7118 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7120 TestIfBadThingTouchesOtherBadThing(x, y);
7122 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7123 MovDir[x][y] = left_dir;
7124 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7125 MovDir[x][y] = right_dir;
7127 if (MovDir[x][y] != old_move_dir)
7130 else if (element == EL_YAMYAM)
7132 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7133 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7135 if (can_turn_left && can_turn_right)
7136 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7137 else if (can_turn_left)
7138 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7139 else if (can_turn_right)
7140 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7142 MovDir[x][y] = back_dir;
7144 MovDelay[x][y] = 16 + 16 * RND(3);
7146 else if (element == EL_DARK_YAMYAM)
7148 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7150 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7153 if (can_turn_left && can_turn_right)
7154 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7155 else if (can_turn_left)
7156 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7157 else if (can_turn_right)
7158 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7160 MovDir[x][y] = back_dir;
7162 MovDelay[x][y] = 16 + 16 * RND(3);
7164 else if (element == EL_PACMAN)
7166 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7167 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7169 if (can_turn_left && can_turn_right)
7170 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7171 else if (can_turn_left)
7172 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7173 else if (can_turn_right)
7174 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7176 MovDir[x][y] = back_dir;
7178 MovDelay[x][y] = 6 + RND(40);
7180 else if (element == EL_PIG)
7182 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7183 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7184 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7185 boolean should_turn_left, should_turn_right, should_move_on;
7187 int rnd = RND(rnd_value);
7189 should_turn_left = (can_turn_left &&
7191 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7192 y + back_dy + left_dy)));
7193 should_turn_right = (can_turn_right &&
7195 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7196 y + back_dy + right_dy)));
7197 should_move_on = (can_move_on &&
7200 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7201 y + move_dy + left_dy) ||
7202 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7203 y + move_dy + right_dy)));
7205 if (should_turn_left || should_turn_right || should_move_on)
7207 if (should_turn_left && should_turn_right && should_move_on)
7208 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7209 rnd < 2 * rnd_value / 3 ? right_dir :
7211 else if (should_turn_left && should_turn_right)
7212 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7213 else if (should_turn_left && should_move_on)
7214 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7215 else if (should_turn_right && should_move_on)
7216 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7217 else if (should_turn_left)
7218 MovDir[x][y] = left_dir;
7219 else if (should_turn_right)
7220 MovDir[x][y] = right_dir;
7221 else if (should_move_on)
7222 MovDir[x][y] = old_move_dir;
7224 else if (can_move_on && rnd > rnd_value / 8)
7225 MovDir[x][y] = old_move_dir;
7226 else if (can_turn_left && can_turn_right)
7227 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7228 else if (can_turn_left && rnd > rnd_value / 8)
7229 MovDir[x][y] = left_dir;
7230 else if (can_turn_right && rnd > rnd_value/8)
7231 MovDir[x][y] = right_dir;
7233 MovDir[x][y] = back_dir;
7235 xx = x + move_xy[MovDir[x][y]].dx;
7236 yy = y + move_xy[MovDir[x][y]].dy;
7238 if (!IN_LEV_FIELD(xx, yy) ||
7239 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7240 MovDir[x][y] = old_move_dir;
7244 else if (element == EL_DRAGON)
7246 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7247 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7248 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7250 int rnd = RND(rnd_value);
7252 if (can_move_on && rnd > rnd_value / 8)
7253 MovDir[x][y] = old_move_dir;
7254 else if (can_turn_left && can_turn_right)
7255 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7256 else if (can_turn_left && rnd > rnd_value / 8)
7257 MovDir[x][y] = left_dir;
7258 else if (can_turn_right && rnd > rnd_value / 8)
7259 MovDir[x][y] = right_dir;
7261 MovDir[x][y] = back_dir;
7263 xx = x + move_xy[MovDir[x][y]].dx;
7264 yy = y + move_xy[MovDir[x][y]].dy;
7266 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7267 MovDir[x][y] = old_move_dir;
7271 else if (element == EL_MOLE)
7273 boolean can_move_on =
7274 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7275 IS_AMOEBOID(Tile[move_x][move_y]) ||
7276 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7279 boolean can_turn_left =
7280 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7281 IS_AMOEBOID(Tile[left_x][left_y])));
7283 boolean can_turn_right =
7284 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7285 IS_AMOEBOID(Tile[right_x][right_y])));
7287 if (can_turn_left && can_turn_right)
7288 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7289 else if (can_turn_left)
7290 MovDir[x][y] = left_dir;
7292 MovDir[x][y] = right_dir;
7295 if (MovDir[x][y] != old_move_dir)
7298 else if (element == EL_BALLOON)
7300 MovDir[x][y] = game.wind_direction;
7303 else if (element == EL_SPRING)
7305 if (MovDir[x][y] & MV_HORIZONTAL)
7307 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7308 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7310 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7311 ResetGfxAnimation(move_x, move_y);
7312 TEST_DrawLevelField(move_x, move_y);
7314 MovDir[x][y] = back_dir;
7316 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7317 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7318 MovDir[x][y] = MV_NONE;
7323 else if (element == EL_ROBOT ||
7324 element == EL_SATELLITE ||
7325 element == EL_PENGUIN ||
7326 element == EL_EMC_ANDROID)
7328 int attr_x = -1, attr_y = -1;
7330 if (game.all_players_gone)
7332 attr_x = game.exit_x;
7333 attr_y = game.exit_y;
7339 for (i = 0; i < MAX_PLAYERS; i++)
7341 struct PlayerInfo *player = &stored_player[i];
7342 int jx = player->jx, jy = player->jy;
7344 if (!player->active)
7348 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7356 if (element == EL_ROBOT &&
7357 game.robot_wheel_x >= 0 &&
7358 game.robot_wheel_y >= 0 &&
7359 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7360 game.engine_version < VERSION_IDENT(3,1,0,0)))
7362 attr_x = game.robot_wheel_x;
7363 attr_y = game.robot_wheel_y;
7366 if (element == EL_PENGUIN)
7369 struct XY *xy = xy_topdown;
7371 for (i = 0; i < NUM_DIRECTIONS; i++)
7373 int ex = x + xy[i].x;
7374 int ey = y + xy[i].y;
7376 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7377 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7378 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7379 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7388 MovDir[x][y] = MV_NONE;
7390 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7391 else if (attr_x > x)
7392 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7394 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7395 else if (attr_y > y)
7396 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7398 if (element == EL_ROBOT)
7402 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7403 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7404 Moving2Blocked(x, y, &newx, &newy);
7406 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7407 MovDelay[x][y] = 8 + 8 * !RND(3);
7409 MovDelay[x][y] = 16;
7411 else if (element == EL_PENGUIN)
7417 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7419 boolean first_horiz = RND(2);
7420 int new_move_dir = MovDir[x][y];
7423 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7424 Moving2Blocked(x, y, &newx, &newy);
7426 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7430 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7431 Moving2Blocked(x, y, &newx, &newy);
7433 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7436 MovDir[x][y] = old_move_dir;
7440 else if (element == EL_SATELLITE)
7446 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7448 boolean first_horiz = RND(2);
7449 int new_move_dir = MovDir[x][y];
7452 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7453 Moving2Blocked(x, y, &newx, &newy);
7455 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7459 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7460 Moving2Blocked(x, y, &newx, &newy);
7462 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7465 MovDir[x][y] = old_move_dir;
7469 else if (element == EL_EMC_ANDROID)
7471 static int check_pos[16] =
7473 -1, // 0 => (invalid)
7476 -1, // 3 => (invalid)
7478 0, // 5 => MV_LEFT | MV_UP
7479 2, // 6 => MV_RIGHT | MV_UP
7480 -1, // 7 => (invalid)
7482 6, // 9 => MV_LEFT | MV_DOWN
7483 4, // 10 => MV_RIGHT | MV_DOWN
7484 -1, // 11 => (invalid)
7485 -1, // 12 => (invalid)
7486 -1, // 13 => (invalid)
7487 -1, // 14 => (invalid)
7488 -1, // 15 => (invalid)
7496 { -1, -1, MV_LEFT | MV_UP },
7498 { +1, -1, MV_RIGHT | MV_UP },
7499 { +1, 0, MV_RIGHT },
7500 { +1, +1, MV_RIGHT | MV_DOWN },
7502 { -1, +1, MV_LEFT | MV_DOWN },
7505 int start_pos, check_order;
7506 boolean can_clone = FALSE;
7509 // check if there is any free field around current position
7510 for (i = 0; i < 8; i++)
7512 int newx = x + check_xy[i].dx;
7513 int newy = y + check_xy[i].dy;
7515 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7523 if (can_clone) // randomly find an element to clone
7527 start_pos = check_pos[RND(8)];
7528 check_order = (RND(2) ? -1 : +1);
7530 for (i = 0; i < 8; i++)
7532 int pos_raw = start_pos + i * check_order;
7533 int pos = (pos_raw + 8) % 8;
7534 int newx = x + check_xy[pos].dx;
7535 int newy = y + check_xy[pos].dy;
7537 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7539 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7540 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7542 Store[x][y] = Tile[newx][newy];
7551 if (can_clone) // randomly find a direction to move
7555 start_pos = check_pos[RND(8)];
7556 check_order = (RND(2) ? -1 : +1);
7558 for (i = 0; i < 8; i++)
7560 int pos_raw = start_pos + i * check_order;
7561 int pos = (pos_raw + 8) % 8;
7562 int newx = x + check_xy[pos].dx;
7563 int newy = y + check_xy[pos].dy;
7564 int new_move_dir = check_xy[pos].dir;
7566 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7568 MovDir[x][y] = new_move_dir;
7569 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7578 if (can_clone) // cloning and moving successful
7581 // cannot clone -- try to move towards player
7583 start_pos = check_pos[MovDir[x][y] & 0x0f];
7584 check_order = (RND(2) ? -1 : +1);
7586 for (i = 0; i < 3; i++)
7588 // first check start_pos, then previous/next or (next/previous) pos
7589 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7590 int pos = (pos_raw + 8) % 8;
7591 int newx = x + check_xy[pos].dx;
7592 int newy = y + check_xy[pos].dy;
7593 int new_move_dir = check_xy[pos].dir;
7595 if (IS_PLAYER(newx, newy))
7598 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7600 MovDir[x][y] = new_move_dir;
7601 MovDelay[x][y] = level.android_move_time * 8 + 1;
7608 else if (move_pattern == MV_TURNING_LEFT ||
7609 move_pattern == MV_TURNING_RIGHT ||
7610 move_pattern == MV_TURNING_LEFT_RIGHT ||
7611 move_pattern == MV_TURNING_RIGHT_LEFT ||
7612 move_pattern == MV_TURNING_RANDOM ||
7613 move_pattern == MV_ALL_DIRECTIONS)
7615 boolean can_turn_left =
7616 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7617 boolean can_turn_right =
7618 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7620 if (element_info[element].move_stepsize == 0) // "not moving"
7623 if (move_pattern == MV_TURNING_LEFT)
7624 MovDir[x][y] = left_dir;
7625 else if (move_pattern == MV_TURNING_RIGHT)
7626 MovDir[x][y] = right_dir;
7627 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7628 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7629 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7630 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7631 else if (move_pattern == MV_TURNING_RANDOM)
7632 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7633 can_turn_right && !can_turn_left ? right_dir :
7634 RND(2) ? left_dir : right_dir);
7635 else if (can_turn_left && can_turn_right)
7636 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7637 else if (can_turn_left)
7638 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7639 else if (can_turn_right)
7640 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7642 MovDir[x][y] = back_dir;
7644 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7646 else if (move_pattern == MV_HORIZONTAL ||
7647 move_pattern == MV_VERTICAL)
7649 if (move_pattern & old_move_dir)
7650 MovDir[x][y] = back_dir;
7651 else if (move_pattern == MV_HORIZONTAL)
7652 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7653 else if (move_pattern == MV_VERTICAL)
7654 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7656 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7658 else if (move_pattern & MV_ANY_DIRECTION)
7660 MovDir[x][y] = move_pattern;
7661 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7663 else if (move_pattern & MV_WIND_DIRECTION)
7665 MovDir[x][y] = game.wind_direction;
7666 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7668 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7670 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7671 MovDir[x][y] = left_dir;
7672 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7673 MovDir[x][y] = right_dir;
7675 if (MovDir[x][y] != old_move_dir)
7676 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7678 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7680 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7681 MovDir[x][y] = right_dir;
7682 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7683 MovDir[x][y] = left_dir;
7685 if (MovDir[x][y] != old_move_dir)
7686 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7688 else if (move_pattern == MV_TOWARDS_PLAYER ||
7689 move_pattern == MV_AWAY_FROM_PLAYER)
7691 int attr_x = -1, attr_y = -1;
7693 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7695 if (game.all_players_gone)
7697 attr_x = game.exit_x;
7698 attr_y = game.exit_y;
7704 for (i = 0; i < MAX_PLAYERS; i++)
7706 struct PlayerInfo *player = &stored_player[i];
7707 int jx = player->jx, jy = player->jy;
7709 if (!player->active)
7713 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7721 MovDir[x][y] = MV_NONE;
7723 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7724 else if (attr_x > x)
7725 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7727 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7728 else if (attr_y > y)
7729 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7731 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7733 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7735 boolean first_horiz = RND(2);
7736 int new_move_dir = MovDir[x][y];
7738 if (element_info[element].move_stepsize == 0) // "not moving"
7740 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7741 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7747 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7748 Moving2Blocked(x, y, &newx, &newy);
7750 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7754 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7755 Moving2Blocked(x, y, &newx, &newy);
7757 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7760 MovDir[x][y] = old_move_dir;
7763 else if (move_pattern == MV_WHEN_PUSHED ||
7764 move_pattern == MV_WHEN_DROPPED)
7766 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7767 MovDir[x][y] = MV_NONE;
7771 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7773 struct XY *test_xy = xy_topdown;
7774 static int test_dir[4] =
7781 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7782 int move_preference = -1000000; // start with very low preference
7783 int new_move_dir = MV_NONE;
7784 int start_test = RND(4);
7787 for (i = 0; i < NUM_DIRECTIONS; i++)
7789 int j = (start_test + i) % 4;
7790 int move_dir = test_dir[j];
7791 int move_dir_preference;
7793 xx = x + test_xy[j].x;
7794 yy = y + test_xy[j].y;
7796 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7797 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7799 new_move_dir = move_dir;
7804 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7807 move_dir_preference = -1 * RunnerVisit[xx][yy];
7808 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7809 move_dir_preference = PlayerVisit[xx][yy];
7811 if (move_dir_preference > move_preference)
7813 // prefer field that has not been visited for the longest time
7814 move_preference = move_dir_preference;
7815 new_move_dir = move_dir;
7817 else if (move_dir_preference == move_preference &&
7818 move_dir == old_move_dir)
7820 // prefer last direction when all directions are preferred equally
7821 move_preference = move_dir_preference;
7822 new_move_dir = move_dir;
7826 MovDir[x][y] = new_move_dir;
7827 if (old_move_dir != new_move_dir)
7828 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7832 static void TurnRound(int x, int y)
7834 int direction = MovDir[x][y];
7838 GfxDir[x][y] = MovDir[x][y];
7840 if (direction != MovDir[x][y])
7844 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7846 ResetGfxFrame(x, y);
7849 static boolean JustBeingPushed(int x, int y)
7853 for (i = 0; i < MAX_PLAYERS; i++)
7855 struct PlayerInfo *player = &stored_player[i];
7857 if (player->active && player->is_pushing && player->MovPos)
7859 int next_jx = player->jx + (player->jx - player->last_jx);
7860 int next_jy = player->jy + (player->jy - player->last_jy);
7862 if (x == next_jx && y == next_jy)
7870 static void StartMoving(int x, int y)
7872 boolean started_moving = FALSE; // some elements can fall _and_ move
7873 int element = Tile[x][y];
7878 if (MovDelay[x][y] == 0)
7879 GfxAction[x][y] = ACTION_DEFAULT;
7881 if (CAN_FALL(element) && y < lev_fieldy - 1)
7883 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7884 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7885 if (JustBeingPushed(x, y))
7888 if (element == EL_QUICKSAND_FULL)
7890 if (IS_FREE(x, y + 1))
7892 InitMovingField(x, y, MV_DOWN);
7893 started_moving = TRUE;
7895 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7896 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7897 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7898 Store[x][y] = EL_ROCK;
7900 Store[x][y] = EL_ROCK;
7903 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7905 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7907 if (!MovDelay[x][y])
7909 MovDelay[x][y] = TILEY + 1;
7911 ResetGfxAnimation(x, y);
7912 ResetGfxAnimation(x, y + 1);
7917 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7918 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7925 Tile[x][y] = EL_QUICKSAND_EMPTY;
7926 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7927 Store[x][y + 1] = Store[x][y];
7930 PlayLevelSoundAction(x, y, ACTION_FILLING);
7932 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7934 if (!MovDelay[x][y])
7936 MovDelay[x][y] = TILEY + 1;
7938 ResetGfxAnimation(x, y);
7939 ResetGfxAnimation(x, y + 1);
7944 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7945 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7952 Tile[x][y] = EL_QUICKSAND_EMPTY;
7953 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7954 Store[x][y + 1] = Store[x][y];
7957 PlayLevelSoundAction(x, y, ACTION_FILLING);
7960 else if (element == EL_QUICKSAND_FAST_FULL)
7962 if (IS_FREE(x, y + 1))
7964 InitMovingField(x, y, MV_DOWN);
7965 started_moving = TRUE;
7967 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7968 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7969 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7970 Store[x][y] = EL_ROCK;
7972 Store[x][y] = EL_ROCK;
7975 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7977 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7979 if (!MovDelay[x][y])
7981 MovDelay[x][y] = TILEY + 1;
7983 ResetGfxAnimation(x, y);
7984 ResetGfxAnimation(x, y + 1);
7989 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7990 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7997 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7998 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7999 Store[x][y + 1] = Store[x][y];
8002 PlayLevelSoundAction(x, y, ACTION_FILLING);
8004 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8006 if (!MovDelay[x][y])
8008 MovDelay[x][y] = TILEY + 1;
8010 ResetGfxAnimation(x, y);
8011 ResetGfxAnimation(x, y + 1);
8016 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8017 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8024 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8025 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8026 Store[x][y + 1] = Store[x][y];
8029 PlayLevelSoundAction(x, y, ACTION_FILLING);
8032 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8033 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8035 InitMovingField(x, y, MV_DOWN);
8036 started_moving = TRUE;
8038 Tile[x][y] = EL_QUICKSAND_FILLING;
8039 Store[x][y] = element;
8041 PlayLevelSoundAction(x, y, ACTION_FILLING);
8043 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8044 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8046 InitMovingField(x, y, MV_DOWN);
8047 started_moving = TRUE;
8049 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8050 Store[x][y] = element;
8052 PlayLevelSoundAction(x, y, ACTION_FILLING);
8054 else if (element == EL_MAGIC_WALL_FULL)
8056 if (IS_FREE(x, y + 1))
8058 InitMovingField(x, y, MV_DOWN);
8059 started_moving = TRUE;
8061 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8062 Store[x][y] = EL_CHANGED(Store[x][y]);
8064 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8066 if (!MovDelay[x][y])
8067 MovDelay[x][y] = TILEY / 4 + 1;
8076 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8077 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8078 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8082 else if (element == EL_BD_MAGIC_WALL_FULL)
8084 if (IS_FREE(x, y + 1))
8086 InitMovingField(x, y, MV_DOWN);
8087 started_moving = TRUE;
8089 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8090 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8092 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8094 if (!MovDelay[x][y])
8095 MovDelay[x][y] = TILEY / 4 + 1;
8104 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8105 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8106 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8110 else if (element == EL_DC_MAGIC_WALL_FULL)
8112 if (IS_FREE(x, y + 1))
8114 InitMovingField(x, y, MV_DOWN);
8115 started_moving = TRUE;
8117 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8118 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8120 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8122 if (!MovDelay[x][y])
8123 MovDelay[x][y] = TILEY / 4 + 1;
8132 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8133 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8134 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8138 else if ((CAN_PASS_MAGIC_WALL(element) &&
8139 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8140 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8141 (CAN_PASS_DC_MAGIC_WALL(element) &&
8142 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8145 InitMovingField(x, y, MV_DOWN);
8146 started_moving = TRUE;
8149 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8150 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8151 EL_DC_MAGIC_WALL_FILLING);
8152 Store[x][y] = element;
8154 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8156 SplashAcid(x, y + 1);
8158 InitMovingField(x, y, MV_DOWN);
8159 started_moving = TRUE;
8161 Store[x][y] = EL_ACID;
8164 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8165 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8166 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8167 CAN_FALL(element) && WasJustFalling[x][y] &&
8168 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8170 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8171 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8172 (Tile[x][y + 1] == EL_BLOCKED)))
8174 /* this is needed for a special case not covered by calling "Impact()"
8175 from "ContinueMoving()": if an element moves to a tile directly below
8176 another element which was just falling on that tile (which was empty
8177 in the previous frame), the falling element above would just stop
8178 instead of smashing the element below (in previous version, the above
8179 element was just checked for "moving" instead of "falling", resulting
8180 in incorrect smashes caused by horizontal movement of the above
8181 element; also, the case of the player being the element to smash was
8182 simply not covered here... :-/ ) */
8184 CheckCollision[x][y] = 0;
8185 CheckImpact[x][y] = 0;
8189 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8191 if (MovDir[x][y] == MV_NONE)
8193 InitMovingField(x, y, MV_DOWN);
8194 started_moving = TRUE;
8197 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8199 if (WasJustFalling[x][y]) // prevent animation from being restarted
8200 MovDir[x][y] = MV_DOWN;
8202 InitMovingField(x, y, MV_DOWN);
8203 started_moving = TRUE;
8205 else if (element == EL_AMOEBA_DROP)
8207 Tile[x][y] = EL_AMOEBA_GROWING;
8208 Store[x][y] = EL_AMOEBA_WET;
8210 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8211 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8212 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8213 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8215 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8216 (IS_FREE(x - 1, y + 1) ||
8217 Tile[x - 1][y + 1] == EL_ACID));
8218 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8219 (IS_FREE(x + 1, y + 1) ||
8220 Tile[x + 1][y + 1] == EL_ACID));
8221 boolean can_fall_any = (can_fall_left || can_fall_right);
8222 boolean can_fall_both = (can_fall_left && can_fall_right);
8223 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8225 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8227 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8228 can_fall_right = FALSE;
8229 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8230 can_fall_left = FALSE;
8231 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8232 can_fall_right = FALSE;
8233 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8234 can_fall_left = FALSE;
8236 can_fall_any = (can_fall_left || can_fall_right);
8237 can_fall_both = FALSE;
8242 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8243 can_fall_right = FALSE; // slip down on left side
8245 can_fall_left = !(can_fall_right = RND(2));
8247 can_fall_both = FALSE;
8252 // if not determined otherwise, prefer left side for slipping down
8253 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8254 started_moving = TRUE;
8257 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8259 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8260 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8261 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8262 int belt_dir = game.belt_dir[belt_nr];
8264 if ((belt_dir == MV_LEFT && left_is_free) ||
8265 (belt_dir == MV_RIGHT && right_is_free))
8267 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8269 InitMovingField(x, y, belt_dir);
8270 started_moving = TRUE;
8272 Pushed[x][y] = TRUE;
8273 Pushed[nextx][y] = TRUE;
8275 GfxAction[x][y] = ACTION_DEFAULT;
8279 MovDir[x][y] = 0; // if element was moving, stop it
8284 // not "else if" because of elements that can fall and move (EL_SPRING)
8285 if (CAN_MOVE(element) && !started_moving)
8287 int move_pattern = element_info[element].move_pattern;
8290 Moving2Blocked(x, y, &newx, &newy);
8292 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8295 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8296 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8298 WasJustMoving[x][y] = 0;
8299 CheckCollision[x][y] = 0;
8301 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8303 if (Tile[x][y] != element) // element has changed
8307 if (!MovDelay[x][y]) // start new movement phase
8309 // all objects that can change their move direction after each step
8310 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8312 if (element != EL_YAMYAM &&
8313 element != EL_DARK_YAMYAM &&
8314 element != EL_PACMAN &&
8315 !(move_pattern & MV_ANY_DIRECTION) &&
8316 move_pattern != MV_TURNING_LEFT &&
8317 move_pattern != MV_TURNING_RIGHT &&
8318 move_pattern != MV_TURNING_LEFT_RIGHT &&
8319 move_pattern != MV_TURNING_RIGHT_LEFT &&
8320 move_pattern != MV_TURNING_RANDOM)
8324 if (MovDelay[x][y] && (element == EL_BUG ||
8325 element == EL_SPACESHIP ||
8326 element == EL_SP_SNIKSNAK ||
8327 element == EL_SP_ELECTRON ||
8328 element == EL_MOLE))
8329 TEST_DrawLevelField(x, y);
8333 if (MovDelay[x][y]) // wait some time before next movement
8337 if (element == EL_ROBOT ||
8338 element == EL_YAMYAM ||
8339 element == EL_DARK_YAMYAM)
8341 DrawLevelElementAnimationIfNeeded(x, y, element);
8342 PlayLevelSoundAction(x, y, ACTION_WAITING);
8344 else if (element == EL_SP_ELECTRON)
8345 DrawLevelElementAnimationIfNeeded(x, y, element);
8346 else if (element == EL_DRAGON)
8349 int dir = MovDir[x][y];
8350 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8351 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8352 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8353 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8354 dir == MV_UP ? IMG_FLAMES_1_UP :
8355 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8356 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8358 GfxAction[x][y] = ACTION_ATTACKING;
8360 if (IS_PLAYER(x, y))
8361 DrawPlayerField(x, y);
8363 TEST_DrawLevelField(x, y);
8365 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8367 for (i = 1; i <= 3; i++)
8369 int xx = x + i * dx;
8370 int yy = y + i * dy;
8371 int sx = SCREENX(xx);
8372 int sy = SCREENY(yy);
8373 int flame_graphic = graphic + (i - 1);
8375 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8380 int flamed = MovingOrBlocked2Element(xx, yy);
8382 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8385 RemoveMovingField(xx, yy);
8387 ChangeDelay[xx][yy] = 0;
8389 Tile[xx][yy] = EL_FLAMES;
8391 if (IN_SCR_FIELD(sx, sy))
8393 TEST_DrawLevelFieldCrumbled(xx, yy);
8394 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8399 if (Tile[xx][yy] == EL_FLAMES)
8400 Tile[xx][yy] = EL_EMPTY;
8401 TEST_DrawLevelField(xx, yy);
8406 if (MovDelay[x][y]) // element still has to wait some time
8408 PlayLevelSoundAction(x, y, ACTION_WAITING);
8414 // now make next step
8416 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8418 if (DONT_COLLIDE_WITH(element) &&
8419 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8420 !PLAYER_ENEMY_PROTECTED(newx, newy))
8422 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8427 else if (CAN_MOVE_INTO_ACID(element) &&
8428 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8429 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8430 (MovDir[x][y] == MV_DOWN ||
8431 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8433 SplashAcid(newx, newy);
8434 Store[x][y] = EL_ACID;
8436 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8438 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8439 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8440 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8441 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8444 TEST_DrawLevelField(x, y);
8446 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8447 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8448 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8450 game.friends_still_needed--;
8451 if (!game.friends_still_needed &&
8453 game.all_players_gone)
8458 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8460 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8461 TEST_DrawLevelField(newx, newy);
8463 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8465 else if (!IS_FREE(newx, newy))
8467 GfxAction[x][y] = ACTION_WAITING;
8469 if (IS_PLAYER(x, y))
8470 DrawPlayerField(x, y);
8472 TEST_DrawLevelField(x, y);
8477 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8479 if (IS_FOOD_PIG(Tile[newx][newy]))
8481 if (IS_MOVING(newx, newy))
8482 RemoveMovingField(newx, newy);
8485 Tile[newx][newy] = EL_EMPTY;
8486 TEST_DrawLevelField(newx, newy);
8489 PlayLevelSound(x, y, SND_PIG_DIGGING);
8491 else if (!IS_FREE(newx, newy))
8493 if (IS_PLAYER(x, y))
8494 DrawPlayerField(x, y);
8496 TEST_DrawLevelField(x, y);
8501 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8503 if (Store[x][y] != EL_EMPTY)
8505 boolean can_clone = FALSE;
8508 // check if element to clone is still there
8509 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8511 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8519 // cannot clone or target field not free anymore -- do not clone
8520 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8521 Store[x][y] = EL_EMPTY;
8524 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8526 if (IS_MV_DIAGONAL(MovDir[x][y]))
8528 int diagonal_move_dir = MovDir[x][y];
8529 int stored = Store[x][y];
8530 int change_delay = 8;
8533 // android is moving diagonally
8535 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8537 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8538 GfxElement[x][y] = EL_EMC_ANDROID;
8539 GfxAction[x][y] = ACTION_SHRINKING;
8540 GfxDir[x][y] = diagonal_move_dir;
8541 ChangeDelay[x][y] = change_delay;
8543 if (Store[x][y] == EL_EMPTY)
8544 Store[x][y] = GfxElementEmpty[x][y];
8546 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8549 DrawLevelGraphicAnimation(x, y, graphic);
8550 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8552 if (Tile[newx][newy] == EL_ACID)
8554 SplashAcid(newx, newy);
8559 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8561 Store[newx][newy] = EL_EMC_ANDROID;
8562 GfxElement[newx][newy] = EL_EMC_ANDROID;
8563 GfxAction[newx][newy] = ACTION_GROWING;
8564 GfxDir[newx][newy] = diagonal_move_dir;
8565 ChangeDelay[newx][newy] = change_delay;
8567 graphic = el_act_dir2img(GfxElement[newx][newy],
8568 GfxAction[newx][newy], GfxDir[newx][newy]);
8570 DrawLevelGraphicAnimation(newx, newy, graphic);
8571 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8577 Tile[newx][newy] = EL_EMPTY;
8578 TEST_DrawLevelField(newx, newy);
8580 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8583 else if (!IS_FREE(newx, newy))
8588 else if (IS_CUSTOM_ELEMENT(element) &&
8589 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8591 if (!DigFieldByCE(newx, newy, element))
8594 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8596 RunnerVisit[x][y] = FrameCounter;
8597 PlayerVisit[x][y] /= 8; // expire player visit path
8600 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8602 if (!IS_FREE(newx, newy))
8604 if (IS_PLAYER(x, y))
8605 DrawPlayerField(x, y);
8607 TEST_DrawLevelField(x, y);
8613 boolean wanna_flame = !RND(10);
8614 int dx = newx - x, dy = newy - y;
8615 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8616 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8617 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8618 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8619 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8620 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8623 IS_CLASSIC_ENEMY(element1) ||
8624 IS_CLASSIC_ENEMY(element2)) &&
8625 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8626 element1 != EL_FLAMES && element2 != EL_FLAMES)
8628 ResetGfxAnimation(x, y);
8629 GfxAction[x][y] = ACTION_ATTACKING;
8631 if (IS_PLAYER(x, y))
8632 DrawPlayerField(x, y);
8634 TEST_DrawLevelField(x, y);
8636 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8638 MovDelay[x][y] = 50;
8640 Tile[newx][newy] = EL_FLAMES;
8641 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8642 Tile[newx1][newy1] = EL_FLAMES;
8643 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8644 Tile[newx2][newy2] = EL_FLAMES;
8650 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8651 Tile[newx][newy] == EL_DIAMOND)
8653 if (IS_MOVING(newx, newy))
8654 RemoveMovingField(newx, newy);
8657 Tile[newx][newy] = EL_EMPTY;
8658 TEST_DrawLevelField(newx, newy);
8661 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8663 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8664 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8666 if (AmoebaNr[newx][newy])
8668 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8669 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8670 Tile[newx][newy] == EL_BD_AMOEBA)
8671 AmoebaCnt[AmoebaNr[newx][newy]]--;
8674 if (IS_MOVING(newx, newy))
8676 RemoveMovingField(newx, newy);
8680 Tile[newx][newy] = EL_EMPTY;
8681 TEST_DrawLevelField(newx, newy);
8684 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8686 else if ((element == EL_PACMAN || element == EL_MOLE)
8687 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8689 if (AmoebaNr[newx][newy])
8691 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8692 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8693 Tile[newx][newy] == EL_BD_AMOEBA)
8694 AmoebaCnt[AmoebaNr[newx][newy]]--;
8697 if (element == EL_MOLE)
8699 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8700 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8702 ResetGfxAnimation(x, y);
8703 GfxAction[x][y] = ACTION_DIGGING;
8704 TEST_DrawLevelField(x, y);
8706 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8708 return; // wait for shrinking amoeba
8710 else // element == EL_PACMAN
8712 Tile[newx][newy] = EL_EMPTY;
8713 TEST_DrawLevelField(newx, newy);
8714 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8717 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8718 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8719 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8721 // wait for shrinking amoeba to completely disappear
8724 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8726 // object was running against a wall
8730 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8731 DrawLevelElementAnimation(x, y, element);
8733 if (DONT_TOUCH(element))
8734 TestIfBadThingTouchesPlayer(x, y);
8739 InitMovingField(x, y, MovDir[x][y]);
8741 PlayLevelSoundAction(x, y, ACTION_MOVING);
8745 ContinueMoving(x, y);
8748 void ContinueMoving(int x, int y)
8750 int element = Tile[x][y];
8751 struct ElementInfo *ei = &element_info[element];
8752 int direction = MovDir[x][y];
8753 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8754 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8755 int newx = x + dx, newy = y + dy;
8756 int stored = Store[x][y];
8757 int stored_new = Store[newx][newy];
8758 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8759 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8760 boolean last_line = (newy == lev_fieldy - 1);
8761 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8763 if (pushed_by_player) // special case: moving object pushed by player
8765 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8767 else if (use_step_delay) // special case: moving object has step delay
8769 if (!MovDelay[x][y])
8770 MovPos[x][y] += getElementMoveStepsize(x, y);
8775 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8779 TEST_DrawLevelField(x, y);
8781 return; // element is still waiting
8784 else // normal case: generically moving object
8786 MovPos[x][y] += getElementMoveStepsize(x, y);
8789 if (ABS(MovPos[x][y]) < TILEX)
8791 TEST_DrawLevelField(x, y);
8793 return; // element is still moving
8796 // element reached destination field
8798 Tile[x][y] = EL_EMPTY;
8799 Tile[newx][newy] = element;
8800 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8802 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8804 element = Tile[newx][newy] = EL_ACID;
8806 else if (element == EL_MOLE)
8808 Tile[x][y] = EL_SAND;
8810 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8812 else if (element == EL_QUICKSAND_FILLING)
8814 element = Tile[newx][newy] = get_next_element(element);
8815 Store[newx][newy] = Store[x][y];
8817 else if (element == EL_QUICKSAND_EMPTYING)
8819 Tile[x][y] = get_next_element(element);
8820 element = Tile[newx][newy] = Store[x][y];
8822 else if (element == EL_QUICKSAND_FAST_FILLING)
8824 element = Tile[newx][newy] = get_next_element(element);
8825 Store[newx][newy] = Store[x][y];
8827 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8829 Tile[x][y] = get_next_element(element);
8830 element = Tile[newx][newy] = Store[x][y];
8832 else if (element == EL_MAGIC_WALL_FILLING)
8834 element = Tile[newx][newy] = get_next_element(element);
8835 if (!game.magic_wall_active)
8836 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8837 Store[newx][newy] = Store[x][y];
8839 else if (element == EL_MAGIC_WALL_EMPTYING)
8841 Tile[x][y] = get_next_element(element);
8842 if (!game.magic_wall_active)
8843 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8844 element = Tile[newx][newy] = Store[x][y];
8846 InitField(newx, newy, FALSE);
8848 else if (element == EL_BD_MAGIC_WALL_FILLING)
8850 element = Tile[newx][newy] = get_next_element(element);
8851 if (!game.magic_wall_active)
8852 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8853 Store[newx][newy] = Store[x][y];
8855 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8857 Tile[x][y] = get_next_element(element);
8858 if (!game.magic_wall_active)
8859 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8860 element = Tile[newx][newy] = Store[x][y];
8862 InitField(newx, newy, FALSE);
8864 else if (element == EL_DC_MAGIC_WALL_FILLING)
8866 element = Tile[newx][newy] = get_next_element(element);
8867 if (!game.magic_wall_active)
8868 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8869 Store[newx][newy] = Store[x][y];
8871 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8873 Tile[x][y] = get_next_element(element);
8874 if (!game.magic_wall_active)
8875 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8876 element = Tile[newx][newy] = Store[x][y];
8878 InitField(newx, newy, FALSE);
8880 else if (element == EL_AMOEBA_DROPPING)
8882 Tile[x][y] = get_next_element(element);
8883 element = Tile[newx][newy] = Store[x][y];
8885 else if (element == EL_SOKOBAN_OBJECT)
8888 Tile[x][y] = Back[x][y];
8890 if (Back[newx][newy])
8891 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8893 Back[x][y] = Back[newx][newy] = 0;
8896 Store[x][y] = EL_EMPTY;
8901 MovDelay[newx][newy] = 0;
8903 if (CAN_CHANGE_OR_HAS_ACTION(element))
8905 // copy element change control values to new field
8906 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8907 ChangePage[newx][newy] = ChangePage[x][y];
8908 ChangeCount[newx][newy] = ChangeCount[x][y];
8909 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8912 CustomValue[newx][newy] = CustomValue[x][y];
8914 ChangeDelay[x][y] = 0;
8915 ChangePage[x][y] = -1;
8916 ChangeCount[x][y] = 0;
8917 ChangeEvent[x][y] = -1;
8919 CustomValue[x][y] = 0;
8921 // copy animation control values to new field
8922 GfxFrame[newx][newy] = GfxFrame[x][y];
8923 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8924 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8925 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8927 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8929 // some elements can leave other elements behind after moving
8930 if (ei->move_leave_element != EL_EMPTY &&
8931 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8932 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8934 int move_leave_element = ei->move_leave_element;
8936 // this makes it possible to leave the removed element again
8937 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8938 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8940 Tile[x][y] = move_leave_element;
8942 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8943 MovDir[x][y] = direction;
8945 InitField(x, y, FALSE);
8947 if (GFX_CRUMBLED(Tile[x][y]))
8948 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8950 if (IS_PLAYER_ELEMENT(move_leave_element))
8951 RelocatePlayer(x, y, move_leave_element);
8954 // do this after checking for left-behind element
8955 ResetGfxAnimation(x, y); // reset animation values for old field
8957 if (!CAN_MOVE(element) ||
8958 (CAN_FALL(element) && direction == MV_DOWN &&
8959 (element == EL_SPRING ||
8960 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8961 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8962 GfxDir[x][y] = MovDir[newx][newy] = 0;
8964 TEST_DrawLevelField(x, y);
8965 TEST_DrawLevelField(newx, newy);
8967 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8969 // prevent pushed element from moving on in pushed direction
8970 if (pushed_by_player && CAN_MOVE(element) &&
8971 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8972 !(element_info[element].move_pattern & direction))
8973 TurnRound(newx, newy);
8975 // prevent elements on conveyor belt from moving on in last direction
8976 if (pushed_by_conveyor && CAN_FALL(element) &&
8977 direction & MV_HORIZONTAL)
8978 MovDir[newx][newy] = 0;
8980 if (!pushed_by_player)
8982 int nextx = newx + dx, nexty = newy + dy;
8983 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8985 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8987 if (CAN_FALL(element) && direction == MV_DOWN)
8988 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8990 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8991 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8993 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8994 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8997 if (DONT_TOUCH(element)) // object may be nasty to player or others
8999 TestIfBadThingTouchesPlayer(newx, newy);
9000 TestIfBadThingTouchesFriend(newx, newy);
9002 if (!IS_CUSTOM_ELEMENT(element))
9003 TestIfBadThingTouchesOtherBadThing(newx, newy);
9005 else if (element == EL_PENGUIN)
9006 TestIfFriendTouchesBadThing(newx, newy);
9008 if (DONT_GET_HIT_BY(element))
9010 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9013 // give the player one last chance (one more frame) to move away
9014 if (CAN_FALL(element) && direction == MV_DOWN &&
9015 (last_line || (!IS_FREE(x, newy + 1) &&
9016 (!IS_PLAYER(x, newy + 1) ||
9017 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9020 if (pushed_by_player && !game.use_change_when_pushing_bug)
9022 int push_side = MV_DIR_OPPOSITE(direction);
9023 struct PlayerInfo *player = PLAYERINFO(x, y);
9025 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9026 player->index_bit, push_side);
9027 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9028 player->index_bit, push_side);
9031 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9032 MovDelay[newx][newy] = 1;
9034 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9036 TestIfElementTouchesCustomElement(x, y); // empty or new element
9037 TestIfElementHitsCustomElement(newx, newy, direction);
9038 TestIfPlayerTouchesCustomElement(newx, newy);
9039 TestIfElementTouchesCustomElement(newx, newy);
9041 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9042 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9043 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9044 MV_DIR_OPPOSITE(direction));
9047 int AmoebaNeighbourNr(int ax, int ay)
9050 int element = Tile[ax][ay];
9052 struct XY *xy = xy_topdown;
9054 for (i = 0; i < NUM_DIRECTIONS; i++)
9056 int x = ax + xy[i].x;
9057 int y = ay + xy[i].y;
9059 if (!IN_LEV_FIELD(x, y))
9062 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9063 group_nr = AmoebaNr[x][y];
9069 static void AmoebaMerge(int ax, int ay)
9071 int i, x, y, xx, yy;
9072 int new_group_nr = AmoebaNr[ax][ay];
9073 struct XY *xy = xy_topdown;
9075 if (new_group_nr == 0)
9078 for (i = 0; i < NUM_DIRECTIONS; i++)
9083 if (!IN_LEV_FIELD(x, y))
9086 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9087 Tile[x][y] == EL_BD_AMOEBA ||
9088 Tile[x][y] == EL_AMOEBA_DEAD) &&
9089 AmoebaNr[x][y] != new_group_nr)
9091 int old_group_nr = AmoebaNr[x][y];
9093 if (old_group_nr == 0)
9096 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9097 AmoebaCnt[old_group_nr] = 0;
9098 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9099 AmoebaCnt2[old_group_nr] = 0;
9101 SCAN_PLAYFIELD(xx, yy)
9103 if (AmoebaNr[xx][yy] == old_group_nr)
9104 AmoebaNr[xx][yy] = new_group_nr;
9110 void AmoebaToDiamond(int ax, int ay)
9114 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9116 int group_nr = AmoebaNr[ax][ay];
9121 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9122 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9128 SCAN_PLAYFIELD(x, y)
9130 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9133 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9137 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9138 SND_AMOEBA_TURNING_TO_GEM :
9139 SND_AMOEBA_TURNING_TO_ROCK));
9144 struct XY *xy = xy_topdown;
9146 for (i = 0; i < NUM_DIRECTIONS; i++)
9151 if (!IN_LEV_FIELD(x, y))
9154 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9156 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9157 SND_AMOEBA_TURNING_TO_GEM :
9158 SND_AMOEBA_TURNING_TO_ROCK));
9165 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9168 int group_nr = AmoebaNr[ax][ay];
9169 boolean done = FALSE;
9174 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9175 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9181 SCAN_PLAYFIELD(x, y)
9183 if (AmoebaNr[x][y] == group_nr &&
9184 (Tile[x][y] == EL_AMOEBA_DEAD ||
9185 Tile[x][y] == EL_BD_AMOEBA ||
9186 Tile[x][y] == EL_AMOEBA_GROWING))
9189 Tile[x][y] = new_element;
9190 InitField(x, y, FALSE);
9191 TEST_DrawLevelField(x, y);
9197 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9198 SND_BD_AMOEBA_TURNING_TO_ROCK :
9199 SND_BD_AMOEBA_TURNING_TO_GEM));
9202 static void AmoebaGrowing(int x, int y)
9204 static DelayCounter sound_delay = { 0 };
9206 if (!MovDelay[x][y]) // start new growing cycle
9210 if (DelayReached(&sound_delay))
9212 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9213 sound_delay.value = 30;
9217 if (MovDelay[x][y]) // wait some time before growing bigger
9220 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9222 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9223 6 - MovDelay[x][y]);
9225 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9228 if (!MovDelay[x][y])
9230 Tile[x][y] = Store[x][y];
9232 TEST_DrawLevelField(x, y);
9237 static void AmoebaShrinking(int x, int y)
9239 static DelayCounter sound_delay = { 0 };
9241 if (!MovDelay[x][y]) // start new shrinking cycle
9245 if (DelayReached(&sound_delay))
9246 sound_delay.value = 30;
9249 if (MovDelay[x][y]) // wait some time before shrinking
9252 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9254 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9255 6 - MovDelay[x][y]);
9257 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9260 if (!MovDelay[x][y])
9262 Tile[x][y] = EL_EMPTY;
9263 TEST_DrawLevelField(x, y);
9265 // don't let mole enter this field in this cycle;
9266 // (give priority to objects falling to this field from above)
9272 static void AmoebaReproduce(int ax, int ay)
9275 int element = Tile[ax][ay];
9276 int graphic = el2img(element);
9277 int newax = ax, neway = ay;
9278 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9279 struct XY *xy = xy_topdown;
9281 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9283 Tile[ax][ay] = EL_AMOEBA_DEAD;
9284 TEST_DrawLevelField(ax, ay);
9288 if (IS_ANIMATED(graphic))
9289 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9291 if (!MovDelay[ax][ay]) // start making new amoeba field
9292 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9294 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9297 if (MovDelay[ax][ay])
9301 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9304 int x = ax + xy[start].x;
9305 int y = ay + xy[start].y;
9307 if (!IN_LEV_FIELD(x, y))
9310 if (IS_FREE(x, y) ||
9311 CAN_GROW_INTO(Tile[x][y]) ||
9312 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9313 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9319 if (newax == ax && neway == ay)
9322 else // normal or "filled" (BD style) amoeba
9325 boolean waiting_for_player = FALSE;
9327 for (i = 0; i < NUM_DIRECTIONS; i++)
9329 int j = (start + i) % 4;
9330 int x = ax + xy[j].x;
9331 int y = ay + xy[j].y;
9333 if (!IN_LEV_FIELD(x, y))
9336 if (IS_FREE(x, y) ||
9337 CAN_GROW_INTO(Tile[x][y]) ||
9338 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9339 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9345 else if (IS_PLAYER(x, y))
9346 waiting_for_player = TRUE;
9349 if (newax == ax && neway == ay) // amoeba cannot grow
9351 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9353 Tile[ax][ay] = EL_AMOEBA_DEAD;
9354 TEST_DrawLevelField(ax, ay);
9355 AmoebaCnt[AmoebaNr[ax][ay]]--;
9357 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9359 if (element == EL_AMOEBA_FULL)
9360 AmoebaToDiamond(ax, ay);
9361 else if (element == EL_BD_AMOEBA)
9362 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9367 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9369 // amoeba gets larger by growing in some direction
9371 int new_group_nr = AmoebaNr[ax][ay];
9374 if (new_group_nr == 0)
9376 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9378 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9384 AmoebaNr[newax][neway] = new_group_nr;
9385 AmoebaCnt[new_group_nr]++;
9386 AmoebaCnt2[new_group_nr]++;
9388 // if amoeba touches other amoeba(s) after growing, unify them
9389 AmoebaMerge(newax, neway);
9391 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9393 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9399 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9400 (neway == lev_fieldy - 1 && newax != ax))
9402 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9403 Store[newax][neway] = element;
9405 else if (neway == ay || element == EL_EMC_DRIPPER)
9407 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9409 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9413 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9414 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9415 Store[ax][ay] = EL_AMOEBA_DROP;
9416 ContinueMoving(ax, ay);
9420 TEST_DrawLevelField(newax, neway);
9423 static void Life(int ax, int ay)
9427 int element = Tile[ax][ay];
9428 int graphic = el2img(element);
9429 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9431 boolean changed = FALSE;
9433 if (IS_ANIMATED(graphic))
9434 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9439 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9440 MovDelay[ax][ay] = life_time;
9442 if (MovDelay[ax][ay]) // wait some time before next cycle
9445 if (MovDelay[ax][ay])
9449 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9451 int xx = ax + x1, yy = ay + y1;
9452 int old_element = Tile[xx][yy];
9453 int num_neighbours = 0;
9455 if (!IN_LEV_FIELD(xx, yy))
9458 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9460 int x = xx + x2, y = yy + y2;
9462 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9465 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9466 boolean is_neighbour = FALSE;
9468 if (level.use_life_bugs)
9470 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9471 (IS_FREE(x, y) && Stop[x][y]));
9474 (Last[x][y] == element || is_player_cell);
9480 boolean is_free = FALSE;
9482 if (level.use_life_bugs)
9483 is_free = (IS_FREE(xx, yy));
9485 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9487 if (xx == ax && yy == ay) // field in the middle
9489 if (num_neighbours < life_parameter[0] ||
9490 num_neighbours > life_parameter[1])
9492 Tile[xx][yy] = EL_EMPTY;
9493 if (Tile[xx][yy] != old_element)
9494 TEST_DrawLevelField(xx, yy);
9495 Stop[xx][yy] = TRUE;
9499 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9500 { // free border field
9501 if (num_neighbours >= life_parameter[2] &&
9502 num_neighbours <= life_parameter[3])
9504 Tile[xx][yy] = element;
9505 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9506 if (Tile[xx][yy] != old_element)
9507 TEST_DrawLevelField(xx, yy);
9508 Stop[xx][yy] = TRUE;
9515 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9516 SND_GAME_OF_LIFE_GROWING);
9519 static void InitRobotWheel(int x, int y)
9521 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9524 static void RunRobotWheel(int x, int y)
9526 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9529 static void StopRobotWheel(int x, int y)
9531 if (game.robot_wheel_x == x &&
9532 game.robot_wheel_y == y)
9534 game.robot_wheel_x = -1;
9535 game.robot_wheel_y = -1;
9536 game.robot_wheel_active = FALSE;
9540 static void InitTimegateWheel(int x, int y)
9542 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9545 static void RunTimegateWheel(int x, int y)
9547 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9550 static void InitMagicBallDelay(int x, int y)
9552 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9555 static void ActivateMagicBall(int bx, int by)
9559 if (level.ball_random)
9561 int pos_border = RND(8); // select one of the eight border elements
9562 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9563 int xx = pos_content % 3;
9564 int yy = pos_content / 3;
9569 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9570 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9574 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9576 int xx = x - bx + 1;
9577 int yy = y - by + 1;
9579 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9580 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9584 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9587 static void CheckExit(int x, int y)
9589 if (game.gems_still_needed > 0 ||
9590 game.sokoban_fields_still_needed > 0 ||
9591 game.sokoban_objects_still_needed > 0 ||
9592 game.lights_still_needed > 0)
9594 int element = Tile[x][y];
9595 int graphic = el2img(element);
9597 if (IS_ANIMATED(graphic))
9598 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9603 // do not re-open exit door closed after last player
9604 if (game.all_players_gone)
9607 Tile[x][y] = EL_EXIT_OPENING;
9609 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9612 static void CheckExitEM(int x, int y)
9614 if (game.gems_still_needed > 0 ||
9615 game.sokoban_fields_still_needed > 0 ||
9616 game.sokoban_objects_still_needed > 0 ||
9617 game.lights_still_needed > 0)
9619 int element = Tile[x][y];
9620 int graphic = el2img(element);
9622 if (IS_ANIMATED(graphic))
9623 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9628 // do not re-open exit door closed after last player
9629 if (game.all_players_gone)
9632 Tile[x][y] = EL_EM_EXIT_OPENING;
9634 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9637 static void CheckExitSteel(int x, int y)
9639 if (game.gems_still_needed > 0 ||
9640 game.sokoban_fields_still_needed > 0 ||
9641 game.sokoban_objects_still_needed > 0 ||
9642 game.lights_still_needed > 0)
9644 int element = Tile[x][y];
9645 int graphic = el2img(element);
9647 if (IS_ANIMATED(graphic))
9648 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9653 // do not re-open exit door closed after last player
9654 if (game.all_players_gone)
9657 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9659 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9662 static void CheckExitSteelEM(int x, int y)
9664 if (game.gems_still_needed > 0 ||
9665 game.sokoban_fields_still_needed > 0 ||
9666 game.sokoban_objects_still_needed > 0 ||
9667 game.lights_still_needed > 0)
9669 int element = Tile[x][y];
9670 int graphic = el2img(element);
9672 if (IS_ANIMATED(graphic))
9673 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9678 // do not re-open exit door closed after last player
9679 if (game.all_players_gone)
9682 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9684 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9687 static void CheckExitSP(int x, int y)
9689 if (game.gems_still_needed > 0)
9691 int element = Tile[x][y];
9692 int graphic = el2img(element);
9694 if (IS_ANIMATED(graphic))
9695 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9700 // do not re-open exit door closed after last player
9701 if (game.all_players_gone)
9704 Tile[x][y] = EL_SP_EXIT_OPENING;
9706 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9709 static void CloseAllOpenTimegates(void)
9713 SCAN_PLAYFIELD(x, y)
9715 int element = Tile[x][y];
9717 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9719 Tile[x][y] = EL_TIMEGATE_CLOSING;
9721 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9726 static void DrawTwinkleOnField(int x, int y)
9728 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9731 if (Tile[x][y] == EL_BD_DIAMOND)
9734 if (MovDelay[x][y] == 0) // next animation frame
9735 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9737 if (MovDelay[x][y] != 0) // wait some time before next frame
9741 DrawLevelElementAnimation(x, y, Tile[x][y]);
9743 if (MovDelay[x][y] != 0)
9745 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9746 10 - MovDelay[x][y]);
9748 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9753 static void WallGrowing(int x, int y)
9757 if (!MovDelay[x][y]) // next animation frame
9758 MovDelay[x][y] = 3 * delay;
9760 if (MovDelay[x][y]) // wait some time before next frame
9764 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9766 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9767 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9769 DrawLevelGraphic(x, y, graphic, frame);
9772 if (!MovDelay[x][y])
9774 if (MovDir[x][y] == MV_LEFT)
9776 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9777 TEST_DrawLevelField(x - 1, y);
9779 else if (MovDir[x][y] == MV_RIGHT)
9781 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9782 TEST_DrawLevelField(x + 1, y);
9784 else if (MovDir[x][y] == MV_UP)
9786 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9787 TEST_DrawLevelField(x, y - 1);
9791 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9792 TEST_DrawLevelField(x, y + 1);
9795 Tile[x][y] = Store[x][y];
9797 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9798 TEST_DrawLevelField(x, y);
9803 static void CheckWallGrowing(int ax, int ay)
9805 int element = Tile[ax][ay];
9806 int graphic = el2img(element);
9807 boolean free_top = FALSE;
9808 boolean free_bottom = FALSE;
9809 boolean free_left = FALSE;
9810 boolean free_right = FALSE;
9811 boolean stop_top = FALSE;
9812 boolean stop_bottom = FALSE;
9813 boolean stop_left = FALSE;
9814 boolean stop_right = FALSE;
9815 boolean new_wall = FALSE;
9817 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9818 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9819 element == EL_EXPANDABLE_STEELWALL_ANY);
9821 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9822 element == EL_EXPANDABLE_WALL_ANY ||
9823 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9824 element == EL_EXPANDABLE_STEELWALL_ANY);
9826 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9827 element == EL_EXPANDABLE_WALL_ANY ||
9828 element == EL_EXPANDABLE_WALL ||
9829 element == EL_BD_EXPANDABLE_WALL ||
9830 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9831 element == EL_EXPANDABLE_STEELWALL_ANY);
9833 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9834 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9836 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9837 element == EL_EXPANDABLE_WALL ||
9838 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9840 int wall_growing = (is_steelwall ?
9841 EL_EXPANDABLE_STEELWALL_GROWING :
9842 EL_EXPANDABLE_WALL_GROWING);
9844 int gfx_wall_growing_up = (is_steelwall ?
9845 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9846 IMG_EXPANDABLE_WALL_GROWING_UP);
9847 int gfx_wall_growing_down = (is_steelwall ?
9848 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9849 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9850 int gfx_wall_growing_left = (is_steelwall ?
9851 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9852 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9853 int gfx_wall_growing_right = (is_steelwall ?
9854 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9855 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9857 if (IS_ANIMATED(graphic))
9858 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9860 if (!MovDelay[ax][ay]) // start building new wall
9861 MovDelay[ax][ay] = 6;
9863 if (MovDelay[ax][ay]) // wait some time before building new wall
9866 if (MovDelay[ax][ay])
9870 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9872 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9874 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9876 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9883 Tile[ax][ay - 1] = wall_growing;
9884 Store[ax][ay - 1] = element;
9885 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9887 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9888 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9895 Tile[ax][ay + 1] = wall_growing;
9896 Store[ax][ay + 1] = element;
9897 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9899 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9900 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
9906 if (grow_horizontal)
9910 Tile[ax - 1][ay] = wall_growing;
9911 Store[ax - 1][ay] = element;
9912 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9914 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9915 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
9922 Tile[ax + 1][ay] = wall_growing;
9923 Store[ax + 1][ay] = element;
9924 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9926 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9927 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
9933 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
9934 TEST_DrawLevelField(ax, ay);
9936 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9938 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9940 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9942 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9945 if (((stop_top && stop_bottom) || stop_horizontal) &&
9946 ((stop_left && stop_right) || stop_vertical))
9947 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
9950 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9953 static void CheckForDragon(int x, int y)
9956 boolean dragon_found = FALSE;
9957 struct XY *xy = xy_topdown;
9959 for (i = 0; i < NUM_DIRECTIONS; i++)
9961 for (j = 0; j < 4; j++)
9963 int xx = x + j * xy[i].x;
9964 int yy = y + j * xy[i].y;
9966 if (IN_LEV_FIELD(xx, yy) &&
9967 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9969 if (Tile[xx][yy] == EL_DRAGON)
9970 dragon_found = TRUE;
9979 for (i = 0; i < NUM_DIRECTIONS; i++)
9981 for (j = 0; j < 3; j++)
9983 int xx = x + j * xy[i].x;
9984 int yy = y + j * xy[i].y;
9986 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9988 Tile[xx][yy] = EL_EMPTY;
9989 TEST_DrawLevelField(xx, yy);
9998 static void InitBuggyBase(int x, int y)
10000 int element = Tile[x][y];
10001 int activating_delay = FRAMES_PER_SECOND / 4;
10003 ChangeDelay[x][y] =
10004 (element == EL_SP_BUGGY_BASE ?
10005 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10006 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10008 element == EL_SP_BUGGY_BASE_ACTIVE ?
10009 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10012 static void WarnBuggyBase(int x, int y)
10015 struct XY *xy = xy_topdown;
10017 for (i = 0; i < NUM_DIRECTIONS; i++)
10019 int xx = x + xy[i].x;
10020 int yy = y + xy[i].y;
10022 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10024 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10031 static void InitTrap(int x, int y)
10033 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10036 static void ActivateTrap(int x, int y)
10038 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10041 static void ChangeActiveTrap(int x, int y)
10043 int graphic = IMG_TRAP_ACTIVE;
10045 // if new animation frame was drawn, correct crumbled sand border
10046 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10047 TEST_DrawLevelFieldCrumbled(x, y);
10050 static int getSpecialActionElement(int element, int number, int base_element)
10052 return (element != EL_EMPTY ? element :
10053 number != -1 ? base_element + number - 1 :
10057 static int getModifiedActionNumber(int value_old, int operator, int operand,
10058 int value_min, int value_max)
10060 int value_new = (operator == CA_MODE_SET ? operand :
10061 operator == CA_MODE_ADD ? value_old + operand :
10062 operator == CA_MODE_SUBTRACT ? value_old - operand :
10063 operator == CA_MODE_MULTIPLY ? value_old * operand :
10064 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10065 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10068 return (value_new < value_min ? value_min :
10069 value_new > value_max ? value_max :
10073 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10075 struct ElementInfo *ei = &element_info[element];
10076 struct ElementChangeInfo *change = &ei->change_page[page];
10077 int target_element = change->target_element;
10078 int action_type = change->action_type;
10079 int action_mode = change->action_mode;
10080 int action_arg = change->action_arg;
10081 int action_element = change->action_element;
10084 if (!change->has_action)
10087 // ---------- determine action paramater values -----------------------------
10089 int level_time_value =
10090 (level.time > 0 ? TimeLeft :
10093 int action_arg_element_raw =
10094 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10095 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10096 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10097 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10098 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10099 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10100 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10102 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10104 int action_arg_direction =
10105 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10106 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10107 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10108 change->actual_trigger_side :
10109 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10110 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10113 int action_arg_number_min =
10114 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10117 int action_arg_number_max =
10118 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10119 action_type == CA_SET_LEVEL_GEMS ? 999 :
10120 action_type == CA_SET_LEVEL_TIME ? 9999 :
10121 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10122 action_type == CA_SET_CE_VALUE ? 9999 :
10123 action_type == CA_SET_CE_SCORE ? 9999 :
10126 int action_arg_number_reset =
10127 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10128 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10129 action_type == CA_SET_LEVEL_TIME ? level.time :
10130 action_type == CA_SET_LEVEL_SCORE ? 0 :
10131 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10132 action_type == CA_SET_CE_SCORE ? 0 :
10135 int action_arg_number =
10136 (action_arg <= CA_ARG_MAX ? action_arg :
10137 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10138 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10139 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10140 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10141 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10142 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10143 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10144 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10145 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10146 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10147 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10148 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10149 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10150 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10151 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10152 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10153 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10154 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10155 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10156 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10157 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10160 int action_arg_number_old =
10161 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10162 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10163 action_type == CA_SET_LEVEL_SCORE ? game.score :
10164 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10165 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10168 int action_arg_number_new =
10169 getModifiedActionNumber(action_arg_number_old,
10170 action_mode, action_arg_number,
10171 action_arg_number_min, action_arg_number_max);
10173 int trigger_player_bits =
10174 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10175 change->actual_trigger_player_bits : change->trigger_player);
10177 int action_arg_player_bits =
10178 (action_arg >= CA_ARG_PLAYER_1 &&
10179 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10180 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10181 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10184 // ---------- execute action -----------------------------------------------
10186 switch (action_type)
10193 // ---------- level actions ----------------------------------------------
10195 case CA_RESTART_LEVEL:
10197 game.restart_level = TRUE;
10202 case CA_SHOW_ENVELOPE:
10204 int element = getSpecialActionElement(action_arg_element,
10205 action_arg_number, EL_ENVELOPE_1);
10207 if (IS_ENVELOPE(element))
10208 local_player->show_envelope = element;
10213 case CA_SET_LEVEL_TIME:
10215 if (level.time > 0) // only modify limited time value
10217 TimeLeft = action_arg_number_new;
10219 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10221 DisplayGameControlValues();
10223 if (!TimeLeft && game.time_limit)
10224 for (i = 0; i < MAX_PLAYERS; i++)
10225 KillPlayer(&stored_player[i]);
10231 case CA_SET_LEVEL_SCORE:
10233 game.score = action_arg_number_new;
10235 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10237 DisplayGameControlValues();
10242 case CA_SET_LEVEL_GEMS:
10244 game.gems_still_needed = action_arg_number_new;
10246 game.snapshot.collected_item = TRUE;
10248 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10250 DisplayGameControlValues();
10255 case CA_SET_LEVEL_WIND:
10257 game.wind_direction = action_arg_direction;
10262 case CA_SET_LEVEL_RANDOM_SEED:
10264 // ensure that setting a new random seed while playing is predictable
10265 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10270 // ---------- player actions ---------------------------------------------
10272 case CA_MOVE_PLAYER:
10273 case CA_MOVE_PLAYER_NEW:
10275 // automatically move to the next field in specified direction
10276 for (i = 0; i < MAX_PLAYERS; i++)
10277 if (trigger_player_bits & (1 << i))
10278 if (action_type == CA_MOVE_PLAYER ||
10279 stored_player[i].MovPos == 0)
10280 stored_player[i].programmed_action = action_arg_direction;
10285 case CA_EXIT_PLAYER:
10287 for (i = 0; i < MAX_PLAYERS; i++)
10288 if (action_arg_player_bits & (1 << i))
10289 ExitPlayer(&stored_player[i]);
10291 if (game.players_still_needed == 0)
10297 case CA_KILL_PLAYER:
10299 for (i = 0; i < MAX_PLAYERS; i++)
10300 if (action_arg_player_bits & (1 << i))
10301 KillPlayer(&stored_player[i]);
10306 case CA_SET_PLAYER_KEYS:
10308 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10309 int element = getSpecialActionElement(action_arg_element,
10310 action_arg_number, EL_KEY_1);
10312 if (IS_KEY(element))
10314 for (i = 0; i < MAX_PLAYERS; i++)
10316 if (trigger_player_bits & (1 << i))
10318 stored_player[i].key[KEY_NR(element)] = key_state;
10320 DrawGameDoorValues();
10328 case CA_SET_PLAYER_SPEED:
10330 for (i = 0; i < MAX_PLAYERS; i++)
10332 if (trigger_player_bits & (1 << i))
10334 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10336 if (action_arg == CA_ARG_SPEED_FASTER &&
10337 stored_player[i].cannot_move)
10339 action_arg_number = STEPSIZE_VERY_SLOW;
10341 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10342 action_arg == CA_ARG_SPEED_FASTER)
10344 action_arg_number = 2;
10345 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10348 else if (action_arg == CA_ARG_NUMBER_RESET)
10350 action_arg_number = level.initial_player_stepsize[i];
10354 getModifiedActionNumber(move_stepsize,
10357 action_arg_number_min,
10358 action_arg_number_max);
10360 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10367 case CA_SET_PLAYER_SHIELD:
10369 for (i = 0; i < MAX_PLAYERS; i++)
10371 if (trigger_player_bits & (1 << i))
10373 if (action_arg == CA_ARG_SHIELD_OFF)
10375 stored_player[i].shield_normal_time_left = 0;
10376 stored_player[i].shield_deadly_time_left = 0;
10378 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10380 stored_player[i].shield_normal_time_left = 999999;
10382 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10384 stored_player[i].shield_normal_time_left = 999999;
10385 stored_player[i].shield_deadly_time_left = 999999;
10393 case CA_SET_PLAYER_GRAVITY:
10395 for (i = 0; i < MAX_PLAYERS; i++)
10397 if (trigger_player_bits & (1 << i))
10399 stored_player[i].gravity =
10400 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10401 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10402 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10403 stored_player[i].gravity);
10410 case CA_SET_PLAYER_ARTWORK:
10412 for (i = 0; i < MAX_PLAYERS; i++)
10414 if (trigger_player_bits & (1 << i))
10416 int artwork_element = action_arg_element;
10418 if (action_arg == CA_ARG_ELEMENT_RESET)
10420 (level.use_artwork_element[i] ? level.artwork_element[i] :
10421 stored_player[i].element_nr);
10423 if (stored_player[i].artwork_element != artwork_element)
10424 stored_player[i].Frame = 0;
10426 stored_player[i].artwork_element = artwork_element;
10428 SetPlayerWaiting(&stored_player[i], FALSE);
10430 // set number of special actions for bored and sleeping animation
10431 stored_player[i].num_special_action_bored =
10432 get_num_special_action(artwork_element,
10433 ACTION_BORING_1, ACTION_BORING_LAST);
10434 stored_player[i].num_special_action_sleeping =
10435 get_num_special_action(artwork_element,
10436 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10443 case CA_SET_PLAYER_INVENTORY:
10445 for (i = 0; i < MAX_PLAYERS; i++)
10447 struct PlayerInfo *player = &stored_player[i];
10450 if (trigger_player_bits & (1 << i))
10452 int inventory_element = action_arg_element;
10454 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10455 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10456 action_arg == CA_ARG_ELEMENT_ACTION)
10458 int element = inventory_element;
10459 int collect_count = element_info[element].collect_count_initial;
10461 if (!IS_CUSTOM_ELEMENT(element))
10464 if (collect_count == 0)
10465 player->inventory_infinite_element = element;
10467 for (k = 0; k < collect_count; k++)
10468 if (player->inventory_size < MAX_INVENTORY_SIZE)
10469 player->inventory_element[player->inventory_size++] =
10472 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10473 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10474 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10476 if (player->inventory_infinite_element != EL_UNDEFINED &&
10477 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10478 action_arg_element_raw))
10479 player->inventory_infinite_element = EL_UNDEFINED;
10481 for (k = 0, j = 0; j < player->inventory_size; j++)
10483 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10484 action_arg_element_raw))
10485 player->inventory_element[k++] = player->inventory_element[j];
10488 player->inventory_size = k;
10490 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10492 if (player->inventory_size > 0)
10494 for (j = 0; j < player->inventory_size - 1; j++)
10495 player->inventory_element[j] = player->inventory_element[j + 1];
10497 player->inventory_size--;
10500 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10502 if (player->inventory_size > 0)
10503 player->inventory_size--;
10505 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10507 player->inventory_infinite_element = EL_UNDEFINED;
10508 player->inventory_size = 0;
10510 else if (action_arg == CA_ARG_INVENTORY_RESET)
10512 player->inventory_infinite_element = EL_UNDEFINED;
10513 player->inventory_size = 0;
10515 if (level.use_initial_inventory[i])
10517 for (j = 0; j < level.initial_inventory_size[i]; j++)
10519 int element = level.initial_inventory_content[i][j];
10520 int collect_count = element_info[element].collect_count_initial;
10522 if (!IS_CUSTOM_ELEMENT(element))
10525 if (collect_count == 0)
10526 player->inventory_infinite_element = element;
10528 for (k = 0; k < collect_count; k++)
10529 if (player->inventory_size < MAX_INVENTORY_SIZE)
10530 player->inventory_element[player->inventory_size++] =
10541 // ---------- CE actions -------------------------------------------------
10543 case CA_SET_CE_VALUE:
10545 int last_ce_value = CustomValue[x][y];
10547 CustomValue[x][y] = action_arg_number_new;
10549 if (CustomValue[x][y] != last_ce_value)
10551 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10552 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10554 if (CustomValue[x][y] == 0)
10556 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10557 ChangeCount[x][y] = 0; // allow at least one more change
10559 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10560 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10567 case CA_SET_CE_SCORE:
10569 int last_ce_score = ei->collect_score;
10571 ei->collect_score = action_arg_number_new;
10573 if (ei->collect_score != last_ce_score)
10575 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10576 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10578 if (ei->collect_score == 0)
10582 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10583 ChangeCount[x][y] = 0; // allow at least one more change
10585 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10586 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10589 This is a very special case that seems to be a mixture between
10590 CheckElementChange() and CheckTriggeredElementChange(): while
10591 the first one only affects single elements that are triggered
10592 directly, the second one affects multiple elements in the playfield
10593 that are triggered indirectly by another element. This is a third
10594 case: Changing the CE score always affects multiple identical CEs,
10595 so every affected CE must be checked, not only the single CE for
10596 which the CE score was changed in the first place (as every instance
10597 of that CE shares the same CE score, and therefore also can change)!
10599 SCAN_PLAYFIELD(xx, yy)
10601 if (Tile[xx][yy] == element)
10602 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10603 CE_SCORE_GETS_ZERO);
10611 case CA_SET_CE_ARTWORK:
10613 int artwork_element = action_arg_element;
10614 boolean reset_frame = FALSE;
10617 if (action_arg == CA_ARG_ELEMENT_RESET)
10618 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10621 if (ei->gfx_element != artwork_element)
10622 reset_frame = TRUE;
10624 ei->gfx_element = artwork_element;
10626 SCAN_PLAYFIELD(xx, yy)
10628 if (Tile[xx][yy] == element)
10632 ResetGfxAnimation(xx, yy);
10633 ResetRandomAnimationValue(xx, yy);
10636 TEST_DrawLevelField(xx, yy);
10643 // ---------- engine actions ---------------------------------------------
10645 case CA_SET_ENGINE_SCAN_MODE:
10647 InitPlayfieldScanMode(action_arg);
10657 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10659 int old_element = Tile[x][y];
10660 int new_element = GetElementFromGroupElement(element);
10661 int previous_move_direction = MovDir[x][y];
10662 int last_ce_value = CustomValue[x][y];
10663 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10664 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10665 boolean add_player_onto_element = (new_element_is_player &&
10666 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10667 IS_WALKABLE(old_element));
10669 if (!add_player_onto_element)
10671 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10672 RemoveMovingField(x, y);
10676 Tile[x][y] = new_element;
10678 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10679 MovDir[x][y] = previous_move_direction;
10681 if (element_info[new_element].use_last_ce_value)
10682 CustomValue[x][y] = last_ce_value;
10684 InitField_WithBug1(x, y, FALSE);
10686 new_element = Tile[x][y]; // element may have changed
10688 ResetGfxAnimation(x, y);
10689 ResetRandomAnimationValue(x, y);
10691 TEST_DrawLevelField(x, y);
10693 if (GFX_CRUMBLED(new_element))
10694 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10696 if (old_element == EL_EXPLOSION)
10698 Store[x][y] = Store2[x][y] = 0;
10700 // check if new element replaces an exploding player, requiring cleanup
10701 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10702 StorePlayer[x][y] = 0;
10705 // check if element under the player changes from accessible to unaccessible
10706 // (needed for special case of dropping element which then changes)
10707 // (must be checked after creating new element for walkable group elements)
10708 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10709 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10711 KillPlayer(PLAYERINFO(x, y));
10717 // "ChangeCount" not set yet to allow "entered by player" change one time
10718 if (new_element_is_player)
10719 RelocatePlayer(x, y, new_element);
10722 ChangeCount[x][y]++; // count number of changes in the same frame
10724 TestIfBadThingTouchesPlayer(x, y);
10725 TestIfPlayerTouchesCustomElement(x, y);
10726 TestIfElementTouchesCustomElement(x, y);
10729 static void CreateField(int x, int y, int element)
10731 CreateFieldExt(x, y, element, FALSE);
10734 static void CreateElementFromChange(int x, int y, int element)
10736 element = GET_VALID_RUNTIME_ELEMENT(element);
10738 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10740 int old_element = Tile[x][y];
10742 // prevent changed element from moving in same engine frame
10743 // unless both old and new element can either fall or move
10744 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10745 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10749 CreateFieldExt(x, y, element, TRUE);
10752 static boolean ChangeElement(int x, int y, int element, int page)
10754 struct ElementInfo *ei = &element_info[element];
10755 struct ElementChangeInfo *change = &ei->change_page[page];
10756 int ce_value = CustomValue[x][y];
10757 int ce_score = ei->collect_score;
10758 int target_element;
10759 int old_element = Tile[x][y];
10761 // always use default change event to prevent running into a loop
10762 if (ChangeEvent[x][y] == -1)
10763 ChangeEvent[x][y] = CE_DELAY;
10765 if (ChangeEvent[x][y] == CE_DELAY)
10767 // reset actual trigger element, trigger player and action element
10768 change->actual_trigger_element = EL_EMPTY;
10769 change->actual_trigger_player = EL_EMPTY;
10770 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10771 change->actual_trigger_side = CH_SIDE_NONE;
10772 change->actual_trigger_ce_value = 0;
10773 change->actual_trigger_ce_score = 0;
10774 change->actual_trigger_x = -1;
10775 change->actual_trigger_y = -1;
10778 // do not change elements more than a specified maximum number of changes
10779 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10782 ChangeCount[x][y]++; // count number of changes in the same frame
10784 if (ei->has_anim_event)
10785 HandleGlobalAnimEventByElementChange(element, page, x, y,
10786 change->actual_trigger_x,
10787 change->actual_trigger_y);
10789 if (change->explode)
10796 if (change->use_target_content)
10798 boolean complete_replace = TRUE;
10799 boolean can_replace[3][3];
10802 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10805 boolean is_walkable;
10806 boolean is_diggable;
10807 boolean is_collectible;
10808 boolean is_removable;
10809 boolean is_destructible;
10810 int ex = x + xx - 1;
10811 int ey = y + yy - 1;
10812 int content_element = change->target_content.e[xx][yy];
10815 can_replace[xx][yy] = TRUE;
10817 if (ex == x && ey == y) // do not check changing element itself
10820 if (content_element == EL_EMPTY_SPACE)
10822 can_replace[xx][yy] = FALSE; // do not replace border with space
10827 if (!IN_LEV_FIELD(ex, ey))
10829 can_replace[xx][yy] = FALSE;
10830 complete_replace = FALSE;
10837 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10838 e = MovingOrBlocked2Element(ex, ey);
10840 is_empty = (IS_FREE(ex, ey) ||
10841 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10843 is_walkable = (is_empty || IS_WALKABLE(e));
10844 is_diggable = (is_empty || IS_DIGGABLE(e));
10845 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10846 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10847 is_removable = (is_diggable || is_collectible);
10849 can_replace[xx][yy] =
10850 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10851 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10852 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10853 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10854 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10855 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10856 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10858 if (!can_replace[xx][yy])
10859 complete_replace = FALSE;
10862 if (!change->only_if_complete || complete_replace)
10864 boolean something_has_changed = FALSE;
10866 if (change->only_if_complete && change->use_random_replace &&
10867 RND(100) < change->random_percentage)
10870 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10872 int ex = x + xx - 1;
10873 int ey = y + yy - 1;
10874 int content_element;
10876 if (can_replace[xx][yy] && (!change->use_random_replace ||
10877 RND(100) < change->random_percentage))
10879 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10880 RemoveMovingField(ex, ey);
10882 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10884 content_element = change->target_content.e[xx][yy];
10885 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10886 ce_value, ce_score);
10888 CreateElementFromChange(ex, ey, target_element);
10890 something_has_changed = TRUE;
10892 // for symmetry reasons, freeze newly created border elements
10893 if (ex != x || ey != y)
10894 Stop[ex][ey] = TRUE; // no more moving in this frame
10898 if (something_has_changed)
10900 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10901 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10907 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10908 ce_value, ce_score);
10910 if (element == EL_DIAGONAL_GROWING ||
10911 element == EL_DIAGONAL_SHRINKING)
10913 target_element = Store[x][y];
10915 Store[x][y] = EL_EMPTY;
10918 // special case: element changes to player (and may be kept if walkable)
10919 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10920 CreateElementFromChange(x, y, EL_EMPTY);
10922 CreateElementFromChange(x, y, target_element);
10924 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10925 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10928 // this uses direct change before indirect change
10929 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10934 static void HandleElementChange(int x, int y, int page)
10936 int element = MovingOrBlocked2Element(x, y);
10937 struct ElementInfo *ei = &element_info[element];
10938 struct ElementChangeInfo *change = &ei->change_page[page];
10939 boolean handle_action_before_change = FALSE;
10942 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10943 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10945 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10946 x, y, element, element_info[element].token_name);
10947 Debug("game:playing:HandleElementChange", "This should never happen!");
10951 // this can happen with classic bombs on walkable, changing elements
10952 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10957 if (ChangeDelay[x][y] == 0) // initialize element change
10959 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10961 if (change->can_change)
10963 // !!! not clear why graphic animation should be reset at all here !!!
10964 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10965 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10968 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10970 When using an animation frame delay of 1 (this only happens with
10971 "sp_zonk.moving.left/right" in the classic graphics), the default
10972 (non-moving) animation shows wrong animation frames (while the
10973 moving animation, like "sp_zonk.moving.left/right", is correct,
10974 so this graphical bug never shows up with the classic graphics).
10975 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10976 be drawn instead of the correct frames 0,1,2,3. This is caused by
10977 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10978 an element change: First when the change delay ("ChangeDelay[][]")
10979 counter has reached zero after decrementing, then a second time in
10980 the next frame (after "GfxFrame[][]" was already incremented) when
10981 "ChangeDelay[][]" is reset to the initial delay value again.
10983 This causes frame 0 to be drawn twice, while the last frame won't
10984 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10986 As some animations may already be cleverly designed around this bug
10987 (at least the "Snake Bite" snake tail animation does this), it cannot
10988 simply be fixed here without breaking such existing animations.
10989 Unfortunately, it cannot easily be detected if a graphics set was
10990 designed "before" or "after" the bug was fixed. As a workaround,
10991 a new graphics set option "game.graphics_engine_version" was added
10992 to be able to specify the game's major release version for which the
10993 graphics set was designed, which can then be used to decide if the
10994 bugfix should be used (version 4 and above) or not (version 3 or
10995 below, or if no version was specified at all, as with old sets).
10997 (The wrong/fixed animation frames can be tested with the test level set
10998 "test_gfxframe" and level "000", which contains a specially prepared
10999 custom element at level position (x/y) == (11/9) which uses the zonk
11000 animation mentioned above. Using "game.graphics_engine_version: 4"
11001 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11002 This can also be seen from the debug output for this test element.)
11005 // when a custom element is about to change (for example by change delay),
11006 // do not reset graphic animation when the custom element is moving
11007 if (game.graphics_engine_version < 4 &&
11010 ResetGfxAnimation(x, y);
11011 ResetRandomAnimationValue(x, y);
11014 if (change->pre_change_function)
11015 change->pre_change_function(x, y);
11019 ChangeDelay[x][y]--;
11021 if (ChangeDelay[x][y] != 0) // continue element change
11023 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11025 // also needed if CE can not change, but has CE delay with CE action
11026 if (IS_ANIMATED(graphic))
11027 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11029 if (change->can_change)
11031 if (change->change_function)
11032 change->change_function(x, y);
11035 else // finish element change
11037 if (ChangePage[x][y] != -1) // remember page from delayed change
11039 page = ChangePage[x][y];
11040 ChangePage[x][y] = -1;
11042 change = &ei->change_page[page];
11045 if (IS_MOVING(x, y)) // never change a running system ;-)
11047 ChangeDelay[x][y] = 1; // try change after next move step
11048 ChangePage[x][y] = page; // remember page to use for change
11053 // special case: set new level random seed before changing element
11054 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11055 handle_action_before_change = TRUE;
11057 if (change->has_action && handle_action_before_change)
11058 ExecuteCustomElementAction(x, y, element, page);
11060 if (change->can_change)
11062 if (ChangeElement(x, y, element, page))
11064 if (change->post_change_function)
11065 change->post_change_function(x, y);
11069 if (change->has_action && !handle_action_before_change)
11070 ExecuteCustomElementAction(x, y, element, page);
11074 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11075 int trigger_element,
11077 int trigger_player,
11081 boolean change_done_any = FALSE;
11082 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11085 if (!(trigger_events[trigger_element][trigger_event]))
11088 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11090 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11092 int element = EL_CUSTOM_START + i;
11093 boolean change_done = FALSE;
11096 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11097 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11100 for (p = 0; p < element_info[element].num_change_pages; p++)
11102 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11104 if (change->can_change_or_has_action &&
11105 change->has_event[trigger_event] &&
11106 change->trigger_side & trigger_side &&
11107 change->trigger_player & trigger_player &&
11108 change->trigger_page & trigger_page_bits &&
11109 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11111 change->actual_trigger_element = trigger_element;
11112 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11113 change->actual_trigger_player_bits = trigger_player;
11114 change->actual_trigger_side = trigger_side;
11115 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11116 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11117 change->actual_trigger_x = trigger_x;
11118 change->actual_trigger_y = trigger_y;
11120 if ((change->can_change && !change_done) || change->has_action)
11124 SCAN_PLAYFIELD(x, y)
11126 if (Tile[x][y] == element)
11128 if (change->can_change && !change_done)
11130 // if element already changed in this frame, not only prevent
11131 // another element change (checked in ChangeElement()), but
11132 // also prevent additional element actions for this element
11134 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11135 !level.use_action_after_change_bug)
11138 ChangeDelay[x][y] = 1;
11139 ChangeEvent[x][y] = trigger_event;
11141 HandleElementChange(x, y, p);
11143 else if (change->has_action)
11145 // if element already changed in this frame, not only prevent
11146 // another element change (checked in ChangeElement()), but
11147 // also prevent additional element actions for this element
11149 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11150 !level.use_action_after_change_bug)
11153 ExecuteCustomElementAction(x, y, element, p);
11154 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11159 if (change->can_change)
11161 change_done = TRUE;
11162 change_done_any = TRUE;
11169 RECURSION_LOOP_DETECTION_END();
11171 return change_done_any;
11174 static boolean CheckElementChangeExt(int x, int y,
11176 int trigger_element,
11178 int trigger_player,
11181 boolean change_done = FALSE;
11184 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11185 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11188 if (Tile[x][y] == EL_BLOCKED)
11190 Blocked2Moving(x, y, &x, &y);
11191 element = Tile[x][y];
11194 // check if element has already changed or is about to change after moving
11195 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11196 Tile[x][y] != element) ||
11198 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11199 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11200 ChangePage[x][y] != -1)))
11203 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11205 for (p = 0; p < element_info[element].num_change_pages; p++)
11207 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11209 /* check trigger element for all events where the element that is checked
11210 for changing interacts with a directly adjacent element -- this is
11211 different to element changes that affect other elements to change on the
11212 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11213 boolean check_trigger_element =
11214 (trigger_event == CE_NEXT_TO_X ||
11215 trigger_event == CE_TOUCHING_X ||
11216 trigger_event == CE_HITTING_X ||
11217 trigger_event == CE_HIT_BY_X ||
11218 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11220 if (change->can_change_or_has_action &&
11221 change->has_event[trigger_event] &&
11222 change->trigger_side & trigger_side &&
11223 change->trigger_player & trigger_player &&
11224 (!check_trigger_element ||
11225 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11227 change->actual_trigger_element = trigger_element;
11228 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11229 change->actual_trigger_player_bits = trigger_player;
11230 change->actual_trigger_side = trigger_side;
11231 change->actual_trigger_ce_value = CustomValue[x][y];
11232 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11233 change->actual_trigger_x = x;
11234 change->actual_trigger_y = y;
11236 // special case: trigger element not at (x,y) position for some events
11237 if (check_trigger_element)
11249 { 0, 0 }, { 0, 0 }, { 0, 0 },
11253 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11254 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11256 change->actual_trigger_ce_value = CustomValue[xx][yy];
11257 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11258 change->actual_trigger_x = xx;
11259 change->actual_trigger_y = yy;
11262 if (change->can_change && !change_done)
11264 ChangeDelay[x][y] = 1;
11265 ChangeEvent[x][y] = trigger_event;
11267 HandleElementChange(x, y, p);
11269 change_done = TRUE;
11271 else if (change->has_action)
11273 ExecuteCustomElementAction(x, y, element, p);
11274 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11279 RECURSION_LOOP_DETECTION_END();
11281 return change_done;
11284 static void PlayPlayerSound(struct PlayerInfo *player)
11286 int jx = player->jx, jy = player->jy;
11287 int sound_element = player->artwork_element;
11288 int last_action = player->last_action_waiting;
11289 int action = player->action_waiting;
11291 if (player->is_waiting)
11293 if (action != last_action)
11294 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11296 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11300 if (action != last_action)
11301 StopSound(element_info[sound_element].sound[last_action]);
11303 if (last_action == ACTION_SLEEPING)
11304 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11308 static void PlayAllPlayersSound(void)
11312 for (i = 0; i < MAX_PLAYERS; i++)
11313 if (stored_player[i].active)
11314 PlayPlayerSound(&stored_player[i]);
11317 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11319 boolean last_waiting = player->is_waiting;
11320 int move_dir = player->MovDir;
11322 player->dir_waiting = move_dir;
11323 player->last_action_waiting = player->action_waiting;
11327 if (!last_waiting) // not waiting -> waiting
11329 player->is_waiting = TRUE;
11331 player->frame_counter_bored =
11333 game.player_boring_delay_fixed +
11334 GetSimpleRandom(game.player_boring_delay_random);
11335 player->frame_counter_sleeping =
11337 game.player_sleeping_delay_fixed +
11338 GetSimpleRandom(game.player_sleeping_delay_random);
11340 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11343 if (game.player_sleeping_delay_fixed +
11344 game.player_sleeping_delay_random > 0 &&
11345 player->anim_delay_counter == 0 &&
11346 player->post_delay_counter == 0 &&
11347 FrameCounter >= player->frame_counter_sleeping)
11348 player->is_sleeping = TRUE;
11349 else if (game.player_boring_delay_fixed +
11350 game.player_boring_delay_random > 0 &&
11351 FrameCounter >= player->frame_counter_bored)
11352 player->is_bored = TRUE;
11354 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11355 player->is_bored ? ACTION_BORING :
11358 if (player->is_sleeping && player->use_murphy)
11360 // special case for sleeping Murphy when leaning against non-free tile
11362 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11363 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11364 !IS_MOVING(player->jx - 1, player->jy)))
11365 move_dir = MV_LEFT;
11366 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11367 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11368 !IS_MOVING(player->jx + 1, player->jy)))
11369 move_dir = MV_RIGHT;
11371 player->is_sleeping = FALSE;
11373 player->dir_waiting = move_dir;
11376 if (player->is_sleeping)
11378 if (player->num_special_action_sleeping > 0)
11380 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11382 int last_special_action = player->special_action_sleeping;
11383 int num_special_action = player->num_special_action_sleeping;
11384 int special_action =
11385 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11386 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11387 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11388 last_special_action + 1 : ACTION_SLEEPING);
11389 int special_graphic =
11390 el_act_dir2img(player->artwork_element, special_action, move_dir);
11392 player->anim_delay_counter =
11393 graphic_info[special_graphic].anim_delay_fixed +
11394 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11395 player->post_delay_counter =
11396 graphic_info[special_graphic].post_delay_fixed +
11397 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11399 player->special_action_sleeping = special_action;
11402 if (player->anim_delay_counter > 0)
11404 player->action_waiting = player->special_action_sleeping;
11405 player->anim_delay_counter--;
11407 else if (player->post_delay_counter > 0)
11409 player->post_delay_counter--;
11413 else if (player->is_bored)
11415 if (player->num_special_action_bored > 0)
11417 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11419 int special_action =
11420 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11421 int special_graphic =
11422 el_act_dir2img(player->artwork_element, special_action, move_dir);
11424 player->anim_delay_counter =
11425 graphic_info[special_graphic].anim_delay_fixed +
11426 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11427 player->post_delay_counter =
11428 graphic_info[special_graphic].post_delay_fixed +
11429 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11431 player->special_action_bored = special_action;
11434 if (player->anim_delay_counter > 0)
11436 player->action_waiting = player->special_action_bored;
11437 player->anim_delay_counter--;
11439 else if (player->post_delay_counter > 0)
11441 player->post_delay_counter--;
11446 else if (last_waiting) // waiting -> not waiting
11448 player->is_waiting = FALSE;
11449 player->is_bored = FALSE;
11450 player->is_sleeping = FALSE;
11452 player->frame_counter_bored = -1;
11453 player->frame_counter_sleeping = -1;
11455 player->anim_delay_counter = 0;
11456 player->post_delay_counter = 0;
11458 player->dir_waiting = player->MovDir;
11459 player->action_waiting = ACTION_DEFAULT;
11461 player->special_action_bored = ACTION_DEFAULT;
11462 player->special_action_sleeping = ACTION_DEFAULT;
11466 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11468 if ((!player->is_moving && player->was_moving) ||
11469 (player->MovPos == 0 && player->was_moving) ||
11470 (player->is_snapping && !player->was_snapping) ||
11471 (player->is_dropping && !player->was_dropping))
11473 if (!CheckSaveEngineSnapshotToList())
11476 player->was_moving = FALSE;
11477 player->was_snapping = TRUE;
11478 player->was_dropping = TRUE;
11482 if (player->is_moving)
11483 player->was_moving = TRUE;
11485 if (!player->is_snapping)
11486 player->was_snapping = FALSE;
11488 if (!player->is_dropping)
11489 player->was_dropping = FALSE;
11492 static struct MouseActionInfo mouse_action_last = { 0 };
11493 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11494 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11497 CheckSaveEngineSnapshotToList();
11499 mouse_action_last = mouse_action;
11502 static void CheckSingleStepMode(struct PlayerInfo *player)
11504 if (tape.single_step && tape.recording && !tape.pausing)
11506 // as it is called "single step mode", just return to pause mode when the
11507 // player stopped moving after one tile (or never starts moving at all)
11508 // (reverse logic needed here in case single step mode used in team mode)
11509 if (player->is_moving ||
11510 player->is_pushing ||
11511 player->is_dropping_pressed ||
11512 player->effective_mouse_action.button)
11513 game.enter_single_step_mode = FALSE;
11516 CheckSaveEngineSnapshot(player);
11519 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11521 int left = player_action & JOY_LEFT;
11522 int right = player_action & JOY_RIGHT;
11523 int up = player_action & JOY_UP;
11524 int down = player_action & JOY_DOWN;
11525 int button1 = player_action & JOY_BUTTON_1;
11526 int button2 = player_action & JOY_BUTTON_2;
11527 int dx = (left ? -1 : right ? 1 : 0);
11528 int dy = (up ? -1 : down ? 1 : 0);
11530 if (!player->active || tape.pausing)
11536 SnapField(player, dx, dy);
11540 DropElement(player);
11542 MovePlayer(player, dx, dy);
11545 CheckSingleStepMode(player);
11547 SetPlayerWaiting(player, FALSE);
11549 return player_action;
11553 // no actions for this player (no input at player's configured device)
11555 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11556 SnapField(player, 0, 0);
11557 CheckGravityMovementWhenNotMoving(player);
11559 if (player->MovPos == 0)
11560 SetPlayerWaiting(player, TRUE);
11562 if (player->MovPos == 0) // needed for tape.playing
11563 player->is_moving = FALSE;
11565 player->is_dropping = FALSE;
11566 player->is_dropping_pressed = FALSE;
11567 player->drop_pressed_delay = 0;
11569 CheckSingleStepMode(player);
11575 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11578 if (!tape.use_mouse_actions)
11581 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11582 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11583 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11586 static void SetTapeActionFromMouseAction(byte *tape_action,
11587 struct MouseActionInfo *mouse_action)
11589 if (!tape.use_mouse_actions)
11592 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11593 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11594 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11597 static void CheckLevelSolved(void)
11599 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11601 if (game_em.level_solved &&
11602 !game_em.game_over) // game won
11606 game_em.game_over = TRUE;
11608 game.all_players_gone = TRUE;
11611 if (game_em.game_over) // game lost
11612 game.all_players_gone = TRUE;
11614 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11616 if (game_sp.level_solved &&
11617 !game_sp.game_over) // game won
11621 game_sp.game_over = TRUE;
11623 game.all_players_gone = TRUE;
11626 if (game_sp.game_over) // game lost
11627 game.all_players_gone = TRUE;
11629 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11631 if (game_mm.level_solved &&
11632 !game_mm.game_over) // game won
11636 game_mm.game_over = TRUE;
11638 game.all_players_gone = TRUE;
11641 if (game_mm.game_over) // game lost
11642 game.all_players_gone = TRUE;
11646 static void CheckLevelTime_StepCounter(void)
11656 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11657 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11659 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11661 DisplayGameControlValues();
11663 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11664 for (i = 0; i < MAX_PLAYERS; i++)
11665 KillPlayer(&stored_player[i]);
11667 else if (game.no_level_time_limit && !game.all_players_gone)
11669 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11671 DisplayGameControlValues();
11675 static void CheckLevelTime(void)
11679 if (TimeFrames >= FRAMES_PER_SECOND)
11684 for (i = 0; i < MAX_PLAYERS; i++)
11686 struct PlayerInfo *player = &stored_player[i];
11688 if (SHIELD_ON(player))
11690 player->shield_normal_time_left--;
11692 if (player->shield_deadly_time_left > 0)
11693 player->shield_deadly_time_left--;
11697 if (!game.LevelSolved && !level.use_step_counter)
11705 if (TimeLeft <= 10 && game.time_limit)
11706 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11708 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11709 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11711 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11713 if (!TimeLeft && game.time_limit)
11715 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11716 game_em.lev->killed_out_of_time = TRUE;
11718 for (i = 0; i < MAX_PLAYERS; i++)
11719 KillPlayer(&stored_player[i]);
11722 else if (game.no_level_time_limit && !game.all_players_gone)
11724 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11727 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11730 if (tape.recording || tape.playing)
11731 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11734 if (tape.recording || tape.playing)
11735 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11737 UpdateAndDisplayGameControlValues();
11740 void AdvanceFrameAndPlayerCounters(int player_nr)
11744 // advance frame counters (global frame counter and time frame counter)
11748 // advance player counters (counters for move delay, move animation etc.)
11749 for (i = 0; i < MAX_PLAYERS; i++)
11751 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11752 int move_delay_value = stored_player[i].move_delay_value;
11753 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11755 if (!advance_player_counters) // not all players may be affected
11758 if (move_frames == 0) // less than one move per game frame
11760 int stepsize = TILEX / move_delay_value;
11761 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11762 int count = (stored_player[i].is_moving ?
11763 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11765 if (count % delay == 0)
11769 stored_player[i].Frame += move_frames;
11771 if (stored_player[i].MovPos != 0)
11772 stored_player[i].StepFrame += move_frames;
11774 if (stored_player[i].move_delay > 0)
11775 stored_player[i].move_delay--;
11777 // due to bugs in previous versions, counter must count up, not down
11778 if (stored_player[i].push_delay != -1)
11779 stored_player[i].push_delay++;
11781 if (stored_player[i].drop_delay > 0)
11782 stored_player[i].drop_delay--;
11784 if (stored_player[i].is_dropping_pressed)
11785 stored_player[i].drop_pressed_delay++;
11789 void AdvanceFrameCounter(void)
11794 void AdvanceGfxFrame(void)
11798 SCAN_PLAYFIELD(x, y)
11804 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11805 struct MouseActionInfo *mouse_action_last)
11807 if (mouse_action->button)
11809 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11810 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11811 int x = mouse_action->lx;
11812 int y = mouse_action->ly;
11813 int element = Tile[x][y];
11817 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11818 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11822 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11823 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
11826 if (level.use_step_counter)
11828 boolean counted_click = FALSE;
11830 // element clicked that can change when clicked/pressed
11831 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
11832 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
11833 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
11834 counted_click = TRUE;
11836 // element clicked that can trigger change when clicked/pressed
11837 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
11838 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
11839 counted_click = TRUE;
11841 if (new_button && counted_click)
11842 CheckLevelTime_StepCounter();
11847 void StartGameActions(boolean init_network_game, boolean record_tape,
11850 unsigned int new_random_seed = InitRND(random_seed);
11853 TapeStartRecording(new_random_seed);
11855 if (setup.auto_pause_on_start && !tape.pausing)
11856 TapeTogglePause(TAPE_TOGGLE_MANUAL);
11858 if (init_network_game)
11860 SendToServer_LevelFile();
11861 SendToServer_StartPlaying();
11869 static void GameActionsExt(void)
11872 static unsigned int game_frame_delay = 0;
11874 unsigned int game_frame_delay_value;
11875 byte *recorded_player_action;
11876 byte summarized_player_action = 0;
11877 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11880 // detect endless loops, caused by custom element programming
11881 if (recursion_loop_detected && recursion_loop_depth == 0)
11883 char *message = getStringCat3("Internal Error! Element ",
11884 EL_NAME(recursion_loop_element),
11885 " caused endless loop! Quit the game?");
11887 Warn("element '%s' caused endless loop in game engine",
11888 EL_NAME(recursion_loop_element));
11890 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11892 recursion_loop_detected = FALSE; // if game should be continued
11899 if (game.restart_level)
11900 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11902 CheckLevelSolved();
11904 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11907 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11910 if (game_status != GAME_MODE_PLAYING) // status might have changed
11913 game_frame_delay_value =
11914 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11916 if (tape.playing && tape.warp_forward && !tape.pausing)
11917 game_frame_delay_value = 0;
11919 SetVideoFrameDelay(game_frame_delay_value);
11921 // (de)activate virtual buttons depending on current game status
11922 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11924 if (game.all_players_gone) // if no players there to be controlled anymore
11925 SetOverlayActive(FALSE);
11926 else if (!tape.playing) // if game continues after tape stopped playing
11927 SetOverlayActive(TRUE);
11932 // ---------- main game synchronization point ----------
11934 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11936 Debug("game:playing:skip", "skip == %d", skip);
11939 // ---------- main game synchronization point ----------
11941 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11945 if (network_playing && !network_player_action_received)
11947 // try to get network player actions in time
11949 // last chance to get network player actions without main loop delay
11950 HandleNetworking();
11952 // game was quit by network peer
11953 if (game_status != GAME_MODE_PLAYING)
11956 // check if network player actions still missing and game still running
11957 if (!network_player_action_received && !checkGameEnded())
11958 return; // failed to get network player actions in time
11960 // do not yet reset "network_player_action_received" (for tape.pausing)
11966 // at this point we know that we really continue executing the game
11968 network_player_action_received = FALSE;
11970 // when playing tape, read previously recorded player input from tape data
11971 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11973 local_player->effective_mouse_action = local_player->mouse_action;
11975 if (recorded_player_action != NULL)
11976 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11977 recorded_player_action);
11979 // TapePlayAction() may return NULL when toggling to "pause before death"
11983 if (tape.set_centered_player)
11985 game.centered_player_nr_next = tape.centered_player_nr_next;
11986 game.set_centered_player = TRUE;
11989 for (i = 0; i < MAX_PLAYERS; i++)
11991 summarized_player_action |= stored_player[i].action;
11993 if (!network_playing && (game.team_mode || tape.playing))
11994 stored_player[i].effective_action = stored_player[i].action;
11997 if (network_playing && !checkGameEnded())
11998 SendToServer_MovePlayer(summarized_player_action);
12000 // summarize all actions at local players mapped input device position
12001 // (this allows using different input devices in single player mode)
12002 if (!network.enabled && !game.team_mode)
12003 stored_player[map_player_action[local_player->index_nr]].effective_action =
12004 summarized_player_action;
12006 // summarize all actions at centered player in local team mode
12007 if (tape.recording &&
12008 setup.team_mode && !network.enabled &&
12009 setup.input_on_focus &&
12010 game.centered_player_nr != -1)
12012 for (i = 0; i < MAX_PLAYERS; i++)
12013 stored_player[map_player_action[i]].effective_action =
12014 (i == game.centered_player_nr ? summarized_player_action : 0);
12017 if (recorded_player_action != NULL)
12018 for (i = 0; i < MAX_PLAYERS; i++)
12019 stored_player[i].effective_action = recorded_player_action[i];
12021 for (i = 0; i < MAX_PLAYERS; i++)
12023 tape_action[i] = stored_player[i].effective_action;
12025 /* (this may happen in the RND game engine if a player was not present on
12026 the playfield on level start, but appeared later from a custom element */
12027 if (setup.team_mode &&
12030 !tape.player_participates[i])
12031 tape.player_participates[i] = TRUE;
12034 SetTapeActionFromMouseAction(tape_action,
12035 &local_player->effective_mouse_action);
12037 // only record actions from input devices, but not programmed actions
12038 if (tape.recording)
12039 TapeRecordAction(tape_action);
12041 // remember if game was played (especially after tape stopped playing)
12042 if (!tape.playing && summarized_player_action && !checkGameFailed())
12043 game.GamePlayed = TRUE;
12045 #if USE_NEW_PLAYER_ASSIGNMENTS
12046 // !!! also map player actions in single player mode !!!
12047 // if (game.team_mode)
12050 byte mapped_action[MAX_PLAYERS];
12052 #if DEBUG_PLAYER_ACTIONS
12053 for (i = 0; i < MAX_PLAYERS; i++)
12054 DebugContinued("", "%d, ", stored_player[i].effective_action);
12057 for (i = 0; i < MAX_PLAYERS; i++)
12058 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12060 for (i = 0; i < MAX_PLAYERS; i++)
12061 stored_player[i].effective_action = mapped_action[i];
12063 #if DEBUG_PLAYER_ACTIONS
12064 DebugContinued("", "=> ");
12065 for (i = 0; i < MAX_PLAYERS; i++)
12066 DebugContinued("", "%d, ", stored_player[i].effective_action);
12067 DebugContinued("game:playing:player", "\n");
12070 #if DEBUG_PLAYER_ACTIONS
12073 for (i = 0; i < MAX_PLAYERS; i++)
12074 DebugContinued("", "%d, ", stored_player[i].effective_action);
12075 DebugContinued("game:playing:player", "\n");
12080 for (i = 0; i < MAX_PLAYERS; i++)
12082 // allow engine snapshot in case of changed movement attempt
12083 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12084 (stored_player[i].effective_action & KEY_MOTION))
12085 game.snapshot.changed_action = TRUE;
12087 // allow engine snapshot in case of snapping/dropping attempt
12088 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12089 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12090 game.snapshot.changed_action = TRUE;
12092 game.snapshot.last_action[i] = stored_player[i].effective_action;
12095 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12097 GameActions_EM_Main();
12099 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12101 GameActions_SP_Main();
12103 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12105 GameActions_MM_Main();
12109 GameActions_RND_Main();
12112 BlitScreenToBitmap(backbuffer);
12114 CheckLevelSolved();
12117 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12119 if (global.show_frames_per_second)
12121 static unsigned int fps_counter = 0;
12122 static int fps_frames = 0;
12123 unsigned int fps_delay_ms = Counter() - fps_counter;
12127 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12129 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12132 fps_counter = Counter();
12134 // always draw FPS to screen after FPS value was updated
12135 redraw_mask |= REDRAW_FPS;
12138 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12139 if (GetDrawDeactivationMask() == REDRAW_NONE)
12140 redraw_mask |= REDRAW_FPS;
12144 static void GameActions_CheckSaveEngineSnapshot(void)
12146 if (!game.snapshot.save_snapshot)
12149 // clear flag for saving snapshot _before_ saving snapshot
12150 game.snapshot.save_snapshot = FALSE;
12152 SaveEngineSnapshotToList();
12155 void GameActions(void)
12159 GameActions_CheckSaveEngineSnapshot();
12162 void GameActions_EM_Main(void)
12164 byte effective_action[MAX_PLAYERS];
12167 for (i = 0; i < MAX_PLAYERS; i++)
12168 effective_action[i] = stored_player[i].effective_action;
12170 GameActions_EM(effective_action);
12173 void GameActions_SP_Main(void)
12175 byte effective_action[MAX_PLAYERS];
12178 for (i = 0; i < MAX_PLAYERS; i++)
12179 effective_action[i] = stored_player[i].effective_action;
12181 GameActions_SP(effective_action);
12183 for (i = 0; i < MAX_PLAYERS; i++)
12185 if (stored_player[i].force_dropping)
12186 stored_player[i].action |= KEY_BUTTON_DROP;
12188 stored_player[i].force_dropping = FALSE;
12192 void GameActions_MM_Main(void)
12196 GameActions_MM(local_player->effective_mouse_action);
12199 void GameActions_RND_Main(void)
12204 void GameActions_RND(void)
12206 static struct MouseActionInfo mouse_action_last = { 0 };
12207 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12208 int magic_wall_x = 0, magic_wall_y = 0;
12209 int i, x, y, element, graphic, last_gfx_frame;
12211 InitPlayfieldScanModeVars();
12213 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12215 SCAN_PLAYFIELD(x, y)
12217 ChangeCount[x][y] = 0;
12218 ChangeEvent[x][y] = -1;
12222 if (game.set_centered_player)
12224 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12226 // switching to "all players" only possible if all players fit to screen
12227 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12229 game.centered_player_nr_next = game.centered_player_nr;
12230 game.set_centered_player = FALSE;
12233 // do not switch focus to non-existing (or non-active) player
12234 if (game.centered_player_nr_next >= 0 &&
12235 !stored_player[game.centered_player_nr_next].active)
12237 game.centered_player_nr_next = game.centered_player_nr;
12238 game.set_centered_player = FALSE;
12242 if (game.set_centered_player &&
12243 ScreenMovPos == 0) // screen currently aligned at tile position
12247 if (game.centered_player_nr_next == -1)
12249 setScreenCenteredToAllPlayers(&sx, &sy);
12253 sx = stored_player[game.centered_player_nr_next].jx;
12254 sy = stored_player[game.centered_player_nr_next].jy;
12257 game.centered_player_nr = game.centered_player_nr_next;
12258 game.set_centered_player = FALSE;
12260 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12261 DrawGameDoorValues();
12264 // check single step mode (set flag and clear again if any player is active)
12265 game.enter_single_step_mode =
12266 (tape.single_step && tape.recording && !tape.pausing);
12268 for (i = 0; i < MAX_PLAYERS; i++)
12270 int actual_player_action = stored_player[i].effective_action;
12273 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12274 - rnd_equinox_tetrachloride 048
12275 - rnd_equinox_tetrachloride_ii 096
12276 - rnd_emanuel_schmieg 002
12277 - doctor_sloan_ww 001, 020
12279 if (stored_player[i].MovPos == 0)
12280 CheckGravityMovement(&stored_player[i]);
12283 // overwrite programmed action with tape action
12284 if (stored_player[i].programmed_action)
12285 actual_player_action = stored_player[i].programmed_action;
12287 PlayerActions(&stored_player[i], actual_player_action);
12289 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12292 // single step pause mode may already have been toggled by "ScrollPlayer()"
12293 if (game.enter_single_step_mode && !tape.pausing)
12294 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12296 ScrollScreen(NULL, SCROLL_GO_ON);
12298 /* for backwards compatibility, the following code emulates a fixed bug that
12299 occured when pushing elements (causing elements that just made their last
12300 pushing step to already (if possible) make their first falling step in the
12301 same game frame, which is bad); this code is also needed to use the famous
12302 "spring push bug" which is used in older levels and might be wanted to be
12303 used also in newer levels, but in this case the buggy pushing code is only
12304 affecting the "spring" element and no other elements */
12306 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12308 for (i = 0; i < MAX_PLAYERS; i++)
12310 struct PlayerInfo *player = &stored_player[i];
12311 int x = player->jx;
12312 int y = player->jy;
12314 if (player->active && player->is_pushing && player->is_moving &&
12316 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12317 Tile[x][y] == EL_SPRING))
12319 ContinueMoving(x, y);
12321 // continue moving after pushing (this is actually a bug)
12322 if (!IS_MOVING(x, y))
12323 Stop[x][y] = FALSE;
12328 SCAN_PLAYFIELD(x, y)
12330 Last[x][y] = Tile[x][y];
12332 ChangeCount[x][y] = 0;
12333 ChangeEvent[x][y] = -1;
12335 // this must be handled before main playfield loop
12336 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12339 if (MovDelay[x][y] <= 0)
12343 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12346 if (MovDelay[x][y] <= 0)
12348 int element = Store[x][y];
12349 int move_direction = MovDir[x][y];
12350 int player_index_bit = Store2[x][y];
12356 TEST_DrawLevelField(x, y);
12358 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12360 if (IS_ENVELOPE(element))
12361 local_player->show_envelope = element;
12366 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12368 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12370 Debug("game:playing:GameActions_RND", "This should never happen!");
12372 ChangePage[x][y] = -1;
12376 Stop[x][y] = FALSE;
12377 if (WasJustMoving[x][y] > 0)
12378 WasJustMoving[x][y]--;
12379 if (WasJustFalling[x][y] > 0)
12380 WasJustFalling[x][y]--;
12381 if (CheckCollision[x][y] > 0)
12382 CheckCollision[x][y]--;
12383 if (CheckImpact[x][y] > 0)
12384 CheckImpact[x][y]--;
12388 /* reset finished pushing action (not done in ContinueMoving() to allow
12389 continuous pushing animation for elements with zero push delay) */
12390 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12392 ResetGfxAnimation(x, y);
12393 TEST_DrawLevelField(x, y);
12397 if (IS_BLOCKED(x, y))
12401 Blocked2Moving(x, y, &oldx, &oldy);
12402 if (!IS_MOVING(oldx, oldy))
12404 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12405 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12406 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12407 Debug("game:playing:GameActions_RND", "This should never happen!");
12413 HandleMouseAction(&mouse_action, &mouse_action_last);
12415 SCAN_PLAYFIELD(x, y)
12417 element = Tile[x][y];
12418 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12419 last_gfx_frame = GfxFrame[x][y];
12421 if (element == EL_EMPTY)
12422 graphic = el2img(GfxElementEmpty[x][y]);
12424 ResetGfxFrame(x, y);
12426 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12427 DrawLevelGraphicAnimation(x, y, graphic);
12429 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12430 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12431 ResetRandomAnimationValue(x, y);
12433 SetRandomAnimationValue(x, y);
12435 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12437 if (IS_INACTIVE(element))
12439 if (IS_ANIMATED(graphic))
12440 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12445 // this may take place after moving, so 'element' may have changed
12446 if (IS_CHANGING(x, y) &&
12447 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12449 int page = element_info[element].event_page_nr[CE_DELAY];
12451 HandleElementChange(x, y, page);
12453 element = Tile[x][y];
12454 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12457 CheckNextToConditions(x, y);
12459 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12463 element = Tile[x][y];
12464 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12466 if (IS_ANIMATED(graphic) &&
12467 !IS_MOVING(x, y) &&
12469 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12471 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12472 TEST_DrawTwinkleOnField(x, y);
12474 else if (element == EL_ACID)
12477 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12479 else if ((element == EL_EXIT_OPEN ||
12480 element == EL_EM_EXIT_OPEN ||
12481 element == EL_SP_EXIT_OPEN ||
12482 element == EL_STEEL_EXIT_OPEN ||
12483 element == EL_EM_STEEL_EXIT_OPEN ||
12484 element == EL_SP_TERMINAL ||
12485 element == EL_SP_TERMINAL_ACTIVE ||
12486 element == EL_EXTRA_TIME ||
12487 element == EL_SHIELD_NORMAL ||
12488 element == EL_SHIELD_DEADLY) &&
12489 IS_ANIMATED(graphic))
12490 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12491 else if (IS_MOVING(x, y))
12492 ContinueMoving(x, y);
12493 else if (IS_ACTIVE_BOMB(element))
12494 CheckDynamite(x, y);
12495 else if (element == EL_AMOEBA_GROWING)
12496 AmoebaGrowing(x, y);
12497 else if (element == EL_AMOEBA_SHRINKING)
12498 AmoebaShrinking(x, y);
12500 #if !USE_NEW_AMOEBA_CODE
12501 else if (IS_AMOEBALIVE(element))
12502 AmoebaReproduce(x, y);
12505 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12507 else if (element == EL_EXIT_CLOSED)
12509 else if (element == EL_EM_EXIT_CLOSED)
12511 else if (element == EL_STEEL_EXIT_CLOSED)
12512 CheckExitSteel(x, y);
12513 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12514 CheckExitSteelEM(x, y);
12515 else if (element == EL_SP_EXIT_CLOSED)
12517 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12518 element == EL_EXPANDABLE_STEELWALL_GROWING)
12520 else if (element == EL_EXPANDABLE_WALL ||
12521 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12522 element == EL_EXPANDABLE_WALL_VERTICAL ||
12523 element == EL_EXPANDABLE_WALL_ANY ||
12524 element == EL_BD_EXPANDABLE_WALL ||
12525 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12526 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12527 element == EL_EXPANDABLE_STEELWALL_ANY)
12528 CheckWallGrowing(x, y);
12529 else if (element == EL_FLAMES)
12530 CheckForDragon(x, y);
12531 else if (element == EL_EXPLOSION)
12532 ; // drawing of correct explosion animation is handled separately
12533 else if (element == EL_ELEMENT_SNAPPING ||
12534 element == EL_DIAGONAL_SHRINKING ||
12535 element == EL_DIAGONAL_GROWING)
12537 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12539 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12541 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12542 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12544 if (IS_BELT_ACTIVE(element))
12545 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12547 if (game.magic_wall_active)
12549 int jx = local_player->jx, jy = local_player->jy;
12551 // play the element sound at the position nearest to the player
12552 if ((element == EL_MAGIC_WALL_FULL ||
12553 element == EL_MAGIC_WALL_ACTIVE ||
12554 element == EL_MAGIC_WALL_EMPTYING ||
12555 element == EL_BD_MAGIC_WALL_FULL ||
12556 element == EL_BD_MAGIC_WALL_ACTIVE ||
12557 element == EL_BD_MAGIC_WALL_EMPTYING ||
12558 element == EL_DC_MAGIC_WALL_FULL ||
12559 element == EL_DC_MAGIC_WALL_ACTIVE ||
12560 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12561 ABS(x - jx) + ABS(y - jy) <
12562 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12570 #if USE_NEW_AMOEBA_CODE
12571 // new experimental amoeba growth stuff
12572 if (!(FrameCounter % 8))
12574 static unsigned int random = 1684108901;
12576 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12578 x = RND(lev_fieldx);
12579 y = RND(lev_fieldy);
12580 element = Tile[x][y];
12582 if (!IS_PLAYER(x, y) &&
12583 (element == EL_EMPTY ||
12584 CAN_GROW_INTO(element) ||
12585 element == EL_QUICKSAND_EMPTY ||
12586 element == EL_QUICKSAND_FAST_EMPTY ||
12587 element == EL_ACID_SPLASH_LEFT ||
12588 element == EL_ACID_SPLASH_RIGHT))
12590 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12591 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12592 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12593 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12594 Tile[x][y] = EL_AMOEBA_DROP;
12597 random = random * 129 + 1;
12602 game.explosions_delayed = FALSE;
12604 SCAN_PLAYFIELD(x, y)
12606 element = Tile[x][y];
12608 if (ExplodeField[x][y])
12609 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12610 else if (element == EL_EXPLOSION)
12611 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12613 ExplodeField[x][y] = EX_TYPE_NONE;
12616 game.explosions_delayed = TRUE;
12618 if (game.magic_wall_active)
12620 if (!(game.magic_wall_time_left % 4))
12622 int element = Tile[magic_wall_x][magic_wall_y];
12624 if (element == EL_BD_MAGIC_WALL_FULL ||
12625 element == EL_BD_MAGIC_WALL_ACTIVE ||
12626 element == EL_BD_MAGIC_WALL_EMPTYING)
12627 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12628 else if (element == EL_DC_MAGIC_WALL_FULL ||
12629 element == EL_DC_MAGIC_WALL_ACTIVE ||
12630 element == EL_DC_MAGIC_WALL_EMPTYING)
12631 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12633 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12636 if (game.magic_wall_time_left > 0)
12638 game.magic_wall_time_left--;
12640 if (!game.magic_wall_time_left)
12642 SCAN_PLAYFIELD(x, y)
12644 element = Tile[x][y];
12646 if (element == EL_MAGIC_WALL_ACTIVE ||
12647 element == EL_MAGIC_WALL_FULL)
12649 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12650 TEST_DrawLevelField(x, y);
12652 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12653 element == EL_BD_MAGIC_WALL_FULL)
12655 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12656 TEST_DrawLevelField(x, y);
12658 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12659 element == EL_DC_MAGIC_WALL_FULL)
12661 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12662 TEST_DrawLevelField(x, y);
12666 game.magic_wall_active = FALSE;
12671 if (game.light_time_left > 0)
12673 game.light_time_left--;
12675 if (game.light_time_left == 0)
12676 RedrawAllLightSwitchesAndInvisibleElements();
12679 if (game.timegate_time_left > 0)
12681 game.timegate_time_left--;
12683 if (game.timegate_time_left == 0)
12684 CloseAllOpenTimegates();
12687 if (game.lenses_time_left > 0)
12689 game.lenses_time_left--;
12691 if (game.lenses_time_left == 0)
12692 RedrawAllInvisibleElementsForLenses();
12695 if (game.magnify_time_left > 0)
12697 game.magnify_time_left--;
12699 if (game.magnify_time_left == 0)
12700 RedrawAllInvisibleElementsForMagnifier();
12703 for (i = 0; i < MAX_PLAYERS; i++)
12705 struct PlayerInfo *player = &stored_player[i];
12707 if (SHIELD_ON(player))
12709 if (player->shield_deadly_time_left)
12710 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12711 else if (player->shield_normal_time_left)
12712 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12716 #if USE_DELAYED_GFX_REDRAW
12717 SCAN_PLAYFIELD(x, y)
12719 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12721 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12722 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12724 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12725 DrawLevelField(x, y);
12727 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12728 DrawLevelFieldCrumbled(x, y);
12730 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12731 DrawLevelFieldCrumbledNeighbours(x, y);
12733 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12734 DrawTwinkleOnField(x, y);
12737 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12742 PlayAllPlayersSound();
12744 for (i = 0; i < MAX_PLAYERS; i++)
12746 struct PlayerInfo *player = &stored_player[i];
12748 if (player->show_envelope != 0 && (!player->active ||
12749 player->MovPos == 0))
12751 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12753 player->show_envelope = 0;
12757 // use random number generator in every frame to make it less predictable
12758 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12761 mouse_action_last = mouse_action;
12764 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12766 int min_x = x, min_y = y, max_x = x, max_y = y;
12767 int scr_fieldx = getScreenFieldSizeX();
12768 int scr_fieldy = getScreenFieldSizeY();
12771 for (i = 0; i < MAX_PLAYERS; i++)
12773 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12775 if (!stored_player[i].active || &stored_player[i] == player)
12778 min_x = MIN(min_x, jx);
12779 min_y = MIN(min_y, jy);
12780 max_x = MAX(max_x, jx);
12781 max_y = MAX(max_y, jy);
12784 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12787 static boolean AllPlayersInVisibleScreen(void)
12791 for (i = 0; i < MAX_PLAYERS; i++)
12793 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12795 if (!stored_player[i].active)
12798 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12805 void ScrollLevel(int dx, int dy)
12807 int scroll_offset = 2 * TILEX_VAR;
12810 BlitBitmap(drawto_field, drawto_field,
12811 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12812 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12813 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12814 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12815 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12816 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12820 x = (dx == 1 ? BX1 : BX2);
12821 for (y = BY1; y <= BY2; y++)
12822 DrawScreenField(x, y);
12827 y = (dy == 1 ? BY1 : BY2);
12828 for (x = BX1; x <= BX2; x++)
12829 DrawScreenField(x, y);
12832 redraw_mask |= REDRAW_FIELD;
12835 static boolean canFallDown(struct PlayerInfo *player)
12837 int jx = player->jx, jy = player->jy;
12839 return (IN_LEV_FIELD(jx, jy + 1) &&
12840 (IS_FREE(jx, jy + 1) ||
12841 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12842 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12843 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12846 static boolean canPassField(int x, int y, int move_dir)
12848 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12849 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12850 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12851 int nextx = x + dx;
12852 int nexty = y + dy;
12853 int element = Tile[x][y];
12855 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12856 !CAN_MOVE(element) &&
12857 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12858 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12859 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12862 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12864 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12865 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12866 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12870 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12871 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12872 (IS_DIGGABLE(Tile[newx][newy]) ||
12873 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12874 canPassField(newx, newy, move_dir)));
12877 static void CheckGravityMovement(struct PlayerInfo *player)
12879 if (player->gravity && !player->programmed_action)
12881 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12882 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12883 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12884 int jx = player->jx, jy = player->jy;
12885 boolean player_is_moving_to_valid_field =
12886 (!player_is_snapping &&
12887 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12888 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12889 boolean player_can_fall_down = canFallDown(player);
12891 if (player_can_fall_down &&
12892 !player_is_moving_to_valid_field)
12893 player->programmed_action = MV_DOWN;
12897 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12899 return CheckGravityMovement(player);
12901 if (player->gravity && !player->programmed_action)
12903 int jx = player->jx, jy = player->jy;
12904 boolean field_under_player_is_free =
12905 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12906 boolean player_is_standing_on_valid_field =
12907 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12908 (IS_WALKABLE(Tile[jx][jy]) &&
12909 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12911 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12912 player->programmed_action = MV_DOWN;
12917 MovePlayerOneStep()
12918 -----------------------------------------------------------------------------
12919 dx, dy: direction (non-diagonal) to try to move the player to
12920 real_dx, real_dy: direction as read from input device (can be diagonal)
12923 boolean MovePlayerOneStep(struct PlayerInfo *player,
12924 int dx, int dy, int real_dx, int real_dy)
12926 int jx = player->jx, jy = player->jy;
12927 int new_jx = jx + dx, new_jy = jy + dy;
12929 boolean player_can_move = !player->cannot_move;
12931 if (!player->active || (!dx && !dy))
12932 return MP_NO_ACTION;
12934 player->MovDir = (dx < 0 ? MV_LEFT :
12935 dx > 0 ? MV_RIGHT :
12937 dy > 0 ? MV_DOWN : MV_NONE);
12939 if (!IN_LEV_FIELD(new_jx, new_jy))
12940 return MP_NO_ACTION;
12942 if (!player_can_move)
12944 if (player->MovPos == 0)
12946 player->is_moving = FALSE;
12947 player->is_digging = FALSE;
12948 player->is_collecting = FALSE;
12949 player->is_snapping = FALSE;
12950 player->is_pushing = FALSE;
12954 if (!network.enabled && game.centered_player_nr == -1 &&
12955 !AllPlayersInSight(player, new_jx, new_jy))
12956 return MP_NO_ACTION;
12958 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
12959 if (can_move != MP_MOVING)
12962 // check if DigField() has caused relocation of the player
12963 if (player->jx != jx || player->jy != jy)
12964 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12966 StorePlayer[jx][jy] = 0;
12967 player->last_jx = jx;
12968 player->last_jy = jy;
12969 player->jx = new_jx;
12970 player->jy = new_jy;
12971 StorePlayer[new_jx][new_jy] = player->element_nr;
12973 if (player->move_delay_value_next != -1)
12975 player->move_delay_value = player->move_delay_value_next;
12976 player->move_delay_value_next = -1;
12980 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12982 player->step_counter++;
12984 PlayerVisit[jx][jy] = FrameCounter;
12986 player->is_moving = TRUE;
12989 // should better be called in MovePlayer(), but this breaks some tapes
12990 ScrollPlayer(player, SCROLL_INIT);
12996 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12998 int jx = player->jx, jy = player->jy;
12999 int old_jx = jx, old_jy = jy;
13000 int moved = MP_NO_ACTION;
13002 if (!player->active)
13007 if (player->MovPos == 0)
13009 player->is_moving = FALSE;
13010 player->is_digging = FALSE;
13011 player->is_collecting = FALSE;
13012 player->is_snapping = FALSE;
13013 player->is_pushing = FALSE;
13019 if (player->move_delay > 0)
13022 player->move_delay = -1; // set to "uninitialized" value
13024 // store if player is automatically moved to next field
13025 player->is_auto_moving = (player->programmed_action != MV_NONE);
13027 // remove the last programmed player action
13028 player->programmed_action = 0;
13030 if (player->MovPos)
13032 // should only happen if pre-1.2 tape recordings are played
13033 // this is only for backward compatibility
13035 int original_move_delay_value = player->move_delay_value;
13038 Debug("game:playing:MovePlayer",
13039 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13043 // scroll remaining steps with finest movement resolution
13044 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13046 while (player->MovPos)
13048 ScrollPlayer(player, SCROLL_GO_ON);
13049 ScrollScreen(NULL, SCROLL_GO_ON);
13051 AdvanceFrameAndPlayerCounters(player->index_nr);
13054 BackToFront_WithFrameDelay(0);
13057 player->move_delay_value = original_move_delay_value;
13060 player->is_active = FALSE;
13062 if (player->last_move_dir & MV_HORIZONTAL)
13064 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13065 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13069 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13070 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13073 if (!moved && !player->is_active)
13075 player->is_moving = FALSE;
13076 player->is_digging = FALSE;
13077 player->is_collecting = FALSE;
13078 player->is_snapping = FALSE;
13079 player->is_pushing = FALSE;
13085 if (moved & MP_MOVING && !ScreenMovPos &&
13086 (player->index_nr == game.centered_player_nr ||
13087 game.centered_player_nr == -1))
13089 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13091 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13093 // actual player has left the screen -- scroll in that direction
13094 if (jx != old_jx) // player has moved horizontally
13095 scroll_x += (jx - old_jx);
13096 else // player has moved vertically
13097 scroll_y += (jy - old_jy);
13101 int offset_raw = game.scroll_delay_value;
13103 if (jx != old_jx) // player has moved horizontally
13105 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13106 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13107 int new_scroll_x = jx - MIDPOSX + offset_x;
13109 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13110 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13111 scroll_x = new_scroll_x;
13113 // don't scroll over playfield boundaries
13114 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13116 // don't scroll more than one field at a time
13117 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13119 // don't scroll against the player's moving direction
13120 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13121 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13122 scroll_x = old_scroll_x;
13124 else // player has moved vertically
13126 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13127 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13128 int new_scroll_y = jy - MIDPOSY + offset_y;
13130 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13131 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13132 scroll_y = new_scroll_y;
13134 // don't scroll over playfield boundaries
13135 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13137 // don't scroll more than one field at a time
13138 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13140 // don't scroll against the player's moving direction
13141 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13142 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13143 scroll_y = old_scroll_y;
13147 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13149 if (!network.enabled && game.centered_player_nr == -1 &&
13150 !AllPlayersInVisibleScreen())
13152 scroll_x = old_scroll_x;
13153 scroll_y = old_scroll_y;
13157 ScrollScreen(player, SCROLL_INIT);
13158 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13163 player->StepFrame = 0;
13165 if (moved & MP_MOVING)
13167 if (old_jx != jx && old_jy == jy)
13168 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13169 else if (old_jx == jx && old_jy != jy)
13170 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13172 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13174 player->last_move_dir = player->MovDir;
13175 player->is_moving = TRUE;
13176 player->is_snapping = FALSE;
13177 player->is_switching = FALSE;
13178 player->is_dropping = FALSE;
13179 player->is_dropping_pressed = FALSE;
13180 player->drop_pressed_delay = 0;
13183 // should better be called here than above, but this breaks some tapes
13184 ScrollPlayer(player, SCROLL_INIT);
13189 CheckGravityMovementWhenNotMoving(player);
13191 player->is_moving = FALSE;
13193 /* at this point, the player is allowed to move, but cannot move right now
13194 (e.g. because of something blocking the way) -- ensure that the player
13195 is also allowed to move in the next frame (in old versions before 3.1.1,
13196 the player was forced to wait again for eight frames before next try) */
13198 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13199 player->move_delay = 0; // allow direct movement in the next frame
13202 if (player->move_delay == -1) // not yet initialized by DigField()
13203 player->move_delay = player->move_delay_value;
13205 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13207 TestIfPlayerTouchesBadThing(jx, jy);
13208 TestIfPlayerTouchesCustomElement(jx, jy);
13211 if (!player->active)
13212 RemovePlayer(player);
13217 void ScrollPlayer(struct PlayerInfo *player, int mode)
13219 int jx = player->jx, jy = player->jy;
13220 int last_jx = player->last_jx, last_jy = player->last_jy;
13221 int move_stepsize = TILEX / player->move_delay_value;
13223 if (!player->active)
13226 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13229 if (mode == SCROLL_INIT)
13231 player->actual_frame_counter.count = FrameCounter;
13232 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13234 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13235 Tile[last_jx][last_jy] == EL_EMPTY)
13237 int last_field_block_delay = 0; // start with no blocking at all
13238 int block_delay_adjustment = player->block_delay_adjustment;
13240 // if player blocks last field, add delay for exactly one move
13241 if (player->block_last_field)
13243 last_field_block_delay += player->move_delay_value;
13245 // when blocking enabled, prevent moving up despite gravity
13246 if (player->gravity && player->MovDir == MV_UP)
13247 block_delay_adjustment = -1;
13250 // add block delay adjustment (also possible when not blocking)
13251 last_field_block_delay += block_delay_adjustment;
13253 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13254 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13257 if (player->MovPos != 0) // player has not yet reached destination
13260 else if (!FrameReached(&player->actual_frame_counter))
13263 if (player->MovPos != 0)
13265 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13266 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13268 // before DrawPlayer() to draw correct player graphic for this case
13269 if (player->MovPos == 0)
13270 CheckGravityMovement(player);
13273 if (player->MovPos == 0) // player reached destination field
13275 if (player->move_delay_reset_counter > 0)
13277 player->move_delay_reset_counter--;
13279 if (player->move_delay_reset_counter == 0)
13281 // continue with normal speed after quickly moving through gate
13282 HALVE_PLAYER_SPEED(player);
13284 // be able to make the next move without delay
13285 player->move_delay = 0;
13289 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13290 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13291 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13292 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13293 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13294 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13295 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13296 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13298 ExitPlayer(player);
13300 if (game.players_still_needed == 0 &&
13301 (game.friends_still_needed == 0 ||
13302 IS_SP_ELEMENT(Tile[jx][jy])))
13306 player->last_jx = jx;
13307 player->last_jy = jy;
13309 // this breaks one level: "machine", level 000
13311 int move_direction = player->MovDir;
13312 int enter_side = MV_DIR_OPPOSITE(move_direction);
13313 int leave_side = move_direction;
13314 int old_jx = last_jx;
13315 int old_jy = last_jy;
13316 int old_element = Tile[old_jx][old_jy];
13317 int new_element = Tile[jx][jy];
13319 if (IS_CUSTOM_ELEMENT(old_element))
13320 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13322 player->index_bit, leave_side);
13324 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13325 CE_PLAYER_LEAVES_X,
13326 player->index_bit, leave_side);
13328 // needed because pushed element has not yet reached its destination,
13329 // so it would trigger a change event at its previous field location
13330 if (!player->is_pushing)
13332 if (IS_CUSTOM_ELEMENT(new_element))
13333 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13334 player->index_bit, enter_side);
13336 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13337 CE_PLAYER_ENTERS_X,
13338 player->index_bit, enter_side);
13341 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13342 CE_MOVE_OF_X, move_direction);
13345 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13347 TestIfPlayerTouchesBadThing(jx, jy);
13348 TestIfPlayerTouchesCustomElement(jx, jy);
13350 // needed because pushed element has not yet reached its destination,
13351 // so it would trigger a change event at its previous field location
13352 if (!player->is_pushing)
13353 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13355 if (level.finish_dig_collect &&
13356 (player->is_digging || player->is_collecting))
13358 int last_element = player->last_removed_element;
13359 int move_direction = player->MovDir;
13360 int enter_side = MV_DIR_OPPOSITE(move_direction);
13361 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13362 CE_PLAYER_COLLECTS_X);
13364 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13365 player->index_bit, enter_side);
13367 player->last_removed_element = EL_UNDEFINED;
13370 if (!player->active)
13371 RemovePlayer(player);
13374 if (level.use_step_counter)
13375 CheckLevelTime_StepCounter();
13377 if (tape.single_step && tape.recording && !tape.pausing &&
13378 !player->programmed_action)
13379 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13381 if (!player->programmed_action)
13382 CheckSaveEngineSnapshot(player);
13386 void ScrollScreen(struct PlayerInfo *player, int mode)
13388 static DelayCounter screen_frame_counter = { 0 };
13390 if (mode == SCROLL_INIT)
13392 // set scrolling step size according to actual player's moving speed
13393 ScrollStepSize = TILEX / player->move_delay_value;
13395 screen_frame_counter.count = FrameCounter;
13396 screen_frame_counter.value = 1;
13398 ScreenMovDir = player->MovDir;
13399 ScreenMovPos = player->MovPos;
13400 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13403 else if (!FrameReached(&screen_frame_counter))
13408 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13409 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13410 redraw_mask |= REDRAW_FIELD;
13413 ScreenMovDir = MV_NONE;
13416 void CheckNextToConditions(int x, int y)
13418 int element = Tile[x][y];
13420 if (IS_PLAYER(x, y))
13421 TestIfPlayerNextToCustomElement(x, y);
13423 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13424 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13425 TestIfElementNextToCustomElement(x, y);
13428 void TestIfPlayerNextToCustomElement(int x, int y)
13430 struct XY *xy = xy_topdown;
13431 static int trigger_sides[4][2] =
13433 // center side border side
13434 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13435 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13436 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13437 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13441 if (!IS_PLAYER(x, y))
13444 struct PlayerInfo *player = PLAYERINFO(x, y);
13446 if (player->is_moving)
13449 for (i = 0; i < NUM_DIRECTIONS; i++)
13451 int xx = x + xy[i].x;
13452 int yy = y + xy[i].y;
13453 int border_side = trigger_sides[i][1];
13454 int border_element;
13456 if (!IN_LEV_FIELD(xx, yy))
13459 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13460 continue; // center and border element not connected
13462 border_element = Tile[xx][yy];
13464 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13465 player->index_bit, border_side);
13466 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13467 CE_PLAYER_NEXT_TO_X,
13468 player->index_bit, border_side);
13470 /* use player element that is initially defined in the level playfield,
13471 not the player element that corresponds to the runtime player number
13472 (example: a level that contains EL_PLAYER_3 as the only player would
13473 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13475 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13476 CE_NEXT_TO_X, border_side);
13480 void TestIfPlayerTouchesCustomElement(int x, int y)
13482 struct XY *xy = xy_topdown;
13483 static int trigger_sides[4][2] =
13485 // center side border side
13486 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13487 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13488 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13489 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13491 static int touch_dir[4] =
13493 MV_LEFT | MV_RIGHT,
13498 int center_element = Tile[x][y]; // should always be non-moving!
13501 for (i = 0; i < NUM_DIRECTIONS; i++)
13503 int xx = x + xy[i].x;
13504 int yy = y + xy[i].y;
13505 int center_side = trigger_sides[i][0];
13506 int border_side = trigger_sides[i][1];
13507 int border_element;
13509 if (!IN_LEV_FIELD(xx, yy))
13512 if (IS_PLAYER(x, y)) // player found at center element
13514 struct PlayerInfo *player = PLAYERINFO(x, y);
13516 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13517 border_element = Tile[xx][yy]; // may be moving!
13518 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13519 border_element = Tile[xx][yy];
13520 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13521 border_element = MovingOrBlocked2Element(xx, yy);
13523 continue; // center and border element do not touch
13525 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13526 player->index_bit, border_side);
13527 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13528 CE_PLAYER_TOUCHES_X,
13529 player->index_bit, border_side);
13532 /* use player element that is initially defined in the level playfield,
13533 not the player element that corresponds to the runtime player number
13534 (example: a level that contains EL_PLAYER_3 as the only player would
13535 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13536 int player_element = PLAYERINFO(x, y)->initial_element;
13538 // as element "X" is the player here, check opposite (center) side
13539 CheckElementChangeBySide(xx, yy, border_element, player_element,
13540 CE_TOUCHING_X, center_side);
13543 else if (IS_PLAYER(xx, yy)) // player found at border element
13545 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13547 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13549 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13550 continue; // center and border element do not touch
13553 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13554 player->index_bit, center_side);
13555 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13556 CE_PLAYER_TOUCHES_X,
13557 player->index_bit, center_side);
13560 /* use player element that is initially defined in the level playfield,
13561 not the player element that corresponds to the runtime player number
13562 (example: a level that contains EL_PLAYER_3 as the only player would
13563 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13564 int player_element = PLAYERINFO(xx, yy)->initial_element;
13566 // as element "X" is the player here, check opposite (border) side
13567 CheckElementChangeBySide(x, y, center_element, player_element,
13568 CE_TOUCHING_X, border_side);
13576 void TestIfElementNextToCustomElement(int x, int y)
13578 struct XY *xy = xy_topdown;
13579 static int trigger_sides[4][2] =
13581 // center side border side
13582 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13583 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13584 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13585 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13587 int center_element = Tile[x][y]; // should always be non-moving!
13590 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13593 for (i = 0; i < NUM_DIRECTIONS; i++)
13595 int xx = x + xy[i].x;
13596 int yy = y + xy[i].y;
13597 int border_side = trigger_sides[i][1];
13598 int border_element;
13600 if (!IN_LEV_FIELD(xx, yy))
13603 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13604 continue; // center and border element not connected
13606 border_element = Tile[xx][yy];
13608 // check for change of center element (but change it only once)
13609 if (CheckElementChangeBySide(x, y, center_element, border_element,
13610 CE_NEXT_TO_X, border_side))
13615 void TestIfElementTouchesCustomElement(int x, int y)
13617 struct XY *xy = xy_topdown;
13618 static int trigger_sides[4][2] =
13620 // center side border side
13621 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13622 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13623 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13624 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13626 static int touch_dir[4] =
13628 MV_LEFT | MV_RIGHT,
13633 boolean change_center_element = FALSE;
13634 int center_element = Tile[x][y]; // should always be non-moving!
13635 int border_element_old[NUM_DIRECTIONS];
13638 for (i = 0; i < NUM_DIRECTIONS; i++)
13640 int xx = x + xy[i].x;
13641 int yy = y + xy[i].y;
13642 int border_element;
13644 border_element_old[i] = -1;
13646 if (!IN_LEV_FIELD(xx, yy))
13649 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13650 border_element = Tile[xx][yy]; // may be moving!
13651 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13652 border_element = Tile[xx][yy];
13653 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13654 border_element = MovingOrBlocked2Element(xx, yy);
13656 continue; // center and border element do not touch
13658 border_element_old[i] = border_element;
13661 for (i = 0; i < NUM_DIRECTIONS; i++)
13663 int xx = x + xy[i].x;
13664 int yy = y + xy[i].y;
13665 int center_side = trigger_sides[i][0];
13666 int border_element = border_element_old[i];
13668 if (border_element == -1)
13671 // check for change of border element
13672 CheckElementChangeBySide(xx, yy, border_element, center_element,
13673 CE_TOUCHING_X, center_side);
13675 // (center element cannot be player, so we don't have to check this here)
13678 for (i = 0; i < NUM_DIRECTIONS; i++)
13680 int xx = x + xy[i].x;
13681 int yy = y + xy[i].y;
13682 int border_side = trigger_sides[i][1];
13683 int border_element = border_element_old[i];
13685 if (border_element == -1)
13688 // check for change of center element (but change it only once)
13689 if (!change_center_element)
13690 change_center_element =
13691 CheckElementChangeBySide(x, y, center_element, border_element,
13692 CE_TOUCHING_X, border_side);
13694 if (IS_PLAYER(xx, yy))
13696 /* use player element that is initially defined in the level playfield,
13697 not the player element that corresponds to the runtime player number
13698 (example: a level that contains EL_PLAYER_3 as the only player would
13699 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13700 int player_element = PLAYERINFO(xx, yy)->initial_element;
13702 // as element "X" is the player here, check opposite (border) side
13703 CheckElementChangeBySide(x, y, center_element, player_element,
13704 CE_TOUCHING_X, border_side);
13709 void TestIfElementHitsCustomElement(int x, int y, int direction)
13711 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13712 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13713 int hitx = x + dx, hity = y + dy;
13714 int hitting_element = Tile[x][y];
13715 int touched_element;
13717 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13720 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13721 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13723 if (IN_LEV_FIELD(hitx, hity))
13725 int opposite_direction = MV_DIR_OPPOSITE(direction);
13726 int hitting_side = direction;
13727 int touched_side = opposite_direction;
13728 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13729 MovDir[hitx][hity] != direction ||
13730 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13736 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13737 CE_HITTING_X, touched_side);
13739 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13740 CE_HIT_BY_X, hitting_side);
13742 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13743 CE_HIT_BY_SOMETHING, opposite_direction);
13745 if (IS_PLAYER(hitx, hity))
13747 /* use player element that is initially defined in the level playfield,
13748 not the player element that corresponds to the runtime player number
13749 (example: a level that contains EL_PLAYER_3 as the only player would
13750 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13751 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13753 CheckElementChangeBySide(x, y, hitting_element, player_element,
13754 CE_HITTING_X, touched_side);
13759 // "hitting something" is also true when hitting the playfield border
13760 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13761 CE_HITTING_SOMETHING, direction);
13764 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13766 int i, kill_x = -1, kill_y = -1;
13768 int bad_element = -1;
13769 struct XY *test_xy = xy_topdown;
13770 static int test_dir[4] =
13778 for (i = 0; i < NUM_DIRECTIONS; i++)
13780 int test_x, test_y, test_move_dir, test_element;
13782 test_x = good_x + test_xy[i].x;
13783 test_y = good_y + test_xy[i].y;
13785 if (!IN_LEV_FIELD(test_x, test_y))
13789 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13791 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13793 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13794 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13796 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13797 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13801 bad_element = test_element;
13807 if (kill_x != -1 || kill_y != -1)
13809 if (IS_PLAYER(good_x, good_y))
13811 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13813 if (player->shield_deadly_time_left > 0 &&
13814 !IS_INDESTRUCTIBLE(bad_element))
13815 Bang(kill_x, kill_y);
13816 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13817 KillPlayer(player);
13820 Bang(good_x, good_y);
13824 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13826 int i, kill_x = -1, kill_y = -1;
13827 int bad_element = Tile[bad_x][bad_y];
13828 struct XY *test_xy = xy_topdown;
13829 static int touch_dir[4] =
13831 MV_LEFT | MV_RIGHT,
13836 static int test_dir[4] =
13844 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13847 for (i = 0; i < NUM_DIRECTIONS; i++)
13849 int test_x, test_y, test_move_dir, test_element;
13851 test_x = bad_x + test_xy[i].x;
13852 test_y = bad_y + test_xy[i].y;
13854 if (!IN_LEV_FIELD(test_x, test_y))
13858 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13860 test_element = Tile[test_x][test_y];
13862 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13863 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13865 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13866 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13868 // good thing is player or penguin that does not move away
13869 if (IS_PLAYER(test_x, test_y))
13871 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13873 if (bad_element == EL_ROBOT && player->is_moving)
13874 continue; // robot does not kill player if he is moving
13876 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13878 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13879 continue; // center and border element do not touch
13887 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 TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13916 int bad_element = Tile[bad_x][bad_y];
13917 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13918 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13919 int test_x = bad_x + dx, test_y = bad_y + dy;
13920 int test_move_dir, test_element;
13921 int kill_x = -1, kill_y = -1;
13923 if (!IN_LEV_FIELD(test_x, test_y))
13927 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13929 test_element = Tile[test_x][test_y];
13931 if (test_move_dir != bad_move_dir)
13933 // good thing can be player or penguin that does not move away
13934 if (IS_PLAYER(test_x, test_y))
13936 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13938 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13939 player as being hit when he is moving towards the bad thing, because
13940 the "get hit by" condition would be lost after the player stops) */
13941 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13942 return; // player moves away from bad thing
13947 else if (test_element == EL_PENGUIN)
13954 if (kill_x != -1 || kill_y != -1)
13956 if (IS_PLAYER(kill_x, kill_y))
13958 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13960 if (player->shield_deadly_time_left > 0 &&
13961 !IS_INDESTRUCTIBLE(bad_element))
13962 Bang(bad_x, bad_y);
13963 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13964 KillPlayer(player);
13967 Bang(kill_x, kill_y);
13971 void TestIfPlayerTouchesBadThing(int x, int y)
13973 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13976 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13978 TestIfGoodThingHitsBadThing(x, y, move_dir);
13981 void TestIfBadThingTouchesPlayer(int x, int y)
13983 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13986 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13988 TestIfBadThingHitsGoodThing(x, y, move_dir);
13991 void TestIfFriendTouchesBadThing(int x, int y)
13993 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13996 void TestIfBadThingTouchesFriend(int x, int y)
13998 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14001 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14003 int i, kill_x = bad_x, kill_y = bad_y;
14004 struct XY *xy = xy_topdown;
14006 for (i = 0; i < NUM_DIRECTIONS; i++)
14010 x = bad_x + xy[i].x;
14011 y = bad_y + xy[i].y;
14012 if (!IN_LEV_FIELD(x, y))
14015 element = Tile[x][y];
14016 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14017 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14025 if (kill_x != bad_x || kill_y != bad_y)
14026 Bang(bad_x, bad_y);
14029 void KillPlayer(struct PlayerInfo *player)
14031 int jx = player->jx, jy = player->jy;
14033 if (!player->active)
14037 Debug("game:playing:KillPlayer",
14038 "0: killed == %d, active == %d, reanimated == %d",
14039 player->killed, player->active, player->reanimated);
14042 /* the following code was introduced to prevent an infinite loop when calling
14044 -> CheckTriggeredElementChangeExt()
14045 -> ExecuteCustomElementAction()
14047 -> (infinitely repeating the above sequence of function calls)
14048 which occurs when killing the player while having a CE with the setting
14049 "kill player X when explosion of <player X>"; the solution using a new
14050 field "player->killed" was chosen for backwards compatibility, although
14051 clever use of the fields "player->active" etc. would probably also work */
14053 if (player->killed)
14057 player->killed = TRUE;
14059 // remove accessible field at the player's position
14060 RemoveField(jx, jy);
14062 // deactivate shield (else Bang()/Explode() would not work right)
14063 player->shield_normal_time_left = 0;
14064 player->shield_deadly_time_left = 0;
14067 Debug("game:playing:KillPlayer",
14068 "1: killed == %d, active == %d, reanimated == %d",
14069 player->killed, player->active, player->reanimated);
14075 Debug("game:playing:KillPlayer",
14076 "2: killed == %d, active == %d, reanimated == %d",
14077 player->killed, player->active, player->reanimated);
14080 if (player->reanimated) // killed player may have been reanimated
14081 player->killed = player->reanimated = FALSE;
14083 BuryPlayer(player);
14086 static void KillPlayerUnlessEnemyProtected(int x, int y)
14088 if (!PLAYER_ENEMY_PROTECTED(x, y))
14089 KillPlayer(PLAYERINFO(x, y));
14092 static void KillPlayerUnlessExplosionProtected(int x, int y)
14094 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14095 KillPlayer(PLAYERINFO(x, y));
14098 void BuryPlayer(struct PlayerInfo *player)
14100 int jx = player->jx, jy = player->jy;
14102 if (!player->active)
14105 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14107 RemovePlayer(player);
14109 player->buried = TRUE;
14111 if (game.all_players_gone)
14112 game.GameOver = TRUE;
14115 void RemovePlayer(struct PlayerInfo *player)
14117 int jx = player->jx, jy = player->jy;
14118 int i, found = FALSE;
14120 player->present = FALSE;
14121 player->active = FALSE;
14123 // required for some CE actions (even if the player is not active anymore)
14124 player->MovPos = 0;
14126 if (!ExplodeField[jx][jy])
14127 StorePlayer[jx][jy] = 0;
14129 if (player->is_moving)
14130 TEST_DrawLevelField(player->last_jx, player->last_jy);
14132 for (i = 0; i < MAX_PLAYERS; i++)
14133 if (stored_player[i].active)
14138 game.all_players_gone = TRUE;
14139 game.GameOver = TRUE;
14142 game.exit_x = game.robot_wheel_x = jx;
14143 game.exit_y = game.robot_wheel_y = jy;
14146 void ExitPlayer(struct PlayerInfo *player)
14148 DrawPlayer(player); // needed here only to cleanup last field
14149 RemovePlayer(player);
14151 if (game.players_still_needed > 0)
14152 game.players_still_needed--;
14155 static void SetFieldForSnapping(int x, int y, int element, int direction,
14156 int player_index_bit)
14158 struct ElementInfo *ei = &element_info[element];
14159 int direction_bit = MV_DIR_TO_BIT(direction);
14160 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14161 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14162 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14164 Tile[x][y] = EL_ELEMENT_SNAPPING;
14165 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14166 MovDir[x][y] = direction;
14167 Store[x][y] = element;
14168 Store2[x][y] = player_index_bit;
14170 ResetGfxAnimation(x, y);
14172 GfxElement[x][y] = element;
14173 GfxAction[x][y] = action;
14174 GfxDir[x][y] = direction;
14175 GfxFrame[x][y] = -1;
14178 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14179 int player_index_bit)
14181 TestIfElementTouchesCustomElement(x, y); // for empty space
14183 if (level.finish_dig_collect)
14185 int dig_side = MV_DIR_OPPOSITE(direction);
14186 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14187 CE_PLAYER_COLLECTS_X);
14189 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14190 player_index_bit, dig_side);
14191 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14192 player_index_bit, dig_side);
14197 =============================================================================
14198 checkDiagonalPushing()
14199 -----------------------------------------------------------------------------
14200 check if diagonal input device direction results in pushing of object
14201 (by checking if the alternative direction is walkable, diggable, ...)
14202 =============================================================================
14205 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14206 int x, int y, int real_dx, int real_dy)
14208 int jx, jy, dx, dy, xx, yy;
14210 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14213 // diagonal direction: check alternative direction
14218 xx = jx + (dx == 0 ? real_dx : 0);
14219 yy = jy + (dy == 0 ? real_dy : 0);
14221 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14225 =============================================================================
14227 -----------------------------------------------------------------------------
14228 x, y: field next to player (non-diagonal) to try to dig to
14229 real_dx, real_dy: direction as read from input device (can be diagonal)
14230 =============================================================================
14233 static int DigField(struct PlayerInfo *player,
14234 int oldx, int oldy, int x, int y,
14235 int real_dx, int real_dy, int mode)
14237 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14238 boolean player_was_pushing = player->is_pushing;
14239 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14240 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14241 int jx = oldx, jy = oldy;
14242 int dx = x - jx, dy = y - jy;
14243 int nextx = x + dx, nexty = y + dy;
14244 int move_direction = (dx == -1 ? MV_LEFT :
14245 dx == +1 ? MV_RIGHT :
14247 dy == +1 ? MV_DOWN : MV_NONE);
14248 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14249 int dig_side = MV_DIR_OPPOSITE(move_direction);
14250 int old_element = Tile[jx][jy];
14251 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14254 if (is_player) // function can also be called by EL_PENGUIN
14256 if (player->MovPos == 0)
14258 player->is_digging = FALSE;
14259 player->is_collecting = FALSE;
14262 if (player->MovPos == 0) // last pushing move finished
14263 player->is_pushing = FALSE;
14265 if (mode == DF_NO_PUSH) // player just stopped pushing
14267 player->is_switching = FALSE;
14268 player->push_delay = -1;
14270 return MP_NO_ACTION;
14273 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14274 old_element = Back[jx][jy];
14276 // in case of element dropped at player position, check background
14277 else if (Back[jx][jy] != EL_EMPTY &&
14278 game.engine_version >= VERSION_IDENT(2,2,0,0))
14279 old_element = Back[jx][jy];
14281 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14282 return MP_NO_ACTION; // field has no opening in this direction
14284 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14285 return MP_NO_ACTION; // field has no opening in this direction
14287 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14291 Tile[jx][jy] = player->artwork_element;
14292 InitMovingField(jx, jy, MV_DOWN);
14293 Store[jx][jy] = EL_ACID;
14294 ContinueMoving(jx, jy);
14295 BuryPlayer(player);
14297 return MP_DONT_RUN_INTO;
14300 if (player_can_move && DONT_RUN_INTO(element))
14302 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14304 return MP_DONT_RUN_INTO;
14307 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14308 return MP_NO_ACTION;
14310 collect_count = element_info[element].collect_count_initial;
14312 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14313 return MP_NO_ACTION;
14315 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14316 player_can_move = player_can_move_or_snap;
14318 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14319 game.engine_version >= VERSION_IDENT(2,2,0,0))
14321 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14322 player->index_bit, dig_side);
14323 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14324 player->index_bit, dig_side);
14326 if (element == EL_DC_LANDMINE)
14329 if (Tile[x][y] != element) // field changed by snapping
14332 return MP_NO_ACTION;
14335 if (player->gravity && is_player && !player->is_auto_moving &&
14336 canFallDown(player) && move_direction != MV_DOWN &&
14337 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14338 return MP_NO_ACTION; // player cannot walk here due to gravity
14340 if (player_can_move &&
14341 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14343 int sound_element = SND_ELEMENT(element);
14344 int sound_action = ACTION_WALKING;
14346 if (IS_RND_GATE(element))
14348 if (!player->key[RND_GATE_NR(element)])
14349 return MP_NO_ACTION;
14351 else if (IS_RND_GATE_GRAY(element))
14353 if (!player->key[RND_GATE_GRAY_NR(element)])
14354 return MP_NO_ACTION;
14356 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14358 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14359 return MP_NO_ACTION;
14361 else if (element == EL_EXIT_OPEN ||
14362 element == EL_EM_EXIT_OPEN ||
14363 element == EL_EM_EXIT_OPENING ||
14364 element == EL_STEEL_EXIT_OPEN ||
14365 element == EL_EM_STEEL_EXIT_OPEN ||
14366 element == EL_EM_STEEL_EXIT_OPENING ||
14367 element == EL_SP_EXIT_OPEN ||
14368 element == EL_SP_EXIT_OPENING)
14370 sound_action = ACTION_PASSING; // player is passing exit
14372 else if (element == EL_EMPTY)
14374 sound_action = ACTION_MOVING; // nothing to walk on
14377 // play sound from background or player, whatever is available
14378 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14379 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14381 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14383 else if (player_can_move &&
14384 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14386 if (!ACCESS_FROM(element, opposite_direction))
14387 return MP_NO_ACTION; // field not accessible from this direction
14389 if (CAN_MOVE(element)) // only fixed elements can be passed!
14390 return MP_NO_ACTION;
14392 if (IS_EM_GATE(element))
14394 if (!player->key[EM_GATE_NR(element)])
14395 return MP_NO_ACTION;
14397 else if (IS_EM_GATE_GRAY(element))
14399 if (!player->key[EM_GATE_GRAY_NR(element)])
14400 return MP_NO_ACTION;
14402 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14404 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14405 return MP_NO_ACTION;
14407 else if (IS_EMC_GATE(element))
14409 if (!player->key[EMC_GATE_NR(element)])
14410 return MP_NO_ACTION;
14412 else if (IS_EMC_GATE_GRAY(element))
14414 if (!player->key[EMC_GATE_GRAY_NR(element)])
14415 return MP_NO_ACTION;
14417 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14419 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14420 return MP_NO_ACTION;
14422 else if (element == EL_DC_GATE_WHITE ||
14423 element == EL_DC_GATE_WHITE_GRAY ||
14424 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14426 if (player->num_white_keys == 0)
14427 return MP_NO_ACTION;
14429 player->num_white_keys--;
14431 else if (IS_SP_PORT(element))
14433 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14434 element == EL_SP_GRAVITY_PORT_RIGHT ||
14435 element == EL_SP_GRAVITY_PORT_UP ||
14436 element == EL_SP_GRAVITY_PORT_DOWN)
14437 player->gravity = !player->gravity;
14438 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14439 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14440 element == EL_SP_GRAVITY_ON_PORT_UP ||
14441 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14442 player->gravity = TRUE;
14443 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14444 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14445 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14446 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14447 player->gravity = FALSE;
14450 // automatically move to the next field with double speed
14451 player->programmed_action = move_direction;
14453 if (player->move_delay_reset_counter == 0)
14455 player->move_delay_reset_counter = 2; // two double speed steps
14457 DOUBLE_PLAYER_SPEED(player);
14460 PlayLevelSoundAction(x, y, ACTION_PASSING);
14462 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14466 if (mode != DF_SNAP)
14468 GfxElement[x][y] = GFX_ELEMENT(element);
14469 player->is_digging = TRUE;
14472 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14474 // use old behaviour for old levels (digging)
14475 if (!level.finish_dig_collect)
14477 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14478 player->index_bit, dig_side);
14480 // if digging triggered player relocation, finish digging tile
14481 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14482 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14485 if (mode == DF_SNAP)
14487 if (level.block_snap_field)
14488 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14490 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14492 // use old behaviour for old levels (snapping)
14493 if (!level.finish_dig_collect)
14494 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14495 player->index_bit, dig_side);
14498 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14502 if (is_player && mode != DF_SNAP)
14504 GfxElement[x][y] = element;
14505 player->is_collecting = TRUE;
14508 if (element == EL_SPEED_PILL)
14510 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14512 else if (element == EL_EXTRA_TIME && level.time > 0)
14514 TimeLeft += level.extra_time;
14516 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14518 DisplayGameControlValues();
14520 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14522 int shield_time = (element == EL_SHIELD_DEADLY ?
14523 level.shield_deadly_time :
14524 level.shield_normal_time);
14526 player->shield_normal_time_left += shield_time;
14527 if (element == EL_SHIELD_DEADLY)
14528 player->shield_deadly_time_left += shield_time;
14530 else if (element == EL_DYNAMITE ||
14531 element == EL_EM_DYNAMITE ||
14532 element == EL_SP_DISK_RED)
14534 if (player->inventory_size < MAX_INVENTORY_SIZE)
14535 player->inventory_element[player->inventory_size++] = element;
14537 DrawGameDoorValues();
14539 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14541 player->dynabomb_count++;
14542 player->dynabombs_left++;
14544 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14546 player->dynabomb_size++;
14548 else if (element == EL_DYNABOMB_INCREASE_POWER)
14550 player->dynabomb_xl = TRUE;
14552 else if (IS_KEY(element))
14554 player->key[KEY_NR(element)] = TRUE;
14556 DrawGameDoorValues();
14558 else if (element == EL_DC_KEY_WHITE)
14560 player->num_white_keys++;
14562 // display white keys?
14563 // DrawGameDoorValues();
14565 else if (IS_ENVELOPE(element))
14567 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14569 if (!wait_for_snapping)
14570 player->show_envelope = element;
14572 else if (element == EL_EMC_LENSES)
14574 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14576 RedrawAllInvisibleElementsForLenses();
14578 else if (element == EL_EMC_MAGNIFIER)
14580 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14582 RedrawAllInvisibleElementsForMagnifier();
14584 else if (IS_DROPPABLE(element) ||
14585 IS_THROWABLE(element)) // can be collected and dropped
14589 if (collect_count == 0)
14590 player->inventory_infinite_element = element;
14592 for (i = 0; i < collect_count; i++)
14593 if (player->inventory_size < MAX_INVENTORY_SIZE)
14594 player->inventory_element[player->inventory_size++] = element;
14596 DrawGameDoorValues();
14598 else if (collect_count > 0)
14600 game.gems_still_needed -= collect_count;
14601 if (game.gems_still_needed < 0)
14602 game.gems_still_needed = 0;
14604 game.snapshot.collected_item = TRUE;
14606 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14608 DisplayGameControlValues();
14611 RaiseScoreElement(element);
14612 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14614 // use old behaviour for old levels (collecting)
14615 if (!level.finish_dig_collect && is_player)
14617 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14618 player->index_bit, dig_side);
14620 // if collecting triggered player relocation, finish collecting tile
14621 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14622 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14625 if (mode == DF_SNAP)
14627 if (level.block_snap_field)
14628 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14630 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14632 // use old behaviour for old levels (snapping)
14633 if (!level.finish_dig_collect)
14634 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14635 player->index_bit, dig_side);
14638 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14640 if (mode == DF_SNAP && element != EL_BD_ROCK)
14641 return MP_NO_ACTION;
14643 if (CAN_FALL(element) && dy)
14644 return MP_NO_ACTION;
14646 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14647 !(element == EL_SPRING && level.use_spring_bug))
14648 return MP_NO_ACTION;
14650 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14651 ((move_direction & MV_VERTICAL &&
14652 ((element_info[element].move_pattern & MV_LEFT &&
14653 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14654 (element_info[element].move_pattern & MV_RIGHT &&
14655 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14656 (move_direction & MV_HORIZONTAL &&
14657 ((element_info[element].move_pattern & MV_UP &&
14658 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14659 (element_info[element].move_pattern & MV_DOWN &&
14660 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14661 return MP_NO_ACTION;
14663 // do not push elements already moving away faster than player
14664 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14665 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14666 return MP_NO_ACTION;
14668 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14670 if (player->push_delay_value == -1 || !player_was_pushing)
14671 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14673 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14675 if (player->push_delay_value == -1)
14676 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14678 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14680 if (!player->is_pushing)
14681 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14684 player->is_pushing = TRUE;
14685 player->is_active = TRUE;
14687 if (!(IN_LEV_FIELD(nextx, nexty) &&
14688 (IS_FREE(nextx, nexty) ||
14689 (IS_SB_ELEMENT(element) &&
14690 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14691 (IS_CUSTOM_ELEMENT(element) &&
14692 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14693 return MP_NO_ACTION;
14695 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14696 return MP_NO_ACTION;
14698 if (player->push_delay == -1) // new pushing; restart delay
14699 player->push_delay = 0;
14701 if (player->push_delay < player->push_delay_value &&
14702 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14703 element != EL_SPRING && element != EL_BALLOON)
14705 // make sure that there is no move delay before next try to push
14706 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14707 player->move_delay = 0;
14709 return MP_NO_ACTION;
14712 if (IS_CUSTOM_ELEMENT(element) &&
14713 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14715 if (!DigFieldByCE(nextx, nexty, element))
14716 return MP_NO_ACTION;
14719 if (IS_SB_ELEMENT(element))
14721 boolean sokoban_task_solved = FALSE;
14723 if (element == EL_SOKOBAN_FIELD_FULL)
14725 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14727 IncrementSokobanFieldsNeeded();
14728 IncrementSokobanObjectsNeeded();
14731 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14733 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14735 DecrementSokobanFieldsNeeded();
14736 DecrementSokobanObjectsNeeded();
14738 // sokoban object was pushed from empty field to sokoban field
14739 if (Back[x][y] == EL_EMPTY)
14740 sokoban_task_solved = TRUE;
14743 Tile[x][y] = EL_SOKOBAN_OBJECT;
14745 if (Back[x][y] == Back[nextx][nexty])
14746 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14747 else if (Back[x][y] != 0)
14748 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14751 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14754 if (sokoban_task_solved &&
14755 game.sokoban_fields_still_needed == 0 &&
14756 game.sokoban_objects_still_needed == 0 &&
14757 level.auto_exit_sokoban)
14759 game.players_still_needed = 0;
14763 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14767 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14769 InitMovingField(x, y, move_direction);
14770 GfxAction[x][y] = ACTION_PUSHING;
14772 if (mode == DF_SNAP)
14773 ContinueMoving(x, y);
14775 MovPos[x][y] = (dx != 0 ? dx : dy);
14777 Pushed[x][y] = TRUE;
14778 Pushed[nextx][nexty] = TRUE;
14780 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14781 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14783 player->push_delay_value = -1; // get new value later
14785 // check for element change _after_ element has been pushed
14786 if (game.use_change_when_pushing_bug)
14788 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14789 player->index_bit, dig_side);
14790 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14791 player->index_bit, dig_side);
14794 else if (IS_SWITCHABLE(element))
14796 if (PLAYER_SWITCHING(player, x, y))
14798 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14799 player->index_bit, dig_side);
14804 player->is_switching = TRUE;
14805 player->switch_x = x;
14806 player->switch_y = y;
14808 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14810 if (element == EL_ROBOT_WHEEL)
14812 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14814 game.robot_wheel_x = x;
14815 game.robot_wheel_y = y;
14816 game.robot_wheel_active = TRUE;
14818 TEST_DrawLevelField(x, y);
14820 else if (element == EL_SP_TERMINAL)
14824 SCAN_PLAYFIELD(xx, yy)
14826 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14830 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14832 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14834 ResetGfxAnimation(xx, yy);
14835 TEST_DrawLevelField(xx, yy);
14839 else if (IS_BELT_SWITCH(element))
14841 ToggleBeltSwitch(x, y);
14843 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14844 element == EL_SWITCHGATE_SWITCH_DOWN ||
14845 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14846 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14848 ToggleSwitchgateSwitch();
14850 else if (element == EL_LIGHT_SWITCH ||
14851 element == EL_LIGHT_SWITCH_ACTIVE)
14853 ToggleLightSwitch(x, y);
14855 else if (element == EL_TIMEGATE_SWITCH ||
14856 element == EL_DC_TIMEGATE_SWITCH)
14858 ActivateTimegateSwitch(x, y);
14860 else if (element == EL_BALLOON_SWITCH_LEFT ||
14861 element == EL_BALLOON_SWITCH_RIGHT ||
14862 element == EL_BALLOON_SWITCH_UP ||
14863 element == EL_BALLOON_SWITCH_DOWN ||
14864 element == EL_BALLOON_SWITCH_NONE ||
14865 element == EL_BALLOON_SWITCH_ANY)
14867 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14868 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14869 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14870 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14871 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14874 else if (element == EL_LAMP)
14876 Tile[x][y] = EL_LAMP_ACTIVE;
14877 game.lights_still_needed--;
14879 ResetGfxAnimation(x, y);
14880 TEST_DrawLevelField(x, y);
14882 else if (element == EL_TIME_ORB_FULL)
14884 Tile[x][y] = EL_TIME_ORB_EMPTY;
14886 if (level.time > 0 || level.use_time_orb_bug)
14888 TimeLeft += level.time_orb_time;
14889 game.no_level_time_limit = FALSE;
14891 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14893 DisplayGameControlValues();
14896 ResetGfxAnimation(x, y);
14897 TEST_DrawLevelField(x, y);
14899 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14900 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14904 game.ball_active = !game.ball_active;
14906 SCAN_PLAYFIELD(xx, yy)
14908 int e = Tile[xx][yy];
14910 if (game.ball_active)
14912 if (e == EL_EMC_MAGIC_BALL)
14913 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14914 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14915 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14919 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14920 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14921 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14922 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14927 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14928 player->index_bit, dig_side);
14930 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14931 player->index_bit, dig_side);
14933 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14934 player->index_bit, dig_side);
14940 if (!PLAYER_SWITCHING(player, x, y))
14942 player->is_switching = TRUE;
14943 player->switch_x = x;
14944 player->switch_y = y;
14946 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14947 player->index_bit, dig_side);
14948 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14949 player->index_bit, dig_side);
14951 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14952 player->index_bit, dig_side);
14953 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14954 player->index_bit, dig_side);
14957 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14958 player->index_bit, dig_side);
14959 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14960 player->index_bit, dig_side);
14962 return MP_NO_ACTION;
14965 player->push_delay = -1;
14967 if (is_player) // function can also be called by EL_PENGUIN
14969 if (Tile[x][y] != element) // really digged/collected something
14971 player->is_collecting = !player->is_digging;
14972 player->is_active = TRUE;
14974 player->last_removed_element = element;
14981 static boolean DigFieldByCE(int x, int y, int digging_element)
14983 int element = Tile[x][y];
14985 if (!IS_FREE(x, y))
14987 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14988 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14991 // no element can dig solid indestructible elements
14992 if (IS_INDESTRUCTIBLE(element) &&
14993 !IS_DIGGABLE(element) &&
14994 !IS_COLLECTIBLE(element))
14997 if (AmoebaNr[x][y] &&
14998 (element == EL_AMOEBA_FULL ||
14999 element == EL_BD_AMOEBA ||
15000 element == EL_AMOEBA_GROWING))
15002 AmoebaCnt[AmoebaNr[x][y]]--;
15003 AmoebaCnt2[AmoebaNr[x][y]]--;
15006 if (IS_MOVING(x, y))
15007 RemoveMovingField(x, y);
15011 TEST_DrawLevelField(x, y);
15014 // if digged element was about to explode, prevent the explosion
15015 ExplodeField[x][y] = EX_TYPE_NONE;
15017 PlayLevelSoundAction(x, y, action);
15020 Store[x][y] = EL_EMPTY;
15022 // this makes it possible to leave the removed element again
15023 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15024 Store[x][y] = element;
15029 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15031 int jx = player->jx, jy = player->jy;
15032 int x = jx + dx, y = jy + dy;
15033 int snap_direction = (dx == -1 ? MV_LEFT :
15034 dx == +1 ? MV_RIGHT :
15036 dy == +1 ? MV_DOWN : MV_NONE);
15037 boolean can_continue_snapping = (level.continuous_snapping &&
15038 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15040 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15043 if (!player->active || !IN_LEV_FIELD(x, y))
15051 if (player->MovPos == 0)
15052 player->is_pushing = FALSE;
15054 player->is_snapping = FALSE;
15056 if (player->MovPos == 0)
15058 player->is_moving = FALSE;
15059 player->is_digging = FALSE;
15060 player->is_collecting = FALSE;
15066 // prevent snapping with already pressed snap key when not allowed
15067 if (player->is_snapping && !can_continue_snapping)
15070 player->MovDir = snap_direction;
15072 if (player->MovPos == 0)
15074 player->is_moving = FALSE;
15075 player->is_digging = FALSE;
15076 player->is_collecting = FALSE;
15079 player->is_dropping = FALSE;
15080 player->is_dropping_pressed = FALSE;
15081 player->drop_pressed_delay = 0;
15083 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15086 player->is_snapping = TRUE;
15087 player->is_active = TRUE;
15089 if (player->MovPos == 0)
15091 player->is_moving = FALSE;
15092 player->is_digging = FALSE;
15093 player->is_collecting = FALSE;
15096 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15097 TEST_DrawLevelField(player->last_jx, player->last_jy);
15099 TEST_DrawLevelField(x, y);
15104 static boolean DropElement(struct PlayerInfo *player)
15106 int old_element, new_element;
15107 int dropx = player->jx, dropy = player->jy;
15108 int drop_direction = player->MovDir;
15109 int drop_side = drop_direction;
15110 int drop_element = get_next_dropped_element(player);
15112 /* do not drop an element on top of another element; when holding drop key
15113 pressed without moving, dropped element must move away before the next
15114 element can be dropped (this is especially important if the next element
15115 is dynamite, which can be placed on background for historical reasons) */
15116 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15119 if (IS_THROWABLE(drop_element))
15121 dropx += GET_DX_FROM_DIR(drop_direction);
15122 dropy += GET_DY_FROM_DIR(drop_direction);
15124 if (!IN_LEV_FIELD(dropx, dropy))
15128 old_element = Tile[dropx][dropy]; // old element at dropping position
15129 new_element = drop_element; // default: no change when dropping
15131 // check if player is active, not moving and ready to drop
15132 if (!player->active || player->MovPos || player->drop_delay > 0)
15135 // check if player has anything that can be dropped
15136 if (new_element == EL_UNDEFINED)
15139 // only set if player has anything that can be dropped
15140 player->is_dropping_pressed = TRUE;
15142 // check if drop key was pressed long enough for EM style dynamite
15143 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15146 // check if anything can be dropped at the current position
15147 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15150 // collected custom elements can only be dropped on empty fields
15151 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15154 if (old_element != EL_EMPTY)
15155 Back[dropx][dropy] = old_element; // store old element on this field
15157 ResetGfxAnimation(dropx, dropy);
15158 ResetRandomAnimationValue(dropx, dropy);
15160 if (player->inventory_size > 0 ||
15161 player->inventory_infinite_element != EL_UNDEFINED)
15163 if (player->inventory_size > 0)
15165 player->inventory_size--;
15167 DrawGameDoorValues();
15169 if (new_element == EL_DYNAMITE)
15170 new_element = EL_DYNAMITE_ACTIVE;
15171 else if (new_element == EL_EM_DYNAMITE)
15172 new_element = EL_EM_DYNAMITE_ACTIVE;
15173 else if (new_element == EL_SP_DISK_RED)
15174 new_element = EL_SP_DISK_RED_ACTIVE;
15177 Tile[dropx][dropy] = new_element;
15179 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15180 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15181 el2img(Tile[dropx][dropy]), 0);
15183 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15185 // needed if previous element just changed to "empty" in the last frame
15186 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15188 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15189 player->index_bit, drop_side);
15190 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15192 player->index_bit, drop_side);
15194 TestIfElementTouchesCustomElement(dropx, dropy);
15196 else // player is dropping a dyna bomb
15198 player->dynabombs_left--;
15200 Tile[dropx][dropy] = new_element;
15202 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15203 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15204 el2img(Tile[dropx][dropy]), 0);
15206 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15209 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15210 InitField_WithBug1(dropx, dropy, FALSE);
15212 new_element = Tile[dropx][dropy]; // element might have changed
15214 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15215 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15217 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15218 MovDir[dropx][dropy] = drop_direction;
15220 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15222 // do not cause impact style collision by dropping elements that can fall
15223 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15226 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15227 player->is_dropping = TRUE;
15229 player->drop_pressed_delay = 0;
15230 player->is_dropping_pressed = FALSE;
15232 player->drop_x = dropx;
15233 player->drop_y = dropy;
15238 // ----------------------------------------------------------------------------
15239 // game sound playing functions
15240 // ----------------------------------------------------------------------------
15242 static int *loop_sound_frame = NULL;
15243 static int *loop_sound_volume = NULL;
15245 void InitPlayLevelSound(void)
15247 int num_sounds = getSoundListSize();
15249 checked_free(loop_sound_frame);
15250 checked_free(loop_sound_volume);
15252 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15253 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15256 static void PlayLevelSound(int x, int y, int nr)
15258 int sx = SCREENX(x), sy = SCREENY(y);
15259 int volume, stereo_position;
15260 int max_distance = 8;
15261 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15263 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15264 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15267 if (!IN_LEV_FIELD(x, y) ||
15268 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15269 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15272 volume = SOUND_MAX_VOLUME;
15274 if (!IN_SCR_FIELD(sx, sy))
15276 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15277 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15279 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15282 stereo_position = (SOUND_MAX_LEFT +
15283 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15284 (SCR_FIELDX + 2 * max_distance));
15286 if (IS_LOOP_SOUND(nr))
15288 /* This assures that quieter loop sounds do not overwrite louder ones,
15289 while restarting sound volume comparison with each new game frame. */
15291 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15294 loop_sound_volume[nr] = volume;
15295 loop_sound_frame[nr] = FrameCounter;
15298 PlaySoundExt(nr, volume, stereo_position, type);
15301 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15303 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15304 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15305 y < LEVELY(BY1) ? LEVELY(BY1) :
15306 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15310 static void PlayLevelSoundAction(int x, int y, int action)
15312 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15315 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15317 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15319 if (sound_effect != SND_UNDEFINED)
15320 PlayLevelSound(x, y, sound_effect);
15323 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15326 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15328 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15329 PlayLevelSound(x, y, sound_effect);
15332 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15334 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15336 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15337 PlayLevelSound(x, y, sound_effect);
15340 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15342 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15344 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15345 StopSound(sound_effect);
15348 static int getLevelMusicNr(void)
15350 int level_pos = level_nr - leveldir_current->first_level;
15352 if (levelset.music[level_nr] != MUS_UNDEFINED)
15353 return levelset.music[level_nr]; // from config file
15355 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15358 static void FadeLevelSounds(void)
15363 static void FadeLevelMusic(void)
15365 int music_nr = getLevelMusicNr();
15366 char *curr_music = getCurrentlyPlayingMusicFilename();
15367 char *next_music = getMusicInfoEntryFilename(music_nr);
15369 if (!strEqual(curr_music, next_music))
15373 void FadeLevelSoundsAndMusic(void)
15379 static void PlayLevelMusic(void)
15381 int music_nr = getLevelMusicNr();
15382 char *curr_music = getCurrentlyPlayingMusicFilename();
15383 char *next_music = getMusicInfoEntryFilename(music_nr);
15385 if (!strEqual(curr_music, next_music))
15386 PlayMusicLoop(music_nr);
15389 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15391 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15393 int x = xx - offset;
15394 int y = yy - offset;
15399 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15403 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15407 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15411 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15415 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15419 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15423 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15426 case SOUND_android_clone:
15427 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15430 case SOUND_android_move:
15431 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15435 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15439 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15443 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15446 case SOUND_eater_eat:
15447 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15451 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15454 case SOUND_collect:
15455 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15458 case SOUND_diamond:
15459 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15463 // !!! CHECK THIS !!!
15465 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15467 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15471 case SOUND_wonderfall:
15472 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15476 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15480 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15484 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15488 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15492 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15496 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15500 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15504 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15507 case SOUND_exit_open:
15508 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15511 case SOUND_exit_leave:
15512 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15515 case SOUND_dynamite:
15516 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15520 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15524 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15528 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15532 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15536 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15540 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15544 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15549 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15551 int element = map_element_SP_to_RND(element_sp);
15552 int action = map_action_SP_to_RND(action_sp);
15553 int offset = (setup.sp_show_border_elements ? 0 : 1);
15554 int x = xx - offset;
15555 int y = yy - offset;
15557 PlayLevelSoundElementAction(x, y, element, action);
15560 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15562 int element = map_element_MM_to_RND(element_mm);
15563 int action = map_action_MM_to_RND(action_mm);
15565 int x = xx - offset;
15566 int y = yy - offset;
15568 if (!IS_MM_ELEMENT(element))
15569 element = EL_MM_DEFAULT;
15571 PlayLevelSoundElementAction(x, y, element, action);
15574 void PlaySound_MM(int sound_mm)
15576 int sound = map_sound_MM_to_RND(sound_mm);
15578 if (sound == SND_UNDEFINED)
15584 void PlaySoundLoop_MM(int sound_mm)
15586 int sound = map_sound_MM_to_RND(sound_mm);
15588 if (sound == SND_UNDEFINED)
15591 PlaySoundLoop(sound);
15594 void StopSound_MM(int sound_mm)
15596 int sound = map_sound_MM_to_RND(sound_mm);
15598 if (sound == SND_UNDEFINED)
15604 void RaiseScore(int value)
15606 game.score += value;
15608 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15610 DisplayGameControlValues();
15613 void RaiseScoreElement(int element)
15618 case EL_BD_DIAMOND:
15619 case EL_EMERALD_YELLOW:
15620 case EL_EMERALD_RED:
15621 case EL_EMERALD_PURPLE:
15622 case EL_SP_INFOTRON:
15623 RaiseScore(level.score[SC_EMERALD]);
15626 RaiseScore(level.score[SC_DIAMOND]);
15629 RaiseScore(level.score[SC_CRYSTAL]);
15632 RaiseScore(level.score[SC_PEARL]);
15635 case EL_BD_BUTTERFLY:
15636 case EL_SP_ELECTRON:
15637 RaiseScore(level.score[SC_BUG]);
15640 case EL_BD_FIREFLY:
15641 case EL_SP_SNIKSNAK:
15642 RaiseScore(level.score[SC_SPACESHIP]);
15645 case EL_DARK_YAMYAM:
15646 RaiseScore(level.score[SC_YAMYAM]);
15649 RaiseScore(level.score[SC_ROBOT]);
15652 RaiseScore(level.score[SC_PACMAN]);
15655 RaiseScore(level.score[SC_NUT]);
15658 case EL_EM_DYNAMITE:
15659 case EL_SP_DISK_RED:
15660 case EL_DYNABOMB_INCREASE_NUMBER:
15661 case EL_DYNABOMB_INCREASE_SIZE:
15662 case EL_DYNABOMB_INCREASE_POWER:
15663 RaiseScore(level.score[SC_DYNAMITE]);
15665 case EL_SHIELD_NORMAL:
15666 case EL_SHIELD_DEADLY:
15667 RaiseScore(level.score[SC_SHIELD]);
15669 case EL_EXTRA_TIME:
15670 RaiseScore(level.extra_time_score);
15684 case EL_DC_KEY_WHITE:
15685 RaiseScore(level.score[SC_KEY]);
15688 RaiseScore(element_info[element].collect_score);
15693 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15695 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15699 // prevent short reactivation of overlay buttons while closing door
15700 SetOverlayActive(FALSE);
15701 UnmapGameButtons();
15703 // door may still be open due to skipped or envelope style request
15704 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
15707 if (network.enabled)
15709 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15714 FadeSkipNextFadeIn();
15716 SetGameStatus(GAME_MODE_MAIN);
15721 else // continue playing the game
15723 if (tape.playing && tape.deactivate_display)
15724 TapeDeactivateDisplayOff(TRUE);
15726 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15728 if (tape.playing && tape.deactivate_display)
15729 TapeDeactivateDisplayOn();
15733 void RequestQuitGame(boolean escape_key_pressed)
15735 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15736 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15737 level_editor_test_game);
15738 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15739 quick_quit || score_info_tape_play);
15741 RequestQuitGameExt(skip_request, quick_quit,
15742 "Do you really want to quit the game?");
15745 static char *getRestartGameMessage(void)
15747 boolean play_again = hasStartedNetworkGame();
15748 static char message[MAX_OUTPUT_LINESIZE];
15749 char *game_over_text = "Game over!";
15750 char *play_again_text = " Play it again?";
15752 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
15753 game_mm.game_over_message != NULL)
15754 game_over_text = game_mm.game_over_message;
15756 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
15757 (play_again ? play_again_text : ""));
15762 static void RequestRestartGame(void)
15764 char *message = getRestartGameMessage();
15765 boolean has_started_game = hasStartedNetworkGame();
15766 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15767 int door_state = DOOR_CLOSE_1;
15769 if (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game)
15771 CloseDoor(door_state);
15773 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15777 // if game was invoked from level editor, also close tape recorder door
15778 if (level_editor_test_game)
15779 door_state = DOOR_CLOSE_ALL;
15781 CloseDoor(door_state);
15783 SetGameStatus(GAME_MODE_MAIN);
15789 boolean CheckRestartGame(void)
15791 static int game_over_delay = 0;
15792 int game_over_delay_value = 50;
15793 boolean game_over = checkGameFailed();
15797 game_over_delay = game_over_delay_value;
15802 if (game_over_delay > 0)
15804 if (game_over_delay == game_over_delay_value / 2)
15805 PlaySound(SND_GAME_LOSING);
15812 // do not ask to play again if request dialog is already active
15813 if (game.request_active)
15816 // do not ask to play again if request dialog already handled
15817 if (game.RestartGameRequested)
15820 // do not ask to play again if game was never actually played
15821 if (!game.GamePlayed)
15824 // do not ask to play again if this was disabled in setup menu
15825 if (!setup.ask_on_game_over)
15828 game.RestartGameRequested = TRUE;
15830 RequestRestartGame();
15835 boolean checkGameSolved(void)
15837 // set for all game engines if level was solved
15838 return game.LevelSolved_GameEnd;
15841 boolean checkGameFailed(void)
15843 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15844 return (game_em.game_over && !game_em.level_solved);
15845 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15846 return (game_sp.game_over && !game_sp.level_solved);
15847 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15848 return (game_mm.game_over && !game_mm.level_solved);
15849 else // GAME_ENGINE_TYPE_RND
15850 return (game.GameOver && !game.LevelSolved);
15853 boolean checkGameEnded(void)
15855 return (checkGameSolved() || checkGameFailed());
15859 // ----------------------------------------------------------------------------
15860 // random generator functions
15861 // ----------------------------------------------------------------------------
15863 unsigned int InitEngineRandom_RND(int seed)
15865 game.num_random_calls = 0;
15867 return InitEngineRandom(seed);
15870 unsigned int RND(int max)
15874 game.num_random_calls++;
15876 return GetEngineRandom(max);
15883 // ----------------------------------------------------------------------------
15884 // game engine snapshot handling functions
15885 // ----------------------------------------------------------------------------
15887 struct EngineSnapshotInfo
15889 // runtime values for custom element collect score
15890 int collect_score[NUM_CUSTOM_ELEMENTS];
15892 // runtime values for group element choice position
15893 int choice_pos[NUM_GROUP_ELEMENTS];
15895 // runtime values for belt position animations
15896 int belt_graphic[4][NUM_BELT_PARTS];
15897 int belt_anim_mode[4][NUM_BELT_PARTS];
15900 static struct EngineSnapshotInfo engine_snapshot_rnd;
15901 static char *snapshot_level_identifier = NULL;
15902 static int snapshot_level_nr = -1;
15904 static void SaveEngineSnapshotValues_RND(void)
15906 static int belt_base_active_element[4] =
15908 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15909 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15910 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15911 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15915 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15917 int element = EL_CUSTOM_START + i;
15919 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15922 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15924 int element = EL_GROUP_START + i;
15926 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15929 for (i = 0; i < 4; i++)
15931 for (j = 0; j < NUM_BELT_PARTS; j++)
15933 int element = belt_base_active_element[i] + j;
15934 int graphic = el2img(element);
15935 int anim_mode = graphic_info[graphic].anim_mode;
15937 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15938 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15943 static void LoadEngineSnapshotValues_RND(void)
15945 unsigned int num_random_calls = game.num_random_calls;
15948 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15950 int element = EL_CUSTOM_START + i;
15952 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15955 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15957 int element = EL_GROUP_START + i;
15959 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15962 for (i = 0; i < 4; i++)
15964 for (j = 0; j < NUM_BELT_PARTS; j++)
15966 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15967 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15969 graphic_info[graphic].anim_mode = anim_mode;
15973 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15975 InitRND(tape.random_seed);
15976 for (i = 0; i < num_random_calls; i++)
15980 if (game.num_random_calls != num_random_calls)
15982 Error("number of random calls out of sync");
15983 Error("number of random calls should be %d", num_random_calls);
15984 Error("number of random calls is %d", game.num_random_calls);
15986 Fail("this should not happen -- please debug");
15990 void FreeEngineSnapshotSingle(void)
15992 FreeSnapshotSingle();
15994 setString(&snapshot_level_identifier, NULL);
15995 snapshot_level_nr = -1;
15998 void FreeEngineSnapshotList(void)
16000 FreeSnapshotList();
16003 static ListNode *SaveEngineSnapshotBuffers(void)
16005 ListNode *buffers = NULL;
16007 // copy some special values to a structure better suited for the snapshot
16009 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16010 SaveEngineSnapshotValues_RND();
16011 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16012 SaveEngineSnapshotValues_EM();
16013 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16014 SaveEngineSnapshotValues_SP(&buffers);
16015 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16016 SaveEngineSnapshotValues_MM();
16018 // save values stored in special snapshot structure
16020 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16021 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16022 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16023 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16024 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16025 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16026 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16027 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16029 // save further RND engine values
16031 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16032 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16033 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16035 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16036 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16037 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16038 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16039 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16041 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16042 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16043 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16045 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16047 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16048 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16050 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16051 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16052 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16053 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16054 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16055 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16056 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16057 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16058 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16059 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16060 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16061 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16062 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16063 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16064 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16065 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16066 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16067 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16069 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16070 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16072 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16073 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16074 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16076 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16077 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16079 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16080 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16081 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16082 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16083 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16084 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16086 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16087 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16090 ListNode *node = engine_snapshot_list_rnd;
16093 while (node != NULL)
16095 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16100 Debug("game:playing:SaveEngineSnapshotBuffers",
16101 "size of engine snapshot: %d bytes", num_bytes);
16107 void SaveEngineSnapshotSingle(void)
16109 ListNode *buffers = SaveEngineSnapshotBuffers();
16111 // finally save all snapshot buffers to single snapshot
16112 SaveSnapshotSingle(buffers);
16114 // save level identification information
16115 setString(&snapshot_level_identifier, leveldir_current->identifier);
16116 snapshot_level_nr = level_nr;
16119 boolean CheckSaveEngineSnapshotToList(void)
16121 boolean save_snapshot =
16122 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16123 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16124 game.snapshot.changed_action) ||
16125 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16126 game.snapshot.collected_item));
16128 game.snapshot.changed_action = FALSE;
16129 game.snapshot.collected_item = FALSE;
16130 game.snapshot.save_snapshot = save_snapshot;
16132 return save_snapshot;
16135 void SaveEngineSnapshotToList(void)
16137 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16141 ListNode *buffers = SaveEngineSnapshotBuffers();
16143 // finally save all snapshot buffers to snapshot list
16144 SaveSnapshotToList(buffers);
16147 void SaveEngineSnapshotToListInitial(void)
16149 FreeEngineSnapshotList();
16151 SaveEngineSnapshotToList();
16154 static void LoadEngineSnapshotValues(void)
16156 // restore special values from snapshot structure
16158 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16159 LoadEngineSnapshotValues_RND();
16160 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16161 LoadEngineSnapshotValues_EM();
16162 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16163 LoadEngineSnapshotValues_SP();
16164 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16165 LoadEngineSnapshotValues_MM();
16168 void LoadEngineSnapshotSingle(void)
16170 LoadSnapshotSingle();
16172 LoadEngineSnapshotValues();
16175 static void LoadEngineSnapshot_Undo(int steps)
16177 LoadSnapshotFromList_Older(steps);
16179 LoadEngineSnapshotValues();
16182 static void LoadEngineSnapshot_Redo(int steps)
16184 LoadSnapshotFromList_Newer(steps);
16186 LoadEngineSnapshotValues();
16189 boolean CheckEngineSnapshotSingle(void)
16191 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16192 snapshot_level_nr == level_nr);
16195 boolean CheckEngineSnapshotList(void)
16197 return CheckSnapshotList();
16201 // ---------- new game button stuff -------------------------------------------
16208 boolean *setup_value;
16209 boolean allowed_on_tape;
16210 boolean is_touch_button;
16212 } gamebutton_info[NUM_GAME_BUTTONS] =
16215 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16216 GAME_CTRL_ID_STOP, NULL,
16217 TRUE, FALSE, "stop game"
16220 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16221 GAME_CTRL_ID_PAUSE, NULL,
16222 TRUE, FALSE, "pause game"
16225 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16226 GAME_CTRL_ID_PLAY, NULL,
16227 TRUE, FALSE, "play game"
16230 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16231 GAME_CTRL_ID_UNDO, NULL,
16232 TRUE, FALSE, "undo step"
16235 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16236 GAME_CTRL_ID_REDO, NULL,
16237 TRUE, FALSE, "redo step"
16240 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16241 GAME_CTRL_ID_SAVE, NULL,
16242 TRUE, FALSE, "save game"
16245 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16246 GAME_CTRL_ID_PAUSE2, NULL,
16247 TRUE, FALSE, "pause game"
16250 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16251 GAME_CTRL_ID_LOAD, NULL,
16252 TRUE, FALSE, "load game"
16255 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16256 GAME_CTRL_ID_RESTART, NULL,
16257 TRUE, FALSE, "restart game"
16260 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16261 GAME_CTRL_ID_PANEL_STOP, NULL,
16262 FALSE, FALSE, "stop game"
16265 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16266 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16267 FALSE, FALSE, "pause game"
16270 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16271 GAME_CTRL_ID_PANEL_PLAY, NULL,
16272 FALSE, FALSE, "play game"
16275 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16276 GAME_CTRL_ID_PANEL_RESTART, NULL,
16277 FALSE, FALSE, "restart game"
16280 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16281 GAME_CTRL_ID_TOUCH_STOP, NULL,
16282 FALSE, TRUE, "stop game"
16285 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16286 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16287 FALSE, TRUE, "pause game"
16290 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16291 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16292 FALSE, TRUE, "restart game"
16295 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16296 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16297 TRUE, FALSE, "background music on/off"
16300 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16301 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16302 TRUE, FALSE, "sound loops on/off"
16305 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16306 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16307 TRUE, FALSE, "normal sounds on/off"
16310 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16311 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16312 FALSE, FALSE, "background music on/off"
16315 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16316 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16317 FALSE, FALSE, "sound loops on/off"
16320 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16321 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16322 FALSE, FALSE, "normal sounds on/off"
16326 void CreateGameButtons(void)
16330 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16332 int graphic = gamebutton_info[i].graphic;
16333 struct GraphicInfo *gfx = &graphic_info[graphic];
16334 struct XY *pos = gamebutton_info[i].pos;
16335 struct GadgetInfo *gi;
16338 unsigned int event_mask;
16339 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16340 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16341 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16342 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16343 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16344 int gd_x = gfx->src_x;
16345 int gd_y = gfx->src_y;
16346 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16347 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16348 int gd_xa = gfx->src_x + gfx->active_xoffset;
16349 int gd_ya = gfx->src_y + gfx->active_yoffset;
16350 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16351 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16352 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16353 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16356 // do not use touch buttons if overlay touch buttons are disabled
16357 if (is_touch_button && !setup.touch.overlay_buttons)
16360 if (gfx->bitmap == NULL)
16362 game_gadget[id] = NULL;
16367 if (id == GAME_CTRL_ID_STOP ||
16368 id == GAME_CTRL_ID_PANEL_STOP ||
16369 id == GAME_CTRL_ID_TOUCH_STOP ||
16370 id == GAME_CTRL_ID_PLAY ||
16371 id == GAME_CTRL_ID_PANEL_PLAY ||
16372 id == GAME_CTRL_ID_SAVE ||
16373 id == GAME_CTRL_ID_LOAD ||
16374 id == GAME_CTRL_ID_RESTART ||
16375 id == GAME_CTRL_ID_PANEL_RESTART ||
16376 id == GAME_CTRL_ID_TOUCH_RESTART)
16378 button_type = GD_TYPE_NORMAL_BUTTON;
16380 event_mask = GD_EVENT_RELEASED;
16382 else if (id == GAME_CTRL_ID_UNDO ||
16383 id == GAME_CTRL_ID_REDO)
16385 button_type = GD_TYPE_NORMAL_BUTTON;
16387 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16391 button_type = GD_TYPE_CHECK_BUTTON;
16392 checked = (gamebutton_info[i].setup_value != NULL ?
16393 *gamebutton_info[i].setup_value : FALSE);
16394 event_mask = GD_EVENT_PRESSED;
16397 gi = CreateGadget(GDI_CUSTOM_ID, id,
16398 GDI_IMAGE_ID, graphic,
16399 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16402 GDI_WIDTH, gfx->width,
16403 GDI_HEIGHT, gfx->height,
16404 GDI_TYPE, button_type,
16405 GDI_STATE, GD_BUTTON_UNPRESSED,
16406 GDI_CHECKED, checked,
16407 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16408 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16409 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16410 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16411 GDI_DIRECT_DRAW, FALSE,
16412 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16413 GDI_EVENT_MASK, event_mask,
16414 GDI_CALLBACK_ACTION, HandleGameButtons,
16418 Fail("cannot create gadget");
16420 game_gadget[id] = gi;
16424 void FreeGameButtons(void)
16428 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16429 FreeGadget(game_gadget[i]);
16432 static void UnmapGameButtonsAtSamePosition(int id)
16436 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16438 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16439 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16440 UnmapGadget(game_gadget[i]);
16443 static void UnmapGameButtonsAtSamePosition_All(void)
16445 if (setup.show_load_save_buttons)
16447 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16448 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16449 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16451 else if (setup.show_undo_redo_buttons)
16453 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16454 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16455 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16459 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16460 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16461 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16463 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16464 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16465 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16469 void MapLoadSaveButtons(void)
16471 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16472 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16474 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16475 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16478 void MapUndoRedoButtons(void)
16480 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16481 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16483 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16484 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16487 void ModifyPauseButtons(void)
16491 GAME_CTRL_ID_PAUSE,
16492 GAME_CTRL_ID_PAUSE2,
16493 GAME_CTRL_ID_PANEL_PAUSE,
16494 GAME_CTRL_ID_TOUCH_PAUSE,
16499 // do not redraw pause button on closed door (may happen when restarting game)
16500 if (!(GetDoorState() & DOOR_OPEN_1))
16503 for (i = 0; ids[i] > -1; i++)
16504 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16507 static void MapGameButtonsExt(boolean on_tape)
16511 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16513 if ((i == GAME_CTRL_ID_UNDO ||
16514 i == GAME_CTRL_ID_REDO) &&
16515 game_status != GAME_MODE_PLAYING)
16518 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16519 MapGadget(game_gadget[i]);
16522 UnmapGameButtonsAtSamePosition_All();
16524 RedrawGameButtons();
16527 static void UnmapGameButtonsExt(boolean on_tape)
16531 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16532 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16533 UnmapGadget(game_gadget[i]);
16536 static void RedrawGameButtonsExt(boolean on_tape)
16540 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16541 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16542 RedrawGadget(game_gadget[i]);
16545 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16550 gi->checked = state;
16553 static void RedrawSoundButtonGadget(int id)
16555 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16556 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16557 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16558 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16559 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16560 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16563 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16564 RedrawGadget(game_gadget[id2]);
16567 void MapGameButtons(void)
16569 MapGameButtonsExt(FALSE);
16572 void UnmapGameButtons(void)
16574 UnmapGameButtonsExt(FALSE);
16577 void RedrawGameButtons(void)
16579 RedrawGameButtonsExt(FALSE);
16582 void MapGameButtonsOnTape(void)
16584 MapGameButtonsExt(TRUE);
16587 void UnmapGameButtonsOnTape(void)
16589 UnmapGameButtonsExt(TRUE);
16592 void RedrawGameButtonsOnTape(void)
16594 RedrawGameButtonsExt(TRUE);
16597 static void GameUndoRedoExt(void)
16599 ClearPlayerAction();
16601 tape.pausing = TRUE;
16604 UpdateAndDisplayGameControlValues();
16606 DrawCompleteVideoDisplay();
16607 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16608 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16609 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16611 ModifyPauseButtons();
16616 static void GameUndo(int steps)
16618 if (!CheckEngineSnapshotList())
16621 int tape_property_bits = tape.property_bits;
16623 LoadEngineSnapshot_Undo(steps);
16625 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16630 static void GameRedo(int steps)
16632 if (!CheckEngineSnapshotList())
16635 int tape_property_bits = tape.property_bits;
16637 LoadEngineSnapshot_Redo(steps);
16639 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16644 static void HandleGameButtonsExt(int id, int button)
16646 static boolean game_undo_executed = FALSE;
16647 int steps = BUTTON_STEPSIZE(button);
16648 boolean handle_game_buttons =
16649 (game_status == GAME_MODE_PLAYING ||
16650 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16652 if (!handle_game_buttons)
16657 case GAME_CTRL_ID_STOP:
16658 case GAME_CTRL_ID_PANEL_STOP:
16659 case GAME_CTRL_ID_TOUCH_STOP:
16664 case GAME_CTRL_ID_PAUSE:
16665 case GAME_CTRL_ID_PAUSE2:
16666 case GAME_CTRL_ID_PANEL_PAUSE:
16667 case GAME_CTRL_ID_TOUCH_PAUSE:
16668 if (network.enabled && game_status == GAME_MODE_PLAYING)
16671 SendToServer_ContinuePlaying();
16673 SendToServer_PausePlaying();
16676 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16678 game_undo_executed = FALSE;
16682 case GAME_CTRL_ID_PLAY:
16683 case GAME_CTRL_ID_PANEL_PLAY:
16684 if (game_status == GAME_MODE_MAIN)
16686 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16688 else if (tape.pausing)
16690 if (network.enabled)
16691 SendToServer_ContinuePlaying();
16693 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16697 case GAME_CTRL_ID_UNDO:
16698 // Important: When using "save snapshot when collecting an item" mode,
16699 // load last (current) snapshot for first "undo" after pressing "pause"
16700 // (else the last-but-one snapshot would be loaded, because the snapshot
16701 // pointer already points to the last snapshot when pressing "pause",
16702 // which is fine for "every step/move" mode, but not for "every collect")
16703 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16704 !game_undo_executed)
16707 game_undo_executed = TRUE;
16712 case GAME_CTRL_ID_REDO:
16716 case GAME_CTRL_ID_SAVE:
16720 case GAME_CTRL_ID_LOAD:
16724 case GAME_CTRL_ID_RESTART:
16725 case GAME_CTRL_ID_PANEL_RESTART:
16726 case GAME_CTRL_ID_TOUCH_RESTART:
16731 case SOUND_CTRL_ID_MUSIC:
16732 case SOUND_CTRL_ID_PANEL_MUSIC:
16733 if (setup.sound_music)
16735 setup.sound_music = FALSE;
16739 else if (audio.music_available)
16741 setup.sound = setup.sound_music = TRUE;
16743 SetAudioMode(setup.sound);
16745 if (game_status == GAME_MODE_PLAYING)
16749 RedrawSoundButtonGadget(id);
16753 case SOUND_CTRL_ID_LOOPS:
16754 case SOUND_CTRL_ID_PANEL_LOOPS:
16755 if (setup.sound_loops)
16756 setup.sound_loops = FALSE;
16757 else if (audio.loops_available)
16759 setup.sound = setup.sound_loops = TRUE;
16761 SetAudioMode(setup.sound);
16764 RedrawSoundButtonGadget(id);
16768 case SOUND_CTRL_ID_SIMPLE:
16769 case SOUND_CTRL_ID_PANEL_SIMPLE:
16770 if (setup.sound_simple)
16771 setup.sound_simple = FALSE;
16772 else if (audio.sound_available)
16774 setup.sound = setup.sound_simple = TRUE;
16776 SetAudioMode(setup.sound);
16779 RedrawSoundButtonGadget(id);
16788 static void HandleGameButtons(struct GadgetInfo *gi)
16790 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16793 void HandleSoundButtonKeys(Key key)
16795 if (key == setup.shortcut.sound_simple)
16796 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16797 else if (key == setup.shortcut.sound_loops)
16798 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16799 else if (key == setup.shortcut.sound_music)
16800 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);