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_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
883 RND(element_info[e].ce_value_random_initial))
884 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
885 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
886 RND((c)->delay_random * (c)->delay_frames))
887 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
888 RND((c)->delay_random))
891 #define GET_VALID_RUNTIME_ELEMENT(e) \
892 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
894 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
895 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
896 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
897 (be) + (e) - EL_SELF)
899 #define GET_PLAYER_FROM_BITS(p) \
900 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
902 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
903 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
904 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
905 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
906 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
907 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
908 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
909 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
910 RESOLVED_REFERENCE_ELEMENT(be, e) : \
913 #define CAN_GROW_INTO(e) \
914 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
916 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
917 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
922 (CAN_MOVE_INTO_ACID(e) && \
923 Tile[x][y] == EL_ACID) || \
926 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
927 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
928 (CAN_MOVE_INTO_ACID(e) && \
929 Tile[x][y] == EL_ACID) || \
932 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
933 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
935 (CAN_MOVE_INTO_ACID(e) && \
936 Tile[x][y] == EL_ACID) || \
937 (DONT_COLLIDE_WITH(e) && \
939 !PLAYER_ENEMY_PROTECTED(x, y))))
941 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
942 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
944 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
945 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
947 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
948 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
950 #define ANDROID_CAN_CLONE_FIELD(x, y) \
951 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
952 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
954 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
955 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
957 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
958 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
960 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
961 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
963 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
966 #define PIG_CAN_ENTER_FIELD(e, x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
969 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
971 Tile[x][y] == EL_EM_EXIT_OPEN || \
972 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
973 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
974 IS_FOOD_PENGUIN(Tile[x][y])))
975 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
976 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
978 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
979 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
981 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
982 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
984 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
985 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
986 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
988 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
990 #define CE_ENTER_FIELD_COND(e, x, y) \
991 (!IS_PLAYER(x, y) && \
992 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
994 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
995 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
997 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
998 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1000 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1001 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1002 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1003 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1005 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1007 // game button identifiers
1008 #define GAME_CTRL_ID_STOP 0
1009 #define GAME_CTRL_ID_PAUSE 1
1010 #define GAME_CTRL_ID_PLAY 2
1011 #define GAME_CTRL_ID_UNDO 3
1012 #define GAME_CTRL_ID_REDO 4
1013 #define GAME_CTRL_ID_SAVE 5
1014 #define GAME_CTRL_ID_PAUSE2 6
1015 #define GAME_CTRL_ID_LOAD 7
1016 #define GAME_CTRL_ID_PANEL_STOP 8
1017 #define GAME_CTRL_ID_PANEL_PAUSE 9
1018 #define GAME_CTRL_ID_PANEL_PLAY 10
1019 #define GAME_CTRL_ID_TOUCH_STOP 11
1020 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1021 #define SOUND_CTRL_ID_MUSIC 13
1022 #define SOUND_CTRL_ID_LOOPS 14
1023 #define SOUND_CTRL_ID_SIMPLE 15
1024 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1025 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1026 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1028 #define NUM_GAME_BUTTONS 19
1031 // forward declaration for internal use
1033 static void CreateField(int, int, int);
1035 static void ResetGfxAnimation(int, int);
1037 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1038 static void AdvanceFrameAndPlayerCounters(int);
1040 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1041 static boolean MovePlayer(struct PlayerInfo *, int, int);
1042 static void ScrollPlayer(struct PlayerInfo *, int);
1043 static void ScrollScreen(struct PlayerInfo *, int);
1045 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1046 static boolean DigFieldByCE(int, int, int);
1047 static boolean SnapField(struct PlayerInfo *, int, int);
1048 static boolean DropElement(struct PlayerInfo *);
1050 static void InitBeltMovement(void);
1051 static void CloseAllOpenTimegates(void);
1052 static void CheckGravityMovement(struct PlayerInfo *);
1053 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1054 static void KillPlayerUnlessEnemyProtected(int, int);
1055 static void KillPlayerUnlessExplosionProtected(int, int);
1057 static void TestIfPlayerTouchesCustomElement(int, int);
1058 static void TestIfElementTouchesCustomElement(int, int);
1059 static void TestIfElementHitsCustomElement(int, int, int);
1061 static void HandleElementChange(int, int, int);
1062 static void ExecuteCustomElementAction(int, int, int, int);
1063 static boolean ChangeElement(int, int, int, int);
1065 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1066 #define CheckTriggeredElementChange(x, y, e, ev) \
1067 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1068 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1069 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1070 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1071 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1072 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1073 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1075 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1076 #define CheckElementChange(x, y, e, te, ev) \
1077 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1078 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1079 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1080 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1081 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1083 static void PlayLevelSound(int, int, int);
1084 static void PlayLevelSoundNearest(int, int, int);
1085 static void PlayLevelSoundAction(int, int, int);
1086 static void PlayLevelSoundElementAction(int, int, int, int);
1087 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1088 static void PlayLevelSoundActionIfLoop(int, int, int);
1089 static void StopLevelSoundActionIfLoop(int, int, int);
1090 static void PlayLevelMusic(void);
1091 static void FadeLevelSoundsAndMusic(void);
1093 static void HandleGameButtons(struct GadgetInfo *);
1095 int AmoebaNeighbourNr(int, int);
1096 void AmoebaToDiamond(int, int);
1097 void ContinueMoving(int, int);
1098 void Bang(int, int);
1099 void InitMovDir(int, int);
1100 void InitAmoebaNr(int, int);
1101 int NewHiScore(int);
1103 void TestIfGoodThingHitsBadThing(int, int, int);
1104 void TestIfBadThingHitsGoodThing(int, int, int);
1105 void TestIfPlayerTouchesBadThing(int, int);
1106 void TestIfPlayerRunsIntoBadThing(int, int, int);
1107 void TestIfBadThingTouchesPlayer(int, int);
1108 void TestIfBadThingRunsIntoPlayer(int, int, int);
1109 void TestIfFriendTouchesBadThing(int, int);
1110 void TestIfBadThingTouchesFriend(int, int);
1111 void TestIfBadThingTouchesOtherBadThing(int, int);
1112 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1114 void KillPlayer(struct PlayerInfo *);
1115 void BuryPlayer(struct PlayerInfo *);
1116 void RemovePlayer(struct PlayerInfo *);
1117 void ExitPlayer(struct PlayerInfo *);
1119 static int getInvisibleActiveFromInvisibleElement(int);
1120 static int getInvisibleFromInvisibleActiveElement(int);
1122 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1124 // for detection of endless loops, caused by custom element programming
1125 // (using maximal playfield width x 10 is just a rough approximation)
1126 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1128 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1130 if (recursion_loop_detected) \
1133 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1135 recursion_loop_detected = TRUE; \
1136 recursion_loop_element = (e); \
1139 recursion_loop_depth++; \
1142 #define RECURSION_LOOP_DETECTION_END() \
1144 recursion_loop_depth--; \
1147 static int recursion_loop_depth;
1148 static boolean recursion_loop_detected;
1149 static boolean recursion_loop_element;
1151 static int map_player_action[MAX_PLAYERS];
1154 // ----------------------------------------------------------------------------
1155 // definition of elements that automatically change to other elements after
1156 // a specified time, eventually calling a function when changing
1157 // ----------------------------------------------------------------------------
1159 // forward declaration for changer functions
1160 static void InitBuggyBase(int, int);
1161 static void WarnBuggyBase(int, int);
1163 static void InitTrap(int, int);
1164 static void ActivateTrap(int, int);
1165 static void ChangeActiveTrap(int, int);
1167 static void InitRobotWheel(int, int);
1168 static void RunRobotWheel(int, int);
1169 static void StopRobotWheel(int, int);
1171 static void InitTimegateWheel(int, int);
1172 static void RunTimegateWheel(int, int);
1174 static void InitMagicBallDelay(int, int);
1175 static void ActivateMagicBall(int, int);
1177 struct ChangingElementInfo
1182 void (*pre_change_function)(int x, int y);
1183 void (*change_function)(int x, int y);
1184 void (*post_change_function)(int x, int y);
1187 static struct ChangingElementInfo change_delay_list[] =
1222 EL_STEEL_EXIT_OPENING,
1230 EL_STEEL_EXIT_CLOSING,
1231 EL_STEEL_EXIT_CLOSED,
1254 EL_EM_STEEL_EXIT_OPENING,
1255 EL_EM_STEEL_EXIT_OPEN,
1262 EL_EM_STEEL_EXIT_CLOSING,
1286 EL_SWITCHGATE_OPENING,
1294 EL_SWITCHGATE_CLOSING,
1295 EL_SWITCHGATE_CLOSED,
1302 EL_TIMEGATE_OPENING,
1310 EL_TIMEGATE_CLOSING,
1319 EL_ACID_SPLASH_LEFT,
1327 EL_ACID_SPLASH_RIGHT,
1336 EL_SP_BUGGY_BASE_ACTIVATING,
1343 EL_SP_BUGGY_BASE_ACTIVATING,
1344 EL_SP_BUGGY_BASE_ACTIVE,
1351 EL_SP_BUGGY_BASE_ACTIVE,
1375 EL_ROBOT_WHEEL_ACTIVE,
1383 EL_TIMEGATE_SWITCH_ACTIVE,
1391 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1392 EL_DC_TIMEGATE_SWITCH,
1399 EL_EMC_MAGIC_BALL_ACTIVE,
1400 EL_EMC_MAGIC_BALL_ACTIVE,
1407 EL_EMC_SPRING_BUMPER_ACTIVE,
1408 EL_EMC_SPRING_BUMPER,
1415 EL_DIAGONAL_SHRINKING,
1423 EL_DIAGONAL_GROWING,
1444 int push_delay_fixed, push_delay_random;
1448 { EL_SPRING, 0, 0 },
1449 { EL_BALLOON, 0, 0 },
1451 { EL_SOKOBAN_OBJECT, 2, 0 },
1452 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1453 { EL_SATELLITE, 2, 0 },
1454 { EL_SP_DISK_YELLOW, 2, 0 },
1456 { EL_UNDEFINED, 0, 0 },
1464 move_stepsize_list[] =
1466 { EL_AMOEBA_DROP, 2 },
1467 { EL_AMOEBA_DROPPING, 2 },
1468 { EL_QUICKSAND_FILLING, 1 },
1469 { EL_QUICKSAND_EMPTYING, 1 },
1470 { EL_QUICKSAND_FAST_FILLING, 2 },
1471 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1472 { EL_MAGIC_WALL_FILLING, 2 },
1473 { EL_MAGIC_WALL_EMPTYING, 2 },
1474 { EL_BD_MAGIC_WALL_FILLING, 2 },
1475 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1476 { EL_DC_MAGIC_WALL_FILLING, 2 },
1477 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1479 { EL_UNDEFINED, 0 },
1487 collect_count_list[] =
1490 { EL_BD_DIAMOND, 1 },
1491 { EL_EMERALD_YELLOW, 1 },
1492 { EL_EMERALD_RED, 1 },
1493 { EL_EMERALD_PURPLE, 1 },
1495 { EL_SP_INFOTRON, 1 },
1499 { EL_UNDEFINED, 0 },
1507 access_direction_list[] =
1509 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1510 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1511 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1512 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1513 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1514 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1515 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1516 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1517 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1518 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1519 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1521 { EL_SP_PORT_LEFT, MV_RIGHT },
1522 { EL_SP_PORT_RIGHT, MV_LEFT },
1523 { EL_SP_PORT_UP, MV_DOWN },
1524 { EL_SP_PORT_DOWN, MV_UP },
1525 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1526 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1527 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1528 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1529 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1530 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1531 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1532 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1533 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1534 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1535 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1536 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1537 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1538 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1539 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1541 { EL_UNDEFINED, MV_NONE }
1544 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1546 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1547 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1548 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1549 IS_JUST_CHANGING(x, y))
1551 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1553 // static variables for playfield scan mode (scanning forward or backward)
1554 static int playfield_scan_start_x = 0;
1555 static int playfield_scan_start_y = 0;
1556 static int playfield_scan_delta_x = 1;
1557 static int playfield_scan_delta_y = 1;
1559 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1560 (y) >= 0 && (y) <= lev_fieldy - 1; \
1561 (y) += playfield_scan_delta_y) \
1562 for ((x) = playfield_scan_start_x; \
1563 (x) >= 0 && (x) <= lev_fieldx - 1; \
1564 (x) += playfield_scan_delta_x)
1567 void DEBUG_SetMaximumDynamite(void)
1571 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1572 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1573 local_player->inventory_element[local_player->inventory_size++] =
1578 static void InitPlayfieldScanModeVars(void)
1580 if (game.use_reverse_scan_direction)
1582 playfield_scan_start_x = lev_fieldx - 1;
1583 playfield_scan_start_y = lev_fieldy - 1;
1585 playfield_scan_delta_x = -1;
1586 playfield_scan_delta_y = -1;
1590 playfield_scan_start_x = 0;
1591 playfield_scan_start_y = 0;
1593 playfield_scan_delta_x = 1;
1594 playfield_scan_delta_y = 1;
1598 static void InitPlayfieldScanMode(int mode)
1600 game.use_reverse_scan_direction =
1601 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1603 InitPlayfieldScanModeVars();
1606 static int get_move_delay_from_stepsize(int move_stepsize)
1609 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1611 // make sure that stepsize value is always a power of 2
1612 move_stepsize = (1 << log_2(move_stepsize));
1614 return TILEX / move_stepsize;
1617 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1620 int player_nr = player->index_nr;
1621 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1622 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1624 // do no immediately change move delay -- the player might just be moving
1625 player->move_delay_value_next = move_delay;
1627 // information if player can move must be set separately
1628 player->cannot_move = cannot_move;
1632 player->move_delay = game.initial_move_delay[player_nr];
1633 player->move_delay_value = game.initial_move_delay_value[player_nr];
1635 player->move_delay_value_next = -1;
1637 player->move_delay_reset_counter = 0;
1641 void GetPlayerConfig(void)
1643 GameFrameDelay = setup.game_frame_delay;
1645 if (!audio.sound_available)
1646 setup.sound_simple = FALSE;
1648 if (!audio.loops_available)
1649 setup.sound_loops = FALSE;
1651 if (!audio.music_available)
1652 setup.sound_music = FALSE;
1654 if (!video.fullscreen_available)
1655 setup.fullscreen = FALSE;
1657 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1659 SetAudioMode(setup.sound);
1662 int GetElementFromGroupElement(int element)
1664 if (IS_GROUP_ELEMENT(element))
1666 struct ElementGroupInfo *group = element_info[element].group;
1667 int last_anim_random_frame = gfx.anim_random_frame;
1670 if (group->choice_mode == ANIM_RANDOM)
1671 gfx.anim_random_frame = RND(group->num_elements_resolved);
1673 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1674 group->choice_mode, 0,
1677 if (group->choice_mode == ANIM_RANDOM)
1678 gfx.anim_random_frame = last_anim_random_frame;
1680 group->choice_pos++;
1682 element = group->element_resolved[element_pos];
1688 static void IncrementSokobanFieldsNeeded(void)
1690 if (level.sb_fields_needed)
1691 game.sokoban_fields_still_needed++;
1694 static void IncrementSokobanObjectsNeeded(void)
1696 if (level.sb_objects_needed)
1697 game.sokoban_objects_still_needed++;
1700 static void DecrementSokobanFieldsNeeded(void)
1702 if (game.sokoban_fields_still_needed > 0)
1703 game.sokoban_fields_still_needed--;
1706 static void DecrementSokobanObjectsNeeded(void)
1708 if (game.sokoban_objects_still_needed > 0)
1709 game.sokoban_objects_still_needed--;
1712 static void InitPlayerField(int x, int y, int element, boolean init_game)
1714 if (element == EL_SP_MURPHY)
1718 if (stored_player[0].present)
1720 Tile[x][y] = EL_SP_MURPHY_CLONE;
1726 stored_player[0].initial_element = element;
1727 stored_player[0].use_murphy = TRUE;
1729 if (!level.use_artwork_element[0])
1730 stored_player[0].artwork_element = EL_SP_MURPHY;
1733 Tile[x][y] = EL_PLAYER_1;
1739 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1740 int jx = player->jx, jy = player->jy;
1742 player->present = TRUE;
1744 player->block_last_field = (element == EL_SP_MURPHY ?
1745 level.sp_block_last_field :
1746 level.block_last_field);
1748 // ---------- initialize player's last field block delay ------------------
1750 // always start with reliable default value (no adjustment needed)
1751 player->block_delay_adjustment = 0;
1753 // special case 1: in Supaplex, Murphy blocks last field one more frame
1754 if (player->block_last_field && element == EL_SP_MURPHY)
1755 player->block_delay_adjustment = 1;
1757 // special case 2: in game engines before 3.1.1, blocking was different
1758 if (game.use_block_last_field_bug)
1759 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1761 if (!network.enabled || player->connected_network)
1763 player->active = TRUE;
1765 // remove potentially duplicate players
1766 if (StorePlayer[jx][jy] == Tile[x][y])
1767 StorePlayer[jx][jy] = 0;
1769 StorePlayer[x][y] = Tile[x][y];
1771 #if DEBUG_INIT_PLAYER
1772 Debug("game:init:player", "- player element %d activated",
1773 player->element_nr);
1774 Debug("game:init:player", " (local player is %d and currently %s)",
1775 local_player->element_nr,
1776 local_player->active ? "active" : "not active");
1780 Tile[x][y] = EL_EMPTY;
1782 player->jx = player->last_jx = x;
1783 player->jy = player->last_jy = y;
1786 // always check if player was just killed and should be reanimated
1788 int player_nr = GET_PLAYER_NR(element);
1789 struct PlayerInfo *player = &stored_player[player_nr];
1791 if (player->active && player->killed)
1792 player->reanimated = TRUE; // if player was just killed, reanimate him
1796 static void InitField(int x, int y, boolean init_game)
1798 int element = Tile[x][y];
1807 InitPlayerField(x, y, element, init_game);
1810 case EL_SOKOBAN_FIELD_PLAYER:
1811 element = Tile[x][y] = EL_PLAYER_1;
1812 InitField(x, y, init_game);
1814 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1815 InitField(x, y, init_game);
1818 case EL_SOKOBAN_FIELD_EMPTY:
1819 IncrementSokobanFieldsNeeded();
1822 case EL_SOKOBAN_OBJECT:
1823 IncrementSokobanObjectsNeeded();
1827 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1828 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1829 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1830 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1831 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1832 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1833 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1834 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1835 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1836 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1845 case EL_SPACESHIP_RIGHT:
1846 case EL_SPACESHIP_UP:
1847 case EL_SPACESHIP_LEFT:
1848 case EL_SPACESHIP_DOWN:
1849 case EL_BD_BUTTERFLY:
1850 case EL_BD_BUTTERFLY_RIGHT:
1851 case EL_BD_BUTTERFLY_UP:
1852 case EL_BD_BUTTERFLY_LEFT:
1853 case EL_BD_BUTTERFLY_DOWN:
1855 case EL_BD_FIREFLY_RIGHT:
1856 case EL_BD_FIREFLY_UP:
1857 case EL_BD_FIREFLY_LEFT:
1858 case EL_BD_FIREFLY_DOWN:
1859 case EL_PACMAN_RIGHT:
1861 case EL_PACMAN_LEFT:
1862 case EL_PACMAN_DOWN:
1864 case EL_YAMYAM_LEFT:
1865 case EL_YAMYAM_RIGHT:
1867 case EL_YAMYAM_DOWN:
1868 case EL_DARK_YAMYAM:
1871 case EL_SP_SNIKSNAK:
1872 case EL_SP_ELECTRON:
1878 case EL_SPRING_LEFT:
1879 case EL_SPRING_RIGHT:
1883 case EL_AMOEBA_FULL:
1888 case EL_AMOEBA_DROP:
1889 if (y == lev_fieldy - 1)
1891 Tile[x][y] = EL_AMOEBA_GROWING;
1892 Store[x][y] = EL_AMOEBA_WET;
1896 case EL_DYNAMITE_ACTIVE:
1897 case EL_SP_DISK_RED_ACTIVE:
1898 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1899 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1900 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1901 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1902 MovDelay[x][y] = 96;
1905 case EL_EM_DYNAMITE_ACTIVE:
1906 MovDelay[x][y] = 32;
1910 game.lights_still_needed++;
1914 game.friends_still_needed++;
1919 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1922 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1923 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1924 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1925 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1926 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1927 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1928 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1929 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1930 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1931 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1932 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1933 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1936 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1937 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1938 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1940 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1942 game.belt_dir[belt_nr] = belt_dir;
1943 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1945 else // more than one switch -- set it like the first switch
1947 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1952 case EL_LIGHT_SWITCH_ACTIVE:
1954 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1957 case EL_INVISIBLE_STEELWALL:
1958 case EL_INVISIBLE_WALL:
1959 case EL_INVISIBLE_SAND:
1960 if (game.light_time_left > 0 ||
1961 game.lenses_time_left > 0)
1962 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1965 case EL_EMC_MAGIC_BALL:
1966 if (game.ball_active)
1967 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1970 case EL_EMC_MAGIC_BALL_SWITCH:
1971 if (game.ball_active)
1972 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1975 case EL_TRIGGER_PLAYER:
1976 case EL_TRIGGER_ELEMENT:
1977 case EL_TRIGGER_CE_VALUE:
1978 case EL_TRIGGER_CE_SCORE:
1980 case EL_ANY_ELEMENT:
1981 case EL_CURRENT_CE_VALUE:
1982 case EL_CURRENT_CE_SCORE:
1999 // reference elements should not be used on the playfield
2000 Tile[x][y] = EL_EMPTY;
2004 if (IS_CUSTOM_ELEMENT(element))
2006 if (CAN_MOVE(element))
2009 if (!element_info[element].use_last_ce_value || init_game)
2010 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2012 else if (IS_GROUP_ELEMENT(element))
2014 Tile[x][y] = GetElementFromGroupElement(element);
2016 InitField(x, y, init_game);
2023 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2026 static void InitField_WithBug1(int x, int y, boolean init_game)
2028 InitField(x, y, init_game);
2030 // not needed to call InitMovDir() -- already done by InitField()!
2031 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2032 CAN_MOVE(Tile[x][y]))
2036 static void InitField_WithBug2(int x, int y, boolean init_game)
2038 int old_element = Tile[x][y];
2040 InitField(x, y, init_game);
2042 // not needed to call InitMovDir() -- already done by InitField()!
2043 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2044 CAN_MOVE(old_element) &&
2045 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2048 /* this case is in fact a combination of not less than three bugs:
2049 first, it calls InitMovDir() for elements that can move, although this is
2050 already done by InitField(); then, it checks the element that was at this
2051 field _before_ the call to InitField() (which can change it); lastly, it
2052 was not called for "mole with direction" elements, which were treated as
2053 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2057 static int get_key_element_from_nr(int key_nr)
2059 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2060 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2061 EL_EM_KEY_1 : EL_KEY_1);
2063 return key_base_element + key_nr;
2066 static int get_next_dropped_element(struct PlayerInfo *player)
2068 return (player->inventory_size > 0 ?
2069 player->inventory_element[player->inventory_size - 1] :
2070 player->inventory_infinite_element != EL_UNDEFINED ?
2071 player->inventory_infinite_element :
2072 player->dynabombs_left > 0 ?
2073 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2077 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2079 // pos >= 0: get element from bottom of the stack;
2080 // pos < 0: get element from top of the stack
2084 int min_inventory_size = -pos;
2085 int inventory_pos = player->inventory_size - min_inventory_size;
2086 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2088 return (player->inventory_size >= min_inventory_size ?
2089 player->inventory_element[inventory_pos] :
2090 player->inventory_infinite_element != EL_UNDEFINED ?
2091 player->inventory_infinite_element :
2092 player->dynabombs_left >= min_dynabombs_left ?
2093 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2098 int min_dynabombs_left = pos + 1;
2099 int min_inventory_size = pos + 1 - player->dynabombs_left;
2100 int inventory_pos = pos - player->dynabombs_left;
2102 return (player->inventory_infinite_element != EL_UNDEFINED ?
2103 player->inventory_infinite_element :
2104 player->dynabombs_left >= min_dynabombs_left ?
2105 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2106 player->inventory_size >= min_inventory_size ?
2107 player->inventory_element[inventory_pos] :
2112 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2114 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2115 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2118 if (gpo1->sort_priority != gpo2->sort_priority)
2119 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2121 compare_result = gpo1->nr - gpo2->nr;
2123 return compare_result;
2126 int getPlayerInventorySize(int player_nr)
2128 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2129 return game_em.ply[player_nr]->dynamite;
2130 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2131 return game_sp.red_disk_count;
2133 return stored_player[player_nr].inventory_size;
2136 static void InitGameControlValues(void)
2140 for (i = 0; game_panel_controls[i].nr != -1; i++)
2142 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2143 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2144 struct TextPosInfo *pos = gpc->pos;
2146 int type = gpc->type;
2150 Error("'game_panel_controls' structure corrupted at %d", i);
2152 Fail("this should not happen -- please debug");
2155 // force update of game controls after initialization
2156 gpc->value = gpc->last_value = -1;
2157 gpc->frame = gpc->last_frame = -1;
2158 gpc->gfx_frame = -1;
2160 // determine panel value width for later calculation of alignment
2161 if (type == TYPE_INTEGER || type == TYPE_STRING)
2163 pos->width = pos->size * getFontWidth(pos->font);
2164 pos->height = getFontHeight(pos->font);
2166 else if (type == TYPE_ELEMENT)
2168 pos->width = pos->size;
2169 pos->height = pos->size;
2172 // fill structure for game panel draw order
2174 gpo->sort_priority = pos->sort_priority;
2177 // sort game panel controls according to sort_priority and control number
2178 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2179 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2182 static void UpdatePlayfieldElementCount(void)
2184 boolean use_element_count = FALSE;
2187 // first check if it is needed at all to calculate playfield element count
2188 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2189 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2190 use_element_count = TRUE;
2192 if (!use_element_count)
2195 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2196 element_info[i].element_count = 0;
2198 SCAN_PLAYFIELD(x, y)
2200 element_info[Tile[x][y]].element_count++;
2203 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2204 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2205 if (IS_IN_GROUP(j, i))
2206 element_info[EL_GROUP_START + i].element_count +=
2207 element_info[j].element_count;
2210 static void UpdateGameControlValues(void)
2213 int time = (game.LevelSolved ?
2214 game.LevelSolved_CountingTime :
2215 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2217 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2218 game_sp.time_played :
2219 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2220 game_mm.energy_left :
2221 game.no_time_limit ? TimePlayed : TimeLeft);
2222 int score = (game.LevelSolved ?
2223 game.LevelSolved_CountingScore :
2224 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2225 game_em.lev->score :
2226 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2228 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2231 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2232 game_em.lev->gems_needed :
2233 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2234 game_sp.infotrons_still_needed :
2235 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2236 game_mm.kettles_still_needed :
2237 game.gems_still_needed);
2238 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2239 game_em.lev->gems_needed > 0 :
2240 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2241 game_sp.infotrons_still_needed > 0 :
2242 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2243 game_mm.kettles_still_needed > 0 ||
2244 game_mm.lights_still_needed > 0 :
2245 game.gems_still_needed > 0 ||
2246 game.sokoban_fields_still_needed > 0 ||
2247 game.sokoban_objects_still_needed > 0 ||
2248 game.lights_still_needed > 0);
2249 int health = (game.LevelSolved ?
2250 game.LevelSolved_CountingHealth :
2251 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2252 MM_HEALTH(game_mm.laser_overload_value) :
2255 UpdatePlayfieldElementCount();
2257 // update game panel control values
2259 // used instead of "level_nr" (for network games)
2260 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2261 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2263 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2264 for (i = 0; i < MAX_NUM_KEYS; i++)
2265 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2266 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2267 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2269 if (game.centered_player_nr == -1)
2271 for (i = 0; i < MAX_PLAYERS; i++)
2273 // only one player in Supaplex game engine
2274 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2277 for (k = 0; k < MAX_NUM_KEYS; k++)
2279 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2281 if (game_em.ply[i]->keys & (1 << k))
2282 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2283 get_key_element_from_nr(k);
2285 else if (stored_player[i].key[k])
2286 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2287 get_key_element_from_nr(k);
2290 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2291 getPlayerInventorySize(i);
2293 if (stored_player[i].num_white_keys > 0)
2294 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2297 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2298 stored_player[i].num_white_keys;
2303 int player_nr = game.centered_player_nr;
2305 for (k = 0; k < MAX_NUM_KEYS; k++)
2307 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2309 if (game_em.ply[player_nr]->keys & (1 << k))
2310 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2311 get_key_element_from_nr(k);
2313 else if (stored_player[player_nr].key[k])
2314 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2315 get_key_element_from_nr(k);
2318 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2319 getPlayerInventorySize(player_nr);
2321 if (stored_player[player_nr].num_white_keys > 0)
2322 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2324 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2325 stored_player[player_nr].num_white_keys;
2328 // try to display as many collected keys as possible in the default game panel
2329 for (i = STD_NUM_KEYS; i < MAX_NUM_KEYS + 1; i++) // EMC keys + white key
2331 int nr = GAME_PANEL_KEY_1 + i;
2332 int emc_key = get_key_element_from_nr(i);
2333 int element = (i < MAX_NUM_KEYS ? emc_key : EL_DC_KEY_WHITE);
2334 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2335 struct TextPosInfo *pos = gpc->pos;
2337 // check if panel position is undefined for a certain EMC key or white key
2338 if (gpc->value != EL_EMPTY && pos->x == -1 && pos->y == -1)
2340 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2342 // 1st try: display key at the same position as normal or EM keys
2343 if (game_panel_controls[nr_new].value == EL_EMPTY)
2345 game_panel_controls[nr_new].value = element;
2349 // 2nd try: display key at the next free position in the key panel
2350 for (k = 0; k < STD_NUM_KEYS; k++)
2352 nr_new = GAME_PANEL_KEY_1 + k;
2354 if (game_panel_controls[nr_new].value == EL_EMPTY)
2356 game_panel_controls[nr_new].value = element;
2365 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2367 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2368 get_inventory_element_from_pos(local_player, i);
2369 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2370 get_inventory_element_from_pos(local_player, -i - 1);
2373 game_panel_controls[GAME_PANEL_SCORE].value = score;
2374 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2376 game_panel_controls[GAME_PANEL_TIME].value = time;
2378 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2379 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2380 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2382 if (level.time == 0)
2383 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2385 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2387 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2388 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2390 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2392 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2393 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2395 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2396 local_player->shield_normal_time_left;
2397 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2398 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2400 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2401 local_player->shield_deadly_time_left;
2403 game_panel_controls[GAME_PANEL_EXIT].value =
2404 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2406 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2407 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2408 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2409 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2410 EL_EMC_MAGIC_BALL_SWITCH);
2412 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2413 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2414 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2415 game.light_time_left;
2417 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2418 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2419 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2420 game.timegate_time_left;
2422 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2423 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2425 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2426 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2427 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2428 game.lenses_time_left;
2430 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2431 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2432 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2433 game.magnify_time_left;
2435 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2436 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2437 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2438 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2439 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2440 EL_BALLOON_SWITCH_NONE);
2442 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2443 local_player->dynabomb_count;
2444 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2445 local_player->dynabomb_size;
2446 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2447 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2449 game_panel_controls[GAME_PANEL_PENGUINS].value =
2450 game.friends_still_needed;
2452 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2453 game.sokoban_objects_still_needed;
2454 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2455 game.sokoban_fields_still_needed;
2457 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2458 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2460 for (i = 0; i < NUM_BELTS; i++)
2462 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2463 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2464 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2465 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2466 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2469 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2470 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2471 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2472 game.magic_wall_time_left;
2474 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2475 local_player->gravity;
2477 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2478 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2480 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2481 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2482 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2483 game.panel.element[i].id : EL_UNDEFINED);
2485 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2486 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2487 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2488 element_info[game.panel.element_count[i].id].element_count : 0);
2490 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2491 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2492 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2493 element_info[game.panel.ce_score[i].id].collect_score : 0);
2495 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2496 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2497 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2498 element_info[game.panel.ce_score_element[i].id].collect_score :
2501 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2502 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2503 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2505 // update game panel control frames
2507 for (i = 0; game_panel_controls[i].nr != -1; i++)
2509 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2511 if (gpc->type == TYPE_ELEMENT)
2513 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2515 int last_anim_random_frame = gfx.anim_random_frame;
2516 int element = gpc->value;
2517 int graphic = el2panelimg(element);
2519 if (gpc->value != gpc->last_value)
2522 gpc->gfx_random = INIT_GFX_RANDOM();
2528 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2529 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2530 gpc->gfx_random = INIT_GFX_RANDOM();
2533 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2534 gfx.anim_random_frame = gpc->gfx_random;
2536 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2537 gpc->gfx_frame = element_info[element].collect_score;
2539 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2541 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2542 gfx.anim_random_frame = last_anim_random_frame;
2545 else if (gpc->type == TYPE_GRAPHIC)
2547 if (gpc->graphic != IMG_UNDEFINED)
2549 int last_anim_random_frame = gfx.anim_random_frame;
2550 int graphic = gpc->graphic;
2552 if (gpc->value != gpc->last_value)
2555 gpc->gfx_random = INIT_GFX_RANDOM();
2561 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2562 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2563 gpc->gfx_random = INIT_GFX_RANDOM();
2566 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2567 gfx.anim_random_frame = gpc->gfx_random;
2569 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2571 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2572 gfx.anim_random_frame = last_anim_random_frame;
2578 static void DisplayGameControlValues(void)
2580 boolean redraw_panel = FALSE;
2583 for (i = 0; game_panel_controls[i].nr != -1; i++)
2585 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2587 if (PANEL_DEACTIVATED(gpc->pos))
2590 if (gpc->value == gpc->last_value &&
2591 gpc->frame == gpc->last_frame)
2594 redraw_panel = TRUE;
2600 // copy default game door content to main double buffer
2602 // !!! CHECK AGAIN !!!
2603 SetPanelBackground();
2604 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2605 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2607 // redraw game control buttons
2608 RedrawGameButtons();
2610 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2612 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2614 int nr = game_panel_order[i].nr;
2615 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2616 struct TextPosInfo *pos = gpc->pos;
2617 int type = gpc->type;
2618 int value = gpc->value;
2619 int frame = gpc->frame;
2620 int size = pos->size;
2621 int font = pos->font;
2622 boolean draw_masked = pos->draw_masked;
2623 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2625 if (PANEL_DEACTIVATED(pos))
2628 if (pos->class == get_hash_from_key("extra_panel_items") &&
2629 !setup.prefer_extra_panel_items)
2632 gpc->last_value = value;
2633 gpc->last_frame = frame;
2635 if (type == TYPE_INTEGER)
2637 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2638 nr == GAME_PANEL_TIME)
2640 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2642 if (use_dynamic_size) // use dynamic number of digits
2644 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2645 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2646 int size2 = size1 + 1;
2647 int font1 = pos->font;
2648 int font2 = pos->font_alt;
2650 size = (value < value_change ? size1 : size2);
2651 font = (value < value_change ? font1 : font2);
2655 // correct text size if "digits" is zero or less
2657 size = strlen(int2str(value, size));
2659 // dynamically correct text alignment
2660 pos->width = size * getFontWidth(font);
2662 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2663 int2str(value, size), font, mask_mode);
2665 else if (type == TYPE_ELEMENT)
2667 int element, graphic;
2671 int dst_x = PANEL_XPOS(pos);
2672 int dst_y = PANEL_YPOS(pos);
2674 if (value != EL_UNDEFINED && value != EL_EMPTY)
2677 graphic = el2panelimg(value);
2680 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2681 element, EL_NAME(element), size);
2684 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2687 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2690 width = graphic_info[graphic].width * size / TILESIZE;
2691 height = graphic_info[graphic].height * size / TILESIZE;
2694 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2697 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2701 else if (type == TYPE_GRAPHIC)
2703 int graphic = gpc->graphic;
2704 int graphic_active = gpc->graphic_active;
2708 int dst_x = PANEL_XPOS(pos);
2709 int dst_y = PANEL_YPOS(pos);
2710 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2711 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2713 if (graphic != IMG_UNDEFINED && !skip)
2715 if (pos->style == STYLE_REVERSE)
2716 value = 100 - value;
2718 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2720 if (pos->direction & MV_HORIZONTAL)
2722 width = graphic_info[graphic_active].width * value / 100;
2723 height = graphic_info[graphic_active].height;
2725 if (pos->direction == MV_LEFT)
2727 src_x += graphic_info[graphic_active].width - width;
2728 dst_x += graphic_info[graphic_active].width - width;
2733 width = graphic_info[graphic_active].width;
2734 height = graphic_info[graphic_active].height * value / 100;
2736 if (pos->direction == MV_UP)
2738 src_y += graphic_info[graphic_active].height - height;
2739 dst_y += graphic_info[graphic_active].height - height;
2744 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2747 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2750 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2752 if (pos->direction & MV_HORIZONTAL)
2754 if (pos->direction == MV_RIGHT)
2761 dst_x = PANEL_XPOS(pos);
2764 width = graphic_info[graphic].width - width;
2768 if (pos->direction == MV_DOWN)
2775 dst_y = PANEL_YPOS(pos);
2778 height = graphic_info[graphic].height - height;
2782 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2785 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2789 else if (type == TYPE_STRING)
2791 boolean active = (value != 0);
2792 char *state_normal = "off";
2793 char *state_active = "on";
2794 char *state = (active ? state_active : state_normal);
2795 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2796 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2797 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2798 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2800 if (nr == GAME_PANEL_GRAVITY_STATE)
2802 int font1 = pos->font; // (used for normal state)
2803 int font2 = pos->font_alt; // (used for active state)
2805 font = (active ? font2 : font1);
2814 // don't truncate output if "chars" is zero or less
2817 // dynamically correct text alignment
2818 pos->width = size * getFontWidth(font);
2821 s_cut = getStringCopyN(s, size);
2823 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2824 s_cut, font, mask_mode);
2830 redraw_mask |= REDRAW_DOOR_1;
2833 SetGameStatus(GAME_MODE_PLAYING);
2836 void UpdateAndDisplayGameControlValues(void)
2838 if (tape.deactivate_display)
2841 UpdateGameControlValues();
2842 DisplayGameControlValues();
2846 static void UpdateGameDoorValues(void)
2848 UpdateGameControlValues();
2852 void DrawGameDoorValues(void)
2854 DisplayGameControlValues();
2858 // ============================================================================
2860 // ----------------------------------------------------------------------------
2861 // initialize game engine due to level / tape version number
2862 // ============================================================================
2864 static void InitGameEngine(void)
2866 int i, j, k, l, x, y;
2868 // set game engine from tape file when re-playing, else from level file
2869 game.engine_version = (tape.playing ? tape.engine_version :
2870 level.game_version);
2872 // set single or multi-player game mode (needed for re-playing tapes)
2873 game.team_mode = setup.team_mode;
2877 int num_players = 0;
2879 for (i = 0; i < MAX_PLAYERS; i++)
2880 if (tape.player_participates[i])
2883 // multi-player tapes contain input data for more than one player
2884 game.team_mode = (num_players > 1);
2888 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2889 level.game_version);
2890 Debug("game:init:level", " tape.file_version == %06d",
2892 Debug("game:init:level", " tape.game_version == %06d",
2894 Debug("game:init:level", " tape.engine_version == %06d",
2895 tape.engine_version);
2896 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2897 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2900 // --------------------------------------------------------------------------
2901 // set flags for bugs and changes according to active game engine version
2902 // --------------------------------------------------------------------------
2906 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2908 Bug was introduced in version:
2911 Bug was fixed in version:
2915 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2916 but the property "can fall" was missing, which caused some levels to be
2917 unsolvable. This was fixed in version 4.2.0.0.
2919 Affected levels/tapes:
2920 An example for a tape that was fixed by this bugfix is tape 029 from the
2921 level set "rnd_sam_bateman".
2922 The wrong behaviour will still be used for all levels or tapes that were
2923 created/recorded with it. An example for this is tape 023 from the level
2924 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2927 boolean use_amoeba_dropping_cannot_fall_bug =
2928 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2929 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2931 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2932 tape.game_version < VERSION_IDENT(4,2,0,0)));
2935 Summary of bugfix/change:
2936 Fixed move speed of elements entering or leaving magic wall.
2938 Fixed/changed in version:
2942 Before 2.0.1, move speed of elements entering or leaving magic wall was
2943 twice as fast as it is now.
2944 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2946 Affected levels/tapes:
2947 The first condition is generally needed for all levels/tapes before version
2948 2.0.1, which might use the old behaviour before it was changed; known tapes
2949 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2950 The second condition is an exception from the above case and is needed for
2951 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2952 above, but before it was known that this change would break tapes like the
2953 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2954 although the engine version while recording maybe was before 2.0.1. There
2955 are a lot of tapes that are affected by this exception, like tape 006 from
2956 the level set "rnd_conor_mancone".
2959 boolean use_old_move_stepsize_for_magic_wall =
2960 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2962 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2963 tape.game_version < VERSION_IDENT(4,2,0,0)));
2966 Summary of bugfix/change:
2967 Fixed handling for custom elements that change when pushed by the player.
2969 Fixed/changed in version:
2973 Before 3.1.0, custom elements that "change when pushing" changed directly
2974 after the player started pushing them (until then handled in "DigField()").
2975 Since 3.1.0, these custom elements are not changed until the "pushing"
2976 move of the element is finished (now handled in "ContinueMoving()").
2978 Affected levels/tapes:
2979 The first condition is generally needed for all levels/tapes before version
2980 3.1.0, which might use the old behaviour before it was changed; known tapes
2981 that are affected are some tapes from the level set "Walpurgis Gardens" by
2983 The second condition is an exception from the above case and is needed for
2984 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2985 above (including some development versions of 3.1.0), but before it was
2986 known that this change would break tapes like the above and was fixed in
2987 3.1.1, so that the changed behaviour was active although the engine version
2988 while recording maybe was before 3.1.0. There is at least one tape that is
2989 affected by this exception, which is the tape for the one-level set "Bug
2990 Machine" by Juergen Bonhagen.
2993 game.use_change_when_pushing_bug =
2994 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2996 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2997 tape.game_version < VERSION_IDENT(3,1,1,0)));
3000 Summary of bugfix/change:
3001 Fixed handling for blocking the field the player leaves when moving.
3003 Fixed/changed in version:
3007 Before 3.1.1, when "block last field when moving" was enabled, the field
3008 the player is leaving when moving was blocked for the time of the move,
3009 and was directly unblocked afterwards. This resulted in the last field
3010 being blocked for exactly one less than the number of frames of one player
3011 move. Additionally, even when blocking was disabled, the last field was
3012 blocked for exactly one frame.
3013 Since 3.1.1, due to changes in player movement handling, the last field
3014 is not blocked at all when blocking is disabled. When blocking is enabled,
3015 the last field is blocked for exactly the number of frames of one player
3016 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3017 last field is blocked for exactly one more than the number of frames of
3020 Affected levels/tapes:
3021 (!!! yet to be determined -- probably many !!!)
3024 game.use_block_last_field_bug =
3025 (game.engine_version < VERSION_IDENT(3,1,1,0));
3027 /* various special flags and settings for native Emerald Mine game engine */
3029 game_em.use_single_button =
3030 (game.engine_version > VERSION_IDENT(4,0,0,2));
3032 game_em.use_snap_key_bug =
3033 (game.engine_version < VERSION_IDENT(4,0,1,0));
3035 game_em.use_random_bug =
3036 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3038 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3040 game_em.use_old_explosions = use_old_em_engine;
3041 game_em.use_old_android = use_old_em_engine;
3042 game_em.use_old_push_elements = use_old_em_engine;
3043 game_em.use_old_push_into_acid = use_old_em_engine;
3045 game_em.use_wrap_around = !use_old_em_engine;
3047 // --------------------------------------------------------------------------
3049 // set maximal allowed number of custom element changes per game frame
3050 game.max_num_changes_per_frame = 1;
3052 // default scan direction: scan playfield from top/left to bottom/right
3053 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3055 // dynamically adjust element properties according to game engine version
3056 InitElementPropertiesEngine(game.engine_version);
3058 // ---------- initialize special element properties -------------------------
3060 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3061 if (use_amoeba_dropping_cannot_fall_bug)
3062 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3064 // ---------- initialize player's initial move delay ------------------------
3066 // dynamically adjust player properties according to level information
3067 for (i = 0; i < MAX_PLAYERS; i++)
3068 game.initial_move_delay_value[i] =
3069 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3071 // dynamically adjust player properties according to game engine version
3072 for (i = 0; i < MAX_PLAYERS; i++)
3073 game.initial_move_delay[i] =
3074 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3075 game.initial_move_delay_value[i] : 0);
3077 // ---------- initialize player's initial push delay ------------------------
3079 // dynamically adjust player properties according to game engine version
3080 game.initial_push_delay_value =
3081 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3083 // ---------- initialize changing elements ----------------------------------
3085 // initialize changing elements information
3086 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3088 struct ElementInfo *ei = &element_info[i];
3090 // this pointer might have been changed in the level editor
3091 ei->change = &ei->change_page[0];
3093 if (!IS_CUSTOM_ELEMENT(i))
3095 ei->change->target_element = EL_EMPTY_SPACE;
3096 ei->change->delay_fixed = 0;
3097 ei->change->delay_random = 0;
3098 ei->change->delay_frames = 1;
3101 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3103 ei->has_change_event[j] = FALSE;
3105 ei->event_page_nr[j] = 0;
3106 ei->event_page[j] = &ei->change_page[0];
3110 // add changing elements from pre-defined list
3111 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3113 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3114 struct ElementInfo *ei = &element_info[ch_delay->element];
3116 ei->change->target_element = ch_delay->target_element;
3117 ei->change->delay_fixed = ch_delay->change_delay;
3119 ei->change->pre_change_function = ch_delay->pre_change_function;
3120 ei->change->change_function = ch_delay->change_function;
3121 ei->change->post_change_function = ch_delay->post_change_function;
3123 ei->change->can_change = TRUE;
3124 ei->change->can_change_or_has_action = TRUE;
3126 ei->has_change_event[CE_DELAY] = TRUE;
3128 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3129 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3132 // ---------- initialize internal run-time variables ------------------------
3134 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3136 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3138 for (j = 0; j < ei->num_change_pages; j++)
3140 ei->change_page[j].can_change_or_has_action =
3141 (ei->change_page[j].can_change |
3142 ei->change_page[j].has_action);
3146 // add change events from custom element configuration
3147 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3149 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3151 for (j = 0; j < ei->num_change_pages; j++)
3153 if (!ei->change_page[j].can_change_or_has_action)
3156 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3158 // only add event page for the first page found with this event
3159 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3161 ei->has_change_event[k] = TRUE;
3163 ei->event_page_nr[k] = j;
3164 ei->event_page[k] = &ei->change_page[j];
3170 // ---------- initialize reference elements in change conditions ------------
3172 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3174 int element = EL_CUSTOM_START + i;
3175 struct ElementInfo *ei = &element_info[element];
3177 for (j = 0; j < ei->num_change_pages; j++)
3179 int trigger_element = ei->change_page[j].initial_trigger_element;
3181 if (trigger_element >= EL_PREV_CE_8 &&
3182 trigger_element <= EL_NEXT_CE_8)
3183 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3185 ei->change_page[j].trigger_element = trigger_element;
3189 // ---------- initialize run-time trigger player and element ----------------
3191 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3193 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3195 for (j = 0; j < ei->num_change_pages; j++)
3197 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3198 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3199 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3200 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3201 ei->change_page[j].actual_trigger_ce_value = 0;
3202 ei->change_page[j].actual_trigger_ce_score = 0;
3206 // ---------- initialize trigger events -------------------------------------
3208 // initialize trigger events information
3209 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3210 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3211 trigger_events[i][j] = FALSE;
3213 // add trigger events from element change event properties
3214 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3216 struct ElementInfo *ei = &element_info[i];
3218 for (j = 0; j < ei->num_change_pages; j++)
3220 if (!ei->change_page[j].can_change_or_has_action)
3223 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3225 int trigger_element = ei->change_page[j].trigger_element;
3227 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3229 if (ei->change_page[j].has_event[k])
3231 if (IS_GROUP_ELEMENT(trigger_element))
3233 struct ElementGroupInfo *group =
3234 element_info[trigger_element].group;
3236 for (l = 0; l < group->num_elements_resolved; l++)
3237 trigger_events[group->element_resolved[l]][k] = TRUE;
3239 else if (trigger_element == EL_ANY_ELEMENT)
3240 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3241 trigger_events[l][k] = TRUE;
3243 trigger_events[trigger_element][k] = TRUE;
3250 // ---------- initialize push delay -----------------------------------------
3252 // initialize push delay values to default
3253 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3255 if (!IS_CUSTOM_ELEMENT(i))
3257 // set default push delay values (corrected since version 3.0.7-1)
3258 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3260 element_info[i].push_delay_fixed = 2;
3261 element_info[i].push_delay_random = 8;
3265 element_info[i].push_delay_fixed = 8;
3266 element_info[i].push_delay_random = 8;
3271 // set push delay value for certain elements from pre-defined list
3272 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3274 int e = push_delay_list[i].element;
3276 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3277 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3280 // set push delay value for Supaplex elements for newer engine versions
3281 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3283 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3285 if (IS_SP_ELEMENT(i))
3287 // set SP push delay to just enough to push under a falling zonk
3288 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3290 element_info[i].push_delay_fixed = delay;
3291 element_info[i].push_delay_random = 0;
3296 // ---------- initialize move stepsize --------------------------------------
3298 // initialize move stepsize values to default
3299 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3300 if (!IS_CUSTOM_ELEMENT(i))
3301 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3303 // set move stepsize value for certain elements from pre-defined list
3304 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3306 int e = move_stepsize_list[i].element;
3308 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3310 // set move stepsize value for certain elements for older engine versions
3311 if (use_old_move_stepsize_for_magic_wall)
3313 if (e == EL_MAGIC_WALL_FILLING ||
3314 e == EL_MAGIC_WALL_EMPTYING ||
3315 e == EL_BD_MAGIC_WALL_FILLING ||
3316 e == EL_BD_MAGIC_WALL_EMPTYING)
3317 element_info[e].move_stepsize *= 2;
3321 // ---------- initialize collect score --------------------------------------
3323 // initialize collect score values for custom elements from initial value
3324 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3325 if (IS_CUSTOM_ELEMENT(i))
3326 element_info[i].collect_score = element_info[i].collect_score_initial;
3328 // ---------- initialize collect count --------------------------------------
3330 // initialize collect count values for non-custom elements
3331 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3332 if (!IS_CUSTOM_ELEMENT(i))
3333 element_info[i].collect_count_initial = 0;
3335 // add collect count values for all elements from pre-defined list
3336 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3337 element_info[collect_count_list[i].element].collect_count_initial =
3338 collect_count_list[i].count;
3340 // ---------- initialize access direction -----------------------------------
3342 // initialize access direction values to default (access from every side)
3343 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3344 if (!IS_CUSTOM_ELEMENT(i))
3345 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3347 // set access direction value for certain elements from pre-defined list
3348 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3349 element_info[access_direction_list[i].element].access_direction =
3350 access_direction_list[i].direction;
3352 // ---------- initialize explosion content ----------------------------------
3353 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3355 if (IS_CUSTOM_ELEMENT(i))
3358 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3360 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3362 element_info[i].content.e[x][y] =
3363 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3364 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3365 i == EL_PLAYER_3 ? EL_EMERALD :
3366 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3367 i == EL_MOLE ? EL_EMERALD_RED :
3368 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3369 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3370 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3371 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3372 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3373 i == EL_WALL_EMERALD ? EL_EMERALD :
3374 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3375 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3376 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3377 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3378 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3379 i == EL_WALL_PEARL ? EL_PEARL :
3380 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3385 // ---------- initialize recursion detection --------------------------------
3386 recursion_loop_depth = 0;
3387 recursion_loop_detected = FALSE;
3388 recursion_loop_element = EL_UNDEFINED;
3390 // ---------- initialize graphics engine ------------------------------------
3391 game.scroll_delay_value =
3392 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3393 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3394 !setup.forced_scroll_delay ? 0 :
3395 setup.scroll_delay ? setup.scroll_delay_value : 0);
3396 game.scroll_delay_value =
3397 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3399 // ---------- initialize game engine snapshots ------------------------------
3400 for (i = 0; i < MAX_PLAYERS; i++)
3401 game.snapshot.last_action[i] = 0;
3402 game.snapshot.changed_action = FALSE;
3403 game.snapshot.collected_item = FALSE;
3404 game.snapshot.mode =
3405 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3406 SNAPSHOT_MODE_EVERY_STEP :
3407 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3408 SNAPSHOT_MODE_EVERY_MOVE :
3409 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3410 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3411 game.snapshot.save_snapshot = FALSE;
3413 // ---------- initialize level time for Supaplex engine ---------------------
3414 // Supaplex levels with time limit currently unsupported -- should be added
3415 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3418 // ---------- initialize flags for handling game actions --------------------
3420 // set flags for game actions to default values
3421 game.use_key_actions = TRUE;
3422 game.use_mouse_actions = FALSE;
3424 // when using Mirror Magic game engine, handle mouse events only
3425 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3427 game.use_key_actions = FALSE;
3428 game.use_mouse_actions = TRUE;
3431 // check for custom elements with mouse click events
3432 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3434 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3436 int element = EL_CUSTOM_START + i;
3438 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3439 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3440 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3441 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3442 game.use_mouse_actions = TRUE;
3447 static int get_num_special_action(int element, int action_first,
3450 int num_special_action = 0;
3453 for (i = action_first; i <= action_last; i++)
3455 boolean found = FALSE;
3457 for (j = 0; j < NUM_DIRECTIONS; j++)
3458 if (el_act_dir2img(element, i, j) !=
3459 el_act_dir2img(element, ACTION_DEFAULT, j))
3463 num_special_action++;
3468 return num_special_action;
3472 // ============================================================================
3474 // ----------------------------------------------------------------------------
3475 // initialize and start new game
3476 // ============================================================================
3478 #if DEBUG_INIT_PLAYER
3479 static void DebugPrintPlayerStatus(char *message)
3486 Debug("game:init:player", "%s:", message);
3488 for (i = 0; i < MAX_PLAYERS; i++)
3490 struct PlayerInfo *player = &stored_player[i];
3492 Debug("game:init:player",
3493 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3497 player->connected_locally,
3498 player->connected_network,
3500 (local_player == player ? " (local player)" : ""));
3507 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3508 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3509 int fade_mask = REDRAW_FIELD;
3511 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3512 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3513 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3514 int initial_move_dir = MV_DOWN;
3517 // required here to update video display before fading (FIX THIS)
3518 DrawMaskedBorder(REDRAW_DOOR_2);
3520 if (!game.restart_level)
3521 CloseDoor(DOOR_CLOSE_1);
3523 SetGameStatus(GAME_MODE_PLAYING);
3525 if (level_editor_test_game)
3526 FadeSkipNextFadeOut();
3528 FadeSetEnterScreen();
3531 fade_mask = REDRAW_ALL;
3533 FadeLevelSoundsAndMusic();
3535 ExpireSoundLoops(TRUE);
3539 if (level_editor_test_game)
3540 FadeSkipNextFadeIn();
3542 // needed if different viewport properties defined for playing
3543 ChangeViewportPropertiesIfNeeded();
3547 DrawCompleteVideoDisplay();
3549 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3552 InitGameControlValues();
3554 // initialize tape actions from game when recording tape
3557 tape.use_key_actions = game.use_key_actions;
3558 tape.use_mouse_actions = game.use_mouse_actions;
3561 // don't play tapes over network
3562 network_playing = (network.enabled && !tape.playing);
3564 for (i = 0; i < MAX_PLAYERS; i++)
3566 struct PlayerInfo *player = &stored_player[i];
3568 player->index_nr = i;
3569 player->index_bit = (1 << i);
3570 player->element_nr = EL_PLAYER_1 + i;
3572 player->present = FALSE;
3573 player->active = FALSE;
3574 player->mapped = FALSE;
3576 player->killed = FALSE;
3577 player->reanimated = FALSE;
3578 player->buried = FALSE;
3581 player->effective_action = 0;
3582 player->programmed_action = 0;
3583 player->snap_action = 0;
3585 player->mouse_action.lx = 0;
3586 player->mouse_action.ly = 0;
3587 player->mouse_action.button = 0;
3588 player->mouse_action.button_hint = 0;
3590 player->effective_mouse_action.lx = 0;
3591 player->effective_mouse_action.ly = 0;
3592 player->effective_mouse_action.button = 0;
3593 player->effective_mouse_action.button_hint = 0;
3595 for (j = 0; j < MAX_NUM_KEYS; j++)
3596 player->key[j] = FALSE;
3598 player->num_white_keys = 0;
3600 player->dynabomb_count = 0;
3601 player->dynabomb_size = 1;
3602 player->dynabombs_left = 0;
3603 player->dynabomb_xl = FALSE;
3605 player->MovDir = initial_move_dir;
3608 player->GfxDir = initial_move_dir;
3609 player->GfxAction = ACTION_DEFAULT;
3611 player->StepFrame = 0;
3613 player->initial_element = player->element_nr;
3614 player->artwork_element =
3615 (level.use_artwork_element[i] ? level.artwork_element[i] :
3616 player->element_nr);
3617 player->use_murphy = FALSE;
3619 player->block_last_field = FALSE; // initialized in InitPlayerField()
3620 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3622 player->gravity = level.initial_player_gravity[i];
3624 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3626 player->actual_frame_counter = 0;
3628 player->step_counter = 0;
3630 player->last_move_dir = initial_move_dir;
3632 player->is_active = FALSE;
3634 player->is_waiting = FALSE;
3635 player->is_moving = FALSE;
3636 player->is_auto_moving = FALSE;
3637 player->is_digging = FALSE;
3638 player->is_snapping = FALSE;
3639 player->is_collecting = FALSE;
3640 player->is_pushing = FALSE;
3641 player->is_switching = FALSE;
3642 player->is_dropping = FALSE;
3643 player->is_dropping_pressed = FALSE;
3645 player->is_bored = FALSE;
3646 player->is_sleeping = FALSE;
3648 player->was_waiting = TRUE;
3649 player->was_moving = FALSE;
3650 player->was_snapping = FALSE;
3651 player->was_dropping = FALSE;
3653 player->force_dropping = FALSE;
3655 player->frame_counter_bored = -1;
3656 player->frame_counter_sleeping = -1;
3658 player->anim_delay_counter = 0;
3659 player->post_delay_counter = 0;
3661 player->dir_waiting = initial_move_dir;
3662 player->action_waiting = ACTION_DEFAULT;
3663 player->last_action_waiting = ACTION_DEFAULT;
3664 player->special_action_bored = ACTION_DEFAULT;
3665 player->special_action_sleeping = ACTION_DEFAULT;
3667 player->switch_x = -1;
3668 player->switch_y = -1;
3670 player->drop_x = -1;
3671 player->drop_y = -1;
3673 player->show_envelope = 0;
3675 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3677 player->push_delay = -1; // initialized when pushing starts
3678 player->push_delay_value = game.initial_push_delay_value;
3680 player->drop_delay = 0;
3681 player->drop_pressed_delay = 0;
3683 player->last_jx = -1;
3684 player->last_jy = -1;
3688 player->shield_normal_time_left = 0;
3689 player->shield_deadly_time_left = 0;
3691 player->inventory_infinite_element = EL_UNDEFINED;
3692 player->inventory_size = 0;
3694 if (level.use_initial_inventory[i])
3696 for (j = 0; j < level.initial_inventory_size[i]; j++)
3698 int element = level.initial_inventory_content[i][j];
3699 int collect_count = element_info[element].collect_count_initial;
3702 if (!IS_CUSTOM_ELEMENT(element))
3705 if (collect_count == 0)
3706 player->inventory_infinite_element = element;
3708 for (k = 0; k < collect_count; k++)
3709 if (player->inventory_size < MAX_INVENTORY_SIZE)
3710 player->inventory_element[player->inventory_size++] = element;
3714 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3715 SnapField(player, 0, 0);
3717 map_player_action[i] = i;
3720 network_player_action_received = FALSE;
3722 // initial null action
3723 if (network_playing)
3724 SendToServer_MovePlayer(MV_NONE);
3729 TimeLeft = level.time;
3732 ScreenMovDir = MV_NONE;
3736 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3738 game.robot_wheel_x = -1;
3739 game.robot_wheel_y = -1;
3744 game.all_players_gone = FALSE;
3746 game.LevelSolved = FALSE;
3747 game.GameOver = FALSE;
3749 game.GamePlayed = !tape.playing;
3751 game.LevelSolved_GameWon = FALSE;
3752 game.LevelSolved_GameEnd = FALSE;
3753 game.LevelSolved_SaveTape = FALSE;
3754 game.LevelSolved_SaveScore = FALSE;
3756 game.LevelSolved_CountingTime = 0;
3757 game.LevelSolved_CountingScore = 0;
3758 game.LevelSolved_CountingHealth = 0;
3760 game.panel.active = TRUE;
3762 game.no_time_limit = (level.time == 0);
3764 game.yamyam_content_nr = 0;
3765 game.robot_wheel_active = FALSE;
3766 game.magic_wall_active = FALSE;
3767 game.magic_wall_time_left = 0;
3768 game.light_time_left = 0;
3769 game.timegate_time_left = 0;
3770 game.switchgate_pos = 0;
3771 game.wind_direction = level.wind_direction_initial;
3774 game.score_final = 0;
3776 game.health = MAX_HEALTH;
3777 game.health_final = MAX_HEALTH;
3779 game.gems_still_needed = level.gems_needed;
3780 game.sokoban_fields_still_needed = 0;
3781 game.sokoban_objects_still_needed = 0;
3782 game.lights_still_needed = 0;
3783 game.players_still_needed = 0;
3784 game.friends_still_needed = 0;
3786 game.lenses_time_left = 0;
3787 game.magnify_time_left = 0;
3789 game.ball_active = level.ball_active_initial;
3790 game.ball_content_nr = 0;
3792 game.explosions_delayed = TRUE;
3794 game.envelope_active = FALSE;
3796 for (i = 0; i < NUM_BELTS; i++)
3798 game.belt_dir[i] = MV_NONE;
3799 game.belt_dir_nr[i] = 3; // not moving, next moving left
3802 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3803 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3805 #if DEBUG_INIT_PLAYER
3806 DebugPrintPlayerStatus("Player status at level initialization");
3809 SCAN_PLAYFIELD(x, y)
3811 Tile[x][y] = Last[x][y] = level.field[x][y];
3812 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3813 ChangeDelay[x][y] = 0;
3814 ChangePage[x][y] = -1;
3815 CustomValue[x][y] = 0; // initialized in InitField()
3816 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3818 WasJustMoving[x][y] = 0;
3819 WasJustFalling[x][y] = 0;
3820 CheckCollision[x][y] = 0;
3821 CheckImpact[x][y] = 0;
3823 Pushed[x][y] = FALSE;
3825 ChangeCount[x][y] = 0;
3826 ChangeEvent[x][y] = -1;
3828 ExplodePhase[x][y] = 0;
3829 ExplodeDelay[x][y] = 0;
3830 ExplodeField[x][y] = EX_TYPE_NONE;
3832 RunnerVisit[x][y] = 0;
3833 PlayerVisit[x][y] = 0;
3836 GfxRandom[x][y] = INIT_GFX_RANDOM();
3837 GfxElement[x][y] = EL_UNDEFINED;
3838 GfxAction[x][y] = ACTION_DEFAULT;
3839 GfxDir[x][y] = MV_NONE;
3840 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3843 SCAN_PLAYFIELD(x, y)
3845 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3847 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3849 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3852 InitField(x, y, TRUE);
3854 ResetGfxAnimation(x, y);
3859 for (i = 0; i < MAX_PLAYERS; i++)
3861 struct PlayerInfo *player = &stored_player[i];
3863 // set number of special actions for bored and sleeping animation
3864 player->num_special_action_bored =
3865 get_num_special_action(player->artwork_element,
3866 ACTION_BORING_1, ACTION_BORING_LAST);
3867 player->num_special_action_sleeping =
3868 get_num_special_action(player->artwork_element,
3869 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3872 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3873 emulate_sb ? EMU_SOKOBAN :
3874 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3876 // initialize type of slippery elements
3877 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3879 if (!IS_CUSTOM_ELEMENT(i))
3881 // default: elements slip down either to the left or right randomly
3882 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3884 // SP style elements prefer to slip down on the left side
3885 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3886 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3888 // BD style elements prefer to slip down on the left side
3889 if (game.emulation == EMU_BOULDERDASH)
3890 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3894 // initialize explosion and ignition delay
3895 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3897 if (!IS_CUSTOM_ELEMENT(i))
3900 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3901 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3902 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3903 int last_phase = (num_phase + 1) * delay;
3904 int half_phase = (num_phase / 2) * delay;
3906 element_info[i].explosion_delay = last_phase - 1;
3907 element_info[i].ignition_delay = half_phase;
3909 if (i == EL_BLACK_ORB)
3910 element_info[i].ignition_delay = 1;
3914 // correct non-moving belts to start moving left
3915 for (i = 0; i < NUM_BELTS; i++)
3916 if (game.belt_dir[i] == MV_NONE)
3917 game.belt_dir_nr[i] = 3; // not moving, next moving left
3919 #if USE_NEW_PLAYER_ASSIGNMENTS
3920 // use preferred player also in local single-player mode
3921 if (!network.enabled && !game.team_mode)
3923 int new_index_nr = setup.network_player_nr;
3925 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3927 for (i = 0; i < MAX_PLAYERS; i++)
3928 stored_player[i].connected_locally = FALSE;
3930 stored_player[new_index_nr].connected_locally = TRUE;
3934 for (i = 0; i < MAX_PLAYERS; i++)
3936 stored_player[i].connected = FALSE;
3938 // in network game mode, the local player might not be the first player
3939 if (stored_player[i].connected_locally)
3940 local_player = &stored_player[i];
3943 if (!network.enabled)
3944 local_player->connected = TRUE;
3948 for (i = 0; i < MAX_PLAYERS; i++)
3949 stored_player[i].connected = tape.player_participates[i];
3951 else if (network.enabled)
3953 // add team mode players connected over the network (needed for correct
3954 // assignment of player figures from level to locally playing players)
3956 for (i = 0; i < MAX_PLAYERS; i++)
3957 if (stored_player[i].connected_network)
3958 stored_player[i].connected = TRUE;
3960 else if (game.team_mode)
3962 // try to guess locally connected team mode players (needed for correct
3963 // assignment of player figures from level to locally playing players)
3965 for (i = 0; i < MAX_PLAYERS; i++)
3966 if (setup.input[i].use_joystick ||
3967 setup.input[i].key.left != KSYM_UNDEFINED)
3968 stored_player[i].connected = TRUE;
3971 #if DEBUG_INIT_PLAYER
3972 DebugPrintPlayerStatus("Player status after level initialization");
3975 #if DEBUG_INIT_PLAYER
3976 Debug("game:init:player", "Reassigning players ...");
3979 // check if any connected player was not found in playfield
3980 for (i = 0; i < MAX_PLAYERS; i++)
3982 struct PlayerInfo *player = &stored_player[i];
3984 if (player->connected && !player->present)
3986 struct PlayerInfo *field_player = NULL;
3988 #if DEBUG_INIT_PLAYER
3989 Debug("game:init:player",
3990 "- looking for field player for player %d ...", i + 1);
3993 // assign first free player found that is present in the playfield
3995 // first try: look for unmapped playfield player that is not connected
3996 for (j = 0; j < MAX_PLAYERS; j++)
3997 if (field_player == NULL &&
3998 stored_player[j].present &&
3999 !stored_player[j].mapped &&
4000 !stored_player[j].connected)
4001 field_player = &stored_player[j];
4003 // second try: look for *any* unmapped playfield player
4004 for (j = 0; j < MAX_PLAYERS; j++)
4005 if (field_player == NULL &&
4006 stored_player[j].present &&
4007 !stored_player[j].mapped)
4008 field_player = &stored_player[j];
4010 if (field_player != NULL)
4012 int jx = field_player->jx, jy = field_player->jy;
4014 #if DEBUG_INIT_PLAYER
4015 Debug("game:init:player", "- found player %d",
4016 field_player->index_nr + 1);
4019 player->present = FALSE;
4020 player->active = FALSE;
4022 field_player->present = TRUE;
4023 field_player->active = TRUE;
4026 player->initial_element = field_player->initial_element;
4027 player->artwork_element = field_player->artwork_element;
4029 player->block_last_field = field_player->block_last_field;
4030 player->block_delay_adjustment = field_player->block_delay_adjustment;
4033 StorePlayer[jx][jy] = field_player->element_nr;
4035 field_player->jx = field_player->last_jx = jx;
4036 field_player->jy = field_player->last_jy = jy;
4038 if (local_player == player)
4039 local_player = field_player;
4041 map_player_action[field_player->index_nr] = i;
4043 field_player->mapped = TRUE;
4045 #if DEBUG_INIT_PLAYER
4046 Debug("game:init:player", "- map_player_action[%d] == %d",
4047 field_player->index_nr + 1, i + 1);
4052 if (player->connected && player->present)
4053 player->mapped = TRUE;
4056 #if DEBUG_INIT_PLAYER
4057 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4062 // check if any connected player was not found in playfield
4063 for (i = 0; i < MAX_PLAYERS; i++)
4065 struct PlayerInfo *player = &stored_player[i];
4067 if (player->connected && !player->present)
4069 for (j = 0; j < MAX_PLAYERS; j++)
4071 struct PlayerInfo *field_player = &stored_player[j];
4072 int jx = field_player->jx, jy = field_player->jy;
4074 // assign first free player found that is present in the playfield
4075 if (field_player->present && !field_player->connected)
4077 player->present = TRUE;
4078 player->active = TRUE;
4080 field_player->present = FALSE;
4081 field_player->active = FALSE;
4083 player->initial_element = field_player->initial_element;
4084 player->artwork_element = field_player->artwork_element;
4086 player->block_last_field = field_player->block_last_field;
4087 player->block_delay_adjustment = field_player->block_delay_adjustment;
4089 StorePlayer[jx][jy] = player->element_nr;
4091 player->jx = player->last_jx = jx;
4092 player->jy = player->last_jy = jy;
4102 Debug("game:init:player", "local_player->present == %d",
4103 local_player->present);
4106 // set focus to local player for network games, else to all players
4107 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4108 game.centered_player_nr_next = game.centered_player_nr;
4109 game.set_centered_player = FALSE;
4110 game.set_centered_player_wrap = FALSE;
4112 if (network_playing && tape.recording)
4114 // store client dependent player focus when recording network games
4115 tape.centered_player_nr_next = game.centered_player_nr_next;
4116 tape.set_centered_player = TRUE;
4121 // when playing a tape, eliminate all players who do not participate
4123 #if USE_NEW_PLAYER_ASSIGNMENTS
4125 if (!game.team_mode)
4127 for (i = 0; i < MAX_PLAYERS; i++)
4129 if (stored_player[i].active &&
4130 !tape.player_participates[map_player_action[i]])
4132 struct PlayerInfo *player = &stored_player[i];
4133 int jx = player->jx, jy = player->jy;
4135 #if DEBUG_INIT_PLAYER
4136 Debug("game:init:player", "Removing player %d at (%d, %d)",
4140 player->active = FALSE;
4141 StorePlayer[jx][jy] = 0;
4142 Tile[jx][jy] = EL_EMPTY;
4149 for (i = 0; i < MAX_PLAYERS; i++)
4151 if (stored_player[i].active &&
4152 !tape.player_participates[i])
4154 struct PlayerInfo *player = &stored_player[i];
4155 int jx = player->jx, jy = player->jy;
4157 player->active = FALSE;
4158 StorePlayer[jx][jy] = 0;
4159 Tile[jx][jy] = EL_EMPTY;
4164 else if (!network.enabled && !game.team_mode) // && !tape.playing
4166 // when in single player mode, eliminate all but the local player
4168 for (i = 0; i < MAX_PLAYERS; i++)
4170 struct PlayerInfo *player = &stored_player[i];
4172 if (player->active && player != local_player)
4174 int jx = player->jx, jy = player->jy;
4176 player->active = FALSE;
4177 player->present = FALSE;
4179 StorePlayer[jx][jy] = 0;
4180 Tile[jx][jy] = EL_EMPTY;
4185 for (i = 0; i < MAX_PLAYERS; i++)
4186 if (stored_player[i].active)
4187 game.players_still_needed++;
4189 if (level.solved_by_one_player)
4190 game.players_still_needed = 1;
4192 // when recording the game, store which players take part in the game
4195 #if USE_NEW_PLAYER_ASSIGNMENTS
4196 for (i = 0; i < MAX_PLAYERS; i++)
4197 if (stored_player[i].connected)
4198 tape.player_participates[i] = TRUE;
4200 for (i = 0; i < MAX_PLAYERS; i++)
4201 if (stored_player[i].active)
4202 tape.player_participates[i] = TRUE;
4206 #if DEBUG_INIT_PLAYER
4207 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4210 if (BorderElement == EL_EMPTY)
4213 SBX_Right = lev_fieldx - SCR_FIELDX;
4215 SBY_Lower = lev_fieldy - SCR_FIELDY;
4220 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4222 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4225 if (full_lev_fieldx <= SCR_FIELDX)
4226 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4227 if (full_lev_fieldy <= SCR_FIELDY)
4228 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4230 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4232 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4235 // if local player not found, look for custom element that might create
4236 // the player (make some assumptions about the right custom element)
4237 if (!local_player->present)
4239 int start_x = 0, start_y = 0;
4240 int found_rating = 0;
4241 int found_element = EL_UNDEFINED;
4242 int player_nr = local_player->index_nr;
4244 SCAN_PLAYFIELD(x, y)
4246 int element = Tile[x][y];
4251 if (level.use_start_element[player_nr] &&
4252 level.start_element[player_nr] == element &&
4259 found_element = element;
4262 if (!IS_CUSTOM_ELEMENT(element))
4265 if (CAN_CHANGE(element))
4267 for (i = 0; i < element_info[element].num_change_pages; i++)
4269 // check for player created from custom element as single target
4270 content = element_info[element].change_page[i].target_element;
4271 is_player = ELEM_IS_PLAYER(content);
4273 if (is_player && (found_rating < 3 ||
4274 (found_rating == 3 && element < found_element)))
4280 found_element = element;
4285 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4287 // check for player created from custom element as explosion content
4288 content = element_info[element].content.e[xx][yy];
4289 is_player = ELEM_IS_PLAYER(content);
4291 if (is_player && (found_rating < 2 ||
4292 (found_rating == 2 && element < found_element)))
4294 start_x = x + xx - 1;
4295 start_y = y + yy - 1;
4298 found_element = element;
4301 if (!CAN_CHANGE(element))
4304 for (i = 0; i < element_info[element].num_change_pages; i++)
4306 // check for player created from custom element as extended target
4308 element_info[element].change_page[i].target_content.e[xx][yy];
4310 is_player = ELEM_IS_PLAYER(content);
4312 if (is_player && (found_rating < 1 ||
4313 (found_rating == 1 && element < found_element)))
4315 start_x = x + xx - 1;
4316 start_y = y + yy - 1;
4319 found_element = element;
4325 scroll_x = SCROLL_POSITION_X(start_x);
4326 scroll_y = SCROLL_POSITION_Y(start_y);
4330 scroll_x = SCROLL_POSITION_X(local_player->jx);
4331 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4334 // !!! FIX THIS (START) !!!
4335 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4337 InitGameEngine_EM();
4339 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4341 InitGameEngine_SP();
4343 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4345 InitGameEngine_MM();
4349 DrawLevel(REDRAW_FIELD);
4352 // after drawing the level, correct some elements
4353 if (game.timegate_time_left == 0)
4354 CloseAllOpenTimegates();
4357 // blit playfield from scroll buffer to normal back buffer for fading in
4358 BlitScreenToBitmap(backbuffer);
4359 // !!! FIX THIS (END) !!!
4361 DrawMaskedBorder(fade_mask);
4366 // full screen redraw is required at this point in the following cases:
4367 // - special editor door undrawn when game was started from level editor
4368 // - drawing area (playfield) was changed and has to be removed completely
4369 redraw_mask = REDRAW_ALL;
4373 if (!game.restart_level)
4375 // copy default game door content to main double buffer
4377 // !!! CHECK AGAIN !!!
4378 SetPanelBackground();
4379 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4380 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4383 SetPanelBackground();
4384 SetDrawBackgroundMask(REDRAW_DOOR_1);
4386 UpdateAndDisplayGameControlValues();
4388 if (!game.restart_level)
4394 CreateGameButtons();
4399 // copy actual game door content to door double buffer for OpenDoor()
4400 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4402 OpenDoor(DOOR_OPEN_ALL);
4404 KeyboardAutoRepeatOffUnlessAutoplay();
4406 #if DEBUG_INIT_PLAYER
4407 DebugPrintPlayerStatus("Player status (final)");
4416 if (!game.restart_level && !tape.playing)
4418 LevelStats_incPlayed(level_nr);
4420 SaveLevelSetup_SeriesInfo();
4423 game.restart_level = FALSE;
4424 game.restart_game_message = NULL;
4425 game.request_active = FALSE;
4427 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4428 InitGameActions_MM();
4430 SaveEngineSnapshotToListInitial();
4432 if (!game.restart_level)
4434 PlaySound(SND_GAME_STARTING);
4436 if (setup.sound_music)
4441 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4442 int actual_player_x, int actual_player_y)
4444 // this is used for non-R'n'D game engines to update certain engine values
4446 // needed to determine if sounds are played within the visible screen area
4447 scroll_x = actual_scroll_x;
4448 scroll_y = actual_scroll_y;
4450 // needed to get player position for "follow finger" playing input method
4451 local_player->jx = actual_player_x;
4452 local_player->jy = actual_player_y;
4455 void InitMovDir(int x, int y)
4457 int i, element = Tile[x][y];
4458 static int xy[4][2] =
4465 static int direction[3][4] =
4467 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4468 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4469 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4478 Tile[x][y] = EL_BUG;
4479 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4482 case EL_SPACESHIP_RIGHT:
4483 case EL_SPACESHIP_UP:
4484 case EL_SPACESHIP_LEFT:
4485 case EL_SPACESHIP_DOWN:
4486 Tile[x][y] = EL_SPACESHIP;
4487 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4490 case EL_BD_BUTTERFLY_RIGHT:
4491 case EL_BD_BUTTERFLY_UP:
4492 case EL_BD_BUTTERFLY_LEFT:
4493 case EL_BD_BUTTERFLY_DOWN:
4494 Tile[x][y] = EL_BD_BUTTERFLY;
4495 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4498 case EL_BD_FIREFLY_RIGHT:
4499 case EL_BD_FIREFLY_UP:
4500 case EL_BD_FIREFLY_LEFT:
4501 case EL_BD_FIREFLY_DOWN:
4502 Tile[x][y] = EL_BD_FIREFLY;
4503 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4506 case EL_PACMAN_RIGHT:
4508 case EL_PACMAN_LEFT:
4509 case EL_PACMAN_DOWN:
4510 Tile[x][y] = EL_PACMAN;
4511 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4514 case EL_YAMYAM_LEFT:
4515 case EL_YAMYAM_RIGHT:
4517 case EL_YAMYAM_DOWN:
4518 Tile[x][y] = EL_YAMYAM;
4519 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4522 case EL_SP_SNIKSNAK:
4523 MovDir[x][y] = MV_UP;
4526 case EL_SP_ELECTRON:
4527 MovDir[x][y] = MV_LEFT;
4534 Tile[x][y] = EL_MOLE;
4535 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4538 case EL_SPRING_LEFT:
4539 case EL_SPRING_RIGHT:
4540 Tile[x][y] = EL_SPRING;
4541 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4545 if (IS_CUSTOM_ELEMENT(element))
4547 struct ElementInfo *ei = &element_info[element];
4548 int move_direction_initial = ei->move_direction_initial;
4549 int move_pattern = ei->move_pattern;
4551 if (move_direction_initial == MV_START_PREVIOUS)
4553 if (MovDir[x][y] != MV_NONE)
4556 move_direction_initial = MV_START_AUTOMATIC;
4559 if (move_direction_initial == MV_START_RANDOM)
4560 MovDir[x][y] = 1 << RND(4);
4561 else if (move_direction_initial & MV_ANY_DIRECTION)
4562 MovDir[x][y] = move_direction_initial;
4563 else if (move_pattern == MV_ALL_DIRECTIONS ||
4564 move_pattern == MV_TURNING_LEFT ||
4565 move_pattern == MV_TURNING_RIGHT ||
4566 move_pattern == MV_TURNING_LEFT_RIGHT ||
4567 move_pattern == MV_TURNING_RIGHT_LEFT ||
4568 move_pattern == MV_TURNING_RANDOM)
4569 MovDir[x][y] = 1 << RND(4);
4570 else if (move_pattern == MV_HORIZONTAL)
4571 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4572 else if (move_pattern == MV_VERTICAL)
4573 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4574 else if (move_pattern & MV_ANY_DIRECTION)
4575 MovDir[x][y] = element_info[element].move_pattern;
4576 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4577 move_pattern == MV_ALONG_RIGHT_SIDE)
4579 // use random direction as default start direction
4580 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4581 MovDir[x][y] = 1 << RND(4);
4583 for (i = 0; i < NUM_DIRECTIONS; i++)
4585 int x1 = x + xy[i][0];
4586 int y1 = y + xy[i][1];
4588 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4590 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4591 MovDir[x][y] = direction[0][i];
4593 MovDir[x][y] = direction[1][i];
4602 MovDir[x][y] = 1 << RND(4);
4604 if (element != EL_BUG &&
4605 element != EL_SPACESHIP &&
4606 element != EL_BD_BUTTERFLY &&
4607 element != EL_BD_FIREFLY)
4610 for (i = 0; i < NUM_DIRECTIONS; i++)
4612 int x1 = x + xy[i][0];
4613 int y1 = y + xy[i][1];
4615 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4617 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4619 MovDir[x][y] = direction[0][i];
4622 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4623 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4625 MovDir[x][y] = direction[1][i];
4634 GfxDir[x][y] = MovDir[x][y];
4637 void InitAmoebaNr(int x, int y)
4640 int group_nr = AmoebaNeighbourNr(x, y);
4644 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4646 if (AmoebaCnt[i] == 0)
4654 AmoebaNr[x][y] = group_nr;
4655 AmoebaCnt[group_nr]++;
4656 AmoebaCnt2[group_nr]++;
4659 static void LevelSolved(void)
4661 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4662 game.players_still_needed > 0)
4665 game.LevelSolved = TRUE;
4666 game.GameOver = TRUE;
4668 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4669 game_em.lev->score :
4670 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4673 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4674 MM_HEALTH(game_mm.laser_overload_value) :
4677 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4678 game.LevelSolved_CountingScore = game.score_final;
4679 game.LevelSolved_CountingHealth = game.health_final;
4684 static int time_count_steps;
4685 static int time, time_final;
4686 static int score, score_final;
4687 static int health, health_final;
4688 static int game_over_delay_1 = 0;
4689 static int game_over_delay_2 = 0;
4690 static int game_over_delay_3 = 0;
4691 int game_over_delay_value_1 = 50;
4692 int game_over_delay_value_2 = 25;
4693 int game_over_delay_value_3 = 50;
4695 if (!game.LevelSolved_GameWon)
4699 // do not start end game actions before the player stops moving (to exit)
4700 if (local_player->active && local_player->MovPos)
4703 game.LevelSolved_GameWon = TRUE;
4704 game.LevelSolved_SaveTape = tape.recording;
4705 game.LevelSolved_SaveScore = !tape.playing;
4709 LevelStats_incSolved(level_nr);
4711 SaveLevelSetup_SeriesInfo();
4714 if (tape.auto_play) // tape might already be stopped here
4715 tape.auto_play_level_solved = TRUE;
4719 game_over_delay_1 = 0;
4720 game_over_delay_2 = 0;
4721 game_over_delay_3 = game_over_delay_value_3;
4723 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4724 score = score_final = game.score_final;
4725 health = health_final = game.health_final;
4727 if (level.score[SC_TIME_BONUS] > 0)
4732 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4734 else if (game.no_time_limit && TimePlayed < 999)
4737 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4740 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4742 game_over_delay_1 = game_over_delay_value_1;
4744 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4747 score_final += health * level.score[SC_TIME_BONUS];
4749 game_over_delay_2 = game_over_delay_value_2;
4752 game.score_final = score_final;
4753 game.health_final = health_final;
4756 if (level_editor_test_game)
4759 score = score_final;
4761 game.LevelSolved_CountingTime = time;
4762 game.LevelSolved_CountingScore = score;
4764 game_panel_controls[GAME_PANEL_TIME].value = time;
4765 game_panel_controls[GAME_PANEL_SCORE].value = score;
4767 DisplayGameControlValues();
4770 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4772 // check if last player has left the level
4773 if (game.exit_x >= 0 &&
4776 int x = game.exit_x;
4777 int y = game.exit_y;
4778 int element = Tile[x][y];
4780 // close exit door after last player
4781 if ((game.all_players_gone &&
4782 (element == EL_EXIT_OPEN ||
4783 element == EL_SP_EXIT_OPEN ||
4784 element == EL_STEEL_EXIT_OPEN)) ||
4785 element == EL_EM_EXIT_OPEN ||
4786 element == EL_EM_STEEL_EXIT_OPEN)
4790 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4791 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4792 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4793 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4794 EL_EM_STEEL_EXIT_CLOSING);
4796 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4799 // player disappears
4800 DrawLevelField(x, y);
4803 for (i = 0; i < MAX_PLAYERS; i++)
4805 struct PlayerInfo *player = &stored_player[i];
4807 if (player->present)
4809 RemovePlayer(player);
4811 // player disappears
4812 DrawLevelField(player->jx, player->jy);
4817 PlaySound(SND_GAME_WINNING);
4820 if (game_over_delay_1 > 0)
4822 game_over_delay_1--;
4827 if (time != time_final)
4829 int time_to_go = ABS(time_final - time);
4830 int time_count_dir = (time < time_final ? +1 : -1);
4832 if (time_to_go < time_count_steps)
4833 time_count_steps = 1;
4835 time += time_count_steps * time_count_dir;
4836 score += time_count_steps * level.score[SC_TIME_BONUS];
4838 game.LevelSolved_CountingTime = time;
4839 game.LevelSolved_CountingScore = score;
4841 game_panel_controls[GAME_PANEL_TIME].value = time;
4842 game_panel_controls[GAME_PANEL_SCORE].value = score;
4844 DisplayGameControlValues();
4846 if (time == time_final)
4847 StopSound(SND_GAME_LEVELTIME_BONUS);
4848 else if (setup.sound_loops)
4849 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4851 PlaySound(SND_GAME_LEVELTIME_BONUS);
4856 if (game_over_delay_2 > 0)
4858 game_over_delay_2--;
4863 if (health != health_final)
4865 int health_count_dir = (health < health_final ? +1 : -1);
4867 health += health_count_dir;
4868 score += level.score[SC_TIME_BONUS];
4870 game.LevelSolved_CountingHealth = health;
4871 game.LevelSolved_CountingScore = score;
4873 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4874 game_panel_controls[GAME_PANEL_SCORE].value = score;
4876 DisplayGameControlValues();
4878 if (health == health_final)
4879 StopSound(SND_GAME_LEVELTIME_BONUS);
4880 else if (setup.sound_loops)
4881 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4883 PlaySound(SND_GAME_LEVELTIME_BONUS);
4888 game.panel.active = FALSE;
4890 if (game_over_delay_3 > 0)
4892 game_over_delay_3--;
4902 // used instead of "level_nr" (needed for network games)
4903 int last_level_nr = levelset.level_nr;
4906 game.LevelSolved_GameEnd = TRUE;
4908 if (game.LevelSolved_SaveTape)
4910 // make sure that request dialog to save tape does not open door again
4911 if (!global.use_envelope_request)
4912 CloseDoor(DOOR_CLOSE_1);
4914 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4917 // if no tape is to be saved, close both doors simultaneously
4918 CloseDoor(DOOR_CLOSE_ALL);
4920 if (level_editor_test_game)
4922 SetGameStatus(GAME_MODE_MAIN);
4929 if (!game.LevelSolved_SaveScore)
4931 SetGameStatus(GAME_MODE_MAIN);
4938 if (level_nr == leveldir_current->handicap_level)
4940 leveldir_current->handicap_level++;
4942 SaveLevelSetup_SeriesInfo();
4945 if (setup.increment_levels &&
4946 level_nr < leveldir_current->last_level &&
4949 level_nr++; // advance to next level
4950 TapeErase(); // start with empty tape
4952 if (setup.auto_play_next_level)
4954 LoadLevel(level_nr);
4956 SaveLevelSetup_SeriesInfo();
4960 hi_pos = NewHiScore(last_level_nr);
4962 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4964 SetGameStatus(GAME_MODE_SCORES);
4966 DrawHallOfFame(last_level_nr, hi_pos);
4968 else if (setup.auto_play_next_level && setup.increment_levels &&
4969 last_level_nr < leveldir_current->last_level &&
4972 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4976 SetGameStatus(GAME_MODE_MAIN);
4982 int NewHiScore(int level_nr)
4986 boolean one_score_entry_per_name = !program.many_scores_per_name;
4988 LoadScore(level_nr);
4990 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4991 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4994 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4996 if (game.score_final > highscore[k].Score)
4998 // player has made it to the hall of fame
5000 if (k < MAX_SCORE_ENTRIES - 1)
5002 int m = MAX_SCORE_ENTRIES - 1;
5004 if (one_score_entry_per_name)
5006 for (l = k; l < MAX_SCORE_ENTRIES; l++)
5007 if (strEqual(setup.player_name, highscore[l].Name))
5010 if (m == k) // player's new highscore overwrites his old one
5014 for (l = m; l > k; l--)
5016 strcpy(highscore[l].Name, highscore[l - 1].Name);
5017 highscore[l].Score = highscore[l - 1].Score;
5023 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
5024 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
5025 highscore[k].Score = game.score_final;
5030 else if (one_score_entry_per_name &&
5031 !strncmp(setup.player_name, highscore[k].Name,
5032 MAX_PLAYER_NAME_LEN))
5033 break; // player already there with a higher score
5037 SaveScore(level_nr);
5042 static int getElementMoveStepsizeExt(int x, int y, int direction)
5044 int element = Tile[x][y];
5045 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5046 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5047 int horiz_move = (dx != 0);
5048 int sign = (horiz_move ? dx : dy);
5049 int step = sign * element_info[element].move_stepsize;
5051 // special values for move stepsize for spring and things on conveyor belt
5054 if (CAN_FALL(element) &&
5055 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5056 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5057 else if (element == EL_SPRING)
5058 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5064 static int getElementMoveStepsize(int x, int y)
5066 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5069 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5071 if (player->GfxAction != action || player->GfxDir != dir)
5073 player->GfxAction = action;
5074 player->GfxDir = dir;
5076 player->StepFrame = 0;
5080 static void ResetGfxFrame(int x, int y)
5082 // profiling showed that "autotest" spends 10~20% of its time in this function
5083 if (DrawingDeactivatedField())
5086 int element = Tile[x][y];
5087 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5089 if (graphic_info[graphic].anim_global_sync)
5090 GfxFrame[x][y] = FrameCounter;
5091 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5092 GfxFrame[x][y] = CustomValue[x][y];
5093 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5094 GfxFrame[x][y] = element_info[element].collect_score;
5095 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5096 GfxFrame[x][y] = ChangeDelay[x][y];
5099 static void ResetGfxAnimation(int x, int y)
5101 GfxAction[x][y] = ACTION_DEFAULT;
5102 GfxDir[x][y] = MovDir[x][y];
5105 ResetGfxFrame(x, y);
5108 static void ResetRandomAnimationValue(int x, int y)
5110 GfxRandom[x][y] = INIT_GFX_RANDOM();
5113 static void InitMovingField(int x, int y, int direction)
5115 int element = Tile[x][y];
5116 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5117 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5120 boolean is_moving_before, is_moving_after;
5122 // check if element was/is moving or being moved before/after mode change
5123 is_moving_before = (WasJustMoving[x][y] != 0);
5124 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5126 // reset animation only for moving elements which change direction of moving
5127 // or which just started or stopped moving
5128 // (else CEs with property "can move" / "not moving" are reset each frame)
5129 if (is_moving_before != is_moving_after ||
5130 direction != MovDir[x][y])
5131 ResetGfxAnimation(x, y);
5133 MovDir[x][y] = direction;
5134 GfxDir[x][y] = direction;
5136 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5137 direction == MV_DOWN && CAN_FALL(element) ?
5138 ACTION_FALLING : ACTION_MOVING);
5140 // this is needed for CEs with property "can move" / "not moving"
5142 if (is_moving_after)
5144 if (Tile[newx][newy] == EL_EMPTY)
5145 Tile[newx][newy] = EL_BLOCKED;
5147 MovDir[newx][newy] = MovDir[x][y];
5149 CustomValue[newx][newy] = CustomValue[x][y];
5151 GfxFrame[newx][newy] = GfxFrame[x][y];
5152 GfxRandom[newx][newy] = GfxRandom[x][y];
5153 GfxAction[newx][newy] = GfxAction[x][y];
5154 GfxDir[newx][newy] = GfxDir[x][y];
5158 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5160 int direction = MovDir[x][y];
5161 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5162 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5168 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5170 int oldx = x, oldy = y;
5171 int direction = MovDir[x][y];
5173 if (direction == MV_LEFT)
5175 else if (direction == MV_RIGHT)
5177 else if (direction == MV_UP)
5179 else if (direction == MV_DOWN)
5182 *comes_from_x = oldx;
5183 *comes_from_y = oldy;
5186 static int MovingOrBlocked2Element(int x, int y)
5188 int element = Tile[x][y];
5190 if (element == EL_BLOCKED)
5194 Blocked2Moving(x, y, &oldx, &oldy);
5195 return Tile[oldx][oldy];
5201 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5203 // like MovingOrBlocked2Element(), but if element is moving
5204 // and (x,y) is the field the moving element is just leaving,
5205 // return EL_BLOCKED instead of the element value
5206 int element = Tile[x][y];
5208 if (IS_MOVING(x, y))
5210 if (element == EL_BLOCKED)
5214 Blocked2Moving(x, y, &oldx, &oldy);
5215 return Tile[oldx][oldy];
5224 static void RemoveField(int x, int y)
5226 Tile[x][y] = EL_EMPTY;
5232 CustomValue[x][y] = 0;
5235 ChangeDelay[x][y] = 0;
5236 ChangePage[x][y] = -1;
5237 Pushed[x][y] = FALSE;
5239 GfxElement[x][y] = EL_UNDEFINED;
5240 GfxAction[x][y] = ACTION_DEFAULT;
5241 GfxDir[x][y] = MV_NONE;
5244 static void RemoveMovingField(int x, int y)
5246 int oldx = x, oldy = y, newx = x, newy = y;
5247 int element = Tile[x][y];
5248 int next_element = EL_UNDEFINED;
5250 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5253 if (IS_MOVING(x, y))
5255 Moving2Blocked(x, y, &newx, &newy);
5257 if (Tile[newx][newy] != EL_BLOCKED)
5259 // element is moving, but target field is not free (blocked), but
5260 // already occupied by something different (example: acid pool);
5261 // in this case, only remove the moving field, but not the target
5263 RemoveField(oldx, oldy);
5265 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5267 TEST_DrawLevelField(oldx, oldy);
5272 else if (element == EL_BLOCKED)
5274 Blocked2Moving(x, y, &oldx, &oldy);
5275 if (!IS_MOVING(oldx, oldy))
5279 if (element == EL_BLOCKED &&
5280 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5281 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5282 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5283 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5284 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5285 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5286 next_element = get_next_element(Tile[oldx][oldy]);
5288 RemoveField(oldx, oldy);
5289 RemoveField(newx, newy);
5291 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5293 if (next_element != EL_UNDEFINED)
5294 Tile[oldx][oldy] = next_element;
5296 TEST_DrawLevelField(oldx, oldy);
5297 TEST_DrawLevelField(newx, newy);
5300 void DrawDynamite(int x, int y)
5302 int sx = SCREENX(x), sy = SCREENY(y);
5303 int graphic = el2img(Tile[x][y]);
5306 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5309 if (IS_WALKABLE_INSIDE(Back[x][y]))
5313 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5314 else if (Store[x][y])
5315 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5317 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5319 if (Back[x][y] || Store[x][y])
5320 DrawGraphicThruMask(sx, sy, graphic, frame);
5322 DrawGraphic(sx, sy, graphic, frame);
5325 static void CheckDynamite(int x, int y)
5327 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5331 if (MovDelay[x][y] != 0)
5334 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5340 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5345 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5347 boolean num_checked_players = 0;
5350 for (i = 0; i < MAX_PLAYERS; i++)
5352 if (stored_player[i].active)
5354 int sx = stored_player[i].jx;
5355 int sy = stored_player[i].jy;
5357 if (num_checked_players == 0)
5364 *sx1 = MIN(*sx1, sx);
5365 *sy1 = MIN(*sy1, sy);
5366 *sx2 = MAX(*sx2, sx);
5367 *sy2 = MAX(*sy2, sy);
5370 num_checked_players++;
5375 static boolean checkIfAllPlayersFitToScreen_RND(void)
5377 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5379 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5381 return (sx2 - sx1 < SCR_FIELDX &&
5382 sy2 - sy1 < SCR_FIELDY);
5385 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5387 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5389 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5391 *sx = (sx1 + sx2) / 2;
5392 *sy = (sy1 + sy2) / 2;
5395 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5396 boolean center_screen, boolean quick_relocation)
5398 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5399 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5400 boolean no_delay = (tape.warp_forward);
5401 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5402 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5403 int new_scroll_x, new_scroll_y;
5405 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5407 // case 1: quick relocation inside visible screen (without scrolling)
5414 if (!level.shifted_relocation || center_screen)
5416 // relocation _with_ centering of screen
5418 new_scroll_x = SCROLL_POSITION_X(x);
5419 new_scroll_y = SCROLL_POSITION_Y(y);
5423 // relocation _without_ centering of screen
5425 int center_scroll_x = SCROLL_POSITION_X(old_x);
5426 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5427 int offset_x = x + (scroll_x - center_scroll_x);
5428 int offset_y = y + (scroll_y - center_scroll_y);
5430 // for new screen position, apply previous offset to center position
5431 new_scroll_x = SCROLL_POSITION_X(offset_x);
5432 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5435 if (quick_relocation)
5437 // case 2: quick relocation (redraw without visible scrolling)
5439 scroll_x = new_scroll_x;
5440 scroll_y = new_scroll_y;
5447 // case 3: visible relocation (with scrolling to new position)
5449 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5451 SetVideoFrameDelay(wait_delay_value);
5453 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5455 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5456 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5458 if (dx == 0 && dy == 0) // no scrolling needed at all
5464 // set values for horizontal/vertical screen scrolling (half tile size)
5465 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5466 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5467 int pos_x = dx * TILEX / 2;
5468 int pos_y = dy * TILEY / 2;
5469 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5470 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5472 ScrollLevel(dx, dy);
5475 // scroll in two steps of half tile size to make things smoother
5476 BlitScreenToBitmapExt_RND(window, fx, fy);
5478 // scroll second step to align at full tile size
5479 BlitScreenToBitmap(window);
5485 SetVideoFrameDelay(frame_delay_value_old);
5488 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5490 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5491 int player_nr = GET_PLAYER_NR(el_player);
5492 struct PlayerInfo *player = &stored_player[player_nr];
5493 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5494 boolean no_delay = (tape.warp_forward);
5495 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5496 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5497 int old_jx = player->jx;
5498 int old_jy = player->jy;
5499 int old_element = Tile[old_jx][old_jy];
5500 int element = Tile[jx][jy];
5501 boolean player_relocated = (old_jx != jx || old_jy != jy);
5503 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5504 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5505 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5506 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5507 int leave_side_horiz = move_dir_horiz;
5508 int leave_side_vert = move_dir_vert;
5509 int enter_side = enter_side_horiz | enter_side_vert;
5510 int leave_side = leave_side_horiz | leave_side_vert;
5512 if (player->buried) // do not reanimate dead player
5515 if (!player_relocated) // no need to relocate the player
5518 if (IS_PLAYER(jx, jy)) // player already placed at new position
5520 RemoveField(jx, jy); // temporarily remove newly placed player
5521 DrawLevelField(jx, jy);
5524 if (player->present)
5526 while (player->MovPos)
5528 ScrollPlayer(player, SCROLL_GO_ON);
5529 ScrollScreen(NULL, SCROLL_GO_ON);
5531 AdvanceFrameAndPlayerCounters(player->index_nr);
5535 BackToFront_WithFrameDelay(wait_delay_value);
5538 DrawPlayer(player); // needed here only to cleanup last field
5539 DrawLevelField(player->jx, player->jy); // remove player graphic
5541 player->is_moving = FALSE;
5544 if (IS_CUSTOM_ELEMENT(old_element))
5545 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5547 player->index_bit, leave_side);
5549 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5551 player->index_bit, leave_side);
5553 Tile[jx][jy] = el_player;
5554 InitPlayerField(jx, jy, el_player, TRUE);
5556 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5557 possible that the relocation target field did not contain a player element,
5558 but a walkable element, to which the new player was relocated -- in this
5559 case, restore that (already initialized!) element on the player field */
5560 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5562 Tile[jx][jy] = element; // restore previously existing element
5565 // only visually relocate centered player
5566 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5567 FALSE, level.instant_relocation);
5569 TestIfPlayerTouchesBadThing(jx, jy);
5570 TestIfPlayerTouchesCustomElement(jx, jy);
5572 if (IS_CUSTOM_ELEMENT(element))
5573 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5574 player->index_bit, enter_side);
5576 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5577 player->index_bit, enter_side);
5579 if (player->is_switching)
5581 /* ensure that relocation while still switching an element does not cause
5582 a new element to be treated as also switched directly after relocation
5583 (this is important for teleporter switches that teleport the player to
5584 a place where another teleporter switch is in the same direction, which
5585 would then incorrectly be treated as immediately switched before the
5586 direction key that caused the switch was released) */
5588 player->switch_x += jx - old_jx;
5589 player->switch_y += jy - old_jy;
5593 static void Explode(int ex, int ey, int phase, int mode)
5599 // !!! eliminate this variable !!!
5600 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5602 if (game.explosions_delayed)
5604 ExplodeField[ex][ey] = mode;
5608 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5610 int center_element = Tile[ex][ey];
5611 int artwork_element, explosion_element; // set these values later
5613 // remove things displayed in background while burning dynamite
5614 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5617 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5619 // put moving element to center field (and let it explode there)
5620 center_element = MovingOrBlocked2Element(ex, ey);
5621 RemoveMovingField(ex, ey);
5622 Tile[ex][ey] = center_element;
5625 // now "center_element" is finally determined -- set related values now
5626 artwork_element = center_element; // for custom player artwork
5627 explosion_element = center_element; // for custom player artwork
5629 if (IS_PLAYER(ex, ey))
5631 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5633 artwork_element = stored_player[player_nr].artwork_element;
5635 if (level.use_explosion_element[player_nr])
5637 explosion_element = level.explosion_element[player_nr];
5638 artwork_element = explosion_element;
5642 if (mode == EX_TYPE_NORMAL ||
5643 mode == EX_TYPE_CENTER ||
5644 mode == EX_TYPE_CROSS)
5645 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5647 last_phase = element_info[explosion_element].explosion_delay + 1;
5649 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5651 int xx = x - ex + 1;
5652 int yy = y - ey + 1;
5655 if (!IN_LEV_FIELD(x, y) ||
5656 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5657 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5660 element = Tile[x][y];
5662 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5664 element = MovingOrBlocked2Element(x, y);
5666 if (!IS_EXPLOSION_PROOF(element))
5667 RemoveMovingField(x, y);
5670 // indestructible elements can only explode in center (but not flames)
5671 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5672 mode == EX_TYPE_BORDER)) ||
5673 element == EL_FLAMES)
5676 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5677 behaviour, for example when touching a yamyam that explodes to rocks
5678 with active deadly shield, a rock is created under the player !!! */
5679 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5681 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5682 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5683 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5685 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5688 if (IS_ACTIVE_BOMB(element))
5690 // re-activate things under the bomb like gate or penguin
5691 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5698 // save walkable background elements while explosion on same tile
5699 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5700 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5701 Back[x][y] = element;
5703 // ignite explodable elements reached by other explosion
5704 if (element == EL_EXPLOSION)
5705 element = Store2[x][y];
5707 if (AmoebaNr[x][y] &&
5708 (element == EL_AMOEBA_FULL ||
5709 element == EL_BD_AMOEBA ||
5710 element == EL_AMOEBA_GROWING))
5712 AmoebaCnt[AmoebaNr[x][y]]--;
5713 AmoebaCnt2[AmoebaNr[x][y]]--;
5718 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5720 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5722 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5724 if (PLAYERINFO(ex, ey)->use_murphy)
5725 Store[x][y] = EL_EMPTY;
5728 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5729 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5730 else if (ELEM_IS_PLAYER(center_element))
5731 Store[x][y] = EL_EMPTY;
5732 else if (center_element == EL_YAMYAM)
5733 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5734 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5735 Store[x][y] = element_info[center_element].content.e[xx][yy];
5737 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5738 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5739 // otherwise) -- FIX THIS !!!
5740 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5741 Store[x][y] = element_info[element].content.e[1][1];
5743 else if (!CAN_EXPLODE(element))
5744 Store[x][y] = element_info[element].content.e[1][1];
5747 Store[x][y] = EL_EMPTY;
5749 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5750 center_element == EL_AMOEBA_TO_DIAMOND)
5751 Store2[x][y] = element;
5753 Tile[x][y] = EL_EXPLOSION;
5754 GfxElement[x][y] = artwork_element;
5756 ExplodePhase[x][y] = 1;
5757 ExplodeDelay[x][y] = last_phase;
5762 if (center_element == EL_YAMYAM)
5763 game.yamyam_content_nr =
5764 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5776 GfxFrame[x][y] = 0; // restart explosion animation
5778 last_phase = ExplodeDelay[x][y];
5780 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5782 // this can happen if the player leaves an explosion just in time
5783 if (GfxElement[x][y] == EL_UNDEFINED)
5784 GfxElement[x][y] = EL_EMPTY;
5786 border_element = Store2[x][y];
5787 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5788 border_element = StorePlayer[x][y];
5790 if (phase == element_info[border_element].ignition_delay ||
5791 phase == last_phase)
5793 boolean border_explosion = FALSE;
5795 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5796 !PLAYER_EXPLOSION_PROTECTED(x, y))
5798 KillPlayerUnlessExplosionProtected(x, y);
5799 border_explosion = TRUE;
5801 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5803 Tile[x][y] = Store2[x][y];
5806 border_explosion = TRUE;
5808 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5810 AmoebaToDiamond(x, y);
5812 border_explosion = TRUE;
5815 // if an element just explodes due to another explosion (chain-reaction),
5816 // do not immediately end the new explosion when it was the last frame of
5817 // the explosion (as it would be done in the following "if"-statement!)
5818 if (border_explosion && phase == last_phase)
5822 if (phase == last_phase)
5826 element = Tile[x][y] = Store[x][y];
5827 Store[x][y] = Store2[x][y] = 0;
5828 GfxElement[x][y] = EL_UNDEFINED;
5830 // player can escape from explosions and might therefore be still alive
5831 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5832 element <= EL_PLAYER_IS_EXPLODING_4)
5834 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5835 int explosion_element = EL_PLAYER_1 + player_nr;
5836 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5837 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5839 if (level.use_explosion_element[player_nr])
5840 explosion_element = level.explosion_element[player_nr];
5842 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5843 element_info[explosion_element].content.e[xx][yy]);
5846 // restore probably existing indestructible background element
5847 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5848 element = Tile[x][y] = Back[x][y];
5851 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5852 GfxDir[x][y] = MV_NONE;
5853 ChangeDelay[x][y] = 0;
5854 ChangePage[x][y] = -1;
5856 CustomValue[x][y] = 0;
5858 InitField_WithBug2(x, y, FALSE);
5860 TEST_DrawLevelField(x, y);
5862 TestIfElementTouchesCustomElement(x, y);
5864 if (GFX_CRUMBLED(element))
5865 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5867 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5868 StorePlayer[x][y] = 0;
5870 if (ELEM_IS_PLAYER(element))
5871 RelocatePlayer(x, y, element);
5873 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5875 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5876 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5879 TEST_DrawLevelFieldCrumbled(x, y);
5881 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5883 DrawLevelElement(x, y, Back[x][y]);
5884 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5886 else if (IS_WALKABLE_UNDER(Back[x][y]))
5888 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5889 DrawLevelElementThruMask(x, y, Back[x][y]);
5891 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5892 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5896 static void DynaExplode(int ex, int ey)
5899 int dynabomb_element = Tile[ex][ey];
5900 int dynabomb_size = 1;
5901 boolean dynabomb_xl = FALSE;
5902 struct PlayerInfo *player;
5903 static int xy[4][2] =
5911 if (IS_ACTIVE_BOMB(dynabomb_element))
5913 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5914 dynabomb_size = player->dynabomb_size;
5915 dynabomb_xl = player->dynabomb_xl;
5916 player->dynabombs_left++;
5919 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5921 for (i = 0; i < NUM_DIRECTIONS; i++)
5923 for (j = 1; j <= dynabomb_size; j++)
5925 int x = ex + j * xy[i][0];
5926 int y = ey + j * xy[i][1];
5929 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5932 element = Tile[x][y];
5934 // do not restart explosions of fields with active bombs
5935 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5938 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5940 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5941 !IS_DIGGABLE(element) && !dynabomb_xl)
5947 void Bang(int x, int y)
5949 int element = MovingOrBlocked2Element(x, y);
5950 int explosion_type = EX_TYPE_NORMAL;
5952 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5954 struct PlayerInfo *player = PLAYERINFO(x, y);
5956 element = Tile[x][y] = player->initial_element;
5958 if (level.use_explosion_element[player->index_nr])
5960 int explosion_element = level.explosion_element[player->index_nr];
5962 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5963 explosion_type = EX_TYPE_CROSS;
5964 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5965 explosion_type = EX_TYPE_CENTER;
5973 case EL_BD_BUTTERFLY:
5976 case EL_DARK_YAMYAM:
5980 RaiseScoreElement(element);
5983 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5984 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5985 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5986 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5987 case EL_DYNABOMB_INCREASE_NUMBER:
5988 case EL_DYNABOMB_INCREASE_SIZE:
5989 case EL_DYNABOMB_INCREASE_POWER:
5990 explosion_type = EX_TYPE_DYNA;
5993 case EL_DC_LANDMINE:
5994 explosion_type = EX_TYPE_CENTER;
5999 case EL_LAMP_ACTIVE:
6000 case EL_AMOEBA_TO_DIAMOND:
6001 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6002 explosion_type = EX_TYPE_CENTER;
6006 if (element_info[element].explosion_type == EXPLODES_CROSS)
6007 explosion_type = EX_TYPE_CROSS;
6008 else if (element_info[element].explosion_type == EXPLODES_1X1)
6009 explosion_type = EX_TYPE_CENTER;
6013 if (explosion_type == EX_TYPE_DYNA)
6016 Explode(x, y, EX_PHASE_START, explosion_type);
6018 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6021 static void SplashAcid(int x, int y)
6023 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6024 (!IN_LEV_FIELD(x - 1, y - 2) ||
6025 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6026 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6028 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6029 (!IN_LEV_FIELD(x + 1, y - 2) ||
6030 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6031 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6033 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6036 static void InitBeltMovement(void)
6038 static int belt_base_element[4] =
6040 EL_CONVEYOR_BELT_1_LEFT,
6041 EL_CONVEYOR_BELT_2_LEFT,
6042 EL_CONVEYOR_BELT_3_LEFT,
6043 EL_CONVEYOR_BELT_4_LEFT
6045 static int belt_base_active_element[4] =
6047 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6048 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6049 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6050 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6055 // set frame order for belt animation graphic according to belt direction
6056 for (i = 0; i < NUM_BELTS; i++)
6060 for (j = 0; j < NUM_BELT_PARTS; j++)
6062 int element = belt_base_active_element[belt_nr] + j;
6063 int graphic_1 = el2img(element);
6064 int graphic_2 = el2panelimg(element);
6066 if (game.belt_dir[i] == MV_LEFT)
6068 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6069 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6073 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6074 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6079 SCAN_PLAYFIELD(x, y)
6081 int element = Tile[x][y];
6083 for (i = 0; i < NUM_BELTS; i++)
6085 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6087 int e_belt_nr = getBeltNrFromBeltElement(element);
6090 if (e_belt_nr == belt_nr)
6092 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6094 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6101 static void ToggleBeltSwitch(int x, int y)
6103 static int belt_base_element[4] =
6105 EL_CONVEYOR_BELT_1_LEFT,
6106 EL_CONVEYOR_BELT_2_LEFT,
6107 EL_CONVEYOR_BELT_3_LEFT,
6108 EL_CONVEYOR_BELT_4_LEFT
6110 static int belt_base_active_element[4] =
6112 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6113 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6114 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6115 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6117 static int belt_base_switch_element[4] =
6119 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6120 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6121 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6122 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6124 static int belt_move_dir[4] =
6132 int element = Tile[x][y];
6133 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6134 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6135 int belt_dir = belt_move_dir[belt_dir_nr];
6138 if (!IS_BELT_SWITCH(element))
6141 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6142 game.belt_dir[belt_nr] = belt_dir;
6144 if (belt_dir_nr == 3)
6147 // set frame order for belt animation graphic according to belt direction
6148 for (i = 0; i < NUM_BELT_PARTS; i++)
6150 int element = belt_base_active_element[belt_nr] + i;
6151 int graphic_1 = el2img(element);
6152 int graphic_2 = el2panelimg(element);
6154 if (belt_dir == MV_LEFT)
6156 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6157 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6161 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6162 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6166 SCAN_PLAYFIELD(xx, yy)
6168 int element = Tile[xx][yy];
6170 if (IS_BELT_SWITCH(element))
6172 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6174 if (e_belt_nr == belt_nr)
6176 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6177 TEST_DrawLevelField(xx, yy);
6180 else if (IS_BELT(element) && belt_dir != MV_NONE)
6182 int e_belt_nr = getBeltNrFromBeltElement(element);
6184 if (e_belt_nr == belt_nr)
6186 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6188 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6189 TEST_DrawLevelField(xx, yy);
6192 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6194 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6196 if (e_belt_nr == belt_nr)
6198 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6200 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6201 TEST_DrawLevelField(xx, yy);
6207 static void ToggleSwitchgateSwitch(int x, int y)
6211 game.switchgate_pos = !game.switchgate_pos;
6213 SCAN_PLAYFIELD(xx, yy)
6215 int element = Tile[xx][yy];
6217 if (element == EL_SWITCHGATE_SWITCH_UP)
6219 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6220 TEST_DrawLevelField(xx, yy);
6222 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6224 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6225 TEST_DrawLevelField(xx, yy);
6227 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6229 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6230 TEST_DrawLevelField(xx, yy);
6232 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6234 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6235 TEST_DrawLevelField(xx, yy);
6237 else if (element == EL_SWITCHGATE_OPEN ||
6238 element == EL_SWITCHGATE_OPENING)
6240 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6242 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6244 else if (element == EL_SWITCHGATE_CLOSED ||
6245 element == EL_SWITCHGATE_CLOSING)
6247 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6249 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6254 static int getInvisibleActiveFromInvisibleElement(int element)
6256 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6257 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6258 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6262 static int getInvisibleFromInvisibleActiveElement(int element)
6264 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6265 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6266 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6270 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6274 SCAN_PLAYFIELD(x, y)
6276 int element = Tile[x][y];
6278 if (element == EL_LIGHT_SWITCH &&
6279 game.light_time_left > 0)
6281 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6282 TEST_DrawLevelField(x, y);
6284 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6285 game.light_time_left == 0)
6287 Tile[x][y] = EL_LIGHT_SWITCH;
6288 TEST_DrawLevelField(x, y);
6290 else if (element == EL_EMC_DRIPPER &&
6291 game.light_time_left > 0)
6293 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6294 TEST_DrawLevelField(x, y);
6296 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6297 game.light_time_left == 0)
6299 Tile[x][y] = EL_EMC_DRIPPER;
6300 TEST_DrawLevelField(x, y);
6302 else if (element == EL_INVISIBLE_STEELWALL ||
6303 element == EL_INVISIBLE_WALL ||
6304 element == EL_INVISIBLE_SAND)
6306 if (game.light_time_left > 0)
6307 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6309 TEST_DrawLevelField(x, y);
6311 // uncrumble neighbour fields, if needed
6312 if (element == EL_INVISIBLE_SAND)
6313 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6315 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6316 element == EL_INVISIBLE_WALL_ACTIVE ||
6317 element == EL_INVISIBLE_SAND_ACTIVE)
6319 if (game.light_time_left == 0)
6320 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6322 TEST_DrawLevelField(x, y);
6324 // re-crumble neighbour fields, if needed
6325 if (element == EL_INVISIBLE_SAND)
6326 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6331 static void RedrawAllInvisibleElementsForLenses(void)
6335 SCAN_PLAYFIELD(x, y)
6337 int element = Tile[x][y];
6339 if (element == EL_EMC_DRIPPER &&
6340 game.lenses_time_left > 0)
6342 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6343 TEST_DrawLevelField(x, y);
6345 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6346 game.lenses_time_left == 0)
6348 Tile[x][y] = EL_EMC_DRIPPER;
6349 TEST_DrawLevelField(x, y);
6351 else if (element == EL_INVISIBLE_STEELWALL ||
6352 element == EL_INVISIBLE_WALL ||
6353 element == EL_INVISIBLE_SAND)
6355 if (game.lenses_time_left > 0)
6356 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6358 TEST_DrawLevelField(x, y);
6360 // uncrumble neighbour fields, if needed
6361 if (element == EL_INVISIBLE_SAND)
6362 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6364 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6365 element == EL_INVISIBLE_WALL_ACTIVE ||
6366 element == EL_INVISIBLE_SAND_ACTIVE)
6368 if (game.lenses_time_left == 0)
6369 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6371 TEST_DrawLevelField(x, y);
6373 // re-crumble neighbour fields, if needed
6374 if (element == EL_INVISIBLE_SAND)
6375 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6380 static void RedrawAllInvisibleElementsForMagnifier(void)
6384 SCAN_PLAYFIELD(x, y)
6386 int element = Tile[x][y];
6388 if (element == EL_EMC_FAKE_GRASS &&
6389 game.magnify_time_left > 0)
6391 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6392 TEST_DrawLevelField(x, y);
6394 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6395 game.magnify_time_left == 0)
6397 Tile[x][y] = EL_EMC_FAKE_GRASS;
6398 TEST_DrawLevelField(x, y);
6400 else if (IS_GATE_GRAY(element) &&
6401 game.magnify_time_left > 0)
6403 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6404 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6405 IS_EM_GATE_GRAY(element) ?
6406 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6407 IS_EMC_GATE_GRAY(element) ?
6408 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6409 IS_DC_GATE_GRAY(element) ?
6410 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6412 TEST_DrawLevelField(x, y);
6414 else if (IS_GATE_GRAY_ACTIVE(element) &&
6415 game.magnify_time_left == 0)
6417 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6418 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6419 IS_EM_GATE_GRAY_ACTIVE(element) ?
6420 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6421 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6422 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6423 IS_DC_GATE_GRAY_ACTIVE(element) ?
6424 EL_DC_GATE_WHITE_GRAY :
6426 TEST_DrawLevelField(x, y);
6431 static void ToggleLightSwitch(int x, int y)
6433 int element = Tile[x][y];
6435 game.light_time_left =
6436 (element == EL_LIGHT_SWITCH ?
6437 level.time_light * FRAMES_PER_SECOND : 0);
6439 RedrawAllLightSwitchesAndInvisibleElements();
6442 static void ActivateTimegateSwitch(int x, int y)
6446 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6448 SCAN_PLAYFIELD(xx, yy)
6450 int element = Tile[xx][yy];
6452 if (element == EL_TIMEGATE_CLOSED ||
6453 element == EL_TIMEGATE_CLOSING)
6455 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6456 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6460 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6462 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6463 TEST_DrawLevelField(xx, yy);
6469 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6470 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6473 static void Impact(int x, int y)
6475 boolean last_line = (y == lev_fieldy - 1);
6476 boolean object_hit = FALSE;
6477 boolean impact = (last_line || object_hit);
6478 int element = Tile[x][y];
6479 int smashed = EL_STEELWALL;
6481 if (!last_line) // check if element below was hit
6483 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6486 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6487 MovDir[x][y + 1] != MV_DOWN ||
6488 MovPos[x][y + 1] <= TILEY / 2));
6490 // do not smash moving elements that left the smashed field in time
6491 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6492 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6495 #if USE_QUICKSAND_IMPACT_BUGFIX
6496 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6498 RemoveMovingField(x, y + 1);
6499 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6500 Tile[x][y + 2] = EL_ROCK;
6501 TEST_DrawLevelField(x, y + 2);
6506 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6508 RemoveMovingField(x, y + 1);
6509 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6510 Tile[x][y + 2] = EL_ROCK;
6511 TEST_DrawLevelField(x, y + 2);
6518 smashed = MovingOrBlocked2Element(x, y + 1);
6520 impact = (last_line || object_hit);
6523 if (!last_line && smashed == EL_ACID) // element falls into acid
6525 SplashAcid(x, y + 1);
6529 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6530 // only reset graphic animation if graphic really changes after impact
6532 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6534 ResetGfxAnimation(x, y);
6535 TEST_DrawLevelField(x, y);
6538 if (impact && CAN_EXPLODE_IMPACT(element))
6543 else if (impact && element == EL_PEARL &&
6544 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6546 ResetGfxAnimation(x, y);
6548 Tile[x][y] = EL_PEARL_BREAKING;
6549 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6552 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6554 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6559 if (impact && element == EL_AMOEBA_DROP)
6561 if (object_hit && IS_PLAYER(x, y + 1))
6562 KillPlayerUnlessEnemyProtected(x, y + 1);
6563 else if (object_hit && smashed == EL_PENGUIN)
6567 Tile[x][y] = EL_AMOEBA_GROWING;
6568 Store[x][y] = EL_AMOEBA_WET;
6570 ResetRandomAnimationValue(x, y);
6575 if (object_hit) // check which object was hit
6577 if ((CAN_PASS_MAGIC_WALL(element) &&
6578 (smashed == EL_MAGIC_WALL ||
6579 smashed == EL_BD_MAGIC_WALL)) ||
6580 (CAN_PASS_DC_MAGIC_WALL(element) &&
6581 smashed == EL_DC_MAGIC_WALL))
6584 int activated_magic_wall =
6585 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6586 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6587 EL_DC_MAGIC_WALL_ACTIVE);
6589 // activate magic wall / mill
6590 SCAN_PLAYFIELD(xx, yy)
6592 if (Tile[xx][yy] == smashed)
6593 Tile[xx][yy] = activated_magic_wall;
6596 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6597 game.magic_wall_active = TRUE;
6599 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6600 SND_MAGIC_WALL_ACTIVATING :
6601 smashed == EL_BD_MAGIC_WALL ?
6602 SND_BD_MAGIC_WALL_ACTIVATING :
6603 SND_DC_MAGIC_WALL_ACTIVATING));
6606 if (IS_PLAYER(x, y + 1))
6608 if (CAN_SMASH_PLAYER(element))
6610 KillPlayerUnlessEnemyProtected(x, y + 1);
6614 else if (smashed == EL_PENGUIN)
6616 if (CAN_SMASH_PLAYER(element))
6622 else if (element == EL_BD_DIAMOND)
6624 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6630 else if (((element == EL_SP_INFOTRON ||
6631 element == EL_SP_ZONK) &&
6632 (smashed == EL_SP_SNIKSNAK ||
6633 smashed == EL_SP_ELECTRON ||
6634 smashed == EL_SP_DISK_ORANGE)) ||
6635 (element == EL_SP_INFOTRON &&
6636 smashed == EL_SP_DISK_YELLOW))
6641 else if (CAN_SMASH_EVERYTHING(element))
6643 if (IS_CLASSIC_ENEMY(smashed) ||
6644 CAN_EXPLODE_SMASHED(smashed))
6649 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6651 if (smashed == EL_LAMP ||
6652 smashed == EL_LAMP_ACTIVE)
6657 else if (smashed == EL_NUT)
6659 Tile[x][y + 1] = EL_NUT_BREAKING;
6660 PlayLevelSound(x, y, SND_NUT_BREAKING);
6661 RaiseScoreElement(EL_NUT);
6664 else if (smashed == EL_PEARL)
6666 ResetGfxAnimation(x, y);
6668 Tile[x][y + 1] = EL_PEARL_BREAKING;
6669 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6672 else if (smashed == EL_DIAMOND)
6674 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6675 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6678 else if (IS_BELT_SWITCH(smashed))
6680 ToggleBeltSwitch(x, y + 1);
6682 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6683 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6684 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6685 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6687 ToggleSwitchgateSwitch(x, y + 1);
6689 else if (smashed == EL_LIGHT_SWITCH ||
6690 smashed == EL_LIGHT_SWITCH_ACTIVE)
6692 ToggleLightSwitch(x, y + 1);
6696 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6698 CheckElementChangeBySide(x, y + 1, smashed, element,
6699 CE_SWITCHED, CH_SIDE_TOP);
6700 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6706 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6711 // play sound of magic wall / mill
6713 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6714 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6715 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6717 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6718 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6719 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6720 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6721 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6722 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6727 // play sound of object that hits the ground
6728 if (last_line || object_hit)
6729 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6732 static void TurnRoundExt(int x, int y)
6744 { 0, 0 }, { 0, 0 }, { 0, 0 },
6749 int left, right, back;
6753 { MV_DOWN, MV_UP, MV_RIGHT },
6754 { MV_UP, MV_DOWN, MV_LEFT },
6756 { MV_LEFT, MV_RIGHT, MV_DOWN },
6760 { MV_RIGHT, MV_LEFT, MV_UP }
6763 int element = Tile[x][y];
6764 int move_pattern = element_info[element].move_pattern;
6766 int old_move_dir = MovDir[x][y];
6767 int left_dir = turn[old_move_dir].left;
6768 int right_dir = turn[old_move_dir].right;
6769 int back_dir = turn[old_move_dir].back;
6771 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6772 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6773 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6774 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6776 int left_x = x + left_dx, left_y = y + left_dy;
6777 int right_x = x + right_dx, right_y = y + right_dy;
6778 int move_x = x + move_dx, move_y = y + move_dy;
6782 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6784 TestIfBadThingTouchesOtherBadThing(x, y);
6786 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6787 MovDir[x][y] = right_dir;
6788 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6789 MovDir[x][y] = left_dir;
6791 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6793 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6796 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6798 TestIfBadThingTouchesOtherBadThing(x, y);
6800 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6801 MovDir[x][y] = left_dir;
6802 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6803 MovDir[x][y] = right_dir;
6805 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6807 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6810 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6812 TestIfBadThingTouchesOtherBadThing(x, y);
6814 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6815 MovDir[x][y] = left_dir;
6816 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6817 MovDir[x][y] = right_dir;
6819 if (MovDir[x][y] != old_move_dir)
6822 else if (element == EL_YAMYAM)
6824 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6825 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6827 if (can_turn_left && can_turn_right)
6828 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6829 else if (can_turn_left)
6830 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6831 else if (can_turn_right)
6832 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6834 MovDir[x][y] = back_dir;
6836 MovDelay[x][y] = 16 + 16 * RND(3);
6838 else if (element == EL_DARK_YAMYAM)
6840 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6842 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6845 if (can_turn_left && can_turn_right)
6846 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6847 else if (can_turn_left)
6848 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6849 else if (can_turn_right)
6850 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6852 MovDir[x][y] = back_dir;
6854 MovDelay[x][y] = 16 + 16 * RND(3);
6856 else if (element == EL_PACMAN)
6858 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6859 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6861 if (can_turn_left && can_turn_right)
6862 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6863 else if (can_turn_left)
6864 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6865 else if (can_turn_right)
6866 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6868 MovDir[x][y] = back_dir;
6870 MovDelay[x][y] = 6 + RND(40);
6872 else if (element == EL_PIG)
6874 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6875 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6876 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6877 boolean should_turn_left, should_turn_right, should_move_on;
6879 int rnd = RND(rnd_value);
6881 should_turn_left = (can_turn_left &&
6883 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6884 y + back_dy + left_dy)));
6885 should_turn_right = (can_turn_right &&
6887 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6888 y + back_dy + right_dy)));
6889 should_move_on = (can_move_on &&
6892 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6893 y + move_dy + left_dy) ||
6894 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6895 y + move_dy + right_dy)));
6897 if (should_turn_left || should_turn_right || should_move_on)
6899 if (should_turn_left && should_turn_right && should_move_on)
6900 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6901 rnd < 2 * rnd_value / 3 ? right_dir :
6903 else if (should_turn_left && should_turn_right)
6904 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6905 else if (should_turn_left && should_move_on)
6906 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6907 else if (should_turn_right && should_move_on)
6908 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6909 else if (should_turn_left)
6910 MovDir[x][y] = left_dir;
6911 else if (should_turn_right)
6912 MovDir[x][y] = right_dir;
6913 else if (should_move_on)
6914 MovDir[x][y] = old_move_dir;
6916 else if (can_move_on && rnd > rnd_value / 8)
6917 MovDir[x][y] = old_move_dir;
6918 else if (can_turn_left && can_turn_right)
6919 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6920 else if (can_turn_left && rnd > rnd_value / 8)
6921 MovDir[x][y] = left_dir;
6922 else if (can_turn_right && rnd > rnd_value/8)
6923 MovDir[x][y] = right_dir;
6925 MovDir[x][y] = back_dir;
6927 xx = x + move_xy[MovDir[x][y]].dx;
6928 yy = y + move_xy[MovDir[x][y]].dy;
6930 if (!IN_LEV_FIELD(xx, yy) ||
6931 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6932 MovDir[x][y] = old_move_dir;
6936 else if (element == EL_DRAGON)
6938 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6939 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6940 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6942 int rnd = RND(rnd_value);
6944 if (can_move_on && rnd > rnd_value / 8)
6945 MovDir[x][y] = old_move_dir;
6946 else if (can_turn_left && can_turn_right)
6947 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6948 else if (can_turn_left && rnd > rnd_value / 8)
6949 MovDir[x][y] = left_dir;
6950 else if (can_turn_right && rnd > rnd_value / 8)
6951 MovDir[x][y] = right_dir;
6953 MovDir[x][y] = back_dir;
6955 xx = x + move_xy[MovDir[x][y]].dx;
6956 yy = y + move_xy[MovDir[x][y]].dy;
6958 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6959 MovDir[x][y] = old_move_dir;
6963 else if (element == EL_MOLE)
6965 boolean can_move_on =
6966 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6967 IS_AMOEBOID(Tile[move_x][move_y]) ||
6968 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
6971 boolean can_turn_left =
6972 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6973 IS_AMOEBOID(Tile[left_x][left_y])));
6975 boolean can_turn_right =
6976 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6977 IS_AMOEBOID(Tile[right_x][right_y])));
6979 if (can_turn_left && can_turn_right)
6980 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6981 else if (can_turn_left)
6982 MovDir[x][y] = left_dir;
6984 MovDir[x][y] = right_dir;
6987 if (MovDir[x][y] != old_move_dir)
6990 else if (element == EL_BALLOON)
6992 MovDir[x][y] = game.wind_direction;
6995 else if (element == EL_SPRING)
6997 if (MovDir[x][y] & MV_HORIZONTAL)
6999 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7000 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7002 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7003 ResetGfxAnimation(move_x, move_y);
7004 TEST_DrawLevelField(move_x, move_y);
7006 MovDir[x][y] = back_dir;
7008 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7009 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7010 MovDir[x][y] = MV_NONE;
7015 else if (element == EL_ROBOT ||
7016 element == EL_SATELLITE ||
7017 element == EL_PENGUIN ||
7018 element == EL_EMC_ANDROID)
7020 int attr_x = -1, attr_y = -1;
7022 if (game.all_players_gone)
7024 attr_x = game.exit_x;
7025 attr_y = game.exit_y;
7031 for (i = 0; i < MAX_PLAYERS; i++)
7033 struct PlayerInfo *player = &stored_player[i];
7034 int jx = player->jx, jy = player->jy;
7036 if (!player->active)
7040 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7048 if (element == EL_ROBOT &&
7049 game.robot_wheel_x >= 0 &&
7050 game.robot_wheel_y >= 0 &&
7051 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7052 game.engine_version < VERSION_IDENT(3,1,0,0)))
7054 attr_x = game.robot_wheel_x;
7055 attr_y = game.robot_wheel_y;
7058 if (element == EL_PENGUIN)
7061 static int xy[4][2] =
7069 for (i = 0; i < NUM_DIRECTIONS; i++)
7071 int ex = x + xy[i][0];
7072 int ey = y + xy[i][1];
7074 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7075 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7076 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7077 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7086 MovDir[x][y] = MV_NONE;
7088 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7089 else if (attr_x > x)
7090 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7092 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7093 else if (attr_y > y)
7094 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7096 if (element == EL_ROBOT)
7100 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7101 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7102 Moving2Blocked(x, y, &newx, &newy);
7104 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7105 MovDelay[x][y] = 8 + 8 * !RND(3);
7107 MovDelay[x][y] = 16;
7109 else if (element == EL_PENGUIN)
7115 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7117 boolean first_horiz = RND(2);
7118 int new_move_dir = MovDir[x][y];
7121 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7122 Moving2Blocked(x, y, &newx, &newy);
7124 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7128 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7129 Moving2Blocked(x, y, &newx, &newy);
7131 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7134 MovDir[x][y] = old_move_dir;
7138 else if (element == EL_SATELLITE)
7144 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7146 boolean first_horiz = RND(2);
7147 int new_move_dir = MovDir[x][y];
7150 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7151 Moving2Blocked(x, y, &newx, &newy);
7153 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7157 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7158 Moving2Blocked(x, y, &newx, &newy);
7160 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7163 MovDir[x][y] = old_move_dir;
7167 else if (element == EL_EMC_ANDROID)
7169 static int check_pos[16] =
7171 -1, // 0 => (invalid)
7174 -1, // 3 => (invalid)
7176 0, // 5 => MV_LEFT | MV_UP
7177 2, // 6 => MV_RIGHT | MV_UP
7178 -1, // 7 => (invalid)
7180 6, // 9 => MV_LEFT | MV_DOWN
7181 4, // 10 => MV_RIGHT | MV_DOWN
7182 -1, // 11 => (invalid)
7183 -1, // 12 => (invalid)
7184 -1, // 13 => (invalid)
7185 -1, // 14 => (invalid)
7186 -1, // 15 => (invalid)
7194 { -1, -1, MV_LEFT | MV_UP },
7196 { +1, -1, MV_RIGHT | MV_UP },
7197 { +1, 0, MV_RIGHT },
7198 { +1, +1, MV_RIGHT | MV_DOWN },
7200 { -1, +1, MV_LEFT | MV_DOWN },
7203 int start_pos, check_order;
7204 boolean can_clone = FALSE;
7207 // check if there is any free field around current position
7208 for (i = 0; i < 8; i++)
7210 int newx = x + check_xy[i].dx;
7211 int newy = y + check_xy[i].dy;
7213 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7221 if (can_clone) // randomly find an element to clone
7225 start_pos = check_pos[RND(8)];
7226 check_order = (RND(2) ? -1 : +1);
7228 for (i = 0; i < 8; i++)
7230 int pos_raw = start_pos + i * check_order;
7231 int pos = (pos_raw + 8) % 8;
7232 int newx = x + check_xy[pos].dx;
7233 int newy = y + check_xy[pos].dy;
7235 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7237 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7238 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7240 Store[x][y] = Tile[newx][newy];
7249 if (can_clone) // randomly find a direction to move
7253 start_pos = check_pos[RND(8)];
7254 check_order = (RND(2) ? -1 : +1);
7256 for (i = 0; i < 8; i++)
7258 int pos_raw = start_pos + i * check_order;
7259 int pos = (pos_raw + 8) % 8;
7260 int newx = x + check_xy[pos].dx;
7261 int newy = y + check_xy[pos].dy;
7262 int new_move_dir = check_xy[pos].dir;
7264 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7266 MovDir[x][y] = new_move_dir;
7267 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7276 if (can_clone) // cloning and moving successful
7279 // cannot clone -- try to move towards player
7281 start_pos = check_pos[MovDir[x][y] & 0x0f];
7282 check_order = (RND(2) ? -1 : +1);
7284 for (i = 0; i < 3; i++)
7286 // first check start_pos, then previous/next or (next/previous) pos
7287 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7288 int pos = (pos_raw + 8) % 8;
7289 int newx = x + check_xy[pos].dx;
7290 int newy = y + check_xy[pos].dy;
7291 int new_move_dir = check_xy[pos].dir;
7293 if (IS_PLAYER(newx, newy))
7296 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7298 MovDir[x][y] = new_move_dir;
7299 MovDelay[x][y] = level.android_move_time * 8 + 1;
7306 else if (move_pattern == MV_TURNING_LEFT ||
7307 move_pattern == MV_TURNING_RIGHT ||
7308 move_pattern == MV_TURNING_LEFT_RIGHT ||
7309 move_pattern == MV_TURNING_RIGHT_LEFT ||
7310 move_pattern == MV_TURNING_RANDOM ||
7311 move_pattern == MV_ALL_DIRECTIONS)
7313 boolean can_turn_left =
7314 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7315 boolean can_turn_right =
7316 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7318 if (element_info[element].move_stepsize == 0) // "not moving"
7321 if (move_pattern == MV_TURNING_LEFT)
7322 MovDir[x][y] = left_dir;
7323 else if (move_pattern == MV_TURNING_RIGHT)
7324 MovDir[x][y] = right_dir;
7325 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7326 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7327 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7328 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7329 else if (move_pattern == MV_TURNING_RANDOM)
7330 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7331 can_turn_right && !can_turn_left ? right_dir :
7332 RND(2) ? left_dir : right_dir);
7333 else if (can_turn_left && can_turn_right)
7334 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7335 else if (can_turn_left)
7336 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7337 else if (can_turn_right)
7338 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7340 MovDir[x][y] = back_dir;
7342 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7344 else if (move_pattern == MV_HORIZONTAL ||
7345 move_pattern == MV_VERTICAL)
7347 if (move_pattern & old_move_dir)
7348 MovDir[x][y] = back_dir;
7349 else if (move_pattern == MV_HORIZONTAL)
7350 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7351 else if (move_pattern == MV_VERTICAL)
7352 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7354 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7356 else if (move_pattern & MV_ANY_DIRECTION)
7358 MovDir[x][y] = move_pattern;
7359 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7361 else if (move_pattern & MV_WIND_DIRECTION)
7363 MovDir[x][y] = game.wind_direction;
7364 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7366 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7368 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7369 MovDir[x][y] = left_dir;
7370 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7371 MovDir[x][y] = right_dir;
7373 if (MovDir[x][y] != old_move_dir)
7374 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7376 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7378 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7379 MovDir[x][y] = right_dir;
7380 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7381 MovDir[x][y] = left_dir;
7383 if (MovDir[x][y] != old_move_dir)
7384 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7386 else if (move_pattern == MV_TOWARDS_PLAYER ||
7387 move_pattern == MV_AWAY_FROM_PLAYER)
7389 int attr_x = -1, attr_y = -1;
7391 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7393 if (game.all_players_gone)
7395 attr_x = game.exit_x;
7396 attr_y = game.exit_y;
7402 for (i = 0; i < MAX_PLAYERS; i++)
7404 struct PlayerInfo *player = &stored_player[i];
7405 int jx = player->jx, jy = player->jy;
7407 if (!player->active)
7411 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7419 MovDir[x][y] = MV_NONE;
7421 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7422 else if (attr_x > x)
7423 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7425 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7426 else if (attr_y > y)
7427 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7429 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7431 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7433 boolean first_horiz = RND(2);
7434 int new_move_dir = MovDir[x][y];
7436 if (element_info[element].move_stepsize == 0) // "not moving"
7438 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7439 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7445 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7446 Moving2Blocked(x, y, &newx, &newy);
7448 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7452 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7453 Moving2Blocked(x, y, &newx, &newy);
7455 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7458 MovDir[x][y] = old_move_dir;
7461 else if (move_pattern == MV_WHEN_PUSHED ||
7462 move_pattern == MV_WHEN_DROPPED)
7464 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7465 MovDir[x][y] = MV_NONE;
7469 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7471 static int test_xy[7][2] =
7481 static int test_dir[7] =
7491 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7492 int move_preference = -1000000; // start with very low preference
7493 int new_move_dir = MV_NONE;
7494 int start_test = RND(4);
7497 for (i = 0; i < NUM_DIRECTIONS; i++)
7499 int move_dir = test_dir[start_test + i];
7500 int move_dir_preference;
7502 xx = x + test_xy[start_test + i][0];
7503 yy = y + test_xy[start_test + i][1];
7505 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7506 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7508 new_move_dir = move_dir;
7513 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7516 move_dir_preference = -1 * RunnerVisit[xx][yy];
7517 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7518 move_dir_preference = PlayerVisit[xx][yy];
7520 if (move_dir_preference > move_preference)
7522 // prefer field that has not been visited for the longest time
7523 move_preference = move_dir_preference;
7524 new_move_dir = move_dir;
7526 else if (move_dir_preference == move_preference &&
7527 move_dir == old_move_dir)
7529 // prefer last direction when all directions are preferred equally
7530 move_preference = move_dir_preference;
7531 new_move_dir = move_dir;
7535 MovDir[x][y] = new_move_dir;
7536 if (old_move_dir != new_move_dir)
7537 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7541 static void TurnRound(int x, int y)
7543 int direction = MovDir[x][y];
7547 GfxDir[x][y] = MovDir[x][y];
7549 if (direction != MovDir[x][y])
7553 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7555 ResetGfxFrame(x, y);
7558 static boolean JustBeingPushed(int x, int y)
7562 for (i = 0; i < MAX_PLAYERS; i++)
7564 struct PlayerInfo *player = &stored_player[i];
7566 if (player->active && player->is_pushing && player->MovPos)
7568 int next_jx = player->jx + (player->jx - player->last_jx);
7569 int next_jy = player->jy + (player->jy - player->last_jy);
7571 if (x == next_jx && y == next_jy)
7579 static void StartMoving(int x, int y)
7581 boolean started_moving = FALSE; // some elements can fall _and_ move
7582 int element = Tile[x][y];
7587 if (MovDelay[x][y] == 0)
7588 GfxAction[x][y] = ACTION_DEFAULT;
7590 if (CAN_FALL(element) && y < lev_fieldy - 1)
7592 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7593 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7594 if (JustBeingPushed(x, y))
7597 if (element == EL_QUICKSAND_FULL)
7599 if (IS_FREE(x, y + 1))
7601 InitMovingField(x, y, MV_DOWN);
7602 started_moving = TRUE;
7604 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7605 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7606 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7607 Store[x][y] = EL_ROCK;
7609 Store[x][y] = EL_ROCK;
7612 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7614 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7616 if (!MovDelay[x][y])
7618 MovDelay[x][y] = TILEY + 1;
7620 ResetGfxAnimation(x, y);
7621 ResetGfxAnimation(x, y + 1);
7626 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7627 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7634 Tile[x][y] = EL_QUICKSAND_EMPTY;
7635 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7636 Store[x][y + 1] = Store[x][y];
7639 PlayLevelSoundAction(x, y, ACTION_FILLING);
7641 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7643 if (!MovDelay[x][y])
7645 MovDelay[x][y] = TILEY + 1;
7647 ResetGfxAnimation(x, y);
7648 ResetGfxAnimation(x, y + 1);
7653 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7654 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7661 Tile[x][y] = EL_QUICKSAND_EMPTY;
7662 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7663 Store[x][y + 1] = Store[x][y];
7666 PlayLevelSoundAction(x, y, ACTION_FILLING);
7669 else if (element == EL_QUICKSAND_FAST_FULL)
7671 if (IS_FREE(x, y + 1))
7673 InitMovingField(x, y, MV_DOWN);
7674 started_moving = TRUE;
7676 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7677 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7678 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7679 Store[x][y] = EL_ROCK;
7681 Store[x][y] = EL_ROCK;
7684 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7686 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7688 if (!MovDelay[x][y])
7690 MovDelay[x][y] = TILEY + 1;
7692 ResetGfxAnimation(x, y);
7693 ResetGfxAnimation(x, y + 1);
7698 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7699 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7706 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7707 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7708 Store[x][y + 1] = Store[x][y];
7711 PlayLevelSoundAction(x, y, ACTION_FILLING);
7713 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7715 if (!MovDelay[x][y])
7717 MovDelay[x][y] = TILEY + 1;
7719 ResetGfxAnimation(x, y);
7720 ResetGfxAnimation(x, y + 1);
7725 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7726 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7733 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7734 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7735 Store[x][y + 1] = Store[x][y];
7738 PlayLevelSoundAction(x, y, ACTION_FILLING);
7741 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7742 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7744 InitMovingField(x, y, MV_DOWN);
7745 started_moving = TRUE;
7747 Tile[x][y] = EL_QUICKSAND_FILLING;
7748 Store[x][y] = element;
7750 PlayLevelSoundAction(x, y, ACTION_FILLING);
7752 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7753 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7755 InitMovingField(x, y, MV_DOWN);
7756 started_moving = TRUE;
7758 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7759 Store[x][y] = element;
7761 PlayLevelSoundAction(x, y, ACTION_FILLING);
7763 else if (element == EL_MAGIC_WALL_FULL)
7765 if (IS_FREE(x, y + 1))
7767 InitMovingField(x, y, MV_DOWN);
7768 started_moving = TRUE;
7770 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7771 Store[x][y] = EL_CHANGED(Store[x][y]);
7773 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7775 if (!MovDelay[x][y])
7776 MovDelay[x][y] = TILEY / 4 + 1;
7785 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7786 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7787 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7791 else if (element == EL_BD_MAGIC_WALL_FULL)
7793 if (IS_FREE(x, y + 1))
7795 InitMovingField(x, y, MV_DOWN);
7796 started_moving = TRUE;
7798 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7799 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7801 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7803 if (!MovDelay[x][y])
7804 MovDelay[x][y] = TILEY / 4 + 1;
7813 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7814 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7815 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7819 else if (element == EL_DC_MAGIC_WALL_FULL)
7821 if (IS_FREE(x, y + 1))
7823 InitMovingField(x, y, MV_DOWN);
7824 started_moving = TRUE;
7826 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7827 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7829 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7831 if (!MovDelay[x][y])
7832 MovDelay[x][y] = TILEY / 4 + 1;
7841 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7842 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7843 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7847 else if ((CAN_PASS_MAGIC_WALL(element) &&
7848 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7849 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7850 (CAN_PASS_DC_MAGIC_WALL(element) &&
7851 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7854 InitMovingField(x, y, MV_DOWN);
7855 started_moving = TRUE;
7858 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7859 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7860 EL_DC_MAGIC_WALL_FILLING);
7861 Store[x][y] = element;
7863 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7865 SplashAcid(x, y + 1);
7867 InitMovingField(x, y, MV_DOWN);
7868 started_moving = TRUE;
7870 Store[x][y] = EL_ACID;
7873 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7874 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7875 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7876 CAN_FALL(element) && WasJustFalling[x][y] &&
7877 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7879 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7880 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7881 (Tile[x][y + 1] == EL_BLOCKED)))
7883 /* this is needed for a special case not covered by calling "Impact()"
7884 from "ContinueMoving()": if an element moves to a tile directly below
7885 another element which was just falling on that tile (which was empty
7886 in the previous frame), the falling element above would just stop
7887 instead of smashing the element below (in previous version, the above
7888 element was just checked for "moving" instead of "falling", resulting
7889 in incorrect smashes caused by horizontal movement of the above
7890 element; also, the case of the player being the element to smash was
7891 simply not covered here... :-/ ) */
7893 CheckCollision[x][y] = 0;
7894 CheckImpact[x][y] = 0;
7898 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7900 if (MovDir[x][y] == MV_NONE)
7902 InitMovingField(x, y, MV_DOWN);
7903 started_moving = TRUE;
7906 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7908 if (WasJustFalling[x][y]) // prevent animation from being restarted
7909 MovDir[x][y] = MV_DOWN;
7911 InitMovingField(x, y, MV_DOWN);
7912 started_moving = TRUE;
7914 else if (element == EL_AMOEBA_DROP)
7916 Tile[x][y] = EL_AMOEBA_GROWING;
7917 Store[x][y] = EL_AMOEBA_WET;
7919 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7920 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7921 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7922 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7924 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7925 (IS_FREE(x - 1, y + 1) ||
7926 Tile[x - 1][y + 1] == EL_ACID));
7927 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7928 (IS_FREE(x + 1, y + 1) ||
7929 Tile[x + 1][y + 1] == EL_ACID));
7930 boolean can_fall_any = (can_fall_left || can_fall_right);
7931 boolean can_fall_both = (can_fall_left && can_fall_right);
7932 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7934 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7936 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7937 can_fall_right = FALSE;
7938 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7939 can_fall_left = FALSE;
7940 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7941 can_fall_right = FALSE;
7942 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7943 can_fall_left = FALSE;
7945 can_fall_any = (can_fall_left || can_fall_right);
7946 can_fall_both = FALSE;
7951 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7952 can_fall_right = FALSE; // slip down on left side
7954 can_fall_left = !(can_fall_right = RND(2));
7956 can_fall_both = FALSE;
7961 // if not determined otherwise, prefer left side for slipping down
7962 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7963 started_moving = TRUE;
7966 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
7968 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7969 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7970 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
7971 int belt_dir = game.belt_dir[belt_nr];
7973 if ((belt_dir == MV_LEFT && left_is_free) ||
7974 (belt_dir == MV_RIGHT && right_is_free))
7976 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7978 InitMovingField(x, y, belt_dir);
7979 started_moving = TRUE;
7981 Pushed[x][y] = TRUE;
7982 Pushed[nextx][y] = TRUE;
7984 GfxAction[x][y] = ACTION_DEFAULT;
7988 MovDir[x][y] = 0; // if element was moving, stop it
7993 // not "else if" because of elements that can fall and move (EL_SPRING)
7994 if (CAN_MOVE(element) && !started_moving)
7996 int move_pattern = element_info[element].move_pattern;
7999 Moving2Blocked(x, y, &newx, &newy);
8001 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8004 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8005 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8007 WasJustMoving[x][y] = 0;
8008 CheckCollision[x][y] = 0;
8010 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8012 if (Tile[x][y] != element) // element has changed
8016 if (!MovDelay[x][y]) // start new movement phase
8018 // all objects that can change their move direction after each step
8019 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8021 if (element != EL_YAMYAM &&
8022 element != EL_DARK_YAMYAM &&
8023 element != EL_PACMAN &&
8024 !(move_pattern & MV_ANY_DIRECTION) &&
8025 move_pattern != MV_TURNING_LEFT &&
8026 move_pattern != MV_TURNING_RIGHT &&
8027 move_pattern != MV_TURNING_LEFT_RIGHT &&
8028 move_pattern != MV_TURNING_RIGHT_LEFT &&
8029 move_pattern != MV_TURNING_RANDOM)
8033 if (MovDelay[x][y] && (element == EL_BUG ||
8034 element == EL_SPACESHIP ||
8035 element == EL_SP_SNIKSNAK ||
8036 element == EL_SP_ELECTRON ||
8037 element == EL_MOLE))
8038 TEST_DrawLevelField(x, y);
8042 if (MovDelay[x][y]) // wait some time before next movement
8046 if (element == EL_ROBOT ||
8047 element == EL_YAMYAM ||
8048 element == EL_DARK_YAMYAM)
8050 DrawLevelElementAnimationIfNeeded(x, y, element);
8051 PlayLevelSoundAction(x, y, ACTION_WAITING);
8053 else if (element == EL_SP_ELECTRON)
8054 DrawLevelElementAnimationIfNeeded(x, y, element);
8055 else if (element == EL_DRAGON)
8058 int dir = MovDir[x][y];
8059 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8060 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8061 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8062 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8063 dir == MV_UP ? IMG_FLAMES_1_UP :
8064 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8065 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8067 GfxAction[x][y] = ACTION_ATTACKING;
8069 if (IS_PLAYER(x, y))
8070 DrawPlayerField(x, y);
8072 TEST_DrawLevelField(x, y);
8074 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8076 for (i = 1; i <= 3; i++)
8078 int xx = x + i * dx;
8079 int yy = y + i * dy;
8080 int sx = SCREENX(xx);
8081 int sy = SCREENY(yy);
8082 int flame_graphic = graphic + (i - 1);
8084 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8089 int flamed = MovingOrBlocked2Element(xx, yy);
8091 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8094 RemoveMovingField(xx, yy);
8096 ChangeDelay[xx][yy] = 0;
8098 Tile[xx][yy] = EL_FLAMES;
8100 if (IN_SCR_FIELD(sx, sy))
8102 TEST_DrawLevelFieldCrumbled(xx, yy);
8103 DrawGraphic(sx, sy, flame_graphic, frame);
8108 if (Tile[xx][yy] == EL_FLAMES)
8109 Tile[xx][yy] = EL_EMPTY;
8110 TEST_DrawLevelField(xx, yy);
8115 if (MovDelay[x][y]) // element still has to wait some time
8117 PlayLevelSoundAction(x, y, ACTION_WAITING);
8123 // now make next step
8125 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8127 if (DONT_COLLIDE_WITH(element) &&
8128 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8129 !PLAYER_ENEMY_PROTECTED(newx, newy))
8131 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8136 else if (CAN_MOVE_INTO_ACID(element) &&
8137 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8138 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8139 (MovDir[x][y] == MV_DOWN ||
8140 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8142 SplashAcid(newx, newy);
8143 Store[x][y] = EL_ACID;
8145 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8147 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8148 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8149 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8150 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8153 TEST_DrawLevelField(x, y);
8155 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8156 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8157 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8159 game.friends_still_needed--;
8160 if (!game.friends_still_needed &&
8162 game.all_players_gone)
8167 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8169 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8170 TEST_DrawLevelField(newx, newy);
8172 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8174 else if (!IS_FREE(newx, newy))
8176 GfxAction[x][y] = ACTION_WAITING;
8178 if (IS_PLAYER(x, y))
8179 DrawPlayerField(x, y);
8181 TEST_DrawLevelField(x, y);
8186 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8188 if (IS_FOOD_PIG(Tile[newx][newy]))
8190 if (IS_MOVING(newx, newy))
8191 RemoveMovingField(newx, newy);
8194 Tile[newx][newy] = EL_EMPTY;
8195 TEST_DrawLevelField(newx, newy);
8198 PlayLevelSound(x, y, SND_PIG_DIGGING);
8200 else if (!IS_FREE(newx, newy))
8202 if (IS_PLAYER(x, y))
8203 DrawPlayerField(x, y);
8205 TEST_DrawLevelField(x, y);
8210 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8212 if (Store[x][y] != EL_EMPTY)
8214 boolean can_clone = FALSE;
8217 // check if element to clone is still there
8218 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8220 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8228 // cannot clone or target field not free anymore -- do not clone
8229 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8230 Store[x][y] = EL_EMPTY;
8233 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8235 if (IS_MV_DIAGONAL(MovDir[x][y]))
8237 int diagonal_move_dir = MovDir[x][y];
8238 int stored = Store[x][y];
8239 int change_delay = 8;
8242 // android is moving diagonally
8244 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8246 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8247 GfxElement[x][y] = EL_EMC_ANDROID;
8248 GfxAction[x][y] = ACTION_SHRINKING;
8249 GfxDir[x][y] = diagonal_move_dir;
8250 ChangeDelay[x][y] = change_delay;
8252 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8255 DrawLevelGraphicAnimation(x, y, graphic);
8256 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8258 if (Tile[newx][newy] == EL_ACID)
8260 SplashAcid(newx, newy);
8265 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8267 Store[newx][newy] = EL_EMC_ANDROID;
8268 GfxElement[newx][newy] = EL_EMC_ANDROID;
8269 GfxAction[newx][newy] = ACTION_GROWING;
8270 GfxDir[newx][newy] = diagonal_move_dir;
8271 ChangeDelay[newx][newy] = change_delay;
8273 graphic = el_act_dir2img(GfxElement[newx][newy],
8274 GfxAction[newx][newy], GfxDir[newx][newy]);
8276 DrawLevelGraphicAnimation(newx, newy, graphic);
8277 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8283 Tile[newx][newy] = EL_EMPTY;
8284 TEST_DrawLevelField(newx, newy);
8286 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8289 else if (!IS_FREE(newx, newy))
8294 else if (IS_CUSTOM_ELEMENT(element) &&
8295 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8297 if (!DigFieldByCE(newx, newy, element))
8300 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8302 RunnerVisit[x][y] = FrameCounter;
8303 PlayerVisit[x][y] /= 8; // expire player visit path
8306 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8308 if (!IS_FREE(newx, newy))
8310 if (IS_PLAYER(x, y))
8311 DrawPlayerField(x, y);
8313 TEST_DrawLevelField(x, y);
8319 boolean wanna_flame = !RND(10);
8320 int dx = newx - x, dy = newy - y;
8321 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8322 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8323 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8324 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8325 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8326 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8329 IS_CLASSIC_ENEMY(element1) ||
8330 IS_CLASSIC_ENEMY(element2)) &&
8331 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8332 element1 != EL_FLAMES && element2 != EL_FLAMES)
8334 ResetGfxAnimation(x, y);
8335 GfxAction[x][y] = ACTION_ATTACKING;
8337 if (IS_PLAYER(x, y))
8338 DrawPlayerField(x, y);
8340 TEST_DrawLevelField(x, y);
8342 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8344 MovDelay[x][y] = 50;
8346 Tile[newx][newy] = EL_FLAMES;
8347 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8348 Tile[newx1][newy1] = EL_FLAMES;
8349 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8350 Tile[newx2][newy2] = EL_FLAMES;
8356 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8357 Tile[newx][newy] == EL_DIAMOND)
8359 if (IS_MOVING(newx, newy))
8360 RemoveMovingField(newx, newy);
8363 Tile[newx][newy] = EL_EMPTY;
8364 TEST_DrawLevelField(newx, newy);
8367 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8369 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8370 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8372 if (AmoebaNr[newx][newy])
8374 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8375 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8376 Tile[newx][newy] == EL_BD_AMOEBA)
8377 AmoebaCnt[AmoebaNr[newx][newy]]--;
8380 if (IS_MOVING(newx, newy))
8382 RemoveMovingField(newx, newy);
8386 Tile[newx][newy] = EL_EMPTY;
8387 TEST_DrawLevelField(newx, newy);
8390 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8392 else if ((element == EL_PACMAN || element == EL_MOLE)
8393 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8395 if (AmoebaNr[newx][newy])
8397 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8398 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8399 Tile[newx][newy] == EL_BD_AMOEBA)
8400 AmoebaCnt[AmoebaNr[newx][newy]]--;
8403 if (element == EL_MOLE)
8405 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8406 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8408 ResetGfxAnimation(x, y);
8409 GfxAction[x][y] = ACTION_DIGGING;
8410 TEST_DrawLevelField(x, y);
8412 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8414 return; // wait for shrinking amoeba
8416 else // element == EL_PACMAN
8418 Tile[newx][newy] = EL_EMPTY;
8419 TEST_DrawLevelField(newx, newy);
8420 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8423 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8424 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8425 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8427 // wait for shrinking amoeba to completely disappear
8430 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8432 // object was running against a wall
8436 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8437 DrawLevelElementAnimation(x, y, element);
8439 if (DONT_TOUCH(element))
8440 TestIfBadThingTouchesPlayer(x, y);
8445 InitMovingField(x, y, MovDir[x][y]);
8447 PlayLevelSoundAction(x, y, ACTION_MOVING);
8451 ContinueMoving(x, y);
8454 void ContinueMoving(int x, int y)
8456 int element = Tile[x][y];
8457 struct ElementInfo *ei = &element_info[element];
8458 int direction = MovDir[x][y];
8459 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8460 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8461 int newx = x + dx, newy = y + dy;
8462 int stored = Store[x][y];
8463 int stored_new = Store[newx][newy];
8464 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8465 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8466 boolean last_line = (newy == lev_fieldy - 1);
8468 MovPos[x][y] += getElementMoveStepsize(x, y);
8470 if (pushed_by_player) // special case: moving object pushed by player
8471 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8473 if (ABS(MovPos[x][y]) < TILEX)
8475 TEST_DrawLevelField(x, y);
8477 return; // element is still moving
8480 // element reached destination field
8482 Tile[x][y] = EL_EMPTY;
8483 Tile[newx][newy] = element;
8484 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8486 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8488 element = Tile[newx][newy] = EL_ACID;
8490 else if (element == EL_MOLE)
8492 Tile[x][y] = EL_SAND;
8494 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8496 else if (element == EL_QUICKSAND_FILLING)
8498 element = Tile[newx][newy] = get_next_element(element);
8499 Store[newx][newy] = Store[x][y];
8501 else if (element == EL_QUICKSAND_EMPTYING)
8503 Tile[x][y] = get_next_element(element);
8504 element = Tile[newx][newy] = Store[x][y];
8506 else if (element == EL_QUICKSAND_FAST_FILLING)
8508 element = Tile[newx][newy] = get_next_element(element);
8509 Store[newx][newy] = Store[x][y];
8511 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8513 Tile[x][y] = get_next_element(element);
8514 element = Tile[newx][newy] = Store[x][y];
8516 else if (element == EL_MAGIC_WALL_FILLING)
8518 element = Tile[newx][newy] = get_next_element(element);
8519 if (!game.magic_wall_active)
8520 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8521 Store[newx][newy] = Store[x][y];
8523 else if (element == EL_MAGIC_WALL_EMPTYING)
8525 Tile[x][y] = get_next_element(element);
8526 if (!game.magic_wall_active)
8527 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8528 element = Tile[newx][newy] = Store[x][y];
8530 InitField(newx, newy, FALSE);
8532 else if (element == EL_BD_MAGIC_WALL_FILLING)
8534 element = Tile[newx][newy] = get_next_element(element);
8535 if (!game.magic_wall_active)
8536 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8537 Store[newx][newy] = Store[x][y];
8539 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8541 Tile[x][y] = get_next_element(element);
8542 if (!game.magic_wall_active)
8543 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8544 element = Tile[newx][newy] = Store[x][y];
8546 InitField(newx, newy, FALSE);
8548 else if (element == EL_DC_MAGIC_WALL_FILLING)
8550 element = Tile[newx][newy] = get_next_element(element);
8551 if (!game.magic_wall_active)
8552 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8553 Store[newx][newy] = Store[x][y];
8555 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8557 Tile[x][y] = get_next_element(element);
8558 if (!game.magic_wall_active)
8559 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8560 element = Tile[newx][newy] = Store[x][y];
8562 InitField(newx, newy, FALSE);
8564 else if (element == EL_AMOEBA_DROPPING)
8566 Tile[x][y] = get_next_element(element);
8567 element = Tile[newx][newy] = Store[x][y];
8569 else if (element == EL_SOKOBAN_OBJECT)
8572 Tile[x][y] = Back[x][y];
8574 if (Back[newx][newy])
8575 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8577 Back[x][y] = Back[newx][newy] = 0;
8580 Store[x][y] = EL_EMPTY;
8585 MovDelay[newx][newy] = 0;
8587 if (CAN_CHANGE_OR_HAS_ACTION(element))
8589 // copy element change control values to new field
8590 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8591 ChangePage[newx][newy] = ChangePage[x][y];
8592 ChangeCount[newx][newy] = ChangeCount[x][y];
8593 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8596 CustomValue[newx][newy] = CustomValue[x][y];
8598 ChangeDelay[x][y] = 0;
8599 ChangePage[x][y] = -1;
8600 ChangeCount[x][y] = 0;
8601 ChangeEvent[x][y] = -1;
8603 CustomValue[x][y] = 0;
8605 // copy animation control values to new field
8606 GfxFrame[newx][newy] = GfxFrame[x][y];
8607 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8608 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8609 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8611 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8613 // some elements can leave other elements behind after moving
8614 if (ei->move_leave_element != EL_EMPTY &&
8615 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8616 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8618 int move_leave_element = ei->move_leave_element;
8620 // this makes it possible to leave the removed element again
8621 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8622 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8624 Tile[x][y] = move_leave_element;
8626 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8627 MovDir[x][y] = direction;
8629 InitField(x, y, FALSE);
8631 if (GFX_CRUMBLED(Tile[x][y]))
8632 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8634 if (ELEM_IS_PLAYER(move_leave_element))
8635 RelocatePlayer(x, y, move_leave_element);
8638 // do this after checking for left-behind element
8639 ResetGfxAnimation(x, y); // reset animation values for old field
8641 if (!CAN_MOVE(element) ||
8642 (CAN_FALL(element) && direction == MV_DOWN &&
8643 (element == EL_SPRING ||
8644 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8645 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8646 GfxDir[x][y] = MovDir[newx][newy] = 0;
8648 TEST_DrawLevelField(x, y);
8649 TEST_DrawLevelField(newx, newy);
8651 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8653 // prevent pushed element from moving on in pushed direction
8654 if (pushed_by_player && CAN_MOVE(element) &&
8655 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8656 !(element_info[element].move_pattern & direction))
8657 TurnRound(newx, newy);
8659 // prevent elements on conveyor belt from moving on in last direction
8660 if (pushed_by_conveyor && CAN_FALL(element) &&
8661 direction & MV_HORIZONTAL)
8662 MovDir[newx][newy] = 0;
8664 if (!pushed_by_player)
8666 int nextx = newx + dx, nexty = newy + dy;
8667 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8669 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8671 if (CAN_FALL(element) && direction == MV_DOWN)
8672 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8674 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8675 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8677 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8678 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8681 if (DONT_TOUCH(element)) // object may be nasty to player or others
8683 TestIfBadThingTouchesPlayer(newx, newy);
8684 TestIfBadThingTouchesFriend(newx, newy);
8686 if (!IS_CUSTOM_ELEMENT(element))
8687 TestIfBadThingTouchesOtherBadThing(newx, newy);
8689 else if (element == EL_PENGUIN)
8690 TestIfFriendTouchesBadThing(newx, newy);
8692 if (DONT_GET_HIT_BY(element))
8694 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8697 // give the player one last chance (one more frame) to move away
8698 if (CAN_FALL(element) && direction == MV_DOWN &&
8699 (last_line || (!IS_FREE(x, newy + 1) &&
8700 (!IS_PLAYER(x, newy + 1) ||
8701 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8704 if (pushed_by_player && !game.use_change_when_pushing_bug)
8706 int push_side = MV_DIR_OPPOSITE(direction);
8707 struct PlayerInfo *player = PLAYERINFO(x, y);
8709 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8710 player->index_bit, push_side);
8711 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8712 player->index_bit, push_side);
8715 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8716 MovDelay[newx][newy] = 1;
8718 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8720 TestIfElementTouchesCustomElement(x, y); // empty or new element
8721 TestIfElementHitsCustomElement(newx, newy, direction);
8722 TestIfPlayerTouchesCustomElement(newx, newy);
8723 TestIfElementTouchesCustomElement(newx, newy);
8725 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8726 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8727 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8728 MV_DIR_OPPOSITE(direction));
8731 int AmoebaNeighbourNr(int ax, int ay)
8734 int element = Tile[ax][ay];
8736 static int xy[4][2] =
8744 for (i = 0; i < NUM_DIRECTIONS; i++)
8746 int x = ax + xy[i][0];
8747 int y = ay + xy[i][1];
8749 if (!IN_LEV_FIELD(x, y))
8752 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8753 group_nr = AmoebaNr[x][y];
8759 static void AmoebaMerge(int ax, int ay)
8761 int i, x, y, xx, yy;
8762 int new_group_nr = AmoebaNr[ax][ay];
8763 static int xy[4][2] =
8771 if (new_group_nr == 0)
8774 for (i = 0; i < NUM_DIRECTIONS; i++)
8779 if (!IN_LEV_FIELD(x, y))
8782 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8783 Tile[x][y] == EL_BD_AMOEBA ||
8784 Tile[x][y] == EL_AMOEBA_DEAD) &&
8785 AmoebaNr[x][y] != new_group_nr)
8787 int old_group_nr = AmoebaNr[x][y];
8789 if (old_group_nr == 0)
8792 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8793 AmoebaCnt[old_group_nr] = 0;
8794 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8795 AmoebaCnt2[old_group_nr] = 0;
8797 SCAN_PLAYFIELD(xx, yy)
8799 if (AmoebaNr[xx][yy] == old_group_nr)
8800 AmoebaNr[xx][yy] = new_group_nr;
8806 void AmoebaToDiamond(int ax, int ay)
8810 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8812 int group_nr = AmoebaNr[ax][ay];
8817 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8818 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8824 SCAN_PLAYFIELD(x, y)
8826 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8829 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8833 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8834 SND_AMOEBA_TURNING_TO_GEM :
8835 SND_AMOEBA_TURNING_TO_ROCK));
8840 static int xy[4][2] =
8848 for (i = 0; i < NUM_DIRECTIONS; i++)
8853 if (!IN_LEV_FIELD(x, y))
8856 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8858 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8859 SND_AMOEBA_TURNING_TO_GEM :
8860 SND_AMOEBA_TURNING_TO_ROCK));
8867 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8870 int group_nr = AmoebaNr[ax][ay];
8871 boolean done = FALSE;
8876 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8877 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8883 SCAN_PLAYFIELD(x, y)
8885 if (AmoebaNr[x][y] == group_nr &&
8886 (Tile[x][y] == EL_AMOEBA_DEAD ||
8887 Tile[x][y] == EL_BD_AMOEBA ||
8888 Tile[x][y] == EL_AMOEBA_GROWING))
8891 Tile[x][y] = new_element;
8892 InitField(x, y, FALSE);
8893 TEST_DrawLevelField(x, y);
8899 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8900 SND_BD_AMOEBA_TURNING_TO_ROCK :
8901 SND_BD_AMOEBA_TURNING_TO_GEM));
8904 static void AmoebaGrowing(int x, int y)
8906 static unsigned int sound_delay = 0;
8907 static unsigned int sound_delay_value = 0;
8909 if (!MovDelay[x][y]) // start new growing cycle
8913 if (DelayReached(&sound_delay, sound_delay_value))
8915 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8916 sound_delay_value = 30;
8920 if (MovDelay[x][y]) // wait some time before growing bigger
8923 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8925 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8926 6 - MovDelay[x][y]);
8928 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8931 if (!MovDelay[x][y])
8933 Tile[x][y] = Store[x][y];
8935 TEST_DrawLevelField(x, y);
8940 static void AmoebaShrinking(int x, int y)
8942 static unsigned int sound_delay = 0;
8943 static unsigned int sound_delay_value = 0;
8945 if (!MovDelay[x][y]) // start new shrinking cycle
8949 if (DelayReached(&sound_delay, sound_delay_value))
8950 sound_delay_value = 30;
8953 if (MovDelay[x][y]) // wait some time before shrinking
8956 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8958 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8959 6 - MovDelay[x][y]);
8961 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8964 if (!MovDelay[x][y])
8966 Tile[x][y] = EL_EMPTY;
8967 TEST_DrawLevelField(x, y);
8969 // don't let mole enter this field in this cycle;
8970 // (give priority to objects falling to this field from above)
8976 static void AmoebaReproduce(int ax, int ay)
8979 int element = Tile[ax][ay];
8980 int graphic = el2img(element);
8981 int newax = ax, neway = ay;
8982 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8983 static int xy[4][2] =
8991 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8993 Tile[ax][ay] = EL_AMOEBA_DEAD;
8994 TEST_DrawLevelField(ax, ay);
8998 if (IS_ANIMATED(graphic))
8999 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9001 if (!MovDelay[ax][ay]) // start making new amoeba field
9002 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9004 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9007 if (MovDelay[ax][ay])
9011 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9014 int x = ax + xy[start][0];
9015 int y = ay + xy[start][1];
9017 if (!IN_LEV_FIELD(x, y))
9020 if (IS_FREE(x, y) ||
9021 CAN_GROW_INTO(Tile[x][y]) ||
9022 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9023 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9029 if (newax == ax && neway == ay)
9032 else // normal or "filled" (BD style) amoeba
9035 boolean waiting_for_player = FALSE;
9037 for (i = 0; i < NUM_DIRECTIONS; i++)
9039 int j = (start + i) % 4;
9040 int x = ax + xy[j][0];
9041 int y = ay + xy[j][1];
9043 if (!IN_LEV_FIELD(x, y))
9046 if (IS_FREE(x, y) ||
9047 CAN_GROW_INTO(Tile[x][y]) ||
9048 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9049 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9055 else if (IS_PLAYER(x, y))
9056 waiting_for_player = TRUE;
9059 if (newax == ax && neway == ay) // amoeba cannot grow
9061 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9063 Tile[ax][ay] = EL_AMOEBA_DEAD;
9064 TEST_DrawLevelField(ax, ay);
9065 AmoebaCnt[AmoebaNr[ax][ay]]--;
9067 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9069 if (element == EL_AMOEBA_FULL)
9070 AmoebaToDiamond(ax, ay);
9071 else if (element == EL_BD_AMOEBA)
9072 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9077 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9079 // amoeba gets larger by growing in some direction
9081 int new_group_nr = AmoebaNr[ax][ay];
9084 if (new_group_nr == 0)
9086 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9088 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9094 AmoebaNr[newax][neway] = new_group_nr;
9095 AmoebaCnt[new_group_nr]++;
9096 AmoebaCnt2[new_group_nr]++;
9098 // if amoeba touches other amoeba(s) after growing, unify them
9099 AmoebaMerge(newax, neway);
9101 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9103 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9109 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9110 (neway == lev_fieldy - 1 && newax != ax))
9112 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9113 Store[newax][neway] = element;
9115 else if (neway == ay || element == EL_EMC_DRIPPER)
9117 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9119 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9123 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9124 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9125 Store[ax][ay] = EL_AMOEBA_DROP;
9126 ContinueMoving(ax, ay);
9130 TEST_DrawLevelField(newax, neway);
9133 static void Life(int ax, int ay)
9137 int element = Tile[ax][ay];
9138 int graphic = el2img(element);
9139 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9141 boolean changed = FALSE;
9143 if (IS_ANIMATED(graphic))
9144 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9149 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9150 MovDelay[ax][ay] = life_time;
9152 if (MovDelay[ax][ay]) // wait some time before next cycle
9155 if (MovDelay[ax][ay])
9159 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9161 int xx = ax+x1, yy = ay+y1;
9162 int old_element = Tile[xx][yy];
9163 int num_neighbours = 0;
9165 if (!IN_LEV_FIELD(xx, yy))
9168 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9170 int x = xx+x2, y = yy+y2;
9172 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9175 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9176 boolean is_neighbour = FALSE;
9178 if (level.use_life_bugs)
9180 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9181 (IS_FREE(x, y) && Stop[x][y]));
9184 (Last[x][y] == element || is_player_cell);
9190 boolean is_free = FALSE;
9192 if (level.use_life_bugs)
9193 is_free = (IS_FREE(xx, yy));
9195 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9197 if (xx == ax && yy == ay) // field in the middle
9199 if (num_neighbours < life_parameter[0] ||
9200 num_neighbours > life_parameter[1])
9202 Tile[xx][yy] = EL_EMPTY;
9203 if (Tile[xx][yy] != old_element)
9204 TEST_DrawLevelField(xx, yy);
9205 Stop[xx][yy] = TRUE;
9209 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9210 { // free border field
9211 if (num_neighbours >= life_parameter[2] &&
9212 num_neighbours <= life_parameter[3])
9214 Tile[xx][yy] = element;
9215 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9216 if (Tile[xx][yy] != old_element)
9217 TEST_DrawLevelField(xx, yy);
9218 Stop[xx][yy] = TRUE;
9225 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9226 SND_GAME_OF_LIFE_GROWING);
9229 static void InitRobotWheel(int x, int y)
9231 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9234 static void RunRobotWheel(int x, int y)
9236 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9239 static void StopRobotWheel(int x, int y)
9241 if (game.robot_wheel_x == x &&
9242 game.robot_wheel_y == y)
9244 game.robot_wheel_x = -1;
9245 game.robot_wheel_y = -1;
9246 game.robot_wheel_active = FALSE;
9250 static void InitTimegateWheel(int x, int y)
9252 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9255 static void RunTimegateWheel(int x, int y)
9257 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9260 static void InitMagicBallDelay(int x, int y)
9262 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9265 static void ActivateMagicBall(int bx, int by)
9269 if (level.ball_random)
9271 int pos_border = RND(8); // select one of the eight border elements
9272 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9273 int xx = pos_content % 3;
9274 int yy = pos_content / 3;
9279 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9280 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9284 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9286 int xx = x - bx + 1;
9287 int yy = y - by + 1;
9289 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9290 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9294 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9297 static void CheckExit(int x, int y)
9299 if (game.gems_still_needed > 0 ||
9300 game.sokoban_fields_still_needed > 0 ||
9301 game.sokoban_objects_still_needed > 0 ||
9302 game.lights_still_needed > 0)
9304 int element = Tile[x][y];
9305 int graphic = el2img(element);
9307 if (IS_ANIMATED(graphic))
9308 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9313 // do not re-open exit door closed after last player
9314 if (game.all_players_gone)
9317 Tile[x][y] = EL_EXIT_OPENING;
9319 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9322 static void CheckExitEM(int x, int y)
9324 if (game.gems_still_needed > 0 ||
9325 game.sokoban_fields_still_needed > 0 ||
9326 game.sokoban_objects_still_needed > 0 ||
9327 game.lights_still_needed > 0)
9329 int element = Tile[x][y];
9330 int graphic = el2img(element);
9332 if (IS_ANIMATED(graphic))
9333 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9338 // do not re-open exit door closed after last player
9339 if (game.all_players_gone)
9342 Tile[x][y] = EL_EM_EXIT_OPENING;
9344 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9347 static void CheckExitSteel(int x, int y)
9349 if (game.gems_still_needed > 0 ||
9350 game.sokoban_fields_still_needed > 0 ||
9351 game.sokoban_objects_still_needed > 0 ||
9352 game.lights_still_needed > 0)
9354 int element = Tile[x][y];
9355 int graphic = el2img(element);
9357 if (IS_ANIMATED(graphic))
9358 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9363 // do not re-open exit door closed after last player
9364 if (game.all_players_gone)
9367 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9369 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9372 static void CheckExitSteelEM(int x, int y)
9374 if (game.gems_still_needed > 0 ||
9375 game.sokoban_fields_still_needed > 0 ||
9376 game.sokoban_objects_still_needed > 0 ||
9377 game.lights_still_needed > 0)
9379 int element = Tile[x][y];
9380 int graphic = el2img(element);
9382 if (IS_ANIMATED(graphic))
9383 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9388 // do not re-open exit door closed after last player
9389 if (game.all_players_gone)
9392 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9394 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9397 static void CheckExitSP(int x, int y)
9399 if (game.gems_still_needed > 0)
9401 int element = Tile[x][y];
9402 int graphic = el2img(element);
9404 if (IS_ANIMATED(graphic))
9405 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9410 // do not re-open exit door closed after last player
9411 if (game.all_players_gone)
9414 Tile[x][y] = EL_SP_EXIT_OPENING;
9416 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9419 static void CloseAllOpenTimegates(void)
9423 SCAN_PLAYFIELD(x, y)
9425 int element = Tile[x][y];
9427 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9429 Tile[x][y] = EL_TIMEGATE_CLOSING;
9431 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9436 static void DrawTwinkleOnField(int x, int y)
9438 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9441 if (Tile[x][y] == EL_BD_DIAMOND)
9444 if (MovDelay[x][y] == 0) // next animation frame
9445 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9447 if (MovDelay[x][y] != 0) // wait some time before next frame
9451 DrawLevelElementAnimation(x, y, Tile[x][y]);
9453 if (MovDelay[x][y] != 0)
9455 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9456 10 - MovDelay[x][y]);
9458 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9463 static void MauerWaechst(int x, int y)
9467 if (!MovDelay[x][y]) // next animation frame
9468 MovDelay[x][y] = 3 * delay;
9470 if (MovDelay[x][y]) // wait some time before next frame
9474 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9476 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9477 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9479 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9482 if (!MovDelay[x][y])
9484 if (MovDir[x][y] == MV_LEFT)
9486 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9487 TEST_DrawLevelField(x - 1, y);
9489 else if (MovDir[x][y] == MV_RIGHT)
9491 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9492 TEST_DrawLevelField(x + 1, y);
9494 else if (MovDir[x][y] == MV_UP)
9496 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9497 TEST_DrawLevelField(x, y - 1);
9501 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9502 TEST_DrawLevelField(x, y + 1);
9505 Tile[x][y] = Store[x][y];
9507 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9508 TEST_DrawLevelField(x, y);
9513 static void MauerAbleger(int ax, int ay)
9515 int element = Tile[ax][ay];
9516 int graphic = el2img(element);
9517 boolean oben_frei = FALSE, unten_frei = FALSE;
9518 boolean links_frei = FALSE, rechts_frei = FALSE;
9519 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9520 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9521 boolean new_wall = FALSE;
9523 if (IS_ANIMATED(graphic))
9524 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9526 if (!MovDelay[ax][ay]) // start building new wall
9527 MovDelay[ax][ay] = 6;
9529 if (MovDelay[ax][ay]) // wait some time before building new wall
9532 if (MovDelay[ax][ay])
9536 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9538 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9540 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9542 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9545 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9546 element == EL_EXPANDABLE_WALL_ANY)
9550 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9551 Store[ax][ay-1] = element;
9552 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9553 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9554 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9555 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9560 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9561 Store[ax][ay+1] = element;
9562 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9563 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9564 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9565 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9570 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9571 element == EL_EXPANDABLE_WALL_ANY ||
9572 element == EL_EXPANDABLE_WALL ||
9573 element == EL_BD_EXPANDABLE_WALL)
9577 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9578 Store[ax-1][ay] = element;
9579 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9580 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9581 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9582 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9588 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9589 Store[ax+1][ay] = element;
9590 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9591 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9592 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9593 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9598 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9599 TEST_DrawLevelField(ax, ay);
9601 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9603 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9604 unten_massiv = TRUE;
9605 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9606 links_massiv = TRUE;
9607 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9608 rechts_massiv = TRUE;
9610 if (((oben_massiv && unten_massiv) ||
9611 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9612 element == EL_EXPANDABLE_WALL) &&
9613 ((links_massiv && rechts_massiv) ||
9614 element == EL_EXPANDABLE_WALL_VERTICAL))
9615 Tile[ax][ay] = EL_WALL;
9618 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9621 static void MauerAblegerStahl(int ax, int ay)
9623 int element = Tile[ax][ay];
9624 int graphic = el2img(element);
9625 boolean oben_frei = FALSE, unten_frei = FALSE;
9626 boolean links_frei = FALSE, rechts_frei = FALSE;
9627 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9628 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9629 boolean new_wall = FALSE;
9631 if (IS_ANIMATED(graphic))
9632 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9634 if (!MovDelay[ax][ay]) // start building new wall
9635 MovDelay[ax][ay] = 6;
9637 if (MovDelay[ax][ay]) // wait some time before building new wall
9640 if (MovDelay[ax][ay])
9644 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9646 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9648 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9650 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9653 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9654 element == EL_EXPANDABLE_STEELWALL_ANY)
9658 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9659 Store[ax][ay-1] = element;
9660 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9661 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9662 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9663 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9668 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9669 Store[ax][ay+1] = element;
9670 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9671 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9672 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9673 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9678 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9679 element == EL_EXPANDABLE_STEELWALL_ANY)
9683 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9684 Store[ax-1][ay] = element;
9685 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9686 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9687 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9688 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9694 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9695 Store[ax+1][ay] = element;
9696 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9697 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9698 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9699 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9704 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9706 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9707 unten_massiv = TRUE;
9708 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9709 links_massiv = TRUE;
9710 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9711 rechts_massiv = TRUE;
9713 if (((oben_massiv && unten_massiv) ||
9714 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9715 ((links_massiv && rechts_massiv) ||
9716 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9717 Tile[ax][ay] = EL_STEELWALL;
9720 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9723 static void CheckForDragon(int x, int y)
9726 boolean dragon_found = FALSE;
9727 static int xy[4][2] =
9735 for (i = 0; i < NUM_DIRECTIONS; i++)
9737 for (j = 0; j < 4; j++)
9739 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9741 if (IN_LEV_FIELD(xx, yy) &&
9742 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9744 if (Tile[xx][yy] == EL_DRAGON)
9745 dragon_found = TRUE;
9754 for (i = 0; i < NUM_DIRECTIONS; i++)
9756 for (j = 0; j < 3; j++)
9758 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9760 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9762 Tile[xx][yy] = EL_EMPTY;
9763 TEST_DrawLevelField(xx, yy);
9772 static void InitBuggyBase(int x, int y)
9774 int element = Tile[x][y];
9775 int activating_delay = FRAMES_PER_SECOND / 4;
9778 (element == EL_SP_BUGGY_BASE ?
9779 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9780 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9782 element == EL_SP_BUGGY_BASE_ACTIVE ?
9783 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9786 static void WarnBuggyBase(int x, int y)
9789 static int xy[4][2] =
9797 for (i = 0; i < NUM_DIRECTIONS; i++)
9799 int xx = x + xy[i][0];
9800 int yy = y + xy[i][1];
9802 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9804 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9811 static void InitTrap(int x, int y)
9813 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9816 static void ActivateTrap(int x, int y)
9818 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9821 static void ChangeActiveTrap(int x, int y)
9823 int graphic = IMG_TRAP_ACTIVE;
9825 // if new animation frame was drawn, correct crumbled sand border
9826 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9827 TEST_DrawLevelFieldCrumbled(x, y);
9830 static int getSpecialActionElement(int element, int number, int base_element)
9832 return (element != EL_EMPTY ? element :
9833 number != -1 ? base_element + number - 1 :
9837 static int getModifiedActionNumber(int value_old, int operator, int operand,
9838 int value_min, int value_max)
9840 int value_new = (operator == CA_MODE_SET ? operand :
9841 operator == CA_MODE_ADD ? value_old + operand :
9842 operator == CA_MODE_SUBTRACT ? value_old - operand :
9843 operator == CA_MODE_MULTIPLY ? value_old * operand :
9844 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9845 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9848 return (value_new < value_min ? value_min :
9849 value_new > value_max ? value_max :
9853 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9855 struct ElementInfo *ei = &element_info[element];
9856 struct ElementChangeInfo *change = &ei->change_page[page];
9857 int target_element = change->target_element;
9858 int action_type = change->action_type;
9859 int action_mode = change->action_mode;
9860 int action_arg = change->action_arg;
9861 int action_element = change->action_element;
9864 if (!change->has_action)
9867 // ---------- determine action paramater values -----------------------------
9869 int level_time_value =
9870 (level.time > 0 ? TimeLeft :
9873 int action_arg_element_raw =
9874 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9875 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9876 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9877 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9878 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9879 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9880 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9882 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9884 int action_arg_direction =
9885 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9886 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9887 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9888 change->actual_trigger_side :
9889 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9890 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9893 int action_arg_number_min =
9894 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9897 int action_arg_number_max =
9898 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9899 action_type == CA_SET_LEVEL_GEMS ? 999 :
9900 action_type == CA_SET_LEVEL_TIME ? 9999 :
9901 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9902 action_type == CA_SET_CE_VALUE ? 9999 :
9903 action_type == CA_SET_CE_SCORE ? 9999 :
9906 int action_arg_number_reset =
9907 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9908 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9909 action_type == CA_SET_LEVEL_TIME ? level.time :
9910 action_type == CA_SET_LEVEL_SCORE ? 0 :
9911 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9912 action_type == CA_SET_CE_SCORE ? 0 :
9915 int action_arg_number =
9916 (action_arg <= CA_ARG_MAX ? action_arg :
9917 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9918 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9919 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9920 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9921 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9922 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9923 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9924 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9925 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9926 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9927 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9928 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9929 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9930 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9931 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9932 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9933 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9934 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9935 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9936 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9937 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9940 int action_arg_number_old =
9941 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9942 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9943 action_type == CA_SET_LEVEL_SCORE ? game.score :
9944 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9945 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9948 int action_arg_number_new =
9949 getModifiedActionNumber(action_arg_number_old,
9950 action_mode, action_arg_number,
9951 action_arg_number_min, action_arg_number_max);
9953 int trigger_player_bits =
9954 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9955 change->actual_trigger_player_bits : change->trigger_player);
9957 int action_arg_player_bits =
9958 (action_arg >= CA_ARG_PLAYER_1 &&
9959 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9960 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9961 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9964 // ---------- execute action -----------------------------------------------
9966 switch (action_type)
9973 // ---------- level actions ----------------------------------------------
9975 case CA_RESTART_LEVEL:
9977 game.restart_level = TRUE;
9982 case CA_SHOW_ENVELOPE:
9984 int element = getSpecialActionElement(action_arg_element,
9985 action_arg_number, EL_ENVELOPE_1);
9987 if (IS_ENVELOPE(element))
9988 local_player->show_envelope = element;
9993 case CA_SET_LEVEL_TIME:
9995 if (level.time > 0) // only modify limited time value
9997 TimeLeft = action_arg_number_new;
9999 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10001 DisplayGameControlValues();
10003 if (!TimeLeft && setup.time_limit)
10004 for (i = 0; i < MAX_PLAYERS; i++)
10005 KillPlayer(&stored_player[i]);
10011 case CA_SET_LEVEL_SCORE:
10013 game.score = action_arg_number_new;
10015 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10017 DisplayGameControlValues();
10022 case CA_SET_LEVEL_GEMS:
10024 game.gems_still_needed = action_arg_number_new;
10026 game.snapshot.collected_item = TRUE;
10028 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10030 DisplayGameControlValues();
10035 case CA_SET_LEVEL_WIND:
10037 game.wind_direction = action_arg_direction;
10042 case CA_SET_LEVEL_RANDOM_SEED:
10044 // ensure that setting a new random seed while playing is predictable
10045 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10050 // ---------- player actions ---------------------------------------------
10052 case CA_MOVE_PLAYER:
10053 case CA_MOVE_PLAYER_NEW:
10055 // automatically move to the next field in specified direction
10056 for (i = 0; i < MAX_PLAYERS; i++)
10057 if (trigger_player_bits & (1 << i))
10058 if (action_type == CA_MOVE_PLAYER ||
10059 stored_player[i].MovPos == 0)
10060 stored_player[i].programmed_action = action_arg_direction;
10065 case CA_EXIT_PLAYER:
10067 for (i = 0; i < MAX_PLAYERS; i++)
10068 if (action_arg_player_bits & (1 << i))
10069 ExitPlayer(&stored_player[i]);
10071 if (game.players_still_needed == 0)
10077 case CA_KILL_PLAYER:
10079 for (i = 0; i < MAX_PLAYERS; i++)
10080 if (action_arg_player_bits & (1 << i))
10081 KillPlayer(&stored_player[i]);
10086 case CA_SET_PLAYER_KEYS:
10088 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10089 int element = getSpecialActionElement(action_arg_element,
10090 action_arg_number, EL_KEY_1);
10092 if (IS_KEY(element))
10094 for (i = 0; i < MAX_PLAYERS; i++)
10096 if (trigger_player_bits & (1 << i))
10098 stored_player[i].key[KEY_NR(element)] = key_state;
10100 DrawGameDoorValues();
10108 case CA_SET_PLAYER_SPEED:
10110 for (i = 0; i < MAX_PLAYERS; i++)
10112 if (trigger_player_bits & (1 << i))
10114 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10116 if (action_arg == CA_ARG_SPEED_FASTER &&
10117 stored_player[i].cannot_move)
10119 action_arg_number = STEPSIZE_VERY_SLOW;
10121 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10122 action_arg == CA_ARG_SPEED_FASTER)
10124 action_arg_number = 2;
10125 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10128 else if (action_arg == CA_ARG_NUMBER_RESET)
10130 action_arg_number = level.initial_player_stepsize[i];
10134 getModifiedActionNumber(move_stepsize,
10137 action_arg_number_min,
10138 action_arg_number_max);
10140 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10147 case CA_SET_PLAYER_SHIELD:
10149 for (i = 0; i < MAX_PLAYERS; i++)
10151 if (trigger_player_bits & (1 << i))
10153 if (action_arg == CA_ARG_SHIELD_OFF)
10155 stored_player[i].shield_normal_time_left = 0;
10156 stored_player[i].shield_deadly_time_left = 0;
10158 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10160 stored_player[i].shield_normal_time_left = 999999;
10162 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10164 stored_player[i].shield_normal_time_left = 999999;
10165 stored_player[i].shield_deadly_time_left = 999999;
10173 case CA_SET_PLAYER_GRAVITY:
10175 for (i = 0; i < MAX_PLAYERS; i++)
10177 if (trigger_player_bits & (1 << i))
10179 stored_player[i].gravity =
10180 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10181 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10182 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10183 stored_player[i].gravity);
10190 case CA_SET_PLAYER_ARTWORK:
10192 for (i = 0; i < MAX_PLAYERS; i++)
10194 if (trigger_player_bits & (1 << i))
10196 int artwork_element = action_arg_element;
10198 if (action_arg == CA_ARG_ELEMENT_RESET)
10200 (level.use_artwork_element[i] ? level.artwork_element[i] :
10201 stored_player[i].element_nr);
10203 if (stored_player[i].artwork_element != artwork_element)
10204 stored_player[i].Frame = 0;
10206 stored_player[i].artwork_element = artwork_element;
10208 SetPlayerWaiting(&stored_player[i], FALSE);
10210 // set number of special actions for bored and sleeping animation
10211 stored_player[i].num_special_action_bored =
10212 get_num_special_action(artwork_element,
10213 ACTION_BORING_1, ACTION_BORING_LAST);
10214 stored_player[i].num_special_action_sleeping =
10215 get_num_special_action(artwork_element,
10216 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10223 case CA_SET_PLAYER_INVENTORY:
10225 for (i = 0; i < MAX_PLAYERS; i++)
10227 struct PlayerInfo *player = &stored_player[i];
10230 if (trigger_player_bits & (1 << i))
10232 int inventory_element = action_arg_element;
10234 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10235 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10236 action_arg == CA_ARG_ELEMENT_ACTION)
10238 int element = inventory_element;
10239 int collect_count = element_info[element].collect_count_initial;
10241 if (!IS_CUSTOM_ELEMENT(element))
10244 if (collect_count == 0)
10245 player->inventory_infinite_element = element;
10247 for (k = 0; k < collect_count; k++)
10248 if (player->inventory_size < MAX_INVENTORY_SIZE)
10249 player->inventory_element[player->inventory_size++] =
10252 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10253 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10254 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10256 if (player->inventory_infinite_element != EL_UNDEFINED &&
10257 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10258 action_arg_element_raw))
10259 player->inventory_infinite_element = EL_UNDEFINED;
10261 for (k = 0, j = 0; j < player->inventory_size; j++)
10263 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10264 action_arg_element_raw))
10265 player->inventory_element[k++] = player->inventory_element[j];
10268 player->inventory_size = k;
10270 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10272 if (player->inventory_size > 0)
10274 for (j = 0; j < player->inventory_size - 1; j++)
10275 player->inventory_element[j] = player->inventory_element[j + 1];
10277 player->inventory_size--;
10280 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10282 if (player->inventory_size > 0)
10283 player->inventory_size--;
10285 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10287 player->inventory_infinite_element = EL_UNDEFINED;
10288 player->inventory_size = 0;
10290 else if (action_arg == CA_ARG_INVENTORY_RESET)
10292 player->inventory_infinite_element = EL_UNDEFINED;
10293 player->inventory_size = 0;
10295 if (level.use_initial_inventory[i])
10297 for (j = 0; j < level.initial_inventory_size[i]; j++)
10299 int element = level.initial_inventory_content[i][j];
10300 int collect_count = element_info[element].collect_count_initial;
10302 if (!IS_CUSTOM_ELEMENT(element))
10305 if (collect_count == 0)
10306 player->inventory_infinite_element = element;
10308 for (k = 0; k < collect_count; k++)
10309 if (player->inventory_size < MAX_INVENTORY_SIZE)
10310 player->inventory_element[player->inventory_size++] =
10321 // ---------- CE actions -------------------------------------------------
10323 case CA_SET_CE_VALUE:
10325 int last_ce_value = CustomValue[x][y];
10327 CustomValue[x][y] = action_arg_number_new;
10329 if (CustomValue[x][y] != last_ce_value)
10331 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10332 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10334 if (CustomValue[x][y] == 0)
10336 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10337 ChangeCount[x][y] = 0; // allow at least one more change
10339 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10340 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10347 case CA_SET_CE_SCORE:
10349 int last_ce_score = ei->collect_score;
10351 ei->collect_score = action_arg_number_new;
10353 if (ei->collect_score != last_ce_score)
10355 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10356 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10358 if (ei->collect_score == 0)
10362 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10363 ChangeCount[x][y] = 0; // allow at least one more change
10365 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10366 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10369 This is a very special case that seems to be a mixture between
10370 CheckElementChange() and CheckTriggeredElementChange(): while
10371 the first one only affects single elements that are triggered
10372 directly, the second one affects multiple elements in the playfield
10373 that are triggered indirectly by another element. This is a third
10374 case: Changing the CE score always affects multiple identical CEs,
10375 so every affected CE must be checked, not only the single CE for
10376 which the CE score was changed in the first place (as every instance
10377 of that CE shares the same CE score, and therefore also can change)!
10379 SCAN_PLAYFIELD(xx, yy)
10381 if (Tile[xx][yy] == element)
10382 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10383 CE_SCORE_GETS_ZERO);
10391 case CA_SET_CE_ARTWORK:
10393 int artwork_element = action_arg_element;
10394 boolean reset_frame = FALSE;
10397 if (action_arg == CA_ARG_ELEMENT_RESET)
10398 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10401 if (ei->gfx_element != artwork_element)
10402 reset_frame = TRUE;
10404 ei->gfx_element = artwork_element;
10406 SCAN_PLAYFIELD(xx, yy)
10408 if (Tile[xx][yy] == element)
10412 ResetGfxAnimation(xx, yy);
10413 ResetRandomAnimationValue(xx, yy);
10416 TEST_DrawLevelField(xx, yy);
10423 // ---------- engine actions ---------------------------------------------
10425 case CA_SET_ENGINE_SCAN_MODE:
10427 InitPlayfieldScanMode(action_arg);
10437 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10439 int old_element = Tile[x][y];
10440 int new_element = GetElementFromGroupElement(element);
10441 int previous_move_direction = MovDir[x][y];
10442 int last_ce_value = CustomValue[x][y];
10443 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10444 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10445 boolean add_player_onto_element = (new_element_is_player &&
10446 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10447 IS_WALKABLE(old_element));
10449 if (!add_player_onto_element)
10451 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10452 RemoveMovingField(x, y);
10456 Tile[x][y] = new_element;
10458 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10459 MovDir[x][y] = previous_move_direction;
10461 if (element_info[new_element].use_last_ce_value)
10462 CustomValue[x][y] = last_ce_value;
10464 InitField_WithBug1(x, y, FALSE);
10466 new_element = Tile[x][y]; // element may have changed
10468 ResetGfxAnimation(x, y);
10469 ResetRandomAnimationValue(x, y);
10471 TEST_DrawLevelField(x, y);
10473 if (GFX_CRUMBLED(new_element))
10474 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10477 // check if element under the player changes from accessible to unaccessible
10478 // (needed for special case of dropping element which then changes)
10479 // (must be checked after creating new element for walkable group elements)
10480 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10481 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10488 // "ChangeCount" not set yet to allow "entered by player" change one time
10489 if (new_element_is_player)
10490 RelocatePlayer(x, y, new_element);
10493 ChangeCount[x][y]++; // count number of changes in the same frame
10495 TestIfBadThingTouchesPlayer(x, y);
10496 TestIfPlayerTouchesCustomElement(x, y);
10497 TestIfElementTouchesCustomElement(x, y);
10500 static void CreateField(int x, int y, int element)
10502 CreateFieldExt(x, y, element, FALSE);
10505 static void CreateElementFromChange(int x, int y, int element)
10507 element = GET_VALID_RUNTIME_ELEMENT(element);
10509 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10511 int old_element = Tile[x][y];
10513 // prevent changed element from moving in same engine frame
10514 // unless both old and new element can either fall or move
10515 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10516 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10520 CreateFieldExt(x, y, element, TRUE);
10523 static boolean ChangeElement(int x, int y, int element, int page)
10525 struct ElementInfo *ei = &element_info[element];
10526 struct ElementChangeInfo *change = &ei->change_page[page];
10527 int ce_value = CustomValue[x][y];
10528 int ce_score = ei->collect_score;
10529 int target_element;
10530 int old_element = Tile[x][y];
10532 // always use default change event to prevent running into a loop
10533 if (ChangeEvent[x][y] == -1)
10534 ChangeEvent[x][y] = CE_DELAY;
10536 if (ChangeEvent[x][y] == CE_DELAY)
10538 // reset actual trigger element, trigger player and action element
10539 change->actual_trigger_element = EL_EMPTY;
10540 change->actual_trigger_player = EL_EMPTY;
10541 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10542 change->actual_trigger_side = CH_SIDE_NONE;
10543 change->actual_trigger_ce_value = 0;
10544 change->actual_trigger_ce_score = 0;
10547 // do not change elements more than a specified maximum number of changes
10548 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10551 ChangeCount[x][y]++; // count number of changes in the same frame
10553 if (change->explode)
10560 if (change->use_target_content)
10562 boolean complete_replace = TRUE;
10563 boolean can_replace[3][3];
10566 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10569 boolean is_walkable;
10570 boolean is_diggable;
10571 boolean is_collectible;
10572 boolean is_removable;
10573 boolean is_destructible;
10574 int ex = x + xx - 1;
10575 int ey = y + yy - 1;
10576 int content_element = change->target_content.e[xx][yy];
10579 can_replace[xx][yy] = TRUE;
10581 if (ex == x && ey == y) // do not check changing element itself
10584 if (content_element == EL_EMPTY_SPACE)
10586 can_replace[xx][yy] = FALSE; // do not replace border with space
10591 if (!IN_LEV_FIELD(ex, ey))
10593 can_replace[xx][yy] = FALSE;
10594 complete_replace = FALSE;
10601 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10602 e = MovingOrBlocked2Element(ex, ey);
10604 is_empty = (IS_FREE(ex, ey) ||
10605 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10607 is_walkable = (is_empty || IS_WALKABLE(e));
10608 is_diggable = (is_empty || IS_DIGGABLE(e));
10609 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10610 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10611 is_removable = (is_diggable || is_collectible);
10613 can_replace[xx][yy] =
10614 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10615 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10616 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10617 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10618 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10619 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10620 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10622 if (!can_replace[xx][yy])
10623 complete_replace = FALSE;
10626 if (!change->only_if_complete || complete_replace)
10628 boolean something_has_changed = FALSE;
10630 if (change->only_if_complete && change->use_random_replace &&
10631 RND(100) < change->random_percentage)
10634 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10636 int ex = x + xx - 1;
10637 int ey = y + yy - 1;
10638 int content_element;
10640 if (can_replace[xx][yy] && (!change->use_random_replace ||
10641 RND(100) < change->random_percentage))
10643 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10644 RemoveMovingField(ex, ey);
10646 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10648 content_element = change->target_content.e[xx][yy];
10649 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10650 ce_value, ce_score);
10652 CreateElementFromChange(ex, ey, target_element);
10654 something_has_changed = TRUE;
10656 // for symmetry reasons, freeze newly created border elements
10657 if (ex != x || ey != y)
10658 Stop[ex][ey] = TRUE; // no more moving in this frame
10662 if (something_has_changed)
10664 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10665 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10671 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10672 ce_value, ce_score);
10674 if (element == EL_DIAGONAL_GROWING ||
10675 element == EL_DIAGONAL_SHRINKING)
10677 target_element = Store[x][y];
10679 Store[x][y] = EL_EMPTY;
10682 CreateElementFromChange(x, y, target_element);
10684 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10685 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10688 // this uses direct change before indirect change
10689 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10694 static void HandleElementChange(int x, int y, int page)
10696 int element = MovingOrBlocked2Element(x, y);
10697 struct ElementInfo *ei = &element_info[element];
10698 struct ElementChangeInfo *change = &ei->change_page[page];
10699 boolean handle_action_before_change = FALSE;
10702 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10703 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10705 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10706 x, y, element, element_info[element].token_name);
10707 Debug("game:playing:HandleElementChange", "This should never happen!");
10711 // this can happen with classic bombs on walkable, changing elements
10712 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10717 if (ChangeDelay[x][y] == 0) // initialize element change
10719 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10721 if (change->can_change)
10723 // !!! not clear why graphic animation should be reset at all here !!!
10724 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10725 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10728 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10730 When using an animation frame delay of 1 (this only happens with
10731 "sp_zonk.moving.left/right" in the classic graphics), the default
10732 (non-moving) animation shows wrong animation frames (while the
10733 moving animation, like "sp_zonk.moving.left/right", is correct,
10734 so this graphical bug never shows up with the classic graphics).
10735 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10736 be drawn instead of the correct frames 0,1,2,3. This is caused by
10737 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10738 an element change: First when the change delay ("ChangeDelay[][]")
10739 counter has reached zero after decrementing, then a second time in
10740 the next frame (after "GfxFrame[][]" was already incremented) when
10741 "ChangeDelay[][]" is reset to the initial delay value again.
10743 This causes frame 0 to be drawn twice, while the last frame won't
10744 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10746 As some animations may already be cleverly designed around this bug
10747 (at least the "Snake Bite" snake tail animation does this), it cannot
10748 simply be fixed here without breaking such existing animations.
10749 Unfortunately, it cannot easily be detected if a graphics set was
10750 designed "before" or "after" the bug was fixed. As a workaround,
10751 a new graphics set option "game.graphics_engine_version" was added
10752 to be able to specify the game's major release version for which the
10753 graphics set was designed, which can then be used to decide if the
10754 bugfix should be used (version 4 and above) or not (version 3 or
10755 below, or if no version was specified at all, as with old sets).
10757 (The wrong/fixed animation frames can be tested with the test level set
10758 "test_gfxframe" and level "000", which contains a specially prepared
10759 custom element at level position (x/y) == (11/9) which uses the zonk
10760 animation mentioned above. Using "game.graphics_engine_version: 4"
10761 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10762 This can also be seen from the debug output for this test element.)
10765 // when a custom element is about to change (for example by change delay),
10766 // do not reset graphic animation when the custom element is moving
10767 if (game.graphics_engine_version < 4 &&
10770 ResetGfxAnimation(x, y);
10771 ResetRandomAnimationValue(x, y);
10774 if (change->pre_change_function)
10775 change->pre_change_function(x, y);
10779 ChangeDelay[x][y]--;
10781 if (ChangeDelay[x][y] != 0) // continue element change
10783 if (change->can_change)
10785 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10787 if (IS_ANIMATED(graphic))
10788 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10790 if (change->change_function)
10791 change->change_function(x, y);
10794 else // finish element change
10796 if (ChangePage[x][y] != -1) // remember page from delayed change
10798 page = ChangePage[x][y];
10799 ChangePage[x][y] = -1;
10801 change = &ei->change_page[page];
10804 if (IS_MOVING(x, y)) // never change a running system ;-)
10806 ChangeDelay[x][y] = 1; // try change after next move step
10807 ChangePage[x][y] = page; // remember page to use for change
10812 // special case: set new level random seed before changing element
10813 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10814 handle_action_before_change = TRUE;
10816 if (change->has_action && handle_action_before_change)
10817 ExecuteCustomElementAction(x, y, element, page);
10819 if (change->can_change)
10821 if (ChangeElement(x, y, element, page))
10823 if (change->post_change_function)
10824 change->post_change_function(x, y);
10828 if (change->has_action && !handle_action_before_change)
10829 ExecuteCustomElementAction(x, y, element, page);
10833 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10834 int trigger_element,
10836 int trigger_player,
10840 boolean change_done_any = FALSE;
10841 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10844 if (!(trigger_events[trigger_element][trigger_event]))
10847 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10849 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10851 int element = EL_CUSTOM_START + i;
10852 boolean change_done = FALSE;
10855 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10856 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10859 for (p = 0; p < element_info[element].num_change_pages; p++)
10861 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10863 if (change->can_change_or_has_action &&
10864 change->has_event[trigger_event] &&
10865 change->trigger_side & trigger_side &&
10866 change->trigger_player & trigger_player &&
10867 change->trigger_page & trigger_page_bits &&
10868 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10870 change->actual_trigger_element = trigger_element;
10871 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10872 change->actual_trigger_player_bits = trigger_player;
10873 change->actual_trigger_side = trigger_side;
10874 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10875 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10877 if ((change->can_change && !change_done) || change->has_action)
10881 SCAN_PLAYFIELD(x, y)
10883 if (Tile[x][y] == element)
10885 if (change->can_change && !change_done)
10887 // if element already changed in this frame, not only prevent
10888 // another element change (checked in ChangeElement()), but
10889 // also prevent additional element actions for this element
10891 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10892 !level.use_action_after_change_bug)
10895 ChangeDelay[x][y] = 1;
10896 ChangeEvent[x][y] = trigger_event;
10898 HandleElementChange(x, y, p);
10900 else if (change->has_action)
10902 // if element already changed in this frame, not only prevent
10903 // another element change (checked in ChangeElement()), but
10904 // also prevent additional element actions for this element
10906 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10907 !level.use_action_after_change_bug)
10910 ExecuteCustomElementAction(x, y, element, p);
10911 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10916 if (change->can_change)
10918 change_done = TRUE;
10919 change_done_any = TRUE;
10926 RECURSION_LOOP_DETECTION_END();
10928 return change_done_any;
10931 static boolean CheckElementChangeExt(int x, int y,
10933 int trigger_element,
10935 int trigger_player,
10938 boolean change_done = FALSE;
10941 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10942 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10945 if (Tile[x][y] == EL_BLOCKED)
10947 Blocked2Moving(x, y, &x, &y);
10948 element = Tile[x][y];
10951 // check if element has already changed or is about to change after moving
10952 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10953 Tile[x][y] != element) ||
10955 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10956 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10957 ChangePage[x][y] != -1)))
10960 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10962 for (p = 0; p < element_info[element].num_change_pages; p++)
10964 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10966 /* check trigger element for all events where the element that is checked
10967 for changing interacts with a directly adjacent element -- this is
10968 different to element changes that affect other elements to change on the
10969 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10970 boolean check_trigger_element =
10971 (trigger_event == CE_TOUCHING_X ||
10972 trigger_event == CE_HITTING_X ||
10973 trigger_event == CE_HIT_BY_X ||
10974 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10976 if (change->can_change_or_has_action &&
10977 change->has_event[trigger_event] &&
10978 change->trigger_side & trigger_side &&
10979 change->trigger_player & trigger_player &&
10980 (!check_trigger_element ||
10981 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10983 change->actual_trigger_element = trigger_element;
10984 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10985 change->actual_trigger_player_bits = trigger_player;
10986 change->actual_trigger_side = trigger_side;
10987 change->actual_trigger_ce_value = CustomValue[x][y];
10988 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10990 // special case: trigger element not at (x,y) position for some events
10991 if (check_trigger_element)
11003 { 0, 0 }, { 0, 0 }, { 0, 0 },
11007 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11008 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11010 change->actual_trigger_ce_value = CustomValue[xx][yy];
11011 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11014 if (change->can_change && !change_done)
11016 ChangeDelay[x][y] = 1;
11017 ChangeEvent[x][y] = trigger_event;
11019 HandleElementChange(x, y, p);
11021 change_done = TRUE;
11023 else if (change->has_action)
11025 ExecuteCustomElementAction(x, y, element, p);
11026 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11031 RECURSION_LOOP_DETECTION_END();
11033 return change_done;
11036 static void PlayPlayerSound(struct PlayerInfo *player)
11038 int jx = player->jx, jy = player->jy;
11039 int sound_element = player->artwork_element;
11040 int last_action = player->last_action_waiting;
11041 int action = player->action_waiting;
11043 if (player->is_waiting)
11045 if (action != last_action)
11046 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11048 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11052 if (action != last_action)
11053 StopSound(element_info[sound_element].sound[last_action]);
11055 if (last_action == ACTION_SLEEPING)
11056 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11060 static void PlayAllPlayersSound(void)
11064 for (i = 0; i < MAX_PLAYERS; i++)
11065 if (stored_player[i].active)
11066 PlayPlayerSound(&stored_player[i]);
11069 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11071 boolean last_waiting = player->is_waiting;
11072 int move_dir = player->MovDir;
11074 player->dir_waiting = move_dir;
11075 player->last_action_waiting = player->action_waiting;
11079 if (!last_waiting) // not waiting -> waiting
11081 player->is_waiting = TRUE;
11083 player->frame_counter_bored =
11085 game.player_boring_delay_fixed +
11086 GetSimpleRandom(game.player_boring_delay_random);
11087 player->frame_counter_sleeping =
11089 game.player_sleeping_delay_fixed +
11090 GetSimpleRandom(game.player_sleeping_delay_random);
11092 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11095 if (game.player_sleeping_delay_fixed +
11096 game.player_sleeping_delay_random > 0 &&
11097 player->anim_delay_counter == 0 &&
11098 player->post_delay_counter == 0 &&
11099 FrameCounter >= player->frame_counter_sleeping)
11100 player->is_sleeping = TRUE;
11101 else if (game.player_boring_delay_fixed +
11102 game.player_boring_delay_random > 0 &&
11103 FrameCounter >= player->frame_counter_bored)
11104 player->is_bored = TRUE;
11106 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11107 player->is_bored ? ACTION_BORING :
11110 if (player->is_sleeping && player->use_murphy)
11112 // special case for sleeping Murphy when leaning against non-free tile
11114 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11115 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11116 !IS_MOVING(player->jx - 1, player->jy)))
11117 move_dir = MV_LEFT;
11118 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11119 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11120 !IS_MOVING(player->jx + 1, player->jy)))
11121 move_dir = MV_RIGHT;
11123 player->is_sleeping = FALSE;
11125 player->dir_waiting = move_dir;
11128 if (player->is_sleeping)
11130 if (player->num_special_action_sleeping > 0)
11132 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11134 int last_special_action = player->special_action_sleeping;
11135 int num_special_action = player->num_special_action_sleeping;
11136 int special_action =
11137 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11138 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11139 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11140 last_special_action + 1 : ACTION_SLEEPING);
11141 int special_graphic =
11142 el_act_dir2img(player->artwork_element, special_action, move_dir);
11144 player->anim_delay_counter =
11145 graphic_info[special_graphic].anim_delay_fixed +
11146 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11147 player->post_delay_counter =
11148 graphic_info[special_graphic].post_delay_fixed +
11149 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11151 player->special_action_sleeping = special_action;
11154 if (player->anim_delay_counter > 0)
11156 player->action_waiting = player->special_action_sleeping;
11157 player->anim_delay_counter--;
11159 else if (player->post_delay_counter > 0)
11161 player->post_delay_counter--;
11165 else if (player->is_bored)
11167 if (player->num_special_action_bored > 0)
11169 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11171 int special_action =
11172 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11173 int special_graphic =
11174 el_act_dir2img(player->artwork_element, special_action, move_dir);
11176 player->anim_delay_counter =
11177 graphic_info[special_graphic].anim_delay_fixed +
11178 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11179 player->post_delay_counter =
11180 graphic_info[special_graphic].post_delay_fixed +
11181 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11183 player->special_action_bored = special_action;
11186 if (player->anim_delay_counter > 0)
11188 player->action_waiting = player->special_action_bored;
11189 player->anim_delay_counter--;
11191 else if (player->post_delay_counter > 0)
11193 player->post_delay_counter--;
11198 else if (last_waiting) // waiting -> not waiting
11200 player->is_waiting = FALSE;
11201 player->is_bored = FALSE;
11202 player->is_sleeping = FALSE;
11204 player->frame_counter_bored = -1;
11205 player->frame_counter_sleeping = -1;
11207 player->anim_delay_counter = 0;
11208 player->post_delay_counter = 0;
11210 player->dir_waiting = player->MovDir;
11211 player->action_waiting = ACTION_DEFAULT;
11213 player->special_action_bored = ACTION_DEFAULT;
11214 player->special_action_sleeping = ACTION_DEFAULT;
11218 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11220 if ((!player->is_moving && player->was_moving) ||
11221 (player->MovPos == 0 && player->was_moving) ||
11222 (player->is_snapping && !player->was_snapping) ||
11223 (player->is_dropping && !player->was_dropping))
11225 if (!CheckSaveEngineSnapshotToList())
11228 player->was_moving = FALSE;
11229 player->was_snapping = TRUE;
11230 player->was_dropping = TRUE;
11234 if (player->is_moving)
11235 player->was_moving = TRUE;
11237 if (!player->is_snapping)
11238 player->was_snapping = FALSE;
11240 if (!player->is_dropping)
11241 player->was_dropping = FALSE;
11244 static struct MouseActionInfo mouse_action_last = { 0 };
11245 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11246 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11249 CheckSaveEngineSnapshotToList();
11251 mouse_action_last = mouse_action;
11254 static void CheckSingleStepMode(struct PlayerInfo *player)
11256 if (tape.single_step && tape.recording && !tape.pausing)
11258 /* as it is called "single step mode", just return to pause mode when the
11259 player stopped moving after one tile (or never starts moving at all) */
11260 if (!player->is_moving &&
11261 !player->is_pushing &&
11262 !player->is_dropping_pressed &&
11263 !player->effective_mouse_action.button)
11264 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11267 CheckSaveEngineSnapshot(player);
11270 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11272 int left = player_action & JOY_LEFT;
11273 int right = player_action & JOY_RIGHT;
11274 int up = player_action & JOY_UP;
11275 int down = player_action & JOY_DOWN;
11276 int button1 = player_action & JOY_BUTTON_1;
11277 int button2 = player_action & JOY_BUTTON_2;
11278 int dx = (left ? -1 : right ? 1 : 0);
11279 int dy = (up ? -1 : down ? 1 : 0);
11281 if (!player->active || tape.pausing)
11287 SnapField(player, dx, dy);
11291 DropElement(player);
11293 MovePlayer(player, dx, dy);
11296 CheckSingleStepMode(player);
11298 SetPlayerWaiting(player, FALSE);
11300 return player_action;
11304 // no actions for this player (no input at player's configured device)
11306 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11307 SnapField(player, 0, 0);
11308 CheckGravityMovementWhenNotMoving(player);
11310 if (player->MovPos == 0)
11311 SetPlayerWaiting(player, TRUE);
11313 if (player->MovPos == 0) // needed for tape.playing
11314 player->is_moving = FALSE;
11316 player->is_dropping = FALSE;
11317 player->is_dropping_pressed = FALSE;
11318 player->drop_pressed_delay = 0;
11320 CheckSingleStepMode(player);
11326 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11329 if (!tape.use_mouse_actions)
11332 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11333 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11334 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11337 static void SetTapeActionFromMouseAction(byte *tape_action,
11338 struct MouseActionInfo *mouse_action)
11340 if (!tape.use_mouse_actions)
11343 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11344 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11345 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11348 static void CheckLevelSolved(void)
11350 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11352 if (game_em.level_solved &&
11353 !game_em.game_over) // game won
11357 game_em.game_over = TRUE;
11359 game.all_players_gone = TRUE;
11362 if (game_em.game_over) // game lost
11363 game.all_players_gone = TRUE;
11365 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11367 if (game_sp.level_solved &&
11368 !game_sp.game_over) // game won
11372 game_sp.game_over = TRUE;
11374 game.all_players_gone = TRUE;
11377 if (game_sp.game_over) // game lost
11378 game.all_players_gone = TRUE;
11380 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11382 if (game_mm.level_solved &&
11383 !game_mm.game_over) // game won
11387 game_mm.game_over = TRUE;
11389 game.all_players_gone = TRUE;
11392 if (game_mm.game_over) // game lost
11393 game.all_players_gone = TRUE;
11397 static void CheckLevelTime(void)
11401 if (TimeFrames >= FRAMES_PER_SECOND)
11406 for (i = 0; i < MAX_PLAYERS; i++)
11408 struct PlayerInfo *player = &stored_player[i];
11410 if (SHIELD_ON(player))
11412 player->shield_normal_time_left--;
11414 if (player->shield_deadly_time_left > 0)
11415 player->shield_deadly_time_left--;
11419 if (!game.LevelSolved && !level.use_step_counter)
11427 if (TimeLeft <= 10 && setup.time_limit)
11428 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11430 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11431 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11433 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11435 if (!TimeLeft && setup.time_limit)
11437 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11438 game_em.lev->killed_out_of_time = TRUE;
11440 for (i = 0; i < MAX_PLAYERS; i++)
11441 KillPlayer(&stored_player[i]);
11444 else if (game.no_time_limit && !game.all_players_gone)
11446 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11449 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11452 if (tape.recording || tape.playing)
11453 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11456 if (tape.recording || tape.playing)
11457 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11459 UpdateAndDisplayGameControlValues();
11462 void AdvanceFrameAndPlayerCounters(int player_nr)
11466 // advance frame counters (global frame counter and time frame counter)
11470 // advance player counters (counters for move delay, move animation etc.)
11471 for (i = 0; i < MAX_PLAYERS; i++)
11473 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11474 int move_delay_value = stored_player[i].move_delay_value;
11475 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11477 if (!advance_player_counters) // not all players may be affected
11480 if (move_frames == 0) // less than one move per game frame
11482 int stepsize = TILEX / move_delay_value;
11483 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11484 int count = (stored_player[i].is_moving ?
11485 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11487 if (count % delay == 0)
11491 stored_player[i].Frame += move_frames;
11493 if (stored_player[i].MovPos != 0)
11494 stored_player[i].StepFrame += move_frames;
11496 if (stored_player[i].move_delay > 0)
11497 stored_player[i].move_delay--;
11499 // due to bugs in previous versions, counter must count up, not down
11500 if (stored_player[i].push_delay != -1)
11501 stored_player[i].push_delay++;
11503 if (stored_player[i].drop_delay > 0)
11504 stored_player[i].drop_delay--;
11506 if (stored_player[i].is_dropping_pressed)
11507 stored_player[i].drop_pressed_delay++;
11511 void StartGameActions(boolean init_network_game, boolean record_tape,
11514 unsigned int new_random_seed = InitRND(random_seed);
11517 TapeStartRecording(new_random_seed);
11519 if (init_network_game)
11521 SendToServer_LevelFile();
11522 SendToServer_StartPlaying();
11530 static void GameActionsExt(void)
11533 static unsigned int game_frame_delay = 0;
11535 unsigned int game_frame_delay_value;
11536 byte *recorded_player_action;
11537 byte summarized_player_action = 0;
11538 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11541 // detect endless loops, caused by custom element programming
11542 if (recursion_loop_detected && recursion_loop_depth == 0)
11544 char *message = getStringCat3("Internal Error! Element ",
11545 EL_NAME(recursion_loop_element),
11546 " caused endless loop! Quit the game?");
11548 Warn("element '%s' caused endless loop in game engine",
11549 EL_NAME(recursion_loop_element));
11551 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11553 recursion_loop_detected = FALSE; // if game should be continued
11560 if (game.restart_level)
11561 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11563 CheckLevelSolved();
11565 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11568 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11571 if (game_status != GAME_MODE_PLAYING) // status might have changed
11574 game_frame_delay_value =
11575 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11577 if (tape.playing && tape.warp_forward && !tape.pausing)
11578 game_frame_delay_value = 0;
11580 SetVideoFrameDelay(game_frame_delay_value);
11582 // (de)activate virtual buttons depending on current game status
11583 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11585 if (game.all_players_gone) // if no players there to be controlled anymore
11586 SetOverlayActive(FALSE);
11587 else if (!tape.playing) // if game continues after tape stopped playing
11588 SetOverlayActive(TRUE);
11593 // ---------- main game synchronization point ----------
11595 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11597 Debug("game:playing:skip", "skip == %d", skip);
11600 // ---------- main game synchronization point ----------
11602 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11606 if (network_playing && !network_player_action_received)
11608 // try to get network player actions in time
11610 // last chance to get network player actions without main loop delay
11611 HandleNetworking();
11613 // game was quit by network peer
11614 if (game_status != GAME_MODE_PLAYING)
11617 // check if network player actions still missing and game still running
11618 if (!network_player_action_received && !checkGameEnded())
11619 return; // failed to get network player actions in time
11621 // do not yet reset "network_player_action_received" (for tape.pausing)
11627 // at this point we know that we really continue executing the game
11629 network_player_action_received = FALSE;
11631 // when playing tape, read previously recorded player input from tape data
11632 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11634 local_player->effective_mouse_action = local_player->mouse_action;
11636 if (recorded_player_action != NULL)
11637 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11638 recorded_player_action);
11640 // TapePlayAction() may return NULL when toggling to "pause before death"
11644 if (tape.set_centered_player)
11646 game.centered_player_nr_next = tape.centered_player_nr_next;
11647 game.set_centered_player = TRUE;
11650 for (i = 0; i < MAX_PLAYERS; i++)
11652 summarized_player_action |= stored_player[i].action;
11654 if (!network_playing && (game.team_mode || tape.playing))
11655 stored_player[i].effective_action = stored_player[i].action;
11658 if (network_playing && !checkGameEnded())
11659 SendToServer_MovePlayer(summarized_player_action);
11661 // summarize all actions at local players mapped input device position
11662 // (this allows using different input devices in single player mode)
11663 if (!network.enabled && !game.team_mode)
11664 stored_player[map_player_action[local_player->index_nr]].effective_action =
11665 summarized_player_action;
11667 // summarize all actions at centered player in local team mode
11668 if (tape.recording &&
11669 setup.team_mode && !network.enabled &&
11670 setup.input_on_focus &&
11671 game.centered_player_nr != -1)
11673 for (i = 0; i < MAX_PLAYERS; i++)
11674 stored_player[map_player_action[i]].effective_action =
11675 (i == game.centered_player_nr ? summarized_player_action : 0);
11678 if (recorded_player_action != NULL)
11679 for (i = 0; i < MAX_PLAYERS; i++)
11680 stored_player[i].effective_action = recorded_player_action[i];
11682 for (i = 0; i < MAX_PLAYERS; i++)
11684 tape_action[i] = stored_player[i].effective_action;
11686 /* (this may happen in the RND game engine if a player was not present on
11687 the playfield on level start, but appeared later from a custom element */
11688 if (setup.team_mode &&
11691 !tape.player_participates[i])
11692 tape.player_participates[i] = TRUE;
11695 SetTapeActionFromMouseAction(tape_action,
11696 &local_player->effective_mouse_action);
11698 // only record actions from input devices, but not programmed actions
11699 if (tape.recording)
11700 TapeRecordAction(tape_action);
11702 // remember if game was played (especially after tape stopped playing)
11703 if (!tape.playing && summarized_player_action)
11704 game.GamePlayed = TRUE;
11706 #if USE_NEW_PLAYER_ASSIGNMENTS
11707 // !!! also map player actions in single player mode !!!
11708 // if (game.team_mode)
11711 byte mapped_action[MAX_PLAYERS];
11713 #if DEBUG_PLAYER_ACTIONS
11714 for (i = 0; i < MAX_PLAYERS; i++)
11715 DebugContinued("", "%d, ", stored_player[i].effective_action);
11718 for (i = 0; i < MAX_PLAYERS; i++)
11719 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11721 for (i = 0; i < MAX_PLAYERS; i++)
11722 stored_player[i].effective_action = mapped_action[i];
11724 #if DEBUG_PLAYER_ACTIONS
11725 DebugContinued("", "=> ");
11726 for (i = 0; i < MAX_PLAYERS; i++)
11727 DebugContinued("", "%d, ", stored_player[i].effective_action);
11728 DebugContinued("game:playing:player", "\n");
11731 #if DEBUG_PLAYER_ACTIONS
11734 for (i = 0; i < MAX_PLAYERS; i++)
11735 DebugContinued("", "%d, ", stored_player[i].effective_action);
11736 DebugContinued("game:playing:player", "\n");
11741 for (i = 0; i < MAX_PLAYERS; i++)
11743 // allow engine snapshot in case of changed movement attempt
11744 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11745 (stored_player[i].effective_action & KEY_MOTION))
11746 game.snapshot.changed_action = TRUE;
11748 // allow engine snapshot in case of snapping/dropping attempt
11749 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11750 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11751 game.snapshot.changed_action = TRUE;
11753 game.snapshot.last_action[i] = stored_player[i].effective_action;
11756 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11758 GameActions_EM_Main();
11760 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11762 GameActions_SP_Main();
11764 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11766 GameActions_MM_Main();
11770 GameActions_RND_Main();
11773 BlitScreenToBitmap(backbuffer);
11775 CheckLevelSolved();
11778 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11780 if (global.show_frames_per_second)
11782 static unsigned int fps_counter = 0;
11783 static int fps_frames = 0;
11784 unsigned int fps_delay_ms = Counter() - fps_counter;
11788 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11790 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11793 fps_counter = Counter();
11795 // always draw FPS to screen after FPS value was updated
11796 redraw_mask |= REDRAW_FPS;
11799 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11800 if (GetDrawDeactivationMask() == REDRAW_NONE)
11801 redraw_mask |= REDRAW_FPS;
11805 static void GameActions_CheckSaveEngineSnapshot(void)
11807 if (!game.snapshot.save_snapshot)
11810 // clear flag for saving snapshot _before_ saving snapshot
11811 game.snapshot.save_snapshot = FALSE;
11813 SaveEngineSnapshotToList();
11816 void GameActions(void)
11820 GameActions_CheckSaveEngineSnapshot();
11823 void GameActions_EM_Main(void)
11825 byte effective_action[MAX_PLAYERS];
11826 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11829 for (i = 0; i < MAX_PLAYERS; i++)
11830 effective_action[i] = stored_player[i].effective_action;
11832 GameActions_EM(effective_action, warp_mode);
11835 void GameActions_SP_Main(void)
11837 byte effective_action[MAX_PLAYERS];
11838 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11841 for (i = 0; i < MAX_PLAYERS; i++)
11842 effective_action[i] = stored_player[i].effective_action;
11844 GameActions_SP(effective_action, warp_mode);
11846 for (i = 0; i < MAX_PLAYERS; i++)
11848 if (stored_player[i].force_dropping)
11849 stored_player[i].action |= KEY_BUTTON_DROP;
11851 stored_player[i].force_dropping = FALSE;
11855 void GameActions_MM_Main(void)
11857 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11859 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11862 void GameActions_RND_Main(void)
11867 void GameActions_RND(void)
11869 static struct MouseActionInfo mouse_action_last = { 0 };
11870 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11871 int magic_wall_x = 0, magic_wall_y = 0;
11872 int i, x, y, element, graphic, last_gfx_frame;
11874 InitPlayfieldScanModeVars();
11876 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11878 SCAN_PLAYFIELD(x, y)
11880 ChangeCount[x][y] = 0;
11881 ChangeEvent[x][y] = -1;
11885 if (game.set_centered_player)
11887 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11889 // switching to "all players" only possible if all players fit to screen
11890 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11892 game.centered_player_nr_next = game.centered_player_nr;
11893 game.set_centered_player = FALSE;
11896 // do not switch focus to non-existing (or non-active) player
11897 if (game.centered_player_nr_next >= 0 &&
11898 !stored_player[game.centered_player_nr_next].active)
11900 game.centered_player_nr_next = game.centered_player_nr;
11901 game.set_centered_player = FALSE;
11905 if (game.set_centered_player &&
11906 ScreenMovPos == 0) // screen currently aligned at tile position
11910 if (game.centered_player_nr_next == -1)
11912 setScreenCenteredToAllPlayers(&sx, &sy);
11916 sx = stored_player[game.centered_player_nr_next].jx;
11917 sy = stored_player[game.centered_player_nr_next].jy;
11920 game.centered_player_nr = game.centered_player_nr_next;
11921 game.set_centered_player = FALSE;
11923 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11924 DrawGameDoorValues();
11927 for (i = 0; i < MAX_PLAYERS; i++)
11929 int actual_player_action = stored_player[i].effective_action;
11932 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11933 - rnd_equinox_tetrachloride 048
11934 - rnd_equinox_tetrachloride_ii 096
11935 - rnd_emanuel_schmieg 002
11936 - doctor_sloan_ww 001, 020
11938 if (stored_player[i].MovPos == 0)
11939 CheckGravityMovement(&stored_player[i]);
11942 // overwrite programmed action with tape action
11943 if (stored_player[i].programmed_action)
11944 actual_player_action = stored_player[i].programmed_action;
11946 PlayerActions(&stored_player[i], actual_player_action);
11948 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11951 ScrollScreen(NULL, SCROLL_GO_ON);
11953 /* for backwards compatibility, the following code emulates a fixed bug that
11954 occured when pushing elements (causing elements that just made their last
11955 pushing step to already (if possible) make their first falling step in the
11956 same game frame, which is bad); this code is also needed to use the famous
11957 "spring push bug" which is used in older levels and might be wanted to be
11958 used also in newer levels, but in this case the buggy pushing code is only
11959 affecting the "spring" element and no other elements */
11961 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11963 for (i = 0; i < MAX_PLAYERS; i++)
11965 struct PlayerInfo *player = &stored_player[i];
11966 int x = player->jx;
11967 int y = player->jy;
11969 if (player->active && player->is_pushing && player->is_moving &&
11971 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11972 Tile[x][y] == EL_SPRING))
11974 ContinueMoving(x, y);
11976 // continue moving after pushing (this is actually a bug)
11977 if (!IS_MOVING(x, y))
11978 Stop[x][y] = FALSE;
11983 SCAN_PLAYFIELD(x, y)
11985 Last[x][y] = Tile[x][y];
11987 ChangeCount[x][y] = 0;
11988 ChangeEvent[x][y] = -1;
11990 // this must be handled before main playfield loop
11991 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
11994 if (MovDelay[x][y] <= 0)
11998 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12001 if (MovDelay[x][y] <= 0)
12004 TEST_DrawLevelField(x, y);
12006 TestIfElementTouchesCustomElement(x, y); // for empty space
12011 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12013 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12015 Debug("game:playing:GameActions_RND", "This should never happen!");
12017 ChangePage[x][y] = -1;
12021 Stop[x][y] = FALSE;
12022 if (WasJustMoving[x][y] > 0)
12023 WasJustMoving[x][y]--;
12024 if (WasJustFalling[x][y] > 0)
12025 WasJustFalling[x][y]--;
12026 if (CheckCollision[x][y] > 0)
12027 CheckCollision[x][y]--;
12028 if (CheckImpact[x][y] > 0)
12029 CheckImpact[x][y]--;
12033 /* reset finished pushing action (not done in ContinueMoving() to allow
12034 continuous pushing animation for elements with zero push delay) */
12035 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12037 ResetGfxAnimation(x, y);
12038 TEST_DrawLevelField(x, y);
12042 if (IS_BLOCKED(x, y))
12046 Blocked2Moving(x, y, &oldx, &oldy);
12047 if (!IS_MOVING(oldx, oldy))
12049 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12050 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12051 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12052 Debug("game:playing:GameActions_RND", "This should never happen!");
12058 if (mouse_action.button)
12060 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12062 x = mouse_action.lx;
12063 y = mouse_action.ly;
12064 element = Tile[x][y];
12068 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12069 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12072 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12073 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12076 SCAN_PLAYFIELD(x, y)
12078 element = Tile[x][y];
12079 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12080 last_gfx_frame = GfxFrame[x][y];
12082 ResetGfxFrame(x, y);
12084 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12085 DrawLevelGraphicAnimation(x, y, graphic);
12087 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12088 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12089 ResetRandomAnimationValue(x, y);
12091 SetRandomAnimationValue(x, y);
12093 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12095 if (IS_INACTIVE(element))
12097 if (IS_ANIMATED(graphic))
12098 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12103 // this may take place after moving, so 'element' may have changed
12104 if (IS_CHANGING(x, y) &&
12105 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12107 int page = element_info[element].event_page_nr[CE_DELAY];
12109 HandleElementChange(x, y, page);
12111 element = Tile[x][y];
12112 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12115 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12119 element = Tile[x][y];
12120 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12122 if (IS_ANIMATED(graphic) &&
12123 !IS_MOVING(x, y) &&
12125 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12127 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12128 TEST_DrawTwinkleOnField(x, y);
12130 else if (element == EL_ACID)
12133 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12135 else if ((element == EL_EXIT_OPEN ||
12136 element == EL_EM_EXIT_OPEN ||
12137 element == EL_SP_EXIT_OPEN ||
12138 element == EL_STEEL_EXIT_OPEN ||
12139 element == EL_EM_STEEL_EXIT_OPEN ||
12140 element == EL_SP_TERMINAL ||
12141 element == EL_SP_TERMINAL_ACTIVE ||
12142 element == EL_EXTRA_TIME ||
12143 element == EL_SHIELD_NORMAL ||
12144 element == EL_SHIELD_DEADLY) &&
12145 IS_ANIMATED(graphic))
12146 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12147 else if (IS_MOVING(x, y))
12148 ContinueMoving(x, y);
12149 else if (IS_ACTIVE_BOMB(element))
12150 CheckDynamite(x, y);
12151 else if (element == EL_AMOEBA_GROWING)
12152 AmoebaGrowing(x, y);
12153 else if (element == EL_AMOEBA_SHRINKING)
12154 AmoebaShrinking(x, y);
12156 #if !USE_NEW_AMOEBA_CODE
12157 else if (IS_AMOEBALIVE(element))
12158 AmoebaReproduce(x, y);
12161 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12163 else if (element == EL_EXIT_CLOSED)
12165 else if (element == EL_EM_EXIT_CLOSED)
12167 else if (element == EL_STEEL_EXIT_CLOSED)
12168 CheckExitSteel(x, y);
12169 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12170 CheckExitSteelEM(x, y);
12171 else if (element == EL_SP_EXIT_CLOSED)
12173 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12174 element == EL_EXPANDABLE_STEELWALL_GROWING)
12175 MauerWaechst(x, y);
12176 else if (element == EL_EXPANDABLE_WALL ||
12177 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12178 element == EL_EXPANDABLE_WALL_VERTICAL ||
12179 element == EL_EXPANDABLE_WALL_ANY ||
12180 element == EL_BD_EXPANDABLE_WALL)
12181 MauerAbleger(x, y);
12182 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12183 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12184 element == EL_EXPANDABLE_STEELWALL_ANY)
12185 MauerAblegerStahl(x, y);
12186 else if (element == EL_FLAMES)
12187 CheckForDragon(x, y);
12188 else if (element == EL_EXPLOSION)
12189 ; // drawing of correct explosion animation is handled separately
12190 else if (element == EL_ELEMENT_SNAPPING ||
12191 element == EL_DIAGONAL_SHRINKING ||
12192 element == EL_DIAGONAL_GROWING)
12194 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12196 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12198 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12199 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12201 if (IS_BELT_ACTIVE(element))
12202 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12204 if (game.magic_wall_active)
12206 int jx = local_player->jx, jy = local_player->jy;
12208 // play the element sound at the position nearest to the player
12209 if ((element == EL_MAGIC_WALL_FULL ||
12210 element == EL_MAGIC_WALL_ACTIVE ||
12211 element == EL_MAGIC_WALL_EMPTYING ||
12212 element == EL_BD_MAGIC_WALL_FULL ||
12213 element == EL_BD_MAGIC_WALL_ACTIVE ||
12214 element == EL_BD_MAGIC_WALL_EMPTYING ||
12215 element == EL_DC_MAGIC_WALL_FULL ||
12216 element == EL_DC_MAGIC_WALL_ACTIVE ||
12217 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12218 ABS(x - jx) + ABS(y - jy) <
12219 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12227 #if USE_NEW_AMOEBA_CODE
12228 // new experimental amoeba growth stuff
12229 if (!(FrameCounter % 8))
12231 static unsigned int random = 1684108901;
12233 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12235 x = RND(lev_fieldx);
12236 y = RND(lev_fieldy);
12237 element = Tile[x][y];
12239 if (!IS_PLAYER(x,y) &&
12240 (element == EL_EMPTY ||
12241 CAN_GROW_INTO(element) ||
12242 element == EL_QUICKSAND_EMPTY ||
12243 element == EL_QUICKSAND_FAST_EMPTY ||
12244 element == EL_ACID_SPLASH_LEFT ||
12245 element == EL_ACID_SPLASH_RIGHT))
12247 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12248 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12249 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12250 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12251 Tile[x][y] = EL_AMOEBA_DROP;
12254 random = random * 129 + 1;
12259 game.explosions_delayed = FALSE;
12261 SCAN_PLAYFIELD(x, y)
12263 element = Tile[x][y];
12265 if (ExplodeField[x][y])
12266 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12267 else if (element == EL_EXPLOSION)
12268 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12270 ExplodeField[x][y] = EX_TYPE_NONE;
12273 game.explosions_delayed = TRUE;
12275 if (game.magic_wall_active)
12277 if (!(game.magic_wall_time_left % 4))
12279 int element = Tile[magic_wall_x][magic_wall_y];
12281 if (element == EL_BD_MAGIC_WALL_FULL ||
12282 element == EL_BD_MAGIC_WALL_ACTIVE ||
12283 element == EL_BD_MAGIC_WALL_EMPTYING)
12284 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12285 else if (element == EL_DC_MAGIC_WALL_FULL ||
12286 element == EL_DC_MAGIC_WALL_ACTIVE ||
12287 element == EL_DC_MAGIC_WALL_EMPTYING)
12288 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12290 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12293 if (game.magic_wall_time_left > 0)
12295 game.magic_wall_time_left--;
12297 if (!game.magic_wall_time_left)
12299 SCAN_PLAYFIELD(x, y)
12301 element = Tile[x][y];
12303 if (element == EL_MAGIC_WALL_ACTIVE ||
12304 element == EL_MAGIC_WALL_FULL)
12306 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12307 TEST_DrawLevelField(x, y);
12309 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12310 element == EL_BD_MAGIC_WALL_FULL)
12312 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12313 TEST_DrawLevelField(x, y);
12315 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12316 element == EL_DC_MAGIC_WALL_FULL)
12318 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12319 TEST_DrawLevelField(x, y);
12323 game.magic_wall_active = FALSE;
12328 if (game.light_time_left > 0)
12330 game.light_time_left--;
12332 if (game.light_time_left == 0)
12333 RedrawAllLightSwitchesAndInvisibleElements();
12336 if (game.timegate_time_left > 0)
12338 game.timegate_time_left--;
12340 if (game.timegate_time_left == 0)
12341 CloseAllOpenTimegates();
12344 if (game.lenses_time_left > 0)
12346 game.lenses_time_left--;
12348 if (game.lenses_time_left == 0)
12349 RedrawAllInvisibleElementsForLenses();
12352 if (game.magnify_time_left > 0)
12354 game.magnify_time_left--;
12356 if (game.magnify_time_left == 0)
12357 RedrawAllInvisibleElementsForMagnifier();
12360 for (i = 0; i < MAX_PLAYERS; i++)
12362 struct PlayerInfo *player = &stored_player[i];
12364 if (SHIELD_ON(player))
12366 if (player->shield_deadly_time_left)
12367 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12368 else if (player->shield_normal_time_left)
12369 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12373 #if USE_DELAYED_GFX_REDRAW
12374 SCAN_PLAYFIELD(x, y)
12376 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12378 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12379 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12381 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12382 DrawLevelField(x, y);
12384 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12385 DrawLevelFieldCrumbled(x, y);
12387 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12388 DrawLevelFieldCrumbledNeighbours(x, y);
12390 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12391 DrawTwinkleOnField(x, y);
12394 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12399 PlayAllPlayersSound();
12401 for (i = 0; i < MAX_PLAYERS; i++)
12403 struct PlayerInfo *player = &stored_player[i];
12405 if (player->show_envelope != 0 && (!player->active ||
12406 player->MovPos == 0))
12408 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12410 player->show_envelope = 0;
12414 // use random number generator in every frame to make it less predictable
12415 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12418 mouse_action_last = mouse_action;
12421 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12423 int min_x = x, min_y = y, max_x = x, max_y = y;
12426 for (i = 0; i < MAX_PLAYERS; i++)
12428 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12430 if (!stored_player[i].active || &stored_player[i] == player)
12433 min_x = MIN(min_x, jx);
12434 min_y = MIN(min_y, jy);
12435 max_x = MAX(max_x, jx);
12436 max_y = MAX(max_y, jy);
12439 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12442 static boolean AllPlayersInVisibleScreen(void)
12446 for (i = 0; i < MAX_PLAYERS; i++)
12448 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12450 if (!stored_player[i].active)
12453 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12460 void ScrollLevel(int dx, int dy)
12462 int scroll_offset = 2 * TILEX_VAR;
12465 BlitBitmap(drawto_field, drawto_field,
12466 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12467 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12468 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12469 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12470 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12471 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12475 x = (dx == 1 ? BX1 : BX2);
12476 for (y = BY1; y <= BY2; y++)
12477 DrawScreenField(x, y);
12482 y = (dy == 1 ? BY1 : BY2);
12483 for (x = BX1; x <= BX2; x++)
12484 DrawScreenField(x, y);
12487 redraw_mask |= REDRAW_FIELD;
12490 static boolean canFallDown(struct PlayerInfo *player)
12492 int jx = player->jx, jy = player->jy;
12494 return (IN_LEV_FIELD(jx, jy + 1) &&
12495 (IS_FREE(jx, jy + 1) ||
12496 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12497 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12498 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12501 static boolean canPassField(int x, int y, int move_dir)
12503 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12504 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12505 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12506 int nextx = x + dx;
12507 int nexty = y + dy;
12508 int element = Tile[x][y];
12510 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12511 !CAN_MOVE(element) &&
12512 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12513 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12514 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12517 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12519 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12520 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12521 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12525 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12526 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12527 (IS_DIGGABLE(Tile[newx][newy]) ||
12528 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12529 canPassField(newx, newy, move_dir)));
12532 static void CheckGravityMovement(struct PlayerInfo *player)
12534 if (player->gravity && !player->programmed_action)
12536 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12537 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12538 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12539 int jx = player->jx, jy = player->jy;
12540 boolean player_is_moving_to_valid_field =
12541 (!player_is_snapping &&
12542 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12543 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12544 boolean player_can_fall_down = canFallDown(player);
12546 if (player_can_fall_down &&
12547 !player_is_moving_to_valid_field)
12548 player->programmed_action = MV_DOWN;
12552 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12554 return CheckGravityMovement(player);
12556 if (player->gravity && !player->programmed_action)
12558 int jx = player->jx, jy = player->jy;
12559 boolean field_under_player_is_free =
12560 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12561 boolean player_is_standing_on_valid_field =
12562 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12563 (IS_WALKABLE(Tile[jx][jy]) &&
12564 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12566 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12567 player->programmed_action = MV_DOWN;
12572 MovePlayerOneStep()
12573 -----------------------------------------------------------------------------
12574 dx, dy: direction (non-diagonal) to try to move the player to
12575 real_dx, real_dy: direction as read from input device (can be diagonal)
12578 boolean MovePlayerOneStep(struct PlayerInfo *player,
12579 int dx, int dy, int real_dx, int real_dy)
12581 int jx = player->jx, jy = player->jy;
12582 int new_jx = jx + dx, new_jy = jy + dy;
12584 boolean player_can_move = !player->cannot_move;
12586 if (!player->active || (!dx && !dy))
12587 return MP_NO_ACTION;
12589 player->MovDir = (dx < 0 ? MV_LEFT :
12590 dx > 0 ? MV_RIGHT :
12592 dy > 0 ? MV_DOWN : MV_NONE);
12594 if (!IN_LEV_FIELD(new_jx, new_jy))
12595 return MP_NO_ACTION;
12597 if (!player_can_move)
12599 if (player->MovPos == 0)
12601 player->is_moving = FALSE;
12602 player->is_digging = FALSE;
12603 player->is_collecting = FALSE;
12604 player->is_snapping = FALSE;
12605 player->is_pushing = FALSE;
12609 if (!network.enabled && game.centered_player_nr == -1 &&
12610 !AllPlayersInSight(player, new_jx, new_jy))
12611 return MP_NO_ACTION;
12613 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12614 if (can_move != MP_MOVING)
12617 // check if DigField() has caused relocation of the player
12618 if (player->jx != jx || player->jy != jy)
12619 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12621 StorePlayer[jx][jy] = 0;
12622 player->last_jx = jx;
12623 player->last_jy = jy;
12624 player->jx = new_jx;
12625 player->jy = new_jy;
12626 StorePlayer[new_jx][new_jy] = player->element_nr;
12628 if (player->move_delay_value_next != -1)
12630 player->move_delay_value = player->move_delay_value_next;
12631 player->move_delay_value_next = -1;
12635 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12637 player->step_counter++;
12639 PlayerVisit[jx][jy] = FrameCounter;
12641 player->is_moving = TRUE;
12644 // should better be called in MovePlayer(), but this breaks some tapes
12645 ScrollPlayer(player, SCROLL_INIT);
12651 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12653 int jx = player->jx, jy = player->jy;
12654 int old_jx = jx, old_jy = jy;
12655 int moved = MP_NO_ACTION;
12657 if (!player->active)
12662 if (player->MovPos == 0)
12664 player->is_moving = FALSE;
12665 player->is_digging = FALSE;
12666 player->is_collecting = FALSE;
12667 player->is_snapping = FALSE;
12668 player->is_pushing = FALSE;
12674 if (player->move_delay > 0)
12677 player->move_delay = -1; // set to "uninitialized" value
12679 // store if player is automatically moved to next field
12680 player->is_auto_moving = (player->programmed_action != MV_NONE);
12682 // remove the last programmed player action
12683 player->programmed_action = 0;
12685 if (player->MovPos)
12687 // should only happen if pre-1.2 tape recordings are played
12688 // this is only for backward compatibility
12690 int original_move_delay_value = player->move_delay_value;
12693 Debug("game:playing:MovePlayer",
12694 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12698 // scroll remaining steps with finest movement resolution
12699 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12701 while (player->MovPos)
12703 ScrollPlayer(player, SCROLL_GO_ON);
12704 ScrollScreen(NULL, SCROLL_GO_ON);
12706 AdvanceFrameAndPlayerCounters(player->index_nr);
12709 BackToFront_WithFrameDelay(0);
12712 player->move_delay_value = original_move_delay_value;
12715 player->is_active = FALSE;
12717 if (player->last_move_dir & MV_HORIZONTAL)
12719 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12720 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12724 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12725 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12728 if (!moved && !player->is_active)
12730 player->is_moving = FALSE;
12731 player->is_digging = FALSE;
12732 player->is_collecting = FALSE;
12733 player->is_snapping = FALSE;
12734 player->is_pushing = FALSE;
12740 if (moved & MP_MOVING && !ScreenMovPos &&
12741 (player->index_nr == game.centered_player_nr ||
12742 game.centered_player_nr == -1))
12744 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12746 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12748 // actual player has left the screen -- scroll in that direction
12749 if (jx != old_jx) // player has moved horizontally
12750 scroll_x += (jx - old_jx);
12751 else // player has moved vertically
12752 scroll_y += (jy - old_jy);
12756 int offset_raw = game.scroll_delay_value;
12758 if (jx != old_jx) // player has moved horizontally
12760 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12761 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12762 int new_scroll_x = jx - MIDPOSX + offset_x;
12764 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12765 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12766 scroll_x = new_scroll_x;
12768 // don't scroll over playfield boundaries
12769 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12771 // don't scroll more than one field at a time
12772 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12774 // don't scroll against the player's moving direction
12775 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12776 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12777 scroll_x = old_scroll_x;
12779 else // player has moved vertically
12781 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12782 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12783 int new_scroll_y = jy - MIDPOSY + offset_y;
12785 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12786 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12787 scroll_y = new_scroll_y;
12789 // don't scroll over playfield boundaries
12790 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12792 // don't scroll more than one field at a time
12793 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12795 // don't scroll against the player's moving direction
12796 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12797 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12798 scroll_y = old_scroll_y;
12802 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12804 if (!network.enabled && game.centered_player_nr == -1 &&
12805 !AllPlayersInVisibleScreen())
12807 scroll_x = old_scroll_x;
12808 scroll_y = old_scroll_y;
12812 ScrollScreen(player, SCROLL_INIT);
12813 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12818 player->StepFrame = 0;
12820 if (moved & MP_MOVING)
12822 if (old_jx != jx && old_jy == jy)
12823 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12824 else if (old_jx == jx && old_jy != jy)
12825 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12827 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12829 player->last_move_dir = player->MovDir;
12830 player->is_moving = TRUE;
12831 player->is_snapping = FALSE;
12832 player->is_switching = FALSE;
12833 player->is_dropping = FALSE;
12834 player->is_dropping_pressed = FALSE;
12835 player->drop_pressed_delay = 0;
12838 // should better be called here than above, but this breaks some tapes
12839 ScrollPlayer(player, SCROLL_INIT);
12844 CheckGravityMovementWhenNotMoving(player);
12846 player->is_moving = FALSE;
12848 /* at this point, the player is allowed to move, but cannot move right now
12849 (e.g. because of something blocking the way) -- ensure that the player
12850 is also allowed to move in the next frame (in old versions before 3.1.1,
12851 the player was forced to wait again for eight frames before next try) */
12853 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12854 player->move_delay = 0; // allow direct movement in the next frame
12857 if (player->move_delay == -1) // not yet initialized by DigField()
12858 player->move_delay = player->move_delay_value;
12860 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12862 TestIfPlayerTouchesBadThing(jx, jy);
12863 TestIfPlayerTouchesCustomElement(jx, jy);
12866 if (!player->active)
12867 RemovePlayer(player);
12872 void ScrollPlayer(struct PlayerInfo *player, int mode)
12874 int jx = player->jx, jy = player->jy;
12875 int last_jx = player->last_jx, last_jy = player->last_jy;
12876 int move_stepsize = TILEX / player->move_delay_value;
12878 if (!player->active)
12881 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12884 if (mode == SCROLL_INIT)
12886 player->actual_frame_counter = FrameCounter;
12887 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12889 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12890 Tile[last_jx][last_jy] == EL_EMPTY)
12892 int last_field_block_delay = 0; // start with no blocking at all
12893 int block_delay_adjustment = player->block_delay_adjustment;
12895 // if player blocks last field, add delay for exactly one move
12896 if (player->block_last_field)
12898 last_field_block_delay += player->move_delay_value;
12900 // when blocking enabled, prevent moving up despite gravity
12901 if (player->gravity && player->MovDir == MV_UP)
12902 block_delay_adjustment = -1;
12905 // add block delay adjustment (also possible when not blocking)
12906 last_field_block_delay += block_delay_adjustment;
12908 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12909 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12912 if (player->MovPos != 0) // player has not yet reached destination
12915 else if (!FrameReached(&player->actual_frame_counter, 1))
12918 if (player->MovPos != 0)
12920 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12921 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12923 // before DrawPlayer() to draw correct player graphic for this case
12924 if (player->MovPos == 0)
12925 CheckGravityMovement(player);
12928 if (player->MovPos == 0) // player reached destination field
12930 if (player->move_delay_reset_counter > 0)
12932 player->move_delay_reset_counter--;
12934 if (player->move_delay_reset_counter == 0)
12936 // continue with normal speed after quickly moving through gate
12937 HALVE_PLAYER_SPEED(player);
12939 // be able to make the next move without delay
12940 player->move_delay = 0;
12944 player->last_jx = jx;
12945 player->last_jy = jy;
12947 if (Tile[jx][jy] == EL_EXIT_OPEN ||
12948 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
12949 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
12950 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
12951 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12952 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12953 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
12954 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12956 ExitPlayer(player);
12958 if (game.players_still_needed == 0 &&
12959 (game.friends_still_needed == 0 ||
12960 IS_SP_ELEMENT(Tile[jx][jy])))
12964 // this breaks one level: "machine", level 000
12966 int move_direction = player->MovDir;
12967 int enter_side = MV_DIR_OPPOSITE(move_direction);
12968 int leave_side = move_direction;
12969 int old_jx = last_jx;
12970 int old_jy = last_jy;
12971 int old_element = Tile[old_jx][old_jy];
12972 int new_element = Tile[jx][jy];
12974 if (IS_CUSTOM_ELEMENT(old_element))
12975 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12977 player->index_bit, leave_side);
12979 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12980 CE_PLAYER_LEAVES_X,
12981 player->index_bit, leave_side);
12983 if (IS_CUSTOM_ELEMENT(new_element))
12984 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12985 player->index_bit, enter_side);
12987 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12988 CE_PLAYER_ENTERS_X,
12989 player->index_bit, enter_side);
12991 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12992 CE_MOVE_OF_X, move_direction);
12995 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12997 TestIfPlayerTouchesBadThing(jx, jy);
12998 TestIfPlayerTouchesCustomElement(jx, jy);
13000 /* needed because pushed element has not yet reached its destination,
13001 so it would trigger a change event at its previous field location */
13002 if (!player->is_pushing)
13003 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13005 if (!player->active)
13006 RemovePlayer(player);
13009 if (!game.LevelSolved && level.use_step_counter)
13019 if (TimeLeft <= 10 && setup.time_limit)
13020 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13022 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13024 DisplayGameControlValues();
13026 if (!TimeLeft && setup.time_limit)
13027 for (i = 0; i < MAX_PLAYERS; i++)
13028 KillPlayer(&stored_player[i]);
13030 else if (game.no_time_limit && !game.all_players_gone)
13032 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13034 DisplayGameControlValues();
13038 if (tape.single_step && tape.recording && !tape.pausing &&
13039 !player->programmed_action)
13040 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13042 if (!player->programmed_action)
13043 CheckSaveEngineSnapshot(player);
13047 void ScrollScreen(struct PlayerInfo *player, int mode)
13049 static unsigned int screen_frame_counter = 0;
13051 if (mode == SCROLL_INIT)
13053 // set scrolling step size according to actual player's moving speed
13054 ScrollStepSize = TILEX / player->move_delay_value;
13056 screen_frame_counter = FrameCounter;
13057 ScreenMovDir = player->MovDir;
13058 ScreenMovPos = player->MovPos;
13059 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13062 else if (!FrameReached(&screen_frame_counter, 1))
13067 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13068 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13069 redraw_mask |= REDRAW_FIELD;
13072 ScreenMovDir = MV_NONE;
13075 void TestIfPlayerTouchesCustomElement(int x, int y)
13077 static int xy[4][2] =
13084 static int trigger_sides[4][2] =
13086 // center side border side
13087 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13088 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13089 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13090 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13092 static int touch_dir[4] =
13094 MV_LEFT | MV_RIGHT,
13099 int center_element = Tile[x][y]; // should always be non-moving!
13102 for (i = 0; i < NUM_DIRECTIONS; i++)
13104 int xx = x + xy[i][0];
13105 int yy = y + xy[i][1];
13106 int center_side = trigger_sides[i][0];
13107 int border_side = trigger_sides[i][1];
13108 int border_element;
13110 if (!IN_LEV_FIELD(xx, yy))
13113 if (IS_PLAYER(x, y)) // player found at center element
13115 struct PlayerInfo *player = PLAYERINFO(x, y);
13117 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13118 border_element = Tile[xx][yy]; // may be moving!
13119 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13120 border_element = Tile[xx][yy];
13121 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13122 border_element = MovingOrBlocked2Element(xx, yy);
13124 continue; // center and border element do not touch
13126 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13127 player->index_bit, border_side);
13128 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13129 CE_PLAYER_TOUCHES_X,
13130 player->index_bit, border_side);
13133 /* use player element that is initially defined in the level playfield,
13134 not the player element that corresponds to the runtime player number
13135 (example: a level that contains EL_PLAYER_3 as the only player would
13136 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13137 int player_element = PLAYERINFO(x, y)->initial_element;
13139 CheckElementChangeBySide(xx, yy, border_element, player_element,
13140 CE_TOUCHING_X, border_side);
13143 else if (IS_PLAYER(xx, yy)) // player found at border element
13145 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13147 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13149 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13150 continue; // center and border element do not touch
13153 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13154 player->index_bit, center_side);
13155 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13156 CE_PLAYER_TOUCHES_X,
13157 player->index_bit, center_side);
13160 /* use player element that is initially defined in the level playfield,
13161 not the player element that corresponds to the runtime player number
13162 (example: a level that contains EL_PLAYER_3 as the only player would
13163 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13164 int player_element = PLAYERINFO(xx, yy)->initial_element;
13166 CheckElementChangeBySide(x, y, center_element, player_element,
13167 CE_TOUCHING_X, center_side);
13175 void TestIfElementTouchesCustomElement(int x, int y)
13177 static int xy[4][2] =
13184 static int trigger_sides[4][2] =
13186 // center side border side
13187 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13188 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13189 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13190 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13192 static int touch_dir[4] =
13194 MV_LEFT | MV_RIGHT,
13199 boolean change_center_element = FALSE;
13200 int center_element = Tile[x][y]; // should always be non-moving!
13201 int border_element_old[NUM_DIRECTIONS];
13204 for (i = 0; i < NUM_DIRECTIONS; i++)
13206 int xx = x + xy[i][0];
13207 int yy = y + xy[i][1];
13208 int border_element;
13210 border_element_old[i] = -1;
13212 if (!IN_LEV_FIELD(xx, yy))
13215 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13216 border_element = Tile[xx][yy]; // may be moving!
13217 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13218 border_element = Tile[xx][yy];
13219 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13220 border_element = MovingOrBlocked2Element(xx, yy);
13222 continue; // center and border element do not touch
13224 border_element_old[i] = border_element;
13227 for (i = 0; i < NUM_DIRECTIONS; i++)
13229 int xx = x + xy[i][0];
13230 int yy = y + xy[i][1];
13231 int center_side = trigger_sides[i][0];
13232 int border_element = border_element_old[i];
13234 if (border_element == -1)
13237 // check for change of border element
13238 CheckElementChangeBySide(xx, yy, border_element, center_element,
13239 CE_TOUCHING_X, center_side);
13241 // (center element cannot be player, so we dont have to check this here)
13244 for (i = 0; i < NUM_DIRECTIONS; i++)
13246 int xx = x + xy[i][0];
13247 int yy = y + xy[i][1];
13248 int border_side = trigger_sides[i][1];
13249 int border_element = border_element_old[i];
13251 if (border_element == -1)
13254 // check for change of center element (but change it only once)
13255 if (!change_center_element)
13256 change_center_element =
13257 CheckElementChangeBySide(x, y, center_element, border_element,
13258 CE_TOUCHING_X, border_side);
13260 if (IS_PLAYER(xx, yy))
13262 /* use player element that is initially defined in the level playfield,
13263 not the player element that corresponds to the runtime player number
13264 (example: a level that contains EL_PLAYER_3 as the only player would
13265 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13266 int player_element = PLAYERINFO(xx, yy)->initial_element;
13268 CheckElementChangeBySide(x, y, center_element, player_element,
13269 CE_TOUCHING_X, border_side);
13274 void TestIfElementHitsCustomElement(int x, int y, int direction)
13276 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13277 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13278 int hitx = x + dx, hity = y + dy;
13279 int hitting_element = Tile[x][y];
13280 int touched_element;
13282 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13285 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13286 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13288 if (IN_LEV_FIELD(hitx, hity))
13290 int opposite_direction = MV_DIR_OPPOSITE(direction);
13291 int hitting_side = direction;
13292 int touched_side = opposite_direction;
13293 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13294 MovDir[hitx][hity] != direction ||
13295 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13301 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13302 CE_HITTING_X, touched_side);
13304 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13305 CE_HIT_BY_X, hitting_side);
13307 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13308 CE_HIT_BY_SOMETHING, opposite_direction);
13310 if (IS_PLAYER(hitx, hity))
13312 /* use player element that is initially defined in the level playfield,
13313 not the player element that corresponds to the runtime player number
13314 (example: a level that contains EL_PLAYER_3 as the only player would
13315 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13316 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13318 CheckElementChangeBySide(x, y, hitting_element, player_element,
13319 CE_HITTING_X, touched_side);
13324 // "hitting something" is also true when hitting the playfield border
13325 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13326 CE_HITTING_SOMETHING, direction);
13329 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13331 int i, kill_x = -1, kill_y = -1;
13333 int bad_element = -1;
13334 static int test_xy[4][2] =
13341 static int test_dir[4] =
13349 for (i = 0; i < NUM_DIRECTIONS; i++)
13351 int test_x, test_y, test_move_dir, test_element;
13353 test_x = good_x + test_xy[i][0];
13354 test_y = good_y + test_xy[i][1];
13356 if (!IN_LEV_FIELD(test_x, test_y))
13360 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13362 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13364 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13365 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13367 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13368 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13372 bad_element = test_element;
13378 if (kill_x != -1 || kill_y != -1)
13380 if (IS_PLAYER(good_x, good_y))
13382 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13384 if (player->shield_deadly_time_left > 0 &&
13385 !IS_INDESTRUCTIBLE(bad_element))
13386 Bang(kill_x, kill_y);
13387 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13388 KillPlayer(player);
13391 Bang(good_x, good_y);
13395 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13397 int i, kill_x = -1, kill_y = -1;
13398 int bad_element = Tile[bad_x][bad_y];
13399 static int test_xy[4][2] =
13406 static int touch_dir[4] =
13408 MV_LEFT | MV_RIGHT,
13413 static int test_dir[4] =
13421 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13424 for (i = 0; i < NUM_DIRECTIONS; i++)
13426 int test_x, test_y, test_move_dir, test_element;
13428 test_x = bad_x + test_xy[i][0];
13429 test_y = bad_y + test_xy[i][1];
13431 if (!IN_LEV_FIELD(test_x, test_y))
13435 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13437 test_element = Tile[test_x][test_y];
13439 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13440 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13442 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13443 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13445 // good thing is player or penguin that does not move away
13446 if (IS_PLAYER(test_x, test_y))
13448 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13450 if (bad_element == EL_ROBOT && player->is_moving)
13451 continue; // robot does not kill player if he is moving
13453 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13455 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13456 continue; // center and border element do not touch
13464 else if (test_element == EL_PENGUIN)
13474 if (kill_x != -1 || kill_y != -1)
13476 if (IS_PLAYER(kill_x, kill_y))
13478 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13480 if (player->shield_deadly_time_left > 0 &&
13481 !IS_INDESTRUCTIBLE(bad_element))
13482 Bang(bad_x, bad_y);
13483 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13484 KillPlayer(player);
13487 Bang(kill_x, kill_y);
13491 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13493 int bad_element = Tile[bad_x][bad_y];
13494 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13495 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13496 int test_x = bad_x + dx, test_y = bad_y + dy;
13497 int test_move_dir, test_element;
13498 int kill_x = -1, kill_y = -1;
13500 if (!IN_LEV_FIELD(test_x, test_y))
13504 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13506 test_element = Tile[test_x][test_y];
13508 if (test_move_dir != bad_move_dir)
13510 // good thing can be player or penguin that does not move away
13511 if (IS_PLAYER(test_x, test_y))
13513 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13515 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13516 player as being hit when he is moving towards the bad thing, because
13517 the "get hit by" condition would be lost after the player stops) */
13518 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13519 return; // player moves away from bad thing
13524 else if (test_element == EL_PENGUIN)
13531 if (kill_x != -1 || kill_y != -1)
13533 if (IS_PLAYER(kill_x, kill_y))
13535 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13537 if (player->shield_deadly_time_left > 0 &&
13538 !IS_INDESTRUCTIBLE(bad_element))
13539 Bang(bad_x, bad_y);
13540 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13541 KillPlayer(player);
13544 Bang(kill_x, kill_y);
13548 void TestIfPlayerTouchesBadThing(int x, int y)
13550 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13553 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13555 TestIfGoodThingHitsBadThing(x, y, move_dir);
13558 void TestIfBadThingTouchesPlayer(int x, int y)
13560 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13563 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13565 TestIfBadThingHitsGoodThing(x, y, move_dir);
13568 void TestIfFriendTouchesBadThing(int x, int y)
13570 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13573 void TestIfBadThingTouchesFriend(int x, int y)
13575 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13578 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13580 int i, kill_x = bad_x, kill_y = bad_y;
13581 static int xy[4][2] =
13589 for (i = 0; i < NUM_DIRECTIONS; i++)
13593 x = bad_x + xy[i][0];
13594 y = bad_y + xy[i][1];
13595 if (!IN_LEV_FIELD(x, y))
13598 element = Tile[x][y];
13599 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13600 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13608 if (kill_x != bad_x || kill_y != bad_y)
13609 Bang(bad_x, bad_y);
13612 void KillPlayer(struct PlayerInfo *player)
13614 int jx = player->jx, jy = player->jy;
13616 if (!player->active)
13620 Debug("game:playing:KillPlayer",
13621 "0: killed == %d, active == %d, reanimated == %d",
13622 player->killed, player->active, player->reanimated);
13625 /* the following code was introduced to prevent an infinite loop when calling
13627 -> CheckTriggeredElementChangeExt()
13628 -> ExecuteCustomElementAction()
13630 -> (infinitely repeating the above sequence of function calls)
13631 which occurs when killing the player while having a CE with the setting
13632 "kill player X when explosion of <player X>"; the solution using a new
13633 field "player->killed" was chosen for backwards compatibility, although
13634 clever use of the fields "player->active" etc. would probably also work */
13636 if (player->killed)
13640 player->killed = TRUE;
13642 // remove accessible field at the player's position
13643 Tile[jx][jy] = EL_EMPTY;
13645 // deactivate shield (else Bang()/Explode() would not work right)
13646 player->shield_normal_time_left = 0;
13647 player->shield_deadly_time_left = 0;
13650 Debug("game:playing:KillPlayer",
13651 "1: killed == %d, active == %d, reanimated == %d",
13652 player->killed, player->active, player->reanimated);
13658 Debug("game:playing:KillPlayer",
13659 "2: killed == %d, active == %d, reanimated == %d",
13660 player->killed, player->active, player->reanimated);
13663 if (player->reanimated) // killed player may have been reanimated
13664 player->killed = player->reanimated = FALSE;
13666 BuryPlayer(player);
13669 static void KillPlayerUnlessEnemyProtected(int x, int y)
13671 if (!PLAYER_ENEMY_PROTECTED(x, y))
13672 KillPlayer(PLAYERINFO(x, y));
13675 static void KillPlayerUnlessExplosionProtected(int x, int y)
13677 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13678 KillPlayer(PLAYERINFO(x, y));
13681 void BuryPlayer(struct PlayerInfo *player)
13683 int jx = player->jx, jy = player->jy;
13685 if (!player->active)
13688 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13689 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13691 RemovePlayer(player);
13693 player->buried = TRUE;
13695 if (game.all_players_gone)
13696 game.GameOver = TRUE;
13699 void RemovePlayer(struct PlayerInfo *player)
13701 int jx = player->jx, jy = player->jy;
13702 int i, found = FALSE;
13704 player->present = FALSE;
13705 player->active = FALSE;
13707 // required for some CE actions (even if the player is not active anymore)
13708 player->MovPos = 0;
13710 if (!ExplodeField[jx][jy])
13711 StorePlayer[jx][jy] = 0;
13713 if (player->is_moving)
13714 TEST_DrawLevelField(player->last_jx, player->last_jy);
13716 for (i = 0; i < MAX_PLAYERS; i++)
13717 if (stored_player[i].active)
13722 game.all_players_gone = TRUE;
13723 game.GameOver = TRUE;
13726 game.exit_x = game.robot_wheel_x = jx;
13727 game.exit_y = game.robot_wheel_y = jy;
13730 void ExitPlayer(struct PlayerInfo *player)
13732 DrawPlayer(player); // needed here only to cleanup last field
13733 RemovePlayer(player);
13735 if (game.players_still_needed > 0)
13736 game.players_still_needed--;
13739 static void setFieldForSnapping(int x, int y, int element, int direction)
13741 struct ElementInfo *ei = &element_info[element];
13742 int direction_bit = MV_DIR_TO_BIT(direction);
13743 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13744 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13745 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13747 Tile[x][y] = EL_ELEMENT_SNAPPING;
13748 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13750 ResetGfxAnimation(x, y);
13752 GfxElement[x][y] = element;
13753 GfxAction[x][y] = action;
13754 GfxDir[x][y] = direction;
13755 GfxFrame[x][y] = -1;
13759 =============================================================================
13760 checkDiagonalPushing()
13761 -----------------------------------------------------------------------------
13762 check if diagonal input device direction results in pushing of object
13763 (by checking if the alternative direction is walkable, diggable, ...)
13764 =============================================================================
13767 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13768 int x, int y, int real_dx, int real_dy)
13770 int jx, jy, dx, dy, xx, yy;
13772 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13775 // diagonal direction: check alternative direction
13780 xx = jx + (dx == 0 ? real_dx : 0);
13781 yy = jy + (dy == 0 ? real_dy : 0);
13783 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13787 =============================================================================
13789 -----------------------------------------------------------------------------
13790 x, y: field next to player (non-diagonal) to try to dig to
13791 real_dx, real_dy: direction as read from input device (can be diagonal)
13792 =============================================================================
13795 static int DigField(struct PlayerInfo *player,
13796 int oldx, int oldy, int x, int y,
13797 int real_dx, int real_dy, int mode)
13799 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13800 boolean player_was_pushing = player->is_pushing;
13801 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13802 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13803 int jx = oldx, jy = oldy;
13804 int dx = x - jx, dy = y - jy;
13805 int nextx = x + dx, nexty = y + dy;
13806 int move_direction = (dx == -1 ? MV_LEFT :
13807 dx == +1 ? MV_RIGHT :
13809 dy == +1 ? MV_DOWN : MV_NONE);
13810 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13811 int dig_side = MV_DIR_OPPOSITE(move_direction);
13812 int old_element = Tile[jx][jy];
13813 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13816 if (is_player) // function can also be called by EL_PENGUIN
13818 if (player->MovPos == 0)
13820 player->is_digging = FALSE;
13821 player->is_collecting = FALSE;
13824 if (player->MovPos == 0) // last pushing move finished
13825 player->is_pushing = FALSE;
13827 if (mode == DF_NO_PUSH) // player just stopped pushing
13829 player->is_switching = FALSE;
13830 player->push_delay = -1;
13832 return MP_NO_ACTION;
13836 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13837 old_element = Back[jx][jy];
13839 // in case of element dropped at player position, check background
13840 else if (Back[jx][jy] != EL_EMPTY &&
13841 game.engine_version >= VERSION_IDENT(2,2,0,0))
13842 old_element = Back[jx][jy];
13844 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13845 return MP_NO_ACTION; // field has no opening in this direction
13847 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13848 return MP_NO_ACTION; // field has no opening in this direction
13850 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13854 Tile[jx][jy] = player->artwork_element;
13855 InitMovingField(jx, jy, MV_DOWN);
13856 Store[jx][jy] = EL_ACID;
13857 ContinueMoving(jx, jy);
13858 BuryPlayer(player);
13860 return MP_DONT_RUN_INTO;
13863 if (player_can_move && DONT_RUN_INTO(element))
13865 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13867 return MP_DONT_RUN_INTO;
13870 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13871 return MP_NO_ACTION;
13873 collect_count = element_info[element].collect_count_initial;
13875 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13876 return MP_NO_ACTION;
13878 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13879 player_can_move = player_can_move_or_snap;
13881 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13882 game.engine_version >= VERSION_IDENT(2,2,0,0))
13884 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13885 player->index_bit, dig_side);
13886 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13887 player->index_bit, dig_side);
13889 if (element == EL_DC_LANDMINE)
13892 if (Tile[x][y] != element) // field changed by snapping
13895 return MP_NO_ACTION;
13898 if (player->gravity && is_player && !player->is_auto_moving &&
13899 canFallDown(player) && move_direction != MV_DOWN &&
13900 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13901 return MP_NO_ACTION; // player cannot walk here due to gravity
13903 if (player_can_move &&
13904 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13906 int sound_element = SND_ELEMENT(element);
13907 int sound_action = ACTION_WALKING;
13909 if (IS_RND_GATE(element))
13911 if (!player->key[RND_GATE_NR(element)])
13912 return MP_NO_ACTION;
13914 else if (IS_RND_GATE_GRAY(element))
13916 if (!player->key[RND_GATE_GRAY_NR(element)])
13917 return MP_NO_ACTION;
13919 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13921 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13922 return MP_NO_ACTION;
13924 else if (element == EL_EXIT_OPEN ||
13925 element == EL_EM_EXIT_OPEN ||
13926 element == EL_EM_EXIT_OPENING ||
13927 element == EL_STEEL_EXIT_OPEN ||
13928 element == EL_EM_STEEL_EXIT_OPEN ||
13929 element == EL_EM_STEEL_EXIT_OPENING ||
13930 element == EL_SP_EXIT_OPEN ||
13931 element == EL_SP_EXIT_OPENING)
13933 sound_action = ACTION_PASSING; // player is passing exit
13935 else if (element == EL_EMPTY)
13937 sound_action = ACTION_MOVING; // nothing to walk on
13940 // play sound from background or player, whatever is available
13941 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13942 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13944 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13946 else if (player_can_move &&
13947 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13949 if (!ACCESS_FROM(element, opposite_direction))
13950 return MP_NO_ACTION; // field not accessible from this direction
13952 if (CAN_MOVE(element)) // only fixed elements can be passed!
13953 return MP_NO_ACTION;
13955 if (IS_EM_GATE(element))
13957 if (!player->key[EM_GATE_NR(element)])
13958 return MP_NO_ACTION;
13960 else if (IS_EM_GATE_GRAY(element))
13962 if (!player->key[EM_GATE_GRAY_NR(element)])
13963 return MP_NO_ACTION;
13965 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13967 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13968 return MP_NO_ACTION;
13970 else if (IS_EMC_GATE(element))
13972 if (!player->key[EMC_GATE_NR(element)])
13973 return MP_NO_ACTION;
13975 else if (IS_EMC_GATE_GRAY(element))
13977 if (!player->key[EMC_GATE_GRAY_NR(element)])
13978 return MP_NO_ACTION;
13980 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13982 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13983 return MP_NO_ACTION;
13985 else if (element == EL_DC_GATE_WHITE ||
13986 element == EL_DC_GATE_WHITE_GRAY ||
13987 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13989 if (player->num_white_keys == 0)
13990 return MP_NO_ACTION;
13992 player->num_white_keys--;
13994 else if (IS_SP_PORT(element))
13996 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13997 element == EL_SP_GRAVITY_PORT_RIGHT ||
13998 element == EL_SP_GRAVITY_PORT_UP ||
13999 element == EL_SP_GRAVITY_PORT_DOWN)
14000 player->gravity = !player->gravity;
14001 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14002 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14003 element == EL_SP_GRAVITY_ON_PORT_UP ||
14004 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14005 player->gravity = TRUE;
14006 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14007 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14008 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14009 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14010 player->gravity = FALSE;
14013 // automatically move to the next field with double speed
14014 player->programmed_action = move_direction;
14016 if (player->move_delay_reset_counter == 0)
14018 player->move_delay_reset_counter = 2; // two double speed steps
14020 DOUBLE_PLAYER_SPEED(player);
14023 PlayLevelSoundAction(x, y, ACTION_PASSING);
14025 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14029 if (mode != DF_SNAP)
14031 GfxElement[x][y] = GFX_ELEMENT(element);
14032 player->is_digging = TRUE;
14035 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14037 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14038 player->index_bit, dig_side);
14040 // if digging triggered player relocation, finish digging tile
14041 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14042 setFieldForSnapping(x, y, element, move_direction);
14044 if (mode == DF_SNAP)
14046 if (level.block_snap_field)
14047 setFieldForSnapping(x, y, element, move_direction);
14049 TestIfElementTouchesCustomElement(x, y); // for empty space
14051 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14052 player->index_bit, dig_side);
14055 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14059 if (is_player && mode != DF_SNAP)
14061 GfxElement[x][y] = element;
14062 player->is_collecting = TRUE;
14065 if (element == EL_SPEED_PILL)
14067 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14069 else if (element == EL_EXTRA_TIME && level.time > 0)
14071 TimeLeft += level.extra_time;
14073 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14075 DisplayGameControlValues();
14077 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14079 player->shield_normal_time_left += level.shield_normal_time;
14080 if (element == EL_SHIELD_DEADLY)
14081 player->shield_deadly_time_left += level.shield_deadly_time;
14083 else if (element == EL_DYNAMITE ||
14084 element == EL_EM_DYNAMITE ||
14085 element == EL_SP_DISK_RED)
14087 if (player->inventory_size < MAX_INVENTORY_SIZE)
14088 player->inventory_element[player->inventory_size++] = element;
14090 DrawGameDoorValues();
14092 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14094 player->dynabomb_count++;
14095 player->dynabombs_left++;
14097 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14099 player->dynabomb_size++;
14101 else if (element == EL_DYNABOMB_INCREASE_POWER)
14103 player->dynabomb_xl = TRUE;
14105 else if (IS_KEY(element))
14107 player->key[KEY_NR(element)] = TRUE;
14109 DrawGameDoorValues();
14111 else if (element == EL_DC_KEY_WHITE)
14113 player->num_white_keys++;
14115 // display white keys?
14116 // DrawGameDoorValues();
14118 else if (IS_ENVELOPE(element))
14120 player->show_envelope = element;
14122 else if (element == EL_EMC_LENSES)
14124 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14126 RedrawAllInvisibleElementsForLenses();
14128 else if (element == EL_EMC_MAGNIFIER)
14130 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14132 RedrawAllInvisibleElementsForMagnifier();
14134 else if (IS_DROPPABLE(element) ||
14135 IS_THROWABLE(element)) // can be collected and dropped
14139 if (collect_count == 0)
14140 player->inventory_infinite_element = element;
14142 for (i = 0; i < collect_count; i++)
14143 if (player->inventory_size < MAX_INVENTORY_SIZE)
14144 player->inventory_element[player->inventory_size++] = element;
14146 DrawGameDoorValues();
14148 else if (collect_count > 0)
14150 game.gems_still_needed -= collect_count;
14151 if (game.gems_still_needed < 0)
14152 game.gems_still_needed = 0;
14154 game.snapshot.collected_item = TRUE;
14156 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14158 DisplayGameControlValues();
14161 RaiseScoreElement(element);
14162 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14166 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14167 player->index_bit, dig_side);
14169 // if collecting triggered player relocation, finish collecting tile
14170 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14171 setFieldForSnapping(x, y, element, move_direction);
14174 if (mode == DF_SNAP)
14176 if (level.block_snap_field)
14177 setFieldForSnapping(x, y, element, move_direction);
14179 TestIfElementTouchesCustomElement(x, y); // for empty space
14181 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14182 player->index_bit, dig_side);
14185 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14187 if (mode == DF_SNAP && element != EL_BD_ROCK)
14188 return MP_NO_ACTION;
14190 if (CAN_FALL(element) && dy)
14191 return MP_NO_ACTION;
14193 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14194 !(element == EL_SPRING && level.use_spring_bug))
14195 return MP_NO_ACTION;
14197 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14198 ((move_direction & MV_VERTICAL &&
14199 ((element_info[element].move_pattern & MV_LEFT &&
14200 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14201 (element_info[element].move_pattern & MV_RIGHT &&
14202 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14203 (move_direction & MV_HORIZONTAL &&
14204 ((element_info[element].move_pattern & MV_UP &&
14205 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14206 (element_info[element].move_pattern & MV_DOWN &&
14207 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14208 return MP_NO_ACTION;
14210 // do not push elements already moving away faster than player
14211 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14212 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14213 return MP_NO_ACTION;
14215 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14217 if (player->push_delay_value == -1 || !player_was_pushing)
14218 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14220 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14222 if (player->push_delay_value == -1)
14223 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14225 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14227 if (!player->is_pushing)
14228 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14231 player->is_pushing = TRUE;
14232 player->is_active = TRUE;
14234 if (!(IN_LEV_FIELD(nextx, nexty) &&
14235 (IS_FREE(nextx, nexty) ||
14236 (IS_SB_ELEMENT(element) &&
14237 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14238 (IS_CUSTOM_ELEMENT(element) &&
14239 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14240 return MP_NO_ACTION;
14242 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14243 return MP_NO_ACTION;
14245 if (player->push_delay == -1) // new pushing; restart delay
14246 player->push_delay = 0;
14248 if (player->push_delay < player->push_delay_value &&
14249 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14250 element != EL_SPRING && element != EL_BALLOON)
14252 // make sure that there is no move delay before next try to push
14253 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14254 player->move_delay = 0;
14256 return MP_NO_ACTION;
14259 if (IS_CUSTOM_ELEMENT(element) &&
14260 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14262 if (!DigFieldByCE(nextx, nexty, element))
14263 return MP_NO_ACTION;
14266 if (IS_SB_ELEMENT(element))
14268 boolean sokoban_task_solved = FALSE;
14270 if (element == EL_SOKOBAN_FIELD_FULL)
14272 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14274 IncrementSokobanFieldsNeeded();
14275 IncrementSokobanObjectsNeeded();
14278 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14280 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14282 DecrementSokobanFieldsNeeded();
14283 DecrementSokobanObjectsNeeded();
14285 // sokoban object was pushed from empty field to sokoban field
14286 if (Back[x][y] == EL_EMPTY)
14287 sokoban_task_solved = TRUE;
14290 Tile[x][y] = EL_SOKOBAN_OBJECT;
14292 if (Back[x][y] == Back[nextx][nexty])
14293 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14294 else if (Back[x][y] != 0)
14295 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14298 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14301 if (sokoban_task_solved &&
14302 game.sokoban_fields_still_needed == 0 &&
14303 game.sokoban_objects_still_needed == 0 &&
14304 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14306 game.players_still_needed = 0;
14310 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14314 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14316 InitMovingField(x, y, move_direction);
14317 GfxAction[x][y] = ACTION_PUSHING;
14319 if (mode == DF_SNAP)
14320 ContinueMoving(x, y);
14322 MovPos[x][y] = (dx != 0 ? dx : dy);
14324 Pushed[x][y] = TRUE;
14325 Pushed[nextx][nexty] = TRUE;
14327 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14328 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14330 player->push_delay_value = -1; // get new value later
14332 // check for element change _after_ element has been pushed
14333 if (game.use_change_when_pushing_bug)
14335 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14336 player->index_bit, dig_side);
14337 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14338 player->index_bit, dig_side);
14341 else if (IS_SWITCHABLE(element))
14343 if (PLAYER_SWITCHING(player, x, y))
14345 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14346 player->index_bit, dig_side);
14351 player->is_switching = TRUE;
14352 player->switch_x = x;
14353 player->switch_y = y;
14355 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14357 if (element == EL_ROBOT_WHEEL)
14359 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14361 game.robot_wheel_x = x;
14362 game.robot_wheel_y = y;
14363 game.robot_wheel_active = TRUE;
14365 TEST_DrawLevelField(x, y);
14367 else if (element == EL_SP_TERMINAL)
14371 SCAN_PLAYFIELD(xx, yy)
14373 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14377 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14379 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14381 ResetGfxAnimation(xx, yy);
14382 TEST_DrawLevelField(xx, yy);
14386 else if (IS_BELT_SWITCH(element))
14388 ToggleBeltSwitch(x, y);
14390 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14391 element == EL_SWITCHGATE_SWITCH_DOWN ||
14392 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14393 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14395 ToggleSwitchgateSwitch(x, y);
14397 else if (element == EL_LIGHT_SWITCH ||
14398 element == EL_LIGHT_SWITCH_ACTIVE)
14400 ToggleLightSwitch(x, y);
14402 else if (element == EL_TIMEGATE_SWITCH ||
14403 element == EL_DC_TIMEGATE_SWITCH)
14405 ActivateTimegateSwitch(x, y);
14407 else if (element == EL_BALLOON_SWITCH_LEFT ||
14408 element == EL_BALLOON_SWITCH_RIGHT ||
14409 element == EL_BALLOON_SWITCH_UP ||
14410 element == EL_BALLOON_SWITCH_DOWN ||
14411 element == EL_BALLOON_SWITCH_NONE ||
14412 element == EL_BALLOON_SWITCH_ANY)
14414 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14415 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14416 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14417 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14418 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14421 else if (element == EL_LAMP)
14423 Tile[x][y] = EL_LAMP_ACTIVE;
14424 game.lights_still_needed--;
14426 ResetGfxAnimation(x, y);
14427 TEST_DrawLevelField(x, y);
14429 else if (element == EL_TIME_ORB_FULL)
14431 Tile[x][y] = EL_TIME_ORB_EMPTY;
14433 if (level.time > 0 || level.use_time_orb_bug)
14435 TimeLeft += level.time_orb_time;
14436 game.no_time_limit = FALSE;
14438 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14440 DisplayGameControlValues();
14443 ResetGfxAnimation(x, y);
14444 TEST_DrawLevelField(x, y);
14446 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14447 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14451 game.ball_active = !game.ball_active;
14453 SCAN_PLAYFIELD(xx, yy)
14455 int e = Tile[xx][yy];
14457 if (game.ball_active)
14459 if (e == EL_EMC_MAGIC_BALL)
14460 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14461 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14462 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14466 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14467 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14468 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14469 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14474 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14475 player->index_bit, dig_side);
14477 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14478 player->index_bit, dig_side);
14480 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14481 player->index_bit, dig_side);
14487 if (!PLAYER_SWITCHING(player, x, y))
14489 player->is_switching = TRUE;
14490 player->switch_x = x;
14491 player->switch_y = y;
14493 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14494 player->index_bit, dig_side);
14495 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14496 player->index_bit, dig_side);
14498 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14499 player->index_bit, dig_side);
14500 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14501 player->index_bit, dig_side);
14504 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14505 player->index_bit, dig_side);
14506 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14507 player->index_bit, dig_side);
14509 return MP_NO_ACTION;
14512 player->push_delay = -1;
14514 if (is_player) // function can also be called by EL_PENGUIN
14516 if (Tile[x][y] != element) // really digged/collected something
14518 player->is_collecting = !player->is_digging;
14519 player->is_active = TRUE;
14526 static boolean DigFieldByCE(int x, int y, int digging_element)
14528 int element = Tile[x][y];
14530 if (!IS_FREE(x, y))
14532 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14533 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14536 // no element can dig solid indestructible elements
14537 if (IS_INDESTRUCTIBLE(element) &&
14538 !IS_DIGGABLE(element) &&
14539 !IS_COLLECTIBLE(element))
14542 if (AmoebaNr[x][y] &&
14543 (element == EL_AMOEBA_FULL ||
14544 element == EL_BD_AMOEBA ||
14545 element == EL_AMOEBA_GROWING))
14547 AmoebaCnt[AmoebaNr[x][y]]--;
14548 AmoebaCnt2[AmoebaNr[x][y]]--;
14551 if (IS_MOVING(x, y))
14552 RemoveMovingField(x, y);
14556 TEST_DrawLevelField(x, y);
14559 // if digged element was about to explode, prevent the explosion
14560 ExplodeField[x][y] = EX_TYPE_NONE;
14562 PlayLevelSoundAction(x, y, action);
14565 Store[x][y] = EL_EMPTY;
14567 // this makes it possible to leave the removed element again
14568 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14569 Store[x][y] = element;
14574 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14576 int jx = player->jx, jy = player->jy;
14577 int x = jx + dx, y = jy + dy;
14578 int snap_direction = (dx == -1 ? MV_LEFT :
14579 dx == +1 ? MV_RIGHT :
14581 dy == +1 ? MV_DOWN : MV_NONE);
14582 boolean can_continue_snapping = (level.continuous_snapping &&
14583 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14585 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14588 if (!player->active || !IN_LEV_FIELD(x, y))
14596 if (player->MovPos == 0)
14597 player->is_pushing = FALSE;
14599 player->is_snapping = FALSE;
14601 if (player->MovPos == 0)
14603 player->is_moving = FALSE;
14604 player->is_digging = FALSE;
14605 player->is_collecting = FALSE;
14611 // prevent snapping with already pressed snap key when not allowed
14612 if (player->is_snapping && !can_continue_snapping)
14615 player->MovDir = snap_direction;
14617 if (player->MovPos == 0)
14619 player->is_moving = FALSE;
14620 player->is_digging = FALSE;
14621 player->is_collecting = FALSE;
14624 player->is_dropping = FALSE;
14625 player->is_dropping_pressed = FALSE;
14626 player->drop_pressed_delay = 0;
14628 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14631 player->is_snapping = TRUE;
14632 player->is_active = TRUE;
14634 if (player->MovPos == 0)
14636 player->is_moving = FALSE;
14637 player->is_digging = FALSE;
14638 player->is_collecting = FALSE;
14641 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14642 TEST_DrawLevelField(player->last_jx, player->last_jy);
14644 TEST_DrawLevelField(x, y);
14649 static boolean DropElement(struct PlayerInfo *player)
14651 int old_element, new_element;
14652 int dropx = player->jx, dropy = player->jy;
14653 int drop_direction = player->MovDir;
14654 int drop_side = drop_direction;
14655 int drop_element = get_next_dropped_element(player);
14657 /* do not drop an element on top of another element; when holding drop key
14658 pressed without moving, dropped element must move away before the next
14659 element can be dropped (this is especially important if the next element
14660 is dynamite, which can be placed on background for historical reasons) */
14661 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14664 if (IS_THROWABLE(drop_element))
14666 dropx += GET_DX_FROM_DIR(drop_direction);
14667 dropy += GET_DY_FROM_DIR(drop_direction);
14669 if (!IN_LEV_FIELD(dropx, dropy))
14673 old_element = Tile[dropx][dropy]; // old element at dropping position
14674 new_element = drop_element; // default: no change when dropping
14676 // check if player is active, not moving and ready to drop
14677 if (!player->active || player->MovPos || player->drop_delay > 0)
14680 // check if player has anything that can be dropped
14681 if (new_element == EL_UNDEFINED)
14684 // only set if player has anything that can be dropped
14685 player->is_dropping_pressed = TRUE;
14687 // check if drop key was pressed long enough for EM style dynamite
14688 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14691 // check if anything can be dropped at the current position
14692 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14695 // collected custom elements can only be dropped on empty fields
14696 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14699 if (old_element != EL_EMPTY)
14700 Back[dropx][dropy] = old_element; // store old element on this field
14702 ResetGfxAnimation(dropx, dropy);
14703 ResetRandomAnimationValue(dropx, dropy);
14705 if (player->inventory_size > 0 ||
14706 player->inventory_infinite_element != EL_UNDEFINED)
14708 if (player->inventory_size > 0)
14710 player->inventory_size--;
14712 DrawGameDoorValues();
14714 if (new_element == EL_DYNAMITE)
14715 new_element = EL_DYNAMITE_ACTIVE;
14716 else if (new_element == EL_EM_DYNAMITE)
14717 new_element = EL_EM_DYNAMITE_ACTIVE;
14718 else if (new_element == EL_SP_DISK_RED)
14719 new_element = EL_SP_DISK_RED_ACTIVE;
14722 Tile[dropx][dropy] = new_element;
14724 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14725 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14726 el2img(Tile[dropx][dropy]), 0);
14728 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14730 // needed if previous element just changed to "empty" in the last frame
14731 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14733 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14734 player->index_bit, drop_side);
14735 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14737 player->index_bit, drop_side);
14739 TestIfElementTouchesCustomElement(dropx, dropy);
14741 else // player is dropping a dyna bomb
14743 player->dynabombs_left--;
14745 Tile[dropx][dropy] = new_element;
14747 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14748 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14749 el2img(Tile[dropx][dropy]), 0);
14751 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14754 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14755 InitField_WithBug1(dropx, dropy, FALSE);
14757 new_element = Tile[dropx][dropy]; // element might have changed
14759 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14760 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14762 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14763 MovDir[dropx][dropy] = drop_direction;
14765 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14767 // do not cause impact style collision by dropping elements that can fall
14768 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14771 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14772 player->is_dropping = TRUE;
14774 player->drop_pressed_delay = 0;
14775 player->is_dropping_pressed = FALSE;
14777 player->drop_x = dropx;
14778 player->drop_y = dropy;
14783 // ----------------------------------------------------------------------------
14784 // game sound playing functions
14785 // ----------------------------------------------------------------------------
14787 static int *loop_sound_frame = NULL;
14788 static int *loop_sound_volume = NULL;
14790 void InitPlayLevelSound(void)
14792 int num_sounds = getSoundListSize();
14794 checked_free(loop_sound_frame);
14795 checked_free(loop_sound_volume);
14797 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14798 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14801 static void PlayLevelSound(int x, int y, int nr)
14803 int sx = SCREENX(x), sy = SCREENY(y);
14804 int volume, stereo_position;
14805 int max_distance = 8;
14806 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14808 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14809 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14812 if (!IN_LEV_FIELD(x, y) ||
14813 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14814 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14817 volume = SOUND_MAX_VOLUME;
14819 if (!IN_SCR_FIELD(sx, sy))
14821 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14822 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14824 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14827 stereo_position = (SOUND_MAX_LEFT +
14828 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14829 (SCR_FIELDX + 2 * max_distance));
14831 if (IS_LOOP_SOUND(nr))
14833 /* This assures that quieter loop sounds do not overwrite louder ones,
14834 while restarting sound volume comparison with each new game frame. */
14836 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14839 loop_sound_volume[nr] = volume;
14840 loop_sound_frame[nr] = FrameCounter;
14843 PlaySoundExt(nr, volume, stereo_position, type);
14846 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14848 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14849 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14850 y < LEVELY(BY1) ? LEVELY(BY1) :
14851 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14855 static void PlayLevelSoundAction(int x, int y, int action)
14857 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14860 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14862 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14864 if (sound_effect != SND_UNDEFINED)
14865 PlayLevelSound(x, y, sound_effect);
14868 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14871 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14873 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14874 PlayLevelSound(x, y, sound_effect);
14877 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14879 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14881 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14882 PlayLevelSound(x, y, sound_effect);
14885 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14887 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14889 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14890 StopSound(sound_effect);
14893 static int getLevelMusicNr(void)
14895 if (levelset.music[level_nr] != MUS_UNDEFINED)
14896 return levelset.music[level_nr]; // from config file
14898 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14901 static void FadeLevelSounds(void)
14906 static void FadeLevelMusic(void)
14908 int music_nr = getLevelMusicNr();
14909 char *curr_music = getCurrentlyPlayingMusicFilename();
14910 char *next_music = getMusicInfoEntryFilename(music_nr);
14912 if (!strEqual(curr_music, next_music))
14916 void FadeLevelSoundsAndMusic(void)
14922 static void PlayLevelMusic(void)
14924 int music_nr = getLevelMusicNr();
14925 char *curr_music = getCurrentlyPlayingMusicFilename();
14926 char *next_music = getMusicInfoEntryFilename(music_nr);
14928 if (!strEqual(curr_music, next_music))
14929 PlayMusicLoop(music_nr);
14932 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14934 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14936 int x = xx - offset;
14937 int y = yy - offset;
14942 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14946 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14950 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14954 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14958 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14962 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14966 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14969 case SOUND_android_clone:
14970 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14973 case SOUND_android_move:
14974 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14978 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14982 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14986 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14989 case SOUND_eater_eat:
14990 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14994 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14997 case SOUND_collect:
14998 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15001 case SOUND_diamond:
15002 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15006 // !!! CHECK THIS !!!
15008 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15010 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15014 case SOUND_wonderfall:
15015 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15019 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15023 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15027 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15031 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15035 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15039 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15043 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15047 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15050 case SOUND_exit_open:
15051 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15054 case SOUND_exit_leave:
15055 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15058 case SOUND_dynamite:
15059 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15063 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15067 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15071 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15075 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15079 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15083 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15087 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15092 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15094 int element = map_element_SP_to_RND(element_sp);
15095 int action = map_action_SP_to_RND(action_sp);
15096 int offset = (setup.sp_show_border_elements ? 0 : 1);
15097 int x = xx - offset;
15098 int y = yy - offset;
15100 PlayLevelSoundElementAction(x, y, element, action);
15103 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15105 int element = map_element_MM_to_RND(element_mm);
15106 int action = map_action_MM_to_RND(action_mm);
15108 int x = xx - offset;
15109 int y = yy - offset;
15111 if (!IS_MM_ELEMENT(element))
15112 element = EL_MM_DEFAULT;
15114 PlayLevelSoundElementAction(x, y, element, action);
15117 void PlaySound_MM(int sound_mm)
15119 int sound = map_sound_MM_to_RND(sound_mm);
15121 if (sound == SND_UNDEFINED)
15127 void PlaySoundLoop_MM(int sound_mm)
15129 int sound = map_sound_MM_to_RND(sound_mm);
15131 if (sound == SND_UNDEFINED)
15134 PlaySoundLoop(sound);
15137 void StopSound_MM(int sound_mm)
15139 int sound = map_sound_MM_to_RND(sound_mm);
15141 if (sound == SND_UNDEFINED)
15147 void RaiseScore(int value)
15149 game.score += value;
15151 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15153 DisplayGameControlValues();
15156 void RaiseScoreElement(int element)
15161 case EL_BD_DIAMOND:
15162 case EL_EMERALD_YELLOW:
15163 case EL_EMERALD_RED:
15164 case EL_EMERALD_PURPLE:
15165 case EL_SP_INFOTRON:
15166 RaiseScore(level.score[SC_EMERALD]);
15169 RaiseScore(level.score[SC_DIAMOND]);
15172 RaiseScore(level.score[SC_CRYSTAL]);
15175 RaiseScore(level.score[SC_PEARL]);
15178 case EL_BD_BUTTERFLY:
15179 case EL_SP_ELECTRON:
15180 RaiseScore(level.score[SC_BUG]);
15183 case EL_BD_FIREFLY:
15184 case EL_SP_SNIKSNAK:
15185 RaiseScore(level.score[SC_SPACESHIP]);
15188 case EL_DARK_YAMYAM:
15189 RaiseScore(level.score[SC_YAMYAM]);
15192 RaiseScore(level.score[SC_ROBOT]);
15195 RaiseScore(level.score[SC_PACMAN]);
15198 RaiseScore(level.score[SC_NUT]);
15201 case EL_EM_DYNAMITE:
15202 case EL_SP_DISK_RED:
15203 case EL_DYNABOMB_INCREASE_NUMBER:
15204 case EL_DYNABOMB_INCREASE_SIZE:
15205 case EL_DYNABOMB_INCREASE_POWER:
15206 RaiseScore(level.score[SC_DYNAMITE]);
15208 case EL_SHIELD_NORMAL:
15209 case EL_SHIELD_DEADLY:
15210 RaiseScore(level.score[SC_SHIELD]);
15212 case EL_EXTRA_TIME:
15213 RaiseScore(level.extra_time_score);
15227 case EL_DC_KEY_WHITE:
15228 RaiseScore(level.score[SC_KEY]);
15231 RaiseScore(element_info[element].collect_score);
15236 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15238 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15240 // closing door required in case of envelope style request dialogs
15243 // prevent short reactivation of overlay buttons while closing door
15244 SetOverlayActive(FALSE);
15246 CloseDoor(DOOR_CLOSE_1);
15249 if (network.enabled)
15250 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15254 FadeSkipNextFadeIn();
15256 SetGameStatus(GAME_MODE_MAIN);
15261 else // continue playing the game
15263 if (tape.playing && tape.deactivate_display)
15264 TapeDeactivateDisplayOff(TRUE);
15266 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15268 if (tape.playing && tape.deactivate_display)
15269 TapeDeactivateDisplayOn();
15273 void RequestQuitGame(boolean ask_if_really_quit)
15275 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15276 boolean skip_request = game.all_players_gone || quick_quit;
15278 RequestQuitGameExt(skip_request, quick_quit,
15279 "Do you really want to quit the game?");
15282 void RequestRestartGame(char *message)
15284 game.restart_game_message = NULL;
15286 boolean has_started_game = hasStartedNetworkGame();
15287 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15289 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15291 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15295 // needed in case of envelope request to close game panel
15296 CloseDoor(DOOR_CLOSE_1);
15298 SetGameStatus(GAME_MODE_MAIN);
15304 void CheckGameOver(void)
15306 static boolean last_game_over = FALSE;
15307 static int game_over_delay = 0;
15308 int game_over_delay_value = 50;
15309 boolean game_over = checkGameFailed();
15311 // do not handle game over if request dialog is already active
15312 if (game.request_active)
15315 // do not ask to play again if game was never actually played
15316 if (!game.GamePlayed)
15321 last_game_over = FALSE;
15322 game_over_delay = game_over_delay_value;
15327 if (game_over_delay > 0)
15334 if (last_game_over != game_over)
15335 game.restart_game_message = (hasStartedNetworkGame() ?
15336 "Game over! Play it again?" :
15339 last_game_over = game_over;
15342 boolean checkGameSolved(void)
15344 // set for all game engines if level was solved
15345 return game.LevelSolved_GameEnd;
15348 boolean checkGameFailed(void)
15350 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15351 return (game_em.game_over && !game_em.level_solved);
15352 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15353 return (game_sp.game_over && !game_sp.level_solved);
15354 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15355 return (game_mm.game_over && !game_mm.level_solved);
15356 else // GAME_ENGINE_TYPE_RND
15357 return (game.GameOver && !game.LevelSolved);
15360 boolean checkGameEnded(void)
15362 return (checkGameSolved() || checkGameFailed());
15366 // ----------------------------------------------------------------------------
15367 // random generator functions
15368 // ----------------------------------------------------------------------------
15370 unsigned int InitEngineRandom_RND(int seed)
15372 game.num_random_calls = 0;
15374 return InitEngineRandom(seed);
15377 unsigned int RND(int max)
15381 game.num_random_calls++;
15383 return GetEngineRandom(max);
15390 // ----------------------------------------------------------------------------
15391 // game engine snapshot handling functions
15392 // ----------------------------------------------------------------------------
15394 struct EngineSnapshotInfo
15396 // runtime values for custom element collect score
15397 int collect_score[NUM_CUSTOM_ELEMENTS];
15399 // runtime values for group element choice position
15400 int choice_pos[NUM_GROUP_ELEMENTS];
15402 // runtime values for belt position animations
15403 int belt_graphic[4][NUM_BELT_PARTS];
15404 int belt_anim_mode[4][NUM_BELT_PARTS];
15407 static struct EngineSnapshotInfo engine_snapshot_rnd;
15408 static char *snapshot_level_identifier = NULL;
15409 static int snapshot_level_nr = -1;
15411 static void SaveEngineSnapshotValues_RND(void)
15413 static int belt_base_active_element[4] =
15415 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15416 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15417 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15418 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15422 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15424 int element = EL_CUSTOM_START + i;
15426 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15429 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15431 int element = EL_GROUP_START + i;
15433 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15436 for (i = 0; i < 4; i++)
15438 for (j = 0; j < NUM_BELT_PARTS; j++)
15440 int element = belt_base_active_element[i] + j;
15441 int graphic = el2img(element);
15442 int anim_mode = graphic_info[graphic].anim_mode;
15444 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15445 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15450 static void LoadEngineSnapshotValues_RND(void)
15452 unsigned int num_random_calls = game.num_random_calls;
15455 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15457 int element = EL_CUSTOM_START + i;
15459 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15462 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15464 int element = EL_GROUP_START + i;
15466 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15469 for (i = 0; i < 4; i++)
15471 for (j = 0; j < NUM_BELT_PARTS; j++)
15473 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15474 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15476 graphic_info[graphic].anim_mode = anim_mode;
15480 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15482 InitRND(tape.random_seed);
15483 for (i = 0; i < num_random_calls; i++)
15487 if (game.num_random_calls != num_random_calls)
15489 Error("number of random calls out of sync");
15490 Error("number of random calls should be %d", num_random_calls);
15491 Error("number of random calls is %d", game.num_random_calls);
15493 Fail("this should not happen -- please debug");
15497 void FreeEngineSnapshotSingle(void)
15499 FreeSnapshotSingle();
15501 setString(&snapshot_level_identifier, NULL);
15502 snapshot_level_nr = -1;
15505 void FreeEngineSnapshotList(void)
15507 FreeSnapshotList();
15510 static ListNode *SaveEngineSnapshotBuffers(void)
15512 ListNode *buffers = NULL;
15514 // copy some special values to a structure better suited for the snapshot
15516 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15517 SaveEngineSnapshotValues_RND();
15518 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15519 SaveEngineSnapshotValues_EM();
15520 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15521 SaveEngineSnapshotValues_SP(&buffers);
15522 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15523 SaveEngineSnapshotValues_MM(&buffers);
15525 // save values stored in special snapshot structure
15527 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15528 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15529 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15530 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15531 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15532 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15533 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15534 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15536 // save further RND engine values
15538 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15539 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15540 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15542 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15543 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15544 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15545 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15546 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15548 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15549 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15550 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15552 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15554 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15555 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15557 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15558 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15559 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15560 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15561 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15562 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15563 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15564 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15565 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15566 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15567 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15568 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15569 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15570 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15571 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15572 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15573 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15574 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15576 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15577 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15579 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15580 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15581 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15583 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15584 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15586 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15587 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15588 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15589 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15590 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15592 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15593 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15596 ListNode *node = engine_snapshot_list_rnd;
15599 while (node != NULL)
15601 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15606 Debug("game:playing:SaveEngineSnapshotBuffers",
15607 "size of engine snapshot: %d bytes", num_bytes);
15613 void SaveEngineSnapshotSingle(void)
15615 ListNode *buffers = SaveEngineSnapshotBuffers();
15617 // finally save all snapshot buffers to single snapshot
15618 SaveSnapshotSingle(buffers);
15620 // save level identification information
15621 setString(&snapshot_level_identifier, leveldir_current->identifier);
15622 snapshot_level_nr = level_nr;
15625 boolean CheckSaveEngineSnapshotToList(void)
15627 boolean save_snapshot =
15628 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15629 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15630 game.snapshot.changed_action) ||
15631 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15632 game.snapshot.collected_item));
15634 game.snapshot.changed_action = FALSE;
15635 game.snapshot.collected_item = FALSE;
15636 game.snapshot.save_snapshot = save_snapshot;
15638 return save_snapshot;
15641 void SaveEngineSnapshotToList(void)
15643 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15647 ListNode *buffers = SaveEngineSnapshotBuffers();
15649 // finally save all snapshot buffers to snapshot list
15650 SaveSnapshotToList(buffers);
15653 void SaveEngineSnapshotToListInitial(void)
15655 FreeEngineSnapshotList();
15657 SaveEngineSnapshotToList();
15660 static void LoadEngineSnapshotValues(void)
15662 // restore special values from snapshot structure
15664 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15665 LoadEngineSnapshotValues_RND();
15666 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15667 LoadEngineSnapshotValues_EM();
15668 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15669 LoadEngineSnapshotValues_SP();
15670 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15671 LoadEngineSnapshotValues_MM();
15674 void LoadEngineSnapshotSingle(void)
15676 LoadSnapshotSingle();
15678 LoadEngineSnapshotValues();
15681 static void LoadEngineSnapshot_Undo(int steps)
15683 LoadSnapshotFromList_Older(steps);
15685 LoadEngineSnapshotValues();
15688 static void LoadEngineSnapshot_Redo(int steps)
15690 LoadSnapshotFromList_Newer(steps);
15692 LoadEngineSnapshotValues();
15695 boolean CheckEngineSnapshotSingle(void)
15697 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15698 snapshot_level_nr == level_nr);
15701 boolean CheckEngineSnapshotList(void)
15703 return CheckSnapshotList();
15707 // ---------- new game button stuff -------------------------------------------
15714 boolean *setup_value;
15715 boolean allowed_on_tape;
15716 boolean is_touch_button;
15718 } gamebutton_info[NUM_GAME_BUTTONS] =
15721 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15722 GAME_CTRL_ID_STOP, NULL,
15723 TRUE, FALSE, "stop game"
15726 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15727 GAME_CTRL_ID_PAUSE, NULL,
15728 TRUE, FALSE, "pause game"
15731 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15732 GAME_CTRL_ID_PLAY, NULL,
15733 TRUE, FALSE, "play game"
15736 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15737 GAME_CTRL_ID_UNDO, NULL,
15738 TRUE, FALSE, "undo step"
15741 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15742 GAME_CTRL_ID_REDO, NULL,
15743 TRUE, FALSE, "redo step"
15746 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15747 GAME_CTRL_ID_SAVE, NULL,
15748 TRUE, FALSE, "save game"
15751 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15752 GAME_CTRL_ID_PAUSE2, NULL,
15753 TRUE, FALSE, "pause game"
15756 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15757 GAME_CTRL_ID_LOAD, NULL,
15758 TRUE, FALSE, "load game"
15761 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15762 GAME_CTRL_ID_PANEL_STOP, NULL,
15763 FALSE, FALSE, "stop game"
15766 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15767 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15768 FALSE, FALSE, "pause game"
15771 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15772 GAME_CTRL_ID_PANEL_PLAY, NULL,
15773 FALSE, FALSE, "play game"
15776 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15777 GAME_CTRL_ID_TOUCH_STOP, NULL,
15778 FALSE, TRUE, "stop game"
15781 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15782 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15783 FALSE, TRUE, "pause game"
15786 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15787 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15788 TRUE, FALSE, "background music on/off"
15791 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15792 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15793 TRUE, FALSE, "sound loops on/off"
15796 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15797 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15798 TRUE, FALSE, "normal sounds on/off"
15801 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15802 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15803 FALSE, FALSE, "background music on/off"
15806 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15807 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15808 FALSE, FALSE, "sound loops on/off"
15811 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15812 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15813 FALSE, FALSE, "normal sounds on/off"
15817 void CreateGameButtons(void)
15821 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15823 int graphic = gamebutton_info[i].graphic;
15824 struct GraphicInfo *gfx = &graphic_info[graphic];
15825 struct XY *pos = gamebutton_info[i].pos;
15826 struct GadgetInfo *gi;
15829 unsigned int event_mask;
15830 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15831 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15832 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15833 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15834 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15835 int gd_x = gfx->src_x;
15836 int gd_y = gfx->src_y;
15837 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15838 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15839 int gd_xa = gfx->src_x + gfx->active_xoffset;
15840 int gd_ya = gfx->src_y + gfx->active_yoffset;
15841 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15842 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15843 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15844 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15847 if (gfx->bitmap == NULL)
15849 game_gadget[id] = NULL;
15854 if (id == GAME_CTRL_ID_STOP ||
15855 id == GAME_CTRL_ID_PANEL_STOP ||
15856 id == GAME_CTRL_ID_TOUCH_STOP ||
15857 id == GAME_CTRL_ID_PLAY ||
15858 id == GAME_CTRL_ID_PANEL_PLAY ||
15859 id == GAME_CTRL_ID_SAVE ||
15860 id == GAME_CTRL_ID_LOAD)
15862 button_type = GD_TYPE_NORMAL_BUTTON;
15864 event_mask = GD_EVENT_RELEASED;
15866 else if (id == GAME_CTRL_ID_UNDO ||
15867 id == GAME_CTRL_ID_REDO)
15869 button_type = GD_TYPE_NORMAL_BUTTON;
15871 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15875 button_type = GD_TYPE_CHECK_BUTTON;
15876 checked = (gamebutton_info[i].setup_value != NULL ?
15877 *gamebutton_info[i].setup_value : FALSE);
15878 event_mask = GD_EVENT_PRESSED;
15881 gi = CreateGadget(GDI_CUSTOM_ID, id,
15882 GDI_IMAGE_ID, graphic,
15883 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15886 GDI_WIDTH, gfx->width,
15887 GDI_HEIGHT, gfx->height,
15888 GDI_TYPE, button_type,
15889 GDI_STATE, GD_BUTTON_UNPRESSED,
15890 GDI_CHECKED, checked,
15891 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15892 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15893 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15894 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15895 GDI_DIRECT_DRAW, FALSE,
15896 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15897 GDI_EVENT_MASK, event_mask,
15898 GDI_CALLBACK_ACTION, HandleGameButtons,
15902 Fail("cannot create gadget");
15904 game_gadget[id] = gi;
15908 void FreeGameButtons(void)
15912 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15913 FreeGadget(game_gadget[i]);
15916 static void UnmapGameButtonsAtSamePosition(int id)
15920 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15922 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15923 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15924 UnmapGadget(game_gadget[i]);
15927 static void UnmapGameButtonsAtSamePosition_All(void)
15929 if (setup.show_snapshot_buttons)
15931 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15932 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15933 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15937 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15938 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15939 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15941 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15942 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15943 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15947 static void MapGameButtonsAtSamePosition(int id)
15951 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15953 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15954 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15955 MapGadget(game_gadget[i]);
15957 UnmapGameButtonsAtSamePosition_All();
15960 void MapUndoRedoButtons(void)
15962 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15963 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15965 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15966 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15969 void UnmapUndoRedoButtons(void)
15971 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15972 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15974 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15975 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15978 void ModifyPauseButtons(void)
15982 GAME_CTRL_ID_PAUSE,
15983 GAME_CTRL_ID_PAUSE2,
15984 GAME_CTRL_ID_PANEL_PAUSE,
15985 GAME_CTRL_ID_TOUCH_PAUSE,
15990 for (i = 0; ids[i] > -1; i++)
15991 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15994 static void MapGameButtonsExt(boolean on_tape)
15998 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15999 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16000 i != GAME_CTRL_ID_UNDO &&
16001 i != GAME_CTRL_ID_REDO)
16002 MapGadget(game_gadget[i]);
16004 UnmapGameButtonsAtSamePosition_All();
16006 RedrawGameButtons();
16009 static void UnmapGameButtonsExt(boolean on_tape)
16013 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16014 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16015 UnmapGadget(game_gadget[i]);
16018 static void RedrawGameButtonsExt(boolean on_tape)
16022 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16023 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16024 RedrawGadget(game_gadget[i]);
16027 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16032 gi->checked = state;
16035 static void RedrawSoundButtonGadget(int id)
16037 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16038 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16039 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16040 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16041 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16042 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16045 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16046 RedrawGadget(game_gadget[id2]);
16049 void MapGameButtons(void)
16051 MapGameButtonsExt(FALSE);
16054 void UnmapGameButtons(void)
16056 UnmapGameButtonsExt(FALSE);
16059 void RedrawGameButtons(void)
16061 RedrawGameButtonsExt(FALSE);
16064 void MapGameButtonsOnTape(void)
16066 MapGameButtonsExt(TRUE);
16069 void UnmapGameButtonsOnTape(void)
16071 UnmapGameButtonsExt(TRUE);
16074 void RedrawGameButtonsOnTape(void)
16076 RedrawGameButtonsExt(TRUE);
16079 static void GameUndoRedoExt(void)
16081 ClearPlayerAction();
16083 tape.pausing = TRUE;
16086 UpdateAndDisplayGameControlValues();
16088 DrawCompleteVideoDisplay();
16089 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16090 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16091 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16096 static void GameUndo(int steps)
16098 if (!CheckEngineSnapshotList())
16101 LoadEngineSnapshot_Undo(steps);
16106 static void GameRedo(int steps)
16108 if (!CheckEngineSnapshotList())
16111 LoadEngineSnapshot_Redo(steps);
16116 static void HandleGameButtonsExt(int id, int button)
16118 static boolean game_undo_executed = FALSE;
16119 int steps = BUTTON_STEPSIZE(button);
16120 boolean handle_game_buttons =
16121 (game_status == GAME_MODE_PLAYING ||
16122 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16124 if (!handle_game_buttons)
16129 case GAME_CTRL_ID_STOP:
16130 case GAME_CTRL_ID_PANEL_STOP:
16131 case GAME_CTRL_ID_TOUCH_STOP:
16132 if (game_status == GAME_MODE_MAIN)
16138 RequestQuitGame(TRUE);
16142 case GAME_CTRL_ID_PAUSE:
16143 case GAME_CTRL_ID_PAUSE2:
16144 case GAME_CTRL_ID_PANEL_PAUSE:
16145 case GAME_CTRL_ID_TOUCH_PAUSE:
16146 if (network.enabled && game_status == GAME_MODE_PLAYING)
16149 SendToServer_ContinuePlaying();
16151 SendToServer_PausePlaying();
16154 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16156 game_undo_executed = FALSE;
16160 case GAME_CTRL_ID_PLAY:
16161 case GAME_CTRL_ID_PANEL_PLAY:
16162 if (game_status == GAME_MODE_MAIN)
16164 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16166 else if (tape.pausing)
16168 if (network.enabled)
16169 SendToServer_ContinuePlaying();
16171 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16175 case GAME_CTRL_ID_UNDO:
16176 // Important: When using "save snapshot when collecting an item" mode,
16177 // load last (current) snapshot for first "undo" after pressing "pause"
16178 // (else the last-but-one snapshot would be loaded, because the snapshot
16179 // pointer already points to the last snapshot when pressing "pause",
16180 // which is fine for "every step/move" mode, but not for "every collect")
16181 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16182 !game_undo_executed)
16185 game_undo_executed = TRUE;
16190 case GAME_CTRL_ID_REDO:
16194 case GAME_CTRL_ID_SAVE:
16198 case GAME_CTRL_ID_LOAD:
16202 case SOUND_CTRL_ID_MUSIC:
16203 case SOUND_CTRL_ID_PANEL_MUSIC:
16204 if (setup.sound_music)
16206 setup.sound_music = FALSE;
16210 else if (audio.music_available)
16212 setup.sound = setup.sound_music = TRUE;
16214 SetAudioMode(setup.sound);
16216 if (game_status == GAME_MODE_PLAYING)
16220 RedrawSoundButtonGadget(id);
16224 case SOUND_CTRL_ID_LOOPS:
16225 case SOUND_CTRL_ID_PANEL_LOOPS:
16226 if (setup.sound_loops)
16227 setup.sound_loops = FALSE;
16228 else if (audio.loops_available)
16230 setup.sound = setup.sound_loops = TRUE;
16232 SetAudioMode(setup.sound);
16235 RedrawSoundButtonGadget(id);
16239 case SOUND_CTRL_ID_SIMPLE:
16240 case SOUND_CTRL_ID_PANEL_SIMPLE:
16241 if (setup.sound_simple)
16242 setup.sound_simple = FALSE;
16243 else if (audio.sound_available)
16245 setup.sound = setup.sound_simple = TRUE;
16247 SetAudioMode(setup.sound);
16250 RedrawSoundButtonGadget(id);
16259 static void HandleGameButtons(struct GadgetInfo *gi)
16261 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16264 void HandleSoundButtonKeys(Key key)
16266 if (key == setup.shortcut.sound_simple)
16267 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16268 else if (key == setup.shortcut.sound_loops)
16269 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16270 else if (key == setup.shortcut.sound_music)
16271 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);