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(el2panelimg(gpc->value),
2542 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2543 gfx.anim_random_frame = last_anim_random_frame;
2546 else if (gpc->type == TYPE_GRAPHIC)
2548 if (gpc->graphic != IMG_UNDEFINED)
2550 int last_anim_random_frame = gfx.anim_random_frame;
2551 int graphic = gpc->graphic;
2553 if (gpc->value != gpc->last_value)
2556 gpc->gfx_random = INIT_GFX_RANDOM();
2562 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2563 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2564 gpc->gfx_random = INIT_GFX_RANDOM();
2567 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2568 gfx.anim_random_frame = gpc->gfx_random;
2570 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2572 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2573 gfx.anim_random_frame = last_anim_random_frame;
2579 static void DisplayGameControlValues(void)
2581 boolean redraw_panel = FALSE;
2584 for (i = 0; game_panel_controls[i].nr != -1; i++)
2586 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2588 if (PANEL_DEACTIVATED(gpc->pos))
2591 if (gpc->value == gpc->last_value &&
2592 gpc->frame == gpc->last_frame)
2595 redraw_panel = TRUE;
2601 // copy default game door content to main double buffer
2603 // !!! CHECK AGAIN !!!
2604 SetPanelBackground();
2605 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2606 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2608 // redraw game control buttons
2609 RedrawGameButtons();
2611 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2613 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2615 int nr = game_panel_order[i].nr;
2616 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2617 struct TextPosInfo *pos = gpc->pos;
2618 int type = gpc->type;
2619 int value = gpc->value;
2620 int frame = gpc->frame;
2621 int size = pos->size;
2622 int font = pos->font;
2623 boolean draw_masked = pos->draw_masked;
2624 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2626 if (PANEL_DEACTIVATED(pos))
2629 if (pos->class == get_hash_from_key("extra_panel_items") &&
2630 !setup.prefer_extra_panel_items)
2633 gpc->last_value = value;
2634 gpc->last_frame = frame;
2636 if (type == TYPE_INTEGER)
2638 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2639 nr == GAME_PANEL_TIME)
2641 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2643 if (use_dynamic_size) // use dynamic number of digits
2645 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2646 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2647 int size2 = size1 + 1;
2648 int font1 = pos->font;
2649 int font2 = pos->font_alt;
2651 size = (value < value_change ? size1 : size2);
2652 font = (value < value_change ? font1 : font2);
2656 // correct text size if "digits" is zero or less
2658 size = strlen(int2str(value, size));
2660 // dynamically correct text alignment
2661 pos->width = size * getFontWidth(font);
2663 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2664 int2str(value, size), font, mask_mode);
2666 else if (type == TYPE_ELEMENT)
2668 int element, graphic;
2672 int dst_x = PANEL_XPOS(pos);
2673 int dst_y = PANEL_YPOS(pos);
2675 if (value != EL_UNDEFINED && value != EL_EMPTY)
2678 graphic = el2panelimg(value);
2681 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2682 element, EL_NAME(element), size);
2685 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2688 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2691 width = graphic_info[graphic].width * size / TILESIZE;
2692 height = graphic_info[graphic].height * size / TILESIZE;
2695 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2698 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2702 else if (type == TYPE_GRAPHIC)
2704 int graphic = gpc->graphic;
2705 int graphic_active = gpc->graphic_active;
2709 int dst_x = PANEL_XPOS(pos);
2710 int dst_y = PANEL_YPOS(pos);
2711 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2712 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2714 if (graphic != IMG_UNDEFINED && !skip)
2716 if (pos->style == STYLE_REVERSE)
2717 value = 100 - value;
2719 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2721 if (pos->direction & MV_HORIZONTAL)
2723 width = graphic_info[graphic_active].width * value / 100;
2724 height = graphic_info[graphic_active].height;
2726 if (pos->direction == MV_LEFT)
2728 src_x += graphic_info[graphic_active].width - width;
2729 dst_x += graphic_info[graphic_active].width - width;
2734 width = graphic_info[graphic_active].width;
2735 height = graphic_info[graphic_active].height * value / 100;
2737 if (pos->direction == MV_UP)
2739 src_y += graphic_info[graphic_active].height - height;
2740 dst_y += graphic_info[graphic_active].height - height;
2745 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2748 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2751 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2753 if (pos->direction & MV_HORIZONTAL)
2755 if (pos->direction == MV_RIGHT)
2762 dst_x = PANEL_XPOS(pos);
2765 width = graphic_info[graphic].width - width;
2769 if (pos->direction == MV_DOWN)
2776 dst_y = PANEL_YPOS(pos);
2779 height = graphic_info[graphic].height - height;
2783 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2786 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2790 else if (type == TYPE_STRING)
2792 boolean active = (value != 0);
2793 char *state_normal = "off";
2794 char *state_active = "on";
2795 char *state = (active ? state_active : state_normal);
2796 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2797 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2798 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2799 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2801 if (nr == GAME_PANEL_GRAVITY_STATE)
2803 int font1 = pos->font; // (used for normal state)
2804 int font2 = pos->font_alt; // (used for active state)
2806 font = (active ? font2 : font1);
2815 // don't truncate output if "chars" is zero or less
2818 // dynamically correct text alignment
2819 pos->width = size * getFontWidth(font);
2822 s_cut = getStringCopyN(s, size);
2824 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2825 s_cut, font, mask_mode);
2831 redraw_mask |= REDRAW_DOOR_1;
2834 SetGameStatus(GAME_MODE_PLAYING);
2837 void UpdateAndDisplayGameControlValues(void)
2839 if (tape.deactivate_display)
2842 UpdateGameControlValues();
2843 DisplayGameControlValues();
2847 static void UpdateGameDoorValues(void)
2849 UpdateGameControlValues();
2853 void DrawGameDoorValues(void)
2855 DisplayGameControlValues();
2859 // ============================================================================
2861 // ----------------------------------------------------------------------------
2862 // initialize game engine due to level / tape version number
2863 // ============================================================================
2865 static void InitGameEngine(void)
2867 int i, j, k, l, x, y;
2869 // set game engine from tape file when re-playing, else from level file
2870 game.engine_version = (tape.playing ? tape.engine_version :
2871 level.game_version);
2873 // set single or multi-player game mode (needed for re-playing tapes)
2874 game.team_mode = setup.team_mode;
2878 int num_players = 0;
2880 for (i = 0; i < MAX_PLAYERS; i++)
2881 if (tape.player_participates[i])
2884 // multi-player tapes contain input data for more than one player
2885 game.team_mode = (num_players > 1);
2889 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2890 level.game_version);
2891 Debug("game:init:level", " tape.file_version == %06d",
2893 Debug("game:init:level", " tape.game_version == %06d",
2895 Debug("game:init:level", " tape.engine_version == %06d",
2896 tape.engine_version);
2897 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2898 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2901 // --------------------------------------------------------------------------
2902 // set flags for bugs and changes according to active game engine version
2903 // --------------------------------------------------------------------------
2907 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2909 Bug was introduced in version:
2912 Bug was fixed in version:
2916 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2917 but the property "can fall" was missing, which caused some levels to be
2918 unsolvable. This was fixed in version 4.2.0.0.
2920 Affected levels/tapes:
2921 An example for a tape that was fixed by this bugfix is tape 029 from the
2922 level set "rnd_sam_bateman".
2923 The wrong behaviour will still be used for all levels or tapes that were
2924 created/recorded with it. An example for this is tape 023 from the level
2925 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2928 boolean use_amoeba_dropping_cannot_fall_bug =
2929 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2930 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2932 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2933 tape.game_version < VERSION_IDENT(4,2,0,0)));
2936 Summary of bugfix/change:
2937 Fixed move speed of elements entering or leaving magic wall.
2939 Fixed/changed in version:
2943 Before 2.0.1, move speed of elements entering or leaving magic wall was
2944 twice as fast as it is now.
2945 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2947 Affected levels/tapes:
2948 The first condition is generally needed for all levels/tapes before version
2949 2.0.1, which might use the old behaviour before it was changed; known tapes
2950 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2951 The second condition is an exception from the above case and is needed for
2952 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2953 above, but before it was known that this change would break tapes like the
2954 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2955 although the engine version while recording maybe was before 2.0.1. There
2956 are a lot of tapes that are affected by this exception, like tape 006 from
2957 the level set "rnd_conor_mancone".
2960 boolean use_old_move_stepsize_for_magic_wall =
2961 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2963 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2964 tape.game_version < VERSION_IDENT(4,2,0,0)));
2967 Summary of bugfix/change:
2968 Fixed handling for custom elements that change when pushed by the player.
2970 Fixed/changed in version:
2974 Before 3.1.0, custom elements that "change when pushing" changed directly
2975 after the player started pushing them (until then handled in "DigField()").
2976 Since 3.1.0, these custom elements are not changed until the "pushing"
2977 move of the element is finished (now handled in "ContinueMoving()").
2979 Affected levels/tapes:
2980 The first condition is generally needed for all levels/tapes before version
2981 3.1.0, which might use the old behaviour before it was changed; known tapes
2982 that are affected are some tapes from the level set "Walpurgis Gardens" by
2984 The second condition is an exception from the above case and is needed for
2985 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2986 above (including some development versions of 3.1.0), but before it was
2987 known that this change would break tapes like the above and was fixed in
2988 3.1.1, so that the changed behaviour was active although the engine version
2989 while recording maybe was before 3.1.0. There is at least one tape that is
2990 affected by this exception, which is the tape for the one-level set "Bug
2991 Machine" by Juergen Bonhagen.
2994 game.use_change_when_pushing_bug =
2995 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2997 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2998 tape.game_version < VERSION_IDENT(3,1,1,0)));
3001 Summary of bugfix/change:
3002 Fixed handling for blocking the field the player leaves when moving.
3004 Fixed/changed in version:
3008 Before 3.1.1, when "block last field when moving" was enabled, the field
3009 the player is leaving when moving was blocked for the time of the move,
3010 and was directly unblocked afterwards. This resulted in the last field
3011 being blocked for exactly one less than the number of frames of one player
3012 move. Additionally, even when blocking was disabled, the last field was
3013 blocked for exactly one frame.
3014 Since 3.1.1, due to changes in player movement handling, the last field
3015 is not blocked at all when blocking is disabled. When blocking is enabled,
3016 the last field is blocked for exactly the number of frames of one player
3017 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3018 last field is blocked for exactly one more than the number of frames of
3021 Affected levels/tapes:
3022 (!!! yet to be determined -- probably many !!!)
3025 game.use_block_last_field_bug =
3026 (game.engine_version < VERSION_IDENT(3,1,1,0));
3028 /* various special flags and settings for native Emerald Mine game engine */
3030 game_em.use_single_button =
3031 (game.engine_version > VERSION_IDENT(4,0,0,2));
3033 game_em.use_snap_key_bug =
3034 (game.engine_version < VERSION_IDENT(4,0,1,0));
3036 game_em.use_random_bug =
3037 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3039 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3041 game_em.use_old_explosions = use_old_em_engine;
3042 game_em.use_old_android = use_old_em_engine;
3043 game_em.use_old_push_elements = use_old_em_engine;
3044 game_em.use_old_push_into_acid = use_old_em_engine;
3046 game_em.use_wrap_around = !use_old_em_engine;
3048 // --------------------------------------------------------------------------
3050 // set maximal allowed number of custom element changes per game frame
3051 game.max_num_changes_per_frame = 1;
3053 // default scan direction: scan playfield from top/left to bottom/right
3054 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3056 // dynamically adjust element properties according to game engine version
3057 InitElementPropertiesEngine(game.engine_version);
3059 // ---------- initialize special element properties -------------------------
3061 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3062 if (use_amoeba_dropping_cannot_fall_bug)
3063 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3065 // ---------- initialize player's initial move delay ------------------------
3067 // dynamically adjust player properties according to level information
3068 for (i = 0; i < MAX_PLAYERS; i++)
3069 game.initial_move_delay_value[i] =
3070 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3072 // dynamically adjust player properties according to game engine version
3073 for (i = 0; i < MAX_PLAYERS; i++)
3074 game.initial_move_delay[i] =
3075 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3076 game.initial_move_delay_value[i] : 0);
3078 // ---------- initialize player's initial push delay ------------------------
3080 // dynamically adjust player properties according to game engine version
3081 game.initial_push_delay_value =
3082 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3084 // ---------- initialize changing elements ----------------------------------
3086 // initialize changing elements information
3087 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3089 struct ElementInfo *ei = &element_info[i];
3091 // this pointer might have been changed in the level editor
3092 ei->change = &ei->change_page[0];
3094 if (!IS_CUSTOM_ELEMENT(i))
3096 ei->change->target_element = EL_EMPTY_SPACE;
3097 ei->change->delay_fixed = 0;
3098 ei->change->delay_random = 0;
3099 ei->change->delay_frames = 1;
3102 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3104 ei->has_change_event[j] = FALSE;
3106 ei->event_page_nr[j] = 0;
3107 ei->event_page[j] = &ei->change_page[0];
3111 // add changing elements from pre-defined list
3112 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3114 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3115 struct ElementInfo *ei = &element_info[ch_delay->element];
3117 ei->change->target_element = ch_delay->target_element;
3118 ei->change->delay_fixed = ch_delay->change_delay;
3120 ei->change->pre_change_function = ch_delay->pre_change_function;
3121 ei->change->change_function = ch_delay->change_function;
3122 ei->change->post_change_function = ch_delay->post_change_function;
3124 ei->change->can_change = TRUE;
3125 ei->change->can_change_or_has_action = TRUE;
3127 ei->has_change_event[CE_DELAY] = TRUE;
3129 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3130 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3133 // ---------- initialize internal run-time variables ------------------------
3135 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3137 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3139 for (j = 0; j < ei->num_change_pages; j++)
3141 ei->change_page[j].can_change_or_has_action =
3142 (ei->change_page[j].can_change |
3143 ei->change_page[j].has_action);
3147 // add change events from custom element configuration
3148 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3150 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3152 for (j = 0; j < ei->num_change_pages; j++)
3154 if (!ei->change_page[j].can_change_or_has_action)
3157 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3159 // only add event page for the first page found with this event
3160 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3162 ei->has_change_event[k] = TRUE;
3164 ei->event_page_nr[k] = j;
3165 ei->event_page[k] = &ei->change_page[j];
3171 // ---------- initialize reference elements in change conditions ------------
3173 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3175 int element = EL_CUSTOM_START + i;
3176 struct ElementInfo *ei = &element_info[element];
3178 for (j = 0; j < ei->num_change_pages; j++)
3180 int trigger_element = ei->change_page[j].initial_trigger_element;
3182 if (trigger_element >= EL_PREV_CE_8 &&
3183 trigger_element <= EL_NEXT_CE_8)
3184 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3186 ei->change_page[j].trigger_element = trigger_element;
3190 // ---------- initialize run-time trigger player and element ----------------
3192 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3194 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3196 for (j = 0; j < ei->num_change_pages; j++)
3198 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3199 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3200 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3201 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3202 ei->change_page[j].actual_trigger_ce_value = 0;
3203 ei->change_page[j].actual_trigger_ce_score = 0;
3207 // ---------- initialize trigger events -------------------------------------
3209 // initialize trigger events information
3210 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3211 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3212 trigger_events[i][j] = FALSE;
3214 // add trigger events from element change event properties
3215 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3217 struct ElementInfo *ei = &element_info[i];
3219 for (j = 0; j < ei->num_change_pages; j++)
3221 if (!ei->change_page[j].can_change_or_has_action)
3224 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3226 int trigger_element = ei->change_page[j].trigger_element;
3228 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3230 if (ei->change_page[j].has_event[k])
3232 if (IS_GROUP_ELEMENT(trigger_element))
3234 struct ElementGroupInfo *group =
3235 element_info[trigger_element].group;
3237 for (l = 0; l < group->num_elements_resolved; l++)
3238 trigger_events[group->element_resolved[l]][k] = TRUE;
3240 else if (trigger_element == EL_ANY_ELEMENT)
3241 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3242 trigger_events[l][k] = TRUE;
3244 trigger_events[trigger_element][k] = TRUE;
3251 // ---------- initialize push delay -----------------------------------------
3253 // initialize push delay values to default
3254 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3256 if (!IS_CUSTOM_ELEMENT(i))
3258 // set default push delay values (corrected since version 3.0.7-1)
3259 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3261 element_info[i].push_delay_fixed = 2;
3262 element_info[i].push_delay_random = 8;
3266 element_info[i].push_delay_fixed = 8;
3267 element_info[i].push_delay_random = 8;
3272 // set push delay value for certain elements from pre-defined list
3273 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3275 int e = push_delay_list[i].element;
3277 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3278 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3281 // set push delay value for Supaplex elements for newer engine versions
3282 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3284 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3286 if (IS_SP_ELEMENT(i))
3288 // set SP push delay to just enough to push under a falling zonk
3289 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3291 element_info[i].push_delay_fixed = delay;
3292 element_info[i].push_delay_random = 0;
3297 // ---------- initialize move stepsize --------------------------------------
3299 // initialize move stepsize values to default
3300 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3301 if (!IS_CUSTOM_ELEMENT(i))
3302 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3304 // set move stepsize value for certain elements from pre-defined list
3305 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3307 int e = move_stepsize_list[i].element;
3309 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3311 // set move stepsize value for certain elements for older engine versions
3312 if (use_old_move_stepsize_for_magic_wall)
3314 if (e == EL_MAGIC_WALL_FILLING ||
3315 e == EL_MAGIC_WALL_EMPTYING ||
3316 e == EL_BD_MAGIC_WALL_FILLING ||
3317 e == EL_BD_MAGIC_WALL_EMPTYING)
3318 element_info[e].move_stepsize *= 2;
3322 // ---------- initialize collect score --------------------------------------
3324 // initialize collect score values for custom elements from initial value
3325 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3326 if (IS_CUSTOM_ELEMENT(i))
3327 element_info[i].collect_score = element_info[i].collect_score_initial;
3329 // ---------- initialize collect count --------------------------------------
3331 // initialize collect count values for non-custom elements
3332 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3333 if (!IS_CUSTOM_ELEMENT(i))
3334 element_info[i].collect_count_initial = 0;
3336 // add collect count values for all elements from pre-defined list
3337 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3338 element_info[collect_count_list[i].element].collect_count_initial =
3339 collect_count_list[i].count;
3341 // ---------- initialize access direction -----------------------------------
3343 // initialize access direction values to default (access from every side)
3344 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3345 if (!IS_CUSTOM_ELEMENT(i))
3346 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3348 // set access direction value for certain elements from pre-defined list
3349 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3350 element_info[access_direction_list[i].element].access_direction =
3351 access_direction_list[i].direction;
3353 // ---------- initialize explosion content ----------------------------------
3354 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3356 if (IS_CUSTOM_ELEMENT(i))
3359 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3361 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3363 element_info[i].content.e[x][y] =
3364 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3365 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3366 i == EL_PLAYER_3 ? EL_EMERALD :
3367 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3368 i == EL_MOLE ? EL_EMERALD_RED :
3369 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3370 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3371 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3372 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3373 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3374 i == EL_WALL_EMERALD ? EL_EMERALD :
3375 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3376 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3377 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3378 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3379 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3380 i == EL_WALL_PEARL ? EL_PEARL :
3381 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3386 // ---------- initialize recursion detection --------------------------------
3387 recursion_loop_depth = 0;
3388 recursion_loop_detected = FALSE;
3389 recursion_loop_element = EL_UNDEFINED;
3391 // ---------- initialize graphics engine ------------------------------------
3392 game.scroll_delay_value =
3393 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3394 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3395 !setup.forced_scroll_delay ? 0 :
3396 setup.scroll_delay ? setup.scroll_delay_value : 0);
3397 game.scroll_delay_value =
3398 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3400 // ---------- initialize game engine snapshots ------------------------------
3401 for (i = 0; i < MAX_PLAYERS; i++)
3402 game.snapshot.last_action[i] = 0;
3403 game.snapshot.changed_action = FALSE;
3404 game.snapshot.collected_item = FALSE;
3405 game.snapshot.mode =
3406 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3407 SNAPSHOT_MODE_EVERY_STEP :
3408 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3409 SNAPSHOT_MODE_EVERY_MOVE :
3410 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3411 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3412 game.snapshot.save_snapshot = FALSE;
3414 // ---------- initialize level time for Supaplex engine ---------------------
3415 // Supaplex levels with time limit currently unsupported -- should be added
3416 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3419 // ---------- initialize flags for handling game actions --------------------
3421 // set flags for game actions to default values
3422 game.use_key_actions = TRUE;
3423 game.use_mouse_actions = FALSE;
3425 // when using Mirror Magic game engine, handle mouse events only
3426 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3428 game.use_key_actions = FALSE;
3429 game.use_mouse_actions = TRUE;
3432 // check for custom elements with mouse click events
3433 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3435 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3437 int element = EL_CUSTOM_START + i;
3439 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3440 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3441 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3442 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3443 game.use_mouse_actions = TRUE;
3448 static int get_num_special_action(int element, int action_first,
3451 int num_special_action = 0;
3454 for (i = action_first; i <= action_last; i++)
3456 boolean found = FALSE;
3458 for (j = 0; j < NUM_DIRECTIONS; j++)
3459 if (el_act_dir2img(element, i, j) !=
3460 el_act_dir2img(element, ACTION_DEFAULT, j))
3464 num_special_action++;
3469 return num_special_action;
3473 // ============================================================================
3475 // ----------------------------------------------------------------------------
3476 // initialize and start new game
3477 // ============================================================================
3479 #if DEBUG_INIT_PLAYER
3480 static void DebugPrintPlayerStatus(char *message)
3487 Debug("game:init:player", "%s:", message);
3489 for (i = 0; i < MAX_PLAYERS; i++)
3491 struct PlayerInfo *player = &stored_player[i];
3493 Debug("game:init:player",
3494 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3498 player->connected_locally,
3499 player->connected_network,
3501 (local_player == player ? " (local player)" : ""));
3508 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3509 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3510 int fade_mask = REDRAW_FIELD;
3512 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3513 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3514 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3515 int initial_move_dir = MV_DOWN;
3518 // required here to update video display before fading (FIX THIS)
3519 DrawMaskedBorder(REDRAW_DOOR_2);
3521 if (!game.restart_level)
3522 CloseDoor(DOOR_CLOSE_1);
3524 SetGameStatus(GAME_MODE_PLAYING);
3526 if (level_editor_test_game)
3527 FadeSkipNextFadeOut();
3529 FadeSetEnterScreen();
3532 fade_mask = REDRAW_ALL;
3534 FadeLevelSoundsAndMusic();
3536 ExpireSoundLoops(TRUE);
3540 if (level_editor_test_game)
3541 FadeSkipNextFadeIn();
3543 // needed if different viewport properties defined for playing
3544 ChangeViewportPropertiesIfNeeded();
3548 DrawCompleteVideoDisplay();
3550 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3553 InitGameControlValues();
3555 // initialize tape actions from game when recording tape
3558 tape.use_key_actions = game.use_key_actions;
3559 tape.use_mouse_actions = game.use_mouse_actions;
3562 // don't play tapes over network
3563 network_playing = (network.enabled && !tape.playing);
3565 for (i = 0; i < MAX_PLAYERS; i++)
3567 struct PlayerInfo *player = &stored_player[i];
3569 player->index_nr = i;
3570 player->index_bit = (1 << i);
3571 player->element_nr = EL_PLAYER_1 + i;
3573 player->present = FALSE;
3574 player->active = FALSE;
3575 player->mapped = FALSE;
3577 player->killed = FALSE;
3578 player->reanimated = FALSE;
3579 player->buried = FALSE;
3582 player->effective_action = 0;
3583 player->programmed_action = 0;
3584 player->snap_action = 0;
3586 player->mouse_action.lx = 0;
3587 player->mouse_action.ly = 0;
3588 player->mouse_action.button = 0;
3589 player->mouse_action.button_hint = 0;
3591 player->effective_mouse_action.lx = 0;
3592 player->effective_mouse_action.ly = 0;
3593 player->effective_mouse_action.button = 0;
3594 player->effective_mouse_action.button_hint = 0;
3596 for (j = 0; j < MAX_NUM_KEYS; j++)
3597 player->key[j] = FALSE;
3599 player->num_white_keys = 0;
3601 player->dynabomb_count = 0;
3602 player->dynabomb_size = 1;
3603 player->dynabombs_left = 0;
3604 player->dynabomb_xl = FALSE;
3606 player->MovDir = initial_move_dir;
3609 player->GfxDir = initial_move_dir;
3610 player->GfxAction = ACTION_DEFAULT;
3612 player->StepFrame = 0;
3614 player->initial_element = player->element_nr;
3615 player->artwork_element =
3616 (level.use_artwork_element[i] ? level.artwork_element[i] :
3617 player->element_nr);
3618 player->use_murphy = FALSE;
3620 player->block_last_field = FALSE; // initialized in InitPlayerField()
3621 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3623 player->gravity = level.initial_player_gravity[i];
3625 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3627 player->actual_frame_counter = 0;
3629 player->step_counter = 0;
3631 player->last_move_dir = initial_move_dir;
3633 player->is_active = FALSE;
3635 player->is_waiting = FALSE;
3636 player->is_moving = FALSE;
3637 player->is_auto_moving = FALSE;
3638 player->is_digging = FALSE;
3639 player->is_snapping = FALSE;
3640 player->is_collecting = FALSE;
3641 player->is_pushing = FALSE;
3642 player->is_switching = FALSE;
3643 player->is_dropping = FALSE;
3644 player->is_dropping_pressed = FALSE;
3646 player->is_bored = FALSE;
3647 player->is_sleeping = FALSE;
3649 player->was_waiting = TRUE;
3650 player->was_moving = FALSE;
3651 player->was_snapping = FALSE;
3652 player->was_dropping = FALSE;
3654 player->force_dropping = FALSE;
3656 player->frame_counter_bored = -1;
3657 player->frame_counter_sleeping = -1;
3659 player->anim_delay_counter = 0;
3660 player->post_delay_counter = 0;
3662 player->dir_waiting = initial_move_dir;
3663 player->action_waiting = ACTION_DEFAULT;
3664 player->last_action_waiting = ACTION_DEFAULT;
3665 player->special_action_bored = ACTION_DEFAULT;
3666 player->special_action_sleeping = ACTION_DEFAULT;
3668 player->switch_x = -1;
3669 player->switch_y = -1;
3671 player->drop_x = -1;
3672 player->drop_y = -1;
3674 player->show_envelope = 0;
3676 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3678 player->push_delay = -1; // initialized when pushing starts
3679 player->push_delay_value = game.initial_push_delay_value;
3681 player->drop_delay = 0;
3682 player->drop_pressed_delay = 0;
3684 player->last_jx = -1;
3685 player->last_jy = -1;
3689 player->shield_normal_time_left = 0;
3690 player->shield_deadly_time_left = 0;
3692 player->inventory_infinite_element = EL_UNDEFINED;
3693 player->inventory_size = 0;
3695 if (level.use_initial_inventory[i])
3697 for (j = 0; j < level.initial_inventory_size[i]; j++)
3699 int element = level.initial_inventory_content[i][j];
3700 int collect_count = element_info[element].collect_count_initial;
3703 if (!IS_CUSTOM_ELEMENT(element))
3706 if (collect_count == 0)
3707 player->inventory_infinite_element = element;
3709 for (k = 0; k < collect_count; k++)
3710 if (player->inventory_size < MAX_INVENTORY_SIZE)
3711 player->inventory_element[player->inventory_size++] = element;
3715 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3716 SnapField(player, 0, 0);
3718 map_player_action[i] = i;
3721 network_player_action_received = FALSE;
3723 // initial null action
3724 if (network_playing)
3725 SendToServer_MovePlayer(MV_NONE);
3730 TimeLeft = level.time;
3733 ScreenMovDir = MV_NONE;
3737 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3739 game.robot_wheel_x = -1;
3740 game.robot_wheel_y = -1;
3745 game.all_players_gone = FALSE;
3747 game.LevelSolved = FALSE;
3748 game.GameOver = FALSE;
3750 game.GamePlayed = !tape.playing;
3752 game.LevelSolved_GameWon = FALSE;
3753 game.LevelSolved_GameEnd = FALSE;
3754 game.LevelSolved_SaveTape = FALSE;
3755 game.LevelSolved_SaveScore = FALSE;
3757 game.LevelSolved_CountingTime = 0;
3758 game.LevelSolved_CountingScore = 0;
3759 game.LevelSolved_CountingHealth = 0;
3761 game.panel.active = TRUE;
3763 game.no_time_limit = (level.time == 0);
3765 game.yamyam_content_nr = 0;
3766 game.robot_wheel_active = FALSE;
3767 game.magic_wall_active = FALSE;
3768 game.magic_wall_time_left = 0;
3769 game.light_time_left = 0;
3770 game.timegate_time_left = 0;
3771 game.switchgate_pos = 0;
3772 game.wind_direction = level.wind_direction_initial;
3775 game.score_final = 0;
3777 game.health = MAX_HEALTH;
3778 game.health_final = MAX_HEALTH;
3780 game.gems_still_needed = level.gems_needed;
3781 game.sokoban_fields_still_needed = 0;
3782 game.sokoban_objects_still_needed = 0;
3783 game.lights_still_needed = 0;
3784 game.players_still_needed = 0;
3785 game.friends_still_needed = 0;
3787 game.lenses_time_left = 0;
3788 game.magnify_time_left = 0;
3790 game.ball_active = level.ball_active_initial;
3791 game.ball_content_nr = 0;
3793 game.explosions_delayed = TRUE;
3795 game.envelope_active = FALSE;
3797 for (i = 0; i < NUM_BELTS; i++)
3799 game.belt_dir[i] = MV_NONE;
3800 game.belt_dir_nr[i] = 3; // not moving, next moving left
3803 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3804 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3806 #if DEBUG_INIT_PLAYER
3807 DebugPrintPlayerStatus("Player status at level initialization");
3810 SCAN_PLAYFIELD(x, y)
3812 Tile[x][y] = Last[x][y] = level.field[x][y];
3813 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3814 ChangeDelay[x][y] = 0;
3815 ChangePage[x][y] = -1;
3816 CustomValue[x][y] = 0; // initialized in InitField()
3817 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3819 WasJustMoving[x][y] = 0;
3820 WasJustFalling[x][y] = 0;
3821 CheckCollision[x][y] = 0;
3822 CheckImpact[x][y] = 0;
3824 Pushed[x][y] = FALSE;
3826 ChangeCount[x][y] = 0;
3827 ChangeEvent[x][y] = -1;
3829 ExplodePhase[x][y] = 0;
3830 ExplodeDelay[x][y] = 0;
3831 ExplodeField[x][y] = EX_TYPE_NONE;
3833 RunnerVisit[x][y] = 0;
3834 PlayerVisit[x][y] = 0;
3837 GfxRandom[x][y] = INIT_GFX_RANDOM();
3838 GfxElement[x][y] = EL_UNDEFINED;
3839 GfxAction[x][y] = ACTION_DEFAULT;
3840 GfxDir[x][y] = MV_NONE;
3841 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3844 SCAN_PLAYFIELD(x, y)
3846 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3848 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3850 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3853 InitField(x, y, TRUE);
3855 ResetGfxAnimation(x, y);
3860 for (i = 0; i < MAX_PLAYERS; i++)
3862 struct PlayerInfo *player = &stored_player[i];
3864 // set number of special actions for bored and sleeping animation
3865 player->num_special_action_bored =
3866 get_num_special_action(player->artwork_element,
3867 ACTION_BORING_1, ACTION_BORING_LAST);
3868 player->num_special_action_sleeping =
3869 get_num_special_action(player->artwork_element,
3870 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3873 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3874 emulate_sb ? EMU_SOKOBAN :
3875 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3877 // initialize type of slippery elements
3878 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3880 if (!IS_CUSTOM_ELEMENT(i))
3882 // default: elements slip down either to the left or right randomly
3883 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3885 // SP style elements prefer to slip down on the left side
3886 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3887 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3889 // BD style elements prefer to slip down on the left side
3890 if (game.emulation == EMU_BOULDERDASH)
3891 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3895 // initialize explosion and ignition delay
3896 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3898 if (!IS_CUSTOM_ELEMENT(i))
3901 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3902 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3903 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3904 int last_phase = (num_phase + 1) * delay;
3905 int half_phase = (num_phase / 2) * delay;
3907 element_info[i].explosion_delay = last_phase - 1;
3908 element_info[i].ignition_delay = half_phase;
3910 if (i == EL_BLACK_ORB)
3911 element_info[i].ignition_delay = 1;
3915 // correct non-moving belts to start moving left
3916 for (i = 0; i < NUM_BELTS; i++)
3917 if (game.belt_dir[i] == MV_NONE)
3918 game.belt_dir_nr[i] = 3; // not moving, next moving left
3920 #if USE_NEW_PLAYER_ASSIGNMENTS
3921 // use preferred player also in local single-player mode
3922 if (!network.enabled && !game.team_mode)
3924 int new_index_nr = setup.network_player_nr;
3926 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3928 for (i = 0; i < MAX_PLAYERS; i++)
3929 stored_player[i].connected_locally = FALSE;
3931 stored_player[new_index_nr].connected_locally = TRUE;
3935 for (i = 0; i < MAX_PLAYERS; i++)
3937 stored_player[i].connected = FALSE;
3939 // in network game mode, the local player might not be the first player
3940 if (stored_player[i].connected_locally)
3941 local_player = &stored_player[i];
3944 if (!network.enabled)
3945 local_player->connected = TRUE;
3949 for (i = 0; i < MAX_PLAYERS; i++)
3950 stored_player[i].connected = tape.player_participates[i];
3952 else if (network.enabled)
3954 // add team mode players connected over the network (needed for correct
3955 // assignment of player figures from level to locally playing players)
3957 for (i = 0; i < MAX_PLAYERS; i++)
3958 if (stored_player[i].connected_network)
3959 stored_player[i].connected = TRUE;
3961 else if (game.team_mode)
3963 // try to guess locally connected team mode players (needed for correct
3964 // assignment of player figures from level to locally playing players)
3966 for (i = 0; i < MAX_PLAYERS; i++)
3967 if (setup.input[i].use_joystick ||
3968 setup.input[i].key.left != KSYM_UNDEFINED)
3969 stored_player[i].connected = TRUE;
3972 #if DEBUG_INIT_PLAYER
3973 DebugPrintPlayerStatus("Player status after level initialization");
3976 #if DEBUG_INIT_PLAYER
3977 Debug("game:init:player", "Reassigning players ...");
3980 // check if any connected player was not found in playfield
3981 for (i = 0; i < MAX_PLAYERS; i++)
3983 struct PlayerInfo *player = &stored_player[i];
3985 if (player->connected && !player->present)
3987 struct PlayerInfo *field_player = NULL;
3989 #if DEBUG_INIT_PLAYER
3990 Debug("game:init:player",
3991 "- looking for field player for player %d ...", i + 1);
3994 // assign first free player found that is present in the playfield
3996 // first try: look for unmapped playfield player that is not connected
3997 for (j = 0; j < MAX_PLAYERS; j++)
3998 if (field_player == NULL &&
3999 stored_player[j].present &&
4000 !stored_player[j].mapped &&
4001 !stored_player[j].connected)
4002 field_player = &stored_player[j];
4004 // second try: look for *any* unmapped playfield player
4005 for (j = 0; j < MAX_PLAYERS; j++)
4006 if (field_player == NULL &&
4007 stored_player[j].present &&
4008 !stored_player[j].mapped)
4009 field_player = &stored_player[j];
4011 if (field_player != NULL)
4013 int jx = field_player->jx, jy = field_player->jy;
4015 #if DEBUG_INIT_PLAYER
4016 Debug("game:init:player", "- found player %d",
4017 field_player->index_nr + 1);
4020 player->present = FALSE;
4021 player->active = FALSE;
4023 field_player->present = TRUE;
4024 field_player->active = TRUE;
4027 player->initial_element = field_player->initial_element;
4028 player->artwork_element = field_player->artwork_element;
4030 player->block_last_field = field_player->block_last_field;
4031 player->block_delay_adjustment = field_player->block_delay_adjustment;
4034 StorePlayer[jx][jy] = field_player->element_nr;
4036 field_player->jx = field_player->last_jx = jx;
4037 field_player->jy = field_player->last_jy = jy;
4039 if (local_player == player)
4040 local_player = field_player;
4042 map_player_action[field_player->index_nr] = i;
4044 field_player->mapped = TRUE;
4046 #if DEBUG_INIT_PLAYER
4047 Debug("game:init:player", "- map_player_action[%d] == %d",
4048 field_player->index_nr + 1, i + 1);
4053 if (player->connected && player->present)
4054 player->mapped = TRUE;
4057 #if DEBUG_INIT_PLAYER
4058 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4063 // check if any connected player was not found in playfield
4064 for (i = 0; i < MAX_PLAYERS; i++)
4066 struct PlayerInfo *player = &stored_player[i];
4068 if (player->connected && !player->present)
4070 for (j = 0; j < MAX_PLAYERS; j++)
4072 struct PlayerInfo *field_player = &stored_player[j];
4073 int jx = field_player->jx, jy = field_player->jy;
4075 // assign first free player found that is present in the playfield
4076 if (field_player->present && !field_player->connected)
4078 player->present = TRUE;
4079 player->active = TRUE;
4081 field_player->present = FALSE;
4082 field_player->active = FALSE;
4084 player->initial_element = field_player->initial_element;
4085 player->artwork_element = field_player->artwork_element;
4087 player->block_last_field = field_player->block_last_field;
4088 player->block_delay_adjustment = field_player->block_delay_adjustment;
4090 StorePlayer[jx][jy] = player->element_nr;
4092 player->jx = player->last_jx = jx;
4093 player->jy = player->last_jy = jy;
4103 Debug("game:init:player", "local_player->present == %d",
4104 local_player->present);
4107 // set focus to local player for network games, else to all players
4108 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4109 game.centered_player_nr_next = game.centered_player_nr;
4110 game.set_centered_player = FALSE;
4111 game.set_centered_player_wrap = FALSE;
4113 if (network_playing && tape.recording)
4115 // store client dependent player focus when recording network games
4116 tape.centered_player_nr_next = game.centered_player_nr_next;
4117 tape.set_centered_player = TRUE;
4122 // when playing a tape, eliminate all players who do not participate
4124 #if USE_NEW_PLAYER_ASSIGNMENTS
4126 if (!game.team_mode)
4128 for (i = 0; i < MAX_PLAYERS; i++)
4130 if (stored_player[i].active &&
4131 !tape.player_participates[map_player_action[i]])
4133 struct PlayerInfo *player = &stored_player[i];
4134 int jx = player->jx, jy = player->jy;
4136 #if DEBUG_INIT_PLAYER
4137 Debug("game:init:player", "Removing player %d at (%d, %d)",
4141 player->active = FALSE;
4142 StorePlayer[jx][jy] = 0;
4143 Tile[jx][jy] = EL_EMPTY;
4150 for (i = 0; i < MAX_PLAYERS; i++)
4152 if (stored_player[i].active &&
4153 !tape.player_participates[i])
4155 struct PlayerInfo *player = &stored_player[i];
4156 int jx = player->jx, jy = player->jy;
4158 player->active = FALSE;
4159 StorePlayer[jx][jy] = 0;
4160 Tile[jx][jy] = EL_EMPTY;
4165 else if (!network.enabled && !game.team_mode) // && !tape.playing
4167 // when in single player mode, eliminate all but the local player
4169 for (i = 0; i < MAX_PLAYERS; i++)
4171 struct PlayerInfo *player = &stored_player[i];
4173 if (player->active && player != local_player)
4175 int jx = player->jx, jy = player->jy;
4177 player->active = FALSE;
4178 player->present = FALSE;
4180 StorePlayer[jx][jy] = 0;
4181 Tile[jx][jy] = EL_EMPTY;
4186 for (i = 0; i < MAX_PLAYERS; i++)
4187 if (stored_player[i].active)
4188 game.players_still_needed++;
4190 if (level.solved_by_one_player)
4191 game.players_still_needed = 1;
4193 // when recording the game, store which players take part in the game
4196 #if USE_NEW_PLAYER_ASSIGNMENTS
4197 for (i = 0; i < MAX_PLAYERS; i++)
4198 if (stored_player[i].connected)
4199 tape.player_participates[i] = TRUE;
4201 for (i = 0; i < MAX_PLAYERS; i++)
4202 if (stored_player[i].active)
4203 tape.player_participates[i] = TRUE;
4207 #if DEBUG_INIT_PLAYER
4208 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4211 if (BorderElement == EL_EMPTY)
4214 SBX_Right = lev_fieldx - SCR_FIELDX;
4216 SBY_Lower = lev_fieldy - SCR_FIELDY;
4221 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4223 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4226 if (full_lev_fieldx <= SCR_FIELDX)
4227 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4228 if (full_lev_fieldy <= SCR_FIELDY)
4229 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4231 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4233 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4236 // if local player not found, look for custom element that might create
4237 // the player (make some assumptions about the right custom element)
4238 if (!local_player->present)
4240 int start_x = 0, start_y = 0;
4241 int found_rating = 0;
4242 int found_element = EL_UNDEFINED;
4243 int player_nr = local_player->index_nr;
4245 SCAN_PLAYFIELD(x, y)
4247 int element = Tile[x][y];
4252 if (level.use_start_element[player_nr] &&
4253 level.start_element[player_nr] == element &&
4260 found_element = element;
4263 if (!IS_CUSTOM_ELEMENT(element))
4266 if (CAN_CHANGE(element))
4268 for (i = 0; i < element_info[element].num_change_pages; i++)
4270 // check for player created from custom element as single target
4271 content = element_info[element].change_page[i].target_element;
4272 is_player = ELEM_IS_PLAYER(content);
4274 if (is_player && (found_rating < 3 ||
4275 (found_rating == 3 && element < found_element)))
4281 found_element = element;
4286 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4288 // check for player created from custom element as explosion content
4289 content = element_info[element].content.e[xx][yy];
4290 is_player = ELEM_IS_PLAYER(content);
4292 if (is_player && (found_rating < 2 ||
4293 (found_rating == 2 && element < found_element)))
4295 start_x = x + xx - 1;
4296 start_y = y + yy - 1;
4299 found_element = element;
4302 if (!CAN_CHANGE(element))
4305 for (i = 0; i < element_info[element].num_change_pages; i++)
4307 // check for player created from custom element as extended target
4309 element_info[element].change_page[i].target_content.e[xx][yy];
4311 is_player = ELEM_IS_PLAYER(content);
4313 if (is_player && (found_rating < 1 ||
4314 (found_rating == 1 && element < found_element)))
4316 start_x = x + xx - 1;
4317 start_y = y + yy - 1;
4320 found_element = element;
4326 scroll_x = SCROLL_POSITION_X(start_x);
4327 scroll_y = SCROLL_POSITION_Y(start_y);
4331 scroll_x = SCROLL_POSITION_X(local_player->jx);
4332 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4335 // !!! FIX THIS (START) !!!
4336 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4338 InitGameEngine_EM();
4340 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4342 InitGameEngine_SP();
4344 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4346 InitGameEngine_MM();
4350 DrawLevel(REDRAW_FIELD);
4353 // after drawing the level, correct some elements
4354 if (game.timegate_time_left == 0)
4355 CloseAllOpenTimegates();
4358 // blit playfield from scroll buffer to normal back buffer for fading in
4359 BlitScreenToBitmap(backbuffer);
4360 // !!! FIX THIS (END) !!!
4362 DrawMaskedBorder(fade_mask);
4367 // full screen redraw is required at this point in the following cases:
4368 // - special editor door undrawn when game was started from level editor
4369 // - drawing area (playfield) was changed and has to be removed completely
4370 redraw_mask = REDRAW_ALL;
4374 if (!game.restart_level)
4376 // copy default game door content to main double buffer
4378 // !!! CHECK AGAIN !!!
4379 SetPanelBackground();
4380 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4381 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4384 SetPanelBackground();
4385 SetDrawBackgroundMask(REDRAW_DOOR_1);
4387 UpdateAndDisplayGameControlValues();
4389 if (!game.restart_level)
4395 CreateGameButtons();
4400 // copy actual game door content to door double buffer for OpenDoor()
4401 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4403 OpenDoor(DOOR_OPEN_ALL);
4405 KeyboardAutoRepeatOffUnlessAutoplay();
4407 #if DEBUG_INIT_PLAYER
4408 DebugPrintPlayerStatus("Player status (final)");
4417 if (!game.restart_level && !tape.playing)
4419 LevelStats_incPlayed(level_nr);
4421 SaveLevelSetup_SeriesInfo();
4424 game.restart_level = FALSE;
4425 game.restart_game_message = NULL;
4426 game.request_active = FALSE;
4428 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4429 InitGameActions_MM();
4431 SaveEngineSnapshotToListInitial();
4433 if (!game.restart_level)
4435 PlaySound(SND_GAME_STARTING);
4437 if (setup.sound_music)
4442 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4443 int actual_player_x, int actual_player_y)
4445 // this is used for non-R'n'D game engines to update certain engine values
4447 // needed to determine if sounds are played within the visible screen area
4448 scroll_x = actual_scroll_x;
4449 scroll_y = actual_scroll_y;
4451 // needed to get player position for "follow finger" playing input method
4452 local_player->jx = actual_player_x;
4453 local_player->jy = actual_player_y;
4456 void InitMovDir(int x, int y)
4458 int i, element = Tile[x][y];
4459 static int xy[4][2] =
4466 static int direction[3][4] =
4468 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4469 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4470 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4479 Tile[x][y] = EL_BUG;
4480 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4483 case EL_SPACESHIP_RIGHT:
4484 case EL_SPACESHIP_UP:
4485 case EL_SPACESHIP_LEFT:
4486 case EL_SPACESHIP_DOWN:
4487 Tile[x][y] = EL_SPACESHIP;
4488 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4491 case EL_BD_BUTTERFLY_RIGHT:
4492 case EL_BD_BUTTERFLY_UP:
4493 case EL_BD_BUTTERFLY_LEFT:
4494 case EL_BD_BUTTERFLY_DOWN:
4495 Tile[x][y] = EL_BD_BUTTERFLY;
4496 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4499 case EL_BD_FIREFLY_RIGHT:
4500 case EL_BD_FIREFLY_UP:
4501 case EL_BD_FIREFLY_LEFT:
4502 case EL_BD_FIREFLY_DOWN:
4503 Tile[x][y] = EL_BD_FIREFLY;
4504 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4507 case EL_PACMAN_RIGHT:
4509 case EL_PACMAN_LEFT:
4510 case EL_PACMAN_DOWN:
4511 Tile[x][y] = EL_PACMAN;
4512 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4515 case EL_YAMYAM_LEFT:
4516 case EL_YAMYAM_RIGHT:
4518 case EL_YAMYAM_DOWN:
4519 Tile[x][y] = EL_YAMYAM;
4520 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4523 case EL_SP_SNIKSNAK:
4524 MovDir[x][y] = MV_UP;
4527 case EL_SP_ELECTRON:
4528 MovDir[x][y] = MV_LEFT;
4535 Tile[x][y] = EL_MOLE;
4536 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4539 case EL_SPRING_LEFT:
4540 case EL_SPRING_RIGHT:
4541 Tile[x][y] = EL_SPRING;
4542 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4546 if (IS_CUSTOM_ELEMENT(element))
4548 struct ElementInfo *ei = &element_info[element];
4549 int move_direction_initial = ei->move_direction_initial;
4550 int move_pattern = ei->move_pattern;
4552 if (move_direction_initial == MV_START_PREVIOUS)
4554 if (MovDir[x][y] != MV_NONE)
4557 move_direction_initial = MV_START_AUTOMATIC;
4560 if (move_direction_initial == MV_START_RANDOM)
4561 MovDir[x][y] = 1 << RND(4);
4562 else if (move_direction_initial & MV_ANY_DIRECTION)
4563 MovDir[x][y] = move_direction_initial;
4564 else if (move_pattern == MV_ALL_DIRECTIONS ||
4565 move_pattern == MV_TURNING_LEFT ||
4566 move_pattern == MV_TURNING_RIGHT ||
4567 move_pattern == MV_TURNING_LEFT_RIGHT ||
4568 move_pattern == MV_TURNING_RIGHT_LEFT ||
4569 move_pattern == MV_TURNING_RANDOM)
4570 MovDir[x][y] = 1 << RND(4);
4571 else if (move_pattern == MV_HORIZONTAL)
4572 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4573 else if (move_pattern == MV_VERTICAL)
4574 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4575 else if (move_pattern & MV_ANY_DIRECTION)
4576 MovDir[x][y] = element_info[element].move_pattern;
4577 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4578 move_pattern == MV_ALONG_RIGHT_SIDE)
4580 // use random direction as default start direction
4581 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4582 MovDir[x][y] = 1 << RND(4);
4584 for (i = 0; i < NUM_DIRECTIONS; i++)
4586 int x1 = x + xy[i][0];
4587 int y1 = y + xy[i][1];
4589 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4591 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4592 MovDir[x][y] = direction[0][i];
4594 MovDir[x][y] = direction[1][i];
4603 MovDir[x][y] = 1 << RND(4);
4605 if (element != EL_BUG &&
4606 element != EL_SPACESHIP &&
4607 element != EL_BD_BUTTERFLY &&
4608 element != EL_BD_FIREFLY)
4611 for (i = 0; i < NUM_DIRECTIONS; i++)
4613 int x1 = x + xy[i][0];
4614 int y1 = y + xy[i][1];
4616 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4618 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4620 MovDir[x][y] = direction[0][i];
4623 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4624 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4626 MovDir[x][y] = direction[1][i];
4635 GfxDir[x][y] = MovDir[x][y];
4638 void InitAmoebaNr(int x, int y)
4641 int group_nr = AmoebaNeighbourNr(x, y);
4645 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4647 if (AmoebaCnt[i] == 0)
4655 AmoebaNr[x][y] = group_nr;
4656 AmoebaCnt[group_nr]++;
4657 AmoebaCnt2[group_nr]++;
4660 static void LevelSolved(void)
4662 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4663 game.players_still_needed > 0)
4666 game.LevelSolved = TRUE;
4667 game.GameOver = TRUE;
4669 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4670 game_em.lev->score :
4671 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4674 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4675 MM_HEALTH(game_mm.laser_overload_value) :
4678 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4679 game.LevelSolved_CountingScore = game.score_final;
4680 game.LevelSolved_CountingHealth = game.health_final;
4685 static int time_count_steps;
4686 static int time, time_final;
4687 static int score, score_final;
4688 static int health, health_final;
4689 static int game_over_delay_1 = 0;
4690 static int game_over_delay_2 = 0;
4691 static int game_over_delay_3 = 0;
4692 int game_over_delay_value_1 = 50;
4693 int game_over_delay_value_2 = 25;
4694 int game_over_delay_value_3 = 50;
4696 if (!game.LevelSolved_GameWon)
4700 // do not start end game actions before the player stops moving (to exit)
4701 if (local_player->active && local_player->MovPos)
4704 game.LevelSolved_GameWon = TRUE;
4705 game.LevelSolved_SaveTape = tape.recording;
4706 game.LevelSolved_SaveScore = !tape.playing;
4710 LevelStats_incSolved(level_nr);
4712 SaveLevelSetup_SeriesInfo();
4715 if (tape.auto_play) // tape might already be stopped here
4716 tape.auto_play_level_solved = TRUE;
4720 game_over_delay_1 = 0;
4721 game_over_delay_2 = 0;
4722 game_over_delay_3 = game_over_delay_value_3;
4724 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4725 score = score_final = game.score_final;
4726 health = health_final = game.health_final;
4728 if (level.score[SC_TIME_BONUS] > 0)
4733 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4735 else if (game.no_time_limit && TimePlayed < 999)
4738 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4741 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4743 game_over_delay_1 = game_over_delay_value_1;
4745 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4748 score_final += health * level.score[SC_TIME_BONUS];
4750 game_over_delay_2 = game_over_delay_value_2;
4753 game.score_final = score_final;
4754 game.health_final = health_final;
4757 if (level_editor_test_game)
4760 score = score_final;
4762 game.LevelSolved_CountingTime = time;
4763 game.LevelSolved_CountingScore = score;
4765 game_panel_controls[GAME_PANEL_TIME].value = time;
4766 game_panel_controls[GAME_PANEL_SCORE].value = score;
4768 DisplayGameControlValues();
4771 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4773 // check if last player has left the level
4774 if (game.exit_x >= 0 &&
4777 int x = game.exit_x;
4778 int y = game.exit_y;
4779 int element = Tile[x][y];
4781 // close exit door after last player
4782 if ((game.all_players_gone &&
4783 (element == EL_EXIT_OPEN ||
4784 element == EL_SP_EXIT_OPEN ||
4785 element == EL_STEEL_EXIT_OPEN)) ||
4786 element == EL_EM_EXIT_OPEN ||
4787 element == EL_EM_STEEL_EXIT_OPEN)
4791 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4792 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4793 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4794 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4795 EL_EM_STEEL_EXIT_CLOSING);
4797 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4800 // player disappears
4801 DrawLevelField(x, y);
4804 for (i = 0; i < MAX_PLAYERS; i++)
4806 struct PlayerInfo *player = &stored_player[i];
4808 if (player->present)
4810 RemovePlayer(player);
4812 // player disappears
4813 DrawLevelField(player->jx, player->jy);
4818 PlaySound(SND_GAME_WINNING);
4821 if (game_over_delay_1 > 0)
4823 game_over_delay_1--;
4828 if (time != time_final)
4830 int time_to_go = ABS(time_final - time);
4831 int time_count_dir = (time < time_final ? +1 : -1);
4833 if (time_to_go < time_count_steps)
4834 time_count_steps = 1;
4836 time += time_count_steps * time_count_dir;
4837 score += time_count_steps * level.score[SC_TIME_BONUS];
4839 game.LevelSolved_CountingTime = time;
4840 game.LevelSolved_CountingScore = score;
4842 game_panel_controls[GAME_PANEL_TIME].value = time;
4843 game_panel_controls[GAME_PANEL_SCORE].value = score;
4845 DisplayGameControlValues();
4847 if (time == time_final)
4848 StopSound(SND_GAME_LEVELTIME_BONUS);
4849 else if (setup.sound_loops)
4850 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4852 PlaySound(SND_GAME_LEVELTIME_BONUS);
4857 if (game_over_delay_2 > 0)
4859 game_over_delay_2--;
4864 if (health != health_final)
4866 int health_count_dir = (health < health_final ? +1 : -1);
4868 health += health_count_dir;
4869 score += level.score[SC_TIME_BONUS];
4871 game.LevelSolved_CountingHealth = health;
4872 game.LevelSolved_CountingScore = score;
4874 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4875 game_panel_controls[GAME_PANEL_SCORE].value = score;
4877 DisplayGameControlValues();
4879 if (health == health_final)
4880 StopSound(SND_GAME_LEVELTIME_BONUS);
4881 else if (setup.sound_loops)
4882 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4884 PlaySound(SND_GAME_LEVELTIME_BONUS);
4889 game.panel.active = FALSE;
4891 if (game_over_delay_3 > 0)
4893 game_over_delay_3--;
4903 // used instead of "level_nr" (needed for network games)
4904 int last_level_nr = levelset.level_nr;
4907 game.LevelSolved_GameEnd = TRUE;
4909 if (game.LevelSolved_SaveTape)
4911 // make sure that request dialog to save tape does not open door again
4912 if (!global.use_envelope_request)
4913 CloseDoor(DOOR_CLOSE_1);
4915 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4918 // if no tape is to be saved, close both doors simultaneously
4919 CloseDoor(DOOR_CLOSE_ALL);
4921 if (level_editor_test_game)
4923 SetGameStatus(GAME_MODE_MAIN);
4930 if (!game.LevelSolved_SaveScore)
4932 SetGameStatus(GAME_MODE_MAIN);
4939 if (level_nr == leveldir_current->handicap_level)
4941 leveldir_current->handicap_level++;
4943 SaveLevelSetup_SeriesInfo();
4946 if (setup.increment_levels &&
4947 level_nr < leveldir_current->last_level &&
4950 level_nr++; // advance to next level
4951 TapeErase(); // start with empty tape
4953 if (setup.auto_play_next_level)
4955 LoadLevel(level_nr);
4957 SaveLevelSetup_SeriesInfo();
4961 hi_pos = NewHiScore(last_level_nr);
4963 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4965 SetGameStatus(GAME_MODE_SCORES);
4967 DrawHallOfFame(last_level_nr, hi_pos);
4969 else if (setup.auto_play_next_level && setup.increment_levels &&
4970 last_level_nr < leveldir_current->last_level &&
4973 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4977 SetGameStatus(GAME_MODE_MAIN);
4983 int NewHiScore(int level_nr)
4987 boolean one_score_entry_per_name = !program.many_scores_per_name;
4989 LoadScore(level_nr);
4991 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4992 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4995 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4997 if (game.score_final > highscore[k].Score)
4999 // player has made it to the hall of fame
5001 if (k < MAX_SCORE_ENTRIES - 1)
5003 int m = MAX_SCORE_ENTRIES - 1;
5005 if (one_score_entry_per_name)
5007 for (l = k; l < MAX_SCORE_ENTRIES; l++)
5008 if (strEqual(setup.player_name, highscore[l].Name))
5011 if (m == k) // player's new highscore overwrites his old one
5015 for (l = m; l > k; l--)
5017 strcpy(highscore[l].Name, highscore[l - 1].Name);
5018 highscore[l].Score = highscore[l - 1].Score;
5024 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
5025 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
5026 highscore[k].Score = game.score_final;
5031 else if (one_score_entry_per_name &&
5032 !strncmp(setup.player_name, highscore[k].Name,
5033 MAX_PLAYER_NAME_LEN))
5034 break; // player already there with a higher score
5038 SaveScore(level_nr);
5043 static int getElementMoveStepsizeExt(int x, int y, int direction)
5045 int element = Tile[x][y];
5046 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5047 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5048 int horiz_move = (dx != 0);
5049 int sign = (horiz_move ? dx : dy);
5050 int step = sign * element_info[element].move_stepsize;
5052 // special values for move stepsize for spring and things on conveyor belt
5055 if (CAN_FALL(element) &&
5056 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5057 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5058 else if (element == EL_SPRING)
5059 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5065 static int getElementMoveStepsize(int x, int y)
5067 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5070 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5072 if (player->GfxAction != action || player->GfxDir != dir)
5074 player->GfxAction = action;
5075 player->GfxDir = dir;
5077 player->StepFrame = 0;
5081 static void ResetGfxFrame(int x, int y)
5083 // profiling showed that "autotest" spends 10~20% of its time in this function
5084 if (DrawingDeactivatedField())
5087 int element = Tile[x][y];
5088 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5090 if (graphic_info[graphic].anim_global_sync)
5091 GfxFrame[x][y] = FrameCounter;
5092 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5093 GfxFrame[x][y] = CustomValue[x][y];
5094 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5095 GfxFrame[x][y] = element_info[element].collect_score;
5096 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5097 GfxFrame[x][y] = ChangeDelay[x][y];
5100 static void ResetGfxAnimation(int x, int y)
5102 GfxAction[x][y] = ACTION_DEFAULT;
5103 GfxDir[x][y] = MovDir[x][y];
5106 ResetGfxFrame(x, y);
5109 static void ResetRandomAnimationValue(int x, int y)
5111 GfxRandom[x][y] = INIT_GFX_RANDOM();
5114 static void InitMovingField(int x, int y, int direction)
5116 int element = Tile[x][y];
5117 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5118 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5121 boolean is_moving_before, is_moving_after;
5123 // check if element was/is moving or being moved before/after mode change
5124 is_moving_before = (WasJustMoving[x][y] != 0);
5125 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5127 // reset animation only for moving elements which change direction of moving
5128 // or which just started or stopped moving
5129 // (else CEs with property "can move" / "not moving" are reset each frame)
5130 if (is_moving_before != is_moving_after ||
5131 direction != MovDir[x][y])
5132 ResetGfxAnimation(x, y);
5134 MovDir[x][y] = direction;
5135 GfxDir[x][y] = direction;
5137 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5138 direction == MV_DOWN && CAN_FALL(element) ?
5139 ACTION_FALLING : ACTION_MOVING);
5141 // this is needed for CEs with property "can move" / "not moving"
5143 if (is_moving_after)
5145 if (Tile[newx][newy] == EL_EMPTY)
5146 Tile[newx][newy] = EL_BLOCKED;
5148 MovDir[newx][newy] = MovDir[x][y];
5150 CustomValue[newx][newy] = CustomValue[x][y];
5152 GfxFrame[newx][newy] = GfxFrame[x][y];
5153 GfxRandom[newx][newy] = GfxRandom[x][y];
5154 GfxAction[newx][newy] = GfxAction[x][y];
5155 GfxDir[newx][newy] = GfxDir[x][y];
5159 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5161 int direction = MovDir[x][y];
5162 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5163 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5169 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5171 int oldx = x, oldy = y;
5172 int direction = MovDir[x][y];
5174 if (direction == MV_LEFT)
5176 else if (direction == MV_RIGHT)
5178 else if (direction == MV_UP)
5180 else if (direction == MV_DOWN)
5183 *comes_from_x = oldx;
5184 *comes_from_y = oldy;
5187 static int MovingOrBlocked2Element(int x, int y)
5189 int element = Tile[x][y];
5191 if (element == EL_BLOCKED)
5195 Blocked2Moving(x, y, &oldx, &oldy);
5196 return Tile[oldx][oldy];
5202 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5204 // like MovingOrBlocked2Element(), but if element is moving
5205 // and (x,y) is the field the moving element is just leaving,
5206 // return EL_BLOCKED instead of the element value
5207 int element = Tile[x][y];
5209 if (IS_MOVING(x, y))
5211 if (element == EL_BLOCKED)
5215 Blocked2Moving(x, y, &oldx, &oldy);
5216 return Tile[oldx][oldy];
5225 static void RemoveField(int x, int y)
5227 Tile[x][y] = EL_EMPTY;
5233 CustomValue[x][y] = 0;
5236 ChangeDelay[x][y] = 0;
5237 ChangePage[x][y] = -1;
5238 Pushed[x][y] = FALSE;
5240 GfxElement[x][y] = EL_UNDEFINED;
5241 GfxAction[x][y] = ACTION_DEFAULT;
5242 GfxDir[x][y] = MV_NONE;
5245 static void RemoveMovingField(int x, int y)
5247 int oldx = x, oldy = y, newx = x, newy = y;
5248 int element = Tile[x][y];
5249 int next_element = EL_UNDEFINED;
5251 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5254 if (IS_MOVING(x, y))
5256 Moving2Blocked(x, y, &newx, &newy);
5258 if (Tile[newx][newy] != EL_BLOCKED)
5260 // element is moving, but target field is not free (blocked), but
5261 // already occupied by something different (example: acid pool);
5262 // in this case, only remove the moving field, but not the target
5264 RemoveField(oldx, oldy);
5266 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5268 TEST_DrawLevelField(oldx, oldy);
5273 else if (element == EL_BLOCKED)
5275 Blocked2Moving(x, y, &oldx, &oldy);
5276 if (!IS_MOVING(oldx, oldy))
5280 if (element == EL_BLOCKED &&
5281 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5282 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5283 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5284 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5285 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5286 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5287 next_element = get_next_element(Tile[oldx][oldy]);
5289 RemoveField(oldx, oldy);
5290 RemoveField(newx, newy);
5292 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5294 if (next_element != EL_UNDEFINED)
5295 Tile[oldx][oldy] = next_element;
5297 TEST_DrawLevelField(oldx, oldy);
5298 TEST_DrawLevelField(newx, newy);
5301 void DrawDynamite(int x, int y)
5303 int sx = SCREENX(x), sy = SCREENY(y);
5304 int graphic = el2img(Tile[x][y]);
5307 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5310 if (IS_WALKABLE_INSIDE(Back[x][y]))
5314 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5315 else if (Store[x][y])
5316 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5318 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5320 if (Back[x][y] || Store[x][y])
5321 DrawGraphicThruMask(sx, sy, graphic, frame);
5323 DrawGraphic(sx, sy, graphic, frame);
5326 static void CheckDynamite(int x, int y)
5328 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5332 if (MovDelay[x][y] != 0)
5335 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5341 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5346 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5348 boolean num_checked_players = 0;
5351 for (i = 0; i < MAX_PLAYERS; i++)
5353 if (stored_player[i].active)
5355 int sx = stored_player[i].jx;
5356 int sy = stored_player[i].jy;
5358 if (num_checked_players == 0)
5365 *sx1 = MIN(*sx1, sx);
5366 *sy1 = MIN(*sy1, sy);
5367 *sx2 = MAX(*sx2, sx);
5368 *sy2 = MAX(*sy2, sy);
5371 num_checked_players++;
5376 static boolean checkIfAllPlayersFitToScreen_RND(void)
5378 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5380 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5382 return (sx2 - sx1 < SCR_FIELDX &&
5383 sy2 - sy1 < SCR_FIELDY);
5386 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5388 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5390 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5392 *sx = (sx1 + sx2) / 2;
5393 *sy = (sy1 + sy2) / 2;
5396 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5397 boolean center_screen, boolean quick_relocation)
5399 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5400 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5401 boolean no_delay = (tape.warp_forward);
5402 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5403 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5404 int new_scroll_x, new_scroll_y;
5406 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5408 // case 1: quick relocation inside visible screen (without scrolling)
5415 if (!level.shifted_relocation || center_screen)
5417 // relocation _with_ centering of screen
5419 new_scroll_x = SCROLL_POSITION_X(x);
5420 new_scroll_y = SCROLL_POSITION_Y(y);
5424 // relocation _without_ centering of screen
5426 int center_scroll_x = SCROLL_POSITION_X(old_x);
5427 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5428 int offset_x = x + (scroll_x - center_scroll_x);
5429 int offset_y = y + (scroll_y - center_scroll_y);
5431 // for new screen position, apply previous offset to center position
5432 new_scroll_x = SCROLL_POSITION_X(offset_x);
5433 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5436 if (quick_relocation)
5438 // case 2: quick relocation (redraw without visible scrolling)
5440 scroll_x = new_scroll_x;
5441 scroll_y = new_scroll_y;
5448 // case 3: visible relocation (with scrolling to new position)
5450 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5452 SetVideoFrameDelay(wait_delay_value);
5454 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5456 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5457 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5459 if (dx == 0 && dy == 0) // no scrolling needed at all
5465 // set values for horizontal/vertical screen scrolling (half tile size)
5466 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5467 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5468 int pos_x = dx * TILEX / 2;
5469 int pos_y = dy * TILEY / 2;
5470 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5471 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5473 ScrollLevel(dx, dy);
5476 // scroll in two steps of half tile size to make things smoother
5477 BlitScreenToBitmapExt_RND(window, fx, fy);
5479 // scroll second step to align at full tile size
5480 BlitScreenToBitmap(window);
5486 SetVideoFrameDelay(frame_delay_value_old);
5489 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5491 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5492 int player_nr = GET_PLAYER_NR(el_player);
5493 struct PlayerInfo *player = &stored_player[player_nr];
5494 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5495 boolean no_delay = (tape.warp_forward);
5496 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5497 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5498 int old_jx = player->jx;
5499 int old_jy = player->jy;
5500 int old_element = Tile[old_jx][old_jy];
5501 int element = Tile[jx][jy];
5502 boolean player_relocated = (old_jx != jx || old_jy != jy);
5504 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5505 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5506 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5507 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5508 int leave_side_horiz = move_dir_horiz;
5509 int leave_side_vert = move_dir_vert;
5510 int enter_side = enter_side_horiz | enter_side_vert;
5511 int leave_side = leave_side_horiz | leave_side_vert;
5513 if (player->buried) // do not reanimate dead player
5516 if (!player_relocated) // no need to relocate the player
5519 if (IS_PLAYER(jx, jy)) // player already placed at new position
5521 RemoveField(jx, jy); // temporarily remove newly placed player
5522 DrawLevelField(jx, jy);
5525 if (player->present)
5527 while (player->MovPos)
5529 ScrollPlayer(player, SCROLL_GO_ON);
5530 ScrollScreen(NULL, SCROLL_GO_ON);
5532 AdvanceFrameAndPlayerCounters(player->index_nr);
5536 BackToFront_WithFrameDelay(wait_delay_value);
5539 DrawPlayer(player); // needed here only to cleanup last field
5540 DrawLevelField(player->jx, player->jy); // remove player graphic
5542 player->is_moving = FALSE;
5545 if (IS_CUSTOM_ELEMENT(old_element))
5546 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5548 player->index_bit, leave_side);
5550 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5552 player->index_bit, leave_side);
5554 Tile[jx][jy] = el_player;
5555 InitPlayerField(jx, jy, el_player, TRUE);
5557 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5558 possible that the relocation target field did not contain a player element,
5559 but a walkable element, to which the new player was relocated -- in this
5560 case, restore that (already initialized!) element on the player field */
5561 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5563 Tile[jx][jy] = element; // restore previously existing element
5566 // only visually relocate centered player
5567 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5568 FALSE, level.instant_relocation);
5570 TestIfPlayerTouchesBadThing(jx, jy);
5571 TestIfPlayerTouchesCustomElement(jx, jy);
5573 if (IS_CUSTOM_ELEMENT(element))
5574 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5575 player->index_bit, enter_side);
5577 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5578 player->index_bit, enter_side);
5580 if (player->is_switching)
5582 /* ensure that relocation while still switching an element does not cause
5583 a new element to be treated as also switched directly after relocation
5584 (this is important for teleporter switches that teleport the player to
5585 a place where another teleporter switch is in the same direction, which
5586 would then incorrectly be treated as immediately switched before the
5587 direction key that caused the switch was released) */
5589 player->switch_x += jx - old_jx;
5590 player->switch_y += jy - old_jy;
5594 static void Explode(int ex, int ey, int phase, int mode)
5600 // !!! eliminate this variable !!!
5601 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5603 if (game.explosions_delayed)
5605 ExplodeField[ex][ey] = mode;
5609 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5611 int center_element = Tile[ex][ey];
5612 int artwork_element, explosion_element; // set these values later
5614 // remove things displayed in background while burning dynamite
5615 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5618 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5620 // put moving element to center field (and let it explode there)
5621 center_element = MovingOrBlocked2Element(ex, ey);
5622 RemoveMovingField(ex, ey);
5623 Tile[ex][ey] = center_element;
5626 // now "center_element" is finally determined -- set related values now
5627 artwork_element = center_element; // for custom player artwork
5628 explosion_element = center_element; // for custom player artwork
5630 if (IS_PLAYER(ex, ey))
5632 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5634 artwork_element = stored_player[player_nr].artwork_element;
5636 if (level.use_explosion_element[player_nr])
5638 explosion_element = level.explosion_element[player_nr];
5639 artwork_element = explosion_element;
5643 if (mode == EX_TYPE_NORMAL ||
5644 mode == EX_TYPE_CENTER ||
5645 mode == EX_TYPE_CROSS)
5646 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5648 last_phase = element_info[explosion_element].explosion_delay + 1;
5650 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5652 int xx = x - ex + 1;
5653 int yy = y - ey + 1;
5656 if (!IN_LEV_FIELD(x, y) ||
5657 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5658 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5661 element = Tile[x][y];
5663 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5665 element = MovingOrBlocked2Element(x, y);
5667 if (!IS_EXPLOSION_PROOF(element))
5668 RemoveMovingField(x, y);
5671 // indestructible elements can only explode in center (but not flames)
5672 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5673 mode == EX_TYPE_BORDER)) ||
5674 element == EL_FLAMES)
5677 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5678 behaviour, for example when touching a yamyam that explodes to rocks
5679 with active deadly shield, a rock is created under the player !!! */
5680 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5682 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5683 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5684 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5686 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5689 if (IS_ACTIVE_BOMB(element))
5691 // re-activate things under the bomb like gate or penguin
5692 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5699 // save walkable background elements while explosion on same tile
5700 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5701 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5702 Back[x][y] = element;
5704 // ignite explodable elements reached by other explosion
5705 if (element == EL_EXPLOSION)
5706 element = Store2[x][y];
5708 if (AmoebaNr[x][y] &&
5709 (element == EL_AMOEBA_FULL ||
5710 element == EL_BD_AMOEBA ||
5711 element == EL_AMOEBA_GROWING))
5713 AmoebaCnt[AmoebaNr[x][y]]--;
5714 AmoebaCnt2[AmoebaNr[x][y]]--;
5719 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5721 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5723 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5725 if (PLAYERINFO(ex, ey)->use_murphy)
5726 Store[x][y] = EL_EMPTY;
5729 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5730 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5731 else if (ELEM_IS_PLAYER(center_element))
5732 Store[x][y] = EL_EMPTY;
5733 else if (center_element == EL_YAMYAM)
5734 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5735 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5736 Store[x][y] = element_info[center_element].content.e[xx][yy];
5738 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5739 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5740 // otherwise) -- FIX THIS !!!
5741 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5742 Store[x][y] = element_info[element].content.e[1][1];
5744 else if (!CAN_EXPLODE(element))
5745 Store[x][y] = element_info[element].content.e[1][1];
5748 Store[x][y] = EL_EMPTY;
5750 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5751 center_element == EL_AMOEBA_TO_DIAMOND)
5752 Store2[x][y] = element;
5754 Tile[x][y] = EL_EXPLOSION;
5755 GfxElement[x][y] = artwork_element;
5757 ExplodePhase[x][y] = 1;
5758 ExplodeDelay[x][y] = last_phase;
5763 if (center_element == EL_YAMYAM)
5764 game.yamyam_content_nr =
5765 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5777 GfxFrame[x][y] = 0; // restart explosion animation
5779 last_phase = ExplodeDelay[x][y];
5781 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5783 // this can happen if the player leaves an explosion just in time
5784 if (GfxElement[x][y] == EL_UNDEFINED)
5785 GfxElement[x][y] = EL_EMPTY;
5787 border_element = Store2[x][y];
5788 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5789 border_element = StorePlayer[x][y];
5791 if (phase == element_info[border_element].ignition_delay ||
5792 phase == last_phase)
5794 boolean border_explosion = FALSE;
5796 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5797 !PLAYER_EXPLOSION_PROTECTED(x, y))
5799 KillPlayerUnlessExplosionProtected(x, y);
5800 border_explosion = TRUE;
5802 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5804 Tile[x][y] = Store2[x][y];
5807 border_explosion = TRUE;
5809 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5811 AmoebaToDiamond(x, y);
5813 border_explosion = TRUE;
5816 // if an element just explodes due to another explosion (chain-reaction),
5817 // do not immediately end the new explosion when it was the last frame of
5818 // the explosion (as it would be done in the following "if"-statement!)
5819 if (border_explosion && phase == last_phase)
5823 if (phase == last_phase)
5827 element = Tile[x][y] = Store[x][y];
5828 Store[x][y] = Store2[x][y] = 0;
5829 GfxElement[x][y] = EL_UNDEFINED;
5831 // player can escape from explosions and might therefore be still alive
5832 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5833 element <= EL_PLAYER_IS_EXPLODING_4)
5835 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5836 int explosion_element = EL_PLAYER_1 + player_nr;
5837 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5838 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5840 if (level.use_explosion_element[player_nr])
5841 explosion_element = level.explosion_element[player_nr];
5843 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5844 element_info[explosion_element].content.e[xx][yy]);
5847 // restore probably existing indestructible background element
5848 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5849 element = Tile[x][y] = Back[x][y];
5852 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5853 GfxDir[x][y] = MV_NONE;
5854 ChangeDelay[x][y] = 0;
5855 ChangePage[x][y] = -1;
5857 CustomValue[x][y] = 0;
5859 InitField_WithBug2(x, y, FALSE);
5861 TEST_DrawLevelField(x, y);
5863 TestIfElementTouchesCustomElement(x, y);
5865 if (GFX_CRUMBLED(element))
5866 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5868 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5869 StorePlayer[x][y] = 0;
5871 if (ELEM_IS_PLAYER(element))
5872 RelocatePlayer(x, y, element);
5874 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5876 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5877 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5880 TEST_DrawLevelFieldCrumbled(x, y);
5882 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5884 DrawLevelElement(x, y, Back[x][y]);
5885 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5887 else if (IS_WALKABLE_UNDER(Back[x][y]))
5889 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5890 DrawLevelElementThruMask(x, y, Back[x][y]);
5892 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5893 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5897 static void DynaExplode(int ex, int ey)
5900 int dynabomb_element = Tile[ex][ey];
5901 int dynabomb_size = 1;
5902 boolean dynabomb_xl = FALSE;
5903 struct PlayerInfo *player;
5904 static int xy[4][2] =
5912 if (IS_ACTIVE_BOMB(dynabomb_element))
5914 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5915 dynabomb_size = player->dynabomb_size;
5916 dynabomb_xl = player->dynabomb_xl;
5917 player->dynabombs_left++;
5920 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5922 for (i = 0; i < NUM_DIRECTIONS; i++)
5924 for (j = 1; j <= dynabomb_size; j++)
5926 int x = ex + j * xy[i][0];
5927 int y = ey + j * xy[i][1];
5930 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5933 element = Tile[x][y];
5935 // do not restart explosions of fields with active bombs
5936 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5939 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5941 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5942 !IS_DIGGABLE(element) && !dynabomb_xl)
5948 void Bang(int x, int y)
5950 int element = MovingOrBlocked2Element(x, y);
5951 int explosion_type = EX_TYPE_NORMAL;
5953 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5955 struct PlayerInfo *player = PLAYERINFO(x, y);
5957 element = Tile[x][y] = player->initial_element;
5959 if (level.use_explosion_element[player->index_nr])
5961 int explosion_element = level.explosion_element[player->index_nr];
5963 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5964 explosion_type = EX_TYPE_CROSS;
5965 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5966 explosion_type = EX_TYPE_CENTER;
5974 case EL_BD_BUTTERFLY:
5977 case EL_DARK_YAMYAM:
5981 RaiseScoreElement(element);
5984 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5985 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5986 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5987 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5988 case EL_DYNABOMB_INCREASE_NUMBER:
5989 case EL_DYNABOMB_INCREASE_SIZE:
5990 case EL_DYNABOMB_INCREASE_POWER:
5991 explosion_type = EX_TYPE_DYNA;
5994 case EL_DC_LANDMINE:
5995 explosion_type = EX_TYPE_CENTER;
6000 case EL_LAMP_ACTIVE:
6001 case EL_AMOEBA_TO_DIAMOND:
6002 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6003 explosion_type = EX_TYPE_CENTER;
6007 if (element_info[element].explosion_type == EXPLODES_CROSS)
6008 explosion_type = EX_TYPE_CROSS;
6009 else if (element_info[element].explosion_type == EXPLODES_1X1)
6010 explosion_type = EX_TYPE_CENTER;
6014 if (explosion_type == EX_TYPE_DYNA)
6017 Explode(x, y, EX_PHASE_START, explosion_type);
6019 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6022 static void SplashAcid(int x, int y)
6024 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6025 (!IN_LEV_FIELD(x - 1, y - 2) ||
6026 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6027 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6029 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6030 (!IN_LEV_FIELD(x + 1, y - 2) ||
6031 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6032 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6034 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6037 static void InitBeltMovement(void)
6039 static int belt_base_element[4] =
6041 EL_CONVEYOR_BELT_1_LEFT,
6042 EL_CONVEYOR_BELT_2_LEFT,
6043 EL_CONVEYOR_BELT_3_LEFT,
6044 EL_CONVEYOR_BELT_4_LEFT
6046 static int belt_base_active_element[4] =
6048 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6049 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6050 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6051 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6056 // set frame order for belt animation graphic according to belt direction
6057 for (i = 0; i < NUM_BELTS; i++)
6061 for (j = 0; j < NUM_BELT_PARTS; j++)
6063 int element = belt_base_active_element[belt_nr] + j;
6064 int graphic_1 = el2img(element);
6065 int graphic_2 = el2panelimg(element);
6067 if (game.belt_dir[i] == MV_LEFT)
6069 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6070 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6074 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6075 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6080 SCAN_PLAYFIELD(x, y)
6082 int element = Tile[x][y];
6084 for (i = 0; i < NUM_BELTS; i++)
6086 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6088 int e_belt_nr = getBeltNrFromBeltElement(element);
6091 if (e_belt_nr == belt_nr)
6093 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6095 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6102 static void ToggleBeltSwitch(int x, int y)
6104 static int belt_base_element[4] =
6106 EL_CONVEYOR_BELT_1_LEFT,
6107 EL_CONVEYOR_BELT_2_LEFT,
6108 EL_CONVEYOR_BELT_3_LEFT,
6109 EL_CONVEYOR_BELT_4_LEFT
6111 static int belt_base_active_element[4] =
6113 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6114 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6115 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6116 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6118 static int belt_base_switch_element[4] =
6120 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6121 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6122 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6123 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6125 static int belt_move_dir[4] =
6133 int element = Tile[x][y];
6134 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6135 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6136 int belt_dir = belt_move_dir[belt_dir_nr];
6139 if (!IS_BELT_SWITCH(element))
6142 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6143 game.belt_dir[belt_nr] = belt_dir;
6145 if (belt_dir_nr == 3)
6148 // set frame order for belt animation graphic according to belt direction
6149 for (i = 0; i < NUM_BELT_PARTS; i++)
6151 int element = belt_base_active_element[belt_nr] + i;
6152 int graphic_1 = el2img(element);
6153 int graphic_2 = el2panelimg(element);
6155 if (belt_dir == MV_LEFT)
6157 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6158 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6162 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6163 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6167 SCAN_PLAYFIELD(xx, yy)
6169 int element = Tile[xx][yy];
6171 if (IS_BELT_SWITCH(element))
6173 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6175 if (e_belt_nr == belt_nr)
6177 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6178 TEST_DrawLevelField(xx, yy);
6181 else if (IS_BELT(element) && belt_dir != MV_NONE)
6183 int e_belt_nr = getBeltNrFromBeltElement(element);
6185 if (e_belt_nr == belt_nr)
6187 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6189 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6190 TEST_DrawLevelField(xx, yy);
6193 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6195 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6197 if (e_belt_nr == belt_nr)
6199 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6201 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6202 TEST_DrawLevelField(xx, yy);
6208 static void ToggleSwitchgateSwitch(int x, int y)
6212 game.switchgate_pos = !game.switchgate_pos;
6214 SCAN_PLAYFIELD(xx, yy)
6216 int element = Tile[xx][yy];
6218 if (element == EL_SWITCHGATE_SWITCH_UP)
6220 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6221 TEST_DrawLevelField(xx, yy);
6223 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6225 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6226 TEST_DrawLevelField(xx, yy);
6228 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6230 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6231 TEST_DrawLevelField(xx, yy);
6233 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6235 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6236 TEST_DrawLevelField(xx, yy);
6238 else if (element == EL_SWITCHGATE_OPEN ||
6239 element == EL_SWITCHGATE_OPENING)
6241 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6243 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6245 else if (element == EL_SWITCHGATE_CLOSED ||
6246 element == EL_SWITCHGATE_CLOSING)
6248 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6250 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6255 static int getInvisibleActiveFromInvisibleElement(int element)
6257 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6258 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6259 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6263 static int getInvisibleFromInvisibleActiveElement(int element)
6265 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6266 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6267 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6271 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6275 SCAN_PLAYFIELD(x, y)
6277 int element = Tile[x][y];
6279 if (element == EL_LIGHT_SWITCH &&
6280 game.light_time_left > 0)
6282 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6283 TEST_DrawLevelField(x, y);
6285 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6286 game.light_time_left == 0)
6288 Tile[x][y] = EL_LIGHT_SWITCH;
6289 TEST_DrawLevelField(x, y);
6291 else if (element == EL_EMC_DRIPPER &&
6292 game.light_time_left > 0)
6294 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6295 TEST_DrawLevelField(x, y);
6297 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6298 game.light_time_left == 0)
6300 Tile[x][y] = EL_EMC_DRIPPER;
6301 TEST_DrawLevelField(x, y);
6303 else if (element == EL_INVISIBLE_STEELWALL ||
6304 element == EL_INVISIBLE_WALL ||
6305 element == EL_INVISIBLE_SAND)
6307 if (game.light_time_left > 0)
6308 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6310 TEST_DrawLevelField(x, y);
6312 // uncrumble neighbour fields, if needed
6313 if (element == EL_INVISIBLE_SAND)
6314 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6316 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6317 element == EL_INVISIBLE_WALL_ACTIVE ||
6318 element == EL_INVISIBLE_SAND_ACTIVE)
6320 if (game.light_time_left == 0)
6321 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6323 TEST_DrawLevelField(x, y);
6325 // re-crumble neighbour fields, if needed
6326 if (element == EL_INVISIBLE_SAND)
6327 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6332 static void RedrawAllInvisibleElementsForLenses(void)
6336 SCAN_PLAYFIELD(x, y)
6338 int element = Tile[x][y];
6340 if (element == EL_EMC_DRIPPER &&
6341 game.lenses_time_left > 0)
6343 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6344 TEST_DrawLevelField(x, y);
6346 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6347 game.lenses_time_left == 0)
6349 Tile[x][y] = EL_EMC_DRIPPER;
6350 TEST_DrawLevelField(x, y);
6352 else if (element == EL_INVISIBLE_STEELWALL ||
6353 element == EL_INVISIBLE_WALL ||
6354 element == EL_INVISIBLE_SAND)
6356 if (game.lenses_time_left > 0)
6357 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6359 TEST_DrawLevelField(x, y);
6361 // uncrumble neighbour fields, if needed
6362 if (element == EL_INVISIBLE_SAND)
6363 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6365 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6366 element == EL_INVISIBLE_WALL_ACTIVE ||
6367 element == EL_INVISIBLE_SAND_ACTIVE)
6369 if (game.lenses_time_left == 0)
6370 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6372 TEST_DrawLevelField(x, y);
6374 // re-crumble neighbour fields, if needed
6375 if (element == EL_INVISIBLE_SAND)
6376 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6381 static void RedrawAllInvisibleElementsForMagnifier(void)
6385 SCAN_PLAYFIELD(x, y)
6387 int element = Tile[x][y];
6389 if (element == EL_EMC_FAKE_GRASS &&
6390 game.magnify_time_left > 0)
6392 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6393 TEST_DrawLevelField(x, y);
6395 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6396 game.magnify_time_left == 0)
6398 Tile[x][y] = EL_EMC_FAKE_GRASS;
6399 TEST_DrawLevelField(x, y);
6401 else if (IS_GATE_GRAY(element) &&
6402 game.magnify_time_left > 0)
6404 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6405 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6406 IS_EM_GATE_GRAY(element) ?
6407 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6408 IS_EMC_GATE_GRAY(element) ?
6409 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6410 IS_DC_GATE_GRAY(element) ?
6411 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6413 TEST_DrawLevelField(x, y);
6415 else if (IS_GATE_GRAY_ACTIVE(element) &&
6416 game.magnify_time_left == 0)
6418 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6419 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6420 IS_EM_GATE_GRAY_ACTIVE(element) ?
6421 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6422 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6423 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6424 IS_DC_GATE_GRAY_ACTIVE(element) ?
6425 EL_DC_GATE_WHITE_GRAY :
6427 TEST_DrawLevelField(x, y);
6432 static void ToggleLightSwitch(int x, int y)
6434 int element = Tile[x][y];
6436 game.light_time_left =
6437 (element == EL_LIGHT_SWITCH ?
6438 level.time_light * FRAMES_PER_SECOND : 0);
6440 RedrawAllLightSwitchesAndInvisibleElements();
6443 static void ActivateTimegateSwitch(int x, int y)
6447 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6449 SCAN_PLAYFIELD(xx, yy)
6451 int element = Tile[xx][yy];
6453 if (element == EL_TIMEGATE_CLOSED ||
6454 element == EL_TIMEGATE_CLOSING)
6456 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6457 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6461 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6463 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6464 TEST_DrawLevelField(xx, yy);
6470 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6471 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6474 static void Impact(int x, int y)
6476 boolean last_line = (y == lev_fieldy - 1);
6477 boolean object_hit = FALSE;
6478 boolean impact = (last_line || object_hit);
6479 int element = Tile[x][y];
6480 int smashed = EL_STEELWALL;
6482 if (!last_line) // check if element below was hit
6484 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6487 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6488 MovDir[x][y + 1] != MV_DOWN ||
6489 MovPos[x][y + 1] <= TILEY / 2));
6491 // do not smash moving elements that left the smashed field in time
6492 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6493 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6496 #if USE_QUICKSAND_IMPACT_BUGFIX
6497 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6499 RemoveMovingField(x, y + 1);
6500 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6501 Tile[x][y + 2] = EL_ROCK;
6502 TEST_DrawLevelField(x, y + 2);
6507 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6509 RemoveMovingField(x, y + 1);
6510 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6511 Tile[x][y + 2] = EL_ROCK;
6512 TEST_DrawLevelField(x, y + 2);
6519 smashed = MovingOrBlocked2Element(x, y + 1);
6521 impact = (last_line || object_hit);
6524 if (!last_line && smashed == EL_ACID) // element falls into acid
6526 SplashAcid(x, y + 1);
6530 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6531 // only reset graphic animation if graphic really changes after impact
6533 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6535 ResetGfxAnimation(x, y);
6536 TEST_DrawLevelField(x, y);
6539 if (impact && CAN_EXPLODE_IMPACT(element))
6544 else if (impact && element == EL_PEARL &&
6545 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6547 ResetGfxAnimation(x, y);
6549 Tile[x][y] = EL_PEARL_BREAKING;
6550 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6553 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6555 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6560 if (impact && element == EL_AMOEBA_DROP)
6562 if (object_hit && IS_PLAYER(x, y + 1))
6563 KillPlayerUnlessEnemyProtected(x, y + 1);
6564 else if (object_hit && smashed == EL_PENGUIN)
6568 Tile[x][y] = EL_AMOEBA_GROWING;
6569 Store[x][y] = EL_AMOEBA_WET;
6571 ResetRandomAnimationValue(x, y);
6576 if (object_hit) // check which object was hit
6578 if ((CAN_PASS_MAGIC_WALL(element) &&
6579 (smashed == EL_MAGIC_WALL ||
6580 smashed == EL_BD_MAGIC_WALL)) ||
6581 (CAN_PASS_DC_MAGIC_WALL(element) &&
6582 smashed == EL_DC_MAGIC_WALL))
6585 int activated_magic_wall =
6586 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6587 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6588 EL_DC_MAGIC_WALL_ACTIVE);
6590 // activate magic wall / mill
6591 SCAN_PLAYFIELD(xx, yy)
6593 if (Tile[xx][yy] == smashed)
6594 Tile[xx][yy] = activated_magic_wall;
6597 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6598 game.magic_wall_active = TRUE;
6600 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6601 SND_MAGIC_WALL_ACTIVATING :
6602 smashed == EL_BD_MAGIC_WALL ?
6603 SND_BD_MAGIC_WALL_ACTIVATING :
6604 SND_DC_MAGIC_WALL_ACTIVATING));
6607 if (IS_PLAYER(x, y + 1))
6609 if (CAN_SMASH_PLAYER(element))
6611 KillPlayerUnlessEnemyProtected(x, y + 1);
6615 else if (smashed == EL_PENGUIN)
6617 if (CAN_SMASH_PLAYER(element))
6623 else if (element == EL_BD_DIAMOND)
6625 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6631 else if (((element == EL_SP_INFOTRON ||
6632 element == EL_SP_ZONK) &&
6633 (smashed == EL_SP_SNIKSNAK ||
6634 smashed == EL_SP_ELECTRON ||
6635 smashed == EL_SP_DISK_ORANGE)) ||
6636 (element == EL_SP_INFOTRON &&
6637 smashed == EL_SP_DISK_YELLOW))
6642 else if (CAN_SMASH_EVERYTHING(element))
6644 if (IS_CLASSIC_ENEMY(smashed) ||
6645 CAN_EXPLODE_SMASHED(smashed))
6650 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6652 if (smashed == EL_LAMP ||
6653 smashed == EL_LAMP_ACTIVE)
6658 else if (smashed == EL_NUT)
6660 Tile[x][y + 1] = EL_NUT_BREAKING;
6661 PlayLevelSound(x, y, SND_NUT_BREAKING);
6662 RaiseScoreElement(EL_NUT);
6665 else if (smashed == EL_PEARL)
6667 ResetGfxAnimation(x, y);
6669 Tile[x][y + 1] = EL_PEARL_BREAKING;
6670 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6673 else if (smashed == EL_DIAMOND)
6675 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6676 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6679 else if (IS_BELT_SWITCH(smashed))
6681 ToggleBeltSwitch(x, y + 1);
6683 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6684 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6685 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6686 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6688 ToggleSwitchgateSwitch(x, y + 1);
6690 else if (smashed == EL_LIGHT_SWITCH ||
6691 smashed == EL_LIGHT_SWITCH_ACTIVE)
6693 ToggleLightSwitch(x, y + 1);
6697 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6699 CheckElementChangeBySide(x, y + 1, smashed, element,
6700 CE_SWITCHED, CH_SIDE_TOP);
6701 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6707 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6712 // play sound of magic wall / mill
6714 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6715 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6716 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6718 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6719 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6720 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6721 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6722 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6723 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6728 // play sound of object that hits the ground
6729 if (last_line || object_hit)
6730 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6733 static void TurnRoundExt(int x, int y)
6745 { 0, 0 }, { 0, 0 }, { 0, 0 },
6750 int left, right, back;
6754 { MV_DOWN, MV_UP, MV_RIGHT },
6755 { MV_UP, MV_DOWN, MV_LEFT },
6757 { MV_LEFT, MV_RIGHT, MV_DOWN },
6761 { MV_RIGHT, MV_LEFT, MV_UP }
6764 int element = Tile[x][y];
6765 int move_pattern = element_info[element].move_pattern;
6767 int old_move_dir = MovDir[x][y];
6768 int left_dir = turn[old_move_dir].left;
6769 int right_dir = turn[old_move_dir].right;
6770 int back_dir = turn[old_move_dir].back;
6772 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6773 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6774 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6775 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6777 int left_x = x + left_dx, left_y = y + left_dy;
6778 int right_x = x + right_dx, right_y = y + right_dy;
6779 int move_x = x + move_dx, move_y = y + move_dy;
6783 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6785 TestIfBadThingTouchesOtherBadThing(x, y);
6787 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6788 MovDir[x][y] = right_dir;
6789 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6790 MovDir[x][y] = left_dir;
6792 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6794 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6797 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6799 TestIfBadThingTouchesOtherBadThing(x, y);
6801 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6802 MovDir[x][y] = left_dir;
6803 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6804 MovDir[x][y] = right_dir;
6806 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6808 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6811 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6813 TestIfBadThingTouchesOtherBadThing(x, y);
6815 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6816 MovDir[x][y] = left_dir;
6817 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6818 MovDir[x][y] = right_dir;
6820 if (MovDir[x][y] != old_move_dir)
6823 else if (element == EL_YAMYAM)
6825 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6826 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6828 if (can_turn_left && can_turn_right)
6829 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6830 else if (can_turn_left)
6831 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6832 else if (can_turn_right)
6833 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6835 MovDir[x][y] = back_dir;
6837 MovDelay[x][y] = 16 + 16 * RND(3);
6839 else if (element == EL_DARK_YAMYAM)
6841 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6843 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6846 if (can_turn_left && can_turn_right)
6847 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6848 else if (can_turn_left)
6849 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6850 else if (can_turn_right)
6851 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6853 MovDir[x][y] = back_dir;
6855 MovDelay[x][y] = 16 + 16 * RND(3);
6857 else if (element == EL_PACMAN)
6859 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6860 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6862 if (can_turn_left && can_turn_right)
6863 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6864 else if (can_turn_left)
6865 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6866 else if (can_turn_right)
6867 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6869 MovDir[x][y] = back_dir;
6871 MovDelay[x][y] = 6 + RND(40);
6873 else if (element == EL_PIG)
6875 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6876 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6877 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6878 boolean should_turn_left, should_turn_right, should_move_on;
6880 int rnd = RND(rnd_value);
6882 should_turn_left = (can_turn_left &&
6884 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6885 y + back_dy + left_dy)));
6886 should_turn_right = (can_turn_right &&
6888 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6889 y + back_dy + right_dy)));
6890 should_move_on = (can_move_on &&
6893 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6894 y + move_dy + left_dy) ||
6895 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6896 y + move_dy + right_dy)));
6898 if (should_turn_left || should_turn_right || should_move_on)
6900 if (should_turn_left && should_turn_right && should_move_on)
6901 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6902 rnd < 2 * rnd_value / 3 ? right_dir :
6904 else if (should_turn_left && should_turn_right)
6905 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6906 else if (should_turn_left && should_move_on)
6907 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6908 else if (should_turn_right && should_move_on)
6909 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6910 else if (should_turn_left)
6911 MovDir[x][y] = left_dir;
6912 else if (should_turn_right)
6913 MovDir[x][y] = right_dir;
6914 else if (should_move_on)
6915 MovDir[x][y] = old_move_dir;
6917 else if (can_move_on && rnd > rnd_value / 8)
6918 MovDir[x][y] = old_move_dir;
6919 else if (can_turn_left && can_turn_right)
6920 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6921 else if (can_turn_left && rnd > rnd_value / 8)
6922 MovDir[x][y] = left_dir;
6923 else if (can_turn_right && rnd > rnd_value/8)
6924 MovDir[x][y] = right_dir;
6926 MovDir[x][y] = back_dir;
6928 xx = x + move_xy[MovDir[x][y]].dx;
6929 yy = y + move_xy[MovDir[x][y]].dy;
6931 if (!IN_LEV_FIELD(xx, yy) ||
6932 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6933 MovDir[x][y] = old_move_dir;
6937 else if (element == EL_DRAGON)
6939 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6940 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6941 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6943 int rnd = RND(rnd_value);
6945 if (can_move_on && rnd > rnd_value / 8)
6946 MovDir[x][y] = old_move_dir;
6947 else if (can_turn_left && can_turn_right)
6948 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6949 else if (can_turn_left && rnd > rnd_value / 8)
6950 MovDir[x][y] = left_dir;
6951 else if (can_turn_right && rnd > rnd_value / 8)
6952 MovDir[x][y] = right_dir;
6954 MovDir[x][y] = back_dir;
6956 xx = x + move_xy[MovDir[x][y]].dx;
6957 yy = y + move_xy[MovDir[x][y]].dy;
6959 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6960 MovDir[x][y] = old_move_dir;
6964 else if (element == EL_MOLE)
6966 boolean can_move_on =
6967 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6968 IS_AMOEBOID(Tile[move_x][move_y]) ||
6969 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
6972 boolean can_turn_left =
6973 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6974 IS_AMOEBOID(Tile[left_x][left_y])));
6976 boolean can_turn_right =
6977 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6978 IS_AMOEBOID(Tile[right_x][right_y])));
6980 if (can_turn_left && can_turn_right)
6981 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6982 else if (can_turn_left)
6983 MovDir[x][y] = left_dir;
6985 MovDir[x][y] = right_dir;
6988 if (MovDir[x][y] != old_move_dir)
6991 else if (element == EL_BALLOON)
6993 MovDir[x][y] = game.wind_direction;
6996 else if (element == EL_SPRING)
6998 if (MovDir[x][y] & MV_HORIZONTAL)
7000 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7001 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7003 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7004 ResetGfxAnimation(move_x, move_y);
7005 TEST_DrawLevelField(move_x, move_y);
7007 MovDir[x][y] = back_dir;
7009 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7010 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7011 MovDir[x][y] = MV_NONE;
7016 else if (element == EL_ROBOT ||
7017 element == EL_SATELLITE ||
7018 element == EL_PENGUIN ||
7019 element == EL_EMC_ANDROID)
7021 int attr_x = -1, attr_y = -1;
7023 if (game.all_players_gone)
7025 attr_x = game.exit_x;
7026 attr_y = game.exit_y;
7032 for (i = 0; i < MAX_PLAYERS; i++)
7034 struct PlayerInfo *player = &stored_player[i];
7035 int jx = player->jx, jy = player->jy;
7037 if (!player->active)
7041 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7049 if (element == EL_ROBOT &&
7050 game.robot_wheel_x >= 0 &&
7051 game.robot_wheel_y >= 0 &&
7052 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7053 game.engine_version < VERSION_IDENT(3,1,0,0)))
7055 attr_x = game.robot_wheel_x;
7056 attr_y = game.robot_wheel_y;
7059 if (element == EL_PENGUIN)
7062 static int xy[4][2] =
7070 for (i = 0; i < NUM_DIRECTIONS; i++)
7072 int ex = x + xy[i][0];
7073 int ey = y + xy[i][1];
7075 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7076 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7077 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7078 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7087 MovDir[x][y] = MV_NONE;
7089 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7090 else if (attr_x > x)
7091 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7093 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7094 else if (attr_y > y)
7095 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7097 if (element == EL_ROBOT)
7101 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7102 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7103 Moving2Blocked(x, y, &newx, &newy);
7105 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7106 MovDelay[x][y] = 8 + 8 * !RND(3);
7108 MovDelay[x][y] = 16;
7110 else if (element == EL_PENGUIN)
7116 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7118 boolean first_horiz = RND(2);
7119 int new_move_dir = MovDir[x][y];
7122 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7123 Moving2Blocked(x, y, &newx, &newy);
7125 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7129 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7130 Moving2Blocked(x, y, &newx, &newy);
7132 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7135 MovDir[x][y] = old_move_dir;
7139 else if (element == EL_SATELLITE)
7145 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7147 boolean first_horiz = RND(2);
7148 int new_move_dir = MovDir[x][y];
7151 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7152 Moving2Blocked(x, y, &newx, &newy);
7154 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7158 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7159 Moving2Blocked(x, y, &newx, &newy);
7161 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7164 MovDir[x][y] = old_move_dir;
7168 else if (element == EL_EMC_ANDROID)
7170 static int check_pos[16] =
7172 -1, // 0 => (invalid)
7175 -1, // 3 => (invalid)
7177 0, // 5 => MV_LEFT | MV_UP
7178 2, // 6 => MV_RIGHT | MV_UP
7179 -1, // 7 => (invalid)
7181 6, // 9 => MV_LEFT | MV_DOWN
7182 4, // 10 => MV_RIGHT | MV_DOWN
7183 -1, // 11 => (invalid)
7184 -1, // 12 => (invalid)
7185 -1, // 13 => (invalid)
7186 -1, // 14 => (invalid)
7187 -1, // 15 => (invalid)
7195 { -1, -1, MV_LEFT | MV_UP },
7197 { +1, -1, MV_RIGHT | MV_UP },
7198 { +1, 0, MV_RIGHT },
7199 { +1, +1, MV_RIGHT | MV_DOWN },
7201 { -1, +1, MV_LEFT | MV_DOWN },
7204 int start_pos, check_order;
7205 boolean can_clone = FALSE;
7208 // check if there is any free field around current position
7209 for (i = 0; i < 8; i++)
7211 int newx = x + check_xy[i].dx;
7212 int newy = y + check_xy[i].dy;
7214 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7222 if (can_clone) // randomly find an element to clone
7226 start_pos = check_pos[RND(8)];
7227 check_order = (RND(2) ? -1 : +1);
7229 for (i = 0; i < 8; i++)
7231 int pos_raw = start_pos + i * check_order;
7232 int pos = (pos_raw + 8) % 8;
7233 int newx = x + check_xy[pos].dx;
7234 int newy = y + check_xy[pos].dy;
7236 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7238 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7239 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7241 Store[x][y] = Tile[newx][newy];
7250 if (can_clone) // randomly find a direction to move
7254 start_pos = check_pos[RND(8)];
7255 check_order = (RND(2) ? -1 : +1);
7257 for (i = 0; i < 8; i++)
7259 int pos_raw = start_pos + i * check_order;
7260 int pos = (pos_raw + 8) % 8;
7261 int newx = x + check_xy[pos].dx;
7262 int newy = y + check_xy[pos].dy;
7263 int new_move_dir = check_xy[pos].dir;
7265 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7267 MovDir[x][y] = new_move_dir;
7268 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7277 if (can_clone) // cloning and moving successful
7280 // cannot clone -- try to move towards player
7282 start_pos = check_pos[MovDir[x][y] & 0x0f];
7283 check_order = (RND(2) ? -1 : +1);
7285 for (i = 0; i < 3; i++)
7287 // first check start_pos, then previous/next or (next/previous) pos
7288 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7289 int pos = (pos_raw + 8) % 8;
7290 int newx = x + check_xy[pos].dx;
7291 int newy = y + check_xy[pos].dy;
7292 int new_move_dir = check_xy[pos].dir;
7294 if (IS_PLAYER(newx, newy))
7297 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7299 MovDir[x][y] = new_move_dir;
7300 MovDelay[x][y] = level.android_move_time * 8 + 1;
7307 else if (move_pattern == MV_TURNING_LEFT ||
7308 move_pattern == MV_TURNING_RIGHT ||
7309 move_pattern == MV_TURNING_LEFT_RIGHT ||
7310 move_pattern == MV_TURNING_RIGHT_LEFT ||
7311 move_pattern == MV_TURNING_RANDOM ||
7312 move_pattern == MV_ALL_DIRECTIONS)
7314 boolean can_turn_left =
7315 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7316 boolean can_turn_right =
7317 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7319 if (element_info[element].move_stepsize == 0) // "not moving"
7322 if (move_pattern == MV_TURNING_LEFT)
7323 MovDir[x][y] = left_dir;
7324 else if (move_pattern == MV_TURNING_RIGHT)
7325 MovDir[x][y] = right_dir;
7326 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7327 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7328 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7329 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7330 else if (move_pattern == MV_TURNING_RANDOM)
7331 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7332 can_turn_right && !can_turn_left ? right_dir :
7333 RND(2) ? left_dir : right_dir);
7334 else if (can_turn_left && can_turn_right)
7335 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7336 else if (can_turn_left)
7337 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7338 else if (can_turn_right)
7339 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7341 MovDir[x][y] = back_dir;
7343 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7345 else if (move_pattern == MV_HORIZONTAL ||
7346 move_pattern == MV_VERTICAL)
7348 if (move_pattern & old_move_dir)
7349 MovDir[x][y] = back_dir;
7350 else if (move_pattern == MV_HORIZONTAL)
7351 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7352 else if (move_pattern == MV_VERTICAL)
7353 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7355 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7357 else if (move_pattern & MV_ANY_DIRECTION)
7359 MovDir[x][y] = move_pattern;
7360 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7362 else if (move_pattern & MV_WIND_DIRECTION)
7364 MovDir[x][y] = game.wind_direction;
7365 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7367 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7369 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7370 MovDir[x][y] = left_dir;
7371 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7372 MovDir[x][y] = right_dir;
7374 if (MovDir[x][y] != old_move_dir)
7375 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7377 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7379 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7380 MovDir[x][y] = right_dir;
7381 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7382 MovDir[x][y] = left_dir;
7384 if (MovDir[x][y] != old_move_dir)
7385 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7387 else if (move_pattern == MV_TOWARDS_PLAYER ||
7388 move_pattern == MV_AWAY_FROM_PLAYER)
7390 int attr_x = -1, attr_y = -1;
7392 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7394 if (game.all_players_gone)
7396 attr_x = game.exit_x;
7397 attr_y = game.exit_y;
7403 for (i = 0; i < MAX_PLAYERS; i++)
7405 struct PlayerInfo *player = &stored_player[i];
7406 int jx = player->jx, jy = player->jy;
7408 if (!player->active)
7412 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7420 MovDir[x][y] = MV_NONE;
7422 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7423 else if (attr_x > x)
7424 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7426 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7427 else if (attr_y > y)
7428 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7430 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7432 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7434 boolean first_horiz = RND(2);
7435 int new_move_dir = MovDir[x][y];
7437 if (element_info[element].move_stepsize == 0) // "not moving"
7439 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7440 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7446 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7447 Moving2Blocked(x, y, &newx, &newy);
7449 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7453 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7454 Moving2Blocked(x, y, &newx, &newy);
7456 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7459 MovDir[x][y] = old_move_dir;
7462 else if (move_pattern == MV_WHEN_PUSHED ||
7463 move_pattern == MV_WHEN_DROPPED)
7465 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7466 MovDir[x][y] = MV_NONE;
7470 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7472 static int test_xy[7][2] =
7482 static int test_dir[7] =
7492 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7493 int move_preference = -1000000; // start with very low preference
7494 int new_move_dir = MV_NONE;
7495 int start_test = RND(4);
7498 for (i = 0; i < NUM_DIRECTIONS; i++)
7500 int move_dir = test_dir[start_test + i];
7501 int move_dir_preference;
7503 xx = x + test_xy[start_test + i][0];
7504 yy = y + test_xy[start_test + i][1];
7506 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7507 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7509 new_move_dir = move_dir;
7514 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7517 move_dir_preference = -1 * RunnerVisit[xx][yy];
7518 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7519 move_dir_preference = PlayerVisit[xx][yy];
7521 if (move_dir_preference > move_preference)
7523 // prefer field that has not been visited for the longest time
7524 move_preference = move_dir_preference;
7525 new_move_dir = move_dir;
7527 else if (move_dir_preference == move_preference &&
7528 move_dir == old_move_dir)
7530 // prefer last direction when all directions are preferred equally
7531 move_preference = move_dir_preference;
7532 new_move_dir = move_dir;
7536 MovDir[x][y] = new_move_dir;
7537 if (old_move_dir != new_move_dir)
7538 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7542 static void TurnRound(int x, int y)
7544 int direction = MovDir[x][y];
7548 GfxDir[x][y] = MovDir[x][y];
7550 if (direction != MovDir[x][y])
7554 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7556 ResetGfxFrame(x, y);
7559 static boolean JustBeingPushed(int x, int y)
7563 for (i = 0; i < MAX_PLAYERS; i++)
7565 struct PlayerInfo *player = &stored_player[i];
7567 if (player->active && player->is_pushing && player->MovPos)
7569 int next_jx = player->jx + (player->jx - player->last_jx);
7570 int next_jy = player->jy + (player->jy - player->last_jy);
7572 if (x == next_jx && y == next_jy)
7580 static void StartMoving(int x, int y)
7582 boolean started_moving = FALSE; // some elements can fall _and_ move
7583 int element = Tile[x][y];
7588 if (MovDelay[x][y] == 0)
7589 GfxAction[x][y] = ACTION_DEFAULT;
7591 if (CAN_FALL(element) && y < lev_fieldy - 1)
7593 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7594 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7595 if (JustBeingPushed(x, y))
7598 if (element == EL_QUICKSAND_FULL)
7600 if (IS_FREE(x, y + 1))
7602 InitMovingField(x, y, MV_DOWN);
7603 started_moving = TRUE;
7605 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7606 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7607 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7608 Store[x][y] = EL_ROCK;
7610 Store[x][y] = EL_ROCK;
7613 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7615 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7617 if (!MovDelay[x][y])
7619 MovDelay[x][y] = TILEY + 1;
7621 ResetGfxAnimation(x, y);
7622 ResetGfxAnimation(x, y + 1);
7627 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7628 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7635 Tile[x][y] = EL_QUICKSAND_EMPTY;
7636 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7637 Store[x][y + 1] = Store[x][y];
7640 PlayLevelSoundAction(x, y, ACTION_FILLING);
7642 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7644 if (!MovDelay[x][y])
7646 MovDelay[x][y] = TILEY + 1;
7648 ResetGfxAnimation(x, y);
7649 ResetGfxAnimation(x, y + 1);
7654 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7655 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7662 Tile[x][y] = EL_QUICKSAND_EMPTY;
7663 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7664 Store[x][y + 1] = Store[x][y];
7667 PlayLevelSoundAction(x, y, ACTION_FILLING);
7670 else if (element == EL_QUICKSAND_FAST_FULL)
7672 if (IS_FREE(x, y + 1))
7674 InitMovingField(x, y, MV_DOWN);
7675 started_moving = TRUE;
7677 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7678 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7679 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7680 Store[x][y] = EL_ROCK;
7682 Store[x][y] = EL_ROCK;
7685 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7687 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7689 if (!MovDelay[x][y])
7691 MovDelay[x][y] = TILEY + 1;
7693 ResetGfxAnimation(x, y);
7694 ResetGfxAnimation(x, y + 1);
7699 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7700 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7707 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7708 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7709 Store[x][y + 1] = Store[x][y];
7712 PlayLevelSoundAction(x, y, ACTION_FILLING);
7714 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7716 if (!MovDelay[x][y])
7718 MovDelay[x][y] = TILEY + 1;
7720 ResetGfxAnimation(x, y);
7721 ResetGfxAnimation(x, y + 1);
7726 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7727 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7734 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7735 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7736 Store[x][y + 1] = Store[x][y];
7739 PlayLevelSoundAction(x, y, ACTION_FILLING);
7742 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7743 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7745 InitMovingField(x, y, MV_DOWN);
7746 started_moving = TRUE;
7748 Tile[x][y] = EL_QUICKSAND_FILLING;
7749 Store[x][y] = element;
7751 PlayLevelSoundAction(x, y, ACTION_FILLING);
7753 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7754 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7756 InitMovingField(x, y, MV_DOWN);
7757 started_moving = TRUE;
7759 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7760 Store[x][y] = element;
7762 PlayLevelSoundAction(x, y, ACTION_FILLING);
7764 else if (element == EL_MAGIC_WALL_FULL)
7766 if (IS_FREE(x, y + 1))
7768 InitMovingField(x, y, MV_DOWN);
7769 started_moving = TRUE;
7771 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7772 Store[x][y] = EL_CHANGED(Store[x][y]);
7774 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7776 if (!MovDelay[x][y])
7777 MovDelay[x][y] = TILEY / 4 + 1;
7786 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7787 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7788 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7792 else if (element == EL_BD_MAGIC_WALL_FULL)
7794 if (IS_FREE(x, y + 1))
7796 InitMovingField(x, y, MV_DOWN);
7797 started_moving = TRUE;
7799 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7800 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7802 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7804 if (!MovDelay[x][y])
7805 MovDelay[x][y] = TILEY / 4 + 1;
7814 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7815 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7816 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7820 else if (element == EL_DC_MAGIC_WALL_FULL)
7822 if (IS_FREE(x, y + 1))
7824 InitMovingField(x, y, MV_DOWN);
7825 started_moving = TRUE;
7827 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7828 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7830 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7832 if (!MovDelay[x][y])
7833 MovDelay[x][y] = TILEY / 4 + 1;
7842 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7843 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7844 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7848 else if ((CAN_PASS_MAGIC_WALL(element) &&
7849 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7850 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7851 (CAN_PASS_DC_MAGIC_WALL(element) &&
7852 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7855 InitMovingField(x, y, MV_DOWN);
7856 started_moving = TRUE;
7859 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7860 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7861 EL_DC_MAGIC_WALL_FILLING);
7862 Store[x][y] = element;
7864 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7866 SplashAcid(x, y + 1);
7868 InitMovingField(x, y, MV_DOWN);
7869 started_moving = TRUE;
7871 Store[x][y] = EL_ACID;
7874 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7875 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7876 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7877 CAN_FALL(element) && WasJustFalling[x][y] &&
7878 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7880 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7881 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7882 (Tile[x][y + 1] == EL_BLOCKED)))
7884 /* this is needed for a special case not covered by calling "Impact()"
7885 from "ContinueMoving()": if an element moves to a tile directly below
7886 another element which was just falling on that tile (which was empty
7887 in the previous frame), the falling element above would just stop
7888 instead of smashing the element below (in previous version, the above
7889 element was just checked for "moving" instead of "falling", resulting
7890 in incorrect smashes caused by horizontal movement of the above
7891 element; also, the case of the player being the element to smash was
7892 simply not covered here... :-/ ) */
7894 CheckCollision[x][y] = 0;
7895 CheckImpact[x][y] = 0;
7899 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7901 if (MovDir[x][y] == MV_NONE)
7903 InitMovingField(x, y, MV_DOWN);
7904 started_moving = TRUE;
7907 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7909 if (WasJustFalling[x][y]) // prevent animation from being restarted
7910 MovDir[x][y] = MV_DOWN;
7912 InitMovingField(x, y, MV_DOWN);
7913 started_moving = TRUE;
7915 else if (element == EL_AMOEBA_DROP)
7917 Tile[x][y] = EL_AMOEBA_GROWING;
7918 Store[x][y] = EL_AMOEBA_WET;
7920 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7921 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7922 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7923 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7925 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7926 (IS_FREE(x - 1, y + 1) ||
7927 Tile[x - 1][y + 1] == EL_ACID));
7928 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7929 (IS_FREE(x + 1, y + 1) ||
7930 Tile[x + 1][y + 1] == EL_ACID));
7931 boolean can_fall_any = (can_fall_left || can_fall_right);
7932 boolean can_fall_both = (can_fall_left && can_fall_right);
7933 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7935 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7937 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7938 can_fall_right = FALSE;
7939 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7940 can_fall_left = FALSE;
7941 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7942 can_fall_right = FALSE;
7943 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7944 can_fall_left = FALSE;
7946 can_fall_any = (can_fall_left || can_fall_right);
7947 can_fall_both = FALSE;
7952 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7953 can_fall_right = FALSE; // slip down on left side
7955 can_fall_left = !(can_fall_right = RND(2));
7957 can_fall_both = FALSE;
7962 // if not determined otherwise, prefer left side for slipping down
7963 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7964 started_moving = TRUE;
7967 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
7969 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7970 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7971 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
7972 int belt_dir = game.belt_dir[belt_nr];
7974 if ((belt_dir == MV_LEFT && left_is_free) ||
7975 (belt_dir == MV_RIGHT && right_is_free))
7977 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7979 InitMovingField(x, y, belt_dir);
7980 started_moving = TRUE;
7982 Pushed[x][y] = TRUE;
7983 Pushed[nextx][y] = TRUE;
7985 GfxAction[x][y] = ACTION_DEFAULT;
7989 MovDir[x][y] = 0; // if element was moving, stop it
7994 // not "else if" because of elements that can fall and move (EL_SPRING)
7995 if (CAN_MOVE(element) && !started_moving)
7997 int move_pattern = element_info[element].move_pattern;
8000 Moving2Blocked(x, y, &newx, &newy);
8002 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8005 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8006 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8008 WasJustMoving[x][y] = 0;
8009 CheckCollision[x][y] = 0;
8011 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8013 if (Tile[x][y] != element) // element has changed
8017 if (!MovDelay[x][y]) // start new movement phase
8019 // all objects that can change their move direction after each step
8020 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8022 if (element != EL_YAMYAM &&
8023 element != EL_DARK_YAMYAM &&
8024 element != EL_PACMAN &&
8025 !(move_pattern & MV_ANY_DIRECTION) &&
8026 move_pattern != MV_TURNING_LEFT &&
8027 move_pattern != MV_TURNING_RIGHT &&
8028 move_pattern != MV_TURNING_LEFT_RIGHT &&
8029 move_pattern != MV_TURNING_RIGHT_LEFT &&
8030 move_pattern != MV_TURNING_RANDOM)
8034 if (MovDelay[x][y] && (element == EL_BUG ||
8035 element == EL_SPACESHIP ||
8036 element == EL_SP_SNIKSNAK ||
8037 element == EL_SP_ELECTRON ||
8038 element == EL_MOLE))
8039 TEST_DrawLevelField(x, y);
8043 if (MovDelay[x][y]) // wait some time before next movement
8047 if (element == EL_ROBOT ||
8048 element == EL_YAMYAM ||
8049 element == EL_DARK_YAMYAM)
8051 DrawLevelElementAnimationIfNeeded(x, y, element);
8052 PlayLevelSoundAction(x, y, ACTION_WAITING);
8054 else if (element == EL_SP_ELECTRON)
8055 DrawLevelElementAnimationIfNeeded(x, y, element);
8056 else if (element == EL_DRAGON)
8059 int dir = MovDir[x][y];
8060 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8061 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8062 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8063 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8064 dir == MV_UP ? IMG_FLAMES_1_UP :
8065 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8066 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8068 GfxAction[x][y] = ACTION_ATTACKING;
8070 if (IS_PLAYER(x, y))
8071 DrawPlayerField(x, y);
8073 TEST_DrawLevelField(x, y);
8075 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8077 for (i = 1; i <= 3; i++)
8079 int xx = x + i * dx;
8080 int yy = y + i * dy;
8081 int sx = SCREENX(xx);
8082 int sy = SCREENY(yy);
8083 int flame_graphic = graphic + (i - 1);
8085 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8090 int flamed = MovingOrBlocked2Element(xx, yy);
8092 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8095 RemoveMovingField(xx, yy);
8097 ChangeDelay[xx][yy] = 0;
8099 Tile[xx][yy] = EL_FLAMES;
8101 if (IN_SCR_FIELD(sx, sy))
8103 TEST_DrawLevelFieldCrumbled(xx, yy);
8104 DrawGraphic(sx, sy, flame_graphic, frame);
8109 if (Tile[xx][yy] == EL_FLAMES)
8110 Tile[xx][yy] = EL_EMPTY;
8111 TEST_DrawLevelField(xx, yy);
8116 if (MovDelay[x][y]) // element still has to wait some time
8118 PlayLevelSoundAction(x, y, ACTION_WAITING);
8124 // now make next step
8126 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8128 if (DONT_COLLIDE_WITH(element) &&
8129 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8130 !PLAYER_ENEMY_PROTECTED(newx, newy))
8132 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8137 else if (CAN_MOVE_INTO_ACID(element) &&
8138 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8139 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8140 (MovDir[x][y] == MV_DOWN ||
8141 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8143 SplashAcid(newx, newy);
8144 Store[x][y] = EL_ACID;
8146 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8148 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8149 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8150 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8151 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8154 TEST_DrawLevelField(x, y);
8156 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8157 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8158 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8160 game.friends_still_needed--;
8161 if (!game.friends_still_needed &&
8163 game.all_players_gone)
8168 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8170 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8171 TEST_DrawLevelField(newx, newy);
8173 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8175 else if (!IS_FREE(newx, newy))
8177 GfxAction[x][y] = ACTION_WAITING;
8179 if (IS_PLAYER(x, y))
8180 DrawPlayerField(x, y);
8182 TEST_DrawLevelField(x, y);
8187 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8189 if (IS_FOOD_PIG(Tile[newx][newy]))
8191 if (IS_MOVING(newx, newy))
8192 RemoveMovingField(newx, newy);
8195 Tile[newx][newy] = EL_EMPTY;
8196 TEST_DrawLevelField(newx, newy);
8199 PlayLevelSound(x, y, SND_PIG_DIGGING);
8201 else if (!IS_FREE(newx, newy))
8203 if (IS_PLAYER(x, y))
8204 DrawPlayerField(x, y);
8206 TEST_DrawLevelField(x, y);
8211 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8213 if (Store[x][y] != EL_EMPTY)
8215 boolean can_clone = FALSE;
8218 // check if element to clone is still there
8219 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8221 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8229 // cannot clone or target field not free anymore -- do not clone
8230 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8231 Store[x][y] = EL_EMPTY;
8234 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8236 if (IS_MV_DIAGONAL(MovDir[x][y]))
8238 int diagonal_move_dir = MovDir[x][y];
8239 int stored = Store[x][y];
8240 int change_delay = 8;
8243 // android is moving diagonally
8245 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8247 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8248 GfxElement[x][y] = EL_EMC_ANDROID;
8249 GfxAction[x][y] = ACTION_SHRINKING;
8250 GfxDir[x][y] = diagonal_move_dir;
8251 ChangeDelay[x][y] = change_delay;
8253 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8256 DrawLevelGraphicAnimation(x, y, graphic);
8257 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8259 if (Tile[newx][newy] == EL_ACID)
8261 SplashAcid(newx, newy);
8266 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8268 Store[newx][newy] = EL_EMC_ANDROID;
8269 GfxElement[newx][newy] = EL_EMC_ANDROID;
8270 GfxAction[newx][newy] = ACTION_GROWING;
8271 GfxDir[newx][newy] = diagonal_move_dir;
8272 ChangeDelay[newx][newy] = change_delay;
8274 graphic = el_act_dir2img(GfxElement[newx][newy],
8275 GfxAction[newx][newy], GfxDir[newx][newy]);
8277 DrawLevelGraphicAnimation(newx, newy, graphic);
8278 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8284 Tile[newx][newy] = EL_EMPTY;
8285 TEST_DrawLevelField(newx, newy);
8287 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8290 else if (!IS_FREE(newx, newy))
8295 else if (IS_CUSTOM_ELEMENT(element) &&
8296 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8298 if (!DigFieldByCE(newx, newy, element))
8301 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8303 RunnerVisit[x][y] = FrameCounter;
8304 PlayerVisit[x][y] /= 8; // expire player visit path
8307 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8309 if (!IS_FREE(newx, newy))
8311 if (IS_PLAYER(x, y))
8312 DrawPlayerField(x, y);
8314 TEST_DrawLevelField(x, y);
8320 boolean wanna_flame = !RND(10);
8321 int dx = newx - x, dy = newy - y;
8322 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8323 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8324 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8325 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8326 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8327 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8330 IS_CLASSIC_ENEMY(element1) ||
8331 IS_CLASSIC_ENEMY(element2)) &&
8332 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8333 element1 != EL_FLAMES && element2 != EL_FLAMES)
8335 ResetGfxAnimation(x, y);
8336 GfxAction[x][y] = ACTION_ATTACKING;
8338 if (IS_PLAYER(x, y))
8339 DrawPlayerField(x, y);
8341 TEST_DrawLevelField(x, y);
8343 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8345 MovDelay[x][y] = 50;
8347 Tile[newx][newy] = EL_FLAMES;
8348 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8349 Tile[newx1][newy1] = EL_FLAMES;
8350 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8351 Tile[newx2][newy2] = EL_FLAMES;
8357 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8358 Tile[newx][newy] == EL_DIAMOND)
8360 if (IS_MOVING(newx, newy))
8361 RemoveMovingField(newx, newy);
8364 Tile[newx][newy] = EL_EMPTY;
8365 TEST_DrawLevelField(newx, newy);
8368 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8370 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8371 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8373 if (AmoebaNr[newx][newy])
8375 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8376 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8377 Tile[newx][newy] == EL_BD_AMOEBA)
8378 AmoebaCnt[AmoebaNr[newx][newy]]--;
8381 if (IS_MOVING(newx, newy))
8383 RemoveMovingField(newx, newy);
8387 Tile[newx][newy] = EL_EMPTY;
8388 TEST_DrawLevelField(newx, newy);
8391 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8393 else if ((element == EL_PACMAN || element == EL_MOLE)
8394 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8396 if (AmoebaNr[newx][newy])
8398 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8399 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8400 Tile[newx][newy] == EL_BD_AMOEBA)
8401 AmoebaCnt[AmoebaNr[newx][newy]]--;
8404 if (element == EL_MOLE)
8406 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8407 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8409 ResetGfxAnimation(x, y);
8410 GfxAction[x][y] = ACTION_DIGGING;
8411 TEST_DrawLevelField(x, y);
8413 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8415 return; // wait for shrinking amoeba
8417 else // element == EL_PACMAN
8419 Tile[newx][newy] = EL_EMPTY;
8420 TEST_DrawLevelField(newx, newy);
8421 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8424 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8425 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8426 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8428 // wait for shrinking amoeba to completely disappear
8431 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8433 // object was running against a wall
8437 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8438 DrawLevelElementAnimation(x, y, element);
8440 if (DONT_TOUCH(element))
8441 TestIfBadThingTouchesPlayer(x, y);
8446 InitMovingField(x, y, MovDir[x][y]);
8448 PlayLevelSoundAction(x, y, ACTION_MOVING);
8452 ContinueMoving(x, y);
8455 void ContinueMoving(int x, int y)
8457 int element = Tile[x][y];
8458 struct ElementInfo *ei = &element_info[element];
8459 int direction = MovDir[x][y];
8460 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8461 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8462 int newx = x + dx, newy = y + dy;
8463 int stored = Store[x][y];
8464 int stored_new = Store[newx][newy];
8465 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8466 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8467 boolean last_line = (newy == lev_fieldy - 1);
8469 MovPos[x][y] += getElementMoveStepsize(x, y);
8471 if (pushed_by_player) // special case: moving object pushed by player
8472 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8474 if (ABS(MovPos[x][y]) < TILEX)
8476 TEST_DrawLevelField(x, y);
8478 return; // element is still moving
8481 // element reached destination field
8483 Tile[x][y] = EL_EMPTY;
8484 Tile[newx][newy] = element;
8485 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8487 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8489 element = Tile[newx][newy] = EL_ACID;
8491 else if (element == EL_MOLE)
8493 Tile[x][y] = EL_SAND;
8495 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8497 else if (element == EL_QUICKSAND_FILLING)
8499 element = Tile[newx][newy] = get_next_element(element);
8500 Store[newx][newy] = Store[x][y];
8502 else if (element == EL_QUICKSAND_EMPTYING)
8504 Tile[x][y] = get_next_element(element);
8505 element = Tile[newx][newy] = Store[x][y];
8507 else if (element == EL_QUICKSAND_FAST_FILLING)
8509 element = Tile[newx][newy] = get_next_element(element);
8510 Store[newx][newy] = Store[x][y];
8512 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8514 Tile[x][y] = get_next_element(element);
8515 element = Tile[newx][newy] = Store[x][y];
8517 else if (element == EL_MAGIC_WALL_FILLING)
8519 element = Tile[newx][newy] = get_next_element(element);
8520 if (!game.magic_wall_active)
8521 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8522 Store[newx][newy] = Store[x][y];
8524 else if (element == EL_MAGIC_WALL_EMPTYING)
8526 Tile[x][y] = get_next_element(element);
8527 if (!game.magic_wall_active)
8528 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8529 element = Tile[newx][newy] = Store[x][y];
8531 InitField(newx, newy, FALSE);
8533 else if (element == EL_BD_MAGIC_WALL_FILLING)
8535 element = Tile[newx][newy] = get_next_element(element);
8536 if (!game.magic_wall_active)
8537 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8538 Store[newx][newy] = Store[x][y];
8540 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8542 Tile[x][y] = get_next_element(element);
8543 if (!game.magic_wall_active)
8544 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8545 element = Tile[newx][newy] = Store[x][y];
8547 InitField(newx, newy, FALSE);
8549 else if (element == EL_DC_MAGIC_WALL_FILLING)
8551 element = Tile[newx][newy] = get_next_element(element);
8552 if (!game.magic_wall_active)
8553 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8554 Store[newx][newy] = Store[x][y];
8556 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8558 Tile[x][y] = get_next_element(element);
8559 if (!game.magic_wall_active)
8560 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8561 element = Tile[newx][newy] = Store[x][y];
8563 InitField(newx, newy, FALSE);
8565 else if (element == EL_AMOEBA_DROPPING)
8567 Tile[x][y] = get_next_element(element);
8568 element = Tile[newx][newy] = Store[x][y];
8570 else if (element == EL_SOKOBAN_OBJECT)
8573 Tile[x][y] = Back[x][y];
8575 if (Back[newx][newy])
8576 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8578 Back[x][y] = Back[newx][newy] = 0;
8581 Store[x][y] = EL_EMPTY;
8586 MovDelay[newx][newy] = 0;
8588 if (CAN_CHANGE_OR_HAS_ACTION(element))
8590 // copy element change control values to new field
8591 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8592 ChangePage[newx][newy] = ChangePage[x][y];
8593 ChangeCount[newx][newy] = ChangeCount[x][y];
8594 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8597 CustomValue[newx][newy] = CustomValue[x][y];
8599 ChangeDelay[x][y] = 0;
8600 ChangePage[x][y] = -1;
8601 ChangeCount[x][y] = 0;
8602 ChangeEvent[x][y] = -1;
8604 CustomValue[x][y] = 0;
8606 // copy animation control values to new field
8607 GfxFrame[newx][newy] = GfxFrame[x][y];
8608 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8609 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8610 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8612 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8614 // some elements can leave other elements behind after moving
8615 if (ei->move_leave_element != EL_EMPTY &&
8616 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8617 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8619 int move_leave_element = ei->move_leave_element;
8621 // this makes it possible to leave the removed element again
8622 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8623 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8625 Tile[x][y] = move_leave_element;
8627 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8628 MovDir[x][y] = direction;
8630 InitField(x, y, FALSE);
8632 if (GFX_CRUMBLED(Tile[x][y]))
8633 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8635 if (ELEM_IS_PLAYER(move_leave_element))
8636 RelocatePlayer(x, y, move_leave_element);
8639 // do this after checking for left-behind element
8640 ResetGfxAnimation(x, y); // reset animation values for old field
8642 if (!CAN_MOVE(element) ||
8643 (CAN_FALL(element) && direction == MV_DOWN &&
8644 (element == EL_SPRING ||
8645 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8646 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8647 GfxDir[x][y] = MovDir[newx][newy] = 0;
8649 TEST_DrawLevelField(x, y);
8650 TEST_DrawLevelField(newx, newy);
8652 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8654 // prevent pushed element from moving on in pushed direction
8655 if (pushed_by_player && CAN_MOVE(element) &&
8656 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8657 !(element_info[element].move_pattern & direction))
8658 TurnRound(newx, newy);
8660 // prevent elements on conveyor belt from moving on in last direction
8661 if (pushed_by_conveyor && CAN_FALL(element) &&
8662 direction & MV_HORIZONTAL)
8663 MovDir[newx][newy] = 0;
8665 if (!pushed_by_player)
8667 int nextx = newx + dx, nexty = newy + dy;
8668 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8670 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8672 if (CAN_FALL(element) && direction == MV_DOWN)
8673 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8675 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8676 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8678 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8679 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8682 if (DONT_TOUCH(element)) // object may be nasty to player or others
8684 TestIfBadThingTouchesPlayer(newx, newy);
8685 TestIfBadThingTouchesFriend(newx, newy);
8687 if (!IS_CUSTOM_ELEMENT(element))
8688 TestIfBadThingTouchesOtherBadThing(newx, newy);
8690 else if (element == EL_PENGUIN)
8691 TestIfFriendTouchesBadThing(newx, newy);
8693 if (DONT_GET_HIT_BY(element))
8695 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8698 // give the player one last chance (one more frame) to move away
8699 if (CAN_FALL(element) && direction == MV_DOWN &&
8700 (last_line || (!IS_FREE(x, newy + 1) &&
8701 (!IS_PLAYER(x, newy + 1) ||
8702 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8705 if (pushed_by_player && !game.use_change_when_pushing_bug)
8707 int push_side = MV_DIR_OPPOSITE(direction);
8708 struct PlayerInfo *player = PLAYERINFO(x, y);
8710 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8711 player->index_bit, push_side);
8712 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8713 player->index_bit, push_side);
8716 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8717 MovDelay[newx][newy] = 1;
8719 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8721 TestIfElementTouchesCustomElement(x, y); // empty or new element
8722 TestIfElementHitsCustomElement(newx, newy, direction);
8723 TestIfPlayerTouchesCustomElement(newx, newy);
8724 TestIfElementTouchesCustomElement(newx, newy);
8726 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8727 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8728 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8729 MV_DIR_OPPOSITE(direction));
8732 int AmoebaNeighbourNr(int ax, int ay)
8735 int element = Tile[ax][ay];
8737 static int xy[4][2] =
8745 for (i = 0; i < NUM_DIRECTIONS; i++)
8747 int x = ax + xy[i][0];
8748 int y = ay + xy[i][1];
8750 if (!IN_LEV_FIELD(x, y))
8753 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8754 group_nr = AmoebaNr[x][y];
8760 static void AmoebaMerge(int ax, int ay)
8762 int i, x, y, xx, yy;
8763 int new_group_nr = AmoebaNr[ax][ay];
8764 static int xy[4][2] =
8772 if (new_group_nr == 0)
8775 for (i = 0; i < NUM_DIRECTIONS; i++)
8780 if (!IN_LEV_FIELD(x, y))
8783 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8784 Tile[x][y] == EL_BD_AMOEBA ||
8785 Tile[x][y] == EL_AMOEBA_DEAD) &&
8786 AmoebaNr[x][y] != new_group_nr)
8788 int old_group_nr = AmoebaNr[x][y];
8790 if (old_group_nr == 0)
8793 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8794 AmoebaCnt[old_group_nr] = 0;
8795 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8796 AmoebaCnt2[old_group_nr] = 0;
8798 SCAN_PLAYFIELD(xx, yy)
8800 if (AmoebaNr[xx][yy] == old_group_nr)
8801 AmoebaNr[xx][yy] = new_group_nr;
8807 void AmoebaToDiamond(int ax, int ay)
8811 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8813 int group_nr = AmoebaNr[ax][ay];
8818 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8819 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8825 SCAN_PLAYFIELD(x, y)
8827 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8830 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8834 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8835 SND_AMOEBA_TURNING_TO_GEM :
8836 SND_AMOEBA_TURNING_TO_ROCK));
8841 static int xy[4][2] =
8849 for (i = 0; i < NUM_DIRECTIONS; i++)
8854 if (!IN_LEV_FIELD(x, y))
8857 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8859 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8860 SND_AMOEBA_TURNING_TO_GEM :
8861 SND_AMOEBA_TURNING_TO_ROCK));
8868 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8871 int group_nr = AmoebaNr[ax][ay];
8872 boolean done = FALSE;
8877 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8878 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8884 SCAN_PLAYFIELD(x, y)
8886 if (AmoebaNr[x][y] == group_nr &&
8887 (Tile[x][y] == EL_AMOEBA_DEAD ||
8888 Tile[x][y] == EL_BD_AMOEBA ||
8889 Tile[x][y] == EL_AMOEBA_GROWING))
8892 Tile[x][y] = new_element;
8893 InitField(x, y, FALSE);
8894 TEST_DrawLevelField(x, y);
8900 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8901 SND_BD_AMOEBA_TURNING_TO_ROCK :
8902 SND_BD_AMOEBA_TURNING_TO_GEM));
8905 static void AmoebaGrowing(int x, int y)
8907 static unsigned int sound_delay = 0;
8908 static unsigned int sound_delay_value = 0;
8910 if (!MovDelay[x][y]) // start new growing cycle
8914 if (DelayReached(&sound_delay, sound_delay_value))
8916 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8917 sound_delay_value = 30;
8921 if (MovDelay[x][y]) // wait some time before growing bigger
8924 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8926 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8927 6 - MovDelay[x][y]);
8929 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8932 if (!MovDelay[x][y])
8934 Tile[x][y] = Store[x][y];
8936 TEST_DrawLevelField(x, y);
8941 static void AmoebaShrinking(int x, int y)
8943 static unsigned int sound_delay = 0;
8944 static unsigned int sound_delay_value = 0;
8946 if (!MovDelay[x][y]) // start new shrinking cycle
8950 if (DelayReached(&sound_delay, sound_delay_value))
8951 sound_delay_value = 30;
8954 if (MovDelay[x][y]) // wait some time before shrinking
8957 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8959 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8960 6 - MovDelay[x][y]);
8962 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8965 if (!MovDelay[x][y])
8967 Tile[x][y] = EL_EMPTY;
8968 TEST_DrawLevelField(x, y);
8970 // don't let mole enter this field in this cycle;
8971 // (give priority to objects falling to this field from above)
8977 static void AmoebaReproduce(int ax, int ay)
8980 int element = Tile[ax][ay];
8981 int graphic = el2img(element);
8982 int newax = ax, neway = ay;
8983 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8984 static int xy[4][2] =
8992 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8994 Tile[ax][ay] = EL_AMOEBA_DEAD;
8995 TEST_DrawLevelField(ax, ay);
8999 if (IS_ANIMATED(graphic))
9000 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9002 if (!MovDelay[ax][ay]) // start making new amoeba field
9003 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9005 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9008 if (MovDelay[ax][ay])
9012 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9015 int x = ax + xy[start][0];
9016 int y = ay + xy[start][1];
9018 if (!IN_LEV_FIELD(x, y))
9021 if (IS_FREE(x, y) ||
9022 CAN_GROW_INTO(Tile[x][y]) ||
9023 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9024 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9030 if (newax == ax && neway == ay)
9033 else // normal or "filled" (BD style) amoeba
9036 boolean waiting_for_player = FALSE;
9038 for (i = 0; i < NUM_DIRECTIONS; i++)
9040 int j = (start + i) % 4;
9041 int x = ax + xy[j][0];
9042 int y = ay + xy[j][1];
9044 if (!IN_LEV_FIELD(x, y))
9047 if (IS_FREE(x, y) ||
9048 CAN_GROW_INTO(Tile[x][y]) ||
9049 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9050 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9056 else if (IS_PLAYER(x, y))
9057 waiting_for_player = TRUE;
9060 if (newax == ax && neway == ay) // amoeba cannot grow
9062 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9064 Tile[ax][ay] = EL_AMOEBA_DEAD;
9065 TEST_DrawLevelField(ax, ay);
9066 AmoebaCnt[AmoebaNr[ax][ay]]--;
9068 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9070 if (element == EL_AMOEBA_FULL)
9071 AmoebaToDiamond(ax, ay);
9072 else if (element == EL_BD_AMOEBA)
9073 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9078 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9080 // amoeba gets larger by growing in some direction
9082 int new_group_nr = AmoebaNr[ax][ay];
9085 if (new_group_nr == 0)
9087 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9089 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9095 AmoebaNr[newax][neway] = new_group_nr;
9096 AmoebaCnt[new_group_nr]++;
9097 AmoebaCnt2[new_group_nr]++;
9099 // if amoeba touches other amoeba(s) after growing, unify them
9100 AmoebaMerge(newax, neway);
9102 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9104 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9110 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9111 (neway == lev_fieldy - 1 && newax != ax))
9113 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9114 Store[newax][neway] = element;
9116 else if (neway == ay || element == EL_EMC_DRIPPER)
9118 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9120 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9124 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9125 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9126 Store[ax][ay] = EL_AMOEBA_DROP;
9127 ContinueMoving(ax, ay);
9131 TEST_DrawLevelField(newax, neway);
9134 static void Life(int ax, int ay)
9138 int element = Tile[ax][ay];
9139 int graphic = el2img(element);
9140 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9142 boolean changed = FALSE;
9144 if (IS_ANIMATED(graphic))
9145 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9150 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9151 MovDelay[ax][ay] = life_time;
9153 if (MovDelay[ax][ay]) // wait some time before next cycle
9156 if (MovDelay[ax][ay])
9160 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9162 int xx = ax+x1, yy = ay+y1;
9163 int old_element = Tile[xx][yy];
9164 int num_neighbours = 0;
9166 if (!IN_LEV_FIELD(xx, yy))
9169 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9171 int x = xx+x2, y = yy+y2;
9173 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9176 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9177 boolean is_neighbour = FALSE;
9179 if (level.use_life_bugs)
9181 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9182 (IS_FREE(x, y) && Stop[x][y]));
9185 (Last[x][y] == element || is_player_cell);
9191 boolean is_free = FALSE;
9193 if (level.use_life_bugs)
9194 is_free = (IS_FREE(xx, yy));
9196 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9198 if (xx == ax && yy == ay) // field in the middle
9200 if (num_neighbours < life_parameter[0] ||
9201 num_neighbours > life_parameter[1])
9203 Tile[xx][yy] = EL_EMPTY;
9204 if (Tile[xx][yy] != old_element)
9205 TEST_DrawLevelField(xx, yy);
9206 Stop[xx][yy] = TRUE;
9210 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9211 { // free border field
9212 if (num_neighbours >= life_parameter[2] &&
9213 num_neighbours <= life_parameter[3])
9215 Tile[xx][yy] = element;
9216 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9217 if (Tile[xx][yy] != old_element)
9218 TEST_DrawLevelField(xx, yy);
9219 Stop[xx][yy] = TRUE;
9226 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9227 SND_GAME_OF_LIFE_GROWING);
9230 static void InitRobotWheel(int x, int y)
9232 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9235 static void RunRobotWheel(int x, int y)
9237 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9240 static void StopRobotWheel(int x, int y)
9242 if (game.robot_wheel_x == x &&
9243 game.robot_wheel_y == y)
9245 game.robot_wheel_x = -1;
9246 game.robot_wheel_y = -1;
9247 game.robot_wheel_active = FALSE;
9251 static void InitTimegateWheel(int x, int y)
9253 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9256 static void RunTimegateWheel(int x, int y)
9258 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9261 static void InitMagicBallDelay(int x, int y)
9263 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9266 static void ActivateMagicBall(int bx, int by)
9270 if (level.ball_random)
9272 int pos_border = RND(8); // select one of the eight border elements
9273 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9274 int xx = pos_content % 3;
9275 int yy = pos_content / 3;
9280 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9281 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9285 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9287 int xx = x - bx + 1;
9288 int yy = y - by + 1;
9290 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9291 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9295 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9298 static void CheckExit(int x, int y)
9300 if (game.gems_still_needed > 0 ||
9301 game.sokoban_fields_still_needed > 0 ||
9302 game.sokoban_objects_still_needed > 0 ||
9303 game.lights_still_needed > 0)
9305 int element = Tile[x][y];
9306 int graphic = el2img(element);
9308 if (IS_ANIMATED(graphic))
9309 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9314 // do not re-open exit door closed after last player
9315 if (game.all_players_gone)
9318 Tile[x][y] = EL_EXIT_OPENING;
9320 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9323 static void CheckExitEM(int x, int y)
9325 if (game.gems_still_needed > 0 ||
9326 game.sokoban_fields_still_needed > 0 ||
9327 game.sokoban_objects_still_needed > 0 ||
9328 game.lights_still_needed > 0)
9330 int element = Tile[x][y];
9331 int graphic = el2img(element);
9333 if (IS_ANIMATED(graphic))
9334 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9339 // do not re-open exit door closed after last player
9340 if (game.all_players_gone)
9343 Tile[x][y] = EL_EM_EXIT_OPENING;
9345 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9348 static void CheckExitSteel(int x, int y)
9350 if (game.gems_still_needed > 0 ||
9351 game.sokoban_fields_still_needed > 0 ||
9352 game.sokoban_objects_still_needed > 0 ||
9353 game.lights_still_needed > 0)
9355 int element = Tile[x][y];
9356 int graphic = el2img(element);
9358 if (IS_ANIMATED(graphic))
9359 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9364 // do not re-open exit door closed after last player
9365 if (game.all_players_gone)
9368 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9370 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9373 static void CheckExitSteelEM(int x, int y)
9375 if (game.gems_still_needed > 0 ||
9376 game.sokoban_fields_still_needed > 0 ||
9377 game.sokoban_objects_still_needed > 0 ||
9378 game.lights_still_needed > 0)
9380 int element = Tile[x][y];
9381 int graphic = el2img(element);
9383 if (IS_ANIMATED(graphic))
9384 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9389 // do not re-open exit door closed after last player
9390 if (game.all_players_gone)
9393 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9395 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9398 static void CheckExitSP(int x, int y)
9400 if (game.gems_still_needed > 0)
9402 int element = Tile[x][y];
9403 int graphic = el2img(element);
9405 if (IS_ANIMATED(graphic))
9406 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9411 // do not re-open exit door closed after last player
9412 if (game.all_players_gone)
9415 Tile[x][y] = EL_SP_EXIT_OPENING;
9417 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9420 static void CloseAllOpenTimegates(void)
9424 SCAN_PLAYFIELD(x, y)
9426 int element = Tile[x][y];
9428 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9430 Tile[x][y] = EL_TIMEGATE_CLOSING;
9432 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9437 static void DrawTwinkleOnField(int x, int y)
9439 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9442 if (Tile[x][y] == EL_BD_DIAMOND)
9445 if (MovDelay[x][y] == 0) // next animation frame
9446 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9448 if (MovDelay[x][y] != 0) // wait some time before next frame
9452 DrawLevelElementAnimation(x, y, Tile[x][y]);
9454 if (MovDelay[x][y] != 0)
9456 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9457 10 - MovDelay[x][y]);
9459 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9464 static void MauerWaechst(int x, int y)
9468 if (!MovDelay[x][y]) // next animation frame
9469 MovDelay[x][y] = 3 * delay;
9471 if (MovDelay[x][y]) // wait some time before next frame
9475 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9477 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9478 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9480 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9483 if (!MovDelay[x][y])
9485 if (MovDir[x][y] == MV_LEFT)
9487 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9488 TEST_DrawLevelField(x - 1, y);
9490 else if (MovDir[x][y] == MV_RIGHT)
9492 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9493 TEST_DrawLevelField(x + 1, y);
9495 else if (MovDir[x][y] == MV_UP)
9497 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9498 TEST_DrawLevelField(x, y - 1);
9502 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9503 TEST_DrawLevelField(x, y + 1);
9506 Tile[x][y] = Store[x][y];
9508 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9509 TEST_DrawLevelField(x, y);
9514 static void MauerAbleger(int ax, int ay)
9516 int element = Tile[ax][ay];
9517 int graphic = el2img(element);
9518 boolean oben_frei = FALSE, unten_frei = FALSE;
9519 boolean links_frei = FALSE, rechts_frei = FALSE;
9520 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9521 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9522 boolean new_wall = FALSE;
9524 if (IS_ANIMATED(graphic))
9525 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9527 if (!MovDelay[ax][ay]) // start building new wall
9528 MovDelay[ax][ay] = 6;
9530 if (MovDelay[ax][ay]) // wait some time before building new wall
9533 if (MovDelay[ax][ay])
9537 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9539 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9541 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9543 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9546 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9547 element == EL_EXPANDABLE_WALL_ANY)
9551 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9552 Store[ax][ay-1] = element;
9553 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9554 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9555 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9556 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9561 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9562 Store[ax][ay+1] = element;
9563 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9564 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9565 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9566 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9571 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9572 element == EL_EXPANDABLE_WALL_ANY ||
9573 element == EL_EXPANDABLE_WALL ||
9574 element == EL_BD_EXPANDABLE_WALL)
9578 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9579 Store[ax-1][ay] = element;
9580 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9581 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9582 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9583 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9589 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9590 Store[ax+1][ay] = element;
9591 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9592 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9593 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9594 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9599 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9600 TEST_DrawLevelField(ax, ay);
9602 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9604 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9605 unten_massiv = TRUE;
9606 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9607 links_massiv = TRUE;
9608 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9609 rechts_massiv = TRUE;
9611 if (((oben_massiv && unten_massiv) ||
9612 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9613 element == EL_EXPANDABLE_WALL) &&
9614 ((links_massiv && rechts_massiv) ||
9615 element == EL_EXPANDABLE_WALL_VERTICAL))
9616 Tile[ax][ay] = EL_WALL;
9619 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9622 static void MauerAblegerStahl(int ax, int ay)
9624 int element = Tile[ax][ay];
9625 int graphic = el2img(element);
9626 boolean oben_frei = FALSE, unten_frei = FALSE;
9627 boolean links_frei = FALSE, rechts_frei = FALSE;
9628 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9629 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9630 boolean new_wall = FALSE;
9632 if (IS_ANIMATED(graphic))
9633 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9635 if (!MovDelay[ax][ay]) // start building new wall
9636 MovDelay[ax][ay] = 6;
9638 if (MovDelay[ax][ay]) // wait some time before building new wall
9641 if (MovDelay[ax][ay])
9645 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9647 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9649 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9651 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9654 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9655 element == EL_EXPANDABLE_STEELWALL_ANY)
9659 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9660 Store[ax][ay-1] = element;
9661 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9662 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9663 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9664 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9669 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9670 Store[ax][ay+1] = element;
9671 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9672 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9673 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9674 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9679 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9680 element == EL_EXPANDABLE_STEELWALL_ANY)
9684 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9685 Store[ax-1][ay] = element;
9686 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9687 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9688 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9689 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9695 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9696 Store[ax+1][ay] = element;
9697 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9698 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9699 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9700 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9705 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9707 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9708 unten_massiv = TRUE;
9709 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9710 links_massiv = TRUE;
9711 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9712 rechts_massiv = TRUE;
9714 if (((oben_massiv && unten_massiv) ||
9715 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9716 ((links_massiv && rechts_massiv) ||
9717 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9718 Tile[ax][ay] = EL_STEELWALL;
9721 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9724 static void CheckForDragon(int x, int y)
9727 boolean dragon_found = FALSE;
9728 static int xy[4][2] =
9736 for (i = 0; i < NUM_DIRECTIONS; i++)
9738 for (j = 0; j < 4; j++)
9740 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9742 if (IN_LEV_FIELD(xx, yy) &&
9743 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9745 if (Tile[xx][yy] == EL_DRAGON)
9746 dragon_found = TRUE;
9755 for (i = 0; i < NUM_DIRECTIONS; i++)
9757 for (j = 0; j < 3; j++)
9759 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9761 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9763 Tile[xx][yy] = EL_EMPTY;
9764 TEST_DrawLevelField(xx, yy);
9773 static void InitBuggyBase(int x, int y)
9775 int element = Tile[x][y];
9776 int activating_delay = FRAMES_PER_SECOND / 4;
9779 (element == EL_SP_BUGGY_BASE ?
9780 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9781 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9783 element == EL_SP_BUGGY_BASE_ACTIVE ?
9784 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9787 static void WarnBuggyBase(int x, int y)
9790 static int xy[4][2] =
9798 for (i = 0; i < NUM_DIRECTIONS; i++)
9800 int xx = x + xy[i][0];
9801 int yy = y + xy[i][1];
9803 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9805 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9812 static void InitTrap(int x, int y)
9814 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9817 static void ActivateTrap(int x, int y)
9819 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9822 static void ChangeActiveTrap(int x, int y)
9824 int graphic = IMG_TRAP_ACTIVE;
9826 // if new animation frame was drawn, correct crumbled sand border
9827 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9828 TEST_DrawLevelFieldCrumbled(x, y);
9831 static int getSpecialActionElement(int element, int number, int base_element)
9833 return (element != EL_EMPTY ? element :
9834 number != -1 ? base_element + number - 1 :
9838 static int getModifiedActionNumber(int value_old, int operator, int operand,
9839 int value_min, int value_max)
9841 int value_new = (operator == CA_MODE_SET ? operand :
9842 operator == CA_MODE_ADD ? value_old + operand :
9843 operator == CA_MODE_SUBTRACT ? value_old - operand :
9844 operator == CA_MODE_MULTIPLY ? value_old * operand :
9845 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9846 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9849 return (value_new < value_min ? value_min :
9850 value_new > value_max ? value_max :
9854 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9856 struct ElementInfo *ei = &element_info[element];
9857 struct ElementChangeInfo *change = &ei->change_page[page];
9858 int target_element = change->target_element;
9859 int action_type = change->action_type;
9860 int action_mode = change->action_mode;
9861 int action_arg = change->action_arg;
9862 int action_element = change->action_element;
9865 if (!change->has_action)
9868 // ---------- determine action paramater values -----------------------------
9870 int level_time_value =
9871 (level.time > 0 ? TimeLeft :
9874 int action_arg_element_raw =
9875 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9876 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9877 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9878 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9879 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9880 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9881 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9883 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9885 int action_arg_direction =
9886 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9887 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9888 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9889 change->actual_trigger_side :
9890 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9891 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9894 int action_arg_number_min =
9895 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9898 int action_arg_number_max =
9899 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9900 action_type == CA_SET_LEVEL_GEMS ? 999 :
9901 action_type == CA_SET_LEVEL_TIME ? 9999 :
9902 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9903 action_type == CA_SET_CE_VALUE ? 9999 :
9904 action_type == CA_SET_CE_SCORE ? 9999 :
9907 int action_arg_number_reset =
9908 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9909 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9910 action_type == CA_SET_LEVEL_TIME ? level.time :
9911 action_type == CA_SET_LEVEL_SCORE ? 0 :
9912 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9913 action_type == CA_SET_CE_SCORE ? 0 :
9916 int action_arg_number =
9917 (action_arg <= CA_ARG_MAX ? action_arg :
9918 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9919 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9920 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9921 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9922 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9923 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9924 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9925 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9926 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9927 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9928 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9929 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9930 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9931 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9932 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9933 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9934 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9935 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9936 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9937 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9938 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9941 int action_arg_number_old =
9942 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9943 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9944 action_type == CA_SET_LEVEL_SCORE ? game.score :
9945 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9946 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9949 int action_arg_number_new =
9950 getModifiedActionNumber(action_arg_number_old,
9951 action_mode, action_arg_number,
9952 action_arg_number_min, action_arg_number_max);
9954 int trigger_player_bits =
9955 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9956 change->actual_trigger_player_bits : change->trigger_player);
9958 int action_arg_player_bits =
9959 (action_arg >= CA_ARG_PLAYER_1 &&
9960 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9961 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9962 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9965 // ---------- execute action -----------------------------------------------
9967 switch (action_type)
9974 // ---------- level actions ----------------------------------------------
9976 case CA_RESTART_LEVEL:
9978 game.restart_level = TRUE;
9983 case CA_SHOW_ENVELOPE:
9985 int element = getSpecialActionElement(action_arg_element,
9986 action_arg_number, EL_ENVELOPE_1);
9988 if (IS_ENVELOPE(element))
9989 local_player->show_envelope = element;
9994 case CA_SET_LEVEL_TIME:
9996 if (level.time > 0) // only modify limited time value
9998 TimeLeft = action_arg_number_new;
10000 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10002 DisplayGameControlValues();
10004 if (!TimeLeft && setup.time_limit)
10005 for (i = 0; i < MAX_PLAYERS; i++)
10006 KillPlayer(&stored_player[i]);
10012 case CA_SET_LEVEL_SCORE:
10014 game.score = action_arg_number_new;
10016 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10018 DisplayGameControlValues();
10023 case CA_SET_LEVEL_GEMS:
10025 game.gems_still_needed = action_arg_number_new;
10027 game.snapshot.collected_item = TRUE;
10029 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10031 DisplayGameControlValues();
10036 case CA_SET_LEVEL_WIND:
10038 game.wind_direction = action_arg_direction;
10043 case CA_SET_LEVEL_RANDOM_SEED:
10045 // ensure that setting a new random seed while playing is predictable
10046 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10051 // ---------- player actions ---------------------------------------------
10053 case CA_MOVE_PLAYER:
10054 case CA_MOVE_PLAYER_NEW:
10056 // automatically move to the next field in specified direction
10057 for (i = 0; i < MAX_PLAYERS; i++)
10058 if (trigger_player_bits & (1 << i))
10059 if (action_type == CA_MOVE_PLAYER ||
10060 stored_player[i].MovPos == 0)
10061 stored_player[i].programmed_action = action_arg_direction;
10066 case CA_EXIT_PLAYER:
10068 for (i = 0; i < MAX_PLAYERS; i++)
10069 if (action_arg_player_bits & (1 << i))
10070 ExitPlayer(&stored_player[i]);
10072 if (game.players_still_needed == 0)
10078 case CA_KILL_PLAYER:
10080 for (i = 0; i < MAX_PLAYERS; i++)
10081 if (action_arg_player_bits & (1 << i))
10082 KillPlayer(&stored_player[i]);
10087 case CA_SET_PLAYER_KEYS:
10089 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10090 int element = getSpecialActionElement(action_arg_element,
10091 action_arg_number, EL_KEY_1);
10093 if (IS_KEY(element))
10095 for (i = 0; i < MAX_PLAYERS; i++)
10097 if (trigger_player_bits & (1 << i))
10099 stored_player[i].key[KEY_NR(element)] = key_state;
10101 DrawGameDoorValues();
10109 case CA_SET_PLAYER_SPEED:
10111 for (i = 0; i < MAX_PLAYERS; i++)
10113 if (trigger_player_bits & (1 << i))
10115 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10117 if (action_arg == CA_ARG_SPEED_FASTER &&
10118 stored_player[i].cannot_move)
10120 action_arg_number = STEPSIZE_VERY_SLOW;
10122 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10123 action_arg == CA_ARG_SPEED_FASTER)
10125 action_arg_number = 2;
10126 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10129 else if (action_arg == CA_ARG_NUMBER_RESET)
10131 action_arg_number = level.initial_player_stepsize[i];
10135 getModifiedActionNumber(move_stepsize,
10138 action_arg_number_min,
10139 action_arg_number_max);
10141 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10148 case CA_SET_PLAYER_SHIELD:
10150 for (i = 0; i < MAX_PLAYERS; i++)
10152 if (trigger_player_bits & (1 << i))
10154 if (action_arg == CA_ARG_SHIELD_OFF)
10156 stored_player[i].shield_normal_time_left = 0;
10157 stored_player[i].shield_deadly_time_left = 0;
10159 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10161 stored_player[i].shield_normal_time_left = 999999;
10163 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10165 stored_player[i].shield_normal_time_left = 999999;
10166 stored_player[i].shield_deadly_time_left = 999999;
10174 case CA_SET_PLAYER_GRAVITY:
10176 for (i = 0; i < MAX_PLAYERS; i++)
10178 if (trigger_player_bits & (1 << i))
10180 stored_player[i].gravity =
10181 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10182 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10183 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10184 stored_player[i].gravity);
10191 case CA_SET_PLAYER_ARTWORK:
10193 for (i = 0; i < MAX_PLAYERS; i++)
10195 if (trigger_player_bits & (1 << i))
10197 int artwork_element = action_arg_element;
10199 if (action_arg == CA_ARG_ELEMENT_RESET)
10201 (level.use_artwork_element[i] ? level.artwork_element[i] :
10202 stored_player[i].element_nr);
10204 if (stored_player[i].artwork_element != artwork_element)
10205 stored_player[i].Frame = 0;
10207 stored_player[i].artwork_element = artwork_element;
10209 SetPlayerWaiting(&stored_player[i], FALSE);
10211 // set number of special actions for bored and sleeping animation
10212 stored_player[i].num_special_action_bored =
10213 get_num_special_action(artwork_element,
10214 ACTION_BORING_1, ACTION_BORING_LAST);
10215 stored_player[i].num_special_action_sleeping =
10216 get_num_special_action(artwork_element,
10217 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10224 case CA_SET_PLAYER_INVENTORY:
10226 for (i = 0; i < MAX_PLAYERS; i++)
10228 struct PlayerInfo *player = &stored_player[i];
10231 if (trigger_player_bits & (1 << i))
10233 int inventory_element = action_arg_element;
10235 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10236 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10237 action_arg == CA_ARG_ELEMENT_ACTION)
10239 int element = inventory_element;
10240 int collect_count = element_info[element].collect_count_initial;
10242 if (!IS_CUSTOM_ELEMENT(element))
10245 if (collect_count == 0)
10246 player->inventory_infinite_element = element;
10248 for (k = 0; k < collect_count; k++)
10249 if (player->inventory_size < MAX_INVENTORY_SIZE)
10250 player->inventory_element[player->inventory_size++] =
10253 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10254 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10255 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10257 if (player->inventory_infinite_element != EL_UNDEFINED &&
10258 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10259 action_arg_element_raw))
10260 player->inventory_infinite_element = EL_UNDEFINED;
10262 for (k = 0, j = 0; j < player->inventory_size; j++)
10264 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10265 action_arg_element_raw))
10266 player->inventory_element[k++] = player->inventory_element[j];
10269 player->inventory_size = k;
10271 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10273 if (player->inventory_size > 0)
10275 for (j = 0; j < player->inventory_size - 1; j++)
10276 player->inventory_element[j] = player->inventory_element[j + 1];
10278 player->inventory_size--;
10281 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10283 if (player->inventory_size > 0)
10284 player->inventory_size--;
10286 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10288 player->inventory_infinite_element = EL_UNDEFINED;
10289 player->inventory_size = 0;
10291 else if (action_arg == CA_ARG_INVENTORY_RESET)
10293 player->inventory_infinite_element = EL_UNDEFINED;
10294 player->inventory_size = 0;
10296 if (level.use_initial_inventory[i])
10298 for (j = 0; j < level.initial_inventory_size[i]; j++)
10300 int element = level.initial_inventory_content[i][j];
10301 int collect_count = element_info[element].collect_count_initial;
10303 if (!IS_CUSTOM_ELEMENT(element))
10306 if (collect_count == 0)
10307 player->inventory_infinite_element = element;
10309 for (k = 0; k < collect_count; k++)
10310 if (player->inventory_size < MAX_INVENTORY_SIZE)
10311 player->inventory_element[player->inventory_size++] =
10322 // ---------- CE actions -------------------------------------------------
10324 case CA_SET_CE_VALUE:
10326 int last_ce_value = CustomValue[x][y];
10328 CustomValue[x][y] = action_arg_number_new;
10330 if (CustomValue[x][y] != last_ce_value)
10332 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10333 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10335 if (CustomValue[x][y] == 0)
10337 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10338 ChangeCount[x][y] = 0; // allow at least one more change
10340 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10341 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10348 case CA_SET_CE_SCORE:
10350 int last_ce_score = ei->collect_score;
10352 ei->collect_score = action_arg_number_new;
10354 if (ei->collect_score != last_ce_score)
10356 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10357 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10359 if (ei->collect_score == 0)
10363 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10364 ChangeCount[x][y] = 0; // allow at least one more change
10366 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10367 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10370 This is a very special case that seems to be a mixture between
10371 CheckElementChange() and CheckTriggeredElementChange(): while
10372 the first one only affects single elements that are triggered
10373 directly, the second one affects multiple elements in the playfield
10374 that are triggered indirectly by another element. This is a third
10375 case: Changing the CE score always affects multiple identical CEs,
10376 so every affected CE must be checked, not only the single CE for
10377 which the CE score was changed in the first place (as every instance
10378 of that CE shares the same CE score, and therefore also can change)!
10380 SCAN_PLAYFIELD(xx, yy)
10382 if (Tile[xx][yy] == element)
10383 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10384 CE_SCORE_GETS_ZERO);
10392 case CA_SET_CE_ARTWORK:
10394 int artwork_element = action_arg_element;
10395 boolean reset_frame = FALSE;
10398 if (action_arg == CA_ARG_ELEMENT_RESET)
10399 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10402 if (ei->gfx_element != artwork_element)
10403 reset_frame = TRUE;
10405 ei->gfx_element = artwork_element;
10407 SCAN_PLAYFIELD(xx, yy)
10409 if (Tile[xx][yy] == element)
10413 ResetGfxAnimation(xx, yy);
10414 ResetRandomAnimationValue(xx, yy);
10417 TEST_DrawLevelField(xx, yy);
10424 // ---------- engine actions ---------------------------------------------
10426 case CA_SET_ENGINE_SCAN_MODE:
10428 InitPlayfieldScanMode(action_arg);
10438 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10440 int old_element = Tile[x][y];
10441 int new_element = GetElementFromGroupElement(element);
10442 int previous_move_direction = MovDir[x][y];
10443 int last_ce_value = CustomValue[x][y];
10444 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10445 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10446 boolean add_player_onto_element = (new_element_is_player &&
10447 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10448 IS_WALKABLE(old_element));
10450 if (!add_player_onto_element)
10452 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10453 RemoveMovingField(x, y);
10457 Tile[x][y] = new_element;
10459 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10460 MovDir[x][y] = previous_move_direction;
10462 if (element_info[new_element].use_last_ce_value)
10463 CustomValue[x][y] = last_ce_value;
10465 InitField_WithBug1(x, y, FALSE);
10467 new_element = Tile[x][y]; // element may have changed
10469 ResetGfxAnimation(x, y);
10470 ResetRandomAnimationValue(x, y);
10472 TEST_DrawLevelField(x, y);
10474 if (GFX_CRUMBLED(new_element))
10475 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10478 // check if element under the player changes from accessible to unaccessible
10479 // (needed for special case of dropping element which then changes)
10480 // (must be checked after creating new element for walkable group elements)
10481 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10482 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10489 // "ChangeCount" not set yet to allow "entered by player" change one time
10490 if (new_element_is_player)
10491 RelocatePlayer(x, y, new_element);
10494 ChangeCount[x][y]++; // count number of changes in the same frame
10496 TestIfBadThingTouchesPlayer(x, y);
10497 TestIfPlayerTouchesCustomElement(x, y);
10498 TestIfElementTouchesCustomElement(x, y);
10501 static void CreateField(int x, int y, int element)
10503 CreateFieldExt(x, y, element, FALSE);
10506 static void CreateElementFromChange(int x, int y, int element)
10508 element = GET_VALID_RUNTIME_ELEMENT(element);
10510 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10512 int old_element = Tile[x][y];
10514 // prevent changed element from moving in same engine frame
10515 // unless both old and new element can either fall or move
10516 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10517 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10521 CreateFieldExt(x, y, element, TRUE);
10524 static boolean ChangeElement(int x, int y, int element, int page)
10526 struct ElementInfo *ei = &element_info[element];
10527 struct ElementChangeInfo *change = &ei->change_page[page];
10528 int ce_value = CustomValue[x][y];
10529 int ce_score = ei->collect_score;
10530 int target_element;
10531 int old_element = Tile[x][y];
10533 // always use default change event to prevent running into a loop
10534 if (ChangeEvent[x][y] == -1)
10535 ChangeEvent[x][y] = CE_DELAY;
10537 if (ChangeEvent[x][y] == CE_DELAY)
10539 // reset actual trigger element, trigger player and action element
10540 change->actual_trigger_element = EL_EMPTY;
10541 change->actual_trigger_player = EL_EMPTY;
10542 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10543 change->actual_trigger_side = CH_SIDE_NONE;
10544 change->actual_trigger_ce_value = 0;
10545 change->actual_trigger_ce_score = 0;
10548 // do not change elements more than a specified maximum number of changes
10549 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10552 ChangeCount[x][y]++; // count number of changes in the same frame
10554 if (change->explode)
10561 if (change->use_target_content)
10563 boolean complete_replace = TRUE;
10564 boolean can_replace[3][3];
10567 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10570 boolean is_walkable;
10571 boolean is_diggable;
10572 boolean is_collectible;
10573 boolean is_removable;
10574 boolean is_destructible;
10575 int ex = x + xx - 1;
10576 int ey = y + yy - 1;
10577 int content_element = change->target_content.e[xx][yy];
10580 can_replace[xx][yy] = TRUE;
10582 if (ex == x && ey == y) // do not check changing element itself
10585 if (content_element == EL_EMPTY_SPACE)
10587 can_replace[xx][yy] = FALSE; // do not replace border with space
10592 if (!IN_LEV_FIELD(ex, ey))
10594 can_replace[xx][yy] = FALSE;
10595 complete_replace = FALSE;
10602 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10603 e = MovingOrBlocked2Element(ex, ey);
10605 is_empty = (IS_FREE(ex, ey) ||
10606 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10608 is_walkable = (is_empty || IS_WALKABLE(e));
10609 is_diggable = (is_empty || IS_DIGGABLE(e));
10610 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10611 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10612 is_removable = (is_diggable || is_collectible);
10614 can_replace[xx][yy] =
10615 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10616 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10617 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10618 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10619 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10620 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10621 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10623 if (!can_replace[xx][yy])
10624 complete_replace = FALSE;
10627 if (!change->only_if_complete || complete_replace)
10629 boolean something_has_changed = FALSE;
10631 if (change->only_if_complete && change->use_random_replace &&
10632 RND(100) < change->random_percentage)
10635 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10637 int ex = x + xx - 1;
10638 int ey = y + yy - 1;
10639 int content_element;
10641 if (can_replace[xx][yy] && (!change->use_random_replace ||
10642 RND(100) < change->random_percentage))
10644 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10645 RemoveMovingField(ex, ey);
10647 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10649 content_element = change->target_content.e[xx][yy];
10650 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10651 ce_value, ce_score);
10653 CreateElementFromChange(ex, ey, target_element);
10655 something_has_changed = TRUE;
10657 // for symmetry reasons, freeze newly created border elements
10658 if (ex != x || ey != y)
10659 Stop[ex][ey] = TRUE; // no more moving in this frame
10663 if (something_has_changed)
10665 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10666 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10672 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10673 ce_value, ce_score);
10675 if (element == EL_DIAGONAL_GROWING ||
10676 element == EL_DIAGONAL_SHRINKING)
10678 target_element = Store[x][y];
10680 Store[x][y] = EL_EMPTY;
10683 CreateElementFromChange(x, y, target_element);
10685 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10686 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10689 // this uses direct change before indirect change
10690 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10695 static void HandleElementChange(int x, int y, int page)
10697 int element = MovingOrBlocked2Element(x, y);
10698 struct ElementInfo *ei = &element_info[element];
10699 struct ElementChangeInfo *change = &ei->change_page[page];
10700 boolean handle_action_before_change = FALSE;
10703 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10704 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10706 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10707 x, y, element, element_info[element].token_name);
10708 Debug("game:playing:HandleElementChange", "This should never happen!");
10712 // this can happen with classic bombs on walkable, changing elements
10713 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10718 if (ChangeDelay[x][y] == 0) // initialize element change
10720 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10722 if (change->can_change)
10724 // !!! not clear why graphic animation should be reset at all here !!!
10725 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10726 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10729 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10731 When using an animation frame delay of 1 (this only happens with
10732 "sp_zonk.moving.left/right" in the classic graphics), the default
10733 (non-moving) animation shows wrong animation frames (while the
10734 moving animation, like "sp_zonk.moving.left/right", is correct,
10735 so this graphical bug never shows up with the classic graphics).
10736 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10737 be drawn instead of the correct frames 0,1,2,3. This is caused by
10738 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10739 an element change: First when the change delay ("ChangeDelay[][]")
10740 counter has reached zero after decrementing, then a second time in
10741 the next frame (after "GfxFrame[][]" was already incremented) when
10742 "ChangeDelay[][]" is reset to the initial delay value again.
10744 This causes frame 0 to be drawn twice, while the last frame won't
10745 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10747 As some animations may already be cleverly designed around this bug
10748 (at least the "Snake Bite" snake tail animation does this), it cannot
10749 simply be fixed here without breaking such existing animations.
10750 Unfortunately, it cannot easily be detected if a graphics set was
10751 designed "before" or "after" the bug was fixed. As a workaround,
10752 a new graphics set option "game.graphics_engine_version" was added
10753 to be able to specify the game's major release version for which the
10754 graphics set was designed, which can then be used to decide if the
10755 bugfix should be used (version 4 and above) or not (version 3 or
10756 below, or if no version was specified at all, as with old sets).
10758 (The wrong/fixed animation frames can be tested with the test level set
10759 "test_gfxframe" and level "000", which contains a specially prepared
10760 custom element at level position (x/y) == (11/9) which uses the zonk
10761 animation mentioned above. Using "game.graphics_engine_version: 4"
10762 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10763 This can also be seen from the debug output for this test element.)
10766 // when a custom element is about to change (for example by change delay),
10767 // do not reset graphic animation when the custom element is moving
10768 if (game.graphics_engine_version < 4 &&
10771 ResetGfxAnimation(x, y);
10772 ResetRandomAnimationValue(x, y);
10775 if (change->pre_change_function)
10776 change->pre_change_function(x, y);
10780 ChangeDelay[x][y]--;
10782 if (ChangeDelay[x][y] != 0) // continue element change
10784 if (change->can_change)
10786 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10788 if (IS_ANIMATED(graphic))
10789 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10791 if (change->change_function)
10792 change->change_function(x, y);
10795 else // finish element change
10797 if (ChangePage[x][y] != -1) // remember page from delayed change
10799 page = ChangePage[x][y];
10800 ChangePage[x][y] = -1;
10802 change = &ei->change_page[page];
10805 if (IS_MOVING(x, y)) // never change a running system ;-)
10807 ChangeDelay[x][y] = 1; // try change after next move step
10808 ChangePage[x][y] = page; // remember page to use for change
10813 // special case: set new level random seed before changing element
10814 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10815 handle_action_before_change = TRUE;
10817 if (change->has_action && handle_action_before_change)
10818 ExecuteCustomElementAction(x, y, element, page);
10820 if (change->can_change)
10822 if (ChangeElement(x, y, element, page))
10824 if (change->post_change_function)
10825 change->post_change_function(x, y);
10829 if (change->has_action && !handle_action_before_change)
10830 ExecuteCustomElementAction(x, y, element, page);
10834 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10835 int trigger_element,
10837 int trigger_player,
10841 boolean change_done_any = FALSE;
10842 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10845 if (!(trigger_events[trigger_element][trigger_event]))
10848 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10850 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10852 int element = EL_CUSTOM_START + i;
10853 boolean change_done = FALSE;
10856 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10857 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10860 for (p = 0; p < element_info[element].num_change_pages; p++)
10862 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10864 if (change->can_change_or_has_action &&
10865 change->has_event[trigger_event] &&
10866 change->trigger_side & trigger_side &&
10867 change->trigger_player & trigger_player &&
10868 change->trigger_page & trigger_page_bits &&
10869 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10871 change->actual_trigger_element = trigger_element;
10872 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10873 change->actual_trigger_player_bits = trigger_player;
10874 change->actual_trigger_side = trigger_side;
10875 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10876 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10878 if ((change->can_change && !change_done) || change->has_action)
10882 SCAN_PLAYFIELD(x, y)
10884 if (Tile[x][y] == element)
10886 if (change->can_change && !change_done)
10888 // if element already changed in this frame, not only prevent
10889 // another element change (checked in ChangeElement()), but
10890 // also prevent additional element actions for this element
10892 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10893 !level.use_action_after_change_bug)
10896 ChangeDelay[x][y] = 1;
10897 ChangeEvent[x][y] = trigger_event;
10899 HandleElementChange(x, y, p);
10901 else if (change->has_action)
10903 // if element already changed in this frame, not only prevent
10904 // another element change (checked in ChangeElement()), but
10905 // also prevent additional element actions for this element
10907 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10908 !level.use_action_after_change_bug)
10911 ExecuteCustomElementAction(x, y, element, p);
10912 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10917 if (change->can_change)
10919 change_done = TRUE;
10920 change_done_any = TRUE;
10927 RECURSION_LOOP_DETECTION_END();
10929 return change_done_any;
10932 static boolean CheckElementChangeExt(int x, int y,
10934 int trigger_element,
10936 int trigger_player,
10939 boolean change_done = FALSE;
10942 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10943 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10946 if (Tile[x][y] == EL_BLOCKED)
10948 Blocked2Moving(x, y, &x, &y);
10949 element = Tile[x][y];
10952 // check if element has already changed or is about to change after moving
10953 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10954 Tile[x][y] != element) ||
10956 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10957 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10958 ChangePage[x][y] != -1)))
10961 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10963 for (p = 0; p < element_info[element].num_change_pages; p++)
10965 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10967 /* check trigger element for all events where the element that is checked
10968 for changing interacts with a directly adjacent element -- this is
10969 different to element changes that affect other elements to change on the
10970 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10971 boolean check_trigger_element =
10972 (trigger_event == CE_TOUCHING_X ||
10973 trigger_event == CE_HITTING_X ||
10974 trigger_event == CE_HIT_BY_X ||
10975 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10977 if (change->can_change_or_has_action &&
10978 change->has_event[trigger_event] &&
10979 change->trigger_side & trigger_side &&
10980 change->trigger_player & trigger_player &&
10981 (!check_trigger_element ||
10982 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10984 change->actual_trigger_element = trigger_element;
10985 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10986 change->actual_trigger_player_bits = trigger_player;
10987 change->actual_trigger_side = trigger_side;
10988 change->actual_trigger_ce_value = CustomValue[x][y];
10989 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10991 // special case: trigger element not at (x,y) position for some events
10992 if (check_trigger_element)
11004 { 0, 0 }, { 0, 0 }, { 0, 0 },
11008 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11009 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11011 change->actual_trigger_ce_value = CustomValue[xx][yy];
11012 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11015 if (change->can_change && !change_done)
11017 ChangeDelay[x][y] = 1;
11018 ChangeEvent[x][y] = trigger_event;
11020 HandleElementChange(x, y, p);
11022 change_done = TRUE;
11024 else if (change->has_action)
11026 ExecuteCustomElementAction(x, y, element, p);
11027 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11032 RECURSION_LOOP_DETECTION_END();
11034 return change_done;
11037 static void PlayPlayerSound(struct PlayerInfo *player)
11039 int jx = player->jx, jy = player->jy;
11040 int sound_element = player->artwork_element;
11041 int last_action = player->last_action_waiting;
11042 int action = player->action_waiting;
11044 if (player->is_waiting)
11046 if (action != last_action)
11047 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11049 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11053 if (action != last_action)
11054 StopSound(element_info[sound_element].sound[last_action]);
11056 if (last_action == ACTION_SLEEPING)
11057 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11061 static void PlayAllPlayersSound(void)
11065 for (i = 0; i < MAX_PLAYERS; i++)
11066 if (stored_player[i].active)
11067 PlayPlayerSound(&stored_player[i]);
11070 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11072 boolean last_waiting = player->is_waiting;
11073 int move_dir = player->MovDir;
11075 player->dir_waiting = move_dir;
11076 player->last_action_waiting = player->action_waiting;
11080 if (!last_waiting) // not waiting -> waiting
11082 player->is_waiting = TRUE;
11084 player->frame_counter_bored =
11086 game.player_boring_delay_fixed +
11087 GetSimpleRandom(game.player_boring_delay_random);
11088 player->frame_counter_sleeping =
11090 game.player_sleeping_delay_fixed +
11091 GetSimpleRandom(game.player_sleeping_delay_random);
11093 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11096 if (game.player_sleeping_delay_fixed +
11097 game.player_sleeping_delay_random > 0 &&
11098 player->anim_delay_counter == 0 &&
11099 player->post_delay_counter == 0 &&
11100 FrameCounter >= player->frame_counter_sleeping)
11101 player->is_sleeping = TRUE;
11102 else if (game.player_boring_delay_fixed +
11103 game.player_boring_delay_random > 0 &&
11104 FrameCounter >= player->frame_counter_bored)
11105 player->is_bored = TRUE;
11107 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11108 player->is_bored ? ACTION_BORING :
11111 if (player->is_sleeping && player->use_murphy)
11113 // special case for sleeping Murphy when leaning against non-free tile
11115 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11116 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11117 !IS_MOVING(player->jx - 1, player->jy)))
11118 move_dir = MV_LEFT;
11119 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11120 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11121 !IS_MOVING(player->jx + 1, player->jy)))
11122 move_dir = MV_RIGHT;
11124 player->is_sleeping = FALSE;
11126 player->dir_waiting = move_dir;
11129 if (player->is_sleeping)
11131 if (player->num_special_action_sleeping > 0)
11133 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11135 int last_special_action = player->special_action_sleeping;
11136 int num_special_action = player->num_special_action_sleeping;
11137 int special_action =
11138 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11139 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11140 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11141 last_special_action + 1 : ACTION_SLEEPING);
11142 int special_graphic =
11143 el_act_dir2img(player->artwork_element, special_action, move_dir);
11145 player->anim_delay_counter =
11146 graphic_info[special_graphic].anim_delay_fixed +
11147 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11148 player->post_delay_counter =
11149 graphic_info[special_graphic].post_delay_fixed +
11150 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11152 player->special_action_sleeping = special_action;
11155 if (player->anim_delay_counter > 0)
11157 player->action_waiting = player->special_action_sleeping;
11158 player->anim_delay_counter--;
11160 else if (player->post_delay_counter > 0)
11162 player->post_delay_counter--;
11166 else if (player->is_bored)
11168 if (player->num_special_action_bored > 0)
11170 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11172 int special_action =
11173 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11174 int special_graphic =
11175 el_act_dir2img(player->artwork_element, special_action, move_dir);
11177 player->anim_delay_counter =
11178 graphic_info[special_graphic].anim_delay_fixed +
11179 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11180 player->post_delay_counter =
11181 graphic_info[special_graphic].post_delay_fixed +
11182 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11184 player->special_action_bored = special_action;
11187 if (player->anim_delay_counter > 0)
11189 player->action_waiting = player->special_action_bored;
11190 player->anim_delay_counter--;
11192 else if (player->post_delay_counter > 0)
11194 player->post_delay_counter--;
11199 else if (last_waiting) // waiting -> not waiting
11201 player->is_waiting = FALSE;
11202 player->is_bored = FALSE;
11203 player->is_sleeping = FALSE;
11205 player->frame_counter_bored = -1;
11206 player->frame_counter_sleeping = -1;
11208 player->anim_delay_counter = 0;
11209 player->post_delay_counter = 0;
11211 player->dir_waiting = player->MovDir;
11212 player->action_waiting = ACTION_DEFAULT;
11214 player->special_action_bored = ACTION_DEFAULT;
11215 player->special_action_sleeping = ACTION_DEFAULT;
11219 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11221 if ((!player->is_moving && player->was_moving) ||
11222 (player->MovPos == 0 && player->was_moving) ||
11223 (player->is_snapping && !player->was_snapping) ||
11224 (player->is_dropping && !player->was_dropping))
11226 if (!CheckSaveEngineSnapshotToList())
11229 player->was_moving = FALSE;
11230 player->was_snapping = TRUE;
11231 player->was_dropping = TRUE;
11235 if (player->is_moving)
11236 player->was_moving = TRUE;
11238 if (!player->is_snapping)
11239 player->was_snapping = FALSE;
11241 if (!player->is_dropping)
11242 player->was_dropping = FALSE;
11245 static struct MouseActionInfo mouse_action_last = { 0 };
11246 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11247 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11250 CheckSaveEngineSnapshotToList();
11252 mouse_action_last = mouse_action;
11255 static void CheckSingleStepMode(struct PlayerInfo *player)
11257 if (tape.single_step && tape.recording && !tape.pausing)
11259 /* as it is called "single step mode", just return to pause mode when the
11260 player stopped moving after one tile (or never starts moving at all) */
11261 if (!player->is_moving &&
11262 !player->is_pushing &&
11263 !player->is_dropping_pressed &&
11264 !player->effective_mouse_action.button)
11265 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11268 CheckSaveEngineSnapshot(player);
11271 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11273 int left = player_action & JOY_LEFT;
11274 int right = player_action & JOY_RIGHT;
11275 int up = player_action & JOY_UP;
11276 int down = player_action & JOY_DOWN;
11277 int button1 = player_action & JOY_BUTTON_1;
11278 int button2 = player_action & JOY_BUTTON_2;
11279 int dx = (left ? -1 : right ? 1 : 0);
11280 int dy = (up ? -1 : down ? 1 : 0);
11282 if (!player->active || tape.pausing)
11288 SnapField(player, dx, dy);
11292 DropElement(player);
11294 MovePlayer(player, dx, dy);
11297 CheckSingleStepMode(player);
11299 SetPlayerWaiting(player, FALSE);
11301 return player_action;
11305 // no actions for this player (no input at player's configured device)
11307 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11308 SnapField(player, 0, 0);
11309 CheckGravityMovementWhenNotMoving(player);
11311 if (player->MovPos == 0)
11312 SetPlayerWaiting(player, TRUE);
11314 if (player->MovPos == 0) // needed for tape.playing
11315 player->is_moving = FALSE;
11317 player->is_dropping = FALSE;
11318 player->is_dropping_pressed = FALSE;
11319 player->drop_pressed_delay = 0;
11321 CheckSingleStepMode(player);
11327 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11330 if (!tape.use_mouse_actions)
11333 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11334 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11335 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11338 static void SetTapeActionFromMouseAction(byte *tape_action,
11339 struct MouseActionInfo *mouse_action)
11341 if (!tape.use_mouse_actions)
11344 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11345 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11346 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11349 static void CheckLevelSolved(void)
11351 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11353 if (game_em.level_solved &&
11354 !game_em.game_over) // game won
11358 game_em.game_over = TRUE;
11360 game.all_players_gone = TRUE;
11363 if (game_em.game_over) // game lost
11364 game.all_players_gone = TRUE;
11366 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11368 if (game_sp.level_solved &&
11369 !game_sp.game_over) // game won
11373 game_sp.game_over = TRUE;
11375 game.all_players_gone = TRUE;
11378 if (game_sp.game_over) // game lost
11379 game.all_players_gone = TRUE;
11381 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11383 if (game_mm.level_solved &&
11384 !game_mm.game_over) // game won
11388 game_mm.game_over = TRUE;
11390 game.all_players_gone = TRUE;
11393 if (game_mm.game_over) // game lost
11394 game.all_players_gone = TRUE;
11398 static void CheckLevelTime(void)
11402 if (TimeFrames >= FRAMES_PER_SECOND)
11407 for (i = 0; i < MAX_PLAYERS; i++)
11409 struct PlayerInfo *player = &stored_player[i];
11411 if (SHIELD_ON(player))
11413 player->shield_normal_time_left--;
11415 if (player->shield_deadly_time_left > 0)
11416 player->shield_deadly_time_left--;
11420 if (!game.LevelSolved && !level.use_step_counter)
11428 if (TimeLeft <= 10 && setup.time_limit)
11429 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11431 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11432 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11434 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11436 if (!TimeLeft && setup.time_limit)
11438 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11439 game_em.lev->killed_out_of_time = TRUE;
11441 for (i = 0; i < MAX_PLAYERS; i++)
11442 KillPlayer(&stored_player[i]);
11445 else if (game.no_time_limit && !game.all_players_gone)
11447 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11450 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11453 if (tape.recording || tape.playing)
11454 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11457 if (tape.recording || tape.playing)
11458 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11460 UpdateAndDisplayGameControlValues();
11463 void AdvanceFrameAndPlayerCounters(int player_nr)
11467 // advance frame counters (global frame counter and time frame counter)
11471 // advance player counters (counters for move delay, move animation etc.)
11472 for (i = 0; i < MAX_PLAYERS; i++)
11474 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11475 int move_delay_value = stored_player[i].move_delay_value;
11476 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11478 if (!advance_player_counters) // not all players may be affected
11481 if (move_frames == 0) // less than one move per game frame
11483 int stepsize = TILEX / move_delay_value;
11484 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11485 int count = (stored_player[i].is_moving ?
11486 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11488 if (count % delay == 0)
11492 stored_player[i].Frame += move_frames;
11494 if (stored_player[i].MovPos != 0)
11495 stored_player[i].StepFrame += move_frames;
11497 if (stored_player[i].move_delay > 0)
11498 stored_player[i].move_delay--;
11500 // due to bugs in previous versions, counter must count up, not down
11501 if (stored_player[i].push_delay != -1)
11502 stored_player[i].push_delay++;
11504 if (stored_player[i].drop_delay > 0)
11505 stored_player[i].drop_delay--;
11507 if (stored_player[i].is_dropping_pressed)
11508 stored_player[i].drop_pressed_delay++;
11512 void StartGameActions(boolean init_network_game, boolean record_tape,
11515 unsigned int new_random_seed = InitRND(random_seed);
11518 TapeStartRecording(new_random_seed);
11520 if (init_network_game)
11522 SendToServer_LevelFile();
11523 SendToServer_StartPlaying();
11531 static void GameActionsExt(void)
11534 static unsigned int game_frame_delay = 0;
11536 unsigned int game_frame_delay_value;
11537 byte *recorded_player_action;
11538 byte summarized_player_action = 0;
11539 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11542 // detect endless loops, caused by custom element programming
11543 if (recursion_loop_detected && recursion_loop_depth == 0)
11545 char *message = getStringCat3("Internal Error! Element ",
11546 EL_NAME(recursion_loop_element),
11547 " caused endless loop! Quit the game?");
11549 Warn("element '%s' caused endless loop in game engine",
11550 EL_NAME(recursion_loop_element));
11552 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11554 recursion_loop_detected = FALSE; // if game should be continued
11561 if (game.restart_level)
11562 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11564 CheckLevelSolved();
11566 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11569 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11572 if (game_status != GAME_MODE_PLAYING) // status might have changed
11575 game_frame_delay_value =
11576 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11578 if (tape.playing && tape.warp_forward && !tape.pausing)
11579 game_frame_delay_value = 0;
11581 SetVideoFrameDelay(game_frame_delay_value);
11583 // (de)activate virtual buttons depending on current game status
11584 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11586 if (game.all_players_gone) // if no players there to be controlled anymore
11587 SetOverlayActive(FALSE);
11588 else if (!tape.playing) // if game continues after tape stopped playing
11589 SetOverlayActive(TRUE);
11594 // ---------- main game synchronization point ----------
11596 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11598 Debug("game:playing:skip", "skip == %d", skip);
11601 // ---------- main game synchronization point ----------
11603 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11607 if (network_playing && !network_player_action_received)
11609 // try to get network player actions in time
11611 // last chance to get network player actions without main loop delay
11612 HandleNetworking();
11614 // game was quit by network peer
11615 if (game_status != GAME_MODE_PLAYING)
11618 // check if network player actions still missing and game still running
11619 if (!network_player_action_received && !checkGameEnded())
11620 return; // failed to get network player actions in time
11622 // do not yet reset "network_player_action_received" (for tape.pausing)
11628 // at this point we know that we really continue executing the game
11630 network_player_action_received = FALSE;
11632 // when playing tape, read previously recorded player input from tape data
11633 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11635 local_player->effective_mouse_action = local_player->mouse_action;
11637 if (recorded_player_action != NULL)
11638 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11639 recorded_player_action);
11641 // TapePlayAction() may return NULL when toggling to "pause before death"
11645 if (tape.set_centered_player)
11647 game.centered_player_nr_next = tape.centered_player_nr_next;
11648 game.set_centered_player = TRUE;
11651 for (i = 0; i < MAX_PLAYERS; i++)
11653 summarized_player_action |= stored_player[i].action;
11655 if (!network_playing && (game.team_mode || tape.playing))
11656 stored_player[i].effective_action = stored_player[i].action;
11659 if (network_playing && !checkGameEnded())
11660 SendToServer_MovePlayer(summarized_player_action);
11662 // summarize all actions at local players mapped input device position
11663 // (this allows using different input devices in single player mode)
11664 if (!network.enabled && !game.team_mode)
11665 stored_player[map_player_action[local_player->index_nr]].effective_action =
11666 summarized_player_action;
11668 // summarize all actions at centered player in local team mode
11669 if (tape.recording &&
11670 setup.team_mode && !network.enabled &&
11671 setup.input_on_focus &&
11672 game.centered_player_nr != -1)
11674 for (i = 0; i < MAX_PLAYERS; i++)
11675 stored_player[map_player_action[i]].effective_action =
11676 (i == game.centered_player_nr ? summarized_player_action : 0);
11679 if (recorded_player_action != NULL)
11680 for (i = 0; i < MAX_PLAYERS; i++)
11681 stored_player[i].effective_action = recorded_player_action[i];
11683 for (i = 0; i < MAX_PLAYERS; i++)
11685 tape_action[i] = stored_player[i].effective_action;
11687 /* (this may happen in the RND game engine if a player was not present on
11688 the playfield on level start, but appeared later from a custom element */
11689 if (setup.team_mode &&
11692 !tape.player_participates[i])
11693 tape.player_participates[i] = TRUE;
11696 SetTapeActionFromMouseAction(tape_action,
11697 &local_player->effective_mouse_action);
11699 // only record actions from input devices, but not programmed actions
11700 if (tape.recording)
11701 TapeRecordAction(tape_action);
11703 // remember if game was played (especially after tape stopped playing)
11704 if (!tape.playing && summarized_player_action)
11705 game.GamePlayed = TRUE;
11707 #if USE_NEW_PLAYER_ASSIGNMENTS
11708 // !!! also map player actions in single player mode !!!
11709 // if (game.team_mode)
11712 byte mapped_action[MAX_PLAYERS];
11714 #if DEBUG_PLAYER_ACTIONS
11715 for (i = 0; i < MAX_PLAYERS; i++)
11716 DebugContinued("", "%d, ", stored_player[i].effective_action);
11719 for (i = 0; i < MAX_PLAYERS; i++)
11720 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11722 for (i = 0; i < MAX_PLAYERS; i++)
11723 stored_player[i].effective_action = mapped_action[i];
11725 #if DEBUG_PLAYER_ACTIONS
11726 DebugContinued("", "=> ");
11727 for (i = 0; i < MAX_PLAYERS; i++)
11728 DebugContinued("", "%d, ", stored_player[i].effective_action);
11729 DebugContinued("game:playing:player", "\n");
11732 #if DEBUG_PLAYER_ACTIONS
11735 for (i = 0; i < MAX_PLAYERS; i++)
11736 DebugContinued("", "%d, ", stored_player[i].effective_action);
11737 DebugContinued("game:playing:player", "\n");
11742 for (i = 0; i < MAX_PLAYERS; i++)
11744 // allow engine snapshot in case of changed movement attempt
11745 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11746 (stored_player[i].effective_action & KEY_MOTION))
11747 game.snapshot.changed_action = TRUE;
11749 // allow engine snapshot in case of snapping/dropping attempt
11750 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11751 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11752 game.snapshot.changed_action = TRUE;
11754 game.snapshot.last_action[i] = stored_player[i].effective_action;
11757 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11759 GameActions_EM_Main();
11761 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11763 GameActions_SP_Main();
11765 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11767 GameActions_MM_Main();
11771 GameActions_RND_Main();
11774 BlitScreenToBitmap(backbuffer);
11776 CheckLevelSolved();
11779 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11781 if (global.show_frames_per_second)
11783 static unsigned int fps_counter = 0;
11784 static int fps_frames = 0;
11785 unsigned int fps_delay_ms = Counter() - fps_counter;
11789 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11791 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11794 fps_counter = Counter();
11796 // always draw FPS to screen after FPS value was updated
11797 redraw_mask |= REDRAW_FPS;
11800 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11801 if (GetDrawDeactivationMask() == REDRAW_NONE)
11802 redraw_mask |= REDRAW_FPS;
11806 static void GameActions_CheckSaveEngineSnapshot(void)
11808 if (!game.snapshot.save_snapshot)
11811 // clear flag for saving snapshot _before_ saving snapshot
11812 game.snapshot.save_snapshot = FALSE;
11814 SaveEngineSnapshotToList();
11817 void GameActions(void)
11821 GameActions_CheckSaveEngineSnapshot();
11824 void GameActions_EM_Main(void)
11826 byte effective_action[MAX_PLAYERS];
11827 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11830 for (i = 0; i < MAX_PLAYERS; i++)
11831 effective_action[i] = stored_player[i].effective_action;
11833 GameActions_EM(effective_action, warp_mode);
11836 void GameActions_SP_Main(void)
11838 byte effective_action[MAX_PLAYERS];
11839 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11842 for (i = 0; i < MAX_PLAYERS; i++)
11843 effective_action[i] = stored_player[i].effective_action;
11845 GameActions_SP(effective_action, warp_mode);
11847 for (i = 0; i < MAX_PLAYERS; i++)
11849 if (stored_player[i].force_dropping)
11850 stored_player[i].action |= KEY_BUTTON_DROP;
11852 stored_player[i].force_dropping = FALSE;
11856 void GameActions_MM_Main(void)
11858 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11860 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11863 void GameActions_RND_Main(void)
11868 void GameActions_RND(void)
11870 static struct MouseActionInfo mouse_action_last = { 0 };
11871 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11872 int magic_wall_x = 0, magic_wall_y = 0;
11873 int i, x, y, element, graphic, last_gfx_frame;
11875 InitPlayfieldScanModeVars();
11877 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11879 SCAN_PLAYFIELD(x, y)
11881 ChangeCount[x][y] = 0;
11882 ChangeEvent[x][y] = -1;
11886 if (game.set_centered_player)
11888 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11890 // switching to "all players" only possible if all players fit to screen
11891 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11893 game.centered_player_nr_next = game.centered_player_nr;
11894 game.set_centered_player = FALSE;
11897 // do not switch focus to non-existing (or non-active) player
11898 if (game.centered_player_nr_next >= 0 &&
11899 !stored_player[game.centered_player_nr_next].active)
11901 game.centered_player_nr_next = game.centered_player_nr;
11902 game.set_centered_player = FALSE;
11906 if (game.set_centered_player &&
11907 ScreenMovPos == 0) // screen currently aligned at tile position
11911 if (game.centered_player_nr_next == -1)
11913 setScreenCenteredToAllPlayers(&sx, &sy);
11917 sx = stored_player[game.centered_player_nr_next].jx;
11918 sy = stored_player[game.centered_player_nr_next].jy;
11921 game.centered_player_nr = game.centered_player_nr_next;
11922 game.set_centered_player = FALSE;
11924 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11925 DrawGameDoorValues();
11928 for (i = 0; i < MAX_PLAYERS; i++)
11930 int actual_player_action = stored_player[i].effective_action;
11933 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11934 - rnd_equinox_tetrachloride 048
11935 - rnd_equinox_tetrachloride_ii 096
11936 - rnd_emanuel_schmieg 002
11937 - doctor_sloan_ww 001, 020
11939 if (stored_player[i].MovPos == 0)
11940 CheckGravityMovement(&stored_player[i]);
11943 // overwrite programmed action with tape action
11944 if (stored_player[i].programmed_action)
11945 actual_player_action = stored_player[i].programmed_action;
11947 PlayerActions(&stored_player[i], actual_player_action);
11949 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11952 ScrollScreen(NULL, SCROLL_GO_ON);
11954 /* for backwards compatibility, the following code emulates a fixed bug that
11955 occured when pushing elements (causing elements that just made their last
11956 pushing step to already (if possible) make their first falling step in the
11957 same game frame, which is bad); this code is also needed to use the famous
11958 "spring push bug" which is used in older levels and might be wanted to be
11959 used also in newer levels, but in this case the buggy pushing code is only
11960 affecting the "spring" element and no other elements */
11962 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11964 for (i = 0; i < MAX_PLAYERS; i++)
11966 struct PlayerInfo *player = &stored_player[i];
11967 int x = player->jx;
11968 int y = player->jy;
11970 if (player->active && player->is_pushing && player->is_moving &&
11972 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11973 Tile[x][y] == EL_SPRING))
11975 ContinueMoving(x, y);
11977 // continue moving after pushing (this is actually a bug)
11978 if (!IS_MOVING(x, y))
11979 Stop[x][y] = FALSE;
11984 SCAN_PLAYFIELD(x, y)
11986 Last[x][y] = Tile[x][y];
11988 ChangeCount[x][y] = 0;
11989 ChangeEvent[x][y] = -1;
11991 // this must be handled before main playfield loop
11992 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
11995 if (MovDelay[x][y] <= 0)
11999 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12002 if (MovDelay[x][y] <= 0)
12005 TEST_DrawLevelField(x, y);
12007 TestIfElementTouchesCustomElement(x, y); // for empty space
12012 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12014 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12016 Debug("game:playing:GameActions_RND", "This should never happen!");
12018 ChangePage[x][y] = -1;
12022 Stop[x][y] = FALSE;
12023 if (WasJustMoving[x][y] > 0)
12024 WasJustMoving[x][y]--;
12025 if (WasJustFalling[x][y] > 0)
12026 WasJustFalling[x][y]--;
12027 if (CheckCollision[x][y] > 0)
12028 CheckCollision[x][y]--;
12029 if (CheckImpact[x][y] > 0)
12030 CheckImpact[x][y]--;
12034 /* reset finished pushing action (not done in ContinueMoving() to allow
12035 continuous pushing animation for elements with zero push delay) */
12036 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12038 ResetGfxAnimation(x, y);
12039 TEST_DrawLevelField(x, y);
12043 if (IS_BLOCKED(x, y))
12047 Blocked2Moving(x, y, &oldx, &oldy);
12048 if (!IS_MOVING(oldx, oldy))
12050 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12051 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12052 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12053 Debug("game:playing:GameActions_RND", "This should never happen!");
12059 if (mouse_action.button)
12061 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12063 x = mouse_action.lx;
12064 y = mouse_action.ly;
12065 element = Tile[x][y];
12069 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12070 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12073 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12074 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12077 SCAN_PLAYFIELD(x, y)
12079 element = Tile[x][y];
12080 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12081 last_gfx_frame = GfxFrame[x][y];
12083 ResetGfxFrame(x, y);
12085 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12086 DrawLevelGraphicAnimation(x, y, graphic);
12088 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12089 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12090 ResetRandomAnimationValue(x, y);
12092 SetRandomAnimationValue(x, y);
12094 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12096 if (IS_INACTIVE(element))
12098 if (IS_ANIMATED(graphic))
12099 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12104 // this may take place after moving, so 'element' may have changed
12105 if (IS_CHANGING(x, y) &&
12106 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12108 int page = element_info[element].event_page_nr[CE_DELAY];
12110 HandleElementChange(x, y, page);
12112 element = Tile[x][y];
12113 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12116 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12120 element = Tile[x][y];
12121 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12123 if (IS_ANIMATED(graphic) &&
12124 !IS_MOVING(x, y) &&
12126 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12128 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12129 TEST_DrawTwinkleOnField(x, y);
12131 else if (element == EL_ACID)
12134 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12136 else if ((element == EL_EXIT_OPEN ||
12137 element == EL_EM_EXIT_OPEN ||
12138 element == EL_SP_EXIT_OPEN ||
12139 element == EL_STEEL_EXIT_OPEN ||
12140 element == EL_EM_STEEL_EXIT_OPEN ||
12141 element == EL_SP_TERMINAL ||
12142 element == EL_SP_TERMINAL_ACTIVE ||
12143 element == EL_EXTRA_TIME ||
12144 element == EL_SHIELD_NORMAL ||
12145 element == EL_SHIELD_DEADLY) &&
12146 IS_ANIMATED(graphic))
12147 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12148 else if (IS_MOVING(x, y))
12149 ContinueMoving(x, y);
12150 else if (IS_ACTIVE_BOMB(element))
12151 CheckDynamite(x, y);
12152 else if (element == EL_AMOEBA_GROWING)
12153 AmoebaGrowing(x, y);
12154 else if (element == EL_AMOEBA_SHRINKING)
12155 AmoebaShrinking(x, y);
12157 #if !USE_NEW_AMOEBA_CODE
12158 else if (IS_AMOEBALIVE(element))
12159 AmoebaReproduce(x, y);
12162 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12164 else if (element == EL_EXIT_CLOSED)
12166 else if (element == EL_EM_EXIT_CLOSED)
12168 else if (element == EL_STEEL_EXIT_CLOSED)
12169 CheckExitSteel(x, y);
12170 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12171 CheckExitSteelEM(x, y);
12172 else if (element == EL_SP_EXIT_CLOSED)
12174 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12175 element == EL_EXPANDABLE_STEELWALL_GROWING)
12176 MauerWaechst(x, y);
12177 else if (element == EL_EXPANDABLE_WALL ||
12178 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12179 element == EL_EXPANDABLE_WALL_VERTICAL ||
12180 element == EL_EXPANDABLE_WALL_ANY ||
12181 element == EL_BD_EXPANDABLE_WALL)
12182 MauerAbleger(x, y);
12183 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12184 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12185 element == EL_EXPANDABLE_STEELWALL_ANY)
12186 MauerAblegerStahl(x, y);
12187 else if (element == EL_FLAMES)
12188 CheckForDragon(x, y);
12189 else if (element == EL_EXPLOSION)
12190 ; // drawing of correct explosion animation is handled separately
12191 else if (element == EL_ELEMENT_SNAPPING ||
12192 element == EL_DIAGONAL_SHRINKING ||
12193 element == EL_DIAGONAL_GROWING)
12195 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12197 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12199 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12200 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12202 if (IS_BELT_ACTIVE(element))
12203 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12205 if (game.magic_wall_active)
12207 int jx = local_player->jx, jy = local_player->jy;
12209 // play the element sound at the position nearest to the player
12210 if ((element == EL_MAGIC_WALL_FULL ||
12211 element == EL_MAGIC_WALL_ACTIVE ||
12212 element == EL_MAGIC_WALL_EMPTYING ||
12213 element == EL_BD_MAGIC_WALL_FULL ||
12214 element == EL_BD_MAGIC_WALL_ACTIVE ||
12215 element == EL_BD_MAGIC_WALL_EMPTYING ||
12216 element == EL_DC_MAGIC_WALL_FULL ||
12217 element == EL_DC_MAGIC_WALL_ACTIVE ||
12218 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12219 ABS(x - jx) + ABS(y - jy) <
12220 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12228 #if USE_NEW_AMOEBA_CODE
12229 // new experimental amoeba growth stuff
12230 if (!(FrameCounter % 8))
12232 static unsigned int random = 1684108901;
12234 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12236 x = RND(lev_fieldx);
12237 y = RND(lev_fieldy);
12238 element = Tile[x][y];
12240 if (!IS_PLAYER(x,y) &&
12241 (element == EL_EMPTY ||
12242 CAN_GROW_INTO(element) ||
12243 element == EL_QUICKSAND_EMPTY ||
12244 element == EL_QUICKSAND_FAST_EMPTY ||
12245 element == EL_ACID_SPLASH_LEFT ||
12246 element == EL_ACID_SPLASH_RIGHT))
12248 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12249 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12250 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12251 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12252 Tile[x][y] = EL_AMOEBA_DROP;
12255 random = random * 129 + 1;
12260 game.explosions_delayed = FALSE;
12262 SCAN_PLAYFIELD(x, y)
12264 element = Tile[x][y];
12266 if (ExplodeField[x][y])
12267 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12268 else if (element == EL_EXPLOSION)
12269 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12271 ExplodeField[x][y] = EX_TYPE_NONE;
12274 game.explosions_delayed = TRUE;
12276 if (game.magic_wall_active)
12278 if (!(game.magic_wall_time_left % 4))
12280 int element = Tile[magic_wall_x][magic_wall_y];
12282 if (element == EL_BD_MAGIC_WALL_FULL ||
12283 element == EL_BD_MAGIC_WALL_ACTIVE ||
12284 element == EL_BD_MAGIC_WALL_EMPTYING)
12285 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12286 else if (element == EL_DC_MAGIC_WALL_FULL ||
12287 element == EL_DC_MAGIC_WALL_ACTIVE ||
12288 element == EL_DC_MAGIC_WALL_EMPTYING)
12289 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12291 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12294 if (game.magic_wall_time_left > 0)
12296 game.magic_wall_time_left--;
12298 if (!game.magic_wall_time_left)
12300 SCAN_PLAYFIELD(x, y)
12302 element = Tile[x][y];
12304 if (element == EL_MAGIC_WALL_ACTIVE ||
12305 element == EL_MAGIC_WALL_FULL)
12307 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12308 TEST_DrawLevelField(x, y);
12310 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12311 element == EL_BD_MAGIC_WALL_FULL)
12313 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12314 TEST_DrawLevelField(x, y);
12316 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12317 element == EL_DC_MAGIC_WALL_FULL)
12319 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12320 TEST_DrawLevelField(x, y);
12324 game.magic_wall_active = FALSE;
12329 if (game.light_time_left > 0)
12331 game.light_time_left--;
12333 if (game.light_time_left == 0)
12334 RedrawAllLightSwitchesAndInvisibleElements();
12337 if (game.timegate_time_left > 0)
12339 game.timegate_time_left--;
12341 if (game.timegate_time_left == 0)
12342 CloseAllOpenTimegates();
12345 if (game.lenses_time_left > 0)
12347 game.lenses_time_left--;
12349 if (game.lenses_time_left == 0)
12350 RedrawAllInvisibleElementsForLenses();
12353 if (game.magnify_time_left > 0)
12355 game.magnify_time_left--;
12357 if (game.magnify_time_left == 0)
12358 RedrawAllInvisibleElementsForMagnifier();
12361 for (i = 0; i < MAX_PLAYERS; i++)
12363 struct PlayerInfo *player = &stored_player[i];
12365 if (SHIELD_ON(player))
12367 if (player->shield_deadly_time_left)
12368 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12369 else if (player->shield_normal_time_left)
12370 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12374 #if USE_DELAYED_GFX_REDRAW
12375 SCAN_PLAYFIELD(x, y)
12377 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12379 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12380 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12382 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12383 DrawLevelField(x, y);
12385 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12386 DrawLevelFieldCrumbled(x, y);
12388 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12389 DrawLevelFieldCrumbledNeighbours(x, y);
12391 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12392 DrawTwinkleOnField(x, y);
12395 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12400 PlayAllPlayersSound();
12402 for (i = 0; i < MAX_PLAYERS; i++)
12404 struct PlayerInfo *player = &stored_player[i];
12406 if (player->show_envelope != 0 && (!player->active ||
12407 player->MovPos == 0))
12409 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12411 player->show_envelope = 0;
12415 // use random number generator in every frame to make it less predictable
12416 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12419 mouse_action_last = mouse_action;
12422 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12424 int min_x = x, min_y = y, max_x = x, max_y = y;
12427 for (i = 0; i < MAX_PLAYERS; i++)
12429 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12431 if (!stored_player[i].active || &stored_player[i] == player)
12434 min_x = MIN(min_x, jx);
12435 min_y = MIN(min_y, jy);
12436 max_x = MAX(max_x, jx);
12437 max_y = MAX(max_y, jy);
12440 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12443 static boolean AllPlayersInVisibleScreen(void)
12447 for (i = 0; i < MAX_PLAYERS; i++)
12449 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12451 if (!stored_player[i].active)
12454 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12461 void ScrollLevel(int dx, int dy)
12463 int scroll_offset = 2 * TILEX_VAR;
12466 BlitBitmap(drawto_field, drawto_field,
12467 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12468 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12469 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12470 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12471 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12472 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12476 x = (dx == 1 ? BX1 : BX2);
12477 for (y = BY1; y <= BY2; y++)
12478 DrawScreenField(x, y);
12483 y = (dy == 1 ? BY1 : BY2);
12484 for (x = BX1; x <= BX2; x++)
12485 DrawScreenField(x, y);
12488 redraw_mask |= REDRAW_FIELD;
12491 static boolean canFallDown(struct PlayerInfo *player)
12493 int jx = player->jx, jy = player->jy;
12495 return (IN_LEV_FIELD(jx, jy + 1) &&
12496 (IS_FREE(jx, jy + 1) ||
12497 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12498 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12499 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12502 static boolean canPassField(int x, int y, int move_dir)
12504 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12505 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12506 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12507 int nextx = x + dx;
12508 int nexty = y + dy;
12509 int element = Tile[x][y];
12511 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12512 !CAN_MOVE(element) &&
12513 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12514 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12515 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12518 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12520 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12521 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12522 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12526 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12527 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12528 (IS_DIGGABLE(Tile[newx][newy]) ||
12529 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12530 canPassField(newx, newy, move_dir)));
12533 static void CheckGravityMovement(struct PlayerInfo *player)
12535 if (player->gravity && !player->programmed_action)
12537 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12538 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12539 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12540 int jx = player->jx, jy = player->jy;
12541 boolean player_is_moving_to_valid_field =
12542 (!player_is_snapping &&
12543 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12544 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12545 boolean player_can_fall_down = canFallDown(player);
12547 if (player_can_fall_down &&
12548 !player_is_moving_to_valid_field)
12549 player->programmed_action = MV_DOWN;
12553 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12555 return CheckGravityMovement(player);
12557 if (player->gravity && !player->programmed_action)
12559 int jx = player->jx, jy = player->jy;
12560 boolean field_under_player_is_free =
12561 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12562 boolean player_is_standing_on_valid_field =
12563 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12564 (IS_WALKABLE(Tile[jx][jy]) &&
12565 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12567 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12568 player->programmed_action = MV_DOWN;
12573 MovePlayerOneStep()
12574 -----------------------------------------------------------------------------
12575 dx, dy: direction (non-diagonal) to try to move the player to
12576 real_dx, real_dy: direction as read from input device (can be diagonal)
12579 boolean MovePlayerOneStep(struct PlayerInfo *player,
12580 int dx, int dy, int real_dx, int real_dy)
12582 int jx = player->jx, jy = player->jy;
12583 int new_jx = jx + dx, new_jy = jy + dy;
12585 boolean player_can_move = !player->cannot_move;
12587 if (!player->active || (!dx && !dy))
12588 return MP_NO_ACTION;
12590 player->MovDir = (dx < 0 ? MV_LEFT :
12591 dx > 0 ? MV_RIGHT :
12593 dy > 0 ? MV_DOWN : MV_NONE);
12595 if (!IN_LEV_FIELD(new_jx, new_jy))
12596 return MP_NO_ACTION;
12598 if (!player_can_move)
12600 if (player->MovPos == 0)
12602 player->is_moving = FALSE;
12603 player->is_digging = FALSE;
12604 player->is_collecting = FALSE;
12605 player->is_snapping = FALSE;
12606 player->is_pushing = FALSE;
12610 if (!network.enabled && game.centered_player_nr == -1 &&
12611 !AllPlayersInSight(player, new_jx, new_jy))
12612 return MP_NO_ACTION;
12614 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12615 if (can_move != MP_MOVING)
12618 // check if DigField() has caused relocation of the player
12619 if (player->jx != jx || player->jy != jy)
12620 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12622 StorePlayer[jx][jy] = 0;
12623 player->last_jx = jx;
12624 player->last_jy = jy;
12625 player->jx = new_jx;
12626 player->jy = new_jy;
12627 StorePlayer[new_jx][new_jy] = player->element_nr;
12629 if (player->move_delay_value_next != -1)
12631 player->move_delay_value = player->move_delay_value_next;
12632 player->move_delay_value_next = -1;
12636 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12638 player->step_counter++;
12640 PlayerVisit[jx][jy] = FrameCounter;
12642 player->is_moving = TRUE;
12645 // should better be called in MovePlayer(), but this breaks some tapes
12646 ScrollPlayer(player, SCROLL_INIT);
12652 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12654 int jx = player->jx, jy = player->jy;
12655 int old_jx = jx, old_jy = jy;
12656 int moved = MP_NO_ACTION;
12658 if (!player->active)
12663 if (player->MovPos == 0)
12665 player->is_moving = FALSE;
12666 player->is_digging = FALSE;
12667 player->is_collecting = FALSE;
12668 player->is_snapping = FALSE;
12669 player->is_pushing = FALSE;
12675 if (player->move_delay > 0)
12678 player->move_delay = -1; // set to "uninitialized" value
12680 // store if player is automatically moved to next field
12681 player->is_auto_moving = (player->programmed_action != MV_NONE);
12683 // remove the last programmed player action
12684 player->programmed_action = 0;
12686 if (player->MovPos)
12688 // should only happen if pre-1.2 tape recordings are played
12689 // this is only for backward compatibility
12691 int original_move_delay_value = player->move_delay_value;
12694 Debug("game:playing:MovePlayer",
12695 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12699 // scroll remaining steps with finest movement resolution
12700 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12702 while (player->MovPos)
12704 ScrollPlayer(player, SCROLL_GO_ON);
12705 ScrollScreen(NULL, SCROLL_GO_ON);
12707 AdvanceFrameAndPlayerCounters(player->index_nr);
12710 BackToFront_WithFrameDelay(0);
12713 player->move_delay_value = original_move_delay_value;
12716 player->is_active = FALSE;
12718 if (player->last_move_dir & MV_HORIZONTAL)
12720 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12721 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12725 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12726 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12729 if (!moved && !player->is_active)
12731 player->is_moving = FALSE;
12732 player->is_digging = FALSE;
12733 player->is_collecting = FALSE;
12734 player->is_snapping = FALSE;
12735 player->is_pushing = FALSE;
12741 if (moved & MP_MOVING && !ScreenMovPos &&
12742 (player->index_nr == game.centered_player_nr ||
12743 game.centered_player_nr == -1))
12745 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12747 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12749 // actual player has left the screen -- scroll in that direction
12750 if (jx != old_jx) // player has moved horizontally
12751 scroll_x += (jx - old_jx);
12752 else // player has moved vertically
12753 scroll_y += (jy - old_jy);
12757 int offset_raw = game.scroll_delay_value;
12759 if (jx != old_jx) // player has moved horizontally
12761 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12762 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12763 int new_scroll_x = jx - MIDPOSX + offset_x;
12765 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12766 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12767 scroll_x = new_scroll_x;
12769 // don't scroll over playfield boundaries
12770 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12772 // don't scroll more than one field at a time
12773 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12775 // don't scroll against the player's moving direction
12776 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12777 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12778 scroll_x = old_scroll_x;
12780 else // player has moved vertically
12782 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12783 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12784 int new_scroll_y = jy - MIDPOSY + offset_y;
12786 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12787 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12788 scroll_y = new_scroll_y;
12790 // don't scroll over playfield boundaries
12791 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12793 // don't scroll more than one field at a time
12794 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12796 // don't scroll against the player's moving direction
12797 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12798 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12799 scroll_y = old_scroll_y;
12803 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12805 if (!network.enabled && game.centered_player_nr == -1 &&
12806 !AllPlayersInVisibleScreen())
12808 scroll_x = old_scroll_x;
12809 scroll_y = old_scroll_y;
12813 ScrollScreen(player, SCROLL_INIT);
12814 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12819 player->StepFrame = 0;
12821 if (moved & MP_MOVING)
12823 if (old_jx != jx && old_jy == jy)
12824 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12825 else if (old_jx == jx && old_jy != jy)
12826 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12828 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12830 player->last_move_dir = player->MovDir;
12831 player->is_moving = TRUE;
12832 player->is_snapping = FALSE;
12833 player->is_switching = FALSE;
12834 player->is_dropping = FALSE;
12835 player->is_dropping_pressed = FALSE;
12836 player->drop_pressed_delay = 0;
12839 // should better be called here than above, but this breaks some tapes
12840 ScrollPlayer(player, SCROLL_INIT);
12845 CheckGravityMovementWhenNotMoving(player);
12847 player->is_moving = FALSE;
12849 /* at this point, the player is allowed to move, but cannot move right now
12850 (e.g. because of something blocking the way) -- ensure that the player
12851 is also allowed to move in the next frame (in old versions before 3.1.1,
12852 the player was forced to wait again for eight frames before next try) */
12854 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12855 player->move_delay = 0; // allow direct movement in the next frame
12858 if (player->move_delay == -1) // not yet initialized by DigField()
12859 player->move_delay = player->move_delay_value;
12861 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12863 TestIfPlayerTouchesBadThing(jx, jy);
12864 TestIfPlayerTouchesCustomElement(jx, jy);
12867 if (!player->active)
12868 RemovePlayer(player);
12873 void ScrollPlayer(struct PlayerInfo *player, int mode)
12875 int jx = player->jx, jy = player->jy;
12876 int last_jx = player->last_jx, last_jy = player->last_jy;
12877 int move_stepsize = TILEX / player->move_delay_value;
12879 if (!player->active)
12882 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12885 if (mode == SCROLL_INIT)
12887 player->actual_frame_counter = FrameCounter;
12888 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12890 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12891 Tile[last_jx][last_jy] == EL_EMPTY)
12893 int last_field_block_delay = 0; // start with no blocking at all
12894 int block_delay_adjustment = player->block_delay_adjustment;
12896 // if player blocks last field, add delay for exactly one move
12897 if (player->block_last_field)
12899 last_field_block_delay += player->move_delay_value;
12901 // when blocking enabled, prevent moving up despite gravity
12902 if (player->gravity && player->MovDir == MV_UP)
12903 block_delay_adjustment = -1;
12906 // add block delay adjustment (also possible when not blocking)
12907 last_field_block_delay += block_delay_adjustment;
12909 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12910 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12913 if (player->MovPos != 0) // player has not yet reached destination
12916 else if (!FrameReached(&player->actual_frame_counter, 1))
12919 if (player->MovPos != 0)
12921 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12922 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12924 // before DrawPlayer() to draw correct player graphic for this case
12925 if (player->MovPos == 0)
12926 CheckGravityMovement(player);
12929 if (player->MovPos == 0) // player reached destination field
12931 if (player->move_delay_reset_counter > 0)
12933 player->move_delay_reset_counter--;
12935 if (player->move_delay_reset_counter == 0)
12937 // continue with normal speed after quickly moving through gate
12938 HALVE_PLAYER_SPEED(player);
12940 // be able to make the next move without delay
12941 player->move_delay = 0;
12945 player->last_jx = jx;
12946 player->last_jy = jy;
12948 if (Tile[jx][jy] == EL_EXIT_OPEN ||
12949 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
12950 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
12951 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
12952 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12953 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12954 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
12955 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12957 ExitPlayer(player);
12959 if (game.players_still_needed == 0 &&
12960 (game.friends_still_needed == 0 ||
12961 IS_SP_ELEMENT(Tile[jx][jy])))
12965 // this breaks one level: "machine", level 000
12967 int move_direction = player->MovDir;
12968 int enter_side = MV_DIR_OPPOSITE(move_direction);
12969 int leave_side = move_direction;
12970 int old_jx = last_jx;
12971 int old_jy = last_jy;
12972 int old_element = Tile[old_jx][old_jy];
12973 int new_element = Tile[jx][jy];
12975 if (IS_CUSTOM_ELEMENT(old_element))
12976 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12978 player->index_bit, leave_side);
12980 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12981 CE_PLAYER_LEAVES_X,
12982 player->index_bit, leave_side);
12984 if (IS_CUSTOM_ELEMENT(new_element))
12985 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12986 player->index_bit, enter_side);
12988 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12989 CE_PLAYER_ENTERS_X,
12990 player->index_bit, enter_side);
12992 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12993 CE_MOVE_OF_X, move_direction);
12996 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12998 TestIfPlayerTouchesBadThing(jx, jy);
12999 TestIfPlayerTouchesCustomElement(jx, jy);
13001 /* needed because pushed element has not yet reached its destination,
13002 so it would trigger a change event at its previous field location */
13003 if (!player->is_pushing)
13004 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13006 if (!player->active)
13007 RemovePlayer(player);
13010 if (!game.LevelSolved && level.use_step_counter)
13020 if (TimeLeft <= 10 && setup.time_limit)
13021 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13023 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13025 DisplayGameControlValues();
13027 if (!TimeLeft && setup.time_limit)
13028 for (i = 0; i < MAX_PLAYERS; i++)
13029 KillPlayer(&stored_player[i]);
13031 else if (game.no_time_limit && !game.all_players_gone)
13033 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13035 DisplayGameControlValues();
13039 if (tape.single_step && tape.recording && !tape.pausing &&
13040 !player->programmed_action)
13041 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13043 if (!player->programmed_action)
13044 CheckSaveEngineSnapshot(player);
13048 void ScrollScreen(struct PlayerInfo *player, int mode)
13050 static unsigned int screen_frame_counter = 0;
13052 if (mode == SCROLL_INIT)
13054 // set scrolling step size according to actual player's moving speed
13055 ScrollStepSize = TILEX / player->move_delay_value;
13057 screen_frame_counter = FrameCounter;
13058 ScreenMovDir = player->MovDir;
13059 ScreenMovPos = player->MovPos;
13060 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13063 else if (!FrameReached(&screen_frame_counter, 1))
13068 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13069 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13070 redraw_mask |= REDRAW_FIELD;
13073 ScreenMovDir = MV_NONE;
13076 void TestIfPlayerTouchesCustomElement(int x, int y)
13078 static int xy[4][2] =
13085 static int trigger_sides[4][2] =
13087 // center side border side
13088 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13089 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13090 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13091 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13093 static int touch_dir[4] =
13095 MV_LEFT | MV_RIGHT,
13100 int center_element = Tile[x][y]; // should always be non-moving!
13103 for (i = 0; i < NUM_DIRECTIONS; i++)
13105 int xx = x + xy[i][0];
13106 int yy = y + xy[i][1];
13107 int center_side = trigger_sides[i][0];
13108 int border_side = trigger_sides[i][1];
13109 int border_element;
13111 if (!IN_LEV_FIELD(xx, yy))
13114 if (IS_PLAYER(x, y)) // player found at center element
13116 struct PlayerInfo *player = PLAYERINFO(x, y);
13118 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13119 border_element = Tile[xx][yy]; // may be moving!
13120 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13121 border_element = Tile[xx][yy];
13122 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13123 border_element = MovingOrBlocked2Element(xx, yy);
13125 continue; // center and border element do not touch
13127 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13128 player->index_bit, border_side);
13129 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13130 CE_PLAYER_TOUCHES_X,
13131 player->index_bit, border_side);
13134 /* use player element that is initially defined in the level playfield,
13135 not the player element that corresponds to the runtime player number
13136 (example: a level that contains EL_PLAYER_3 as the only player would
13137 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13138 int player_element = PLAYERINFO(x, y)->initial_element;
13140 CheckElementChangeBySide(xx, yy, border_element, player_element,
13141 CE_TOUCHING_X, border_side);
13144 else if (IS_PLAYER(xx, yy)) // player found at border element
13146 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13148 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13150 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13151 continue; // center and border element do not touch
13154 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13155 player->index_bit, center_side);
13156 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13157 CE_PLAYER_TOUCHES_X,
13158 player->index_bit, center_side);
13161 /* use player element that is initially defined in the level playfield,
13162 not the player element that corresponds to the runtime player number
13163 (example: a level that contains EL_PLAYER_3 as the only player would
13164 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13165 int player_element = PLAYERINFO(xx, yy)->initial_element;
13167 CheckElementChangeBySide(x, y, center_element, player_element,
13168 CE_TOUCHING_X, center_side);
13176 void TestIfElementTouchesCustomElement(int x, int y)
13178 static int xy[4][2] =
13185 static int trigger_sides[4][2] =
13187 // center side border side
13188 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13189 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13190 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13191 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13193 static int touch_dir[4] =
13195 MV_LEFT | MV_RIGHT,
13200 boolean change_center_element = FALSE;
13201 int center_element = Tile[x][y]; // should always be non-moving!
13202 int border_element_old[NUM_DIRECTIONS];
13205 for (i = 0; i < NUM_DIRECTIONS; i++)
13207 int xx = x + xy[i][0];
13208 int yy = y + xy[i][1];
13209 int border_element;
13211 border_element_old[i] = -1;
13213 if (!IN_LEV_FIELD(xx, yy))
13216 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13217 border_element = Tile[xx][yy]; // may be moving!
13218 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13219 border_element = Tile[xx][yy];
13220 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13221 border_element = MovingOrBlocked2Element(xx, yy);
13223 continue; // center and border element do not touch
13225 border_element_old[i] = border_element;
13228 for (i = 0; i < NUM_DIRECTIONS; i++)
13230 int xx = x + xy[i][0];
13231 int yy = y + xy[i][1];
13232 int center_side = trigger_sides[i][0];
13233 int border_element = border_element_old[i];
13235 if (border_element == -1)
13238 // check for change of border element
13239 CheckElementChangeBySide(xx, yy, border_element, center_element,
13240 CE_TOUCHING_X, center_side);
13242 // (center element cannot be player, so we dont have to check this here)
13245 for (i = 0; i < NUM_DIRECTIONS; i++)
13247 int xx = x + xy[i][0];
13248 int yy = y + xy[i][1];
13249 int border_side = trigger_sides[i][1];
13250 int border_element = border_element_old[i];
13252 if (border_element == -1)
13255 // check for change of center element (but change it only once)
13256 if (!change_center_element)
13257 change_center_element =
13258 CheckElementChangeBySide(x, y, center_element, border_element,
13259 CE_TOUCHING_X, border_side);
13261 if (IS_PLAYER(xx, yy))
13263 /* use player element that is initially defined in the level playfield,
13264 not the player element that corresponds to the runtime player number
13265 (example: a level that contains EL_PLAYER_3 as the only player would
13266 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13267 int player_element = PLAYERINFO(xx, yy)->initial_element;
13269 CheckElementChangeBySide(x, y, center_element, player_element,
13270 CE_TOUCHING_X, border_side);
13275 void TestIfElementHitsCustomElement(int x, int y, int direction)
13277 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13278 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13279 int hitx = x + dx, hity = y + dy;
13280 int hitting_element = Tile[x][y];
13281 int touched_element;
13283 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13286 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13287 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13289 if (IN_LEV_FIELD(hitx, hity))
13291 int opposite_direction = MV_DIR_OPPOSITE(direction);
13292 int hitting_side = direction;
13293 int touched_side = opposite_direction;
13294 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13295 MovDir[hitx][hity] != direction ||
13296 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13302 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13303 CE_HITTING_X, touched_side);
13305 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13306 CE_HIT_BY_X, hitting_side);
13308 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13309 CE_HIT_BY_SOMETHING, opposite_direction);
13311 if (IS_PLAYER(hitx, hity))
13313 /* use player element that is initially defined in the level playfield,
13314 not the player element that corresponds to the runtime player number
13315 (example: a level that contains EL_PLAYER_3 as the only player would
13316 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13317 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13319 CheckElementChangeBySide(x, y, hitting_element, player_element,
13320 CE_HITTING_X, touched_side);
13325 // "hitting something" is also true when hitting the playfield border
13326 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13327 CE_HITTING_SOMETHING, direction);
13330 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13332 int i, kill_x = -1, kill_y = -1;
13334 int bad_element = -1;
13335 static int test_xy[4][2] =
13342 static int test_dir[4] =
13350 for (i = 0; i < NUM_DIRECTIONS; i++)
13352 int test_x, test_y, test_move_dir, test_element;
13354 test_x = good_x + test_xy[i][0];
13355 test_y = good_y + test_xy[i][1];
13357 if (!IN_LEV_FIELD(test_x, test_y))
13361 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13363 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13365 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13366 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13368 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13369 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13373 bad_element = test_element;
13379 if (kill_x != -1 || kill_y != -1)
13381 if (IS_PLAYER(good_x, good_y))
13383 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13385 if (player->shield_deadly_time_left > 0 &&
13386 !IS_INDESTRUCTIBLE(bad_element))
13387 Bang(kill_x, kill_y);
13388 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13389 KillPlayer(player);
13392 Bang(good_x, good_y);
13396 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13398 int i, kill_x = -1, kill_y = -1;
13399 int bad_element = Tile[bad_x][bad_y];
13400 static int test_xy[4][2] =
13407 static int touch_dir[4] =
13409 MV_LEFT | MV_RIGHT,
13414 static int test_dir[4] =
13422 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13425 for (i = 0; i < NUM_DIRECTIONS; i++)
13427 int test_x, test_y, test_move_dir, test_element;
13429 test_x = bad_x + test_xy[i][0];
13430 test_y = bad_y + test_xy[i][1];
13432 if (!IN_LEV_FIELD(test_x, test_y))
13436 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13438 test_element = Tile[test_x][test_y];
13440 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13441 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13443 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13444 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13446 // good thing is player or penguin that does not move away
13447 if (IS_PLAYER(test_x, test_y))
13449 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13451 if (bad_element == EL_ROBOT && player->is_moving)
13452 continue; // robot does not kill player if he is moving
13454 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13456 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13457 continue; // center and border element do not touch
13465 else if (test_element == EL_PENGUIN)
13475 if (kill_x != -1 || kill_y != -1)
13477 if (IS_PLAYER(kill_x, kill_y))
13479 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13481 if (player->shield_deadly_time_left > 0 &&
13482 !IS_INDESTRUCTIBLE(bad_element))
13483 Bang(bad_x, bad_y);
13484 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13485 KillPlayer(player);
13488 Bang(kill_x, kill_y);
13492 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13494 int bad_element = Tile[bad_x][bad_y];
13495 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13496 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13497 int test_x = bad_x + dx, test_y = bad_y + dy;
13498 int test_move_dir, test_element;
13499 int kill_x = -1, kill_y = -1;
13501 if (!IN_LEV_FIELD(test_x, test_y))
13505 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13507 test_element = Tile[test_x][test_y];
13509 if (test_move_dir != bad_move_dir)
13511 // good thing can be player or penguin that does not move away
13512 if (IS_PLAYER(test_x, test_y))
13514 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13516 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13517 player as being hit when he is moving towards the bad thing, because
13518 the "get hit by" condition would be lost after the player stops) */
13519 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13520 return; // player moves away from bad thing
13525 else if (test_element == EL_PENGUIN)
13532 if (kill_x != -1 || kill_y != -1)
13534 if (IS_PLAYER(kill_x, kill_y))
13536 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13538 if (player->shield_deadly_time_left > 0 &&
13539 !IS_INDESTRUCTIBLE(bad_element))
13540 Bang(bad_x, bad_y);
13541 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13542 KillPlayer(player);
13545 Bang(kill_x, kill_y);
13549 void TestIfPlayerTouchesBadThing(int x, int y)
13551 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13554 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13556 TestIfGoodThingHitsBadThing(x, y, move_dir);
13559 void TestIfBadThingTouchesPlayer(int x, int y)
13561 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13564 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13566 TestIfBadThingHitsGoodThing(x, y, move_dir);
13569 void TestIfFriendTouchesBadThing(int x, int y)
13571 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13574 void TestIfBadThingTouchesFriend(int x, int y)
13576 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13579 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13581 int i, kill_x = bad_x, kill_y = bad_y;
13582 static int xy[4][2] =
13590 for (i = 0; i < NUM_DIRECTIONS; i++)
13594 x = bad_x + xy[i][0];
13595 y = bad_y + xy[i][1];
13596 if (!IN_LEV_FIELD(x, y))
13599 element = Tile[x][y];
13600 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13601 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13609 if (kill_x != bad_x || kill_y != bad_y)
13610 Bang(bad_x, bad_y);
13613 void KillPlayer(struct PlayerInfo *player)
13615 int jx = player->jx, jy = player->jy;
13617 if (!player->active)
13621 Debug("game:playing:KillPlayer",
13622 "0: killed == %d, active == %d, reanimated == %d",
13623 player->killed, player->active, player->reanimated);
13626 /* the following code was introduced to prevent an infinite loop when calling
13628 -> CheckTriggeredElementChangeExt()
13629 -> ExecuteCustomElementAction()
13631 -> (infinitely repeating the above sequence of function calls)
13632 which occurs when killing the player while having a CE with the setting
13633 "kill player X when explosion of <player X>"; the solution using a new
13634 field "player->killed" was chosen for backwards compatibility, although
13635 clever use of the fields "player->active" etc. would probably also work */
13637 if (player->killed)
13641 player->killed = TRUE;
13643 // remove accessible field at the player's position
13644 Tile[jx][jy] = EL_EMPTY;
13646 // deactivate shield (else Bang()/Explode() would not work right)
13647 player->shield_normal_time_left = 0;
13648 player->shield_deadly_time_left = 0;
13651 Debug("game:playing:KillPlayer",
13652 "1: killed == %d, active == %d, reanimated == %d",
13653 player->killed, player->active, player->reanimated);
13659 Debug("game:playing:KillPlayer",
13660 "2: killed == %d, active == %d, reanimated == %d",
13661 player->killed, player->active, player->reanimated);
13664 if (player->reanimated) // killed player may have been reanimated
13665 player->killed = player->reanimated = FALSE;
13667 BuryPlayer(player);
13670 static void KillPlayerUnlessEnemyProtected(int x, int y)
13672 if (!PLAYER_ENEMY_PROTECTED(x, y))
13673 KillPlayer(PLAYERINFO(x, y));
13676 static void KillPlayerUnlessExplosionProtected(int x, int y)
13678 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13679 KillPlayer(PLAYERINFO(x, y));
13682 void BuryPlayer(struct PlayerInfo *player)
13684 int jx = player->jx, jy = player->jy;
13686 if (!player->active)
13689 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13690 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13692 RemovePlayer(player);
13694 player->buried = TRUE;
13696 if (game.all_players_gone)
13697 game.GameOver = TRUE;
13700 void RemovePlayer(struct PlayerInfo *player)
13702 int jx = player->jx, jy = player->jy;
13703 int i, found = FALSE;
13705 player->present = FALSE;
13706 player->active = FALSE;
13708 // required for some CE actions (even if the player is not active anymore)
13709 player->MovPos = 0;
13711 if (!ExplodeField[jx][jy])
13712 StorePlayer[jx][jy] = 0;
13714 if (player->is_moving)
13715 TEST_DrawLevelField(player->last_jx, player->last_jy);
13717 for (i = 0; i < MAX_PLAYERS; i++)
13718 if (stored_player[i].active)
13723 game.all_players_gone = TRUE;
13724 game.GameOver = TRUE;
13727 game.exit_x = game.robot_wheel_x = jx;
13728 game.exit_y = game.robot_wheel_y = jy;
13731 void ExitPlayer(struct PlayerInfo *player)
13733 DrawPlayer(player); // needed here only to cleanup last field
13734 RemovePlayer(player);
13736 if (game.players_still_needed > 0)
13737 game.players_still_needed--;
13740 static void setFieldForSnapping(int x, int y, int element, int direction)
13742 struct ElementInfo *ei = &element_info[element];
13743 int direction_bit = MV_DIR_TO_BIT(direction);
13744 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13745 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13746 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13748 Tile[x][y] = EL_ELEMENT_SNAPPING;
13749 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13751 ResetGfxAnimation(x, y);
13753 GfxElement[x][y] = element;
13754 GfxAction[x][y] = action;
13755 GfxDir[x][y] = direction;
13756 GfxFrame[x][y] = -1;
13760 =============================================================================
13761 checkDiagonalPushing()
13762 -----------------------------------------------------------------------------
13763 check if diagonal input device direction results in pushing of object
13764 (by checking if the alternative direction is walkable, diggable, ...)
13765 =============================================================================
13768 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13769 int x, int y, int real_dx, int real_dy)
13771 int jx, jy, dx, dy, xx, yy;
13773 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13776 // diagonal direction: check alternative direction
13781 xx = jx + (dx == 0 ? real_dx : 0);
13782 yy = jy + (dy == 0 ? real_dy : 0);
13784 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13788 =============================================================================
13790 -----------------------------------------------------------------------------
13791 x, y: field next to player (non-diagonal) to try to dig to
13792 real_dx, real_dy: direction as read from input device (can be diagonal)
13793 =============================================================================
13796 static int DigField(struct PlayerInfo *player,
13797 int oldx, int oldy, int x, int y,
13798 int real_dx, int real_dy, int mode)
13800 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13801 boolean player_was_pushing = player->is_pushing;
13802 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13803 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13804 int jx = oldx, jy = oldy;
13805 int dx = x - jx, dy = y - jy;
13806 int nextx = x + dx, nexty = y + dy;
13807 int move_direction = (dx == -1 ? MV_LEFT :
13808 dx == +1 ? MV_RIGHT :
13810 dy == +1 ? MV_DOWN : MV_NONE);
13811 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13812 int dig_side = MV_DIR_OPPOSITE(move_direction);
13813 int old_element = Tile[jx][jy];
13814 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13817 if (is_player) // function can also be called by EL_PENGUIN
13819 if (player->MovPos == 0)
13821 player->is_digging = FALSE;
13822 player->is_collecting = FALSE;
13825 if (player->MovPos == 0) // last pushing move finished
13826 player->is_pushing = FALSE;
13828 if (mode == DF_NO_PUSH) // player just stopped pushing
13830 player->is_switching = FALSE;
13831 player->push_delay = -1;
13833 return MP_NO_ACTION;
13837 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13838 old_element = Back[jx][jy];
13840 // in case of element dropped at player position, check background
13841 else if (Back[jx][jy] != EL_EMPTY &&
13842 game.engine_version >= VERSION_IDENT(2,2,0,0))
13843 old_element = Back[jx][jy];
13845 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13846 return MP_NO_ACTION; // field has no opening in this direction
13848 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13849 return MP_NO_ACTION; // field has no opening in this direction
13851 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13855 Tile[jx][jy] = player->artwork_element;
13856 InitMovingField(jx, jy, MV_DOWN);
13857 Store[jx][jy] = EL_ACID;
13858 ContinueMoving(jx, jy);
13859 BuryPlayer(player);
13861 return MP_DONT_RUN_INTO;
13864 if (player_can_move && DONT_RUN_INTO(element))
13866 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13868 return MP_DONT_RUN_INTO;
13871 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13872 return MP_NO_ACTION;
13874 collect_count = element_info[element].collect_count_initial;
13876 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13877 return MP_NO_ACTION;
13879 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13880 player_can_move = player_can_move_or_snap;
13882 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13883 game.engine_version >= VERSION_IDENT(2,2,0,0))
13885 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13886 player->index_bit, dig_side);
13887 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13888 player->index_bit, dig_side);
13890 if (element == EL_DC_LANDMINE)
13893 if (Tile[x][y] != element) // field changed by snapping
13896 return MP_NO_ACTION;
13899 if (player->gravity && is_player && !player->is_auto_moving &&
13900 canFallDown(player) && move_direction != MV_DOWN &&
13901 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13902 return MP_NO_ACTION; // player cannot walk here due to gravity
13904 if (player_can_move &&
13905 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13907 int sound_element = SND_ELEMENT(element);
13908 int sound_action = ACTION_WALKING;
13910 if (IS_RND_GATE(element))
13912 if (!player->key[RND_GATE_NR(element)])
13913 return MP_NO_ACTION;
13915 else if (IS_RND_GATE_GRAY(element))
13917 if (!player->key[RND_GATE_GRAY_NR(element)])
13918 return MP_NO_ACTION;
13920 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13922 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13923 return MP_NO_ACTION;
13925 else if (element == EL_EXIT_OPEN ||
13926 element == EL_EM_EXIT_OPEN ||
13927 element == EL_EM_EXIT_OPENING ||
13928 element == EL_STEEL_EXIT_OPEN ||
13929 element == EL_EM_STEEL_EXIT_OPEN ||
13930 element == EL_EM_STEEL_EXIT_OPENING ||
13931 element == EL_SP_EXIT_OPEN ||
13932 element == EL_SP_EXIT_OPENING)
13934 sound_action = ACTION_PASSING; // player is passing exit
13936 else if (element == EL_EMPTY)
13938 sound_action = ACTION_MOVING; // nothing to walk on
13941 // play sound from background or player, whatever is available
13942 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13943 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13945 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13947 else if (player_can_move &&
13948 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13950 if (!ACCESS_FROM(element, opposite_direction))
13951 return MP_NO_ACTION; // field not accessible from this direction
13953 if (CAN_MOVE(element)) // only fixed elements can be passed!
13954 return MP_NO_ACTION;
13956 if (IS_EM_GATE(element))
13958 if (!player->key[EM_GATE_NR(element)])
13959 return MP_NO_ACTION;
13961 else if (IS_EM_GATE_GRAY(element))
13963 if (!player->key[EM_GATE_GRAY_NR(element)])
13964 return MP_NO_ACTION;
13966 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13968 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13969 return MP_NO_ACTION;
13971 else if (IS_EMC_GATE(element))
13973 if (!player->key[EMC_GATE_NR(element)])
13974 return MP_NO_ACTION;
13976 else if (IS_EMC_GATE_GRAY(element))
13978 if (!player->key[EMC_GATE_GRAY_NR(element)])
13979 return MP_NO_ACTION;
13981 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13983 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13984 return MP_NO_ACTION;
13986 else if (element == EL_DC_GATE_WHITE ||
13987 element == EL_DC_GATE_WHITE_GRAY ||
13988 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13990 if (player->num_white_keys == 0)
13991 return MP_NO_ACTION;
13993 player->num_white_keys--;
13995 else if (IS_SP_PORT(element))
13997 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13998 element == EL_SP_GRAVITY_PORT_RIGHT ||
13999 element == EL_SP_GRAVITY_PORT_UP ||
14000 element == EL_SP_GRAVITY_PORT_DOWN)
14001 player->gravity = !player->gravity;
14002 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14003 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14004 element == EL_SP_GRAVITY_ON_PORT_UP ||
14005 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14006 player->gravity = TRUE;
14007 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14008 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14009 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14010 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14011 player->gravity = FALSE;
14014 // automatically move to the next field with double speed
14015 player->programmed_action = move_direction;
14017 if (player->move_delay_reset_counter == 0)
14019 player->move_delay_reset_counter = 2; // two double speed steps
14021 DOUBLE_PLAYER_SPEED(player);
14024 PlayLevelSoundAction(x, y, ACTION_PASSING);
14026 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14030 if (mode != DF_SNAP)
14032 GfxElement[x][y] = GFX_ELEMENT(element);
14033 player->is_digging = TRUE;
14036 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14038 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14039 player->index_bit, dig_side);
14041 // if digging triggered player relocation, finish digging tile
14042 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14043 setFieldForSnapping(x, y, element, move_direction);
14045 if (mode == DF_SNAP)
14047 if (level.block_snap_field)
14048 setFieldForSnapping(x, y, element, move_direction);
14050 TestIfElementTouchesCustomElement(x, y); // for empty space
14052 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14053 player->index_bit, dig_side);
14056 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14060 if (is_player && mode != DF_SNAP)
14062 GfxElement[x][y] = element;
14063 player->is_collecting = TRUE;
14066 if (element == EL_SPEED_PILL)
14068 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14070 else if (element == EL_EXTRA_TIME && level.time > 0)
14072 TimeLeft += level.extra_time;
14074 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14076 DisplayGameControlValues();
14078 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14080 player->shield_normal_time_left += level.shield_normal_time;
14081 if (element == EL_SHIELD_DEADLY)
14082 player->shield_deadly_time_left += level.shield_deadly_time;
14084 else if (element == EL_DYNAMITE ||
14085 element == EL_EM_DYNAMITE ||
14086 element == EL_SP_DISK_RED)
14088 if (player->inventory_size < MAX_INVENTORY_SIZE)
14089 player->inventory_element[player->inventory_size++] = element;
14091 DrawGameDoorValues();
14093 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14095 player->dynabomb_count++;
14096 player->dynabombs_left++;
14098 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14100 player->dynabomb_size++;
14102 else if (element == EL_DYNABOMB_INCREASE_POWER)
14104 player->dynabomb_xl = TRUE;
14106 else if (IS_KEY(element))
14108 player->key[KEY_NR(element)] = TRUE;
14110 DrawGameDoorValues();
14112 else if (element == EL_DC_KEY_WHITE)
14114 player->num_white_keys++;
14116 // display white keys?
14117 // DrawGameDoorValues();
14119 else if (IS_ENVELOPE(element))
14121 player->show_envelope = element;
14123 else if (element == EL_EMC_LENSES)
14125 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14127 RedrawAllInvisibleElementsForLenses();
14129 else if (element == EL_EMC_MAGNIFIER)
14131 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14133 RedrawAllInvisibleElementsForMagnifier();
14135 else if (IS_DROPPABLE(element) ||
14136 IS_THROWABLE(element)) // can be collected and dropped
14140 if (collect_count == 0)
14141 player->inventory_infinite_element = element;
14143 for (i = 0; i < collect_count; i++)
14144 if (player->inventory_size < MAX_INVENTORY_SIZE)
14145 player->inventory_element[player->inventory_size++] = element;
14147 DrawGameDoorValues();
14149 else if (collect_count > 0)
14151 game.gems_still_needed -= collect_count;
14152 if (game.gems_still_needed < 0)
14153 game.gems_still_needed = 0;
14155 game.snapshot.collected_item = TRUE;
14157 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14159 DisplayGameControlValues();
14162 RaiseScoreElement(element);
14163 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14167 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14168 player->index_bit, dig_side);
14170 // if collecting triggered player relocation, finish collecting tile
14171 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14172 setFieldForSnapping(x, y, element, move_direction);
14175 if (mode == DF_SNAP)
14177 if (level.block_snap_field)
14178 setFieldForSnapping(x, y, element, move_direction);
14180 TestIfElementTouchesCustomElement(x, y); // for empty space
14182 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14183 player->index_bit, dig_side);
14186 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14188 if (mode == DF_SNAP && element != EL_BD_ROCK)
14189 return MP_NO_ACTION;
14191 if (CAN_FALL(element) && dy)
14192 return MP_NO_ACTION;
14194 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14195 !(element == EL_SPRING && level.use_spring_bug))
14196 return MP_NO_ACTION;
14198 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14199 ((move_direction & MV_VERTICAL &&
14200 ((element_info[element].move_pattern & MV_LEFT &&
14201 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14202 (element_info[element].move_pattern & MV_RIGHT &&
14203 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14204 (move_direction & MV_HORIZONTAL &&
14205 ((element_info[element].move_pattern & MV_UP &&
14206 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14207 (element_info[element].move_pattern & MV_DOWN &&
14208 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14209 return MP_NO_ACTION;
14211 // do not push elements already moving away faster than player
14212 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14213 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14214 return MP_NO_ACTION;
14216 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14218 if (player->push_delay_value == -1 || !player_was_pushing)
14219 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14221 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14223 if (player->push_delay_value == -1)
14224 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14226 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14228 if (!player->is_pushing)
14229 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14232 player->is_pushing = TRUE;
14233 player->is_active = TRUE;
14235 if (!(IN_LEV_FIELD(nextx, nexty) &&
14236 (IS_FREE(nextx, nexty) ||
14237 (IS_SB_ELEMENT(element) &&
14238 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14239 (IS_CUSTOM_ELEMENT(element) &&
14240 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14241 return MP_NO_ACTION;
14243 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14244 return MP_NO_ACTION;
14246 if (player->push_delay == -1) // new pushing; restart delay
14247 player->push_delay = 0;
14249 if (player->push_delay < player->push_delay_value &&
14250 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14251 element != EL_SPRING && element != EL_BALLOON)
14253 // make sure that there is no move delay before next try to push
14254 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14255 player->move_delay = 0;
14257 return MP_NO_ACTION;
14260 if (IS_CUSTOM_ELEMENT(element) &&
14261 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14263 if (!DigFieldByCE(nextx, nexty, element))
14264 return MP_NO_ACTION;
14267 if (IS_SB_ELEMENT(element))
14269 boolean sokoban_task_solved = FALSE;
14271 if (element == EL_SOKOBAN_FIELD_FULL)
14273 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14275 IncrementSokobanFieldsNeeded();
14276 IncrementSokobanObjectsNeeded();
14279 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14281 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14283 DecrementSokobanFieldsNeeded();
14284 DecrementSokobanObjectsNeeded();
14286 // sokoban object was pushed from empty field to sokoban field
14287 if (Back[x][y] == EL_EMPTY)
14288 sokoban_task_solved = TRUE;
14291 Tile[x][y] = EL_SOKOBAN_OBJECT;
14293 if (Back[x][y] == Back[nextx][nexty])
14294 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14295 else if (Back[x][y] != 0)
14296 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14299 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14302 if (sokoban_task_solved &&
14303 game.sokoban_fields_still_needed == 0 &&
14304 game.sokoban_objects_still_needed == 0 &&
14305 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14307 game.players_still_needed = 0;
14311 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14315 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14317 InitMovingField(x, y, move_direction);
14318 GfxAction[x][y] = ACTION_PUSHING;
14320 if (mode == DF_SNAP)
14321 ContinueMoving(x, y);
14323 MovPos[x][y] = (dx != 0 ? dx : dy);
14325 Pushed[x][y] = TRUE;
14326 Pushed[nextx][nexty] = TRUE;
14328 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14329 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14331 player->push_delay_value = -1; // get new value later
14333 // check for element change _after_ element has been pushed
14334 if (game.use_change_when_pushing_bug)
14336 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14337 player->index_bit, dig_side);
14338 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14339 player->index_bit, dig_side);
14342 else if (IS_SWITCHABLE(element))
14344 if (PLAYER_SWITCHING(player, x, y))
14346 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14347 player->index_bit, dig_side);
14352 player->is_switching = TRUE;
14353 player->switch_x = x;
14354 player->switch_y = y;
14356 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14358 if (element == EL_ROBOT_WHEEL)
14360 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14362 game.robot_wheel_x = x;
14363 game.robot_wheel_y = y;
14364 game.robot_wheel_active = TRUE;
14366 TEST_DrawLevelField(x, y);
14368 else if (element == EL_SP_TERMINAL)
14372 SCAN_PLAYFIELD(xx, yy)
14374 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14378 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14380 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14382 ResetGfxAnimation(xx, yy);
14383 TEST_DrawLevelField(xx, yy);
14387 else if (IS_BELT_SWITCH(element))
14389 ToggleBeltSwitch(x, y);
14391 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14392 element == EL_SWITCHGATE_SWITCH_DOWN ||
14393 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14394 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14396 ToggleSwitchgateSwitch(x, y);
14398 else if (element == EL_LIGHT_SWITCH ||
14399 element == EL_LIGHT_SWITCH_ACTIVE)
14401 ToggleLightSwitch(x, y);
14403 else if (element == EL_TIMEGATE_SWITCH ||
14404 element == EL_DC_TIMEGATE_SWITCH)
14406 ActivateTimegateSwitch(x, y);
14408 else if (element == EL_BALLOON_SWITCH_LEFT ||
14409 element == EL_BALLOON_SWITCH_RIGHT ||
14410 element == EL_BALLOON_SWITCH_UP ||
14411 element == EL_BALLOON_SWITCH_DOWN ||
14412 element == EL_BALLOON_SWITCH_NONE ||
14413 element == EL_BALLOON_SWITCH_ANY)
14415 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14416 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14417 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14418 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14419 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14422 else if (element == EL_LAMP)
14424 Tile[x][y] = EL_LAMP_ACTIVE;
14425 game.lights_still_needed--;
14427 ResetGfxAnimation(x, y);
14428 TEST_DrawLevelField(x, y);
14430 else if (element == EL_TIME_ORB_FULL)
14432 Tile[x][y] = EL_TIME_ORB_EMPTY;
14434 if (level.time > 0 || level.use_time_orb_bug)
14436 TimeLeft += level.time_orb_time;
14437 game.no_time_limit = FALSE;
14439 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14441 DisplayGameControlValues();
14444 ResetGfxAnimation(x, y);
14445 TEST_DrawLevelField(x, y);
14447 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14448 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14452 game.ball_active = !game.ball_active;
14454 SCAN_PLAYFIELD(xx, yy)
14456 int e = Tile[xx][yy];
14458 if (game.ball_active)
14460 if (e == EL_EMC_MAGIC_BALL)
14461 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14462 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14463 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14467 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14468 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14469 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14470 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14475 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14476 player->index_bit, dig_side);
14478 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14479 player->index_bit, dig_side);
14481 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14482 player->index_bit, dig_side);
14488 if (!PLAYER_SWITCHING(player, x, y))
14490 player->is_switching = TRUE;
14491 player->switch_x = x;
14492 player->switch_y = y;
14494 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14495 player->index_bit, dig_side);
14496 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14497 player->index_bit, dig_side);
14499 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14500 player->index_bit, dig_side);
14501 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14502 player->index_bit, dig_side);
14505 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14506 player->index_bit, dig_side);
14507 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14508 player->index_bit, dig_side);
14510 return MP_NO_ACTION;
14513 player->push_delay = -1;
14515 if (is_player) // function can also be called by EL_PENGUIN
14517 if (Tile[x][y] != element) // really digged/collected something
14519 player->is_collecting = !player->is_digging;
14520 player->is_active = TRUE;
14527 static boolean DigFieldByCE(int x, int y, int digging_element)
14529 int element = Tile[x][y];
14531 if (!IS_FREE(x, y))
14533 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14534 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14537 // no element can dig solid indestructible elements
14538 if (IS_INDESTRUCTIBLE(element) &&
14539 !IS_DIGGABLE(element) &&
14540 !IS_COLLECTIBLE(element))
14543 if (AmoebaNr[x][y] &&
14544 (element == EL_AMOEBA_FULL ||
14545 element == EL_BD_AMOEBA ||
14546 element == EL_AMOEBA_GROWING))
14548 AmoebaCnt[AmoebaNr[x][y]]--;
14549 AmoebaCnt2[AmoebaNr[x][y]]--;
14552 if (IS_MOVING(x, y))
14553 RemoveMovingField(x, y);
14557 TEST_DrawLevelField(x, y);
14560 // if digged element was about to explode, prevent the explosion
14561 ExplodeField[x][y] = EX_TYPE_NONE;
14563 PlayLevelSoundAction(x, y, action);
14566 Store[x][y] = EL_EMPTY;
14568 // this makes it possible to leave the removed element again
14569 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14570 Store[x][y] = element;
14575 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14577 int jx = player->jx, jy = player->jy;
14578 int x = jx + dx, y = jy + dy;
14579 int snap_direction = (dx == -1 ? MV_LEFT :
14580 dx == +1 ? MV_RIGHT :
14582 dy == +1 ? MV_DOWN : MV_NONE);
14583 boolean can_continue_snapping = (level.continuous_snapping &&
14584 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14586 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14589 if (!player->active || !IN_LEV_FIELD(x, y))
14597 if (player->MovPos == 0)
14598 player->is_pushing = FALSE;
14600 player->is_snapping = FALSE;
14602 if (player->MovPos == 0)
14604 player->is_moving = FALSE;
14605 player->is_digging = FALSE;
14606 player->is_collecting = FALSE;
14612 // prevent snapping with already pressed snap key when not allowed
14613 if (player->is_snapping && !can_continue_snapping)
14616 player->MovDir = snap_direction;
14618 if (player->MovPos == 0)
14620 player->is_moving = FALSE;
14621 player->is_digging = FALSE;
14622 player->is_collecting = FALSE;
14625 player->is_dropping = FALSE;
14626 player->is_dropping_pressed = FALSE;
14627 player->drop_pressed_delay = 0;
14629 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14632 player->is_snapping = TRUE;
14633 player->is_active = TRUE;
14635 if (player->MovPos == 0)
14637 player->is_moving = FALSE;
14638 player->is_digging = FALSE;
14639 player->is_collecting = FALSE;
14642 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14643 TEST_DrawLevelField(player->last_jx, player->last_jy);
14645 TEST_DrawLevelField(x, y);
14650 static boolean DropElement(struct PlayerInfo *player)
14652 int old_element, new_element;
14653 int dropx = player->jx, dropy = player->jy;
14654 int drop_direction = player->MovDir;
14655 int drop_side = drop_direction;
14656 int drop_element = get_next_dropped_element(player);
14658 /* do not drop an element on top of another element; when holding drop key
14659 pressed without moving, dropped element must move away before the next
14660 element can be dropped (this is especially important if the next element
14661 is dynamite, which can be placed on background for historical reasons) */
14662 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14665 if (IS_THROWABLE(drop_element))
14667 dropx += GET_DX_FROM_DIR(drop_direction);
14668 dropy += GET_DY_FROM_DIR(drop_direction);
14670 if (!IN_LEV_FIELD(dropx, dropy))
14674 old_element = Tile[dropx][dropy]; // old element at dropping position
14675 new_element = drop_element; // default: no change when dropping
14677 // check if player is active, not moving and ready to drop
14678 if (!player->active || player->MovPos || player->drop_delay > 0)
14681 // check if player has anything that can be dropped
14682 if (new_element == EL_UNDEFINED)
14685 // only set if player has anything that can be dropped
14686 player->is_dropping_pressed = TRUE;
14688 // check if drop key was pressed long enough for EM style dynamite
14689 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14692 // check if anything can be dropped at the current position
14693 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14696 // collected custom elements can only be dropped on empty fields
14697 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14700 if (old_element != EL_EMPTY)
14701 Back[dropx][dropy] = old_element; // store old element on this field
14703 ResetGfxAnimation(dropx, dropy);
14704 ResetRandomAnimationValue(dropx, dropy);
14706 if (player->inventory_size > 0 ||
14707 player->inventory_infinite_element != EL_UNDEFINED)
14709 if (player->inventory_size > 0)
14711 player->inventory_size--;
14713 DrawGameDoorValues();
14715 if (new_element == EL_DYNAMITE)
14716 new_element = EL_DYNAMITE_ACTIVE;
14717 else if (new_element == EL_EM_DYNAMITE)
14718 new_element = EL_EM_DYNAMITE_ACTIVE;
14719 else if (new_element == EL_SP_DISK_RED)
14720 new_element = EL_SP_DISK_RED_ACTIVE;
14723 Tile[dropx][dropy] = new_element;
14725 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14726 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14727 el2img(Tile[dropx][dropy]), 0);
14729 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14731 // needed if previous element just changed to "empty" in the last frame
14732 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14734 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14735 player->index_bit, drop_side);
14736 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14738 player->index_bit, drop_side);
14740 TestIfElementTouchesCustomElement(dropx, dropy);
14742 else // player is dropping a dyna bomb
14744 player->dynabombs_left--;
14746 Tile[dropx][dropy] = new_element;
14748 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14749 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14750 el2img(Tile[dropx][dropy]), 0);
14752 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14755 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14756 InitField_WithBug1(dropx, dropy, FALSE);
14758 new_element = Tile[dropx][dropy]; // element might have changed
14760 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14761 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14763 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14764 MovDir[dropx][dropy] = drop_direction;
14766 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14768 // do not cause impact style collision by dropping elements that can fall
14769 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14772 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14773 player->is_dropping = TRUE;
14775 player->drop_pressed_delay = 0;
14776 player->is_dropping_pressed = FALSE;
14778 player->drop_x = dropx;
14779 player->drop_y = dropy;
14784 // ----------------------------------------------------------------------------
14785 // game sound playing functions
14786 // ----------------------------------------------------------------------------
14788 static int *loop_sound_frame = NULL;
14789 static int *loop_sound_volume = NULL;
14791 void InitPlayLevelSound(void)
14793 int num_sounds = getSoundListSize();
14795 checked_free(loop_sound_frame);
14796 checked_free(loop_sound_volume);
14798 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14799 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14802 static void PlayLevelSound(int x, int y, int nr)
14804 int sx = SCREENX(x), sy = SCREENY(y);
14805 int volume, stereo_position;
14806 int max_distance = 8;
14807 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14809 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14810 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14813 if (!IN_LEV_FIELD(x, y) ||
14814 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14815 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14818 volume = SOUND_MAX_VOLUME;
14820 if (!IN_SCR_FIELD(sx, sy))
14822 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14823 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14825 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14828 stereo_position = (SOUND_MAX_LEFT +
14829 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14830 (SCR_FIELDX + 2 * max_distance));
14832 if (IS_LOOP_SOUND(nr))
14834 /* This assures that quieter loop sounds do not overwrite louder ones,
14835 while restarting sound volume comparison with each new game frame. */
14837 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14840 loop_sound_volume[nr] = volume;
14841 loop_sound_frame[nr] = FrameCounter;
14844 PlaySoundExt(nr, volume, stereo_position, type);
14847 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14849 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14850 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14851 y < LEVELY(BY1) ? LEVELY(BY1) :
14852 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14856 static void PlayLevelSoundAction(int x, int y, int action)
14858 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14861 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14863 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14865 if (sound_effect != SND_UNDEFINED)
14866 PlayLevelSound(x, y, sound_effect);
14869 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14872 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14874 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14875 PlayLevelSound(x, y, sound_effect);
14878 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14880 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14882 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14883 PlayLevelSound(x, y, sound_effect);
14886 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14888 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14890 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14891 StopSound(sound_effect);
14894 static int getLevelMusicNr(void)
14896 if (levelset.music[level_nr] != MUS_UNDEFINED)
14897 return levelset.music[level_nr]; // from config file
14899 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14902 static void FadeLevelSounds(void)
14907 static void FadeLevelMusic(void)
14909 int music_nr = getLevelMusicNr();
14910 char *curr_music = getCurrentlyPlayingMusicFilename();
14911 char *next_music = getMusicInfoEntryFilename(music_nr);
14913 if (!strEqual(curr_music, next_music))
14917 void FadeLevelSoundsAndMusic(void)
14923 static void PlayLevelMusic(void)
14925 int music_nr = getLevelMusicNr();
14926 char *curr_music = getCurrentlyPlayingMusicFilename();
14927 char *next_music = getMusicInfoEntryFilename(music_nr);
14929 if (!strEqual(curr_music, next_music))
14930 PlayMusicLoop(music_nr);
14933 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14935 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14937 int x = xx - offset;
14938 int y = yy - offset;
14943 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14947 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14951 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14955 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14959 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14963 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14967 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14970 case SOUND_android_clone:
14971 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14974 case SOUND_android_move:
14975 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14979 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14983 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14987 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14990 case SOUND_eater_eat:
14991 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14995 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14998 case SOUND_collect:
14999 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15002 case SOUND_diamond:
15003 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15007 // !!! CHECK THIS !!!
15009 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15011 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15015 case SOUND_wonderfall:
15016 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15020 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15024 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15028 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15032 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15036 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15040 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15044 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15048 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15051 case SOUND_exit_open:
15052 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15055 case SOUND_exit_leave:
15056 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15059 case SOUND_dynamite:
15060 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15064 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15068 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15072 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15076 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15080 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15084 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15088 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15093 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15095 int element = map_element_SP_to_RND(element_sp);
15096 int action = map_action_SP_to_RND(action_sp);
15097 int offset = (setup.sp_show_border_elements ? 0 : 1);
15098 int x = xx - offset;
15099 int y = yy - offset;
15101 PlayLevelSoundElementAction(x, y, element, action);
15104 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15106 int element = map_element_MM_to_RND(element_mm);
15107 int action = map_action_MM_to_RND(action_mm);
15109 int x = xx - offset;
15110 int y = yy - offset;
15112 if (!IS_MM_ELEMENT(element))
15113 element = EL_MM_DEFAULT;
15115 PlayLevelSoundElementAction(x, y, element, action);
15118 void PlaySound_MM(int sound_mm)
15120 int sound = map_sound_MM_to_RND(sound_mm);
15122 if (sound == SND_UNDEFINED)
15128 void PlaySoundLoop_MM(int sound_mm)
15130 int sound = map_sound_MM_to_RND(sound_mm);
15132 if (sound == SND_UNDEFINED)
15135 PlaySoundLoop(sound);
15138 void StopSound_MM(int sound_mm)
15140 int sound = map_sound_MM_to_RND(sound_mm);
15142 if (sound == SND_UNDEFINED)
15148 void RaiseScore(int value)
15150 game.score += value;
15152 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15154 DisplayGameControlValues();
15157 void RaiseScoreElement(int element)
15162 case EL_BD_DIAMOND:
15163 case EL_EMERALD_YELLOW:
15164 case EL_EMERALD_RED:
15165 case EL_EMERALD_PURPLE:
15166 case EL_SP_INFOTRON:
15167 RaiseScore(level.score[SC_EMERALD]);
15170 RaiseScore(level.score[SC_DIAMOND]);
15173 RaiseScore(level.score[SC_CRYSTAL]);
15176 RaiseScore(level.score[SC_PEARL]);
15179 case EL_BD_BUTTERFLY:
15180 case EL_SP_ELECTRON:
15181 RaiseScore(level.score[SC_BUG]);
15184 case EL_BD_FIREFLY:
15185 case EL_SP_SNIKSNAK:
15186 RaiseScore(level.score[SC_SPACESHIP]);
15189 case EL_DARK_YAMYAM:
15190 RaiseScore(level.score[SC_YAMYAM]);
15193 RaiseScore(level.score[SC_ROBOT]);
15196 RaiseScore(level.score[SC_PACMAN]);
15199 RaiseScore(level.score[SC_NUT]);
15202 case EL_EM_DYNAMITE:
15203 case EL_SP_DISK_RED:
15204 case EL_DYNABOMB_INCREASE_NUMBER:
15205 case EL_DYNABOMB_INCREASE_SIZE:
15206 case EL_DYNABOMB_INCREASE_POWER:
15207 RaiseScore(level.score[SC_DYNAMITE]);
15209 case EL_SHIELD_NORMAL:
15210 case EL_SHIELD_DEADLY:
15211 RaiseScore(level.score[SC_SHIELD]);
15213 case EL_EXTRA_TIME:
15214 RaiseScore(level.extra_time_score);
15228 case EL_DC_KEY_WHITE:
15229 RaiseScore(level.score[SC_KEY]);
15232 RaiseScore(element_info[element].collect_score);
15237 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15239 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15241 // closing door required in case of envelope style request dialogs
15244 // prevent short reactivation of overlay buttons while closing door
15245 SetOverlayActive(FALSE);
15247 CloseDoor(DOOR_CLOSE_1);
15250 if (network.enabled)
15251 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15255 FadeSkipNextFadeIn();
15257 SetGameStatus(GAME_MODE_MAIN);
15262 else // continue playing the game
15264 if (tape.playing && tape.deactivate_display)
15265 TapeDeactivateDisplayOff(TRUE);
15267 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15269 if (tape.playing && tape.deactivate_display)
15270 TapeDeactivateDisplayOn();
15274 void RequestQuitGame(boolean ask_if_really_quit)
15276 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15277 boolean skip_request = game.all_players_gone || quick_quit;
15279 RequestQuitGameExt(skip_request, quick_quit,
15280 "Do you really want to quit the game?");
15283 void RequestRestartGame(char *message)
15285 game.restart_game_message = NULL;
15287 boolean has_started_game = hasStartedNetworkGame();
15288 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15290 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15292 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15296 // needed in case of envelope request to close game panel
15297 CloseDoor(DOOR_CLOSE_1);
15299 SetGameStatus(GAME_MODE_MAIN);
15305 void CheckGameOver(void)
15307 static boolean last_game_over = FALSE;
15308 static int game_over_delay = 0;
15309 int game_over_delay_value = 50;
15310 boolean game_over = checkGameFailed();
15312 // do not handle game over if request dialog is already active
15313 if (game.request_active)
15316 // do not ask to play again if game was never actually played
15317 if (!game.GamePlayed)
15322 last_game_over = FALSE;
15323 game_over_delay = game_over_delay_value;
15328 if (game_over_delay > 0)
15335 if (last_game_over != game_over)
15336 game.restart_game_message = (hasStartedNetworkGame() ?
15337 "Game over! Play it again?" :
15340 last_game_over = game_over;
15343 boolean checkGameSolved(void)
15345 // set for all game engines if level was solved
15346 return game.LevelSolved_GameEnd;
15349 boolean checkGameFailed(void)
15351 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15352 return (game_em.game_over && !game_em.level_solved);
15353 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15354 return (game_sp.game_over && !game_sp.level_solved);
15355 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15356 return (game_mm.game_over && !game_mm.level_solved);
15357 else // GAME_ENGINE_TYPE_RND
15358 return (game.GameOver && !game.LevelSolved);
15361 boolean checkGameEnded(void)
15363 return (checkGameSolved() || checkGameFailed());
15367 // ----------------------------------------------------------------------------
15368 // random generator functions
15369 // ----------------------------------------------------------------------------
15371 unsigned int InitEngineRandom_RND(int seed)
15373 game.num_random_calls = 0;
15375 return InitEngineRandom(seed);
15378 unsigned int RND(int max)
15382 game.num_random_calls++;
15384 return GetEngineRandom(max);
15391 // ----------------------------------------------------------------------------
15392 // game engine snapshot handling functions
15393 // ----------------------------------------------------------------------------
15395 struct EngineSnapshotInfo
15397 // runtime values for custom element collect score
15398 int collect_score[NUM_CUSTOM_ELEMENTS];
15400 // runtime values for group element choice position
15401 int choice_pos[NUM_GROUP_ELEMENTS];
15403 // runtime values for belt position animations
15404 int belt_graphic[4][NUM_BELT_PARTS];
15405 int belt_anim_mode[4][NUM_BELT_PARTS];
15408 static struct EngineSnapshotInfo engine_snapshot_rnd;
15409 static char *snapshot_level_identifier = NULL;
15410 static int snapshot_level_nr = -1;
15412 static void SaveEngineSnapshotValues_RND(void)
15414 static int belt_base_active_element[4] =
15416 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15417 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15418 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15419 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15423 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15425 int element = EL_CUSTOM_START + i;
15427 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15430 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15432 int element = EL_GROUP_START + i;
15434 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15437 for (i = 0; i < 4; i++)
15439 for (j = 0; j < NUM_BELT_PARTS; j++)
15441 int element = belt_base_active_element[i] + j;
15442 int graphic = el2img(element);
15443 int anim_mode = graphic_info[graphic].anim_mode;
15445 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15446 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15451 static void LoadEngineSnapshotValues_RND(void)
15453 unsigned int num_random_calls = game.num_random_calls;
15456 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15458 int element = EL_CUSTOM_START + i;
15460 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15463 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15465 int element = EL_GROUP_START + i;
15467 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15470 for (i = 0; i < 4; i++)
15472 for (j = 0; j < NUM_BELT_PARTS; j++)
15474 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15475 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15477 graphic_info[graphic].anim_mode = anim_mode;
15481 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15483 InitRND(tape.random_seed);
15484 for (i = 0; i < num_random_calls; i++)
15488 if (game.num_random_calls != num_random_calls)
15490 Error("number of random calls out of sync");
15491 Error("number of random calls should be %d", num_random_calls);
15492 Error("number of random calls is %d", game.num_random_calls);
15494 Fail("this should not happen -- please debug");
15498 void FreeEngineSnapshotSingle(void)
15500 FreeSnapshotSingle();
15502 setString(&snapshot_level_identifier, NULL);
15503 snapshot_level_nr = -1;
15506 void FreeEngineSnapshotList(void)
15508 FreeSnapshotList();
15511 static ListNode *SaveEngineSnapshotBuffers(void)
15513 ListNode *buffers = NULL;
15515 // copy some special values to a structure better suited for the snapshot
15517 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15518 SaveEngineSnapshotValues_RND();
15519 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15520 SaveEngineSnapshotValues_EM();
15521 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15522 SaveEngineSnapshotValues_SP(&buffers);
15523 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15524 SaveEngineSnapshotValues_MM(&buffers);
15526 // save values stored in special snapshot structure
15528 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15529 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15530 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15531 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15532 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15533 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15534 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15535 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15537 // save further RND engine values
15539 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15540 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15541 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15543 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15544 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15545 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15546 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15547 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15549 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15550 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15551 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15553 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15555 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15556 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15558 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15559 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15560 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15561 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15562 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15563 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15564 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15565 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15566 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15567 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15568 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15569 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15570 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15571 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15572 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15573 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15574 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15575 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15577 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15578 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15580 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15581 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15582 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15584 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15585 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15587 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15588 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15589 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15590 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15591 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15593 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15594 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15597 ListNode *node = engine_snapshot_list_rnd;
15600 while (node != NULL)
15602 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15607 Debug("game:playing:SaveEngineSnapshotBuffers",
15608 "size of engine snapshot: %d bytes", num_bytes);
15614 void SaveEngineSnapshotSingle(void)
15616 ListNode *buffers = SaveEngineSnapshotBuffers();
15618 // finally save all snapshot buffers to single snapshot
15619 SaveSnapshotSingle(buffers);
15621 // save level identification information
15622 setString(&snapshot_level_identifier, leveldir_current->identifier);
15623 snapshot_level_nr = level_nr;
15626 boolean CheckSaveEngineSnapshotToList(void)
15628 boolean save_snapshot =
15629 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15630 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15631 game.snapshot.changed_action) ||
15632 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15633 game.snapshot.collected_item));
15635 game.snapshot.changed_action = FALSE;
15636 game.snapshot.collected_item = FALSE;
15637 game.snapshot.save_snapshot = save_snapshot;
15639 return save_snapshot;
15642 void SaveEngineSnapshotToList(void)
15644 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15648 ListNode *buffers = SaveEngineSnapshotBuffers();
15650 // finally save all snapshot buffers to snapshot list
15651 SaveSnapshotToList(buffers);
15654 void SaveEngineSnapshotToListInitial(void)
15656 FreeEngineSnapshotList();
15658 SaveEngineSnapshotToList();
15661 static void LoadEngineSnapshotValues(void)
15663 // restore special values from snapshot structure
15665 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15666 LoadEngineSnapshotValues_RND();
15667 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15668 LoadEngineSnapshotValues_EM();
15669 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15670 LoadEngineSnapshotValues_SP();
15671 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15672 LoadEngineSnapshotValues_MM();
15675 void LoadEngineSnapshotSingle(void)
15677 LoadSnapshotSingle();
15679 LoadEngineSnapshotValues();
15682 static void LoadEngineSnapshot_Undo(int steps)
15684 LoadSnapshotFromList_Older(steps);
15686 LoadEngineSnapshotValues();
15689 static void LoadEngineSnapshot_Redo(int steps)
15691 LoadSnapshotFromList_Newer(steps);
15693 LoadEngineSnapshotValues();
15696 boolean CheckEngineSnapshotSingle(void)
15698 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15699 snapshot_level_nr == level_nr);
15702 boolean CheckEngineSnapshotList(void)
15704 return CheckSnapshotList();
15708 // ---------- new game button stuff -------------------------------------------
15715 boolean *setup_value;
15716 boolean allowed_on_tape;
15717 boolean is_touch_button;
15719 } gamebutton_info[NUM_GAME_BUTTONS] =
15722 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15723 GAME_CTRL_ID_STOP, NULL,
15724 TRUE, FALSE, "stop game"
15727 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15728 GAME_CTRL_ID_PAUSE, NULL,
15729 TRUE, FALSE, "pause game"
15732 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15733 GAME_CTRL_ID_PLAY, NULL,
15734 TRUE, FALSE, "play game"
15737 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15738 GAME_CTRL_ID_UNDO, NULL,
15739 TRUE, FALSE, "undo step"
15742 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15743 GAME_CTRL_ID_REDO, NULL,
15744 TRUE, FALSE, "redo step"
15747 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15748 GAME_CTRL_ID_SAVE, NULL,
15749 TRUE, FALSE, "save game"
15752 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15753 GAME_CTRL_ID_PAUSE2, NULL,
15754 TRUE, FALSE, "pause game"
15757 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15758 GAME_CTRL_ID_LOAD, NULL,
15759 TRUE, FALSE, "load game"
15762 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15763 GAME_CTRL_ID_PANEL_STOP, NULL,
15764 FALSE, FALSE, "stop game"
15767 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15768 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15769 FALSE, FALSE, "pause game"
15772 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15773 GAME_CTRL_ID_PANEL_PLAY, NULL,
15774 FALSE, FALSE, "play game"
15777 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15778 GAME_CTRL_ID_TOUCH_STOP, NULL,
15779 FALSE, TRUE, "stop game"
15782 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15783 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15784 FALSE, TRUE, "pause game"
15787 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15788 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15789 TRUE, FALSE, "background music on/off"
15792 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15793 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15794 TRUE, FALSE, "sound loops on/off"
15797 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15798 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15799 TRUE, FALSE, "normal sounds on/off"
15802 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15803 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15804 FALSE, FALSE, "background music on/off"
15807 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15808 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15809 FALSE, FALSE, "sound loops on/off"
15812 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15813 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15814 FALSE, FALSE, "normal sounds on/off"
15818 void CreateGameButtons(void)
15822 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15824 int graphic = gamebutton_info[i].graphic;
15825 struct GraphicInfo *gfx = &graphic_info[graphic];
15826 struct XY *pos = gamebutton_info[i].pos;
15827 struct GadgetInfo *gi;
15830 unsigned int event_mask;
15831 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15832 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15833 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15834 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15835 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15836 int gd_x = gfx->src_x;
15837 int gd_y = gfx->src_y;
15838 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15839 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15840 int gd_xa = gfx->src_x + gfx->active_xoffset;
15841 int gd_ya = gfx->src_y + gfx->active_yoffset;
15842 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15843 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15844 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15845 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15848 if (gfx->bitmap == NULL)
15850 game_gadget[id] = NULL;
15855 if (id == GAME_CTRL_ID_STOP ||
15856 id == GAME_CTRL_ID_PANEL_STOP ||
15857 id == GAME_CTRL_ID_TOUCH_STOP ||
15858 id == GAME_CTRL_ID_PLAY ||
15859 id == GAME_CTRL_ID_PANEL_PLAY ||
15860 id == GAME_CTRL_ID_SAVE ||
15861 id == GAME_CTRL_ID_LOAD)
15863 button_type = GD_TYPE_NORMAL_BUTTON;
15865 event_mask = GD_EVENT_RELEASED;
15867 else if (id == GAME_CTRL_ID_UNDO ||
15868 id == GAME_CTRL_ID_REDO)
15870 button_type = GD_TYPE_NORMAL_BUTTON;
15872 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15876 button_type = GD_TYPE_CHECK_BUTTON;
15877 checked = (gamebutton_info[i].setup_value != NULL ?
15878 *gamebutton_info[i].setup_value : FALSE);
15879 event_mask = GD_EVENT_PRESSED;
15882 gi = CreateGadget(GDI_CUSTOM_ID, id,
15883 GDI_IMAGE_ID, graphic,
15884 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15887 GDI_WIDTH, gfx->width,
15888 GDI_HEIGHT, gfx->height,
15889 GDI_TYPE, button_type,
15890 GDI_STATE, GD_BUTTON_UNPRESSED,
15891 GDI_CHECKED, checked,
15892 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15893 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15894 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15895 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15896 GDI_DIRECT_DRAW, FALSE,
15897 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15898 GDI_EVENT_MASK, event_mask,
15899 GDI_CALLBACK_ACTION, HandleGameButtons,
15903 Fail("cannot create gadget");
15905 game_gadget[id] = gi;
15909 void FreeGameButtons(void)
15913 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15914 FreeGadget(game_gadget[i]);
15917 static void UnmapGameButtonsAtSamePosition(int id)
15921 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15923 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15924 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15925 UnmapGadget(game_gadget[i]);
15928 static void UnmapGameButtonsAtSamePosition_All(void)
15930 if (setup.show_snapshot_buttons)
15932 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15933 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15934 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15938 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15939 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15940 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15942 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15943 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15944 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15948 static void MapGameButtonsAtSamePosition(int id)
15952 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15954 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15955 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15956 MapGadget(game_gadget[i]);
15958 UnmapGameButtonsAtSamePosition_All();
15961 void MapUndoRedoButtons(void)
15963 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15964 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15966 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15967 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15970 void UnmapUndoRedoButtons(void)
15972 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15973 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15975 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15976 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15979 void ModifyPauseButtons(void)
15983 GAME_CTRL_ID_PAUSE,
15984 GAME_CTRL_ID_PAUSE2,
15985 GAME_CTRL_ID_PANEL_PAUSE,
15986 GAME_CTRL_ID_TOUCH_PAUSE,
15991 for (i = 0; ids[i] > -1; i++)
15992 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15995 static void MapGameButtonsExt(boolean on_tape)
15999 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16000 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16001 i != GAME_CTRL_ID_UNDO &&
16002 i != GAME_CTRL_ID_REDO)
16003 MapGadget(game_gadget[i]);
16005 UnmapGameButtonsAtSamePosition_All();
16007 RedrawGameButtons();
16010 static void UnmapGameButtonsExt(boolean on_tape)
16014 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16015 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16016 UnmapGadget(game_gadget[i]);
16019 static void RedrawGameButtonsExt(boolean on_tape)
16023 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16024 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16025 RedrawGadget(game_gadget[i]);
16028 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16033 gi->checked = state;
16036 static void RedrawSoundButtonGadget(int id)
16038 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16039 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16040 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16041 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16042 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16043 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16046 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16047 RedrawGadget(game_gadget[id2]);
16050 void MapGameButtons(void)
16052 MapGameButtonsExt(FALSE);
16055 void UnmapGameButtons(void)
16057 UnmapGameButtonsExt(FALSE);
16060 void RedrawGameButtons(void)
16062 RedrawGameButtonsExt(FALSE);
16065 void MapGameButtonsOnTape(void)
16067 MapGameButtonsExt(TRUE);
16070 void UnmapGameButtonsOnTape(void)
16072 UnmapGameButtonsExt(TRUE);
16075 void RedrawGameButtonsOnTape(void)
16077 RedrawGameButtonsExt(TRUE);
16080 static void GameUndoRedoExt(void)
16082 ClearPlayerAction();
16084 tape.pausing = TRUE;
16087 UpdateAndDisplayGameControlValues();
16089 DrawCompleteVideoDisplay();
16090 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16091 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16092 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16097 static void GameUndo(int steps)
16099 if (!CheckEngineSnapshotList())
16102 LoadEngineSnapshot_Undo(steps);
16107 static void GameRedo(int steps)
16109 if (!CheckEngineSnapshotList())
16112 LoadEngineSnapshot_Redo(steps);
16117 static void HandleGameButtonsExt(int id, int button)
16119 static boolean game_undo_executed = FALSE;
16120 int steps = BUTTON_STEPSIZE(button);
16121 boolean handle_game_buttons =
16122 (game_status == GAME_MODE_PLAYING ||
16123 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16125 if (!handle_game_buttons)
16130 case GAME_CTRL_ID_STOP:
16131 case GAME_CTRL_ID_PANEL_STOP:
16132 case GAME_CTRL_ID_TOUCH_STOP:
16133 if (game_status == GAME_MODE_MAIN)
16139 RequestQuitGame(TRUE);
16143 case GAME_CTRL_ID_PAUSE:
16144 case GAME_CTRL_ID_PAUSE2:
16145 case GAME_CTRL_ID_PANEL_PAUSE:
16146 case GAME_CTRL_ID_TOUCH_PAUSE:
16147 if (network.enabled && game_status == GAME_MODE_PLAYING)
16150 SendToServer_ContinuePlaying();
16152 SendToServer_PausePlaying();
16155 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16157 game_undo_executed = FALSE;
16161 case GAME_CTRL_ID_PLAY:
16162 case GAME_CTRL_ID_PANEL_PLAY:
16163 if (game_status == GAME_MODE_MAIN)
16165 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16167 else if (tape.pausing)
16169 if (network.enabled)
16170 SendToServer_ContinuePlaying();
16172 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16176 case GAME_CTRL_ID_UNDO:
16177 // Important: When using "save snapshot when collecting an item" mode,
16178 // load last (current) snapshot for first "undo" after pressing "pause"
16179 // (else the last-but-one snapshot would be loaded, because the snapshot
16180 // pointer already points to the last snapshot when pressing "pause",
16181 // which is fine for "every step/move" mode, but not for "every collect")
16182 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16183 !game_undo_executed)
16186 game_undo_executed = TRUE;
16191 case GAME_CTRL_ID_REDO:
16195 case GAME_CTRL_ID_SAVE:
16199 case GAME_CTRL_ID_LOAD:
16203 case SOUND_CTRL_ID_MUSIC:
16204 case SOUND_CTRL_ID_PANEL_MUSIC:
16205 if (setup.sound_music)
16207 setup.sound_music = FALSE;
16211 else if (audio.music_available)
16213 setup.sound = setup.sound_music = TRUE;
16215 SetAudioMode(setup.sound);
16217 if (game_status == GAME_MODE_PLAYING)
16221 RedrawSoundButtonGadget(id);
16225 case SOUND_CTRL_ID_LOOPS:
16226 case SOUND_CTRL_ID_PANEL_LOOPS:
16227 if (setup.sound_loops)
16228 setup.sound_loops = FALSE;
16229 else if (audio.loops_available)
16231 setup.sound = setup.sound_loops = TRUE;
16233 SetAudioMode(setup.sound);
16236 RedrawSoundButtonGadget(id);
16240 case SOUND_CTRL_ID_SIMPLE:
16241 case SOUND_CTRL_ID_PANEL_SIMPLE:
16242 if (setup.sound_simple)
16243 setup.sound_simple = FALSE;
16244 else if (audio.sound_available)
16246 setup.sound = setup.sound_simple = TRUE;
16248 SetAudioMode(setup.sound);
16251 RedrawSoundButtonGadget(id);
16260 static void HandleGameButtons(struct GadgetInfo *gi)
16262 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16265 void HandleSoundButtonKeys(Key key)
16267 if (key == setup.shortcut.sound_simple)
16268 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16269 else if (key == setup.shortcut.sound_loops)
16270 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16271 else if (key == setup.shortcut.sound_music)
16272 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);