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;
3850 ScreenMovDir = MV_NONE;
3854 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3856 game.robot_wheel_x = -1;
3857 game.robot_wheel_y = -1;
3862 game.all_players_gone = FALSE;
3864 game.LevelSolved = FALSE;
3865 game.GameOver = FALSE;
3867 game.GamePlayed = !tape.playing;
3869 game.LevelSolved_GameWon = FALSE;
3870 game.LevelSolved_GameEnd = FALSE;
3871 game.LevelSolved_SaveTape = FALSE;
3872 game.LevelSolved_SaveScore = FALSE;
3874 game.LevelSolved_CountingTime = 0;
3875 game.LevelSolved_CountingScore = 0;
3876 game.LevelSolved_CountingHealth = 0;
3878 game.RestartGameRequested = FALSE;
3880 game.panel.active = TRUE;
3882 game.no_level_time_limit = (level.time == 0);
3883 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3885 game.yamyam_content_nr = 0;
3886 game.robot_wheel_active = FALSE;
3887 game.magic_wall_active = FALSE;
3888 game.magic_wall_time_left = 0;
3889 game.light_time_left = 0;
3890 game.timegate_time_left = 0;
3891 game.switchgate_pos = 0;
3892 game.wind_direction = level.wind_direction_initial;
3894 game.time_final = 0;
3895 game.score_time_final = 0;
3898 game.score_final = 0;
3900 game.health = MAX_HEALTH;
3901 game.health_final = MAX_HEALTH;
3903 game.gems_still_needed = level.gems_needed;
3904 game.sokoban_fields_still_needed = 0;
3905 game.sokoban_objects_still_needed = 0;
3906 game.lights_still_needed = 0;
3907 game.players_still_needed = 0;
3908 game.friends_still_needed = 0;
3910 game.lenses_time_left = 0;
3911 game.magnify_time_left = 0;
3913 game.ball_active = level.ball_active_initial;
3914 game.ball_content_nr = 0;
3916 game.explosions_delayed = TRUE;
3918 game.envelope_active = FALSE;
3920 // special case: set custom artwork setting to initial value
3921 game.use_masked_elements = game.use_masked_elements_initial;
3923 for (i = 0; i < NUM_BELTS; i++)
3925 game.belt_dir[i] = MV_NONE;
3926 game.belt_dir_nr[i] = 3; // not moving, next moving left
3929 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3930 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3932 #if DEBUG_INIT_PLAYER
3933 DebugPrintPlayerStatus("Player status at level initialization");
3936 SCAN_PLAYFIELD(x, y)
3938 Tile[x][y] = Last[x][y] = level.field[x][y];
3939 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3940 ChangeDelay[x][y] = 0;
3941 ChangePage[x][y] = -1;
3942 CustomValue[x][y] = 0; // initialized in InitField()
3943 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3945 WasJustMoving[x][y] = 0;
3946 WasJustFalling[x][y] = 0;
3947 CheckCollision[x][y] = 0;
3948 CheckImpact[x][y] = 0;
3950 Pushed[x][y] = FALSE;
3952 ChangeCount[x][y] = 0;
3953 ChangeEvent[x][y] = -1;
3955 ExplodePhase[x][y] = 0;
3956 ExplodeDelay[x][y] = 0;
3957 ExplodeField[x][y] = EX_TYPE_NONE;
3959 RunnerVisit[x][y] = 0;
3960 PlayerVisit[x][y] = 0;
3963 GfxRandom[x][y] = INIT_GFX_RANDOM();
3964 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
3965 GfxElement[x][y] = EL_UNDEFINED;
3966 GfxElementEmpty[x][y] = EL_EMPTY;
3967 GfxAction[x][y] = ACTION_DEFAULT;
3968 GfxDir[x][y] = MV_NONE;
3969 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3972 SCAN_PLAYFIELD(x, y)
3974 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3976 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3979 InitField(x, y, TRUE);
3981 ResetGfxAnimation(x, y);
3986 // required if level does not contain any "empty space" element
3987 if (element_info[EL_EMPTY].use_gfx_element)
3988 game.use_masked_elements = TRUE;
3990 for (i = 0; i < MAX_PLAYERS; i++)
3992 struct PlayerInfo *player = &stored_player[i];
3994 // set number of special actions for bored and sleeping animation
3995 player->num_special_action_bored =
3996 get_num_special_action(player->artwork_element,
3997 ACTION_BORING_1, ACTION_BORING_LAST);
3998 player->num_special_action_sleeping =
3999 get_num_special_action(player->artwork_element,
4000 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
4003 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
4004 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
4006 // initialize type of slippery elements
4007 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4009 if (!IS_CUSTOM_ELEMENT(i))
4011 // default: elements slip down either to the left or right randomly
4012 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
4014 // SP style elements prefer to slip down on the left side
4015 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
4016 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4018 // BD style elements prefer to slip down on the left side
4019 if (game.emulation == EMU_BOULDERDASH)
4020 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
4024 // initialize explosion and ignition delay
4025 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
4027 if (!IS_CUSTOM_ELEMENT(i))
4030 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
4031 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
4032 game.emulation == EMU_SUPAPLEX ? 3 : 2);
4033 int last_phase = (num_phase + 1) * delay;
4034 int half_phase = (num_phase / 2) * delay;
4036 element_info[i].explosion_delay = last_phase - 1;
4037 element_info[i].ignition_delay = half_phase;
4039 if (i == EL_BLACK_ORB)
4040 element_info[i].ignition_delay = 1;
4044 // correct non-moving belts to start moving left
4045 for (i = 0; i < NUM_BELTS; i++)
4046 if (game.belt_dir[i] == MV_NONE)
4047 game.belt_dir_nr[i] = 3; // not moving, next moving left
4049 #if USE_NEW_PLAYER_ASSIGNMENTS
4050 // use preferred player also in local single-player mode
4051 if (!network.enabled && !game.team_mode)
4053 int new_index_nr = setup.network_player_nr;
4055 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4057 for (i = 0; i < MAX_PLAYERS; i++)
4058 stored_player[i].connected_locally = FALSE;
4060 stored_player[new_index_nr].connected_locally = TRUE;
4064 for (i = 0; i < MAX_PLAYERS; i++)
4066 stored_player[i].connected = FALSE;
4068 // in network game mode, the local player might not be the first player
4069 if (stored_player[i].connected_locally)
4070 local_player = &stored_player[i];
4073 if (!network.enabled)
4074 local_player->connected = TRUE;
4078 for (i = 0; i < MAX_PLAYERS; i++)
4079 stored_player[i].connected = tape.player_participates[i];
4081 else if (network.enabled)
4083 // add team mode players connected over the network (needed for correct
4084 // assignment of player figures from level to locally playing players)
4086 for (i = 0; i < MAX_PLAYERS; i++)
4087 if (stored_player[i].connected_network)
4088 stored_player[i].connected = TRUE;
4090 else if (game.team_mode)
4092 // try to guess locally connected team mode players (needed for correct
4093 // assignment of player figures from level to locally playing players)
4095 for (i = 0; i < MAX_PLAYERS; i++)
4096 if (setup.input[i].use_joystick ||
4097 setup.input[i].key.left != KSYM_UNDEFINED)
4098 stored_player[i].connected = TRUE;
4101 #if DEBUG_INIT_PLAYER
4102 DebugPrintPlayerStatus("Player status after level initialization");
4105 #if DEBUG_INIT_PLAYER
4106 Debug("game:init:player", "Reassigning players ...");
4109 // check if any connected player was not found in playfield
4110 for (i = 0; i < MAX_PLAYERS; i++)
4112 struct PlayerInfo *player = &stored_player[i];
4114 if (player->connected && !player->present)
4116 struct PlayerInfo *field_player = NULL;
4118 #if DEBUG_INIT_PLAYER
4119 Debug("game:init:player",
4120 "- looking for field player for player %d ...", i + 1);
4123 // assign first free player found that is present in the playfield
4125 // first try: look for unmapped playfield player that is not connected
4126 for (j = 0; j < MAX_PLAYERS; j++)
4127 if (field_player == NULL &&
4128 stored_player[j].present &&
4129 !stored_player[j].mapped &&
4130 !stored_player[j].connected)
4131 field_player = &stored_player[j];
4133 // second try: look for *any* unmapped playfield player
4134 for (j = 0; j < MAX_PLAYERS; j++)
4135 if (field_player == NULL &&
4136 stored_player[j].present &&
4137 !stored_player[j].mapped)
4138 field_player = &stored_player[j];
4140 if (field_player != NULL)
4142 int jx = field_player->jx, jy = field_player->jy;
4144 #if DEBUG_INIT_PLAYER
4145 Debug("game:init:player", "- found player %d",
4146 field_player->index_nr + 1);
4149 player->present = FALSE;
4150 player->active = FALSE;
4152 field_player->present = TRUE;
4153 field_player->active = TRUE;
4156 player->initial_element = field_player->initial_element;
4157 player->artwork_element = field_player->artwork_element;
4159 player->block_last_field = field_player->block_last_field;
4160 player->block_delay_adjustment = field_player->block_delay_adjustment;
4163 StorePlayer[jx][jy] = field_player->element_nr;
4165 field_player->jx = field_player->last_jx = jx;
4166 field_player->jy = field_player->last_jy = jy;
4168 if (local_player == player)
4169 local_player = field_player;
4171 map_player_action[field_player->index_nr] = i;
4173 field_player->mapped = TRUE;
4175 #if DEBUG_INIT_PLAYER
4176 Debug("game:init:player", "- map_player_action[%d] == %d",
4177 field_player->index_nr + 1, i + 1);
4182 if (player->connected && player->present)
4183 player->mapped = TRUE;
4186 #if DEBUG_INIT_PLAYER
4187 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4192 // check if any connected player was not found in playfield
4193 for (i = 0; i < MAX_PLAYERS; i++)
4195 struct PlayerInfo *player = &stored_player[i];
4197 if (player->connected && !player->present)
4199 for (j = 0; j < MAX_PLAYERS; j++)
4201 struct PlayerInfo *field_player = &stored_player[j];
4202 int jx = field_player->jx, jy = field_player->jy;
4204 // assign first free player found that is present in the playfield
4205 if (field_player->present && !field_player->connected)
4207 player->present = TRUE;
4208 player->active = TRUE;
4210 field_player->present = FALSE;
4211 field_player->active = FALSE;
4213 player->initial_element = field_player->initial_element;
4214 player->artwork_element = field_player->artwork_element;
4216 player->block_last_field = field_player->block_last_field;
4217 player->block_delay_adjustment = field_player->block_delay_adjustment;
4219 StorePlayer[jx][jy] = player->element_nr;
4221 player->jx = player->last_jx = jx;
4222 player->jy = player->last_jy = jy;
4232 Debug("game:init:player", "local_player->present == %d",
4233 local_player->present);
4236 // set focus to local player for network games, else to all players
4237 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4238 game.centered_player_nr_next = game.centered_player_nr;
4239 game.set_centered_player = FALSE;
4240 game.set_centered_player_wrap = FALSE;
4242 if (network_playing && tape.recording)
4244 // store client dependent player focus when recording network games
4245 tape.centered_player_nr_next = game.centered_player_nr_next;
4246 tape.set_centered_player = TRUE;
4251 // when playing a tape, eliminate all players who do not participate
4253 #if USE_NEW_PLAYER_ASSIGNMENTS
4255 if (!game.team_mode)
4257 for (i = 0; i < MAX_PLAYERS; i++)
4259 if (stored_player[i].active &&
4260 !tape.player_participates[map_player_action[i]])
4262 struct PlayerInfo *player = &stored_player[i];
4263 int jx = player->jx, jy = player->jy;
4265 #if DEBUG_INIT_PLAYER
4266 Debug("game:init:player", "Removing player %d at (%d, %d)",
4270 player->active = FALSE;
4271 StorePlayer[jx][jy] = 0;
4272 Tile[jx][jy] = EL_EMPTY;
4279 for (i = 0; i < MAX_PLAYERS; i++)
4281 if (stored_player[i].active &&
4282 !tape.player_participates[i])
4284 struct PlayerInfo *player = &stored_player[i];
4285 int jx = player->jx, jy = player->jy;
4287 player->active = FALSE;
4288 StorePlayer[jx][jy] = 0;
4289 Tile[jx][jy] = EL_EMPTY;
4294 else if (!network.enabled && !game.team_mode) // && !tape.playing
4296 // when in single player mode, eliminate all but the local player
4298 for (i = 0; i < MAX_PLAYERS; i++)
4300 struct PlayerInfo *player = &stored_player[i];
4302 if (player->active && player != local_player)
4304 int jx = player->jx, jy = player->jy;
4306 player->active = FALSE;
4307 player->present = FALSE;
4309 StorePlayer[jx][jy] = 0;
4310 Tile[jx][jy] = EL_EMPTY;
4315 for (i = 0; i < MAX_PLAYERS; i++)
4316 if (stored_player[i].active)
4317 game.players_still_needed++;
4319 if (level.solved_by_one_player)
4320 game.players_still_needed = 1;
4322 // when recording the game, store which players take part in the game
4325 #if USE_NEW_PLAYER_ASSIGNMENTS
4326 for (i = 0; i < MAX_PLAYERS; i++)
4327 if (stored_player[i].connected)
4328 tape.player_participates[i] = TRUE;
4330 for (i = 0; i < MAX_PLAYERS; i++)
4331 if (stored_player[i].active)
4332 tape.player_participates[i] = TRUE;
4336 #if DEBUG_INIT_PLAYER
4337 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4340 if (BorderElement == EL_EMPTY)
4343 SBX_Right = lev_fieldx - SCR_FIELDX;
4345 SBY_Lower = lev_fieldy - SCR_FIELDY;
4350 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4352 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4355 if (full_lev_fieldx <= SCR_FIELDX)
4356 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4357 if (full_lev_fieldy <= SCR_FIELDY)
4358 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4360 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4362 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4365 // if local player not found, look for custom element that might create
4366 // the player (make some assumptions about the right custom element)
4367 if (!local_player->present)
4369 int start_x = 0, start_y = 0;
4370 int found_rating = 0;
4371 int found_element = EL_UNDEFINED;
4372 int player_nr = local_player->index_nr;
4374 SCAN_PLAYFIELD(x, y)
4376 int element = Tile[x][y];
4381 if (level.use_start_element[player_nr] &&
4382 level.start_element[player_nr] == element &&
4389 found_element = element;
4392 if (!IS_CUSTOM_ELEMENT(element))
4395 if (CAN_CHANGE(element))
4397 for (i = 0; i < element_info[element].num_change_pages; i++)
4399 // check for player created from custom element as single target
4400 content = element_info[element].change_page[i].target_element;
4401 is_player = IS_PLAYER_ELEMENT(content);
4403 if (is_player && (found_rating < 3 ||
4404 (found_rating == 3 && element < found_element)))
4410 found_element = element;
4415 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4417 // check for player created from custom element as explosion content
4418 content = element_info[element].content.e[xx][yy];
4419 is_player = IS_PLAYER_ELEMENT(content);
4421 if (is_player && (found_rating < 2 ||
4422 (found_rating == 2 && element < found_element)))
4424 start_x = x + xx - 1;
4425 start_y = y + yy - 1;
4428 found_element = element;
4431 if (!CAN_CHANGE(element))
4434 for (i = 0; i < element_info[element].num_change_pages; i++)
4436 // check for player created from custom element as extended target
4438 element_info[element].change_page[i].target_content.e[xx][yy];
4440 is_player = IS_PLAYER_ELEMENT(content);
4442 if (is_player && (found_rating < 1 ||
4443 (found_rating == 1 && element < found_element)))
4445 start_x = x + xx - 1;
4446 start_y = y + yy - 1;
4449 found_element = element;
4455 scroll_x = SCROLL_POSITION_X(start_x);
4456 scroll_y = SCROLL_POSITION_Y(start_y);
4460 scroll_x = SCROLL_POSITION_X(local_player->jx);
4461 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4464 if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
4465 scroll_x = game.forced_scroll_x;
4466 if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
4467 scroll_y = game.forced_scroll_y;
4469 // !!! FIX THIS (START) !!!
4470 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4472 InitGameEngine_EM();
4474 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4476 InitGameEngine_SP();
4478 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4480 InitGameEngine_MM();
4484 DrawLevel(REDRAW_FIELD);
4487 // after drawing the level, correct some elements
4488 if (game.timegate_time_left == 0)
4489 CloseAllOpenTimegates();
4492 // blit playfield from scroll buffer to normal back buffer for fading in
4493 BlitScreenToBitmap(backbuffer);
4494 // !!! FIX THIS (END) !!!
4496 DrawMaskedBorder(fade_mask);
4501 // full screen redraw is required at this point in the following cases:
4502 // - special editor door undrawn when game was started from level editor
4503 // - drawing area (playfield) was changed and has to be removed completely
4504 redraw_mask = REDRAW_ALL;
4508 if (!game.restart_level)
4510 // copy default game door content to main double buffer
4512 // !!! CHECK AGAIN !!!
4513 SetPanelBackground();
4514 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4515 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4518 SetPanelBackground();
4519 SetDrawBackgroundMask(REDRAW_DOOR_1);
4521 UpdateAndDisplayGameControlValues();
4523 if (!game.restart_level)
4529 CreateGameButtons();
4534 // copy actual game door content to door double buffer for OpenDoor()
4535 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4537 OpenDoor(DOOR_OPEN_ALL);
4539 KeyboardAutoRepeatOffUnlessAutoplay();
4541 #if DEBUG_INIT_PLAYER
4542 DebugPrintPlayerStatus("Player status (final)");
4551 if (!game.restart_level && !tape.playing)
4553 LevelStats_incPlayed(level_nr);
4555 SaveLevelSetup_SeriesInfo();
4558 game.restart_level = FALSE;
4559 game.request_active = FALSE;
4561 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4562 InitGameActions_MM();
4564 SaveEngineSnapshotToListInitial();
4566 if (!game.restart_level)
4568 PlaySound(SND_GAME_STARTING);
4570 if (setup.sound_music)
4574 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4577 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4578 int actual_player_x, int actual_player_y)
4580 // this is used for non-R'n'D game engines to update certain engine values
4582 // needed to determine if sounds are played within the visible screen area
4583 scroll_x = actual_scroll_x;
4584 scroll_y = actual_scroll_y;
4586 // needed to get player position for "follow finger" playing input method
4587 local_player->jx = actual_player_x;
4588 local_player->jy = actual_player_y;
4591 void InitMovDir(int x, int y)
4593 int i, element = Tile[x][y];
4594 static int xy[4][2] =
4601 static int direction[3][4] =
4603 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4604 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4605 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4614 Tile[x][y] = EL_BUG;
4615 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4618 case EL_SPACESHIP_RIGHT:
4619 case EL_SPACESHIP_UP:
4620 case EL_SPACESHIP_LEFT:
4621 case EL_SPACESHIP_DOWN:
4622 Tile[x][y] = EL_SPACESHIP;
4623 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4626 case EL_BD_BUTTERFLY_RIGHT:
4627 case EL_BD_BUTTERFLY_UP:
4628 case EL_BD_BUTTERFLY_LEFT:
4629 case EL_BD_BUTTERFLY_DOWN:
4630 Tile[x][y] = EL_BD_BUTTERFLY;
4631 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4634 case EL_BD_FIREFLY_RIGHT:
4635 case EL_BD_FIREFLY_UP:
4636 case EL_BD_FIREFLY_LEFT:
4637 case EL_BD_FIREFLY_DOWN:
4638 Tile[x][y] = EL_BD_FIREFLY;
4639 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4642 case EL_PACMAN_RIGHT:
4644 case EL_PACMAN_LEFT:
4645 case EL_PACMAN_DOWN:
4646 Tile[x][y] = EL_PACMAN;
4647 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4650 case EL_YAMYAM_LEFT:
4651 case EL_YAMYAM_RIGHT:
4653 case EL_YAMYAM_DOWN:
4654 Tile[x][y] = EL_YAMYAM;
4655 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4658 case EL_SP_SNIKSNAK:
4659 MovDir[x][y] = MV_UP;
4662 case EL_SP_ELECTRON:
4663 MovDir[x][y] = MV_LEFT;
4670 Tile[x][y] = EL_MOLE;
4671 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4674 case EL_SPRING_LEFT:
4675 case EL_SPRING_RIGHT:
4676 Tile[x][y] = EL_SPRING;
4677 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4681 if (IS_CUSTOM_ELEMENT(element))
4683 struct ElementInfo *ei = &element_info[element];
4684 int move_direction_initial = ei->move_direction_initial;
4685 int move_pattern = ei->move_pattern;
4687 if (move_direction_initial == MV_START_PREVIOUS)
4689 if (MovDir[x][y] != MV_NONE)
4692 move_direction_initial = MV_START_AUTOMATIC;
4695 if (move_direction_initial == MV_START_RANDOM)
4696 MovDir[x][y] = 1 << RND(4);
4697 else if (move_direction_initial & MV_ANY_DIRECTION)
4698 MovDir[x][y] = move_direction_initial;
4699 else if (move_pattern == MV_ALL_DIRECTIONS ||
4700 move_pattern == MV_TURNING_LEFT ||
4701 move_pattern == MV_TURNING_RIGHT ||
4702 move_pattern == MV_TURNING_LEFT_RIGHT ||
4703 move_pattern == MV_TURNING_RIGHT_LEFT ||
4704 move_pattern == MV_TURNING_RANDOM)
4705 MovDir[x][y] = 1 << RND(4);
4706 else if (move_pattern == MV_HORIZONTAL)
4707 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4708 else if (move_pattern == MV_VERTICAL)
4709 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4710 else if (move_pattern & MV_ANY_DIRECTION)
4711 MovDir[x][y] = element_info[element].move_pattern;
4712 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4713 move_pattern == MV_ALONG_RIGHT_SIDE)
4715 // use random direction as default start direction
4716 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4717 MovDir[x][y] = 1 << RND(4);
4719 for (i = 0; i < NUM_DIRECTIONS; i++)
4721 int x1 = x + xy[i][0];
4722 int y1 = y + xy[i][1];
4724 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4726 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4727 MovDir[x][y] = direction[0][i];
4729 MovDir[x][y] = direction[1][i];
4738 MovDir[x][y] = 1 << RND(4);
4740 if (element != EL_BUG &&
4741 element != EL_SPACESHIP &&
4742 element != EL_BD_BUTTERFLY &&
4743 element != EL_BD_FIREFLY)
4746 for (i = 0; i < NUM_DIRECTIONS; i++)
4748 int x1 = x + xy[i][0];
4749 int y1 = y + xy[i][1];
4751 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4753 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4755 MovDir[x][y] = direction[0][i];
4758 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4759 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4761 MovDir[x][y] = direction[1][i];
4770 GfxDir[x][y] = MovDir[x][y];
4773 void InitAmoebaNr(int x, int y)
4776 int group_nr = AmoebaNeighbourNr(x, y);
4780 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4782 if (AmoebaCnt[i] == 0)
4790 AmoebaNr[x][y] = group_nr;
4791 AmoebaCnt[group_nr]++;
4792 AmoebaCnt2[group_nr]++;
4795 static void LevelSolved_SetFinalGameValues(void)
4797 game.time_final = (game.no_level_time_limit ? TimePlayed : TimeLeft);
4798 game.score_time_final = (level.use_step_counter ? TimePlayed :
4799 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4801 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4802 game_em.lev->score :
4803 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4807 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4808 MM_HEALTH(game_mm.laser_overload_value) :
4811 game.LevelSolved_CountingTime = game.time_final;
4812 game.LevelSolved_CountingScore = game.score_final;
4813 game.LevelSolved_CountingHealth = game.health_final;
4816 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4818 game.LevelSolved_CountingTime = time;
4819 game.LevelSolved_CountingScore = score;
4820 game.LevelSolved_CountingHealth = health;
4822 game_panel_controls[GAME_PANEL_TIME].value = time;
4823 game_panel_controls[GAME_PANEL_SCORE].value = score;
4824 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4826 DisplayGameControlValues();
4829 static void LevelSolved(void)
4831 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4832 game.players_still_needed > 0)
4835 game.LevelSolved = TRUE;
4836 game.GameOver = TRUE;
4840 // needed here to display correct panel values while player walks into exit
4841 LevelSolved_SetFinalGameValues();
4846 static int time_count_steps;
4847 static int time, time_final;
4848 static float score, score_final; // needed for time score < 10 for 10 seconds
4849 static int health, health_final;
4850 static int game_over_delay_1 = 0;
4851 static int game_over_delay_2 = 0;
4852 static int game_over_delay_3 = 0;
4853 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4854 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4856 if (!game.LevelSolved_GameWon)
4860 // do not start end game actions before the player stops moving (to exit)
4861 if (local_player->active && local_player->MovPos)
4864 // calculate final game values after player finished walking into exit
4865 LevelSolved_SetFinalGameValues();
4867 game.LevelSolved_GameWon = TRUE;
4868 game.LevelSolved_SaveTape = tape.recording;
4869 game.LevelSolved_SaveScore = !tape.playing;
4873 LevelStats_incSolved(level_nr);
4875 SaveLevelSetup_SeriesInfo();
4878 if (tape.auto_play) // tape might already be stopped here
4879 tape.auto_play_level_solved = TRUE;
4883 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4884 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4885 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4887 time = time_final = game.time_final;
4888 score = score_final = game.score_final;
4889 health = health_final = game.health_final;
4891 // update game panel values before (delayed) counting of score (if any)
4892 LevelSolved_DisplayFinalGameValues(time, score, health);
4894 // if level has time score defined, calculate new final game values
4897 int time_final_max = 999;
4898 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4899 int time_frames = 0;
4900 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4901 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4906 time_frames = time_frames_left;
4908 else if (game.no_level_time_limit && TimePlayed < time_final_max)
4910 time_final = time_final_max;
4911 time_frames = time_frames_final_max - time_frames_played;
4914 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4916 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4918 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4921 score_final += health * time_score;
4924 game.score_final = score_final;
4925 game.health_final = health_final;
4928 // if not counting score after game, immediately update game panel values
4929 if (level_editor_test_game || !setup.count_score_after_game)
4932 score = score_final;
4934 LevelSolved_DisplayFinalGameValues(time, score, health);
4937 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4939 // check if last player has left the level
4940 if (game.exit_x >= 0 &&
4943 int x = game.exit_x;
4944 int y = game.exit_y;
4945 int element = Tile[x][y];
4947 // close exit door after last player
4948 if ((game.all_players_gone &&
4949 (element == EL_EXIT_OPEN ||
4950 element == EL_SP_EXIT_OPEN ||
4951 element == EL_STEEL_EXIT_OPEN)) ||
4952 element == EL_EM_EXIT_OPEN ||
4953 element == EL_EM_STEEL_EXIT_OPEN)
4957 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4958 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4959 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4960 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4961 EL_EM_STEEL_EXIT_CLOSING);
4963 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4966 // player disappears
4967 DrawLevelField(x, y);
4970 for (i = 0; i < MAX_PLAYERS; i++)
4972 struct PlayerInfo *player = &stored_player[i];
4974 if (player->present)
4976 RemovePlayer(player);
4978 // player disappears
4979 DrawLevelField(player->jx, player->jy);
4984 PlaySound(SND_GAME_WINNING);
4987 if (setup.count_score_after_game)
4989 if (time != time_final)
4991 if (game_over_delay_1 > 0)
4993 game_over_delay_1--;
4998 int time_to_go = ABS(time_final - time);
4999 int time_count_dir = (time < time_final ? +1 : -1);
5001 if (time_to_go < time_count_steps)
5002 time_count_steps = 1;
5004 time += time_count_steps * time_count_dir;
5005 score += time_count_steps * time_score;
5007 // set final score to correct rounding differences after counting score
5008 if (time == time_final)
5009 score = score_final;
5011 LevelSolved_DisplayFinalGameValues(time, score, health);
5013 if (time == time_final)
5014 StopSound(SND_GAME_LEVELTIME_BONUS);
5015 else if (setup.sound_loops)
5016 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5018 PlaySound(SND_GAME_LEVELTIME_BONUS);
5023 if (health != health_final)
5025 if (game_over_delay_2 > 0)
5027 game_over_delay_2--;
5032 int health_count_dir = (health < health_final ? +1 : -1);
5034 health += health_count_dir;
5035 score += time_score;
5037 LevelSolved_DisplayFinalGameValues(time, score, health);
5039 if (health == health_final)
5040 StopSound(SND_GAME_LEVELTIME_BONUS);
5041 else if (setup.sound_loops)
5042 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
5044 PlaySound(SND_GAME_LEVELTIME_BONUS);
5050 game.panel.active = FALSE;
5052 if (game_over_delay_3 > 0)
5054 game_over_delay_3--;
5064 // used instead of "level_nr" (needed for network games)
5065 int last_level_nr = levelset.level_nr;
5066 boolean tape_saved = FALSE;
5068 game.LevelSolved_GameEnd = TRUE;
5070 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5072 // make sure that request dialog to save tape does not open door again
5073 if (!global.use_envelope_request)
5074 CloseDoor(DOOR_CLOSE_1);
5077 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5079 // set unique basename for score tape (also saved in high score table)
5080 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5083 // if no tape is to be saved, close both doors simultaneously
5084 CloseDoor(DOOR_CLOSE_ALL);
5086 if (level_editor_test_game || score_info_tape_play)
5088 SetGameStatus(GAME_MODE_MAIN);
5095 if (!game.LevelSolved_SaveScore)
5097 SetGameStatus(GAME_MODE_MAIN);
5104 if (level_nr == leveldir_current->handicap_level)
5106 leveldir_current->handicap_level++;
5108 SaveLevelSetup_SeriesInfo();
5111 // save score and score tape before potentially erasing tape below
5112 NewHighScore(last_level_nr, tape_saved);
5114 if (setup.increment_levels &&
5115 level_nr < leveldir_current->last_level &&
5118 level_nr++; // advance to next level
5119 TapeErase(); // start with empty tape
5121 if (setup.auto_play_next_level)
5123 scores.continue_playing = TRUE;
5124 scores.next_level_nr = level_nr;
5126 LoadLevel(level_nr);
5128 SaveLevelSetup_SeriesInfo();
5132 if (scores.last_added >= 0 && setup.show_scores_after_game)
5134 SetGameStatus(GAME_MODE_SCORES);
5136 DrawHallOfFame(last_level_nr);
5138 else if (scores.continue_playing)
5140 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5144 SetGameStatus(GAME_MODE_MAIN);
5150 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5151 boolean one_score_entry_per_name)
5155 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5158 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5160 struct ScoreEntry *entry = &list->entry[i];
5161 boolean score_is_better = (new_entry->score > entry->score);
5162 boolean score_is_equal = (new_entry->score == entry->score);
5163 boolean time_is_better = (new_entry->time < entry->time);
5164 boolean time_is_equal = (new_entry->time == entry->time);
5165 boolean better_by_score = (score_is_better ||
5166 (score_is_equal && time_is_better));
5167 boolean better_by_time = (time_is_better ||
5168 (time_is_equal && score_is_better));
5169 boolean is_better = (level.rate_time_over_score ? better_by_time :
5171 boolean entry_is_empty = (entry->score == 0 &&
5174 // prevent adding server score entries if also existing in local score file
5175 // (special case: historic score entries have an empty tape basename entry)
5176 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5177 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5179 // add fields from server score entry not stored in local score entry
5180 // (currently, this means setting platform, version and country fields;
5181 // in rare cases, this may also correct an invalid score value, as
5182 // historic scores might have been truncated to 16-bit values locally)
5183 *entry = *new_entry;
5188 if (is_better || entry_is_empty)
5190 // player has made it to the hall of fame
5192 if (i < MAX_SCORE_ENTRIES - 1)
5194 int m = MAX_SCORE_ENTRIES - 1;
5197 if (one_score_entry_per_name)
5199 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5200 if (strEqual(list->entry[l].name, new_entry->name))
5203 if (m == i) // player's new highscore overwrites his old one
5207 for (l = m; l > i; l--)
5208 list->entry[l] = list->entry[l - 1];
5213 *entry = *new_entry;
5217 else if (one_score_entry_per_name &&
5218 strEqual(entry->name, new_entry->name))
5220 // player already in high score list with better score or time
5226 // special case: new score is beyond the last high score list position
5227 return MAX_SCORE_ENTRIES;
5230 void NewHighScore(int level_nr, boolean tape_saved)
5232 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5233 boolean one_per_name = FALSE;
5235 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5236 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5238 new_entry.score = game.score_final;
5239 new_entry.time = game.score_time_final;
5241 LoadScore(level_nr);
5243 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5245 if (scores.last_added >= MAX_SCORE_ENTRIES)
5247 scores.last_added = MAX_SCORE_ENTRIES - 1;
5248 scores.force_last_added = TRUE;
5250 scores.entry[scores.last_added] = new_entry;
5252 // store last added local score entry (before merging server scores)
5253 scores.last_added_local = scores.last_added;
5258 if (scores.last_added < 0)
5261 SaveScore(level_nr);
5263 // store last added local score entry (before merging server scores)
5264 scores.last_added_local = scores.last_added;
5266 if (!game.LevelSolved_SaveTape)
5269 SaveScoreTape(level_nr);
5271 if (setup.ask_for_using_api_server)
5273 setup.use_api_server =
5274 Request("Upload your score and tape to the high score server?", REQ_ASK);
5276 if (!setup.use_api_server)
5277 Request("Not using high score server! Use setup menu to enable again!",
5280 runtime.use_api_server = setup.use_api_server;
5282 // after asking for using API server once, do not ask again
5283 setup.ask_for_using_api_server = FALSE;
5285 SaveSetup_ServerSetup();
5288 SaveServerScore(level_nr, tape_saved);
5291 void MergeServerScore(void)
5293 struct ScoreEntry last_added_entry;
5294 boolean one_per_name = FALSE;
5297 if (scores.last_added >= 0)
5298 last_added_entry = scores.entry[scores.last_added];
5300 for (i = 0; i < server_scores.num_entries; i++)
5302 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5304 if (pos >= 0 && pos <= scores.last_added)
5305 scores.last_added++;
5308 if (scores.last_added >= MAX_SCORE_ENTRIES)
5310 scores.last_added = MAX_SCORE_ENTRIES - 1;
5311 scores.force_last_added = TRUE;
5313 scores.entry[scores.last_added] = last_added_entry;
5317 static int getElementMoveStepsizeExt(int x, int y, int direction)
5319 int element = Tile[x][y];
5320 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5321 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5322 int horiz_move = (dx != 0);
5323 int sign = (horiz_move ? dx : dy);
5324 int step = sign * element_info[element].move_stepsize;
5326 // special values for move stepsize for spring and things on conveyor belt
5329 if (CAN_FALL(element) &&
5330 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5331 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5332 else if (element == EL_SPRING)
5333 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5339 static int getElementMoveStepsize(int x, int y)
5341 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5344 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5346 if (player->GfxAction != action || player->GfxDir != dir)
5348 player->GfxAction = action;
5349 player->GfxDir = dir;
5351 player->StepFrame = 0;
5355 static void ResetGfxFrame(int x, int y)
5357 // profiling showed that "autotest" spends 10~20% of its time in this function
5358 if (DrawingDeactivatedField())
5361 int element = Tile[x][y];
5362 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5364 if (graphic_info[graphic].anim_global_sync)
5365 GfxFrame[x][y] = FrameCounter;
5366 else if (graphic_info[graphic].anim_global_anim_sync)
5367 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5368 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5369 GfxFrame[x][y] = CustomValue[x][y];
5370 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5371 GfxFrame[x][y] = element_info[element].collect_score;
5372 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5373 GfxFrame[x][y] = ChangeDelay[x][y];
5376 static void ResetGfxAnimation(int x, int y)
5378 GfxAction[x][y] = ACTION_DEFAULT;
5379 GfxDir[x][y] = MovDir[x][y];
5382 ResetGfxFrame(x, y);
5385 static void ResetRandomAnimationValue(int x, int y)
5387 GfxRandom[x][y] = INIT_GFX_RANDOM();
5390 static void InitMovingField(int x, int y, int direction)
5392 int element = Tile[x][y];
5393 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5394 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5397 boolean is_moving_before, is_moving_after;
5399 // check if element was/is moving or being moved before/after mode change
5400 is_moving_before = (WasJustMoving[x][y] != 0);
5401 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5403 // reset animation only for moving elements which change direction of moving
5404 // or which just started or stopped moving
5405 // (else CEs with property "can move" / "not moving" are reset each frame)
5406 if (is_moving_before != is_moving_after ||
5407 direction != MovDir[x][y])
5408 ResetGfxAnimation(x, y);
5410 MovDir[x][y] = direction;
5411 GfxDir[x][y] = direction;
5413 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5414 direction == MV_DOWN && CAN_FALL(element) ?
5415 ACTION_FALLING : ACTION_MOVING);
5417 // this is needed for CEs with property "can move" / "not moving"
5419 if (is_moving_after)
5421 if (Tile[newx][newy] == EL_EMPTY)
5422 Tile[newx][newy] = EL_BLOCKED;
5424 MovDir[newx][newy] = MovDir[x][y];
5426 CustomValue[newx][newy] = CustomValue[x][y];
5428 GfxFrame[newx][newy] = GfxFrame[x][y];
5429 GfxRandom[newx][newy] = GfxRandom[x][y];
5430 GfxAction[newx][newy] = GfxAction[x][y];
5431 GfxDir[newx][newy] = GfxDir[x][y];
5435 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5437 int direction = MovDir[x][y];
5438 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5439 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5445 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5447 int direction = MovDir[x][y];
5448 int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0);
5449 int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0);
5451 *comes_from_x = oldx;
5452 *comes_from_y = oldy;
5455 static int MovingOrBlocked2Element(int x, int y)
5457 int element = Tile[x][y];
5459 if (element == EL_BLOCKED)
5463 Blocked2Moving(x, y, &oldx, &oldy);
5465 return Tile[oldx][oldy];
5471 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5473 // like MovingOrBlocked2Element(), but if element is moving
5474 // and (x, y) is the field the moving element is just leaving,
5475 // return EL_BLOCKED instead of the element value
5476 int element = Tile[x][y];
5478 if (IS_MOVING(x, y))
5480 if (element == EL_BLOCKED)
5484 Blocked2Moving(x, y, &oldx, &oldy);
5485 return Tile[oldx][oldy];
5494 static void RemoveField(int x, int y)
5496 Tile[x][y] = EL_EMPTY;
5502 CustomValue[x][y] = 0;
5505 ChangeDelay[x][y] = 0;
5506 ChangePage[x][y] = -1;
5507 Pushed[x][y] = FALSE;
5509 GfxElement[x][y] = EL_UNDEFINED;
5510 GfxAction[x][y] = ACTION_DEFAULT;
5511 GfxDir[x][y] = MV_NONE;
5514 static void RemoveMovingField(int x, int y)
5516 int oldx = x, oldy = y, newx = x, newy = y;
5517 int element = Tile[x][y];
5518 int next_element = EL_UNDEFINED;
5520 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5523 if (IS_MOVING(x, y))
5525 Moving2Blocked(x, y, &newx, &newy);
5527 if (Tile[newx][newy] != EL_BLOCKED)
5529 // element is moving, but target field is not free (blocked), but
5530 // already occupied by something different (example: acid pool);
5531 // in this case, only remove the moving field, but not the target
5533 RemoveField(oldx, oldy);
5535 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5537 TEST_DrawLevelField(oldx, oldy);
5542 else if (element == EL_BLOCKED)
5544 Blocked2Moving(x, y, &oldx, &oldy);
5545 if (!IS_MOVING(oldx, oldy))
5549 if (element == EL_BLOCKED &&
5550 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5551 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5552 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5553 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5554 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5555 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5556 next_element = get_next_element(Tile[oldx][oldy]);
5558 RemoveField(oldx, oldy);
5559 RemoveField(newx, newy);
5561 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5563 if (next_element != EL_UNDEFINED)
5564 Tile[oldx][oldy] = next_element;
5566 TEST_DrawLevelField(oldx, oldy);
5567 TEST_DrawLevelField(newx, newy);
5570 void DrawDynamite(int x, int y)
5572 int sx = SCREENX(x), sy = SCREENY(y);
5573 int graphic = el2img(Tile[x][y]);
5576 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5579 if (IS_WALKABLE_INSIDE(Back[x][y]))
5583 DrawLevelElement(x, y, Back[x][y]);
5584 else if (Store[x][y])
5585 DrawLevelElement(x, y, Store[x][y]);
5586 else if (game.use_masked_elements)
5587 DrawLevelElement(x, y, EL_EMPTY);
5589 frame = getGraphicAnimationFrameXY(graphic, x, y);
5591 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5592 DrawGraphicThruMask(sx, sy, graphic, frame);
5594 DrawGraphic(sx, sy, graphic, frame);
5597 static void CheckDynamite(int x, int y)
5599 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5603 if (MovDelay[x][y] != 0)
5606 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5612 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5617 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5619 boolean num_checked_players = 0;
5622 for (i = 0; i < MAX_PLAYERS; i++)
5624 if (stored_player[i].active)
5626 int sx = stored_player[i].jx;
5627 int sy = stored_player[i].jy;
5629 if (num_checked_players == 0)
5636 *sx1 = MIN(*sx1, sx);
5637 *sy1 = MIN(*sy1, sy);
5638 *sx2 = MAX(*sx2, sx);
5639 *sy2 = MAX(*sy2, sy);
5642 num_checked_players++;
5647 static boolean checkIfAllPlayersFitToScreen_RND(void)
5649 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5651 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5653 return (sx2 - sx1 < SCR_FIELDX &&
5654 sy2 - sy1 < SCR_FIELDY);
5657 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5659 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5661 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5663 *sx = (sx1 + sx2) / 2;
5664 *sy = (sy1 + sy2) / 2;
5667 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5668 boolean center_screen, boolean quick_relocation)
5670 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5671 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5672 boolean no_delay = (tape.warp_forward);
5673 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5674 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5675 int new_scroll_x, new_scroll_y;
5677 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5679 // case 1: quick relocation inside visible screen (without scrolling)
5686 if (!level.shifted_relocation || center_screen)
5688 // relocation _with_ centering of screen
5690 new_scroll_x = SCROLL_POSITION_X(x);
5691 new_scroll_y = SCROLL_POSITION_Y(y);
5695 // relocation _without_ centering of screen
5697 // apply distance between old and new player position to scroll position
5698 int shifted_scroll_x = scroll_x + (x - old_x);
5699 int shifted_scroll_y = scroll_y + (y - old_y);
5701 // make sure that shifted scroll position does not scroll beyond screen
5702 new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
5703 new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
5705 // special case for teleporting from one end of the playfield to the other
5706 // (this kludge prevents the destination area to be shifted by half a tile
5707 // against the source destination for even screen width or screen height;
5708 // probably most useful when used with high "game.forced_scroll_delay_value"
5709 // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
5710 if (quick_relocation)
5712 if (EVEN(SCR_FIELDX))
5714 // relocate (teleport) between left and right border (half or full)
5715 if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
5716 new_scroll_x = SBX_Right;
5717 else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
5718 new_scroll_x = SBX_Right - 1;
5719 else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
5720 new_scroll_x = SBX_Left;
5721 else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
5722 new_scroll_x = SBX_Left + 1;
5725 if (EVEN(SCR_FIELDY))
5727 // relocate (teleport) between top and bottom border (half or full)
5728 if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
5729 new_scroll_y = SBY_Lower;
5730 else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
5731 new_scroll_y = SBY_Lower - 1;
5732 else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
5733 new_scroll_y = SBY_Upper;
5734 else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
5735 new_scroll_y = SBY_Upper + 1;
5740 if (quick_relocation)
5742 // case 2: quick relocation (redraw without visible scrolling)
5744 scroll_x = new_scroll_x;
5745 scroll_y = new_scroll_y;
5752 // case 3: visible relocation (with scrolling to new position)
5754 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5756 SetVideoFrameDelay(wait_delay_value);
5758 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5760 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5761 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5763 if (dx == 0 && dy == 0) // no scrolling needed at all
5769 // set values for horizontal/vertical screen scrolling (half tile size)
5770 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5771 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5772 int pos_x = dx * TILEX / 2;
5773 int pos_y = dy * TILEY / 2;
5774 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5775 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5777 ScrollLevel(dx, dy);
5780 // scroll in two steps of half tile size to make things smoother
5781 BlitScreenToBitmapExt_RND(window, fx, fy);
5783 // scroll second step to align at full tile size
5784 BlitScreenToBitmap(window);
5790 SetVideoFrameDelay(frame_delay_value_old);
5793 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5795 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5796 int player_nr = GET_PLAYER_NR(el_player);
5797 struct PlayerInfo *player = &stored_player[player_nr];
5798 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5799 boolean no_delay = (tape.warp_forward);
5800 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5801 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5802 int old_jx = player->jx;
5803 int old_jy = player->jy;
5804 int old_element = Tile[old_jx][old_jy];
5805 int element = Tile[jx][jy];
5806 boolean player_relocated = (old_jx != jx || old_jy != jy);
5808 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5809 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5810 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5811 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5812 int leave_side_horiz = move_dir_horiz;
5813 int leave_side_vert = move_dir_vert;
5814 int enter_side = enter_side_horiz | enter_side_vert;
5815 int leave_side = leave_side_horiz | leave_side_vert;
5817 if (player->buried) // do not reanimate dead player
5820 if (!player_relocated) // no need to relocate the player
5823 if (IS_PLAYER(jx, jy)) // player already placed at new position
5825 RemoveField(jx, jy); // temporarily remove newly placed player
5826 DrawLevelField(jx, jy);
5829 if (player->present)
5831 while (player->MovPos)
5833 ScrollPlayer(player, SCROLL_GO_ON);
5834 ScrollScreen(NULL, SCROLL_GO_ON);
5836 AdvanceFrameAndPlayerCounters(player->index_nr);
5840 BackToFront_WithFrameDelay(wait_delay_value);
5843 DrawPlayer(player); // needed here only to cleanup last field
5844 DrawLevelField(player->jx, player->jy); // remove player graphic
5846 player->is_moving = FALSE;
5849 if (IS_CUSTOM_ELEMENT(old_element))
5850 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5852 player->index_bit, leave_side);
5854 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5856 player->index_bit, leave_side);
5858 Tile[jx][jy] = el_player;
5859 InitPlayerField(jx, jy, el_player, TRUE);
5861 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5862 possible that the relocation target field did not contain a player element,
5863 but a walkable element, to which the new player was relocated -- in this
5864 case, restore that (already initialized!) element on the player field */
5865 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5867 Tile[jx][jy] = element; // restore previously existing element
5870 // only visually relocate centered player
5871 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5872 FALSE, level.instant_relocation);
5874 TestIfPlayerTouchesBadThing(jx, jy);
5875 TestIfPlayerTouchesCustomElement(jx, jy);
5877 if (IS_CUSTOM_ELEMENT(element))
5878 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5879 player->index_bit, enter_side);
5881 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5882 player->index_bit, enter_side);
5884 if (player->is_switching)
5886 /* ensure that relocation while still switching an element does not cause
5887 a new element to be treated as also switched directly after relocation
5888 (this is important for teleporter switches that teleport the player to
5889 a place where another teleporter switch is in the same direction, which
5890 would then incorrectly be treated as immediately switched before the
5891 direction key that caused the switch was released) */
5893 player->switch_x += jx - old_jx;
5894 player->switch_y += jy - old_jy;
5898 static void Explode(int ex, int ey, int phase, int mode)
5904 if (game.explosions_delayed)
5906 ExplodeField[ex][ey] = mode;
5910 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5912 int center_element = Tile[ex][ey];
5913 int ce_value = CustomValue[ex][ey];
5914 int ce_score = element_info[center_element].collect_score;
5915 int artwork_element, explosion_element; // set these values later
5917 // remove things displayed in background while burning dynamite
5918 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5921 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5923 // put moving element to center field (and let it explode there)
5924 center_element = MovingOrBlocked2Element(ex, ey);
5925 RemoveMovingField(ex, ey);
5926 Tile[ex][ey] = center_element;
5929 // now "center_element" is finally determined -- set related values now
5930 artwork_element = center_element; // for custom player artwork
5931 explosion_element = center_element; // for custom player artwork
5933 if (IS_PLAYER(ex, ey))
5935 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5937 artwork_element = stored_player[player_nr].artwork_element;
5939 if (level.use_explosion_element[player_nr])
5941 explosion_element = level.explosion_element[player_nr];
5942 artwork_element = explosion_element;
5946 if (mode == EX_TYPE_NORMAL ||
5947 mode == EX_TYPE_CENTER ||
5948 mode == EX_TYPE_CROSS)
5949 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5951 last_phase = element_info[explosion_element].explosion_delay + 1;
5953 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5955 int xx = x - ex + 1;
5956 int yy = y - ey + 1;
5959 if (!IN_LEV_FIELD(x, y) ||
5960 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5961 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5964 element = Tile[x][y];
5966 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5968 element = MovingOrBlocked2Element(x, y);
5970 if (!IS_EXPLOSION_PROOF(element))
5971 RemoveMovingField(x, y);
5974 // indestructible elements can only explode in center (but not flames)
5975 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5976 mode == EX_TYPE_BORDER)) ||
5977 element == EL_FLAMES)
5980 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5981 behaviour, for example when touching a yamyam that explodes to rocks
5982 with active deadly shield, a rock is created under the player !!! */
5983 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5985 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5986 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5987 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5989 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5992 if (IS_ACTIVE_BOMB(element))
5994 // re-activate things under the bomb like gate or penguin
5995 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
6002 // save walkable background elements while explosion on same tile
6003 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
6004 (x != ex || y != ey || mode == EX_TYPE_BORDER))
6005 Back[x][y] = element;
6007 // ignite explodable elements reached by other explosion
6008 if (element == EL_EXPLOSION)
6009 element = Store2[x][y];
6011 if (AmoebaNr[x][y] &&
6012 (element == EL_AMOEBA_FULL ||
6013 element == EL_BD_AMOEBA ||
6014 element == EL_AMOEBA_GROWING))
6016 AmoebaCnt[AmoebaNr[x][y]]--;
6017 AmoebaCnt2[AmoebaNr[x][y]]--;
6022 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
6024 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
6026 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
6028 if (PLAYERINFO(ex, ey)->use_murphy)
6029 Store[x][y] = EL_EMPTY;
6032 // !!! check this case -- currently needed for rnd_rado_negundo_v,
6033 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
6034 else if (IS_PLAYER_ELEMENT(center_element))
6035 Store[x][y] = EL_EMPTY;
6036 else if (center_element == EL_YAMYAM)
6037 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
6038 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
6039 Store[x][y] = element_info[center_element].content.e[xx][yy];
6041 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
6042 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
6043 // otherwise) -- FIX THIS !!!
6044 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
6045 Store[x][y] = element_info[element].content.e[1][1];
6047 else if (!CAN_EXPLODE(element))
6048 Store[x][y] = element_info[element].content.e[1][1];
6051 Store[x][y] = EL_EMPTY;
6053 if (IS_CUSTOM_ELEMENT(center_element))
6054 Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value :
6055 Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score :
6056 Store[x][y] >= EL_PREV_CE_8 &&
6057 Store[x][y] <= EL_NEXT_CE_8 ?
6058 RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) :
6061 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
6062 center_element == EL_AMOEBA_TO_DIAMOND)
6063 Store2[x][y] = element;
6065 Tile[x][y] = EL_EXPLOSION;
6066 GfxElement[x][y] = artwork_element;
6068 ExplodePhase[x][y] = 1;
6069 ExplodeDelay[x][y] = last_phase;
6074 if (center_element == EL_YAMYAM)
6075 game.yamyam_content_nr =
6076 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6088 GfxFrame[x][y] = 0; // restart explosion animation
6090 last_phase = ExplodeDelay[x][y];
6092 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6094 // this can happen if the player leaves an explosion just in time
6095 if (GfxElement[x][y] == EL_UNDEFINED)
6096 GfxElement[x][y] = EL_EMPTY;
6098 border_element = Store2[x][y];
6099 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6100 border_element = StorePlayer[x][y];
6102 if (phase == element_info[border_element].ignition_delay ||
6103 phase == last_phase)
6105 boolean border_explosion = FALSE;
6107 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6108 !PLAYER_EXPLOSION_PROTECTED(x, y))
6110 KillPlayerUnlessExplosionProtected(x, y);
6111 border_explosion = TRUE;
6113 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6115 Tile[x][y] = Store2[x][y];
6118 border_explosion = TRUE;
6120 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6122 AmoebaToDiamond(x, y);
6124 border_explosion = TRUE;
6127 // if an element just explodes due to another explosion (chain-reaction),
6128 // do not immediately end the new explosion when it was the last frame of
6129 // the explosion (as it would be done in the following "if"-statement!)
6130 if (border_explosion && phase == last_phase)
6134 // this can happen if the player was just killed by an explosion
6135 if (GfxElement[x][y] == EL_UNDEFINED)
6136 GfxElement[x][y] = EL_EMPTY;
6138 if (phase == last_phase)
6142 element = Tile[x][y] = Store[x][y];
6143 Store[x][y] = Store2[x][y] = 0;
6144 GfxElement[x][y] = EL_UNDEFINED;
6146 // player can escape from explosions and might therefore be still alive
6147 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6148 element <= EL_PLAYER_IS_EXPLODING_4)
6150 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6151 int explosion_element = EL_PLAYER_1 + player_nr;
6152 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6153 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6155 if (level.use_explosion_element[player_nr])
6156 explosion_element = level.explosion_element[player_nr];
6158 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6159 element_info[explosion_element].content.e[xx][yy]);
6162 // restore probably existing indestructible background element
6163 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6164 element = Tile[x][y] = Back[x][y];
6167 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6168 GfxDir[x][y] = MV_NONE;
6169 ChangeDelay[x][y] = 0;
6170 ChangePage[x][y] = -1;
6172 CustomValue[x][y] = 0;
6174 InitField_WithBug2(x, y, FALSE);
6176 TEST_DrawLevelField(x, y);
6178 TestIfElementTouchesCustomElement(x, y);
6180 if (GFX_CRUMBLED(element))
6181 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6183 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6184 StorePlayer[x][y] = 0;
6186 if (IS_PLAYER_ELEMENT(element))
6187 RelocatePlayer(x, y, element);
6189 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6191 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6192 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6195 TEST_DrawLevelFieldCrumbled(x, y);
6197 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6199 DrawLevelElement(x, y, Back[x][y]);
6200 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6202 else if (IS_WALKABLE_UNDER(Back[x][y]))
6204 DrawLevelGraphic(x, y, graphic, frame);
6205 DrawLevelElementThruMask(x, y, Back[x][y]);
6207 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6208 DrawLevelGraphic(x, y, graphic, frame);
6212 static void DynaExplode(int ex, int ey)
6215 int dynabomb_element = Tile[ex][ey];
6216 int dynabomb_size = 1;
6217 boolean dynabomb_xl = FALSE;
6218 struct PlayerInfo *player;
6219 struct XY *xy = xy_topdown;
6221 if (IS_ACTIVE_BOMB(dynabomb_element))
6223 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6224 dynabomb_size = player->dynabomb_size;
6225 dynabomb_xl = player->dynabomb_xl;
6226 player->dynabombs_left++;
6229 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6231 for (i = 0; i < NUM_DIRECTIONS; i++)
6233 for (j = 1; j <= dynabomb_size; j++)
6235 int x = ex + j * xy[i].x;
6236 int y = ey + j * xy[i].y;
6239 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6242 element = Tile[x][y];
6244 // do not restart explosions of fields with active bombs
6245 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6248 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6250 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6251 !IS_DIGGABLE(element) && !dynabomb_xl)
6257 void Bang(int x, int y)
6259 int element = MovingOrBlocked2Element(x, y);
6260 int explosion_type = EX_TYPE_NORMAL;
6262 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6264 struct PlayerInfo *player = PLAYERINFO(x, y);
6266 element = Tile[x][y] = player->initial_element;
6268 if (level.use_explosion_element[player->index_nr])
6270 int explosion_element = level.explosion_element[player->index_nr];
6272 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6273 explosion_type = EX_TYPE_CROSS;
6274 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6275 explosion_type = EX_TYPE_CENTER;
6283 case EL_BD_BUTTERFLY:
6286 case EL_DARK_YAMYAM:
6290 RaiseScoreElement(element);
6293 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6294 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6295 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6296 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6297 case EL_DYNABOMB_INCREASE_NUMBER:
6298 case EL_DYNABOMB_INCREASE_SIZE:
6299 case EL_DYNABOMB_INCREASE_POWER:
6300 explosion_type = EX_TYPE_DYNA;
6303 case EL_DC_LANDMINE:
6304 explosion_type = EX_TYPE_CENTER;
6309 case EL_LAMP_ACTIVE:
6310 case EL_AMOEBA_TO_DIAMOND:
6311 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6312 explosion_type = EX_TYPE_CENTER;
6316 if (element_info[element].explosion_type == EXPLODES_CROSS)
6317 explosion_type = EX_TYPE_CROSS;
6318 else if (element_info[element].explosion_type == EXPLODES_1X1)
6319 explosion_type = EX_TYPE_CENTER;
6323 if (explosion_type == EX_TYPE_DYNA)
6326 Explode(x, y, EX_PHASE_START, explosion_type);
6328 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6331 static void SplashAcid(int x, int y)
6333 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6334 (!IN_LEV_FIELD(x - 1, y - 2) ||
6335 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6336 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6338 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6339 (!IN_LEV_FIELD(x + 1, y - 2) ||
6340 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6341 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6343 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6346 static void InitBeltMovement(void)
6348 static int belt_base_element[4] =
6350 EL_CONVEYOR_BELT_1_LEFT,
6351 EL_CONVEYOR_BELT_2_LEFT,
6352 EL_CONVEYOR_BELT_3_LEFT,
6353 EL_CONVEYOR_BELT_4_LEFT
6355 static int belt_base_active_element[4] =
6357 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6358 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6359 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6360 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6365 // set frame order for belt animation graphic according to belt direction
6366 for (i = 0; i < NUM_BELTS; i++)
6370 for (j = 0; j < NUM_BELT_PARTS; j++)
6372 int element = belt_base_active_element[belt_nr] + j;
6373 int graphic_1 = el2img(element);
6374 int graphic_2 = el2panelimg(element);
6376 if (game.belt_dir[i] == MV_LEFT)
6378 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6379 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6383 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6384 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6389 SCAN_PLAYFIELD(x, y)
6391 int element = Tile[x][y];
6393 for (i = 0; i < NUM_BELTS; i++)
6395 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6397 int e_belt_nr = getBeltNrFromBeltElement(element);
6400 if (e_belt_nr == belt_nr)
6402 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6404 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6411 static void ToggleBeltSwitch(int x, int y)
6413 static int belt_base_element[4] =
6415 EL_CONVEYOR_BELT_1_LEFT,
6416 EL_CONVEYOR_BELT_2_LEFT,
6417 EL_CONVEYOR_BELT_3_LEFT,
6418 EL_CONVEYOR_BELT_4_LEFT
6420 static int belt_base_active_element[4] =
6422 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6423 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6424 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6425 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6427 static int belt_base_switch_element[4] =
6429 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6430 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6431 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6432 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6434 static int belt_move_dir[4] =
6442 int element = Tile[x][y];
6443 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6444 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6445 int belt_dir = belt_move_dir[belt_dir_nr];
6448 if (!IS_BELT_SWITCH(element))
6451 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6452 game.belt_dir[belt_nr] = belt_dir;
6454 if (belt_dir_nr == 3)
6457 // set frame order for belt animation graphic according to belt direction
6458 for (i = 0; i < NUM_BELT_PARTS; i++)
6460 int element = belt_base_active_element[belt_nr] + i;
6461 int graphic_1 = el2img(element);
6462 int graphic_2 = el2panelimg(element);
6464 if (belt_dir == MV_LEFT)
6466 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6467 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6471 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6472 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6476 SCAN_PLAYFIELD(xx, yy)
6478 int element = Tile[xx][yy];
6480 if (IS_BELT_SWITCH(element))
6482 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6484 if (e_belt_nr == belt_nr)
6486 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6487 TEST_DrawLevelField(xx, yy);
6490 else if (IS_BELT(element) && belt_dir != MV_NONE)
6492 int e_belt_nr = getBeltNrFromBeltElement(element);
6494 if (e_belt_nr == belt_nr)
6496 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6498 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6499 TEST_DrawLevelField(xx, yy);
6502 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6504 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6506 if (e_belt_nr == belt_nr)
6508 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6510 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6511 TEST_DrawLevelField(xx, yy);
6517 static void ToggleSwitchgateSwitch(void)
6521 game.switchgate_pos = !game.switchgate_pos;
6523 SCAN_PLAYFIELD(xx, yy)
6525 int element = Tile[xx][yy];
6527 if (element == EL_SWITCHGATE_SWITCH_UP)
6529 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6530 TEST_DrawLevelField(xx, yy);
6532 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6534 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6535 TEST_DrawLevelField(xx, yy);
6537 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6539 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6540 TEST_DrawLevelField(xx, yy);
6542 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6544 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6545 TEST_DrawLevelField(xx, yy);
6547 else if (element == EL_SWITCHGATE_OPEN ||
6548 element == EL_SWITCHGATE_OPENING)
6550 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6552 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6554 else if (element == EL_SWITCHGATE_CLOSED ||
6555 element == EL_SWITCHGATE_CLOSING)
6557 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6559 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6564 static int getInvisibleActiveFromInvisibleElement(int element)
6566 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6567 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6568 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6572 static int getInvisibleFromInvisibleActiveElement(int element)
6574 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6575 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6576 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6580 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6584 SCAN_PLAYFIELD(x, y)
6586 int element = Tile[x][y];
6588 if (element == EL_LIGHT_SWITCH &&
6589 game.light_time_left > 0)
6591 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6592 TEST_DrawLevelField(x, y);
6594 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6595 game.light_time_left == 0)
6597 Tile[x][y] = EL_LIGHT_SWITCH;
6598 TEST_DrawLevelField(x, y);
6600 else if (element == EL_EMC_DRIPPER &&
6601 game.light_time_left > 0)
6603 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6604 TEST_DrawLevelField(x, y);
6606 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6607 game.light_time_left == 0)
6609 Tile[x][y] = EL_EMC_DRIPPER;
6610 TEST_DrawLevelField(x, y);
6612 else if (element == EL_INVISIBLE_STEELWALL ||
6613 element == EL_INVISIBLE_WALL ||
6614 element == EL_INVISIBLE_SAND)
6616 if (game.light_time_left > 0)
6617 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6619 TEST_DrawLevelField(x, y);
6621 // uncrumble neighbour fields, if needed
6622 if (element == EL_INVISIBLE_SAND)
6623 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6625 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6626 element == EL_INVISIBLE_WALL_ACTIVE ||
6627 element == EL_INVISIBLE_SAND_ACTIVE)
6629 if (game.light_time_left == 0)
6630 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6632 TEST_DrawLevelField(x, y);
6634 // re-crumble neighbour fields, if needed
6635 if (element == EL_INVISIBLE_SAND)
6636 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6641 static void RedrawAllInvisibleElementsForLenses(void)
6645 SCAN_PLAYFIELD(x, y)
6647 int element = Tile[x][y];
6649 if (element == EL_EMC_DRIPPER &&
6650 game.lenses_time_left > 0)
6652 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6653 TEST_DrawLevelField(x, y);
6655 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6656 game.lenses_time_left == 0)
6658 Tile[x][y] = EL_EMC_DRIPPER;
6659 TEST_DrawLevelField(x, y);
6661 else if (element == EL_INVISIBLE_STEELWALL ||
6662 element == EL_INVISIBLE_WALL ||
6663 element == EL_INVISIBLE_SAND)
6665 if (game.lenses_time_left > 0)
6666 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6668 TEST_DrawLevelField(x, y);
6670 // uncrumble neighbour fields, if needed
6671 if (element == EL_INVISIBLE_SAND)
6672 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6674 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6675 element == EL_INVISIBLE_WALL_ACTIVE ||
6676 element == EL_INVISIBLE_SAND_ACTIVE)
6678 if (game.lenses_time_left == 0)
6679 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6681 TEST_DrawLevelField(x, y);
6683 // re-crumble neighbour fields, if needed
6684 if (element == EL_INVISIBLE_SAND)
6685 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6690 static void RedrawAllInvisibleElementsForMagnifier(void)
6694 SCAN_PLAYFIELD(x, y)
6696 int element = Tile[x][y];
6698 if (element == EL_EMC_FAKE_GRASS &&
6699 game.magnify_time_left > 0)
6701 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6702 TEST_DrawLevelField(x, y);
6704 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6705 game.magnify_time_left == 0)
6707 Tile[x][y] = EL_EMC_FAKE_GRASS;
6708 TEST_DrawLevelField(x, y);
6710 else if (IS_GATE_GRAY(element) &&
6711 game.magnify_time_left > 0)
6713 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6714 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6715 IS_EM_GATE_GRAY(element) ?
6716 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6717 IS_EMC_GATE_GRAY(element) ?
6718 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6719 IS_DC_GATE_GRAY(element) ?
6720 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6722 TEST_DrawLevelField(x, y);
6724 else if (IS_GATE_GRAY_ACTIVE(element) &&
6725 game.magnify_time_left == 0)
6727 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6728 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6729 IS_EM_GATE_GRAY_ACTIVE(element) ?
6730 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6731 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6732 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6733 IS_DC_GATE_GRAY_ACTIVE(element) ?
6734 EL_DC_GATE_WHITE_GRAY :
6736 TEST_DrawLevelField(x, y);
6741 static void ToggleLightSwitch(int x, int y)
6743 int element = Tile[x][y];
6745 game.light_time_left =
6746 (element == EL_LIGHT_SWITCH ?
6747 level.time_light * FRAMES_PER_SECOND : 0);
6749 RedrawAllLightSwitchesAndInvisibleElements();
6752 static void ActivateTimegateSwitch(int x, int y)
6756 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6758 SCAN_PLAYFIELD(xx, yy)
6760 int element = Tile[xx][yy];
6762 if (element == EL_TIMEGATE_CLOSED ||
6763 element == EL_TIMEGATE_CLOSING)
6765 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6766 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6770 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6772 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6773 TEST_DrawLevelField(xx, yy);
6779 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6780 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6783 static void Impact(int x, int y)
6785 boolean last_line = (y == lev_fieldy - 1);
6786 boolean object_hit = FALSE;
6787 boolean impact = (last_line || object_hit);
6788 int element = Tile[x][y];
6789 int smashed = EL_STEELWALL;
6791 if (!last_line) // check if element below was hit
6793 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6796 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6797 MovDir[x][y + 1] != MV_DOWN ||
6798 MovPos[x][y + 1] <= TILEY / 2));
6800 // do not smash moving elements that left the smashed field in time
6801 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6802 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6805 #if USE_QUICKSAND_IMPACT_BUGFIX
6806 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6808 RemoveMovingField(x, y + 1);
6809 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6810 Tile[x][y + 2] = EL_ROCK;
6811 TEST_DrawLevelField(x, y + 2);
6816 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6818 RemoveMovingField(x, y + 1);
6819 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6820 Tile[x][y + 2] = EL_ROCK;
6821 TEST_DrawLevelField(x, y + 2);
6828 smashed = MovingOrBlocked2Element(x, y + 1);
6830 impact = (last_line || object_hit);
6833 if (!last_line && smashed == EL_ACID) // element falls into acid
6835 SplashAcid(x, y + 1);
6839 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6840 // only reset graphic animation if graphic really changes after impact
6842 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6844 ResetGfxAnimation(x, y);
6845 TEST_DrawLevelField(x, y);
6848 if (impact && CAN_EXPLODE_IMPACT(element))
6853 else if (impact && element == EL_PEARL &&
6854 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6856 ResetGfxAnimation(x, y);
6858 Tile[x][y] = EL_PEARL_BREAKING;
6859 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6862 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6864 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6869 if (impact && element == EL_AMOEBA_DROP)
6871 if (object_hit && IS_PLAYER(x, y + 1))
6872 KillPlayerUnlessEnemyProtected(x, y + 1);
6873 else if (object_hit && smashed == EL_PENGUIN)
6877 Tile[x][y] = EL_AMOEBA_GROWING;
6878 Store[x][y] = EL_AMOEBA_WET;
6880 ResetRandomAnimationValue(x, y);
6885 if (object_hit) // check which object was hit
6887 if ((CAN_PASS_MAGIC_WALL(element) &&
6888 (smashed == EL_MAGIC_WALL ||
6889 smashed == EL_BD_MAGIC_WALL)) ||
6890 (CAN_PASS_DC_MAGIC_WALL(element) &&
6891 smashed == EL_DC_MAGIC_WALL))
6894 int activated_magic_wall =
6895 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6896 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6897 EL_DC_MAGIC_WALL_ACTIVE);
6899 // activate magic wall / mill
6900 SCAN_PLAYFIELD(xx, yy)
6902 if (Tile[xx][yy] == smashed)
6903 Tile[xx][yy] = activated_magic_wall;
6906 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6907 game.magic_wall_active = TRUE;
6909 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6910 SND_MAGIC_WALL_ACTIVATING :
6911 smashed == EL_BD_MAGIC_WALL ?
6912 SND_BD_MAGIC_WALL_ACTIVATING :
6913 SND_DC_MAGIC_WALL_ACTIVATING));
6916 if (IS_PLAYER(x, y + 1))
6918 if (CAN_SMASH_PLAYER(element))
6920 KillPlayerUnlessEnemyProtected(x, y + 1);
6924 else if (smashed == EL_PENGUIN)
6926 if (CAN_SMASH_PLAYER(element))
6932 else if (element == EL_BD_DIAMOND)
6934 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6940 else if (((element == EL_SP_INFOTRON ||
6941 element == EL_SP_ZONK) &&
6942 (smashed == EL_SP_SNIKSNAK ||
6943 smashed == EL_SP_ELECTRON ||
6944 smashed == EL_SP_DISK_ORANGE)) ||
6945 (element == EL_SP_INFOTRON &&
6946 smashed == EL_SP_DISK_YELLOW))
6951 else if (CAN_SMASH_EVERYTHING(element))
6953 if (IS_CLASSIC_ENEMY(smashed) ||
6954 CAN_EXPLODE_SMASHED(smashed))
6959 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6961 if (smashed == EL_LAMP ||
6962 smashed == EL_LAMP_ACTIVE)
6967 else if (smashed == EL_NUT)
6969 Tile[x][y + 1] = EL_NUT_BREAKING;
6970 PlayLevelSound(x, y, SND_NUT_BREAKING);
6971 RaiseScoreElement(EL_NUT);
6974 else if (smashed == EL_PEARL)
6976 ResetGfxAnimation(x, y);
6978 Tile[x][y + 1] = EL_PEARL_BREAKING;
6979 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6982 else if (smashed == EL_DIAMOND)
6984 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6985 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6988 else if (IS_BELT_SWITCH(smashed))
6990 ToggleBeltSwitch(x, y + 1);
6992 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6993 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6994 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6995 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6997 ToggleSwitchgateSwitch();
6999 else if (smashed == EL_LIGHT_SWITCH ||
7000 smashed == EL_LIGHT_SWITCH_ACTIVE)
7002 ToggleLightSwitch(x, y + 1);
7006 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7008 CheckElementChangeBySide(x, y + 1, smashed, element,
7009 CE_SWITCHED, CH_SIDE_TOP);
7010 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
7016 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
7021 // play sound of magic wall / mill
7023 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7024 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
7025 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
7027 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7028 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
7029 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7030 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
7031 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7032 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
7037 // play sound of object that hits the ground
7038 if (last_line || object_hit)
7039 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
7042 static void TurnRoundExt(int x, int y)
7054 { 0, 0 }, { 0, 0 }, { 0, 0 },
7059 int left, right, back;
7063 { MV_DOWN, MV_UP, MV_RIGHT },
7064 { MV_UP, MV_DOWN, MV_LEFT },
7066 { MV_LEFT, MV_RIGHT, MV_DOWN },
7070 { MV_RIGHT, MV_LEFT, MV_UP }
7073 int element = Tile[x][y];
7074 int move_pattern = element_info[element].move_pattern;
7076 int old_move_dir = MovDir[x][y];
7077 int left_dir = turn[old_move_dir].left;
7078 int right_dir = turn[old_move_dir].right;
7079 int back_dir = turn[old_move_dir].back;
7081 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
7082 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
7083 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
7084 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
7086 int left_x = x + left_dx, left_y = y + left_dy;
7087 int right_x = x + right_dx, right_y = y + right_dy;
7088 int move_x = x + move_dx, move_y = y + move_dy;
7092 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7094 TestIfBadThingTouchesOtherBadThing(x, y);
7096 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7097 MovDir[x][y] = right_dir;
7098 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7099 MovDir[x][y] = left_dir;
7101 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7103 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7106 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7108 TestIfBadThingTouchesOtherBadThing(x, y);
7110 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7111 MovDir[x][y] = left_dir;
7112 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7113 MovDir[x][y] = right_dir;
7115 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7117 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7120 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7122 TestIfBadThingTouchesOtherBadThing(x, y);
7124 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7125 MovDir[x][y] = left_dir;
7126 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7127 MovDir[x][y] = right_dir;
7129 if (MovDir[x][y] != old_move_dir)
7132 else if (element == EL_YAMYAM)
7134 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7135 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7137 if (can_turn_left && can_turn_right)
7138 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7139 else if (can_turn_left)
7140 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7141 else if (can_turn_right)
7142 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7144 MovDir[x][y] = back_dir;
7146 MovDelay[x][y] = 16 + 16 * RND(3);
7148 else if (element == EL_DARK_YAMYAM)
7150 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7152 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7155 if (can_turn_left && can_turn_right)
7156 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7157 else if (can_turn_left)
7158 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7159 else if (can_turn_right)
7160 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7162 MovDir[x][y] = back_dir;
7164 MovDelay[x][y] = 16 + 16 * RND(3);
7166 else if (element == EL_PACMAN)
7168 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7169 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7171 if (can_turn_left && can_turn_right)
7172 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7173 else if (can_turn_left)
7174 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7175 else if (can_turn_right)
7176 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7178 MovDir[x][y] = back_dir;
7180 MovDelay[x][y] = 6 + RND(40);
7182 else if (element == EL_PIG)
7184 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7185 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7186 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7187 boolean should_turn_left, should_turn_right, should_move_on;
7189 int rnd = RND(rnd_value);
7191 should_turn_left = (can_turn_left &&
7193 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7194 y + back_dy + left_dy)));
7195 should_turn_right = (can_turn_right &&
7197 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7198 y + back_dy + right_dy)));
7199 should_move_on = (can_move_on &&
7202 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7203 y + move_dy + left_dy) ||
7204 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7205 y + move_dy + right_dy)));
7207 if (should_turn_left || should_turn_right || should_move_on)
7209 if (should_turn_left && should_turn_right && should_move_on)
7210 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7211 rnd < 2 * rnd_value / 3 ? right_dir :
7213 else if (should_turn_left && should_turn_right)
7214 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7215 else if (should_turn_left && should_move_on)
7216 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7217 else if (should_turn_right && should_move_on)
7218 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7219 else if (should_turn_left)
7220 MovDir[x][y] = left_dir;
7221 else if (should_turn_right)
7222 MovDir[x][y] = right_dir;
7223 else if (should_move_on)
7224 MovDir[x][y] = old_move_dir;
7226 else if (can_move_on && rnd > rnd_value / 8)
7227 MovDir[x][y] = old_move_dir;
7228 else if (can_turn_left && can_turn_right)
7229 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7230 else if (can_turn_left && rnd > rnd_value / 8)
7231 MovDir[x][y] = left_dir;
7232 else if (can_turn_right && rnd > rnd_value/8)
7233 MovDir[x][y] = right_dir;
7235 MovDir[x][y] = back_dir;
7237 xx = x + move_xy[MovDir[x][y]].dx;
7238 yy = y + move_xy[MovDir[x][y]].dy;
7240 if (!IN_LEV_FIELD(xx, yy) ||
7241 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7242 MovDir[x][y] = old_move_dir;
7246 else if (element == EL_DRAGON)
7248 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7249 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7250 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7252 int rnd = RND(rnd_value);
7254 if (can_move_on && rnd > rnd_value / 8)
7255 MovDir[x][y] = old_move_dir;
7256 else if (can_turn_left && can_turn_right)
7257 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7258 else if (can_turn_left && rnd > rnd_value / 8)
7259 MovDir[x][y] = left_dir;
7260 else if (can_turn_right && rnd > rnd_value / 8)
7261 MovDir[x][y] = right_dir;
7263 MovDir[x][y] = back_dir;
7265 xx = x + move_xy[MovDir[x][y]].dx;
7266 yy = y + move_xy[MovDir[x][y]].dy;
7268 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7269 MovDir[x][y] = old_move_dir;
7273 else if (element == EL_MOLE)
7275 boolean can_move_on =
7276 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7277 IS_AMOEBOID(Tile[move_x][move_y]) ||
7278 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7281 boolean can_turn_left =
7282 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7283 IS_AMOEBOID(Tile[left_x][left_y])));
7285 boolean can_turn_right =
7286 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7287 IS_AMOEBOID(Tile[right_x][right_y])));
7289 if (can_turn_left && can_turn_right)
7290 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7291 else if (can_turn_left)
7292 MovDir[x][y] = left_dir;
7294 MovDir[x][y] = right_dir;
7297 if (MovDir[x][y] != old_move_dir)
7300 else if (element == EL_BALLOON)
7302 MovDir[x][y] = game.wind_direction;
7305 else if (element == EL_SPRING)
7307 if (MovDir[x][y] & MV_HORIZONTAL)
7309 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7310 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7312 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7313 ResetGfxAnimation(move_x, move_y);
7314 TEST_DrawLevelField(move_x, move_y);
7316 MovDir[x][y] = back_dir;
7318 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7319 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7320 MovDir[x][y] = MV_NONE;
7325 else if (element == EL_ROBOT ||
7326 element == EL_SATELLITE ||
7327 element == EL_PENGUIN ||
7328 element == EL_EMC_ANDROID)
7330 int attr_x = -1, attr_y = -1;
7332 if (game.all_players_gone)
7334 attr_x = game.exit_x;
7335 attr_y = game.exit_y;
7341 for (i = 0; i < MAX_PLAYERS; i++)
7343 struct PlayerInfo *player = &stored_player[i];
7344 int jx = player->jx, jy = player->jy;
7346 if (!player->active)
7350 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7358 if (element == EL_ROBOT &&
7359 game.robot_wheel_x >= 0 &&
7360 game.robot_wheel_y >= 0 &&
7361 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7362 game.engine_version < VERSION_IDENT(3,1,0,0)))
7364 attr_x = game.robot_wheel_x;
7365 attr_y = game.robot_wheel_y;
7368 if (element == EL_PENGUIN)
7371 struct XY *xy = xy_topdown;
7373 for (i = 0; i < NUM_DIRECTIONS; i++)
7375 int ex = x + xy[i].x;
7376 int ey = y + xy[i].y;
7378 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7379 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7380 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7381 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7390 MovDir[x][y] = MV_NONE;
7392 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7393 else if (attr_x > x)
7394 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7396 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7397 else if (attr_y > y)
7398 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7400 if (element == EL_ROBOT)
7404 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7405 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7406 Moving2Blocked(x, y, &newx, &newy);
7408 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7409 MovDelay[x][y] = 8 + 8 * !RND(3);
7411 MovDelay[x][y] = 16;
7413 else if (element == EL_PENGUIN)
7419 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7421 boolean first_horiz = RND(2);
7422 int new_move_dir = MovDir[x][y];
7425 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7426 Moving2Blocked(x, y, &newx, &newy);
7428 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7432 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7433 Moving2Blocked(x, y, &newx, &newy);
7435 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7438 MovDir[x][y] = old_move_dir;
7442 else if (element == EL_SATELLITE)
7448 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7450 boolean first_horiz = RND(2);
7451 int new_move_dir = MovDir[x][y];
7454 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7455 Moving2Blocked(x, y, &newx, &newy);
7457 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7461 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7462 Moving2Blocked(x, y, &newx, &newy);
7464 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7467 MovDir[x][y] = old_move_dir;
7471 else if (element == EL_EMC_ANDROID)
7473 static int check_pos[16] =
7475 -1, // 0 => (invalid)
7478 -1, // 3 => (invalid)
7480 0, // 5 => MV_LEFT | MV_UP
7481 2, // 6 => MV_RIGHT | MV_UP
7482 -1, // 7 => (invalid)
7484 6, // 9 => MV_LEFT | MV_DOWN
7485 4, // 10 => MV_RIGHT | MV_DOWN
7486 -1, // 11 => (invalid)
7487 -1, // 12 => (invalid)
7488 -1, // 13 => (invalid)
7489 -1, // 14 => (invalid)
7490 -1, // 15 => (invalid)
7498 { -1, -1, MV_LEFT | MV_UP },
7500 { +1, -1, MV_RIGHT | MV_UP },
7501 { +1, 0, MV_RIGHT },
7502 { +1, +1, MV_RIGHT | MV_DOWN },
7504 { -1, +1, MV_LEFT | MV_DOWN },
7507 int start_pos, check_order;
7508 boolean can_clone = FALSE;
7511 // check if there is any free field around current position
7512 for (i = 0; i < 8; i++)
7514 int newx = x + check_xy[i].dx;
7515 int newy = y + check_xy[i].dy;
7517 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7525 if (can_clone) // randomly find an element to clone
7529 start_pos = check_pos[RND(8)];
7530 check_order = (RND(2) ? -1 : +1);
7532 for (i = 0; i < 8; i++)
7534 int pos_raw = start_pos + i * check_order;
7535 int pos = (pos_raw + 8) % 8;
7536 int newx = x + check_xy[pos].dx;
7537 int newy = y + check_xy[pos].dy;
7539 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7541 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7542 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7544 Store[x][y] = Tile[newx][newy];
7553 if (can_clone) // randomly find a direction to move
7557 start_pos = check_pos[RND(8)];
7558 check_order = (RND(2) ? -1 : +1);
7560 for (i = 0; i < 8; i++)
7562 int pos_raw = start_pos + i * check_order;
7563 int pos = (pos_raw + 8) % 8;
7564 int newx = x + check_xy[pos].dx;
7565 int newy = y + check_xy[pos].dy;
7566 int new_move_dir = check_xy[pos].dir;
7568 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7570 MovDir[x][y] = new_move_dir;
7571 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7580 if (can_clone) // cloning and moving successful
7583 // cannot clone -- try to move towards player
7585 start_pos = check_pos[MovDir[x][y] & 0x0f];
7586 check_order = (RND(2) ? -1 : +1);
7588 for (i = 0; i < 3; i++)
7590 // first check start_pos, then previous/next or (next/previous) pos
7591 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7592 int pos = (pos_raw + 8) % 8;
7593 int newx = x + check_xy[pos].dx;
7594 int newy = y + check_xy[pos].dy;
7595 int new_move_dir = check_xy[pos].dir;
7597 if (IS_PLAYER(newx, newy))
7600 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7602 MovDir[x][y] = new_move_dir;
7603 MovDelay[x][y] = level.android_move_time * 8 + 1;
7610 else if (move_pattern == MV_TURNING_LEFT ||
7611 move_pattern == MV_TURNING_RIGHT ||
7612 move_pattern == MV_TURNING_LEFT_RIGHT ||
7613 move_pattern == MV_TURNING_RIGHT_LEFT ||
7614 move_pattern == MV_TURNING_RANDOM ||
7615 move_pattern == MV_ALL_DIRECTIONS)
7617 boolean can_turn_left =
7618 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7619 boolean can_turn_right =
7620 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y);
7622 if (element_info[element].move_stepsize == 0) // "not moving"
7625 if (move_pattern == MV_TURNING_LEFT)
7626 MovDir[x][y] = left_dir;
7627 else if (move_pattern == MV_TURNING_RIGHT)
7628 MovDir[x][y] = right_dir;
7629 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7630 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7631 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7632 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7633 else if (move_pattern == MV_TURNING_RANDOM)
7634 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7635 can_turn_right && !can_turn_left ? right_dir :
7636 RND(2) ? left_dir : right_dir);
7637 else if (can_turn_left && can_turn_right)
7638 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7639 else if (can_turn_left)
7640 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7641 else if (can_turn_right)
7642 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7644 MovDir[x][y] = back_dir;
7646 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7648 else if (move_pattern == MV_HORIZONTAL ||
7649 move_pattern == MV_VERTICAL)
7651 if (move_pattern & old_move_dir)
7652 MovDir[x][y] = back_dir;
7653 else if (move_pattern == MV_HORIZONTAL)
7654 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7655 else if (move_pattern == MV_VERTICAL)
7656 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7658 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7660 else if (move_pattern & MV_ANY_DIRECTION)
7662 MovDir[x][y] = move_pattern;
7663 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7665 else if (move_pattern & MV_WIND_DIRECTION)
7667 MovDir[x][y] = game.wind_direction;
7668 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7670 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7672 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7673 MovDir[x][y] = left_dir;
7674 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7675 MovDir[x][y] = right_dir;
7677 if (MovDir[x][y] != old_move_dir)
7678 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7680 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7682 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7683 MovDir[x][y] = right_dir;
7684 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7685 MovDir[x][y] = left_dir;
7687 if (MovDir[x][y] != old_move_dir)
7688 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7690 else if (move_pattern == MV_TOWARDS_PLAYER ||
7691 move_pattern == MV_AWAY_FROM_PLAYER)
7693 int attr_x = -1, attr_y = -1;
7695 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7697 if (game.all_players_gone)
7699 attr_x = game.exit_x;
7700 attr_y = game.exit_y;
7706 for (i = 0; i < MAX_PLAYERS; i++)
7708 struct PlayerInfo *player = &stored_player[i];
7709 int jx = player->jx, jy = player->jy;
7711 if (!player->active)
7715 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7723 MovDir[x][y] = MV_NONE;
7725 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7726 else if (attr_x > x)
7727 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7729 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7730 else if (attr_y > y)
7731 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7733 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7735 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7737 boolean first_horiz = RND(2);
7738 int new_move_dir = MovDir[x][y];
7740 if (element_info[element].move_stepsize == 0) // "not moving"
7742 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7743 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7749 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7750 Moving2Blocked(x, y, &newx, &newy);
7752 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7756 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7757 Moving2Blocked(x, y, &newx, &newy);
7759 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7762 MovDir[x][y] = old_move_dir;
7765 else if (move_pattern == MV_WHEN_PUSHED ||
7766 move_pattern == MV_WHEN_DROPPED)
7768 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7769 MovDir[x][y] = MV_NONE;
7773 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7775 struct XY *test_xy = xy_topdown;
7776 static int test_dir[4] =
7783 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7784 int move_preference = -1000000; // start with very low preference
7785 int new_move_dir = MV_NONE;
7786 int start_test = RND(4);
7789 for (i = 0; i < NUM_DIRECTIONS; i++)
7791 int j = (start_test + i) % 4;
7792 int move_dir = test_dir[j];
7793 int move_dir_preference;
7795 xx = x + test_xy[j].x;
7796 yy = y + test_xy[j].y;
7798 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7799 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7801 new_move_dir = move_dir;
7806 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7809 move_dir_preference = -1 * RunnerVisit[xx][yy];
7810 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7811 move_dir_preference = PlayerVisit[xx][yy];
7813 if (move_dir_preference > move_preference)
7815 // prefer field that has not been visited for the longest time
7816 move_preference = move_dir_preference;
7817 new_move_dir = move_dir;
7819 else if (move_dir_preference == move_preference &&
7820 move_dir == old_move_dir)
7822 // prefer last direction when all directions are preferred equally
7823 move_preference = move_dir_preference;
7824 new_move_dir = move_dir;
7828 MovDir[x][y] = new_move_dir;
7829 if (old_move_dir != new_move_dir)
7830 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7834 static void TurnRound(int x, int y)
7836 int direction = MovDir[x][y];
7840 GfxDir[x][y] = MovDir[x][y];
7842 if (direction != MovDir[x][y])
7846 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7848 ResetGfxFrame(x, y);
7851 static boolean JustBeingPushed(int x, int y)
7855 for (i = 0; i < MAX_PLAYERS; i++)
7857 struct PlayerInfo *player = &stored_player[i];
7859 if (player->active && player->is_pushing && player->MovPos)
7861 int next_jx = player->jx + (player->jx - player->last_jx);
7862 int next_jy = player->jy + (player->jy - player->last_jy);
7864 if (x == next_jx && y == next_jy)
7872 static void StartMoving(int x, int y)
7874 boolean started_moving = FALSE; // some elements can fall _and_ move
7875 int element = Tile[x][y];
7880 if (MovDelay[x][y] == 0)
7881 GfxAction[x][y] = ACTION_DEFAULT;
7883 if (CAN_FALL(element) && y < lev_fieldy - 1)
7885 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7886 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7887 if (JustBeingPushed(x, y))
7890 if (element == EL_QUICKSAND_FULL)
7892 if (IS_FREE(x, y + 1))
7894 InitMovingField(x, y, MV_DOWN);
7895 started_moving = TRUE;
7897 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7898 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7899 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7900 Store[x][y] = EL_ROCK;
7902 Store[x][y] = EL_ROCK;
7905 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7907 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7909 if (!MovDelay[x][y])
7911 MovDelay[x][y] = TILEY + 1;
7913 ResetGfxAnimation(x, y);
7914 ResetGfxAnimation(x, y + 1);
7919 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7920 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7927 Tile[x][y] = EL_QUICKSAND_EMPTY;
7928 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7929 Store[x][y + 1] = Store[x][y];
7932 PlayLevelSoundAction(x, y, ACTION_FILLING);
7934 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7936 if (!MovDelay[x][y])
7938 MovDelay[x][y] = TILEY + 1;
7940 ResetGfxAnimation(x, y);
7941 ResetGfxAnimation(x, y + 1);
7946 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7947 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7954 Tile[x][y] = EL_QUICKSAND_EMPTY;
7955 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7956 Store[x][y + 1] = Store[x][y];
7959 PlayLevelSoundAction(x, y, ACTION_FILLING);
7962 else if (element == EL_QUICKSAND_FAST_FULL)
7964 if (IS_FREE(x, y + 1))
7966 InitMovingField(x, y, MV_DOWN);
7967 started_moving = TRUE;
7969 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7970 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7971 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7972 Store[x][y] = EL_ROCK;
7974 Store[x][y] = EL_ROCK;
7977 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7979 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7981 if (!MovDelay[x][y])
7983 MovDelay[x][y] = TILEY + 1;
7985 ResetGfxAnimation(x, y);
7986 ResetGfxAnimation(x, y + 1);
7991 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7992 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7999 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8000 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
8001 Store[x][y + 1] = Store[x][y];
8004 PlayLevelSoundAction(x, y, ACTION_FILLING);
8006 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8008 if (!MovDelay[x][y])
8010 MovDelay[x][y] = TILEY + 1;
8012 ResetGfxAnimation(x, y);
8013 ResetGfxAnimation(x, y + 1);
8018 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
8019 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
8026 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
8027 Tile[x][y + 1] = EL_QUICKSAND_FULL;
8028 Store[x][y + 1] = Store[x][y];
8031 PlayLevelSoundAction(x, y, ACTION_FILLING);
8034 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8035 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
8037 InitMovingField(x, y, MV_DOWN);
8038 started_moving = TRUE;
8040 Tile[x][y] = EL_QUICKSAND_FILLING;
8041 Store[x][y] = element;
8043 PlayLevelSoundAction(x, y, ACTION_FILLING);
8045 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
8046 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
8048 InitMovingField(x, y, MV_DOWN);
8049 started_moving = TRUE;
8051 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
8052 Store[x][y] = element;
8054 PlayLevelSoundAction(x, y, ACTION_FILLING);
8056 else if (element == EL_MAGIC_WALL_FULL)
8058 if (IS_FREE(x, y + 1))
8060 InitMovingField(x, y, MV_DOWN);
8061 started_moving = TRUE;
8063 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
8064 Store[x][y] = EL_CHANGED(Store[x][y]);
8066 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
8068 if (!MovDelay[x][y])
8069 MovDelay[x][y] = TILEY / 4 + 1;
8078 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
8079 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
8080 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8084 else if (element == EL_BD_MAGIC_WALL_FULL)
8086 if (IS_FREE(x, y + 1))
8088 InitMovingField(x, y, MV_DOWN);
8089 started_moving = TRUE;
8091 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8092 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8094 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8096 if (!MovDelay[x][y])
8097 MovDelay[x][y] = TILEY / 4 + 1;
8106 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8107 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8108 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8112 else if (element == EL_DC_MAGIC_WALL_FULL)
8114 if (IS_FREE(x, y + 1))
8116 InitMovingField(x, y, MV_DOWN);
8117 started_moving = TRUE;
8119 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8120 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8122 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8124 if (!MovDelay[x][y])
8125 MovDelay[x][y] = TILEY / 4 + 1;
8134 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8135 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8136 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8140 else if ((CAN_PASS_MAGIC_WALL(element) &&
8141 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8142 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8143 (CAN_PASS_DC_MAGIC_WALL(element) &&
8144 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8147 InitMovingField(x, y, MV_DOWN);
8148 started_moving = TRUE;
8151 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8152 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8153 EL_DC_MAGIC_WALL_FILLING);
8154 Store[x][y] = element;
8156 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8158 SplashAcid(x, y + 1);
8160 InitMovingField(x, y, MV_DOWN);
8161 started_moving = TRUE;
8163 Store[x][y] = EL_ACID;
8166 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8167 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8168 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8169 CAN_FALL(element) && WasJustFalling[x][y] &&
8170 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8172 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8173 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8174 (Tile[x][y + 1] == EL_BLOCKED)))
8176 /* this is needed for a special case not covered by calling "Impact()"
8177 from "ContinueMoving()": if an element moves to a tile directly below
8178 another element which was just falling on that tile (which was empty
8179 in the previous frame), the falling element above would just stop
8180 instead of smashing the element below (in previous version, the above
8181 element was just checked for "moving" instead of "falling", resulting
8182 in incorrect smashes caused by horizontal movement of the above
8183 element; also, the case of the player being the element to smash was
8184 simply not covered here... :-/ ) */
8186 CheckCollision[x][y] = 0;
8187 CheckImpact[x][y] = 0;
8191 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8193 if (MovDir[x][y] == MV_NONE)
8195 InitMovingField(x, y, MV_DOWN);
8196 started_moving = TRUE;
8199 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8201 if (WasJustFalling[x][y]) // prevent animation from being restarted
8202 MovDir[x][y] = MV_DOWN;
8204 InitMovingField(x, y, MV_DOWN);
8205 started_moving = TRUE;
8207 else if (element == EL_AMOEBA_DROP)
8209 Tile[x][y] = EL_AMOEBA_GROWING;
8210 Store[x][y] = EL_AMOEBA_WET;
8212 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8213 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8214 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8215 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8217 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8218 (IS_FREE(x - 1, y + 1) ||
8219 Tile[x - 1][y + 1] == EL_ACID));
8220 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8221 (IS_FREE(x + 1, y + 1) ||
8222 Tile[x + 1][y + 1] == EL_ACID));
8223 boolean can_fall_any = (can_fall_left || can_fall_right);
8224 boolean can_fall_both = (can_fall_left && can_fall_right);
8225 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8227 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8229 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8230 can_fall_right = FALSE;
8231 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8232 can_fall_left = FALSE;
8233 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8234 can_fall_right = FALSE;
8235 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8236 can_fall_left = FALSE;
8238 can_fall_any = (can_fall_left || can_fall_right);
8239 can_fall_both = FALSE;
8244 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8245 can_fall_right = FALSE; // slip down on left side
8247 can_fall_left = !(can_fall_right = RND(2));
8249 can_fall_both = FALSE;
8254 // if not determined otherwise, prefer left side for slipping down
8255 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8256 started_moving = TRUE;
8259 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8261 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8262 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8263 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8264 int belt_dir = game.belt_dir[belt_nr];
8266 if ((belt_dir == MV_LEFT && left_is_free) ||
8267 (belt_dir == MV_RIGHT && right_is_free))
8269 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8271 InitMovingField(x, y, belt_dir);
8272 started_moving = TRUE;
8274 Pushed[x][y] = TRUE;
8275 Pushed[nextx][y] = TRUE;
8277 GfxAction[x][y] = ACTION_DEFAULT;
8281 MovDir[x][y] = 0; // if element was moving, stop it
8286 // not "else if" because of elements that can fall and move (EL_SPRING)
8287 if (CAN_MOVE(element) && !started_moving)
8289 int move_pattern = element_info[element].move_pattern;
8292 Moving2Blocked(x, y, &newx, &newy);
8294 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8297 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8298 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8300 WasJustMoving[x][y] = 0;
8301 CheckCollision[x][y] = 0;
8303 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8305 if (Tile[x][y] != element) // element has changed
8309 if (!MovDelay[x][y]) // start new movement phase
8311 // all objects that can change their move direction after each step
8312 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8314 if (element != EL_YAMYAM &&
8315 element != EL_DARK_YAMYAM &&
8316 element != EL_PACMAN &&
8317 !(move_pattern & MV_ANY_DIRECTION) &&
8318 move_pattern != MV_TURNING_LEFT &&
8319 move_pattern != MV_TURNING_RIGHT &&
8320 move_pattern != MV_TURNING_LEFT_RIGHT &&
8321 move_pattern != MV_TURNING_RIGHT_LEFT &&
8322 move_pattern != MV_TURNING_RANDOM)
8326 if (MovDelay[x][y] && (element == EL_BUG ||
8327 element == EL_SPACESHIP ||
8328 element == EL_SP_SNIKSNAK ||
8329 element == EL_SP_ELECTRON ||
8330 element == EL_MOLE))
8331 TEST_DrawLevelField(x, y);
8335 if (MovDelay[x][y]) // wait some time before next movement
8339 if (element == EL_ROBOT ||
8340 element == EL_YAMYAM ||
8341 element == EL_DARK_YAMYAM)
8343 DrawLevelElementAnimationIfNeeded(x, y, element);
8344 PlayLevelSoundAction(x, y, ACTION_WAITING);
8346 else if (element == EL_SP_ELECTRON)
8347 DrawLevelElementAnimationIfNeeded(x, y, element);
8348 else if (element == EL_DRAGON)
8351 int dir = MovDir[x][y];
8352 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8353 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8354 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8355 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8356 dir == MV_UP ? IMG_FLAMES_1_UP :
8357 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8358 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8360 GfxAction[x][y] = ACTION_ATTACKING;
8362 if (IS_PLAYER(x, y))
8363 DrawPlayerField(x, y);
8365 TEST_DrawLevelField(x, y);
8367 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8369 for (i = 1; i <= 3; i++)
8371 int xx = x + i * dx;
8372 int yy = y + i * dy;
8373 int sx = SCREENX(xx);
8374 int sy = SCREENY(yy);
8375 int flame_graphic = graphic + (i - 1);
8377 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8382 int flamed = MovingOrBlocked2Element(xx, yy);
8384 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8387 RemoveMovingField(xx, yy);
8389 ChangeDelay[xx][yy] = 0;
8391 Tile[xx][yy] = EL_FLAMES;
8393 if (IN_SCR_FIELD(sx, sy))
8395 TEST_DrawLevelFieldCrumbled(xx, yy);
8396 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8401 if (Tile[xx][yy] == EL_FLAMES)
8402 Tile[xx][yy] = EL_EMPTY;
8403 TEST_DrawLevelField(xx, yy);
8408 if (MovDelay[x][y]) // element still has to wait some time
8410 PlayLevelSoundAction(x, y, ACTION_WAITING);
8416 // now make next step
8418 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8420 if (DONT_COLLIDE_WITH(element) &&
8421 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8422 !PLAYER_ENEMY_PROTECTED(newx, newy))
8424 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8429 else if (CAN_MOVE_INTO_ACID(element) &&
8430 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8431 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8432 (MovDir[x][y] == MV_DOWN ||
8433 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8435 SplashAcid(newx, newy);
8436 Store[x][y] = EL_ACID;
8438 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8440 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8441 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8442 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8443 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8446 TEST_DrawLevelField(x, y);
8448 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8449 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8450 DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0);
8452 game.friends_still_needed--;
8453 if (!game.friends_still_needed &&
8455 game.all_players_gone)
8460 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8462 if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING)
8463 TEST_DrawLevelField(newx, newy);
8465 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8467 else if (!IS_FREE(newx, newy))
8469 GfxAction[x][y] = ACTION_WAITING;
8471 if (IS_PLAYER(x, y))
8472 DrawPlayerField(x, y);
8474 TEST_DrawLevelField(x, y);
8479 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8481 if (IS_FOOD_PIG(Tile[newx][newy]))
8483 if (IS_MOVING(newx, newy))
8484 RemoveMovingField(newx, newy);
8487 Tile[newx][newy] = EL_EMPTY;
8488 TEST_DrawLevelField(newx, newy);
8491 PlayLevelSound(x, y, SND_PIG_DIGGING);
8493 else if (!IS_FREE(newx, newy))
8495 if (IS_PLAYER(x, y))
8496 DrawPlayerField(x, y);
8498 TEST_DrawLevelField(x, y);
8503 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8505 if (Store[x][y] != EL_EMPTY)
8507 boolean can_clone = FALSE;
8510 // check if element to clone is still there
8511 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8513 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8521 // cannot clone or target field not free anymore -- do not clone
8522 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8523 Store[x][y] = EL_EMPTY;
8526 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8528 if (IS_MV_DIAGONAL(MovDir[x][y]))
8530 int diagonal_move_dir = MovDir[x][y];
8531 int stored = Store[x][y];
8532 int change_delay = 8;
8535 // android is moving diagonally
8537 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8539 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8540 GfxElement[x][y] = EL_EMC_ANDROID;
8541 GfxAction[x][y] = ACTION_SHRINKING;
8542 GfxDir[x][y] = diagonal_move_dir;
8543 ChangeDelay[x][y] = change_delay;
8545 if (Store[x][y] == EL_EMPTY)
8546 Store[x][y] = GfxElementEmpty[x][y];
8548 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8551 DrawLevelGraphicAnimation(x, y, graphic);
8552 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8554 if (Tile[newx][newy] == EL_ACID)
8556 SplashAcid(newx, newy);
8561 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8563 Store[newx][newy] = EL_EMC_ANDROID;
8564 GfxElement[newx][newy] = EL_EMC_ANDROID;
8565 GfxAction[newx][newy] = ACTION_GROWING;
8566 GfxDir[newx][newy] = diagonal_move_dir;
8567 ChangeDelay[newx][newy] = change_delay;
8569 graphic = el_act_dir2img(GfxElement[newx][newy],
8570 GfxAction[newx][newy], GfxDir[newx][newy]);
8572 DrawLevelGraphicAnimation(newx, newy, graphic);
8573 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8579 Tile[newx][newy] = EL_EMPTY;
8580 TEST_DrawLevelField(newx, newy);
8582 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8585 else if (!IS_FREE(newx, newy))
8590 else if (IS_CUSTOM_ELEMENT(element) &&
8591 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8593 if (!DigFieldByCE(newx, newy, element))
8596 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8598 RunnerVisit[x][y] = FrameCounter;
8599 PlayerVisit[x][y] /= 8; // expire player visit path
8602 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8604 if (!IS_FREE(newx, newy))
8606 if (IS_PLAYER(x, y))
8607 DrawPlayerField(x, y);
8609 TEST_DrawLevelField(x, y);
8615 boolean wanna_flame = !RND(10);
8616 int dx = newx - x, dy = newy - y;
8617 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8618 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8619 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8620 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8621 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8622 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8625 IS_CLASSIC_ENEMY(element1) ||
8626 IS_CLASSIC_ENEMY(element2)) &&
8627 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8628 element1 != EL_FLAMES && element2 != EL_FLAMES)
8630 ResetGfxAnimation(x, y);
8631 GfxAction[x][y] = ACTION_ATTACKING;
8633 if (IS_PLAYER(x, y))
8634 DrawPlayerField(x, y);
8636 TEST_DrawLevelField(x, y);
8638 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8640 MovDelay[x][y] = 50;
8642 Tile[newx][newy] = EL_FLAMES;
8643 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8644 Tile[newx1][newy1] = EL_FLAMES;
8645 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8646 Tile[newx2][newy2] = EL_FLAMES;
8652 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8653 Tile[newx][newy] == EL_DIAMOND)
8655 if (IS_MOVING(newx, newy))
8656 RemoveMovingField(newx, newy);
8659 Tile[newx][newy] = EL_EMPTY;
8660 TEST_DrawLevelField(newx, newy);
8663 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8665 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8666 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8668 if (AmoebaNr[newx][newy])
8670 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8671 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8672 Tile[newx][newy] == EL_BD_AMOEBA)
8673 AmoebaCnt[AmoebaNr[newx][newy]]--;
8676 if (IS_MOVING(newx, newy))
8678 RemoveMovingField(newx, newy);
8682 Tile[newx][newy] = EL_EMPTY;
8683 TEST_DrawLevelField(newx, newy);
8686 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8688 else if ((element == EL_PACMAN || element == EL_MOLE)
8689 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8691 if (AmoebaNr[newx][newy])
8693 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8694 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8695 Tile[newx][newy] == EL_BD_AMOEBA)
8696 AmoebaCnt[AmoebaNr[newx][newy]]--;
8699 if (element == EL_MOLE)
8701 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8702 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8704 ResetGfxAnimation(x, y);
8705 GfxAction[x][y] = ACTION_DIGGING;
8706 TEST_DrawLevelField(x, y);
8708 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8710 return; // wait for shrinking amoeba
8712 else // element == EL_PACMAN
8714 Tile[newx][newy] = EL_EMPTY;
8715 TEST_DrawLevelField(newx, newy);
8716 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8719 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8720 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8721 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8723 // wait for shrinking amoeba to completely disappear
8726 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8728 // object was running against a wall
8732 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8733 DrawLevelElementAnimation(x, y, element);
8735 if (DONT_TOUCH(element))
8736 TestIfBadThingTouchesPlayer(x, y);
8741 InitMovingField(x, y, MovDir[x][y]);
8743 PlayLevelSoundAction(x, y, ACTION_MOVING);
8747 ContinueMoving(x, y);
8750 void ContinueMoving(int x, int y)
8752 int element = Tile[x][y];
8753 struct ElementInfo *ei = &element_info[element];
8754 int direction = MovDir[x][y];
8755 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8756 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8757 int newx = x + dx, newy = y + dy;
8758 int stored = Store[x][y];
8759 int stored_new = Store[newx][newy];
8760 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8761 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8762 boolean last_line = (newy == lev_fieldy - 1);
8763 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8765 if (pushed_by_player) // special case: moving object pushed by player
8767 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos));
8769 else if (use_step_delay) // special case: moving object has step delay
8771 if (!MovDelay[x][y])
8772 MovPos[x][y] += getElementMoveStepsize(x, y);
8777 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8781 TEST_DrawLevelField(x, y);
8783 return; // element is still waiting
8786 else // normal case: generically moving object
8788 MovPos[x][y] += getElementMoveStepsize(x, y);
8791 if (ABS(MovPos[x][y]) < TILEX)
8793 TEST_DrawLevelField(x, y);
8795 return; // element is still moving
8798 // element reached destination field
8800 Tile[x][y] = EL_EMPTY;
8801 Tile[newx][newy] = element;
8802 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8804 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8806 element = Tile[newx][newy] = EL_ACID;
8808 else if (element == EL_MOLE)
8810 Tile[x][y] = EL_SAND;
8812 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8814 else if (element == EL_QUICKSAND_FILLING)
8816 element = Tile[newx][newy] = get_next_element(element);
8817 Store[newx][newy] = Store[x][y];
8819 else if (element == EL_QUICKSAND_EMPTYING)
8821 Tile[x][y] = get_next_element(element);
8822 element = Tile[newx][newy] = Store[x][y];
8824 else if (element == EL_QUICKSAND_FAST_FILLING)
8826 element = Tile[newx][newy] = get_next_element(element);
8827 Store[newx][newy] = Store[x][y];
8829 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8831 Tile[x][y] = get_next_element(element);
8832 element = Tile[newx][newy] = Store[x][y];
8834 else if (element == EL_MAGIC_WALL_FILLING)
8836 element = Tile[newx][newy] = get_next_element(element);
8837 if (!game.magic_wall_active)
8838 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8839 Store[newx][newy] = Store[x][y];
8841 else if (element == EL_MAGIC_WALL_EMPTYING)
8843 Tile[x][y] = get_next_element(element);
8844 if (!game.magic_wall_active)
8845 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8846 element = Tile[newx][newy] = Store[x][y];
8848 InitField(newx, newy, FALSE);
8850 else if (element == EL_BD_MAGIC_WALL_FILLING)
8852 element = Tile[newx][newy] = get_next_element(element);
8853 if (!game.magic_wall_active)
8854 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8855 Store[newx][newy] = Store[x][y];
8857 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8859 Tile[x][y] = get_next_element(element);
8860 if (!game.magic_wall_active)
8861 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8862 element = Tile[newx][newy] = Store[x][y];
8864 InitField(newx, newy, FALSE);
8866 else if (element == EL_DC_MAGIC_WALL_FILLING)
8868 element = Tile[newx][newy] = get_next_element(element);
8869 if (!game.magic_wall_active)
8870 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8871 Store[newx][newy] = Store[x][y];
8873 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8875 Tile[x][y] = get_next_element(element);
8876 if (!game.magic_wall_active)
8877 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8878 element = Tile[newx][newy] = Store[x][y];
8880 InitField(newx, newy, FALSE);
8882 else if (element == EL_AMOEBA_DROPPING)
8884 Tile[x][y] = get_next_element(element);
8885 element = Tile[newx][newy] = Store[x][y];
8887 else if (element == EL_SOKOBAN_OBJECT)
8890 Tile[x][y] = Back[x][y];
8892 if (Back[newx][newy])
8893 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8895 Back[x][y] = Back[newx][newy] = 0;
8898 Store[x][y] = EL_EMPTY;
8903 MovDelay[newx][newy] = 0;
8905 if (CAN_CHANGE_OR_HAS_ACTION(element))
8907 // copy element change control values to new field
8908 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8909 ChangePage[newx][newy] = ChangePage[x][y];
8910 ChangeCount[newx][newy] = ChangeCount[x][y];
8911 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8914 CustomValue[newx][newy] = CustomValue[x][y];
8916 ChangeDelay[x][y] = 0;
8917 ChangePage[x][y] = -1;
8918 ChangeCount[x][y] = 0;
8919 ChangeEvent[x][y] = -1;
8921 CustomValue[x][y] = 0;
8923 // copy animation control values to new field
8924 GfxFrame[newx][newy] = GfxFrame[x][y];
8925 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8926 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8927 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8929 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8931 // some elements can leave other elements behind after moving
8932 if (ei->move_leave_element != EL_EMPTY &&
8933 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8934 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8936 int move_leave_element = ei->move_leave_element;
8938 // this makes it possible to leave the removed element again
8939 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8940 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8942 Tile[x][y] = move_leave_element;
8944 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8945 MovDir[x][y] = direction;
8947 InitField(x, y, FALSE);
8949 if (GFX_CRUMBLED(Tile[x][y]))
8950 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8952 if (IS_PLAYER_ELEMENT(move_leave_element))
8953 RelocatePlayer(x, y, move_leave_element);
8956 // do this after checking for left-behind element
8957 ResetGfxAnimation(x, y); // reset animation values for old field
8959 if (!CAN_MOVE(element) ||
8960 (CAN_FALL(element) && direction == MV_DOWN &&
8961 (element == EL_SPRING ||
8962 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8963 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8964 GfxDir[x][y] = MovDir[newx][newy] = 0;
8966 TEST_DrawLevelField(x, y);
8967 TEST_DrawLevelField(newx, newy);
8969 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8971 // prevent pushed element from moving on in pushed direction
8972 if (pushed_by_player && CAN_MOVE(element) &&
8973 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8974 !(element_info[element].move_pattern & direction))
8975 TurnRound(newx, newy);
8977 // prevent elements on conveyor belt from moving on in last direction
8978 if (pushed_by_conveyor && CAN_FALL(element) &&
8979 direction & MV_HORIZONTAL)
8980 MovDir[newx][newy] = 0;
8982 if (!pushed_by_player)
8984 int nextx = newx + dx, nexty = newy + dy;
8985 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8987 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8989 if (CAN_FALL(element) && direction == MV_DOWN)
8990 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8992 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8993 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8995 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8996 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8999 if (DONT_TOUCH(element)) // object may be nasty to player or others
9001 TestIfBadThingTouchesPlayer(newx, newy);
9002 TestIfBadThingTouchesFriend(newx, newy);
9004 if (!IS_CUSTOM_ELEMENT(element))
9005 TestIfBadThingTouchesOtherBadThing(newx, newy);
9007 else if (element == EL_PENGUIN)
9008 TestIfFriendTouchesBadThing(newx, newy);
9010 if (DONT_GET_HIT_BY(element))
9012 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
9015 // give the player one last chance (one more frame) to move away
9016 if (CAN_FALL(element) && direction == MV_DOWN &&
9017 (last_line || (!IS_FREE(x, newy + 1) &&
9018 (!IS_PLAYER(x, newy + 1) ||
9019 game.engine_version < VERSION_IDENT(3,1,1,0)))))
9022 if (pushed_by_player && !game.use_change_when_pushing_bug)
9024 int push_side = MV_DIR_OPPOSITE(direction);
9025 struct PlayerInfo *player = PLAYERINFO(x, y);
9027 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
9028 player->index_bit, push_side);
9029 CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X,
9030 player->index_bit, push_side);
9033 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
9034 MovDelay[newx][newy] = 1;
9036 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
9038 TestIfElementTouchesCustomElement(x, y); // empty or new element
9039 TestIfElementHitsCustomElement(newx, newy, direction);
9040 TestIfPlayerTouchesCustomElement(newx, newy);
9041 TestIfElementTouchesCustomElement(newx, newy);
9043 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
9044 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
9045 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
9046 MV_DIR_OPPOSITE(direction));
9049 int AmoebaNeighbourNr(int ax, int ay)
9052 int element = Tile[ax][ay];
9054 struct XY *xy = xy_topdown;
9056 for (i = 0; i < NUM_DIRECTIONS; i++)
9058 int x = ax + xy[i].x;
9059 int y = ay + xy[i].y;
9061 if (!IN_LEV_FIELD(x, y))
9064 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
9065 group_nr = AmoebaNr[x][y];
9071 static void AmoebaMerge(int ax, int ay)
9073 int i, x, y, xx, yy;
9074 int new_group_nr = AmoebaNr[ax][ay];
9075 struct XY *xy = xy_topdown;
9077 if (new_group_nr == 0)
9080 for (i = 0; i < NUM_DIRECTIONS; i++)
9085 if (!IN_LEV_FIELD(x, y))
9088 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9089 Tile[x][y] == EL_BD_AMOEBA ||
9090 Tile[x][y] == EL_AMOEBA_DEAD) &&
9091 AmoebaNr[x][y] != new_group_nr)
9093 int old_group_nr = AmoebaNr[x][y];
9095 if (old_group_nr == 0)
9098 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9099 AmoebaCnt[old_group_nr] = 0;
9100 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9101 AmoebaCnt2[old_group_nr] = 0;
9103 SCAN_PLAYFIELD(xx, yy)
9105 if (AmoebaNr[xx][yy] == old_group_nr)
9106 AmoebaNr[xx][yy] = new_group_nr;
9112 void AmoebaToDiamond(int ax, int ay)
9116 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9118 int group_nr = AmoebaNr[ax][ay];
9123 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9124 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9130 SCAN_PLAYFIELD(x, y)
9132 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9135 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9139 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9140 SND_AMOEBA_TURNING_TO_GEM :
9141 SND_AMOEBA_TURNING_TO_ROCK));
9146 struct XY *xy = xy_topdown;
9148 for (i = 0; i < NUM_DIRECTIONS; i++)
9153 if (!IN_LEV_FIELD(x, y))
9156 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9158 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9159 SND_AMOEBA_TURNING_TO_GEM :
9160 SND_AMOEBA_TURNING_TO_ROCK));
9167 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9170 int group_nr = AmoebaNr[ax][ay];
9171 boolean done = FALSE;
9176 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9177 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9183 SCAN_PLAYFIELD(x, y)
9185 if (AmoebaNr[x][y] == group_nr &&
9186 (Tile[x][y] == EL_AMOEBA_DEAD ||
9187 Tile[x][y] == EL_BD_AMOEBA ||
9188 Tile[x][y] == EL_AMOEBA_GROWING))
9191 Tile[x][y] = new_element;
9192 InitField(x, y, FALSE);
9193 TEST_DrawLevelField(x, y);
9199 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9200 SND_BD_AMOEBA_TURNING_TO_ROCK :
9201 SND_BD_AMOEBA_TURNING_TO_GEM));
9204 static void AmoebaGrowing(int x, int y)
9206 static DelayCounter sound_delay = { 0 };
9208 if (!MovDelay[x][y]) // start new growing cycle
9212 if (DelayReached(&sound_delay))
9214 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9215 sound_delay.value = 30;
9219 if (MovDelay[x][y]) // wait some time before growing bigger
9222 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9224 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9225 6 - MovDelay[x][y]);
9227 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9230 if (!MovDelay[x][y])
9232 Tile[x][y] = Store[x][y];
9234 TEST_DrawLevelField(x, y);
9239 static void AmoebaShrinking(int x, int y)
9241 static DelayCounter sound_delay = { 0 };
9243 if (!MovDelay[x][y]) // start new shrinking cycle
9247 if (DelayReached(&sound_delay))
9248 sound_delay.value = 30;
9251 if (MovDelay[x][y]) // wait some time before shrinking
9254 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9256 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9257 6 - MovDelay[x][y]);
9259 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9262 if (!MovDelay[x][y])
9264 Tile[x][y] = EL_EMPTY;
9265 TEST_DrawLevelField(x, y);
9267 // don't let mole enter this field in this cycle;
9268 // (give priority to objects falling to this field from above)
9274 static void AmoebaReproduce(int ax, int ay)
9277 int element = Tile[ax][ay];
9278 int graphic = el2img(element);
9279 int newax = ax, neway = ay;
9280 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9281 struct XY *xy = xy_topdown;
9283 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9285 Tile[ax][ay] = EL_AMOEBA_DEAD;
9286 TEST_DrawLevelField(ax, ay);
9290 if (IS_ANIMATED(graphic))
9291 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9293 if (!MovDelay[ax][ay]) // start making new amoeba field
9294 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9296 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9299 if (MovDelay[ax][ay])
9303 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9306 int x = ax + xy[start].x;
9307 int y = ay + xy[start].y;
9309 if (!IN_LEV_FIELD(x, y))
9312 if (IS_FREE(x, y) ||
9313 CAN_GROW_INTO(Tile[x][y]) ||
9314 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9315 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9321 if (newax == ax && neway == ay)
9324 else // normal or "filled" (BD style) amoeba
9327 boolean waiting_for_player = FALSE;
9329 for (i = 0; i < NUM_DIRECTIONS; i++)
9331 int j = (start + i) % 4;
9332 int x = ax + xy[j].x;
9333 int y = ay + xy[j].y;
9335 if (!IN_LEV_FIELD(x, y))
9338 if (IS_FREE(x, y) ||
9339 CAN_GROW_INTO(Tile[x][y]) ||
9340 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9341 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9347 else if (IS_PLAYER(x, y))
9348 waiting_for_player = TRUE;
9351 if (newax == ax && neway == ay) // amoeba cannot grow
9353 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9355 Tile[ax][ay] = EL_AMOEBA_DEAD;
9356 TEST_DrawLevelField(ax, ay);
9357 AmoebaCnt[AmoebaNr[ax][ay]]--;
9359 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9361 if (element == EL_AMOEBA_FULL)
9362 AmoebaToDiamond(ax, ay);
9363 else if (element == EL_BD_AMOEBA)
9364 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9369 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9371 // amoeba gets larger by growing in some direction
9373 int new_group_nr = AmoebaNr[ax][ay];
9376 if (new_group_nr == 0)
9378 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9380 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9386 AmoebaNr[newax][neway] = new_group_nr;
9387 AmoebaCnt[new_group_nr]++;
9388 AmoebaCnt2[new_group_nr]++;
9390 // if amoeba touches other amoeba(s) after growing, unify them
9391 AmoebaMerge(newax, neway);
9393 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9395 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9401 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9402 (neway == lev_fieldy - 1 && newax != ax))
9404 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9405 Store[newax][neway] = element;
9407 else if (neway == ay || element == EL_EMC_DRIPPER)
9409 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9411 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9415 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9416 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9417 Store[ax][ay] = EL_AMOEBA_DROP;
9418 ContinueMoving(ax, ay);
9422 TEST_DrawLevelField(newax, neway);
9425 static void Life(int ax, int ay)
9429 int element = Tile[ax][ay];
9430 int graphic = el2img(element);
9431 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9433 boolean changed = FALSE;
9435 if (IS_ANIMATED(graphic))
9436 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9441 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9442 MovDelay[ax][ay] = life_time;
9444 if (MovDelay[ax][ay]) // wait some time before next cycle
9447 if (MovDelay[ax][ay])
9451 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9453 int xx = ax + x1, yy = ay + y1;
9454 int old_element = Tile[xx][yy];
9455 int num_neighbours = 0;
9457 if (!IN_LEV_FIELD(xx, yy))
9460 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9462 int x = xx + x2, y = yy + y2;
9464 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9467 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9468 boolean is_neighbour = FALSE;
9470 if (level.use_life_bugs)
9472 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9473 (IS_FREE(x, y) && Stop[x][y]));
9476 (Last[x][y] == element || is_player_cell);
9482 boolean is_free = FALSE;
9484 if (level.use_life_bugs)
9485 is_free = (IS_FREE(xx, yy));
9487 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9489 if (xx == ax && yy == ay) // field in the middle
9491 if (num_neighbours < life_parameter[0] ||
9492 num_neighbours > life_parameter[1])
9494 Tile[xx][yy] = EL_EMPTY;
9495 if (Tile[xx][yy] != old_element)
9496 TEST_DrawLevelField(xx, yy);
9497 Stop[xx][yy] = TRUE;
9501 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9502 { // free border field
9503 if (num_neighbours >= life_parameter[2] &&
9504 num_neighbours <= life_parameter[3])
9506 Tile[xx][yy] = element;
9507 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9508 if (Tile[xx][yy] != old_element)
9509 TEST_DrawLevelField(xx, yy);
9510 Stop[xx][yy] = TRUE;
9517 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9518 SND_GAME_OF_LIFE_GROWING);
9521 static void InitRobotWheel(int x, int y)
9523 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9526 static void RunRobotWheel(int x, int y)
9528 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9531 static void StopRobotWheel(int x, int y)
9533 if (game.robot_wheel_x == x &&
9534 game.robot_wheel_y == y)
9536 game.robot_wheel_x = -1;
9537 game.robot_wheel_y = -1;
9538 game.robot_wheel_active = FALSE;
9542 static void InitTimegateWheel(int x, int y)
9544 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9547 static void RunTimegateWheel(int x, int y)
9549 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9552 static void InitMagicBallDelay(int x, int y)
9554 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9557 static void ActivateMagicBall(int bx, int by)
9561 if (level.ball_random)
9563 int pos_border = RND(8); // select one of the eight border elements
9564 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9565 int xx = pos_content % 3;
9566 int yy = pos_content / 3;
9571 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9572 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9576 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9578 int xx = x - bx + 1;
9579 int yy = y - by + 1;
9581 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9582 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9586 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9589 static void CheckExit(int x, int y)
9591 if (game.gems_still_needed > 0 ||
9592 game.sokoban_fields_still_needed > 0 ||
9593 game.sokoban_objects_still_needed > 0 ||
9594 game.lights_still_needed > 0)
9596 int element = Tile[x][y];
9597 int graphic = el2img(element);
9599 if (IS_ANIMATED(graphic))
9600 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9605 // do not re-open exit door closed after last player
9606 if (game.all_players_gone)
9609 Tile[x][y] = EL_EXIT_OPENING;
9611 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9614 static void CheckExitEM(int x, int y)
9616 if (game.gems_still_needed > 0 ||
9617 game.sokoban_fields_still_needed > 0 ||
9618 game.sokoban_objects_still_needed > 0 ||
9619 game.lights_still_needed > 0)
9621 int element = Tile[x][y];
9622 int graphic = el2img(element);
9624 if (IS_ANIMATED(graphic))
9625 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9630 // do not re-open exit door closed after last player
9631 if (game.all_players_gone)
9634 Tile[x][y] = EL_EM_EXIT_OPENING;
9636 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9639 static void CheckExitSteel(int x, int y)
9641 if (game.gems_still_needed > 0 ||
9642 game.sokoban_fields_still_needed > 0 ||
9643 game.sokoban_objects_still_needed > 0 ||
9644 game.lights_still_needed > 0)
9646 int element = Tile[x][y];
9647 int graphic = el2img(element);
9649 if (IS_ANIMATED(graphic))
9650 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9655 // do not re-open exit door closed after last player
9656 if (game.all_players_gone)
9659 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9661 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9664 static void CheckExitSteelEM(int x, int y)
9666 if (game.gems_still_needed > 0 ||
9667 game.sokoban_fields_still_needed > 0 ||
9668 game.sokoban_objects_still_needed > 0 ||
9669 game.lights_still_needed > 0)
9671 int element = Tile[x][y];
9672 int graphic = el2img(element);
9674 if (IS_ANIMATED(graphic))
9675 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9680 // do not re-open exit door closed after last player
9681 if (game.all_players_gone)
9684 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9686 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9689 static void CheckExitSP(int x, int y)
9691 if (game.gems_still_needed > 0)
9693 int element = Tile[x][y];
9694 int graphic = el2img(element);
9696 if (IS_ANIMATED(graphic))
9697 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9702 // do not re-open exit door closed after last player
9703 if (game.all_players_gone)
9706 Tile[x][y] = EL_SP_EXIT_OPENING;
9708 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9711 static void CloseAllOpenTimegates(void)
9715 SCAN_PLAYFIELD(x, y)
9717 int element = Tile[x][y];
9719 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9721 Tile[x][y] = EL_TIMEGATE_CLOSING;
9723 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9728 static void DrawTwinkleOnField(int x, int y)
9730 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9733 if (Tile[x][y] == EL_BD_DIAMOND)
9736 if (MovDelay[x][y] == 0) // next animation frame
9737 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9739 if (MovDelay[x][y] != 0) // wait some time before next frame
9743 DrawLevelElementAnimation(x, y, Tile[x][y]);
9745 if (MovDelay[x][y] != 0)
9747 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9748 10 - MovDelay[x][y]);
9750 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9755 static void WallGrowing(int x, int y)
9759 if (!MovDelay[x][y]) // next animation frame
9760 MovDelay[x][y] = 3 * delay;
9762 if (MovDelay[x][y]) // wait some time before next frame
9766 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9768 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9769 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9771 DrawLevelGraphic(x, y, graphic, frame);
9774 if (!MovDelay[x][y])
9776 if (MovDir[x][y] == MV_LEFT)
9778 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9779 TEST_DrawLevelField(x - 1, y);
9781 else if (MovDir[x][y] == MV_RIGHT)
9783 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9784 TEST_DrawLevelField(x + 1, y);
9786 else if (MovDir[x][y] == MV_UP)
9788 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9789 TEST_DrawLevelField(x, y - 1);
9793 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9794 TEST_DrawLevelField(x, y + 1);
9797 Tile[x][y] = Store[x][y];
9799 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9800 TEST_DrawLevelField(x, y);
9805 static void CheckWallGrowing(int ax, int ay)
9807 int element = Tile[ax][ay];
9808 int graphic = el2img(element);
9809 boolean free_top = FALSE;
9810 boolean free_bottom = FALSE;
9811 boolean free_left = FALSE;
9812 boolean free_right = FALSE;
9813 boolean stop_top = FALSE;
9814 boolean stop_bottom = FALSE;
9815 boolean stop_left = FALSE;
9816 boolean stop_right = FALSE;
9817 boolean new_wall = FALSE;
9819 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9820 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9821 element == EL_EXPANDABLE_STEELWALL_ANY);
9823 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9824 element == EL_EXPANDABLE_WALL_ANY ||
9825 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9826 element == EL_EXPANDABLE_STEELWALL_ANY);
9828 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9829 element == EL_EXPANDABLE_WALL_ANY ||
9830 element == EL_EXPANDABLE_WALL ||
9831 element == EL_BD_EXPANDABLE_WALL ||
9832 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9833 element == EL_EXPANDABLE_STEELWALL_ANY);
9835 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9836 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9838 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9839 element == EL_EXPANDABLE_WALL ||
9840 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9842 int wall_growing = (is_steelwall ?
9843 EL_EXPANDABLE_STEELWALL_GROWING :
9844 EL_EXPANDABLE_WALL_GROWING);
9846 int gfx_wall_growing_up = (is_steelwall ?
9847 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9848 IMG_EXPANDABLE_WALL_GROWING_UP);
9849 int gfx_wall_growing_down = (is_steelwall ?
9850 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9851 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9852 int gfx_wall_growing_left = (is_steelwall ?
9853 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9854 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9855 int gfx_wall_growing_right = (is_steelwall ?
9856 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9857 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9859 if (IS_ANIMATED(graphic))
9860 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9862 if (!MovDelay[ax][ay]) // start building new wall
9863 MovDelay[ax][ay] = 6;
9865 if (MovDelay[ax][ay]) // wait some time before building new wall
9868 if (MovDelay[ax][ay])
9872 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9874 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9876 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9878 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9885 Tile[ax][ay - 1] = wall_growing;
9886 Store[ax][ay - 1] = element;
9887 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9889 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9890 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9897 Tile[ax][ay + 1] = wall_growing;
9898 Store[ax][ay + 1] = element;
9899 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9901 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9902 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
9908 if (grow_horizontal)
9912 Tile[ax - 1][ay] = wall_growing;
9913 Store[ax - 1][ay] = element;
9914 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9916 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9917 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
9924 Tile[ax + 1][ay] = wall_growing;
9925 Store[ax + 1][ay] = element;
9926 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9928 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9929 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
9935 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
9936 TEST_DrawLevelField(ax, ay);
9938 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9940 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9942 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9944 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9947 if (((stop_top && stop_bottom) || stop_horizontal) &&
9948 ((stop_left && stop_right) || stop_vertical))
9949 Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL);
9952 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9955 static void CheckForDragon(int x, int y)
9958 boolean dragon_found = FALSE;
9959 struct XY *xy = xy_topdown;
9961 for (i = 0; i < NUM_DIRECTIONS; i++)
9963 for (j = 0; j < 4; j++)
9965 int xx = x + j * xy[i].x;
9966 int yy = y + j * xy[i].y;
9968 if (IN_LEV_FIELD(xx, yy) &&
9969 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9971 if (Tile[xx][yy] == EL_DRAGON)
9972 dragon_found = TRUE;
9981 for (i = 0; i < NUM_DIRECTIONS; i++)
9983 for (j = 0; j < 3; j++)
9985 int xx = x + j * xy[i].x;
9986 int yy = y + j * xy[i].y;
9988 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9990 Tile[xx][yy] = EL_EMPTY;
9991 TEST_DrawLevelField(xx, yy);
10000 static void InitBuggyBase(int x, int y)
10002 int element = Tile[x][y];
10003 int activating_delay = FRAMES_PER_SECOND / 4;
10005 ChangeDelay[x][y] =
10006 (element == EL_SP_BUGGY_BASE ?
10007 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10008 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10010 element == EL_SP_BUGGY_BASE_ACTIVE ?
10011 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10014 static void WarnBuggyBase(int x, int y)
10017 struct XY *xy = xy_topdown;
10019 for (i = 0; i < NUM_DIRECTIONS; i++)
10021 int xx = x + xy[i].x;
10022 int yy = y + xy[i].y;
10024 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10026 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10033 static void InitTrap(int x, int y)
10035 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10038 static void ActivateTrap(int x, int y)
10040 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10043 static void ChangeActiveTrap(int x, int y)
10045 int graphic = IMG_TRAP_ACTIVE;
10047 // if new animation frame was drawn, correct crumbled sand border
10048 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10049 TEST_DrawLevelFieldCrumbled(x, y);
10052 static int getSpecialActionElement(int element, int number, int base_element)
10054 return (element != EL_EMPTY ? element :
10055 number != -1 ? base_element + number - 1 :
10059 static int getModifiedActionNumber(int value_old, int operator, int operand,
10060 int value_min, int value_max)
10062 int value_new = (operator == CA_MODE_SET ? operand :
10063 operator == CA_MODE_ADD ? value_old + operand :
10064 operator == CA_MODE_SUBTRACT ? value_old - operand :
10065 operator == CA_MODE_MULTIPLY ? value_old * operand :
10066 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10067 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10070 return (value_new < value_min ? value_min :
10071 value_new > value_max ? value_max :
10075 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10077 struct ElementInfo *ei = &element_info[element];
10078 struct ElementChangeInfo *change = &ei->change_page[page];
10079 int target_element = change->target_element;
10080 int action_type = change->action_type;
10081 int action_mode = change->action_mode;
10082 int action_arg = change->action_arg;
10083 int action_element = change->action_element;
10086 if (!change->has_action)
10089 // ---------- determine action paramater values -----------------------------
10091 int level_time_value =
10092 (level.time > 0 ? TimeLeft :
10095 int action_arg_element_raw =
10096 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10097 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10098 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10099 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10100 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10101 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10102 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10104 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10106 int action_arg_direction =
10107 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10108 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10109 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10110 change->actual_trigger_side :
10111 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10112 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10115 int action_arg_number_min =
10116 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10119 int action_arg_number_max =
10120 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10121 action_type == CA_SET_LEVEL_GEMS ? 999 :
10122 action_type == CA_SET_LEVEL_TIME ? 9999 :
10123 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10124 action_type == CA_SET_CE_VALUE ? 9999 :
10125 action_type == CA_SET_CE_SCORE ? 9999 :
10128 int action_arg_number_reset =
10129 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10130 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10131 action_type == CA_SET_LEVEL_TIME ? level.time :
10132 action_type == CA_SET_LEVEL_SCORE ? 0 :
10133 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10134 action_type == CA_SET_CE_SCORE ? 0 :
10137 int action_arg_number =
10138 (action_arg <= CA_ARG_MAX ? action_arg :
10139 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10140 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10141 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10142 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10143 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10144 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10145 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10146 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10147 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10148 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10149 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10150 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10151 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10152 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10153 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10154 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10155 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10156 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10157 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10158 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10159 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10162 int action_arg_number_old =
10163 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10164 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10165 action_type == CA_SET_LEVEL_SCORE ? game.score :
10166 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10167 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10170 int action_arg_number_new =
10171 getModifiedActionNumber(action_arg_number_old,
10172 action_mode, action_arg_number,
10173 action_arg_number_min, action_arg_number_max);
10175 int trigger_player_bits =
10176 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10177 change->actual_trigger_player_bits : change->trigger_player);
10179 int action_arg_player_bits =
10180 (action_arg >= CA_ARG_PLAYER_1 &&
10181 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10182 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10183 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10186 // ---------- execute action -----------------------------------------------
10188 switch (action_type)
10195 // ---------- level actions ----------------------------------------------
10197 case CA_RESTART_LEVEL:
10199 game.restart_level = TRUE;
10204 case CA_SHOW_ENVELOPE:
10206 int element = getSpecialActionElement(action_arg_element,
10207 action_arg_number, EL_ENVELOPE_1);
10209 if (IS_ENVELOPE(element))
10210 local_player->show_envelope = element;
10215 case CA_SET_LEVEL_TIME:
10217 if (level.time > 0) // only modify limited time value
10219 TimeLeft = action_arg_number_new;
10221 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10223 DisplayGameControlValues();
10225 if (!TimeLeft && game.time_limit)
10226 for (i = 0; i < MAX_PLAYERS; i++)
10227 KillPlayer(&stored_player[i]);
10233 case CA_SET_LEVEL_SCORE:
10235 game.score = action_arg_number_new;
10237 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10239 DisplayGameControlValues();
10244 case CA_SET_LEVEL_GEMS:
10246 game.gems_still_needed = action_arg_number_new;
10248 game.snapshot.collected_item = TRUE;
10250 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10252 DisplayGameControlValues();
10257 case CA_SET_LEVEL_WIND:
10259 game.wind_direction = action_arg_direction;
10264 case CA_SET_LEVEL_RANDOM_SEED:
10266 // ensure that setting a new random seed while playing is predictable
10267 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10272 // ---------- player actions ---------------------------------------------
10274 case CA_MOVE_PLAYER:
10275 case CA_MOVE_PLAYER_NEW:
10277 // automatically move to the next field in specified direction
10278 for (i = 0; i < MAX_PLAYERS; i++)
10279 if (trigger_player_bits & (1 << i))
10280 if (action_type == CA_MOVE_PLAYER ||
10281 stored_player[i].MovPos == 0)
10282 stored_player[i].programmed_action = action_arg_direction;
10287 case CA_EXIT_PLAYER:
10289 for (i = 0; i < MAX_PLAYERS; i++)
10290 if (action_arg_player_bits & (1 << i))
10291 ExitPlayer(&stored_player[i]);
10293 if (game.players_still_needed == 0)
10299 case CA_KILL_PLAYER:
10301 for (i = 0; i < MAX_PLAYERS; i++)
10302 if (action_arg_player_bits & (1 << i))
10303 KillPlayer(&stored_player[i]);
10308 case CA_SET_PLAYER_KEYS:
10310 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10311 int element = getSpecialActionElement(action_arg_element,
10312 action_arg_number, EL_KEY_1);
10314 if (IS_KEY(element))
10316 for (i = 0; i < MAX_PLAYERS; i++)
10318 if (trigger_player_bits & (1 << i))
10320 stored_player[i].key[KEY_NR(element)] = key_state;
10322 DrawGameDoorValues();
10330 case CA_SET_PLAYER_SPEED:
10332 for (i = 0; i < MAX_PLAYERS; i++)
10334 if (trigger_player_bits & (1 << i))
10336 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10338 if (action_arg == CA_ARG_SPEED_FASTER &&
10339 stored_player[i].cannot_move)
10341 action_arg_number = STEPSIZE_VERY_SLOW;
10343 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10344 action_arg == CA_ARG_SPEED_FASTER)
10346 action_arg_number = 2;
10347 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10350 else if (action_arg == CA_ARG_NUMBER_RESET)
10352 action_arg_number = level.initial_player_stepsize[i];
10356 getModifiedActionNumber(move_stepsize,
10359 action_arg_number_min,
10360 action_arg_number_max);
10362 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10369 case CA_SET_PLAYER_SHIELD:
10371 for (i = 0; i < MAX_PLAYERS; i++)
10373 if (trigger_player_bits & (1 << i))
10375 if (action_arg == CA_ARG_SHIELD_OFF)
10377 stored_player[i].shield_normal_time_left = 0;
10378 stored_player[i].shield_deadly_time_left = 0;
10380 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10382 stored_player[i].shield_normal_time_left = 999999;
10384 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10386 stored_player[i].shield_normal_time_left = 999999;
10387 stored_player[i].shield_deadly_time_left = 999999;
10395 case CA_SET_PLAYER_GRAVITY:
10397 for (i = 0; i < MAX_PLAYERS; i++)
10399 if (trigger_player_bits & (1 << i))
10401 stored_player[i].gravity =
10402 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10403 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10404 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10405 stored_player[i].gravity);
10412 case CA_SET_PLAYER_ARTWORK:
10414 for (i = 0; i < MAX_PLAYERS; i++)
10416 if (trigger_player_bits & (1 << i))
10418 int artwork_element = action_arg_element;
10420 if (action_arg == CA_ARG_ELEMENT_RESET)
10422 (level.use_artwork_element[i] ? level.artwork_element[i] :
10423 stored_player[i].element_nr);
10425 if (stored_player[i].artwork_element != artwork_element)
10426 stored_player[i].Frame = 0;
10428 stored_player[i].artwork_element = artwork_element;
10430 SetPlayerWaiting(&stored_player[i], FALSE);
10432 // set number of special actions for bored and sleeping animation
10433 stored_player[i].num_special_action_bored =
10434 get_num_special_action(artwork_element,
10435 ACTION_BORING_1, ACTION_BORING_LAST);
10436 stored_player[i].num_special_action_sleeping =
10437 get_num_special_action(artwork_element,
10438 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10445 case CA_SET_PLAYER_INVENTORY:
10447 for (i = 0; i < MAX_PLAYERS; i++)
10449 struct PlayerInfo *player = &stored_player[i];
10452 if (trigger_player_bits & (1 << i))
10454 int inventory_element = action_arg_element;
10456 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10457 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10458 action_arg == CA_ARG_ELEMENT_ACTION)
10460 int element = inventory_element;
10461 int collect_count = element_info[element].collect_count_initial;
10463 if (!IS_CUSTOM_ELEMENT(element))
10466 if (collect_count == 0)
10467 player->inventory_infinite_element = element;
10469 for (k = 0; k < collect_count; k++)
10470 if (player->inventory_size < MAX_INVENTORY_SIZE)
10471 player->inventory_element[player->inventory_size++] =
10474 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10475 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10476 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10478 if (player->inventory_infinite_element != EL_UNDEFINED &&
10479 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10480 action_arg_element_raw))
10481 player->inventory_infinite_element = EL_UNDEFINED;
10483 for (k = 0, j = 0; j < player->inventory_size; j++)
10485 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10486 action_arg_element_raw))
10487 player->inventory_element[k++] = player->inventory_element[j];
10490 player->inventory_size = k;
10492 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10494 if (player->inventory_size > 0)
10496 for (j = 0; j < player->inventory_size - 1; j++)
10497 player->inventory_element[j] = player->inventory_element[j + 1];
10499 player->inventory_size--;
10502 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10504 if (player->inventory_size > 0)
10505 player->inventory_size--;
10507 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10509 player->inventory_infinite_element = EL_UNDEFINED;
10510 player->inventory_size = 0;
10512 else if (action_arg == CA_ARG_INVENTORY_RESET)
10514 player->inventory_infinite_element = EL_UNDEFINED;
10515 player->inventory_size = 0;
10517 if (level.use_initial_inventory[i])
10519 for (j = 0; j < level.initial_inventory_size[i]; j++)
10521 int element = level.initial_inventory_content[i][j];
10522 int collect_count = element_info[element].collect_count_initial;
10524 if (!IS_CUSTOM_ELEMENT(element))
10527 if (collect_count == 0)
10528 player->inventory_infinite_element = element;
10530 for (k = 0; k < collect_count; k++)
10531 if (player->inventory_size < MAX_INVENTORY_SIZE)
10532 player->inventory_element[player->inventory_size++] =
10543 // ---------- CE actions -------------------------------------------------
10545 case CA_SET_CE_VALUE:
10547 int last_ce_value = CustomValue[x][y];
10549 CustomValue[x][y] = action_arg_number_new;
10551 if (CustomValue[x][y] != last_ce_value)
10553 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10554 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10556 if (CustomValue[x][y] == 0)
10558 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10559 ChangeCount[x][y] = 0; // allow at least one more change
10561 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10562 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10569 case CA_SET_CE_SCORE:
10571 int last_ce_score = ei->collect_score;
10573 ei->collect_score = action_arg_number_new;
10575 if (ei->collect_score != last_ce_score)
10577 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10578 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10580 if (ei->collect_score == 0)
10584 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10585 ChangeCount[x][y] = 0; // allow at least one more change
10587 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10588 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10591 This is a very special case that seems to be a mixture between
10592 CheckElementChange() and CheckTriggeredElementChange(): while
10593 the first one only affects single elements that are triggered
10594 directly, the second one affects multiple elements in the playfield
10595 that are triggered indirectly by another element. This is a third
10596 case: Changing the CE score always affects multiple identical CEs,
10597 so every affected CE must be checked, not only the single CE for
10598 which the CE score was changed in the first place (as every instance
10599 of that CE shares the same CE score, and therefore also can change)!
10601 SCAN_PLAYFIELD(xx, yy)
10603 if (Tile[xx][yy] == element)
10604 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10605 CE_SCORE_GETS_ZERO);
10613 case CA_SET_CE_ARTWORK:
10615 int artwork_element = action_arg_element;
10616 boolean reset_frame = FALSE;
10619 if (action_arg == CA_ARG_ELEMENT_RESET)
10620 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10623 if (ei->gfx_element != artwork_element)
10624 reset_frame = TRUE;
10626 ei->gfx_element = artwork_element;
10628 SCAN_PLAYFIELD(xx, yy)
10630 if (Tile[xx][yy] == element)
10634 ResetGfxAnimation(xx, yy);
10635 ResetRandomAnimationValue(xx, yy);
10638 TEST_DrawLevelField(xx, yy);
10645 // ---------- engine actions ---------------------------------------------
10647 case CA_SET_ENGINE_SCAN_MODE:
10649 InitPlayfieldScanMode(action_arg);
10659 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10661 int old_element = Tile[x][y];
10662 int new_element = GetElementFromGroupElement(element);
10663 int previous_move_direction = MovDir[x][y];
10664 int last_ce_value = CustomValue[x][y];
10665 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10666 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10667 boolean add_player_onto_element = (new_element_is_player &&
10668 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10669 IS_WALKABLE(old_element));
10671 if (!add_player_onto_element)
10673 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10674 RemoveMovingField(x, y);
10678 Tile[x][y] = new_element;
10680 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10681 MovDir[x][y] = previous_move_direction;
10683 if (element_info[new_element].use_last_ce_value)
10684 CustomValue[x][y] = last_ce_value;
10686 InitField_WithBug1(x, y, FALSE);
10688 new_element = Tile[x][y]; // element may have changed
10690 ResetGfxAnimation(x, y);
10691 ResetRandomAnimationValue(x, y);
10693 TEST_DrawLevelField(x, y);
10695 if (GFX_CRUMBLED(new_element))
10696 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10698 if (old_element == EL_EXPLOSION)
10700 Store[x][y] = Store2[x][y] = 0;
10702 // check if new element replaces an exploding player, requiring cleanup
10703 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
10704 StorePlayer[x][y] = 0;
10707 // check if element under the player changes from accessible to unaccessible
10708 // (needed for special case of dropping element which then changes)
10709 // (must be checked after creating new element for walkable group elements)
10710 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10711 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10713 KillPlayer(PLAYERINFO(x, y));
10719 // "ChangeCount" not set yet to allow "entered by player" change one time
10720 if (new_element_is_player)
10721 RelocatePlayer(x, y, new_element);
10724 ChangeCount[x][y]++; // count number of changes in the same frame
10726 TestIfBadThingTouchesPlayer(x, y);
10727 TestIfPlayerTouchesCustomElement(x, y);
10728 TestIfElementTouchesCustomElement(x, y);
10731 static void CreateField(int x, int y, int element)
10733 CreateFieldExt(x, y, element, FALSE);
10736 static void CreateElementFromChange(int x, int y, int element)
10738 element = GET_VALID_RUNTIME_ELEMENT(element);
10740 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10742 int old_element = Tile[x][y];
10744 // prevent changed element from moving in same engine frame
10745 // unless both old and new element can either fall or move
10746 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10747 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10751 CreateFieldExt(x, y, element, TRUE);
10754 static boolean ChangeElement(int x, int y, int element, int page)
10756 struct ElementInfo *ei = &element_info[element];
10757 struct ElementChangeInfo *change = &ei->change_page[page];
10758 int ce_value = CustomValue[x][y];
10759 int ce_score = ei->collect_score;
10760 int target_element;
10761 int old_element = Tile[x][y];
10763 // always use default change event to prevent running into a loop
10764 if (ChangeEvent[x][y] == -1)
10765 ChangeEvent[x][y] = CE_DELAY;
10767 if (ChangeEvent[x][y] == CE_DELAY)
10769 // reset actual trigger element, trigger player and action element
10770 change->actual_trigger_element = EL_EMPTY;
10771 change->actual_trigger_player = EL_EMPTY;
10772 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10773 change->actual_trigger_side = CH_SIDE_NONE;
10774 change->actual_trigger_ce_value = 0;
10775 change->actual_trigger_ce_score = 0;
10776 change->actual_trigger_x = -1;
10777 change->actual_trigger_y = -1;
10780 // do not change elements more than a specified maximum number of changes
10781 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10784 ChangeCount[x][y]++; // count number of changes in the same frame
10786 if (ei->has_anim_event)
10787 HandleGlobalAnimEventByElementChange(element, page, x, y,
10788 change->actual_trigger_x,
10789 change->actual_trigger_y);
10791 if (change->explode)
10798 if (change->use_target_content)
10800 boolean complete_replace = TRUE;
10801 boolean can_replace[3][3];
10804 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10807 boolean is_walkable;
10808 boolean is_diggable;
10809 boolean is_collectible;
10810 boolean is_removable;
10811 boolean is_destructible;
10812 int ex = x + xx - 1;
10813 int ey = y + yy - 1;
10814 int content_element = change->target_content.e[xx][yy];
10817 can_replace[xx][yy] = TRUE;
10819 if (ex == x && ey == y) // do not check changing element itself
10822 if (content_element == EL_EMPTY_SPACE)
10824 can_replace[xx][yy] = FALSE; // do not replace border with space
10829 if (!IN_LEV_FIELD(ex, ey))
10831 can_replace[xx][yy] = FALSE;
10832 complete_replace = FALSE;
10839 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10840 e = MovingOrBlocked2Element(ex, ey);
10842 is_empty = (IS_FREE(ex, ey) ||
10843 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10845 is_walkable = (is_empty || IS_WALKABLE(e));
10846 is_diggable = (is_empty || IS_DIGGABLE(e));
10847 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10848 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10849 is_removable = (is_diggable || is_collectible);
10851 can_replace[xx][yy] =
10852 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10853 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10854 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10855 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10856 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10857 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10858 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10860 if (!can_replace[xx][yy])
10861 complete_replace = FALSE;
10864 if (!change->only_if_complete || complete_replace)
10866 boolean something_has_changed = FALSE;
10868 if (change->only_if_complete && change->use_random_replace &&
10869 RND(100) < change->random_percentage)
10872 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10874 int ex = x + xx - 1;
10875 int ey = y + yy - 1;
10876 int content_element;
10878 if (can_replace[xx][yy] && (!change->use_random_replace ||
10879 RND(100) < change->random_percentage))
10881 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10882 RemoveMovingField(ex, ey);
10884 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10886 content_element = change->target_content.e[xx][yy];
10887 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10888 ce_value, ce_score);
10890 CreateElementFromChange(ex, ey, target_element);
10892 something_has_changed = TRUE;
10894 // for symmetry reasons, freeze newly created border elements
10895 if (ex != x || ey != y)
10896 Stop[ex][ey] = TRUE; // no more moving in this frame
10900 if (something_has_changed)
10902 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10903 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10909 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10910 ce_value, ce_score);
10912 if (element == EL_DIAGONAL_GROWING ||
10913 element == EL_DIAGONAL_SHRINKING)
10915 target_element = Store[x][y];
10917 Store[x][y] = EL_EMPTY;
10920 // special case: element changes to player (and may be kept if walkable)
10921 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10922 CreateElementFromChange(x, y, EL_EMPTY);
10924 CreateElementFromChange(x, y, target_element);
10926 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10927 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10930 // this uses direct change before indirect change
10931 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10936 static void HandleElementChange(int x, int y, int page)
10938 int element = MovingOrBlocked2Element(x, y);
10939 struct ElementInfo *ei = &element_info[element];
10940 struct ElementChangeInfo *change = &ei->change_page[page];
10941 boolean handle_action_before_change = FALSE;
10944 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10945 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10947 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10948 x, y, element, element_info[element].token_name);
10949 Debug("game:playing:HandleElementChange", "This should never happen!");
10953 // this can happen with classic bombs on walkable, changing elements
10954 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10959 if (ChangeDelay[x][y] == 0) // initialize element change
10961 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10963 if (change->can_change)
10965 // !!! not clear why graphic animation should be reset at all here !!!
10966 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10967 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10970 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10972 When using an animation frame delay of 1 (this only happens with
10973 "sp_zonk.moving.left/right" in the classic graphics), the default
10974 (non-moving) animation shows wrong animation frames (while the
10975 moving animation, like "sp_zonk.moving.left/right", is correct,
10976 so this graphical bug never shows up with the classic graphics).
10977 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10978 be drawn instead of the correct frames 0,1,2,3. This is caused by
10979 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10980 an element change: First when the change delay ("ChangeDelay[][]")
10981 counter has reached zero after decrementing, then a second time in
10982 the next frame (after "GfxFrame[][]" was already incremented) when
10983 "ChangeDelay[][]" is reset to the initial delay value again.
10985 This causes frame 0 to be drawn twice, while the last frame won't
10986 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10988 As some animations may already be cleverly designed around this bug
10989 (at least the "Snake Bite" snake tail animation does this), it cannot
10990 simply be fixed here without breaking such existing animations.
10991 Unfortunately, it cannot easily be detected if a graphics set was
10992 designed "before" or "after" the bug was fixed. As a workaround,
10993 a new graphics set option "game.graphics_engine_version" was added
10994 to be able to specify the game's major release version for which the
10995 graphics set was designed, which can then be used to decide if the
10996 bugfix should be used (version 4 and above) or not (version 3 or
10997 below, or if no version was specified at all, as with old sets).
10999 (The wrong/fixed animation frames can be tested with the test level set
11000 "test_gfxframe" and level "000", which contains a specially prepared
11001 custom element at level position (x/y) == (11/9) which uses the zonk
11002 animation mentioned above. Using "game.graphics_engine_version: 4"
11003 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
11004 This can also be seen from the debug output for this test element.)
11007 // when a custom element is about to change (for example by change delay),
11008 // do not reset graphic animation when the custom element is moving
11009 if (game.graphics_engine_version < 4 &&
11012 ResetGfxAnimation(x, y);
11013 ResetRandomAnimationValue(x, y);
11016 if (change->pre_change_function)
11017 change->pre_change_function(x, y);
11021 ChangeDelay[x][y]--;
11023 if (ChangeDelay[x][y] != 0) // continue element change
11025 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11027 // also needed if CE can not change, but has CE delay with CE action
11028 if (IS_ANIMATED(graphic))
11029 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11031 if (change->can_change)
11033 if (change->change_function)
11034 change->change_function(x, y);
11037 else // finish element change
11039 if (ChangePage[x][y] != -1) // remember page from delayed change
11041 page = ChangePage[x][y];
11042 ChangePage[x][y] = -1;
11044 change = &ei->change_page[page];
11047 if (IS_MOVING(x, y)) // never change a running system ;-)
11049 ChangeDelay[x][y] = 1; // try change after next move step
11050 ChangePage[x][y] = page; // remember page to use for change
11055 // special case: set new level random seed before changing element
11056 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11057 handle_action_before_change = TRUE;
11059 if (change->has_action && handle_action_before_change)
11060 ExecuteCustomElementAction(x, y, element, page);
11062 if (change->can_change)
11064 if (ChangeElement(x, y, element, page))
11066 if (change->post_change_function)
11067 change->post_change_function(x, y);
11071 if (change->has_action && !handle_action_before_change)
11072 ExecuteCustomElementAction(x, y, element, page);
11076 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11077 int trigger_element,
11079 int trigger_player,
11083 boolean change_done_any = FALSE;
11084 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11087 if (!(trigger_events[trigger_element][trigger_event]))
11090 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11092 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11094 int element = EL_CUSTOM_START + i;
11095 boolean change_done = FALSE;
11098 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11099 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11102 for (p = 0; p < element_info[element].num_change_pages; p++)
11104 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11106 if (change->can_change_or_has_action &&
11107 change->has_event[trigger_event] &&
11108 change->trigger_side & trigger_side &&
11109 change->trigger_player & trigger_player &&
11110 change->trigger_page & trigger_page_bits &&
11111 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11113 change->actual_trigger_element = trigger_element;
11114 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11115 change->actual_trigger_player_bits = trigger_player;
11116 change->actual_trigger_side = trigger_side;
11117 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11118 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11119 change->actual_trigger_x = trigger_x;
11120 change->actual_trigger_y = trigger_y;
11122 if ((change->can_change && !change_done) || change->has_action)
11126 SCAN_PLAYFIELD(x, y)
11128 if (Tile[x][y] == element)
11130 if (change->can_change && !change_done)
11132 // if element already changed in this frame, not only prevent
11133 // another element change (checked in ChangeElement()), but
11134 // also prevent additional element actions for this element
11136 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11137 !level.use_action_after_change_bug)
11140 ChangeDelay[x][y] = 1;
11141 ChangeEvent[x][y] = trigger_event;
11143 HandleElementChange(x, y, p);
11145 else if (change->has_action)
11147 // if element already changed in this frame, not only prevent
11148 // another element change (checked in ChangeElement()), but
11149 // also prevent additional element actions for this element
11151 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11152 !level.use_action_after_change_bug)
11155 ExecuteCustomElementAction(x, y, element, p);
11156 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11161 if (change->can_change)
11163 change_done = TRUE;
11164 change_done_any = TRUE;
11171 RECURSION_LOOP_DETECTION_END();
11173 return change_done_any;
11176 static boolean CheckElementChangeExt(int x, int y,
11178 int trigger_element,
11180 int trigger_player,
11183 boolean change_done = FALSE;
11186 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11187 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11190 if (Tile[x][y] == EL_BLOCKED)
11192 Blocked2Moving(x, y, &x, &y);
11193 element = Tile[x][y];
11196 // check if element has already changed or is about to change after moving
11197 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11198 Tile[x][y] != element) ||
11200 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11201 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11202 ChangePage[x][y] != -1)))
11205 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11207 for (p = 0; p < element_info[element].num_change_pages; p++)
11209 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11211 /* check trigger element for all events where the element that is checked
11212 for changing interacts with a directly adjacent element -- this is
11213 different to element changes that affect other elements to change on the
11214 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11215 boolean check_trigger_element =
11216 (trigger_event == CE_NEXT_TO_X ||
11217 trigger_event == CE_TOUCHING_X ||
11218 trigger_event == CE_HITTING_X ||
11219 trigger_event == CE_HIT_BY_X ||
11220 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11222 if (change->can_change_or_has_action &&
11223 change->has_event[trigger_event] &&
11224 change->trigger_side & trigger_side &&
11225 change->trigger_player & trigger_player &&
11226 (!check_trigger_element ||
11227 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11229 change->actual_trigger_element = trigger_element;
11230 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11231 change->actual_trigger_player_bits = trigger_player;
11232 change->actual_trigger_side = trigger_side;
11233 change->actual_trigger_ce_value = CustomValue[x][y];
11234 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11235 change->actual_trigger_x = x;
11236 change->actual_trigger_y = y;
11238 // special case: trigger element not at (x,y) position for some events
11239 if (check_trigger_element)
11251 { 0, 0 }, { 0, 0 }, { 0, 0 },
11255 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11256 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11258 change->actual_trigger_ce_value = CustomValue[xx][yy];
11259 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11260 change->actual_trigger_x = xx;
11261 change->actual_trigger_y = yy;
11264 if (change->can_change && !change_done)
11266 ChangeDelay[x][y] = 1;
11267 ChangeEvent[x][y] = trigger_event;
11269 HandleElementChange(x, y, p);
11271 change_done = TRUE;
11273 else if (change->has_action)
11275 ExecuteCustomElementAction(x, y, element, p);
11276 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11281 RECURSION_LOOP_DETECTION_END();
11283 return change_done;
11286 static void PlayPlayerSound(struct PlayerInfo *player)
11288 int jx = player->jx, jy = player->jy;
11289 int sound_element = player->artwork_element;
11290 int last_action = player->last_action_waiting;
11291 int action = player->action_waiting;
11293 if (player->is_waiting)
11295 if (action != last_action)
11296 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11298 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11302 if (action != last_action)
11303 StopSound(element_info[sound_element].sound[last_action]);
11305 if (last_action == ACTION_SLEEPING)
11306 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11310 static void PlayAllPlayersSound(void)
11314 for (i = 0; i < MAX_PLAYERS; i++)
11315 if (stored_player[i].active)
11316 PlayPlayerSound(&stored_player[i]);
11319 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11321 boolean last_waiting = player->is_waiting;
11322 int move_dir = player->MovDir;
11324 player->dir_waiting = move_dir;
11325 player->last_action_waiting = player->action_waiting;
11329 if (!last_waiting) // not waiting -> waiting
11331 player->is_waiting = TRUE;
11333 player->frame_counter_bored =
11335 game.player_boring_delay_fixed +
11336 GetSimpleRandom(game.player_boring_delay_random);
11337 player->frame_counter_sleeping =
11339 game.player_sleeping_delay_fixed +
11340 GetSimpleRandom(game.player_sleeping_delay_random);
11342 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11345 if (game.player_sleeping_delay_fixed +
11346 game.player_sleeping_delay_random > 0 &&
11347 player->anim_delay_counter == 0 &&
11348 player->post_delay_counter == 0 &&
11349 FrameCounter >= player->frame_counter_sleeping)
11350 player->is_sleeping = TRUE;
11351 else if (game.player_boring_delay_fixed +
11352 game.player_boring_delay_random > 0 &&
11353 FrameCounter >= player->frame_counter_bored)
11354 player->is_bored = TRUE;
11356 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11357 player->is_bored ? ACTION_BORING :
11360 if (player->is_sleeping && player->use_murphy)
11362 // special case for sleeping Murphy when leaning against non-free tile
11364 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11365 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11366 !IS_MOVING(player->jx - 1, player->jy)))
11367 move_dir = MV_LEFT;
11368 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11369 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11370 !IS_MOVING(player->jx + 1, player->jy)))
11371 move_dir = MV_RIGHT;
11373 player->is_sleeping = FALSE;
11375 player->dir_waiting = move_dir;
11378 if (player->is_sleeping)
11380 if (player->num_special_action_sleeping > 0)
11382 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11384 int last_special_action = player->special_action_sleeping;
11385 int num_special_action = player->num_special_action_sleeping;
11386 int special_action =
11387 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11388 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11389 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11390 last_special_action + 1 : ACTION_SLEEPING);
11391 int special_graphic =
11392 el_act_dir2img(player->artwork_element, special_action, move_dir);
11394 player->anim_delay_counter =
11395 graphic_info[special_graphic].anim_delay_fixed +
11396 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11397 player->post_delay_counter =
11398 graphic_info[special_graphic].post_delay_fixed +
11399 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11401 player->special_action_sleeping = special_action;
11404 if (player->anim_delay_counter > 0)
11406 player->action_waiting = player->special_action_sleeping;
11407 player->anim_delay_counter--;
11409 else if (player->post_delay_counter > 0)
11411 player->post_delay_counter--;
11415 else if (player->is_bored)
11417 if (player->num_special_action_bored > 0)
11419 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11421 int special_action =
11422 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11423 int special_graphic =
11424 el_act_dir2img(player->artwork_element, special_action, move_dir);
11426 player->anim_delay_counter =
11427 graphic_info[special_graphic].anim_delay_fixed +
11428 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11429 player->post_delay_counter =
11430 graphic_info[special_graphic].post_delay_fixed +
11431 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11433 player->special_action_bored = special_action;
11436 if (player->anim_delay_counter > 0)
11438 player->action_waiting = player->special_action_bored;
11439 player->anim_delay_counter--;
11441 else if (player->post_delay_counter > 0)
11443 player->post_delay_counter--;
11448 else if (last_waiting) // waiting -> not waiting
11450 player->is_waiting = FALSE;
11451 player->is_bored = FALSE;
11452 player->is_sleeping = FALSE;
11454 player->frame_counter_bored = -1;
11455 player->frame_counter_sleeping = -1;
11457 player->anim_delay_counter = 0;
11458 player->post_delay_counter = 0;
11460 player->dir_waiting = player->MovDir;
11461 player->action_waiting = ACTION_DEFAULT;
11463 player->special_action_bored = ACTION_DEFAULT;
11464 player->special_action_sleeping = ACTION_DEFAULT;
11468 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11470 if ((!player->is_moving && player->was_moving) ||
11471 (player->MovPos == 0 && player->was_moving) ||
11472 (player->is_snapping && !player->was_snapping) ||
11473 (player->is_dropping && !player->was_dropping))
11475 if (!CheckSaveEngineSnapshotToList())
11478 player->was_moving = FALSE;
11479 player->was_snapping = TRUE;
11480 player->was_dropping = TRUE;
11484 if (player->is_moving)
11485 player->was_moving = TRUE;
11487 if (!player->is_snapping)
11488 player->was_snapping = FALSE;
11490 if (!player->is_dropping)
11491 player->was_dropping = FALSE;
11494 static struct MouseActionInfo mouse_action_last = { 0 };
11495 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11496 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11499 CheckSaveEngineSnapshotToList();
11501 mouse_action_last = mouse_action;
11504 static void CheckSingleStepMode(struct PlayerInfo *player)
11506 if (tape.single_step && tape.recording && !tape.pausing)
11508 // as it is called "single step mode", just return to pause mode when the
11509 // player stopped moving after one tile (or never starts moving at all)
11510 // (reverse logic needed here in case single step mode used in team mode)
11511 if (player->is_moving ||
11512 player->is_pushing ||
11513 player->is_dropping_pressed ||
11514 player->effective_mouse_action.button)
11515 game.enter_single_step_mode = FALSE;
11518 CheckSaveEngineSnapshot(player);
11521 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11523 int left = player_action & JOY_LEFT;
11524 int right = player_action & JOY_RIGHT;
11525 int up = player_action & JOY_UP;
11526 int down = player_action & JOY_DOWN;
11527 int button1 = player_action & JOY_BUTTON_1;
11528 int button2 = player_action & JOY_BUTTON_2;
11529 int dx = (left ? -1 : right ? 1 : 0);
11530 int dy = (up ? -1 : down ? 1 : 0);
11532 if (!player->active || tape.pausing)
11538 SnapField(player, dx, dy);
11542 DropElement(player);
11544 MovePlayer(player, dx, dy);
11547 CheckSingleStepMode(player);
11549 SetPlayerWaiting(player, FALSE);
11551 return player_action;
11555 // no actions for this player (no input at player's configured device)
11557 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11558 SnapField(player, 0, 0);
11559 CheckGravityMovementWhenNotMoving(player);
11561 if (player->MovPos == 0)
11562 SetPlayerWaiting(player, TRUE);
11564 if (player->MovPos == 0) // needed for tape.playing
11565 player->is_moving = FALSE;
11567 player->is_dropping = FALSE;
11568 player->is_dropping_pressed = FALSE;
11569 player->drop_pressed_delay = 0;
11571 CheckSingleStepMode(player);
11577 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11580 if (!tape.use_mouse_actions)
11583 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11584 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11585 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11588 static void SetTapeActionFromMouseAction(byte *tape_action,
11589 struct MouseActionInfo *mouse_action)
11591 if (!tape.use_mouse_actions)
11594 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11595 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11596 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11599 static void CheckLevelSolved(void)
11601 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11603 if (game_em.level_solved &&
11604 !game_em.game_over) // game won
11608 game_em.game_over = TRUE;
11610 game.all_players_gone = TRUE;
11613 if (game_em.game_over) // game lost
11614 game.all_players_gone = TRUE;
11616 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11618 if (game_sp.level_solved &&
11619 !game_sp.game_over) // game won
11623 game_sp.game_over = TRUE;
11625 game.all_players_gone = TRUE;
11628 if (game_sp.game_over) // game lost
11629 game.all_players_gone = TRUE;
11631 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11633 if (game_mm.level_solved &&
11634 !game_mm.game_over) // game won
11638 game_mm.game_over = TRUE;
11640 game.all_players_gone = TRUE;
11643 if (game_mm.game_over) // game lost
11644 game.all_players_gone = TRUE;
11648 static void CheckLevelTime_StepCounter(void)
11658 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11659 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11661 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11663 DisplayGameControlValues();
11665 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11666 for (i = 0; i < MAX_PLAYERS; i++)
11667 KillPlayer(&stored_player[i]);
11669 else if (game.no_level_time_limit && !game.all_players_gone)
11671 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11673 DisplayGameControlValues();
11677 static void CheckLevelTime(void)
11681 if (TimeFrames >= FRAMES_PER_SECOND)
11685 for (i = 0; i < MAX_PLAYERS; i++)
11687 struct PlayerInfo *player = &stored_player[i];
11689 if (SHIELD_ON(player))
11691 player->shield_normal_time_left--;
11693 if (player->shield_deadly_time_left > 0)
11694 player->shield_deadly_time_left--;
11698 if (!game.LevelSolved && !level.use_step_counter)
11706 if (TimeLeft <= 10 && game.time_limit)
11707 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11709 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11710 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11712 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11714 if (!TimeLeft && game.time_limit)
11716 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11717 game_em.lev->killed_out_of_time = TRUE;
11719 for (i = 0; i < MAX_PLAYERS; i++)
11720 KillPlayer(&stored_player[i]);
11723 else if (game.no_level_time_limit && !game.all_players_gone)
11725 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11728 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11732 if (TapeTimeFrames >= FRAMES_PER_SECOND)
11734 TapeTimeFrames = 0;
11737 if (tape.recording || tape.playing)
11738 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11741 if (tape.recording || tape.playing)
11742 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11744 UpdateAndDisplayGameControlValues();
11747 void AdvanceFrameAndPlayerCounters(int player_nr)
11751 // advance frame counters (global frame counter and tape time frame counter)
11755 // advance time frame counter (used to control available time to solve level)
11758 // advance player counters (counters for move delay, move animation etc.)
11759 for (i = 0; i < MAX_PLAYERS; i++)
11761 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11762 int move_delay_value = stored_player[i].move_delay_value;
11763 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11765 if (!advance_player_counters) // not all players may be affected
11768 if (move_frames == 0) // less than one move per game frame
11770 int stepsize = TILEX / move_delay_value;
11771 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11772 int count = (stored_player[i].is_moving ?
11773 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11775 if (count % delay == 0)
11779 stored_player[i].Frame += move_frames;
11781 if (stored_player[i].MovPos != 0)
11782 stored_player[i].StepFrame += move_frames;
11784 if (stored_player[i].move_delay > 0)
11785 stored_player[i].move_delay--;
11787 // due to bugs in previous versions, counter must count up, not down
11788 if (stored_player[i].push_delay != -1)
11789 stored_player[i].push_delay++;
11791 if (stored_player[i].drop_delay > 0)
11792 stored_player[i].drop_delay--;
11794 if (stored_player[i].is_dropping_pressed)
11795 stored_player[i].drop_pressed_delay++;
11799 void AdvanceFrameCounter(void)
11804 void AdvanceGfxFrame(void)
11808 SCAN_PLAYFIELD(x, y)
11814 static void HandleMouseAction(struct MouseActionInfo *mouse_action,
11815 struct MouseActionInfo *mouse_action_last)
11817 if (mouse_action->button)
11819 int new_button = (mouse_action->button && mouse_action_last->button == 0);
11820 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button);
11821 int x = mouse_action->lx;
11822 int y = mouse_action->ly;
11823 int element = Tile[x][y];
11827 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
11828 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
11832 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
11833 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
11836 if (level.use_step_counter)
11838 boolean counted_click = FALSE;
11840 // element clicked that can change when clicked/pressed
11841 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
11842 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
11843 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
11844 counted_click = TRUE;
11846 // element clicked that can trigger change when clicked/pressed
11847 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
11848 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
11849 counted_click = TRUE;
11851 if (new_button && counted_click)
11852 CheckLevelTime_StepCounter();
11857 void StartGameActions(boolean init_network_game, boolean record_tape,
11860 unsigned int new_random_seed = InitRND(random_seed);
11863 TapeStartRecording(new_random_seed);
11865 if (setup.auto_pause_on_start && !tape.pausing)
11866 TapeTogglePause(TAPE_TOGGLE_MANUAL);
11868 if (init_network_game)
11870 SendToServer_LevelFile();
11871 SendToServer_StartPlaying();
11879 static void GameActionsExt(void)
11882 static unsigned int game_frame_delay = 0;
11884 unsigned int game_frame_delay_value;
11885 byte *recorded_player_action;
11886 byte summarized_player_action = 0;
11887 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11890 // detect endless loops, caused by custom element programming
11891 if (recursion_loop_detected && recursion_loop_depth == 0)
11893 char *message = getStringCat3("Internal Error! Element ",
11894 EL_NAME(recursion_loop_element),
11895 " caused endless loop! Quit the game?");
11897 Warn("element '%s' caused endless loop in game engine",
11898 EL_NAME(recursion_loop_element));
11900 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11902 recursion_loop_detected = FALSE; // if game should be continued
11909 if (game.restart_level)
11910 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11912 CheckLevelSolved();
11914 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11917 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11920 if (game_status != GAME_MODE_PLAYING) // status might have changed
11923 game_frame_delay_value =
11924 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11926 if (tape.playing && tape.warp_forward && !tape.pausing)
11927 game_frame_delay_value = 0;
11929 SetVideoFrameDelay(game_frame_delay_value);
11931 // (de)activate virtual buttons depending on current game status
11932 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11934 if (game.all_players_gone) // if no players there to be controlled anymore
11935 SetOverlayActive(FALSE);
11936 else if (!tape.playing) // if game continues after tape stopped playing
11937 SetOverlayActive(TRUE);
11942 // ---------- main game synchronization point ----------
11944 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11946 Debug("game:playing:skip", "skip == %d", skip);
11949 // ---------- main game synchronization point ----------
11951 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11955 if (network_playing && !network_player_action_received)
11957 // try to get network player actions in time
11959 // last chance to get network player actions without main loop delay
11960 HandleNetworking();
11962 // game was quit by network peer
11963 if (game_status != GAME_MODE_PLAYING)
11966 // check if network player actions still missing and game still running
11967 if (!network_player_action_received && !checkGameEnded())
11968 return; // failed to get network player actions in time
11970 // do not yet reset "network_player_action_received" (for tape.pausing)
11976 // at this point we know that we really continue executing the game
11978 network_player_action_received = FALSE;
11980 // when playing tape, read previously recorded player input from tape data
11981 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11983 local_player->effective_mouse_action = local_player->mouse_action;
11985 if (recorded_player_action != NULL)
11986 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11987 recorded_player_action);
11989 // TapePlayAction() may return NULL when toggling to "pause before death"
11993 if (tape.set_centered_player)
11995 game.centered_player_nr_next = tape.centered_player_nr_next;
11996 game.set_centered_player = TRUE;
11999 for (i = 0; i < MAX_PLAYERS; i++)
12001 summarized_player_action |= stored_player[i].action;
12003 if (!network_playing && (game.team_mode || tape.playing))
12004 stored_player[i].effective_action = stored_player[i].action;
12007 if (network_playing && !checkGameEnded())
12008 SendToServer_MovePlayer(summarized_player_action);
12010 // summarize all actions at local players mapped input device position
12011 // (this allows using different input devices in single player mode)
12012 if (!network.enabled && !game.team_mode)
12013 stored_player[map_player_action[local_player->index_nr]].effective_action =
12014 summarized_player_action;
12016 // summarize all actions at centered player in local team mode
12017 if (tape.recording &&
12018 setup.team_mode && !network.enabled &&
12019 setup.input_on_focus &&
12020 game.centered_player_nr != -1)
12022 for (i = 0; i < MAX_PLAYERS; i++)
12023 stored_player[map_player_action[i]].effective_action =
12024 (i == game.centered_player_nr ? summarized_player_action : 0);
12027 if (recorded_player_action != NULL)
12028 for (i = 0; i < MAX_PLAYERS; i++)
12029 stored_player[i].effective_action = recorded_player_action[i];
12031 for (i = 0; i < MAX_PLAYERS; i++)
12033 tape_action[i] = stored_player[i].effective_action;
12035 /* (this may happen in the RND game engine if a player was not present on
12036 the playfield on level start, but appeared later from a custom element */
12037 if (setup.team_mode &&
12040 !tape.player_participates[i])
12041 tape.player_participates[i] = TRUE;
12044 SetTapeActionFromMouseAction(tape_action,
12045 &local_player->effective_mouse_action);
12047 // only record actions from input devices, but not programmed actions
12048 if (tape.recording)
12049 TapeRecordAction(tape_action);
12051 // remember if game was played (especially after tape stopped playing)
12052 if (!tape.playing && summarized_player_action && !checkGameFailed())
12053 game.GamePlayed = TRUE;
12055 #if USE_NEW_PLAYER_ASSIGNMENTS
12056 // !!! also map player actions in single player mode !!!
12057 // if (game.team_mode)
12060 byte mapped_action[MAX_PLAYERS];
12062 #if DEBUG_PLAYER_ACTIONS
12063 for (i = 0; i < MAX_PLAYERS; i++)
12064 DebugContinued("", "%d, ", stored_player[i].effective_action);
12067 for (i = 0; i < MAX_PLAYERS; i++)
12068 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
12070 for (i = 0; i < MAX_PLAYERS; i++)
12071 stored_player[i].effective_action = mapped_action[i];
12073 #if DEBUG_PLAYER_ACTIONS
12074 DebugContinued("", "=> ");
12075 for (i = 0; i < MAX_PLAYERS; i++)
12076 DebugContinued("", "%d, ", stored_player[i].effective_action);
12077 DebugContinued("game:playing:player", "\n");
12080 #if DEBUG_PLAYER_ACTIONS
12083 for (i = 0; i < MAX_PLAYERS; i++)
12084 DebugContinued("", "%d, ", stored_player[i].effective_action);
12085 DebugContinued("game:playing:player", "\n");
12090 for (i = 0; i < MAX_PLAYERS; i++)
12092 // allow engine snapshot in case of changed movement attempt
12093 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12094 (stored_player[i].effective_action & KEY_MOTION))
12095 game.snapshot.changed_action = TRUE;
12097 // allow engine snapshot in case of snapping/dropping attempt
12098 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12099 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12100 game.snapshot.changed_action = TRUE;
12102 game.snapshot.last_action[i] = stored_player[i].effective_action;
12105 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12107 GameActions_EM_Main();
12109 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12111 GameActions_SP_Main();
12113 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12115 GameActions_MM_Main();
12119 GameActions_RND_Main();
12122 BlitScreenToBitmap(backbuffer);
12124 CheckLevelSolved();
12127 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12129 if (global.show_frames_per_second)
12131 static unsigned int fps_counter = 0;
12132 static int fps_frames = 0;
12133 unsigned int fps_delay_ms = Counter() - fps_counter;
12137 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12139 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12142 fps_counter = Counter();
12144 // always draw FPS to screen after FPS value was updated
12145 redraw_mask |= REDRAW_FPS;
12148 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12149 if (GetDrawDeactivationMask() == REDRAW_NONE)
12150 redraw_mask |= REDRAW_FPS;
12154 static void GameActions_CheckSaveEngineSnapshot(void)
12156 if (!game.snapshot.save_snapshot)
12159 // clear flag for saving snapshot _before_ saving snapshot
12160 game.snapshot.save_snapshot = FALSE;
12162 SaveEngineSnapshotToList();
12165 void GameActions(void)
12169 GameActions_CheckSaveEngineSnapshot();
12172 void GameActions_EM_Main(void)
12174 byte effective_action[MAX_PLAYERS];
12177 for (i = 0; i < MAX_PLAYERS; i++)
12178 effective_action[i] = stored_player[i].effective_action;
12180 GameActions_EM(effective_action);
12183 void GameActions_SP_Main(void)
12185 byte effective_action[MAX_PLAYERS];
12188 for (i = 0; i < MAX_PLAYERS; i++)
12189 effective_action[i] = stored_player[i].effective_action;
12191 GameActions_SP(effective_action);
12193 for (i = 0; i < MAX_PLAYERS; i++)
12195 if (stored_player[i].force_dropping)
12196 stored_player[i].action |= KEY_BUTTON_DROP;
12198 stored_player[i].force_dropping = FALSE;
12202 void GameActions_MM_Main(void)
12206 GameActions_MM(local_player->effective_mouse_action);
12209 void GameActions_RND_Main(void)
12214 void GameActions_RND(void)
12216 static struct MouseActionInfo mouse_action_last = { 0 };
12217 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12218 int magic_wall_x = 0, magic_wall_y = 0;
12219 int i, x, y, element, graphic, last_gfx_frame;
12221 InitPlayfieldScanModeVars();
12223 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12225 SCAN_PLAYFIELD(x, y)
12227 ChangeCount[x][y] = 0;
12228 ChangeEvent[x][y] = -1;
12232 if (game.set_centered_player)
12234 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12236 // switching to "all players" only possible if all players fit to screen
12237 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12239 game.centered_player_nr_next = game.centered_player_nr;
12240 game.set_centered_player = FALSE;
12243 // do not switch focus to non-existing (or non-active) player
12244 if (game.centered_player_nr_next >= 0 &&
12245 !stored_player[game.centered_player_nr_next].active)
12247 game.centered_player_nr_next = game.centered_player_nr;
12248 game.set_centered_player = FALSE;
12252 if (game.set_centered_player &&
12253 ScreenMovPos == 0) // screen currently aligned at tile position
12257 if (game.centered_player_nr_next == -1)
12259 setScreenCenteredToAllPlayers(&sx, &sy);
12263 sx = stored_player[game.centered_player_nr_next].jx;
12264 sy = stored_player[game.centered_player_nr_next].jy;
12267 game.centered_player_nr = game.centered_player_nr_next;
12268 game.set_centered_player = FALSE;
12270 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12271 DrawGameDoorValues();
12274 // check single step mode (set flag and clear again if any player is active)
12275 game.enter_single_step_mode =
12276 (tape.single_step && tape.recording && !tape.pausing);
12278 for (i = 0; i < MAX_PLAYERS; i++)
12280 int actual_player_action = stored_player[i].effective_action;
12283 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12284 - rnd_equinox_tetrachloride 048
12285 - rnd_equinox_tetrachloride_ii 096
12286 - rnd_emanuel_schmieg 002
12287 - doctor_sloan_ww 001, 020
12289 if (stored_player[i].MovPos == 0)
12290 CheckGravityMovement(&stored_player[i]);
12293 // overwrite programmed action with tape action
12294 if (stored_player[i].programmed_action)
12295 actual_player_action = stored_player[i].programmed_action;
12297 PlayerActions(&stored_player[i], actual_player_action);
12299 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12302 // single step pause mode may already have been toggled by "ScrollPlayer()"
12303 if (game.enter_single_step_mode && !tape.pausing)
12304 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12306 ScrollScreen(NULL, SCROLL_GO_ON);
12308 /* for backwards compatibility, the following code emulates a fixed bug that
12309 occured when pushing elements (causing elements that just made their last
12310 pushing step to already (if possible) make their first falling step in the
12311 same game frame, which is bad); this code is also needed to use the famous
12312 "spring push bug" which is used in older levels and might be wanted to be
12313 used also in newer levels, but in this case the buggy pushing code is only
12314 affecting the "spring" element and no other elements */
12316 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12318 for (i = 0; i < MAX_PLAYERS; i++)
12320 struct PlayerInfo *player = &stored_player[i];
12321 int x = player->jx;
12322 int y = player->jy;
12324 if (player->active && player->is_pushing && player->is_moving &&
12326 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12327 Tile[x][y] == EL_SPRING))
12329 ContinueMoving(x, y);
12331 // continue moving after pushing (this is actually a bug)
12332 if (!IS_MOVING(x, y))
12333 Stop[x][y] = FALSE;
12338 SCAN_PLAYFIELD(x, y)
12340 Last[x][y] = Tile[x][y];
12342 ChangeCount[x][y] = 0;
12343 ChangeEvent[x][y] = -1;
12345 // this must be handled before main playfield loop
12346 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12349 if (MovDelay[x][y] <= 0)
12353 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12356 if (MovDelay[x][y] <= 0)
12358 int element = Store[x][y];
12359 int move_direction = MovDir[x][y];
12360 int player_index_bit = Store2[x][y];
12366 TEST_DrawLevelField(x, y);
12368 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12370 if (IS_ENVELOPE(element))
12371 local_player->show_envelope = element;
12376 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12378 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12380 Debug("game:playing:GameActions_RND", "This should never happen!");
12382 ChangePage[x][y] = -1;
12386 Stop[x][y] = FALSE;
12387 if (WasJustMoving[x][y] > 0)
12388 WasJustMoving[x][y]--;
12389 if (WasJustFalling[x][y] > 0)
12390 WasJustFalling[x][y]--;
12391 if (CheckCollision[x][y] > 0)
12392 CheckCollision[x][y]--;
12393 if (CheckImpact[x][y] > 0)
12394 CheckImpact[x][y]--;
12398 /* reset finished pushing action (not done in ContinueMoving() to allow
12399 continuous pushing animation for elements with zero push delay) */
12400 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12402 ResetGfxAnimation(x, y);
12403 TEST_DrawLevelField(x, y);
12407 if (IS_BLOCKED(x, y))
12411 Blocked2Moving(x, y, &oldx, &oldy);
12412 if (!IS_MOVING(oldx, oldy))
12414 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12415 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12416 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12417 Debug("game:playing:GameActions_RND", "This should never happen!");
12423 HandleMouseAction(&mouse_action, &mouse_action_last);
12425 SCAN_PLAYFIELD(x, y)
12427 element = Tile[x][y];
12428 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12429 last_gfx_frame = GfxFrame[x][y];
12431 if (element == EL_EMPTY)
12432 graphic = el2img(GfxElementEmpty[x][y]);
12434 ResetGfxFrame(x, y);
12436 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12437 DrawLevelGraphicAnimation(x, y, graphic);
12439 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12440 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12441 ResetRandomAnimationValue(x, y);
12443 SetRandomAnimationValue(x, y);
12445 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12447 if (IS_INACTIVE(element))
12449 if (IS_ANIMATED(graphic))
12450 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12455 // this may take place after moving, so 'element' may have changed
12456 if (IS_CHANGING(x, y) &&
12457 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12459 int page = element_info[element].event_page_nr[CE_DELAY];
12461 HandleElementChange(x, y, page);
12463 element = Tile[x][y];
12464 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12467 CheckNextToConditions(x, y);
12469 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12473 element = Tile[x][y];
12474 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12476 if (IS_ANIMATED(graphic) &&
12477 !IS_MOVING(x, y) &&
12479 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12481 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12482 TEST_DrawTwinkleOnField(x, y);
12484 else if (element == EL_ACID)
12487 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12489 else if ((element == EL_EXIT_OPEN ||
12490 element == EL_EM_EXIT_OPEN ||
12491 element == EL_SP_EXIT_OPEN ||
12492 element == EL_STEEL_EXIT_OPEN ||
12493 element == EL_EM_STEEL_EXIT_OPEN ||
12494 element == EL_SP_TERMINAL ||
12495 element == EL_SP_TERMINAL_ACTIVE ||
12496 element == EL_EXTRA_TIME ||
12497 element == EL_SHIELD_NORMAL ||
12498 element == EL_SHIELD_DEADLY) &&
12499 IS_ANIMATED(graphic))
12500 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12501 else if (IS_MOVING(x, y))
12502 ContinueMoving(x, y);
12503 else if (IS_ACTIVE_BOMB(element))
12504 CheckDynamite(x, y);
12505 else if (element == EL_AMOEBA_GROWING)
12506 AmoebaGrowing(x, y);
12507 else if (element == EL_AMOEBA_SHRINKING)
12508 AmoebaShrinking(x, y);
12510 #if !USE_NEW_AMOEBA_CODE
12511 else if (IS_AMOEBALIVE(element))
12512 AmoebaReproduce(x, y);
12515 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12517 else if (element == EL_EXIT_CLOSED)
12519 else if (element == EL_EM_EXIT_CLOSED)
12521 else if (element == EL_STEEL_EXIT_CLOSED)
12522 CheckExitSteel(x, y);
12523 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12524 CheckExitSteelEM(x, y);
12525 else if (element == EL_SP_EXIT_CLOSED)
12527 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12528 element == EL_EXPANDABLE_STEELWALL_GROWING)
12530 else if (element == EL_EXPANDABLE_WALL ||
12531 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12532 element == EL_EXPANDABLE_WALL_VERTICAL ||
12533 element == EL_EXPANDABLE_WALL_ANY ||
12534 element == EL_BD_EXPANDABLE_WALL ||
12535 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12536 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12537 element == EL_EXPANDABLE_STEELWALL_ANY)
12538 CheckWallGrowing(x, y);
12539 else if (element == EL_FLAMES)
12540 CheckForDragon(x, y);
12541 else if (element == EL_EXPLOSION)
12542 ; // drawing of correct explosion animation is handled separately
12543 else if (element == EL_ELEMENT_SNAPPING ||
12544 element == EL_DIAGONAL_SHRINKING ||
12545 element == EL_DIAGONAL_GROWING)
12547 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]);
12549 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12551 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12552 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12554 if (IS_BELT_ACTIVE(element))
12555 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12557 if (game.magic_wall_active)
12559 int jx = local_player->jx, jy = local_player->jy;
12561 // play the element sound at the position nearest to the player
12562 if ((element == EL_MAGIC_WALL_FULL ||
12563 element == EL_MAGIC_WALL_ACTIVE ||
12564 element == EL_MAGIC_WALL_EMPTYING ||
12565 element == EL_BD_MAGIC_WALL_FULL ||
12566 element == EL_BD_MAGIC_WALL_ACTIVE ||
12567 element == EL_BD_MAGIC_WALL_EMPTYING ||
12568 element == EL_DC_MAGIC_WALL_FULL ||
12569 element == EL_DC_MAGIC_WALL_ACTIVE ||
12570 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12571 ABS(x - jx) + ABS(y - jy) <
12572 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12580 #if USE_NEW_AMOEBA_CODE
12581 // new experimental amoeba growth stuff
12582 if (!(FrameCounter % 8))
12584 static unsigned int random = 1684108901;
12586 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12588 x = RND(lev_fieldx);
12589 y = RND(lev_fieldy);
12590 element = Tile[x][y];
12592 if (!IS_PLAYER(x, y) &&
12593 (element == EL_EMPTY ||
12594 CAN_GROW_INTO(element) ||
12595 element == EL_QUICKSAND_EMPTY ||
12596 element == EL_QUICKSAND_FAST_EMPTY ||
12597 element == EL_ACID_SPLASH_LEFT ||
12598 element == EL_ACID_SPLASH_RIGHT))
12600 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12601 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12602 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12603 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12604 Tile[x][y] = EL_AMOEBA_DROP;
12607 random = random * 129 + 1;
12612 game.explosions_delayed = FALSE;
12614 SCAN_PLAYFIELD(x, y)
12616 element = Tile[x][y];
12618 if (ExplodeField[x][y])
12619 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12620 else if (element == EL_EXPLOSION)
12621 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12623 ExplodeField[x][y] = EX_TYPE_NONE;
12626 game.explosions_delayed = TRUE;
12628 if (game.magic_wall_active)
12630 if (!(game.magic_wall_time_left % 4))
12632 int element = Tile[magic_wall_x][magic_wall_y];
12634 if (element == EL_BD_MAGIC_WALL_FULL ||
12635 element == EL_BD_MAGIC_WALL_ACTIVE ||
12636 element == EL_BD_MAGIC_WALL_EMPTYING)
12637 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12638 else if (element == EL_DC_MAGIC_WALL_FULL ||
12639 element == EL_DC_MAGIC_WALL_ACTIVE ||
12640 element == EL_DC_MAGIC_WALL_EMPTYING)
12641 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12643 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12646 if (game.magic_wall_time_left > 0)
12648 game.magic_wall_time_left--;
12650 if (!game.magic_wall_time_left)
12652 SCAN_PLAYFIELD(x, y)
12654 element = Tile[x][y];
12656 if (element == EL_MAGIC_WALL_ACTIVE ||
12657 element == EL_MAGIC_WALL_FULL)
12659 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12660 TEST_DrawLevelField(x, y);
12662 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12663 element == EL_BD_MAGIC_WALL_FULL)
12665 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12666 TEST_DrawLevelField(x, y);
12668 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12669 element == EL_DC_MAGIC_WALL_FULL)
12671 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12672 TEST_DrawLevelField(x, y);
12676 game.magic_wall_active = FALSE;
12681 if (game.light_time_left > 0)
12683 game.light_time_left--;
12685 if (game.light_time_left == 0)
12686 RedrawAllLightSwitchesAndInvisibleElements();
12689 if (game.timegate_time_left > 0)
12691 game.timegate_time_left--;
12693 if (game.timegate_time_left == 0)
12694 CloseAllOpenTimegates();
12697 if (game.lenses_time_left > 0)
12699 game.lenses_time_left--;
12701 if (game.lenses_time_left == 0)
12702 RedrawAllInvisibleElementsForLenses();
12705 if (game.magnify_time_left > 0)
12707 game.magnify_time_left--;
12709 if (game.magnify_time_left == 0)
12710 RedrawAllInvisibleElementsForMagnifier();
12713 for (i = 0; i < MAX_PLAYERS; i++)
12715 struct PlayerInfo *player = &stored_player[i];
12717 if (SHIELD_ON(player))
12719 if (player->shield_deadly_time_left)
12720 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12721 else if (player->shield_normal_time_left)
12722 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12726 #if USE_DELAYED_GFX_REDRAW
12727 SCAN_PLAYFIELD(x, y)
12729 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12731 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12732 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12734 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12735 DrawLevelField(x, y);
12737 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12738 DrawLevelFieldCrumbled(x, y);
12740 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12741 DrawLevelFieldCrumbledNeighbours(x, y);
12743 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12744 DrawTwinkleOnField(x, y);
12747 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12752 PlayAllPlayersSound();
12754 for (i = 0; i < MAX_PLAYERS; i++)
12756 struct PlayerInfo *player = &stored_player[i];
12758 if (player->show_envelope != 0 && (!player->active ||
12759 player->MovPos == 0))
12761 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12763 player->show_envelope = 0;
12767 // use random number generator in every frame to make it less predictable
12768 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12771 mouse_action_last = mouse_action;
12774 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12776 int min_x = x, min_y = y, max_x = x, max_y = y;
12777 int scr_fieldx = getScreenFieldSizeX();
12778 int scr_fieldy = getScreenFieldSizeY();
12781 for (i = 0; i < MAX_PLAYERS; i++)
12783 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12785 if (!stored_player[i].active || &stored_player[i] == player)
12788 min_x = MIN(min_x, jx);
12789 min_y = MIN(min_y, jy);
12790 max_x = MAX(max_x, jx);
12791 max_y = MAX(max_y, jy);
12794 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12797 static boolean AllPlayersInVisibleScreen(void)
12801 for (i = 0; i < MAX_PLAYERS; i++)
12803 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12805 if (!stored_player[i].active)
12808 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12815 void ScrollLevel(int dx, int dy)
12817 int scroll_offset = 2 * TILEX_VAR;
12820 BlitBitmap(drawto_field, drawto_field,
12821 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12822 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12823 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12824 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12825 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12826 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12830 x = (dx == 1 ? BX1 : BX2);
12831 for (y = BY1; y <= BY2; y++)
12832 DrawScreenField(x, y);
12837 y = (dy == 1 ? BY1 : BY2);
12838 for (x = BX1; x <= BX2; x++)
12839 DrawScreenField(x, y);
12842 redraw_mask |= REDRAW_FIELD;
12845 static boolean canFallDown(struct PlayerInfo *player)
12847 int jx = player->jx, jy = player->jy;
12849 return (IN_LEV_FIELD(jx, jy + 1) &&
12850 (IS_FREE(jx, jy + 1) ||
12851 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12852 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12853 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12856 static boolean canPassField(int x, int y, int move_dir)
12858 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12859 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12860 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12861 int nextx = x + dx;
12862 int nexty = y + dy;
12863 int element = Tile[x][y];
12865 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12866 !CAN_MOVE(element) &&
12867 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12868 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12869 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12872 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12874 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12875 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12876 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12880 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12881 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12882 (IS_DIGGABLE(Tile[newx][newy]) ||
12883 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12884 canPassField(newx, newy, move_dir)));
12887 static void CheckGravityMovement(struct PlayerInfo *player)
12889 if (player->gravity && !player->programmed_action)
12891 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12892 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12893 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12894 int jx = player->jx, jy = player->jy;
12895 boolean player_is_moving_to_valid_field =
12896 (!player_is_snapping &&
12897 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12898 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12899 boolean player_can_fall_down = canFallDown(player);
12901 if (player_can_fall_down &&
12902 !player_is_moving_to_valid_field)
12903 player->programmed_action = MV_DOWN;
12907 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12909 return CheckGravityMovement(player);
12911 if (player->gravity && !player->programmed_action)
12913 int jx = player->jx, jy = player->jy;
12914 boolean field_under_player_is_free =
12915 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12916 boolean player_is_standing_on_valid_field =
12917 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12918 (IS_WALKABLE(Tile[jx][jy]) &&
12919 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12921 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12922 player->programmed_action = MV_DOWN;
12927 MovePlayerOneStep()
12928 -----------------------------------------------------------------------------
12929 dx, dy: direction (non-diagonal) to try to move the player to
12930 real_dx, real_dy: direction as read from input device (can be diagonal)
12933 boolean MovePlayerOneStep(struct PlayerInfo *player,
12934 int dx, int dy, int real_dx, int real_dy)
12936 int jx = player->jx, jy = player->jy;
12937 int new_jx = jx + dx, new_jy = jy + dy;
12939 boolean player_can_move = !player->cannot_move;
12941 if (!player->active || (!dx && !dy))
12942 return MP_NO_ACTION;
12944 player->MovDir = (dx < 0 ? MV_LEFT :
12945 dx > 0 ? MV_RIGHT :
12947 dy > 0 ? MV_DOWN : MV_NONE);
12949 if (!IN_LEV_FIELD(new_jx, new_jy))
12950 return MP_NO_ACTION;
12952 if (!player_can_move)
12954 if (player->MovPos == 0)
12956 player->is_moving = FALSE;
12957 player->is_digging = FALSE;
12958 player->is_collecting = FALSE;
12959 player->is_snapping = FALSE;
12960 player->is_pushing = FALSE;
12964 if (!network.enabled && game.centered_player_nr == -1 &&
12965 !AllPlayersInSight(player, new_jx, new_jy))
12966 return MP_NO_ACTION;
12968 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG);
12969 if (can_move != MP_MOVING)
12972 // check if DigField() has caused relocation of the player
12973 if (player->jx != jx || player->jy != jy)
12974 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12976 StorePlayer[jx][jy] = 0;
12977 player->last_jx = jx;
12978 player->last_jy = jy;
12979 player->jx = new_jx;
12980 player->jy = new_jy;
12981 StorePlayer[new_jx][new_jy] = player->element_nr;
12983 if (player->move_delay_value_next != -1)
12985 player->move_delay_value = player->move_delay_value_next;
12986 player->move_delay_value_next = -1;
12990 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12992 player->step_counter++;
12994 PlayerVisit[jx][jy] = FrameCounter;
12996 player->is_moving = TRUE;
12999 // should better be called in MovePlayer(), but this breaks some tapes
13000 ScrollPlayer(player, SCROLL_INIT);
13006 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
13008 int jx = player->jx, jy = player->jy;
13009 int old_jx = jx, old_jy = jy;
13010 int moved = MP_NO_ACTION;
13012 if (!player->active)
13017 if (player->MovPos == 0)
13019 player->is_moving = FALSE;
13020 player->is_digging = FALSE;
13021 player->is_collecting = FALSE;
13022 player->is_snapping = FALSE;
13023 player->is_pushing = FALSE;
13029 if (player->move_delay > 0)
13032 player->move_delay = -1; // set to "uninitialized" value
13034 // store if player is automatically moved to next field
13035 player->is_auto_moving = (player->programmed_action != MV_NONE);
13037 // remove the last programmed player action
13038 player->programmed_action = 0;
13040 if (player->MovPos)
13042 // should only happen if pre-1.2 tape recordings are played
13043 // this is only for backward compatibility
13045 int original_move_delay_value = player->move_delay_value;
13048 Debug("game:playing:MovePlayer",
13049 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13053 // scroll remaining steps with finest movement resolution
13054 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13056 while (player->MovPos)
13058 ScrollPlayer(player, SCROLL_GO_ON);
13059 ScrollScreen(NULL, SCROLL_GO_ON);
13061 AdvanceFrameAndPlayerCounters(player->index_nr);
13064 BackToFront_WithFrameDelay(0);
13067 player->move_delay_value = original_move_delay_value;
13070 player->is_active = FALSE;
13072 if (player->last_move_dir & MV_HORIZONTAL)
13074 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13075 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13079 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13080 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13083 if (!moved && !player->is_active)
13085 player->is_moving = FALSE;
13086 player->is_digging = FALSE;
13087 player->is_collecting = FALSE;
13088 player->is_snapping = FALSE;
13089 player->is_pushing = FALSE;
13095 if (moved & MP_MOVING && !ScreenMovPos &&
13096 (player->index_nr == game.centered_player_nr ||
13097 game.centered_player_nr == -1))
13099 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13101 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13103 // actual player has left the screen -- scroll in that direction
13104 if (jx != old_jx) // player has moved horizontally
13105 scroll_x += (jx - old_jx);
13106 else // player has moved vertically
13107 scroll_y += (jy - old_jy);
13111 int offset_raw = game.scroll_delay_value;
13113 if (jx != old_jx) // player has moved horizontally
13115 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13116 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13117 int new_scroll_x = jx - MIDPOSX + offset_x;
13119 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13120 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13121 scroll_x = new_scroll_x;
13123 // don't scroll over playfield boundaries
13124 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13126 // don't scroll more than one field at a time
13127 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13129 // don't scroll against the player's moving direction
13130 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13131 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13132 scroll_x = old_scroll_x;
13134 else // player has moved vertically
13136 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13137 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13138 int new_scroll_y = jy - MIDPOSY + offset_y;
13140 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13141 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13142 scroll_y = new_scroll_y;
13144 // don't scroll over playfield boundaries
13145 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13147 // don't scroll more than one field at a time
13148 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13150 // don't scroll against the player's moving direction
13151 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13152 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13153 scroll_y = old_scroll_y;
13157 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13159 if (!network.enabled && game.centered_player_nr == -1 &&
13160 !AllPlayersInVisibleScreen())
13162 scroll_x = old_scroll_x;
13163 scroll_y = old_scroll_y;
13167 ScrollScreen(player, SCROLL_INIT);
13168 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13173 player->StepFrame = 0;
13175 if (moved & MP_MOVING)
13177 if (old_jx != jx && old_jy == jy)
13178 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13179 else if (old_jx == jx && old_jy != jy)
13180 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13182 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13184 player->last_move_dir = player->MovDir;
13185 player->is_moving = TRUE;
13186 player->is_snapping = FALSE;
13187 player->is_switching = FALSE;
13188 player->is_dropping = FALSE;
13189 player->is_dropping_pressed = FALSE;
13190 player->drop_pressed_delay = 0;
13193 // should better be called here than above, but this breaks some tapes
13194 ScrollPlayer(player, SCROLL_INIT);
13199 CheckGravityMovementWhenNotMoving(player);
13201 player->is_moving = FALSE;
13203 /* at this point, the player is allowed to move, but cannot move right now
13204 (e.g. because of something blocking the way) -- ensure that the player
13205 is also allowed to move in the next frame (in old versions before 3.1.1,
13206 the player was forced to wait again for eight frames before next try) */
13208 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13209 player->move_delay = 0; // allow direct movement in the next frame
13212 if (player->move_delay == -1) // not yet initialized by DigField()
13213 player->move_delay = player->move_delay_value;
13215 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13217 TestIfPlayerTouchesBadThing(jx, jy);
13218 TestIfPlayerTouchesCustomElement(jx, jy);
13221 if (!player->active)
13222 RemovePlayer(player);
13227 void ScrollPlayer(struct PlayerInfo *player, int mode)
13229 int jx = player->jx, jy = player->jy;
13230 int last_jx = player->last_jx, last_jy = player->last_jy;
13231 int move_stepsize = TILEX / player->move_delay_value;
13233 if (!player->active)
13236 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13239 if (mode == SCROLL_INIT)
13241 player->actual_frame_counter.count = FrameCounter;
13242 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13244 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13245 Tile[last_jx][last_jy] == EL_EMPTY)
13247 int last_field_block_delay = 0; // start with no blocking at all
13248 int block_delay_adjustment = player->block_delay_adjustment;
13250 // if player blocks last field, add delay for exactly one move
13251 if (player->block_last_field)
13253 last_field_block_delay += player->move_delay_value;
13255 // when blocking enabled, prevent moving up despite gravity
13256 if (player->gravity && player->MovDir == MV_UP)
13257 block_delay_adjustment = -1;
13260 // add block delay adjustment (also possible when not blocking)
13261 last_field_block_delay += block_delay_adjustment;
13263 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13264 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13267 if (player->MovPos != 0) // player has not yet reached destination
13270 else if (!FrameReached(&player->actual_frame_counter))
13273 if (player->MovPos != 0)
13275 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13276 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13278 // before DrawPlayer() to draw correct player graphic for this case
13279 if (player->MovPos == 0)
13280 CheckGravityMovement(player);
13283 if (player->MovPos == 0) // player reached destination field
13285 if (player->move_delay_reset_counter > 0)
13287 player->move_delay_reset_counter--;
13289 if (player->move_delay_reset_counter == 0)
13291 // continue with normal speed after quickly moving through gate
13292 HALVE_PLAYER_SPEED(player);
13294 // be able to make the next move without delay
13295 player->move_delay = 0;
13299 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13300 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13301 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13302 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13303 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13304 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13305 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13306 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13308 ExitPlayer(player);
13310 if (game.players_still_needed == 0 &&
13311 (game.friends_still_needed == 0 ||
13312 IS_SP_ELEMENT(Tile[jx][jy])))
13316 player->last_jx = jx;
13317 player->last_jy = jy;
13319 // this breaks one level: "machine", level 000
13321 int move_direction = player->MovDir;
13322 int enter_side = MV_DIR_OPPOSITE(move_direction);
13323 int leave_side = move_direction;
13324 int old_jx = last_jx;
13325 int old_jy = last_jy;
13326 int old_element = Tile[old_jx][old_jy];
13327 int new_element = Tile[jx][jy];
13329 if (IS_CUSTOM_ELEMENT(old_element))
13330 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13332 player->index_bit, leave_side);
13334 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13335 CE_PLAYER_LEAVES_X,
13336 player->index_bit, leave_side);
13338 // needed because pushed element has not yet reached its destination,
13339 // so it would trigger a change event at its previous field location
13340 if (!player->is_pushing)
13342 if (IS_CUSTOM_ELEMENT(new_element))
13343 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13344 player->index_bit, enter_side);
13346 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13347 CE_PLAYER_ENTERS_X,
13348 player->index_bit, enter_side);
13351 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13352 CE_MOVE_OF_X, move_direction);
13355 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13357 TestIfPlayerTouchesBadThing(jx, jy);
13358 TestIfPlayerTouchesCustomElement(jx, jy);
13360 // needed because pushed element has not yet reached its destination,
13361 // so it would trigger a change event at its previous field location
13362 if (!player->is_pushing)
13363 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13365 if (level.finish_dig_collect &&
13366 (player->is_digging || player->is_collecting))
13368 int last_element = player->last_removed_element;
13369 int move_direction = player->MovDir;
13370 int enter_side = MV_DIR_OPPOSITE(move_direction);
13371 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13372 CE_PLAYER_COLLECTS_X);
13374 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13375 player->index_bit, enter_side);
13377 player->last_removed_element = EL_UNDEFINED;
13380 if (!player->active)
13381 RemovePlayer(player);
13384 if (level.use_step_counter)
13385 CheckLevelTime_StepCounter();
13387 if (tape.single_step && tape.recording && !tape.pausing &&
13388 !player->programmed_action)
13389 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13391 if (!player->programmed_action)
13392 CheckSaveEngineSnapshot(player);
13396 void ScrollScreen(struct PlayerInfo *player, int mode)
13398 static DelayCounter screen_frame_counter = { 0 };
13400 if (mode == SCROLL_INIT)
13402 // set scrolling step size according to actual player's moving speed
13403 ScrollStepSize = TILEX / player->move_delay_value;
13405 screen_frame_counter.count = FrameCounter;
13406 screen_frame_counter.value = 1;
13408 ScreenMovDir = player->MovDir;
13409 ScreenMovPos = player->MovPos;
13410 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13413 else if (!FrameReached(&screen_frame_counter))
13418 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13419 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13420 redraw_mask |= REDRAW_FIELD;
13423 ScreenMovDir = MV_NONE;
13426 void CheckNextToConditions(int x, int y)
13428 int element = Tile[x][y];
13430 if (IS_PLAYER(x, y))
13431 TestIfPlayerNextToCustomElement(x, y);
13433 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13434 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13435 TestIfElementNextToCustomElement(x, y);
13438 void TestIfPlayerNextToCustomElement(int x, int y)
13440 struct XY *xy = xy_topdown;
13441 static int trigger_sides[4][2] =
13443 // center side border side
13444 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13445 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13446 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13447 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13451 if (!IS_PLAYER(x, y))
13454 struct PlayerInfo *player = PLAYERINFO(x, y);
13456 if (player->is_moving)
13459 for (i = 0; i < NUM_DIRECTIONS; i++)
13461 int xx = x + xy[i].x;
13462 int yy = y + xy[i].y;
13463 int border_side = trigger_sides[i][1];
13464 int border_element;
13466 if (!IN_LEV_FIELD(xx, yy))
13469 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13470 continue; // center and border element not connected
13472 border_element = Tile[xx][yy];
13474 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13475 player->index_bit, border_side);
13476 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13477 CE_PLAYER_NEXT_TO_X,
13478 player->index_bit, border_side);
13480 /* use player element that is initially defined in the level playfield,
13481 not the player element that corresponds to the runtime player number
13482 (example: a level that contains EL_PLAYER_3 as the only player would
13483 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13485 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13486 CE_NEXT_TO_X, border_side);
13490 void TestIfPlayerTouchesCustomElement(int x, int y)
13492 struct XY *xy = xy_topdown;
13493 static int trigger_sides[4][2] =
13495 // center side border side
13496 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13497 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13498 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13499 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13501 static int touch_dir[4] =
13503 MV_LEFT | MV_RIGHT,
13508 int center_element = Tile[x][y]; // should always be non-moving!
13511 for (i = 0; i < NUM_DIRECTIONS; i++)
13513 int xx = x + xy[i].x;
13514 int yy = y + xy[i].y;
13515 int center_side = trigger_sides[i][0];
13516 int border_side = trigger_sides[i][1];
13517 int border_element;
13519 if (!IN_LEV_FIELD(xx, yy))
13522 if (IS_PLAYER(x, y)) // player found at center element
13524 struct PlayerInfo *player = PLAYERINFO(x, y);
13526 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13527 border_element = Tile[xx][yy]; // may be moving!
13528 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13529 border_element = Tile[xx][yy];
13530 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13531 border_element = MovingOrBlocked2Element(xx, yy);
13533 continue; // center and border element do not touch
13535 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13536 player->index_bit, border_side);
13537 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13538 CE_PLAYER_TOUCHES_X,
13539 player->index_bit, border_side);
13542 /* use player element that is initially defined in the level playfield,
13543 not the player element that corresponds to the runtime player number
13544 (example: a level that contains EL_PLAYER_3 as the only player would
13545 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13546 int player_element = PLAYERINFO(x, y)->initial_element;
13548 // as element "X" is the player here, check opposite (center) side
13549 CheckElementChangeBySide(xx, yy, border_element, player_element,
13550 CE_TOUCHING_X, center_side);
13553 else if (IS_PLAYER(xx, yy)) // player found at border element
13555 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13557 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13559 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13560 continue; // center and border element do not touch
13563 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13564 player->index_bit, center_side);
13565 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13566 CE_PLAYER_TOUCHES_X,
13567 player->index_bit, center_side);
13570 /* use player element that is initially defined in the level playfield,
13571 not the player element that corresponds to the runtime player number
13572 (example: a level that contains EL_PLAYER_3 as the only player would
13573 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13574 int player_element = PLAYERINFO(xx, yy)->initial_element;
13576 // as element "X" is the player here, check opposite (border) side
13577 CheckElementChangeBySide(x, y, center_element, player_element,
13578 CE_TOUCHING_X, border_side);
13586 void TestIfElementNextToCustomElement(int x, int y)
13588 struct XY *xy = xy_topdown;
13589 static int trigger_sides[4][2] =
13591 // center side border side
13592 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13593 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13594 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13595 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13597 int center_element = Tile[x][y]; // should always be non-moving!
13600 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13603 for (i = 0; i < NUM_DIRECTIONS; i++)
13605 int xx = x + xy[i].x;
13606 int yy = y + xy[i].y;
13607 int border_side = trigger_sides[i][1];
13608 int border_element;
13610 if (!IN_LEV_FIELD(xx, yy))
13613 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13614 continue; // center and border element not connected
13616 border_element = Tile[xx][yy];
13618 // check for change of center element (but change it only once)
13619 if (CheckElementChangeBySide(x, y, center_element, border_element,
13620 CE_NEXT_TO_X, border_side))
13625 void TestIfElementTouchesCustomElement(int x, int y)
13627 struct XY *xy = xy_topdown;
13628 static int trigger_sides[4][2] =
13630 // center side border side
13631 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13632 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13633 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13634 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13636 static int touch_dir[4] =
13638 MV_LEFT | MV_RIGHT,
13643 boolean change_center_element = FALSE;
13644 int center_element = Tile[x][y]; // should always be non-moving!
13645 int border_element_old[NUM_DIRECTIONS];
13648 for (i = 0; i < NUM_DIRECTIONS; i++)
13650 int xx = x + xy[i].x;
13651 int yy = y + xy[i].y;
13652 int border_element;
13654 border_element_old[i] = -1;
13656 if (!IN_LEV_FIELD(xx, yy))
13659 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13660 border_element = Tile[xx][yy]; // may be moving!
13661 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13662 border_element = Tile[xx][yy];
13663 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13664 border_element = MovingOrBlocked2Element(xx, yy);
13666 continue; // center and border element do not touch
13668 border_element_old[i] = border_element;
13671 for (i = 0; i < NUM_DIRECTIONS; i++)
13673 int xx = x + xy[i].x;
13674 int yy = y + xy[i].y;
13675 int center_side = trigger_sides[i][0];
13676 int border_element = border_element_old[i];
13678 if (border_element == -1)
13681 // check for change of border element
13682 CheckElementChangeBySide(xx, yy, border_element, center_element,
13683 CE_TOUCHING_X, center_side);
13685 // (center element cannot be player, so we don't have to check this here)
13688 for (i = 0; i < NUM_DIRECTIONS; i++)
13690 int xx = x + xy[i].x;
13691 int yy = y + xy[i].y;
13692 int border_side = trigger_sides[i][1];
13693 int border_element = border_element_old[i];
13695 if (border_element == -1)
13698 // check for change of center element (but change it only once)
13699 if (!change_center_element)
13700 change_center_element =
13701 CheckElementChangeBySide(x, y, center_element, border_element,
13702 CE_TOUCHING_X, border_side);
13704 if (IS_PLAYER(xx, yy))
13706 /* use player element that is initially defined in the level playfield,
13707 not the player element that corresponds to the runtime player number
13708 (example: a level that contains EL_PLAYER_3 as the only player would
13709 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13710 int player_element = PLAYERINFO(xx, yy)->initial_element;
13712 // as element "X" is the player here, check opposite (border) side
13713 CheckElementChangeBySide(x, y, center_element, player_element,
13714 CE_TOUCHING_X, border_side);
13719 void TestIfElementHitsCustomElement(int x, int y, int direction)
13721 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13722 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13723 int hitx = x + dx, hity = y + dy;
13724 int hitting_element = Tile[x][y];
13725 int touched_element;
13727 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13730 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13731 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13733 if (IN_LEV_FIELD(hitx, hity))
13735 int opposite_direction = MV_DIR_OPPOSITE(direction);
13736 int hitting_side = direction;
13737 int touched_side = opposite_direction;
13738 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13739 MovDir[hitx][hity] != direction ||
13740 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13746 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13747 CE_HITTING_X, touched_side);
13749 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13750 CE_HIT_BY_X, hitting_side);
13752 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13753 CE_HIT_BY_SOMETHING, opposite_direction);
13755 if (IS_PLAYER(hitx, hity))
13757 /* use player element that is initially defined in the level playfield,
13758 not the player element that corresponds to the runtime player number
13759 (example: a level that contains EL_PLAYER_3 as the only player would
13760 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13761 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13763 CheckElementChangeBySide(x, y, hitting_element, player_element,
13764 CE_HITTING_X, touched_side);
13769 // "hitting something" is also true when hitting the playfield border
13770 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13771 CE_HITTING_SOMETHING, direction);
13774 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13776 int i, kill_x = -1, kill_y = -1;
13778 int bad_element = -1;
13779 struct XY *test_xy = xy_topdown;
13780 static int test_dir[4] =
13788 for (i = 0; i < NUM_DIRECTIONS; i++)
13790 int test_x, test_y, test_move_dir, test_element;
13792 test_x = good_x + test_xy[i].x;
13793 test_y = good_y + test_xy[i].y;
13795 if (!IN_LEV_FIELD(test_x, test_y))
13799 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13801 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13803 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13804 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13806 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13807 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13811 bad_element = test_element;
13817 if (kill_x != -1 || kill_y != -1)
13819 if (IS_PLAYER(good_x, good_y))
13821 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13823 if (player->shield_deadly_time_left > 0 &&
13824 !IS_INDESTRUCTIBLE(bad_element))
13825 Bang(kill_x, kill_y);
13826 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13827 KillPlayer(player);
13830 Bang(good_x, good_y);
13834 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13836 int i, kill_x = -1, kill_y = -1;
13837 int bad_element = Tile[bad_x][bad_y];
13838 struct XY *test_xy = xy_topdown;
13839 static int touch_dir[4] =
13841 MV_LEFT | MV_RIGHT,
13846 static int test_dir[4] =
13854 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13857 for (i = 0; i < NUM_DIRECTIONS; i++)
13859 int test_x, test_y, test_move_dir, test_element;
13861 test_x = bad_x + test_xy[i].x;
13862 test_y = bad_y + test_xy[i].y;
13864 if (!IN_LEV_FIELD(test_x, test_y))
13868 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13870 test_element = Tile[test_x][test_y];
13872 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13873 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13875 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13876 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13878 // good thing is player or penguin that does not move away
13879 if (IS_PLAYER(test_x, test_y))
13881 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13883 if (bad_element == EL_ROBOT && player->is_moving)
13884 continue; // robot does not kill player if he is moving
13886 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13888 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13889 continue; // center and border element do not touch
13897 else if (test_element == EL_PENGUIN)
13907 if (kill_x != -1 || kill_y != -1)
13909 if (IS_PLAYER(kill_x, kill_y))
13911 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13913 if (player->shield_deadly_time_left > 0 &&
13914 !IS_INDESTRUCTIBLE(bad_element))
13915 Bang(bad_x, bad_y);
13916 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13917 KillPlayer(player);
13920 Bang(kill_x, kill_y);
13924 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13926 int bad_element = Tile[bad_x][bad_y];
13927 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13928 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13929 int test_x = bad_x + dx, test_y = bad_y + dy;
13930 int test_move_dir, test_element;
13931 int kill_x = -1, kill_y = -1;
13933 if (!IN_LEV_FIELD(test_x, test_y))
13937 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13939 test_element = Tile[test_x][test_y];
13941 if (test_move_dir != bad_move_dir)
13943 // good thing can be player or penguin that does not move away
13944 if (IS_PLAYER(test_x, test_y))
13946 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13948 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13949 player as being hit when he is moving towards the bad thing, because
13950 the "get hit by" condition would be lost after the player stops) */
13951 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13952 return; // player moves away from bad thing
13957 else if (test_element == EL_PENGUIN)
13964 if (kill_x != -1 || kill_y != -1)
13966 if (IS_PLAYER(kill_x, kill_y))
13968 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13970 if (player->shield_deadly_time_left > 0 &&
13971 !IS_INDESTRUCTIBLE(bad_element))
13972 Bang(bad_x, bad_y);
13973 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13974 KillPlayer(player);
13977 Bang(kill_x, kill_y);
13981 void TestIfPlayerTouchesBadThing(int x, int y)
13983 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13986 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13988 TestIfGoodThingHitsBadThing(x, y, move_dir);
13991 void TestIfBadThingTouchesPlayer(int x, int y)
13993 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13996 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13998 TestIfBadThingHitsGoodThing(x, y, move_dir);
14001 void TestIfFriendTouchesBadThing(int x, int y)
14003 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
14006 void TestIfBadThingTouchesFriend(int x, int y)
14008 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
14011 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
14013 int i, kill_x = bad_x, kill_y = bad_y;
14014 struct XY *xy = xy_topdown;
14016 for (i = 0; i < NUM_DIRECTIONS; i++)
14020 x = bad_x + xy[i].x;
14021 y = bad_y + xy[i].y;
14022 if (!IN_LEV_FIELD(x, y))
14025 element = Tile[x][y];
14026 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14027 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14035 if (kill_x != bad_x || kill_y != bad_y)
14036 Bang(bad_x, bad_y);
14039 void KillPlayer(struct PlayerInfo *player)
14041 int jx = player->jx, jy = player->jy;
14043 if (!player->active)
14047 Debug("game:playing:KillPlayer",
14048 "0: killed == %d, active == %d, reanimated == %d",
14049 player->killed, player->active, player->reanimated);
14052 /* the following code was introduced to prevent an infinite loop when calling
14054 -> CheckTriggeredElementChangeExt()
14055 -> ExecuteCustomElementAction()
14057 -> (infinitely repeating the above sequence of function calls)
14058 which occurs when killing the player while having a CE with the setting
14059 "kill player X when explosion of <player X>"; the solution using a new
14060 field "player->killed" was chosen for backwards compatibility, although
14061 clever use of the fields "player->active" etc. would probably also work */
14063 if (player->killed)
14067 player->killed = TRUE;
14069 // remove accessible field at the player's position
14070 RemoveField(jx, jy);
14072 // deactivate shield (else Bang()/Explode() would not work right)
14073 player->shield_normal_time_left = 0;
14074 player->shield_deadly_time_left = 0;
14077 Debug("game:playing:KillPlayer",
14078 "1: killed == %d, active == %d, reanimated == %d",
14079 player->killed, player->active, player->reanimated);
14085 Debug("game:playing:KillPlayer",
14086 "2: killed == %d, active == %d, reanimated == %d",
14087 player->killed, player->active, player->reanimated);
14090 if (player->reanimated) // killed player may have been reanimated
14091 player->killed = player->reanimated = FALSE;
14093 BuryPlayer(player);
14096 static void KillPlayerUnlessEnemyProtected(int x, int y)
14098 if (!PLAYER_ENEMY_PROTECTED(x, y))
14099 KillPlayer(PLAYERINFO(x, y));
14102 static void KillPlayerUnlessExplosionProtected(int x, int y)
14104 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14105 KillPlayer(PLAYERINFO(x, y));
14108 void BuryPlayer(struct PlayerInfo *player)
14110 int jx = player->jx, jy = player->jy;
14112 if (!player->active)
14115 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14117 RemovePlayer(player);
14119 player->buried = TRUE;
14121 if (game.all_players_gone)
14122 game.GameOver = TRUE;
14125 void RemovePlayer(struct PlayerInfo *player)
14127 int jx = player->jx, jy = player->jy;
14128 int i, found = FALSE;
14130 player->present = FALSE;
14131 player->active = FALSE;
14133 // required for some CE actions (even if the player is not active anymore)
14134 player->MovPos = 0;
14136 if (!ExplodeField[jx][jy])
14137 StorePlayer[jx][jy] = 0;
14139 if (player->is_moving)
14140 TEST_DrawLevelField(player->last_jx, player->last_jy);
14142 for (i = 0; i < MAX_PLAYERS; i++)
14143 if (stored_player[i].active)
14148 game.all_players_gone = TRUE;
14149 game.GameOver = TRUE;
14152 game.exit_x = game.robot_wheel_x = jx;
14153 game.exit_y = game.robot_wheel_y = jy;
14156 void ExitPlayer(struct PlayerInfo *player)
14158 DrawPlayer(player); // needed here only to cleanup last field
14159 RemovePlayer(player);
14161 if (game.players_still_needed > 0)
14162 game.players_still_needed--;
14165 static void SetFieldForSnapping(int x, int y, int element, int direction,
14166 int player_index_bit)
14168 struct ElementInfo *ei = &element_info[element];
14169 int direction_bit = MV_DIR_TO_BIT(direction);
14170 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14171 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14172 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14174 Tile[x][y] = EL_ELEMENT_SNAPPING;
14175 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14176 MovDir[x][y] = direction;
14177 Store[x][y] = element;
14178 Store2[x][y] = player_index_bit;
14180 ResetGfxAnimation(x, y);
14182 GfxElement[x][y] = element;
14183 GfxAction[x][y] = action;
14184 GfxDir[x][y] = direction;
14185 GfxFrame[x][y] = -1;
14188 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14189 int player_index_bit)
14191 TestIfElementTouchesCustomElement(x, y); // for empty space
14193 if (level.finish_dig_collect)
14195 int dig_side = MV_DIR_OPPOSITE(direction);
14196 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14197 CE_PLAYER_COLLECTS_X);
14199 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14200 player_index_bit, dig_side);
14201 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14202 player_index_bit, dig_side);
14207 =============================================================================
14208 checkDiagonalPushing()
14209 -----------------------------------------------------------------------------
14210 check if diagonal input device direction results in pushing of object
14211 (by checking if the alternative direction is walkable, diggable, ...)
14212 =============================================================================
14215 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14216 int x, int y, int real_dx, int real_dy)
14218 int jx, jy, dx, dy, xx, yy;
14220 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14223 // diagonal direction: check alternative direction
14228 xx = jx + (dx == 0 ? real_dx : 0);
14229 yy = jy + (dy == 0 ? real_dy : 0);
14231 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14235 =============================================================================
14237 -----------------------------------------------------------------------------
14238 x, y: field next to player (non-diagonal) to try to dig to
14239 real_dx, real_dy: direction as read from input device (can be diagonal)
14240 =============================================================================
14243 static int DigField(struct PlayerInfo *player,
14244 int oldx, int oldy, int x, int y,
14245 int real_dx, int real_dy, int mode)
14247 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14248 boolean player_was_pushing = player->is_pushing;
14249 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14250 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14251 int jx = oldx, jy = oldy;
14252 int dx = x - jx, dy = y - jy;
14253 int nextx = x + dx, nexty = y + dy;
14254 int move_direction = (dx == -1 ? MV_LEFT :
14255 dx == +1 ? MV_RIGHT :
14257 dy == +1 ? MV_DOWN : MV_NONE);
14258 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14259 int dig_side = MV_DIR_OPPOSITE(move_direction);
14260 int old_element = Tile[jx][jy];
14261 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14264 if (is_player) // function can also be called by EL_PENGUIN
14266 if (player->MovPos == 0)
14268 player->is_digging = FALSE;
14269 player->is_collecting = FALSE;
14272 if (player->MovPos == 0) // last pushing move finished
14273 player->is_pushing = FALSE;
14275 if (mode == DF_NO_PUSH) // player just stopped pushing
14277 player->is_switching = FALSE;
14278 player->push_delay = -1;
14280 return MP_NO_ACTION;
14283 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14284 old_element = Back[jx][jy];
14286 // in case of element dropped at player position, check background
14287 else if (Back[jx][jy] != EL_EMPTY &&
14288 game.engine_version >= VERSION_IDENT(2,2,0,0))
14289 old_element = Back[jx][jy];
14291 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14292 return MP_NO_ACTION; // field has no opening in this direction
14294 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction))
14295 return MP_NO_ACTION; // field has no opening in this direction
14297 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14301 Tile[jx][jy] = player->artwork_element;
14302 InitMovingField(jx, jy, MV_DOWN);
14303 Store[jx][jy] = EL_ACID;
14304 ContinueMoving(jx, jy);
14305 BuryPlayer(player);
14307 return MP_DONT_RUN_INTO;
14310 if (player_can_move && DONT_RUN_INTO(element))
14312 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14314 return MP_DONT_RUN_INTO;
14317 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14318 return MP_NO_ACTION;
14320 collect_count = element_info[element].collect_count_initial;
14322 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14323 return MP_NO_ACTION;
14325 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14326 player_can_move = player_can_move_or_snap;
14328 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14329 game.engine_version >= VERSION_IDENT(2,2,0,0))
14331 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14332 player->index_bit, dig_side);
14333 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14334 player->index_bit, dig_side);
14336 if (element == EL_DC_LANDMINE)
14339 if (Tile[x][y] != element) // field changed by snapping
14342 return MP_NO_ACTION;
14345 if (player->gravity && is_player && !player->is_auto_moving &&
14346 canFallDown(player) && move_direction != MV_DOWN &&
14347 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14348 return MP_NO_ACTION; // player cannot walk here due to gravity
14350 if (player_can_move &&
14351 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14353 int sound_element = SND_ELEMENT(element);
14354 int sound_action = ACTION_WALKING;
14356 if (IS_RND_GATE(element))
14358 if (!player->key[RND_GATE_NR(element)])
14359 return MP_NO_ACTION;
14361 else if (IS_RND_GATE_GRAY(element))
14363 if (!player->key[RND_GATE_GRAY_NR(element)])
14364 return MP_NO_ACTION;
14366 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14368 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14369 return MP_NO_ACTION;
14371 else if (element == EL_EXIT_OPEN ||
14372 element == EL_EM_EXIT_OPEN ||
14373 element == EL_EM_EXIT_OPENING ||
14374 element == EL_STEEL_EXIT_OPEN ||
14375 element == EL_EM_STEEL_EXIT_OPEN ||
14376 element == EL_EM_STEEL_EXIT_OPENING ||
14377 element == EL_SP_EXIT_OPEN ||
14378 element == EL_SP_EXIT_OPENING)
14380 sound_action = ACTION_PASSING; // player is passing exit
14382 else if (element == EL_EMPTY)
14384 sound_action = ACTION_MOVING; // nothing to walk on
14387 // play sound from background or player, whatever is available
14388 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14389 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14391 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14393 else if (player_can_move &&
14394 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14396 if (!ACCESS_FROM(element, opposite_direction))
14397 return MP_NO_ACTION; // field not accessible from this direction
14399 if (CAN_MOVE(element)) // only fixed elements can be passed!
14400 return MP_NO_ACTION;
14402 if (IS_EM_GATE(element))
14404 if (!player->key[EM_GATE_NR(element)])
14405 return MP_NO_ACTION;
14407 else if (IS_EM_GATE_GRAY(element))
14409 if (!player->key[EM_GATE_GRAY_NR(element)])
14410 return MP_NO_ACTION;
14412 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14414 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14415 return MP_NO_ACTION;
14417 else if (IS_EMC_GATE(element))
14419 if (!player->key[EMC_GATE_NR(element)])
14420 return MP_NO_ACTION;
14422 else if (IS_EMC_GATE_GRAY(element))
14424 if (!player->key[EMC_GATE_GRAY_NR(element)])
14425 return MP_NO_ACTION;
14427 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14429 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14430 return MP_NO_ACTION;
14432 else if (element == EL_DC_GATE_WHITE ||
14433 element == EL_DC_GATE_WHITE_GRAY ||
14434 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14436 if (player->num_white_keys == 0)
14437 return MP_NO_ACTION;
14439 player->num_white_keys--;
14441 else if (IS_SP_PORT(element))
14443 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14444 element == EL_SP_GRAVITY_PORT_RIGHT ||
14445 element == EL_SP_GRAVITY_PORT_UP ||
14446 element == EL_SP_GRAVITY_PORT_DOWN)
14447 player->gravity = !player->gravity;
14448 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14449 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14450 element == EL_SP_GRAVITY_ON_PORT_UP ||
14451 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14452 player->gravity = TRUE;
14453 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14454 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14455 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14456 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14457 player->gravity = FALSE;
14460 // automatically move to the next field with double speed
14461 player->programmed_action = move_direction;
14463 if (player->move_delay_reset_counter == 0)
14465 player->move_delay_reset_counter = 2; // two double speed steps
14467 DOUBLE_PLAYER_SPEED(player);
14470 PlayLevelSoundAction(x, y, ACTION_PASSING);
14472 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14476 if (mode != DF_SNAP)
14478 GfxElement[x][y] = GFX_ELEMENT(element);
14479 player->is_digging = TRUE;
14482 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14484 // use old behaviour for old levels (digging)
14485 if (!level.finish_dig_collect)
14487 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14488 player->index_bit, dig_side);
14490 // if digging triggered player relocation, finish digging tile
14491 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14492 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14495 if (mode == DF_SNAP)
14497 if (level.block_snap_field)
14498 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14500 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14502 // use old behaviour for old levels (snapping)
14503 if (!level.finish_dig_collect)
14504 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14505 player->index_bit, dig_side);
14508 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14512 if (is_player && mode != DF_SNAP)
14514 GfxElement[x][y] = element;
14515 player->is_collecting = TRUE;
14518 if (element == EL_SPEED_PILL)
14520 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14522 else if (element == EL_EXTRA_TIME && level.time > 0)
14524 TimeLeft += level.extra_time;
14526 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14528 DisplayGameControlValues();
14530 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14532 int shield_time = (element == EL_SHIELD_DEADLY ?
14533 level.shield_deadly_time :
14534 level.shield_normal_time);
14536 player->shield_normal_time_left += shield_time;
14537 if (element == EL_SHIELD_DEADLY)
14538 player->shield_deadly_time_left += shield_time;
14540 else if (element == EL_DYNAMITE ||
14541 element == EL_EM_DYNAMITE ||
14542 element == EL_SP_DISK_RED)
14544 if (player->inventory_size < MAX_INVENTORY_SIZE)
14545 player->inventory_element[player->inventory_size++] = element;
14547 DrawGameDoorValues();
14549 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14551 player->dynabomb_count++;
14552 player->dynabombs_left++;
14554 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14556 player->dynabomb_size++;
14558 else if (element == EL_DYNABOMB_INCREASE_POWER)
14560 player->dynabomb_xl = TRUE;
14562 else if (IS_KEY(element))
14564 player->key[KEY_NR(element)] = TRUE;
14566 DrawGameDoorValues();
14568 else if (element == EL_DC_KEY_WHITE)
14570 player->num_white_keys++;
14572 // display white keys?
14573 // DrawGameDoorValues();
14575 else if (IS_ENVELOPE(element))
14577 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14579 if (!wait_for_snapping)
14580 player->show_envelope = element;
14582 else if (element == EL_EMC_LENSES)
14584 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14586 RedrawAllInvisibleElementsForLenses();
14588 else if (element == EL_EMC_MAGNIFIER)
14590 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14592 RedrawAllInvisibleElementsForMagnifier();
14594 else if (IS_DROPPABLE(element) ||
14595 IS_THROWABLE(element)) // can be collected and dropped
14599 if (collect_count == 0)
14600 player->inventory_infinite_element = element;
14602 for (i = 0; i < collect_count; i++)
14603 if (player->inventory_size < MAX_INVENTORY_SIZE)
14604 player->inventory_element[player->inventory_size++] = element;
14606 DrawGameDoorValues();
14608 else if (collect_count > 0)
14610 game.gems_still_needed -= collect_count;
14611 if (game.gems_still_needed < 0)
14612 game.gems_still_needed = 0;
14614 game.snapshot.collected_item = TRUE;
14616 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14618 DisplayGameControlValues();
14621 RaiseScoreElement(element);
14622 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14624 // use old behaviour for old levels (collecting)
14625 if (!level.finish_dig_collect && is_player)
14627 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14628 player->index_bit, dig_side);
14630 // if collecting triggered player relocation, finish collecting tile
14631 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14632 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14635 if (mode == DF_SNAP)
14637 if (level.block_snap_field)
14638 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14640 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14642 // use old behaviour for old levels (snapping)
14643 if (!level.finish_dig_collect)
14644 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14645 player->index_bit, dig_side);
14648 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14650 if (mode == DF_SNAP && element != EL_BD_ROCK)
14651 return MP_NO_ACTION;
14653 if (CAN_FALL(element) && dy)
14654 return MP_NO_ACTION;
14656 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14657 !(element == EL_SPRING && level.use_spring_bug))
14658 return MP_NO_ACTION;
14660 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14661 ((move_direction & MV_VERTICAL &&
14662 ((element_info[element].move_pattern & MV_LEFT &&
14663 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14664 (element_info[element].move_pattern & MV_RIGHT &&
14665 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14666 (move_direction & MV_HORIZONTAL &&
14667 ((element_info[element].move_pattern & MV_UP &&
14668 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14669 (element_info[element].move_pattern & MV_DOWN &&
14670 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14671 return MP_NO_ACTION;
14673 // do not push elements already moving away faster than player
14674 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14675 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14676 return MP_NO_ACTION;
14678 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14680 if (player->push_delay_value == -1 || !player_was_pushing)
14681 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14683 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14685 if (player->push_delay_value == -1)
14686 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14688 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14690 if (!player->is_pushing)
14691 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14694 player->is_pushing = TRUE;
14695 player->is_active = TRUE;
14697 if (!(IN_LEV_FIELD(nextx, nexty) &&
14698 (IS_FREE(nextx, nexty) ||
14699 (IS_SB_ELEMENT(element) &&
14700 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14701 (IS_CUSTOM_ELEMENT(element) &&
14702 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14703 return MP_NO_ACTION;
14705 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14706 return MP_NO_ACTION;
14708 if (player->push_delay == -1) // new pushing; restart delay
14709 player->push_delay = 0;
14711 if (player->push_delay < player->push_delay_value &&
14712 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14713 element != EL_SPRING && element != EL_BALLOON)
14715 // make sure that there is no move delay before next try to push
14716 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14717 player->move_delay = 0;
14719 return MP_NO_ACTION;
14722 if (IS_CUSTOM_ELEMENT(element) &&
14723 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14725 if (!DigFieldByCE(nextx, nexty, element))
14726 return MP_NO_ACTION;
14729 if (IS_SB_ELEMENT(element))
14731 boolean sokoban_task_solved = FALSE;
14733 if (element == EL_SOKOBAN_FIELD_FULL)
14735 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14737 IncrementSokobanFieldsNeeded();
14738 IncrementSokobanObjectsNeeded();
14741 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14743 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14745 DecrementSokobanFieldsNeeded();
14746 DecrementSokobanObjectsNeeded();
14748 // sokoban object was pushed from empty field to sokoban field
14749 if (Back[x][y] == EL_EMPTY)
14750 sokoban_task_solved = TRUE;
14753 Tile[x][y] = EL_SOKOBAN_OBJECT;
14755 if (Back[x][y] == Back[nextx][nexty])
14756 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14757 else if (Back[x][y] != 0)
14758 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14761 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14764 if (sokoban_task_solved &&
14765 game.sokoban_fields_still_needed == 0 &&
14766 game.sokoban_objects_still_needed == 0 &&
14767 level.auto_exit_sokoban)
14769 game.players_still_needed = 0;
14773 PlaySound(SND_GAME_SOKOBAN_SOLVING);
14777 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14779 InitMovingField(x, y, move_direction);
14780 GfxAction[x][y] = ACTION_PUSHING;
14782 if (mode == DF_SNAP)
14783 ContinueMoving(x, y);
14785 MovPos[x][y] = (dx != 0 ? dx : dy);
14787 Pushed[x][y] = TRUE;
14788 Pushed[nextx][nexty] = TRUE;
14790 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14791 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14793 player->push_delay_value = -1; // get new value later
14795 // check for element change _after_ element has been pushed
14796 if (game.use_change_when_pushing_bug)
14798 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14799 player->index_bit, dig_side);
14800 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14801 player->index_bit, dig_side);
14804 else if (IS_SWITCHABLE(element))
14806 if (PLAYER_SWITCHING(player, x, y))
14808 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14809 player->index_bit, dig_side);
14814 player->is_switching = TRUE;
14815 player->switch_x = x;
14816 player->switch_y = y;
14818 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14820 if (element == EL_ROBOT_WHEEL)
14822 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14824 game.robot_wheel_x = x;
14825 game.robot_wheel_y = y;
14826 game.robot_wheel_active = TRUE;
14828 TEST_DrawLevelField(x, y);
14830 else if (element == EL_SP_TERMINAL)
14834 SCAN_PLAYFIELD(xx, yy)
14836 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14840 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14842 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14844 ResetGfxAnimation(xx, yy);
14845 TEST_DrawLevelField(xx, yy);
14849 else if (IS_BELT_SWITCH(element))
14851 ToggleBeltSwitch(x, y);
14853 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14854 element == EL_SWITCHGATE_SWITCH_DOWN ||
14855 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14856 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14858 ToggleSwitchgateSwitch();
14860 else if (element == EL_LIGHT_SWITCH ||
14861 element == EL_LIGHT_SWITCH_ACTIVE)
14863 ToggleLightSwitch(x, y);
14865 else if (element == EL_TIMEGATE_SWITCH ||
14866 element == EL_DC_TIMEGATE_SWITCH)
14868 ActivateTimegateSwitch(x, y);
14870 else if (element == EL_BALLOON_SWITCH_LEFT ||
14871 element == EL_BALLOON_SWITCH_RIGHT ||
14872 element == EL_BALLOON_SWITCH_UP ||
14873 element == EL_BALLOON_SWITCH_DOWN ||
14874 element == EL_BALLOON_SWITCH_NONE ||
14875 element == EL_BALLOON_SWITCH_ANY)
14877 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14878 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14879 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14880 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14881 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14884 else if (element == EL_LAMP)
14886 Tile[x][y] = EL_LAMP_ACTIVE;
14887 game.lights_still_needed--;
14889 ResetGfxAnimation(x, y);
14890 TEST_DrawLevelField(x, y);
14892 else if (element == EL_TIME_ORB_FULL)
14894 Tile[x][y] = EL_TIME_ORB_EMPTY;
14896 if (level.time > 0 || level.use_time_orb_bug)
14898 TimeLeft += level.time_orb_time;
14899 game.no_level_time_limit = FALSE;
14901 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14903 DisplayGameControlValues();
14906 ResetGfxAnimation(x, y);
14907 TEST_DrawLevelField(x, y);
14909 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14910 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14914 game.ball_active = !game.ball_active;
14916 SCAN_PLAYFIELD(xx, yy)
14918 int e = Tile[xx][yy];
14920 if (game.ball_active)
14922 if (e == EL_EMC_MAGIC_BALL)
14923 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14924 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14925 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14929 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14930 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14931 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14932 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14937 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14938 player->index_bit, dig_side);
14940 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14941 player->index_bit, dig_side);
14943 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14944 player->index_bit, dig_side);
14950 if (!PLAYER_SWITCHING(player, x, y))
14952 player->is_switching = TRUE;
14953 player->switch_x = x;
14954 player->switch_y = y;
14956 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14957 player->index_bit, dig_side);
14958 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14959 player->index_bit, dig_side);
14961 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14962 player->index_bit, dig_side);
14963 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14964 player->index_bit, dig_side);
14967 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14968 player->index_bit, dig_side);
14969 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14970 player->index_bit, dig_side);
14972 return MP_NO_ACTION;
14975 player->push_delay = -1;
14977 if (is_player) // function can also be called by EL_PENGUIN
14979 if (Tile[x][y] != element) // really digged/collected something
14981 player->is_collecting = !player->is_digging;
14982 player->is_active = TRUE;
14984 player->last_removed_element = element;
14991 static boolean DigFieldByCE(int x, int y, int digging_element)
14993 int element = Tile[x][y];
14995 if (!IS_FREE(x, y))
14997 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14998 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
15001 // no element can dig solid indestructible elements
15002 if (IS_INDESTRUCTIBLE(element) &&
15003 !IS_DIGGABLE(element) &&
15004 !IS_COLLECTIBLE(element))
15007 if (AmoebaNr[x][y] &&
15008 (element == EL_AMOEBA_FULL ||
15009 element == EL_BD_AMOEBA ||
15010 element == EL_AMOEBA_GROWING))
15012 AmoebaCnt[AmoebaNr[x][y]]--;
15013 AmoebaCnt2[AmoebaNr[x][y]]--;
15016 if (IS_MOVING(x, y))
15017 RemoveMovingField(x, y);
15021 TEST_DrawLevelField(x, y);
15024 // if digged element was about to explode, prevent the explosion
15025 ExplodeField[x][y] = EX_TYPE_NONE;
15027 PlayLevelSoundAction(x, y, action);
15030 Store[x][y] = EL_EMPTY;
15032 // this makes it possible to leave the removed element again
15033 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15034 Store[x][y] = element;
15039 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15041 int jx = player->jx, jy = player->jy;
15042 int x = jx + dx, y = jy + dy;
15043 int snap_direction = (dx == -1 ? MV_LEFT :
15044 dx == +1 ? MV_RIGHT :
15046 dy == +1 ? MV_DOWN : MV_NONE);
15047 boolean can_continue_snapping = (level.continuous_snapping &&
15048 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15050 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15053 if (!player->active || !IN_LEV_FIELD(x, y))
15061 if (player->MovPos == 0)
15062 player->is_pushing = FALSE;
15064 player->is_snapping = FALSE;
15066 if (player->MovPos == 0)
15068 player->is_moving = FALSE;
15069 player->is_digging = FALSE;
15070 player->is_collecting = FALSE;
15076 // prevent snapping with already pressed snap key when not allowed
15077 if (player->is_snapping && !can_continue_snapping)
15080 player->MovDir = snap_direction;
15082 if (player->MovPos == 0)
15084 player->is_moving = FALSE;
15085 player->is_digging = FALSE;
15086 player->is_collecting = FALSE;
15089 player->is_dropping = FALSE;
15090 player->is_dropping_pressed = FALSE;
15091 player->drop_pressed_delay = 0;
15093 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15096 player->is_snapping = TRUE;
15097 player->is_active = TRUE;
15099 if (player->MovPos == 0)
15101 player->is_moving = FALSE;
15102 player->is_digging = FALSE;
15103 player->is_collecting = FALSE;
15106 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15107 TEST_DrawLevelField(player->last_jx, player->last_jy);
15109 TEST_DrawLevelField(x, y);
15114 static boolean DropElement(struct PlayerInfo *player)
15116 int old_element, new_element;
15117 int dropx = player->jx, dropy = player->jy;
15118 int drop_direction = player->MovDir;
15119 int drop_side = drop_direction;
15120 int drop_element = get_next_dropped_element(player);
15122 /* do not drop an element on top of another element; when holding drop key
15123 pressed without moving, dropped element must move away before the next
15124 element can be dropped (this is especially important if the next element
15125 is dynamite, which can be placed on background for historical reasons) */
15126 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15129 if (IS_THROWABLE(drop_element))
15131 dropx += GET_DX_FROM_DIR(drop_direction);
15132 dropy += GET_DY_FROM_DIR(drop_direction);
15134 if (!IN_LEV_FIELD(dropx, dropy))
15138 old_element = Tile[dropx][dropy]; // old element at dropping position
15139 new_element = drop_element; // default: no change when dropping
15141 // check if player is active, not moving and ready to drop
15142 if (!player->active || player->MovPos || player->drop_delay > 0)
15145 // check if player has anything that can be dropped
15146 if (new_element == EL_UNDEFINED)
15149 // only set if player has anything that can be dropped
15150 player->is_dropping_pressed = TRUE;
15152 // check if drop key was pressed long enough for EM style dynamite
15153 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15156 // check if anything can be dropped at the current position
15157 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15160 // collected custom elements can only be dropped on empty fields
15161 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15164 if (old_element != EL_EMPTY)
15165 Back[dropx][dropy] = old_element; // store old element on this field
15167 ResetGfxAnimation(dropx, dropy);
15168 ResetRandomAnimationValue(dropx, dropy);
15170 if (player->inventory_size > 0 ||
15171 player->inventory_infinite_element != EL_UNDEFINED)
15173 if (player->inventory_size > 0)
15175 player->inventory_size--;
15177 DrawGameDoorValues();
15179 if (new_element == EL_DYNAMITE)
15180 new_element = EL_DYNAMITE_ACTIVE;
15181 else if (new_element == EL_EM_DYNAMITE)
15182 new_element = EL_EM_DYNAMITE_ACTIVE;
15183 else if (new_element == EL_SP_DISK_RED)
15184 new_element = EL_SP_DISK_RED_ACTIVE;
15187 Tile[dropx][dropy] = new_element;
15189 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15190 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15191 el2img(Tile[dropx][dropy]), 0);
15193 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15195 // needed if previous element just changed to "empty" in the last frame
15196 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15198 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15199 player->index_bit, drop_side);
15200 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15202 player->index_bit, drop_side);
15204 TestIfElementTouchesCustomElement(dropx, dropy);
15206 else // player is dropping a dyna bomb
15208 player->dynabombs_left--;
15210 Tile[dropx][dropy] = new_element;
15212 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15213 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15214 el2img(Tile[dropx][dropy]), 0);
15216 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15219 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15220 InitField_WithBug1(dropx, dropy, FALSE);
15222 new_element = Tile[dropx][dropy]; // element might have changed
15224 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15225 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15227 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15228 MovDir[dropx][dropy] = drop_direction;
15230 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15232 // do not cause impact style collision by dropping elements that can fall
15233 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15236 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15237 player->is_dropping = TRUE;
15239 player->drop_pressed_delay = 0;
15240 player->is_dropping_pressed = FALSE;
15242 player->drop_x = dropx;
15243 player->drop_y = dropy;
15248 // ----------------------------------------------------------------------------
15249 // game sound playing functions
15250 // ----------------------------------------------------------------------------
15252 static int *loop_sound_frame = NULL;
15253 static int *loop_sound_volume = NULL;
15255 void InitPlayLevelSound(void)
15257 int num_sounds = getSoundListSize();
15259 checked_free(loop_sound_frame);
15260 checked_free(loop_sound_volume);
15262 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15263 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15266 static void PlayLevelSound(int x, int y, int nr)
15268 int sx = SCREENX(x), sy = SCREENY(y);
15269 int volume, stereo_position;
15270 int max_distance = 8;
15271 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15273 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15274 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15277 if (!IN_LEV_FIELD(x, y) ||
15278 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15279 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15282 volume = SOUND_MAX_VOLUME;
15284 if (!IN_SCR_FIELD(sx, sy))
15286 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15287 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15289 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15292 stereo_position = (SOUND_MAX_LEFT +
15293 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15294 (SCR_FIELDX + 2 * max_distance));
15296 if (IS_LOOP_SOUND(nr))
15298 /* This assures that quieter loop sounds do not overwrite louder ones,
15299 while restarting sound volume comparison with each new game frame. */
15301 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15304 loop_sound_volume[nr] = volume;
15305 loop_sound_frame[nr] = FrameCounter;
15308 PlaySoundExt(nr, volume, stereo_position, type);
15311 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15313 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15314 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15315 y < LEVELY(BY1) ? LEVELY(BY1) :
15316 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15320 static void PlayLevelSoundAction(int x, int y, int action)
15322 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15325 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15327 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15329 if (sound_effect != SND_UNDEFINED)
15330 PlayLevelSound(x, y, sound_effect);
15333 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15336 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15338 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15339 PlayLevelSound(x, y, sound_effect);
15342 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15344 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15346 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15347 PlayLevelSound(x, y, sound_effect);
15350 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15352 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15354 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15355 StopSound(sound_effect);
15358 static int getLevelMusicNr(void)
15360 int level_pos = level_nr - leveldir_current->first_level;
15362 if (levelset.music[level_nr] != MUS_UNDEFINED)
15363 return levelset.music[level_nr]; // from config file
15365 return MAP_NOCONF_MUSIC(level_pos); // from music dir
15368 static void FadeLevelSounds(void)
15373 static void FadeLevelMusic(void)
15375 int music_nr = getLevelMusicNr();
15376 char *curr_music = getCurrentlyPlayingMusicFilename();
15377 char *next_music = getMusicInfoEntryFilename(music_nr);
15379 if (!strEqual(curr_music, next_music))
15383 void FadeLevelSoundsAndMusic(void)
15389 static void PlayLevelMusic(void)
15391 int music_nr = getLevelMusicNr();
15392 char *curr_music = getCurrentlyPlayingMusicFilename();
15393 char *next_music = getMusicInfoEntryFilename(music_nr);
15395 if (!strEqual(curr_music, next_music))
15396 PlayMusicLoop(music_nr);
15399 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15401 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15403 int x = xx - offset;
15404 int y = yy - offset;
15409 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15413 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15417 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15421 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15425 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15429 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15433 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15436 case SOUND_android_clone:
15437 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15440 case SOUND_android_move:
15441 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15445 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15449 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15453 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15456 case SOUND_eater_eat:
15457 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15461 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15464 case SOUND_collect:
15465 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15468 case SOUND_diamond:
15469 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15473 // !!! CHECK THIS !!!
15475 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15477 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15481 case SOUND_wonderfall:
15482 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15486 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15490 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15494 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15498 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15502 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15506 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15510 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15514 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15517 case SOUND_exit_open:
15518 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15521 case SOUND_exit_leave:
15522 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15525 case SOUND_dynamite:
15526 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15530 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15534 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15538 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15542 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15546 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15550 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15554 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15559 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15561 int element = map_element_SP_to_RND(element_sp);
15562 int action = map_action_SP_to_RND(action_sp);
15563 int offset = (setup.sp_show_border_elements ? 0 : 1);
15564 int x = xx - offset;
15565 int y = yy - offset;
15567 PlayLevelSoundElementAction(x, y, element, action);
15570 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15572 int element = map_element_MM_to_RND(element_mm);
15573 int action = map_action_MM_to_RND(action_mm);
15575 int x = xx - offset;
15576 int y = yy - offset;
15578 if (!IS_MM_ELEMENT(element))
15579 element = EL_MM_DEFAULT;
15581 PlayLevelSoundElementAction(x, y, element, action);
15584 void PlaySound_MM(int sound_mm)
15586 int sound = map_sound_MM_to_RND(sound_mm);
15588 if (sound == SND_UNDEFINED)
15594 void PlaySoundLoop_MM(int sound_mm)
15596 int sound = map_sound_MM_to_RND(sound_mm);
15598 if (sound == SND_UNDEFINED)
15601 PlaySoundLoop(sound);
15604 void StopSound_MM(int sound_mm)
15606 int sound = map_sound_MM_to_RND(sound_mm);
15608 if (sound == SND_UNDEFINED)
15614 void RaiseScore(int value)
15616 game.score += value;
15618 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15620 DisplayGameControlValues();
15623 void RaiseScoreElement(int element)
15628 case EL_BD_DIAMOND:
15629 case EL_EMERALD_YELLOW:
15630 case EL_EMERALD_RED:
15631 case EL_EMERALD_PURPLE:
15632 case EL_SP_INFOTRON:
15633 RaiseScore(level.score[SC_EMERALD]);
15636 RaiseScore(level.score[SC_DIAMOND]);
15639 RaiseScore(level.score[SC_CRYSTAL]);
15642 RaiseScore(level.score[SC_PEARL]);
15645 case EL_BD_BUTTERFLY:
15646 case EL_SP_ELECTRON:
15647 RaiseScore(level.score[SC_BUG]);
15650 case EL_BD_FIREFLY:
15651 case EL_SP_SNIKSNAK:
15652 RaiseScore(level.score[SC_SPACESHIP]);
15655 case EL_DARK_YAMYAM:
15656 RaiseScore(level.score[SC_YAMYAM]);
15659 RaiseScore(level.score[SC_ROBOT]);
15662 RaiseScore(level.score[SC_PACMAN]);
15665 RaiseScore(level.score[SC_NUT]);
15668 case EL_EM_DYNAMITE:
15669 case EL_SP_DISK_RED:
15670 case EL_DYNABOMB_INCREASE_NUMBER:
15671 case EL_DYNABOMB_INCREASE_SIZE:
15672 case EL_DYNABOMB_INCREASE_POWER:
15673 RaiseScore(level.score[SC_DYNAMITE]);
15675 case EL_SHIELD_NORMAL:
15676 case EL_SHIELD_DEADLY:
15677 RaiseScore(level.score[SC_SHIELD]);
15679 case EL_EXTRA_TIME:
15680 RaiseScore(level.extra_time_score);
15694 case EL_DC_KEY_WHITE:
15695 RaiseScore(level.score[SC_KEY]);
15698 RaiseScore(element_info[element].collect_score);
15703 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15705 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15709 // prevent short reactivation of overlay buttons while closing door
15710 SetOverlayActive(FALSE);
15711 UnmapGameButtons();
15713 // door may still be open due to skipped or envelope style request
15714 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
15717 if (network.enabled)
15719 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15724 FadeSkipNextFadeIn();
15726 SetGameStatus(GAME_MODE_MAIN);
15731 else // continue playing the game
15733 if (tape.playing && tape.deactivate_display)
15734 TapeDeactivateDisplayOff(TRUE);
15736 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15738 if (tape.playing && tape.deactivate_display)
15739 TapeDeactivateDisplayOn();
15743 void RequestQuitGame(boolean escape_key_pressed)
15745 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15746 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15747 level_editor_test_game);
15748 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15749 quick_quit || score_info_tape_play);
15751 RequestQuitGameExt(skip_request, quick_quit,
15752 "Do you really want to quit the game?");
15755 static char *getRestartGameMessage(void)
15757 boolean play_again = hasStartedNetworkGame();
15758 static char message[MAX_OUTPUT_LINESIZE];
15759 char *game_over_text = "Game over!";
15760 char *play_again_text = " Play it again?";
15762 if (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
15763 game_mm.game_over_message != NULL)
15764 game_over_text = game_mm.game_over_message;
15766 snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text,
15767 (play_again ? play_again_text : ""));
15772 static void RequestRestartGame(void)
15774 char *message = getRestartGameMessage();
15775 boolean has_started_game = hasStartedNetworkGame();
15776 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15777 int door_state = DOOR_CLOSE_1;
15779 if (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game)
15781 CloseDoor(door_state);
15783 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15787 // if game was invoked from level editor, also close tape recorder door
15788 if (level_editor_test_game)
15789 door_state = DOOR_CLOSE_ALL;
15791 CloseDoor(door_state);
15793 SetGameStatus(GAME_MODE_MAIN);
15799 boolean CheckRestartGame(void)
15801 static int game_over_delay = 0;
15802 int game_over_delay_value = 50;
15803 boolean game_over = checkGameFailed();
15807 game_over_delay = game_over_delay_value;
15812 if (game_over_delay > 0)
15814 if (game_over_delay == game_over_delay_value / 2)
15815 PlaySound(SND_GAME_LOSING);
15822 // do not ask to play again if request dialog is already active
15823 if (game.request_active)
15826 // do not ask to play again if request dialog already handled
15827 if (game.RestartGameRequested)
15830 // do not ask to play again if game was never actually played
15831 if (!game.GamePlayed)
15834 // do not ask to play again if this was disabled in setup menu
15835 if (!setup.ask_on_game_over)
15838 game.RestartGameRequested = TRUE;
15840 RequestRestartGame();
15845 boolean checkGameSolved(void)
15847 // set for all game engines if level was solved
15848 return game.LevelSolved_GameEnd;
15851 boolean checkGameFailed(void)
15853 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15854 return (game_em.game_over && !game_em.level_solved);
15855 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15856 return (game_sp.game_over && !game_sp.level_solved);
15857 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15858 return (game_mm.game_over && !game_mm.level_solved);
15859 else // GAME_ENGINE_TYPE_RND
15860 return (game.GameOver && !game.LevelSolved);
15863 boolean checkGameEnded(void)
15865 return (checkGameSolved() || checkGameFailed());
15869 // ----------------------------------------------------------------------------
15870 // random generator functions
15871 // ----------------------------------------------------------------------------
15873 unsigned int InitEngineRandom_RND(int seed)
15875 game.num_random_calls = 0;
15877 return InitEngineRandom(seed);
15880 unsigned int RND(int max)
15884 game.num_random_calls++;
15886 return GetEngineRandom(max);
15893 // ----------------------------------------------------------------------------
15894 // game engine snapshot handling functions
15895 // ----------------------------------------------------------------------------
15897 struct EngineSnapshotInfo
15899 // runtime values for custom element collect score
15900 int collect_score[NUM_CUSTOM_ELEMENTS];
15902 // runtime values for group element choice position
15903 int choice_pos[NUM_GROUP_ELEMENTS];
15905 // runtime values for belt position animations
15906 int belt_graphic[4][NUM_BELT_PARTS];
15907 int belt_anim_mode[4][NUM_BELT_PARTS];
15910 static struct EngineSnapshotInfo engine_snapshot_rnd;
15911 static char *snapshot_level_identifier = NULL;
15912 static int snapshot_level_nr = -1;
15914 static void SaveEngineSnapshotValues_RND(void)
15916 static int belt_base_active_element[4] =
15918 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15919 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15920 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15921 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15925 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15927 int element = EL_CUSTOM_START + i;
15929 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15932 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15934 int element = EL_GROUP_START + i;
15936 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15939 for (i = 0; i < 4; i++)
15941 for (j = 0; j < NUM_BELT_PARTS; j++)
15943 int element = belt_base_active_element[i] + j;
15944 int graphic = el2img(element);
15945 int anim_mode = graphic_info[graphic].anim_mode;
15947 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15948 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15953 static void LoadEngineSnapshotValues_RND(void)
15955 unsigned int num_random_calls = game.num_random_calls;
15958 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15960 int element = EL_CUSTOM_START + i;
15962 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15965 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15967 int element = EL_GROUP_START + i;
15969 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15972 for (i = 0; i < 4; i++)
15974 for (j = 0; j < NUM_BELT_PARTS; j++)
15976 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15977 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15979 graphic_info[graphic].anim_mode = anim_mode;
15983 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15985 InitRND(tape.random_seed);
15986 for (i = 0; i < num_random_calls; i++)
15990 if (game.num_random_calls != num_random_calls)
15992 Error("number of random calls out of sync");
15993 Error("number of random calls should be %d", num_random_calls);
15994 Error("number of random calls is %d", game.num_random_calls);
15996 Fail("this should not happen -- please debug");
16000 void FreeEngineSnapshotSingle(void)
16002 FreeSnapshotSingle();
16004 setString(&snapshot_level_identifier, NULL);
16005 snapshot_level_nr = -1;
16008 void FreeEngineSnapshotList(void)
16010 FreeSnapshotList();
16013 static ListNode *SaveEngineSnapshotBuffers(void)
16015 ListNode *buffers = NULL;
16017 // copy some special values to a structure better suited for the snapshot
16019 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16020 SaveEngineSnapshotValues_RND();
16021 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16022 SaveEngineSnapshotValues_EM();
16023 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16024 SaveEngineSnapshotValues_SP(&buffers);
16025 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16026 SaveEngineSnapshotValues_MM();
16028 // save values stored in special snapshot structure
16030 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16031 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
16032 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16033 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
16034 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16035 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
16036 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16037 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
16039 // save further RND engine values
16041 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
16042 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
16043 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
16045 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
16046 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16047 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16048 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16049 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames));
16050 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16052 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16053 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16054 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16056 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16058 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16059 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16061 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16062 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16063 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16064 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16065 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16066 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16067 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16068 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16069 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16070 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16071 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16072 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16073 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16074 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16075 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16076 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16077 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16078 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16080 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16081 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16083 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16084 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16085 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16087 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16088 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16090 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16091 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16092 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16093 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16094 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16095 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16097 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16098 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16101 ListNode *node = engine_snapshot_list_rnd;
16104 while (node != NULL)
16106 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16111 Debug("game:playing:SaveEngineSnapshotBuffers",
16112 "size of engine snapshot: %d bytes", num_bytes);
16118 void SaveEngineSnapshotSingle(void)
16120 ListNode *buffers = SaveEngineSnapshotBuffers();
16122 // finally save all snapshot buffers to single snapshot
16123 SaveSnapshotSingle(buffers);
16125 // save level identification information
16126 setString(&snapshot_level_identifier, leveldir_current->identifier);
16127 snapshot_level_nr = level_nr;
16130 boolean CheckSaveEngineSnapshotToList(void)
16132 boolean save_snapshot =
16133 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16134 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16135 game.snapshot.changed_action) ||
16136 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16137 game.snapshot.collected_item));
16139 game.snapshot.changed_action = FALSE;
16140 game.snapshot.collected_item = FALSE;
16141 game.snapshot.save_snapshot = save_snapshot;
16143 return save_snapshot;
16146 void SaveEngineSnapshotToList(void)
16148 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16152 ListNode *buffers = SaveEngineSnapshotBuffers();
16154 // finally save all snapshot buffers to snapshot list
16155 SaveSnapshotToList(buffers);
16158 void SaveEngineSnapshotToListInitial(void)
16160 FreeEngineSnapshotList();
16162 SaveEngineSnapshotToList();
16165 static void LoadEngineSnapshotValues(void)
16167 // restore special values from snapshot structure
16169 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16170 LoadEngineSnapshotValues_RND();
16171 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16172 LoadEngineSnapshotValues_EM();
16173 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16174 LoadEngineSnapshotValues_SP();
16175 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16176 LoadEngineSnapshotValues_MM();
16179 void LoadEngineSnapshotSingle(void)
16181 LoadSnapshotSingle();
16183 LoadEngineSnapshotValues();
16186 static void LoadEngineSnapshot_Undo(int steps)
16188 LoadSnapshotFromList_Older(steps);
16190 LoadEngineSnapshotValues();
16193 static void LoadEngineSnapshot_Redo(int steps)
16195 LoadSnapshotFromList_Newer(steps);
16197 LoadEngineSnapshotValues();
16200 boolean CheckEngineSnapshotSingle(void)
16202 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16203 snapshot_level_nr == level_nr);
16206 boolean CheckEngineSnapshotList(void)
16208 return CheckSnapshotList();
16212 // ---------- new game button stuff -------------------------------------------
16219 boolean *setup_value;
16220 boolean allowed_on_tape;
16221 boolean is_touch_button;
16223 } gamebutton_info[NUM_GAME_BUTTONS] =
16226 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16227 GAME_CTRL_ID_STOP, NULL,
16228 TRUE, FALSE, "stop game"
16231 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16232 GAME_CTRL_ID_PAUSE, NULL,
16233 TRUE, FALSE, "pause game"
16236 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16237 GAME_CTRL_ID_PLAY, NULL,
16238 TRUE, FALSE, "play game"
16241 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16242 GAME_CTRL_ID_UNDO, NULL,
16243 TRUE, FALSE, "undo step"
16246 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16247 GAME_CTRL_ID_REDO, NULL,
16248 TRUE, FALSE, "redo step"
16251 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16252 GAME_CTRL_ID_SAVE, NULL,
16253 TRUE, FALSE, "save game"
16256 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16257 GAME_CTRL_ID_PAUSE2, NULL,
16258 TRUE, FALSE, "pause game"
16261 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16262 GAME_CTRL_ID_LOAD, NULL,
16263 TRUE, FALSE, "load game"
16266 IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart,
16267 GAME_CTRL_ID_RESTART, NULL,
16268 TRUE, FALSE, "restart game"
16271 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16272 GAME_CTRL_ID_PANEL_STOP, NULL,
16273 FALSE, FALSE, "stop game"
16276 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16277 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16278 FALSE, FALSE, "pause game"
16281 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16282 GAME_CTRL_ID_PANEL_PLAY, NULL,
16283 FALSE, FALSE, "play game"
16286 IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart,
16287 GAME_CTRL_ID_PANEL_RESTART, NULL,
16288 FALSE, FALSE, "restart game"
16291 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16292 GAME_CTRL_ID_TOUCH_STOP, NULL,
16293 FALSE, TRUE, "stop game"
16296 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16297 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16298 FALSE, TRUE, "pause game"
16301 IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart,
16302 GAME_CTRL_ID_TOUCH_RESTART, NULL,
16303 FALSE, TRUE, "restart game"
16306 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16307 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16308 TRUE, FALSE, "background music on/off"
16311 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16312 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16313 TRUE, FALSE, "sound loops on/off"
16316 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16317 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16318 TRUE, FALSE, "normal sounds on/off"
16321 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16322 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16323 FALSE, FALSE, "background music on/off"
16326 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16327 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16328 FALSE, FALSE, "sound loops on/off"
16331 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16332 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16333 FALSE, FALSE, "normal sounds on/off"
16337 void CreateGameButtons(void)
16341 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16343 int graphic = gamebutton_info[i].graphic;
16344 struct GraphicInfo *gfx = &graphic_info[graphic];
16345 struct XY *pos = gamebutton_info[i].pos;
16346 struct GadgetInfo *gi;
16349 unsigned int event_mask;
16350 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16351 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16352 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16353 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16354 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16355 int gd_x = gfx->src_x;
16356 int gd_y = gfx->src_y;
16357 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16358 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16359 int gd_xa = gfx->src_x + gfx->active_xoffset;
16360 int gd_ya = gfx->src_y + gfx->active_yoffset;
16361 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16362 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16363 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16364 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16367 // do not use touch buttons if overlay touch buttons are disabled
16368 if (is_touch_button && !setup.touch.overlay_buttons)
16371 if (gfx->bitmap == NULL)
16373 game_gadget[id] = NULL;
16378 if (id == GAME_CTRL_ID_STOP ||
16379 id == GAME_CTRL_ID_PANEL_STOP ||
16380 id == GAME_CTRL_ID_TOUCH_STOP ||
16381 id == GAME_CTRL_ID_PLAY ||
16382 id == GAME_CTRL_ID_PANEL_PLAY ||
16383 id == GAME_CTRL_ID_SAVE ||
16384 id == GAME_CTRL_ID_LOAD ||
16385 id == GAME_CTRL_ID_RESTART ||
16386 id == GAME_CTRL_ID_PANEL_RESTART ||
16387 id == GAME_CTRL_ID_TOUCH_RESTART)
16389 button_type = GD_TYPE_NORMAL_BUTTON;
16391 event_mask = GD_EVENT_RELEASED;
16393 else if (id == GAME_CTRL_ID_UNDO ||
16394 id == GAME_CTRL_ID_REDO)
16396 button_type = GD_TYPE_NORMAL_BUTTON;
16398 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16402 button_type = GD_TYPE_CHECK_BUTTON;
16403 checked = (gamebutton_info[i].setup_value != NULL ?
16404 *gamebutton_info[i].setup_value : FALSE);
16405 event_mask = GD_EVENT_PRESSED;
16408 gi = CreateGadget(GDI_CUSTOM_ID, id,
16409 GDI_IMAGE_ID, graphic,
16410 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16413 GDI_WIDTH, gfx->width,
16414 GDI_HEIGHT, gfx->height,
16415 GDI_TYPE, button_type,
16416 GDI_STATE, GD_BUTTON_UNPRESSED,
16417 GDI_CHECKED, checked,
16418 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16419 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16420 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16421 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16422 GDI_DIRECT_DRAW, FALSE,
16423 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16424 GDI_EVENT_MASK, event_mask,
16425 GDI_CALLBACK_ACTION, HandleGameButtons,
16429 Fail("cannot create gadget");
16431 game_gadget[id] = gi;
16435 void FreeGameButtons(void)
16439 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16440 FreeGadget(game_gadget[i]);
16443 static void UnmapGameButtonsAtSamePosition(int id)
16447 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16449 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16450 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16451 UnmapGadget(game_gadget[i]);
16454 static void UnmapGameButtonsAtSamePosition_All(void)
16456 if (setup.show_load_save_buttons)
16458 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16459 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16460 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16462 else if (setup.show_undo_redo_buttons)
16464 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16465 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16466 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16470 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16471 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16472 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16474 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16475 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16476 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16480 void MapLoadSaveButtons(void)
16482 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16483 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16485 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16486 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16489 void MapUndoRedoButtons(void)
16491 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16492 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16494 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16495 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16498 void ModifyPauseButtons(void)
16502 GAME_CTRL_ID_PAUSE,
16503 GAME_CTRL_ID_PAUSE2,
16504 GAME_CTRL_ID_PANEL_PAUSE,
16505 GAME_CTRL_ID_TOUCH_PAUSE,
16510 // do not redraw pause button on closed door (may happen when restarting game)
16511 if (!(GetDoorState() & DOOR_OPEN_1))
16514 for (i = 0; ids[i] > -1; i++)
16515 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16518 static void MapGameButtonsExt(boolean on_tape)
16522 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16524 if ((i == GAME_CTRL_ID_UNDO ||
16525 i == GAME_CTRL_ID_REDO) &&
16526 game_status != GAME_MODE_PLAYING)
16529 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16530 MapGadget(game_gadget[i]);
16533 UnmapGameButtonsAtSamePosition_All();
16535 RedrawGameButtons();
16538 static void UnmapGameButtonsExt(boolean on_tape)
16542 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16543 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16544 UnmapGadget(game_gadget[i]);
16547 static void RedrawGameButtonsExt(boolean on_tape)
16551 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16552 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16553 RedrawGadget(game_gadget[i]);
16556 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16561 gi->checked = state;
16564 static void RedrawSoundButtonGadget(int id)
16566 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16567 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16568 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16569 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16570 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16571 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16574 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16575 RedrawGadget(game_gadget[id2]);
16578 void MapGameButtons(void)
16580 MapGameButtonsExt(FALSE);
16583 void UnmapGameButtons(void)
16585 UnmapGameButtonsExt(FALSE);
16588 void RedrawGameButtons(void)
16590 RedrawGameButtonsExt(FALSE);
16593 void MapGameButtonsOnTape(void)
16595 MapGameButtonsExt(TRUE);
16598 void UnmapGameButtonsOnTape(void)
16600 UnmapGameButtonsExt(TRUE);
16603 void RedrawGameButtonsOnTape(void)
16605 RedrawGameButtonsExt(TRUE);
16608 static void GameUndoRedoExt(void)
16610 ClearPlayerAction();
16612 tape.pausing = TRUE;
16615 UpdateAndDisplayGameControlValues();
16617 DrawCompleteVideoDisplay();
16618 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16619 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16620 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16622 ModifyPauseButtons();
16627 static void GameUndo(int steps)
16629 if (!CheckEngineSnapshotList())
16632 int tape_property_bits = tape.property_bits;
16634 LoadEngineSnapshot_Undo(steps);
16636 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16641 static void GameRedo(int steps)
16643 if (!CheckEngineSnapshotList())
16646 int tape_property_bits = tape.property_bits;
16648 LoadEngineSnapshot_Redo(steps);
16650 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16655 static void HandleGameButtonsExt(int id, int button)
16657 static boolean game_undo_executed = FALSE;
16658 int steps = BUTTON_STEPSIZE(button);
16659 boolean handle_game_buttons =
16660 (game_status == GAME_MODE_PLAYING ||
16661 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16663 if (!handle_game_buttons)
16668 case GAME_CTRL_ID_STOP:
16669 case GAME_CTRL_ID_PANEL_STOP:
16670 case GAME_CTRL_ID_TOUCH_STOP:
16675 case GAME_CTRL_ID_PAUSE:
16676 case GAME_CTRL_ID_PAUSE2:
16677 case GAME_CTRL_ID_PANEL_PAUSE:
16678 case GAME_CTRL_ID_TOUCH_PAUSE:
16679 if (network.enabled && game_status == GAME_MODE_PLAYING)
16682 SendToServer_ContinuePlaying();
16684 SendToServer_PausePlaying();
16687 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16689 game_undo_executed = FALSE;
16693 case GAME_CTRL_ID_PLAY:
16694 case GAME_CTRL_ID_PANEL_PLAY:
16695 if (game_status == GAME_MODE_MAIN)
16697 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16699 else if (tape.pausing)
16701 if (network.enabled)
16702 SendToServer_ContinuePlaying();
16704 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16708 case GAME_CTRL_ID_UNDO:
16709 // Important: When using "save snapshot when collecting an item" mode,
16710 // load last (current) snapshot for first "undo" after pressing "pause"
16711 // (else the last-but-one snapshot would be loaded, because the snapshot
16712 // pointer already points to the last snapshot when pressing "pause",
16713 // which is fine for "every step/move" mode, but not for "every collect")
16714 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16715 !game_undo_executed)
16718 game_undo_executed = TRUE;
16723 case GAME_CTRL_ID_REDO:
16727 case GAME_CTRL_ID_SAVE:
16731 case GAME_CTRL_ID_LOAD:
16735 case GAME_CTRL_ID_RESTART:
16736 case GAME_CTRL_ID_PANEL_RESTART:
16737 case GAME_CTRL_ID_TOUCH_RESTART:
16742 case SOUND_CTRL_ID_MUSIC:
16743 case SOUND_CTRL_ID_PANEL_MUSIC:
16744 if (setup.sound_music)
16746 setup.sound_music = FALSE;
16750 else if (audio.music_available)
16752 setup.sound = setup.sound_music = TRUE;
16754 SetAudioMode(setup.sound);
16756 if (game_status == GAME_MODE_PLAYING)
16760 RedrawSoundButtonGadget(id);
16764 case SOUND_CTRL_ID_LOOPS:
16765 case SOUND_CTRL_ID_PANEL_LOOPS:
16766 if (setup.sound_loops)
16767 setup.sound_loops = FALSE;
16768 else if (audio.loops_available)
16770 setup.sound = setup.sound_loops = TRUE;
16772 SetAudioMode(setup.sound);
16775 RedrawSoundButtonGadget(id);
16779 case SOUND_CTRL_ID_SIMPLE:
16780 case SOUND_CTRL_ID_PANEL_SIMPLE:
16781 if (setup.sound_simple)
16782 setup.sound_simple = FALSE;
16783 else if (audio.sound_available)
16785 setup.sound = setup.sound_simple = TRUE;
16787 SetAudioMode(setup.sound);
16790 RedrawSoundButtonGadget(id);
16799 static void HandleGameButtons(struct GadgetInfo *gi)
16801 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16804 void HandleSoundButtonKeys(Key key)
16806 if (key == setup.shortcut.sound_simple)
16807 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16808 else if (key == setup.shortcut.sound_loops)
16809 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16810 else if (key == setup.shortcut.sound_music)
16811 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);