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 Feld[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 Feld[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 Feld[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, Feld[x][y] == EL_EMC_PLANT)
950 #define ANDROID_CAN_CLONE_FIELD(x, y) \
951 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Feld[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, Feld[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(Feld[x][y]))
963 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Feld[x][y]))
966 #define PIG_CAN_ENTER_FIELD(e, x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Feld[x][y]))
969 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Feld[x][y] == EL_EXIT_OPEN || \
971 Feld[x][y] == EL_EM_EXIT_OPEN || \
972 Feld[x][y] == EL_STEEL_EXIT_OPEN || \
973 Feld[x][y] == EL_EM_STEEL_EXIT_OPEN || \
974 IS_FOOD_PENGUIN(Feld[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) && (Feld[x][y] == EL_EMC_SPRING_BUMPER || \
986 Feld[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(Feld[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 AmoebeNachbarNr(int, int);
1096 void AmoebeUmwandeln(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(Feld[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 Feld[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 Feld[x][y] = EL_PLAYER_1;
1739 struct PlayerInfo *player = &stored_player[Feld[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] == Feld[x][y])
1767 StorePlayer[jx][jy] = 0;
1769 StorePlayer[x][y] = Feld[x][y];
1771 #if DEBUG_INIT_PLAYER
1774 printf("- player element %d activated", player->element_nr);
1775 printf(" (local player is %d and currently %s)\n",
1776 local_player->element_nr,
1777 local_player->active ? "active" : "not active");
1782 Feld[x][y] = EL_EMPTY;
1784 player->jx = player->last_jx = x;
1785 player->jy = player->last_jy = y;
1788 // always check if player was just killed and should be reanimated
1790 int player_nr = GET_PLAYER_NR(element);
1791 struct PlayerInfo *player = &stored_player[player_nr];
1793 if (player->active && player->killed)
1794 player->reanimated = TRUE; // if player was just killed, reanimate him
1798 static void InitField(int x, int y, boolean init_game)
1800 int element = Feld[x][y];
1809 InitPlayerField(x, y, element, init_game);
1812 case EL_SOKOBAN_FIELD_PLAYER:
1813 element = Feld[x][y] = EL_PLAYER_1;
1814 InitField(x, y, init_game);
1816 element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1817 InitField(x, y, init_game);
1820 case EL_SOKOBAN_FIELD_EMPTY:
1821 IncrementSokobanFieldsNeeded();
1824 case EL_SOKOBAN_OBJECT:
1825 IncrementSokobanObjectsNeeded();
1829 if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID)
1830 Feld[x][y] = EL_ACID_POOL_TOPLEFT;
1831 else if (x > 0 && Feld[x-1][y] == EL_ACID)
1832 Feld[x][y] = EL_ACID_POOL_TOPRIGHT;
1833 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPLEFT)
1834 Feld[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1835 else if (y > 0 && Feld[x][y-1] == EL_ACID)
1836 Feld[x][y] = EL_ACID_POOL_BOTTOM;
1837 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1838 Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1847 case EL_SPACESHIP_RIGHT:
1848 case EL_SPACESHIP_UP:
1849 case EL_SPACESHIP_LEFT:
1850 case EL_SPACESHIP_DOWN:
1851 case EL_BD_BUTTERFLY:
1852 case EL_BD_BUTTERFLY_RIGHT:
1853 case EL_BD_BUTTERFLY_UP:
1854 case EL_BD_BUTTERFLY_LEFT:
1855 case EL_BD_BUTTERFLY_DOWN:
1857 case EL_BD_FIREFLY_RIGHT:
1858 case EL_BD_FIREFLY_UP:
1859 case EL_BD_FIREFLY_LEFT:
1860 case EL_BD_FIREFLY_DOWN:
1861 case EL_PACMAN_RIGHT:
1863 case EL_PACMAN_LEFT:
1864 case EL_PACMAN_DOWN:
1866 case EL_YAMYAM_LEFT:
1867 case EL_YAMYAM_RIGHT:
1869 case EL_YAMYAM_DOWN:
1870 case EL_DARK_YAMYAM:
1873 case EL_SP_SNIKSNAK:
1874 case EL_SP_ELECTRON:
1880 case EL_SPRING_LEFT:
1881 case EL_SPRING_RIGHT:
1885 case EL_AMOEBA_FULL:
1890 case EL_AMOEBA_DROP:
1891 if (y == lev_fieldy - 1)
1893 Feld[x][y] = EL_AMOEBA_GROWING;
1894 Store[x][y] = EL_AMOEBA_WET;
1898 case EL_DYNAMITE_ACTIVE:
1899 case EL_SP_DISK_RED_ACTIVE:
1900 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1901 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1902 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1903 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1904 MovDelay[x][y] = 96;
1907 case EL_EM_DYNAMITE_ACTIVE:
1908 MovDelay[x][y] = 32;
1912 game.lights_still_needed++;
1916 game.friends_still_needed++;
1921 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1924 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1925 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1926 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1927 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1928 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1929 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1930 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1931 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1932 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1933 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1934 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1935 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1938 int belt_nr = getBeltNrFromBeltSwitchElement(Feld[x][y]);
1939 int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]);
1940 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[x][y]);
1942 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1944 game.belt_dir[belt_nr] = belt_dir;
1945 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1947 else // more than one switch -- set it like the first switch
1949 Feld[x][y] = Feld[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1954 case EL_LIGHT_SWITCH_ACTIVE:
1956 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1959 case EL_INVISIBLE_STEELWALL:
1960 case EL_INVISIBLE_WALL:
1961 case EL_INVISIBLE_SAND:
1962 if (game.light_time_left > 0 ||
1963 game.lenses_time_left > 0)
1964 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
1967 case EL_EMC_MAGIC_BALL:
1968 if (game.ball_active)
1969 Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1972 case EL_EMC_MAGIC_BALL_SWITCH:
1973 if (game.ball_active)
1974 Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1977 case EL_TRIGGER_PLAYER:
1978 case EL_TRIGGER_ELEMENT:
1979 case EL_TRIGGER_CE_VALUE:
1980 case EL_TRIGGER_CE_SCORE:
1982 case EL_ANY_ELEMENT:
1983 case EL_CURRENT_CE_VALUE:
1984 case EL_CURRENT_CE_SCORE:
2001 // reference elements should not be used on the playfield
2002 Feld[x][y] = EL_EMPTY;
2006 if (IS_CUSTOM_ELEMENT(element))
2008 if (CAN_MOVE(element))
2011 if (!element_info[element].use_last_ce_value || init_game)
2012 CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
2014 else if (IS_GROUP_ELEMENT(element))
2016 Feld[x][y] = GetElementFromGroupElement(element);
2018 InitField(x, y, init_game);
2025 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2028 static void InitField_WithBug1(int x, int y, boolean init_game)
2030 InitField(x, y, init_game);
2032 // not needed to call InitMovDir() -- already done by InitField()!
2033 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2034 CAN_MOVE(Feld[x][y]))
2038 static void InitField_WithBug2(int x, int y, boolean init_game)
2040 int old_element = Feld[x][y];
2042 InitField(x, y, init_game);
2044 // not needed to call InitMovDir() -- already done by InitField()!
2045 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2046 CAN_MOVE(old_element) &&
2047 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2050 /* this case is in fact a combination of not less than three bugs:
2051 first, it calls InitMovDir() for elements that can move, although this is
2052 already done by InitField(); then, it checks the element that was at this
2053 field _before_ the call to InitField() (which can change it); lastly, it
2054 was not called for "mole with direction" elements, which were treated as
2055 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2059 static int get_key_element_from_nr(int key_nr)
2061 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2062 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2063 EL_EM_KEY_1 : EL_KEY_1);
2065 return key_base_element + key_nr;
2068 static int get_next_dropped_element(struct PlayerInfo *player)
2070 return (player->inventory_size > 0 ?
2071 player->inventory_element[player->inventory_size - 1] :
2072 player->inventory_infinite_element != EL_UNDEFINED ?
2073 player->inventory_infinite_element :
2074 player->dynabombs_left > 0 ?
2075 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2079 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2081 // pos >= 0: get element from bottom of the stack;
2082 // pos < 0: get element from top of the stack
2086 int min_inventory_size = -pos;
2087 int inventory_pos = player->inventory_size - min_inventory_size;
2088 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2090 return (player->inventory_size >= min_inventory_size ?
2091 player->inventory_element[inventory_pos] :
2092 player->inventory_infinite_element != EL_UNDEFINED ?
2093 player->inventory_infinite_element :
2094 player->dynabombs_left >= min_dynabombs_left ?
2095 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2100 int min_dynabombs_left = pos + 1;
2101 int min_inventory_size = pos + 1 - player->dynabombs_left;
2102 int inventory_pos = pos - player->dynabombs_left;
2104 return (player->inventory_infinite_element != EL_UNDEFINED ?
2105 player->inventory_infinite_element :
2106 player->dynabombs_left >= min_dynabombs_left ?
2107 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2108 player->inventory_size >= min_inventory_size ?
2109 player->inventory_element[inventory_pos] :
2114 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2116 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2117 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2120 if (gpo1->sort_priority != gpo2->sort_priority)
2121 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2123 compare_result = gpo1->nr - gpo2->nr;
2125 return compare_result;
2128 int getPlayerInventorySize(int player_nr)
2130 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2131 return game_em.ply[player_nr]->dynamite;
2132 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2133 return game_sp.red_disk_count;
2135 return stored_player[player_nr].inventory_size;
2138 static void InitGameControlValues(void)
2142 for (i = 0; game_panel_controls[i].nr != -1; i++)
2144 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2145 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2146 struct TextPosInfo *pos = gpc->pos;
2148 int type = gpc->type;
2152 Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
2153 Error(ERR_EXIT, "this should not happen -- please debug");
2156 // force update of game controls after initialization
2157 gpc->value = gpc->last_value = -1;
2158 gpc->frame = gpc->last_frame = -1;
2159 gpc->gfx_frame = -1;
2161 // determine panel value width for later calculation of alignment
2162 if (type == TYPE_INTEGER || type == TYPE_STRING)
2164 pos->width = pos->size * getFontWidth(pos->font);
2165 pos->height = getFontHeight(pos->font);
2167 else if (type == TYPE_ELEMENT)
2169 pos->width = pos->size;
2170 pos->height = pos->size;
2173 // fill structure for game panel draw order
2175 gpo->sort_priority = pos->sort_priority;
2178 // sort game panel controls according to sort_priority and control number
2179 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2180 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2183 static void UpdatePlayfieldElementCount(void)
2185 boolean use_element_count = FALSE;
2188 // first check if it is needed at all to calculate playfield element count
2189 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2190 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2191 use_element_count = TRUE;
2193 if (!use_element_count)
2196 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2197 element_info[i].element_count = 0;
2199 SCAN_PLAYFIELD(x, y)
2201 element_info[Feld[x][y]].element_count++;
2204 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2205 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2206 if (IS_IN_GROUP(j, i))
2207 element_info[EL_GROUP_START + i].element_count +=
2208 element_info[j].element_count;
2211 static void UpdateGameControlValues(void)
2214 int time = (game.LevelSolved ?
2215 game.LevelSolved_CountingTime :
2216 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2218 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2219 game_sp.time_played :
2220 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2221 game_mm.energy_left :
2222 game.no_time_limit ? TimePlayed : TimeLeft);
2223 int score = (game.LevelSolved ?
2224 game.LevelSolved_CountingScore :
2225 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2226 game_em.lev->score :
2227 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2229 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2232 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2233 game_em.lev->gems_needed :
2234 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2235 game_sp.infotrons_still_needed :
2236 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2237 game_mm.kettles_still_needed :
2238 game.gems_still_needed);
2239 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2240 game_em.lev->gems_needed > 0 :
2241 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2242 game_sp.infotrons_still_needed > 0 :
2243 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2244 game_mm.kettles_still_needed > 0 ||
2245 game_mm.lights_still_needed > 0 :
2246 game.gems_still_needed > 0 ||
2247 game.sokoban_fields_still_needed > 0 ||
2248 game.sokoban_objects_still_needed > 0 ||
2249 game.lights_still_needed > 0);
2250 int health = (game.LevelSolved ?
2251 game.LevelSolved_CountingHealth :
2252 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2253 MM_HEALTH(game_mm.laser_overload_value) :
2256 UpdatePlayfieldElementCount();
2258 // update game panel control values
2260 // used instead of "level_nr" (for network games)
2261 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2262 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2264 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2265 for (i = 0; i < MAX_NUM_KEYS; i++)
2266 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2267 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2268 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2270 if (game.centered_player_nr == -1)
2272 for (i = 0; i < MAX_PLAYERS; i++)
2274 // only one player in Supaplex game engine
2275 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2278 for (k = 0; k < MAX_NUM_KEYS; k++)
2280 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2282 if (game_em.ply[i]->keys & (1 << k))
2283 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2284 get_key_element_from_nr(k);
2286 else if (stored_player[i].key[k])
2287 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2288 get_key_element_from_nr(k);
2291 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2292 getPlayerInventorySize(i);
2294 if (stored_player[i].num_white_keys > 0)
2295 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2298 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2299 stored_player[i].num_white_keys;
2304 int player_nr = game.centered_player_nr;
2306 for (k = 0; k < MAX_NUM_KEYS; k++)
2308 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2310 if (game_em.ply[player_nr]->keys & (1 << k))
2311 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2312 get_key_element_from_nr(k);
2314 else if (stored_player[player_nr].key[k])
2315 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2316 get_key_element_from_nr(k);
2319 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2320 getPlayerInventorySize(player_nr);
2322 if (stored_player[player_nr].num_white_keys > 0)
2323 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2325 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2326 stored_player[player_nr].num_white_keys;
2329 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2331 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2332 get_inventory_element_from_pos(local_player, i);
2333 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2334 get_inventory_element_from_pos(local_player, -i - 1);
2337 game_panel_controls[GAME_PANEL_SCORE].value = score;
2338 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2340 game_panel_controls[GAME_PANEL_TIME].value = time;
2342 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2343 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2344 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2346 if (level.time == 0)
2347 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2349 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2351 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2352 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2354 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2356 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2357 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2359 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2360 local_player->shield_normal_time_left;
2361 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2362 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2364 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2365 local_player->shield_deadly_time_left;
2367 game_panel_controls[GAME_PANEL_EXIT].value =
2368 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2370 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2371 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2372 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2373 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2374 EL_EMC_MAGIC_BALL_SWITCH);
2376 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2377 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2378 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2379 game.light_time_left;
2381 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2382 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2383 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2384 game.timegate_time_left;
2386 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2387 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2389 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2390 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2391 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2392 game.lenses_time_left;
2394 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2395 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2396 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2397 game.magnify_time_left;
2399 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2400 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2401 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2402 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2403 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2404 EL_BALLOON_SWITCH_NONE);
2406 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2407 local_player->dynabomb_count;
2408 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2409 local_player->dynabomb_size;
2410 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2411 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2413 game_panel_controls[GAME_PANEL_PENGUINS].value =
2414 game.friends_still_needed;
2416 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2417 game.sokoban_objects_still_needed;
2418 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2419 game.sokoban_fields_still_needed;
2421 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2422 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2424 for (i = 0; i < NUM_BELTS; i++)
2426 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2427 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2428 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2429 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2430 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2433 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2434 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2435 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2436 game.magic_wall_time_left;
2438 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2439 local_player->gravity;
2441 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2442 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2444 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2445 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2446 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2447 game.panel.element[i].id : EL_UNDEFINED);
2449 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2450 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2451 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2452 element_info[game.panel.element_count[i].id].element_count : 0);
2454 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2455 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2456 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2457 element_info[game.panel.ce_score[i].id].collect_score : 0);
2459 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2460 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2461 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2462 element_info[game.panel.ce_score_element[i].id].collect_score :
2465 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2466 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2467 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2469 // update game panel control frames
2471 for (i = 0; game_panel_controls[i].nr != -1; i++)
2473 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2475 if (gpc->type == TYPE_ELEMENT)
2477 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2479 int last_anim_random_frame = gfx.anim_random_frame;
2480 int element = gpc->value;
2481 int graphic = el2panelimg(element);
2483 if (gpc->value != gpc->last_value)
2486 gpc->gfx_random = INIT_GFX_RANDOM();
2492 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2493 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2494 gpc->gfx_random = INIT_GFX_RANDOM();
2497 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2498 gfx.anim_random_frame = gpc->gfx_random;
2500 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2501 gpc->gfx_frame = element_info[element].collect_score;
2503 gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
2506 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2507 gfx.anim_random_frame = last_anim_random_frame;
2510 else if (gpc->type == TYPE_GRAPHIC)
2512 if (gpc->graphic != IMG_UNDEFINED)
2514 int last_anim_random_frame = gfx.anim_random_frame;
2515 int graphic = gpc->graphic;
2517 if (gpc->value != gpc->last_value)
2520 gpc->gfx_random = INIT_GFX_RANDOM();
2526 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2527 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2528 gpc->gfx_random = INIT_GFX_RANDOM();
2531 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2532 gfx.anim_random_frame = gpc->gfx_random;
2534 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2536 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2537 gfx.anim_random_frame = last_anim_random_frame;
2543 static void DisplayGameControlValues(void)
2545 boolean redraw_panel = FALSE;
2548 for (i = 0; game_panel_controls[i].nr != -1; i++)
2550 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2552 if (PANEL_DEACTIVATED(gpc->pos))
2555 if (gpc->value == gpc->last_value &&
2556 gpc->frame == gpc->last_frame)
2559 redraw_panel = TRUE;
2565 // copy default game door content to main double buffer
2567 // !!! CHECK AGAIN !!!
2568 SetPanelBackground();
2569 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2570 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2572 // redraw game control buttons
2573 RedrawGameButtons();
2575 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2577 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2579 int nr = game_panel_order[i].nr;
2580 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2581 struct TextPosInfo *pos = gpc->pos;
2582 int type = gpc->type;
2583 int value = gpc->value;
2584 int frame = gpc->frame;
2585 int size = pos->size;
2586 int font = pos->font;
2587 boolean draw_masked = pos->draw_masked;
2588 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2590 if (PANEL_DEACTIVATED(pos))
2593 gpc->last_value = value;
2594 gpc->last_frame = frame;
2596 if (type == TYPE_INTEGER)
2598 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2599 nr == GAME_PANEL_TIME)
2601 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2603 if (use_dynamic_size) // use dynamic number of digits
2605 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2606 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2607 int size2 = size1 + 1;
2608 int font1 = pos->font;
2609 int font2 = pos->font_alt;
2611 size = (value < value_change ? size1 : size2);
2612 font = (value < value_change ? font1 : font2);
2616 // correct text size if "digits" is zero or less
2618 size = strlen(int2str(value, size));
2620 // dynamically correct text alignment
2621 pos->width = size * getFontWidth(font);
2623 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2624 int2str(value, size), font, mask_mode);
2626 else if (type == TYPE_ELEMENT)
2628 int element, graphic;
2632 int dst_x = PANEL_XPOS(pos);
2633 int dst_y = PANEL_YPOS(pos);
2635 if (value != EL_UNDEFINED && value != EL_EMPTY)
2638 graphic = el2panelimg(value);
2640 // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
2642 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2645 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2648 width = graphic_info[graphic].width * size / TILESIZE;
2649 height = graphic_info[graphic].height * size / TILESIZE;
2652 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2655 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2659 else if (type == TYPE_GRAPHIC)
2661 int graphic = gpc->graphic;
2662 int graphic_active = gpc->graphic_active;
2666 int dst_x = PANEL_XPOS(pos);
2667 int dst_y = PANEL_YPOS(pos);
2668 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2669 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2671 if (graphic != IMG_UNDEFINED && !skip)
2673 if (pos->style == STYLE_REVERSE)
2674 value = 100 - value;
2676 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2678 if (pos->direction & MV_HORIZONTAL)
2680 width = graphic_info[graphic_active].width * value / 100;
2681 height = graphic_info[graphic_active].height;
2683 if (pos->direction == MV_LEFT)
2685 src_x += graphic_info[graphic_active].width - width;
2686 dst_x += graphic_info[graphic_active].width - width;
2691 width = graphic_info[graphic_active].width;
2692 height = graphic_info[graphic_active].height * value / 100;
2694 if (pos->direction == MV_UP)
2696 src_y += graphic_info[graphic_active].height - height;
2697 dst_y += graphic_info[graphic_active].height - height;
2702 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2705 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2708 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2710 if (pos->direction & MV_HORIZONTAL)
2712 if (pos->direction == MV_RIGHT)
2719 dst_x = PANEL_XPOS(pos);
2722 width = graphic_info[graphic].width - width;
2726 if (pos->direction == MV_DOWN)
2733 dst_y = PANEL_YPOS(pos);
2736 height = graphic_info[graphic].height - height;
2740 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2743 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2747 else if (type == TYPE_STRING)
2749 boolean active = (value != 0);
2750 char *state_normal = "off";
2751 char *state_active = "on";
2752 char *state = (active ? state_active : state_normal);
2753 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2754 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2755 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2756 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2758 if (nr == GAME_PANEL_GRAVITY_STATE)
2760 int font1 = pos->font; // (used for normal state)
2761 int font2 = pos->font_alt; // (used for active state)
2763 font = (active ? font2 : font1);
2772 // don't truncate output if "chars" is zero or less
2775 // dynamically correct text alignment
2776 pos->width = size * getFontWidth(font);
2779 s_cut = getStringCopyN(s, size);
2781 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2782 s_cut, font, mask_mode);
2788 redraw_mask |= REDRAW_DOOR_1;
2791 SetGameStatus(GAME_MODE_PLAYING);
2794 void UpdateAndDisplayGameControlValues(void)
2796 if (tape.deactivate_display)
2799 UpdateGameControlValues();
2800 DisplayGameControlValues();
2804 static void UpdateGameDoorValues(void)
2806 UpdateGameControlValues();
2810 void DrawGameDoorValues(void)
2812 DisplayGameControlValues();
2816 // ============================================================================
2818 // ----------------------------------------------------------------------------
2819 // initialize game engine due to level / tape version number
2820 // ============================================================================
2822 static void InitGameEngine(void)
2824 int i, j, k, l, x, y;
2826 // set game engine from tape file when re-playing, else from level file
2827 game.engine_version = (tape.playing ? tape.engine_version :
2828 level.game_version);
2830 // set single or multi-player game mode (needed for re-playing tapes)
2831 game.team_mode = setup.team_mode;
2835 int num_players = 0;
2837 for (i = 0; i < MAX_PLAYERS; i++)
2838 if (tape.player_participates[i])
2841 // multi-player tapes contain input data for more than one player
2842 game.team_mode = (num_players > 1);
2846 printf("level %d: level.game_version == %06d\n", level_nr,
2847 level.game_version);
2848 printf(" tape.file_version == %06d\n",
2850 printf(" tape.game_version == %06d\n",
2852 printf(" tape.engine_version == %06d\n",
2853 tape.engine_version);
2854 printf(" => game.engine_version == %06d [tape mode: %s]\n",
2855 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2858 // --------------------------------------------------------------------------
2859 // set flags for bugs and changes according to active game engine version
2860 // --------------------------------------------------------------------------
2864 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2866 Bug was introduced in version:
2869 Bug was fixed in version:
2873 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2874 but the property "can fall" was missing, which caused some levels to be
2875 unsolvable. This was fixed in version 4.2.0.0.
2877 Affected levels/tapes:
2878 An example for a tape that was fixed by this bugfix is tape 029 from the
2879 level set "rnd_sam_bateman".
2880 The wrong behaviour will still be used for all levels or tapes that were
2881 created/recorded with it. An example for this is tape 023 from the level
2882 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2885 boolean use_amoeba_dropping_cannot_fall_bug =
2886 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2887 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2889 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2890 tape.game_version < VERSION_IDENT(4,2,0,0)));
2893 Summary of bugfix/change:
2894 Fixed move speed of elements entering or leaving magic wall.
2896 Fixed/changed in version:
2900 Before 2.0.1, move speed of elements entering or leaving magic wall was
2901 twice as fast as it is now.
2902 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2904 Affected levels/tapes:
2905 The first condition is generally needed for all levels/tapes before version
2906 2.0.1, which might use the old behaviour before it was changed; known tapes
2907 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2908 The second condition is an exception from the above case and is needed for
2909 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2910 above, but before it was known that this change would break tapes like the
2911 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2912 although the engine version while recording maybe was before 2.0.1. There
2913 are a lot of tapes that are affected by this exception, like tape 006 from
2914 the level set "rnd_conor_mancone".
2917 boolean use_old_move_stepsize_for_magic_wall =
2918 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2920 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2921 tape.game_version < VERSION_IDENT(4,2,0,0)));
2924 Summary of bugfix/change:
2925 Fixed handling for custom elements that change when pushed by the player.
2927 Fixed/changed in version:
2931 Before 3.1.0, custom elements that "change when pushing" changed directly
2932 after the player started pushing them (until then handled in "DigField()").
2933 Since 3.1.0, these custom elements are not changed until the "pushing"
2934 move of the element is finished (now handled in "ContinueMoving()").
2936 Affected levels/tapes:
2937 The first condition is generally needed for all levels/tapes before version
2938 3.1.0, which might use the old behaviour before it was changed; known tapes
2939 that are affected are some tapes from the level set "Walpurgis Gardens" by
2941 The second condition is an exception from the above case and is needed for
2942 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2943 above (including some development versions of 3.1.0), but before it was
2944 known that this change would break tapes like the above and was fixed in
2945 3.1.1, so that the changed behaviour was active although the engine version
2946 while recording maybe was before 3.1.0. There is at least one tape that is
2947 affected by this exception, which is the tape for the one-level set "Bug
2948 Machine" by Juergen Bonhagen.
2951 game.use_change_when_pushing_bug =
2952 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2954 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2955 tape.game_version < VERSION_IDENT(3,1,1,0)));
2958 Summary of bugfix/change:
2959 Fixed handling for blocking the field the player leaves when moving.
2961 Fixed/changed in version:
2965 Before 3.1.1, when "block last field when moving" was enabled, the field
2966 the player is leaving when moving was blocked for the time of the move,
2967 and was directly unblocked afterwards. This resulted in the last field
2968 being blocked for exactly one less than the number of frames of one player
2969 move. Additionally, even when blocking was disabled, the last field was
2970 blocked for exactly one frame.
2971 Since 3.1.1, due to changes in player movement handling, the last field
2972 is not blocked at all when blocking is disabled. When blocking is enabled,
2973 the last field is blocked for exactly the number of frames of one player
2974 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2975 last field is blocked for exactly one more than the number of frames of
2978 Affected levels/tapes:
2979 (!!! yet to be determined -- probably many !!!)
2982 game.use_block_last_field_bug =
2983 (game.engine_version < VERSION_IDENT(3,1,1,0));
2985 /* various special flags and settings for native Emerald Mine game engine */
2987 game_em.use_single_button =
2988 (game.engine_version > VERSION_IDENT(4,0,0,2));
2990 game_em.use_snap_key_bug =
2991 (game.engine_version < VERSION_IDENT(4,0,1,0));
2993 game_em.use_random_bug =
2994 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
2996 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
2998 game_em.use_old_explosions = use_old_em_engine;
2999 game_em.use_old_android = use_old_em_engine;
3000 game_em.use_old_push_elements = use_old_em_engine;
3001 game_em.use_old_push_into_acid = use_old_em_engine;
3003 game_em.use_wrap_around = !use_old_em_engine;
3005 // --------------------------------------------------------------------------
3007 // set maximal allowed number of custom element changes per game frame
3008 game.max_num_changes_per_frame = 1;
3010 // default scan direction: scan playfield from top/left to bottom/right
3011 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3013 // dynamically adjust element properties according to game engine version
3014 InitElementPropertiesEngine(game.engine_version);
3016 // ---------- initialize special element properties -------------------------
3018 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3019 if (use_amoeba_dropping_cannot_fall_bug)
3020 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3022 // ---------- initialize player's initial move delay ------------------------
3024 // dynamically adjust player properties according to level information
3025 for (i = 0; i < MAX_PLAYERS; i++)
3026 game.initial_move_delay_value[i] =
3027 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3029 // dynamically adjust player properties according to game engine version
3030 for (i = 0; i < MAX_PLAYERS; i++)
3031 game.initial_move_delay[i] =
3032 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3033 game.initial_move_delay_value[i] : 0);
3035 // ---------- initialize player's initial push delay ------------------------
3037 // dynamically adjust player properties according to game engine version
3038 game.initial_push_delay_value =
3039 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3041 // ---------- initialize changing elements ----------------------------------
3043 // initialize changing elements information
3044 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3046 struct ElementInfo *ei = &element_info[i];
3048 // this pointer might have been changed in the level editor
3049 ei->change = &ei->change_page[0];
3051 if (!IS_CUSTOM_ELEMENT(i))
3053 ei->change->target_element = EL_EMPTY_SPACE;
3054 ei->change->delay_fixed = 0;
3055 ei->change->delay_random = 0;
3056 ei->change->delay_frames = 1;
3059 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3061 ei->has_change_event[j] = FALSE;
3063 ei->event_page_nr[j] = 0;
3064 ei->event_page[j] = &ei->change_page[0];
3068 // add changing elements from pre-defined list
3069 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3071 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3072 struct ElementInfo *ei = &element_info[ch_delay->element];
3074 ei->change->target_element = ch_delay->target_element;
3075 ei->change->delay_fixed = ch_delay->change_delay;
3077 ei->change->pre_change_function = ch_delay->pre_change_function;
3078 ei->change->change_function = ch_delay->change_function;
3079 ei->change->post_change_function = ch_delay->post_change_function;
3081 ei->change->can_change = TRUE;
3082 ei->change->can_change_or_has_action = TRUE;
3084 ei->has_change_event[CE_DELAY] = TRUE;
3086 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3087 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3090 // ---------- initialize internal run-time variables ------------------------
3092 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3094 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3096 for (j = 0; j < ei->num_change_pages; j++)
3098 ei->change_page[j].can_change_or_has_action =
3099 (ei->change_page[j].can_change |
3100 ei->change_page[j].has_action);
3104 // add change events from custom element configuration
3105 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3107 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3109 for (j = 0; j < ei->num_change_pages; j++)
3111 if (!ei->change_page[j].can_change_or_has_action)
3114 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3116 // only add event page for the first page found with this event
3117 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3119 ei->has_change_event[k] = TRUE;
3121 ei->event_page_nr[k] = j;
3122 ei->event_page[k] = &ei->change_page[j];
3128 // ---------- initialize reference elements in change conditions ------------
3130 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3132 int element = EL_CUSTOM_START + i;
3133 struct ElementInfo *ei = &element_info[element];
3135 for (j = 0; j < ei->num_change_pages; j++)
3137 int trigger_element = ei->change_page[j].initial_trigger_element;
3139 if (trigger_element >= EL_PREV_CE_8 &&
3140 trigger_element <= EL_NEXT_CE_8)
3141 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3143 ei->change_page[j].trigger_element = trigger_element;
3147 // ---------- initialize run-time trigger player and element ----------------
3149 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3151 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3153 for (j = 0; j < ei->num_change_pages; j++)
3155 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3156 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3157 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3158 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3159 ei->change_page[j].actual_trigger_ce_value = 0;
3160 ei->change_page[j].actual_trigger_ce_score = 0;
3164 // ---------- initialize trigger events -------------------------------------
3166 // initialize trigger events information
3167 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3168 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3169 trigger_events[i][j] = FALSE;
3171 // add trigger events from element change event properties
3172 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3174 struct ElementInfo *ei = &element_info[i];
3176 for (j = 0; j < ei->num_change_pages; j++)
3178 if (!ei->change_page[j].can_change_or_has_action)
3181 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3183 int trigger_element = ei->change_page[j].trigger_element;
3185 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3187 if (ei->change_page[j].has_event[k])
3189 if (IS_GROUP_ELEMENT(trigger_element))
3191 struct ElementGroupInfo *group =
3192 element_info[trigger_element].group;
3194 for (l = 0; l < group->num_elements_resolved; l++)
3195 trigger_events[group->element_resolved[l]][k] = TRUE;
3197 else if (trigger_element == EL_ANY_ELEMENT)
3198 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3199 trigger_events[l][k] = TRUE;
3201 trigger_events[trigger_element][k] = TRUE;
3208 // ---------- initialize push delay -----------------------------------------
3210 // initialize push delay values to default
3211 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3213 if (!IS_CUSTOM_ELEMENT(i))
3215 // set default push delay values (corrected since version 3.0.7-1)
3216 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3218 element_info[i].push_delay_fixed = 2;
3219 element_info[i].push_delay_random = 8;
3223 element_info[i].push_delay_fixed = 8;
3224 element_info[i].push_delay_random = 8;
3229 // set push delay value for certain elements from pre-defined list
3230 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3232 int e = push_delay_list[i].element;
3234 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3235 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3238 // set push delay value for Supaplex elements for newer engine versions
3239 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3241 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3243 if (IS_SP_ELEMENT(i))
3245 // set SP push delay to just enough to push under a falling zonk
3246 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3248 element_info[i].push_delay_fixed = delay;
3249 element_info[i].push_delay_random = 0;
3254 // ---------- initialize move stepsize --------------------------------------
3256 // initialize move stepsize values to default
3257 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3258 if (!IS_CUSTOM_ELEMENT(i))
3259 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3261 // set move stepsize value for certain elements from pre-defined list
3262 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3264 int e = move_stepsize_list[i].element;
3266 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3268 // set move stepsize value for certain elements for older engine versions
3269 if (use_old_move_stepsize_for_magic_wall)
3271 if (e == EL_MAGIC_WALL_FILLING ||
3272 e == EL_MAGIC_WALL_EMPTYING ||
3273 e == EL_BD_MAGIC_WALL_FILLING ||
3274 e == EL_BD_MAGIC_WALL_EMPTYING)
3275 element_info[e].move_stepsize *= 2;
3279 // ---------- initialize collect score --------------------------------------
3281 // initialize collect score values for custom elements from initial value
3282 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3283 if (IS_CUSTOM_ELEMENT(i))
3284 element_info[i].collect_score = element_info[i].collect_score_initial;
3286 // ---------- initialize collect count --------------------------------------
3288 // initialize collect count values for non-custom elements
3289 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3290 if (!IS_CUSTOM_ELEMENT(i))
3291 element_info[i].collect_count_initial = 0;
3293 // add collect count values for all elements from pre-defined list
3294 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3295 element_info[collect_count_list[i].element].collect_count_initial =
3296 collect_count_list[i].count;
3298 // ---------- initialize access direction -----------------------------------
3300 // initialize access direction values to default (access from every side)
3301 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3302 if (!IS_CUSTOM_ELEMENT(i))
3303 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3305 // set access direction value for certain elements from pre-defined list
3306 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3307 element_info[access_direction_list[i].element].access_direction =
3308 access_direction_list[i].direction;
3310 // ---------- initialize explosion content ----------------------------------
3311 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3313 if (IS_CUSTOM_ELEMENT(i))
3316 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3318 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3320 element_info[i].content.e[x][y] =
3321 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3322 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3323 i == EL_PLAYER_3 ? EL_EMERALD :
3324 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3325 i == EL_MOLE ? EL_EMERALD_RED :
3326 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3327 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3328 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3329 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3330 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3331 i == EL_WALL_EMERALD ? EL_EMERALD :
3332 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3333 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3334 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3335 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3336 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3337 i == EL_WALL_PEARL ? EL_PEARL :
3338 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3343 // ---------- initialize recursion detection --------------------------------
3344 recursion_loop_depth = 0;
3345 recursion_loop_detected = FALSE;
3346 recursion_loop_element = EL_UNDEFINED;
3348 // ---------- initialize graphics engine ------------------------------------
3349 game.scroll_delay_value =
3350 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3351 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3352 !setup.forced_scroll_delay ? 0 :
3353 setup.scroll_delay ? setup.scroll_delay_value : 0);
3354 game.scroll_delay_value =
3355 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3357 // ---------- initialize game engine snapshots ------------------------------
3358 for (i = 0; i < MAX_PLAYERS; i++)
3359 game.snapshot.last_action[i] = 0;
3360 game.snapshot.changed_action = FALSE;
3361 game.snapshot.collected_item = FALSE;
3362 game.snapshot.mode =
3363 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3364 SNAPSHOT_MODE_EVERY_STEP :
3365 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3366 SNAPSHOT_MODE_EVERY_MOVE :
3367 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3368 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3369 game.snapshot.save_snapshot = FALSE;
3371 // ---------- initialize level time for Supaplex engine ---------------------
3372 // Supaplex levels with time limit currently unsupported -- should be added
3373 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3376 // ---------- initialize flags for handling game actions --------------------
3378 // set flags for game actions to default values
3379 game.use_key_actions = TRUE;
3380 game.use_mouse_actions = FALSE;
3382 // when using Mirror Magic game engine, handle mouse events only
3383 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3385 game.use_key_actions = FALSE;
3386 game.use_mouse_actions = TRUE;
3389 // check for custom elements with mouse click events
3390 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3392 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3394 int element = EL_CUSTOM_START + i;
3396 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3397 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3398 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3399 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3400 game.use_mouse_actions = TRUE;
3405 static int get_num_special_action(int element, int action_first,
3408 int num_special_action = 0;
3411 for (i = action_first; i <= action_last; i++)
3413 boolean found = FALSE;
3415 for (j = 0; j < NUM_DIRECTIONS; j++)
3416 if (el_act_dir2img(element, i, j) !=
3417 el_act_dir2img(element, ACTION_DEFAULT, j))
3421 num_special_action++;
3426 return num_special_action;
3430 // ============================================================================
3432 // ----------------------------------------------------------------------------
3433 // initialize and start new game
3434 // ============================================================================
3436 #if DEBUG_INIT_PLAYER
3437 static void DebugPrintPlayerStatus(char *message)
3444 printf("%s:\n", message);
3446 for (i = 0; i < MAX_PLAYERS; i++)
3448 struct PlayerInfo *player = &stored_player[i];
3450 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3454 player->connected_locally,
3455 player->connected_network,
3458 if (local_player == player)
3459 printf(" (local player)");
3468 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3469 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3470 int fade_mask = REDRAW_FIELD;
3472 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3473 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3474 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3475 int initial_move_dir = MV_DOWN;
3478 // required here to update video display before fading (FIX THIS)
3479 DrawMaskedBorder(REDRAW_DOOR_2);
3481 if (!game.restart_level)
3482 CloseDoor(DOOR_CLOSE_1);
3484 SetGameStatus(GAME_MODE_PLAYING);
3486 if (level_editor_test_game)
3487 FadeSkipNextFadeOut();
3489 FadeSetEnterScreen();
3492 fade_mask = REDRAW_ALL;
3494 FadeLevelSoundsAndMusic();
3496 ExpireSoundLoops(TRUE);
3500 if (level_editor_test_game)
3501 FadeSkipNextFadeIn();
3503 // needed if different viewport properties defined for playing
3504 ChangeViewportPropertiesIfNeeded();
3508 DrawCompleteVideoDisplay();
3510 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3513 InitGameControlValues();
3515 // initialize tape actions from game when recording tape
3518 tape.use_key_actions = game.use_key_actions;
3519 tape.use_mouse_actions = game.use_mouse_actions;
3522 // don't play tapes over network
3523 network_playing = (network.enabled && !tape.playing);
3525 for (i = 0; i < MAX_PLAYERS; i++)
3527 struct PlayerInfo *player = &stored_player[i];
3529 player->index_nr = i;
3530 player->index_bit = (1 << i);
3531 player->element_nr = EL_PLAYER_1 + i;
3533 player->present = FALSE;
3534 player->active = FALSE;
3535 player->mapped = FALSE;
3537 player->killed = FALSE;
3538 player->reanimated = FALSE;
3539 player->buried = FALSE;
3542 player->effective_action = 0;
3543 player->programmed_action = 0;
3544 player->snap_action = 0;
3546 player->mouse_action.lx = 0;
3547 player->mouse_action.ly = 0;
3548 player->mouse_action.button = 0;
3549 player->mouse_action.button_hint = 0;
3551 player->effective_mouse_action.lx = 0;
3552 player->effective_mouse_action.ly = 0;
3553 player->effective_mouse_action.button = 0;
3554 player->effective_mouse_action.button_hint = 0;
3556 for (j = 0; j < MAX_NUM_KEYS; j++)
3557 player->key[j] = FALSE;
3559 player->num_white_keys = 0;
3561 player->dynabomb_count = 0;
3562 player->dynabomb_size = 1;
3563 player->dynabombs_left = 0;
3564 player->dynabomb_xl = FALSE;
3566 player->MovDir = initial_move_dir;
3569 player->GfxDir = initial_move_dir;
3570 player->GfxAction = ACTION_DEFAULT;
3572 player->StepFrame = 0;
3574 player->initial_element = player->element_nr;
3575 player->artwork_element =
3576 (level.use_artwork_element[i] ? level.artwork_element[i] :
3577 player->element_nr);
3578 player->use_murphy = FALSE;
3580 player->block_last_field = FALSE; // initialized in InitPlayerField()
3581 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3583 player->gravity = level.initial_player_gravity[i];
3585 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3587 player->actual_frame_counter = 0;
3589 player->step_counter = 0;
3591 player->last_move_dir = initial_move_dir;
3593 player->is_active = FALSE;
3595 player->is_waiting = FALSE;
3596 player->is_moving = FALSE;
3597 player->is_auto_moving = FALSE;
3598 player->is_digging = FALSE;
3599 player->is_snapping = FALSE;
3600 player->is_collecting = FALSE;
3601 player->is_pushing = FALSE;
3602 player->is_switching = FALSE;
3603 player->is_dropping = FALSE;
3604 player->is_dropping_pressed = FALSE;
3606 player->is_bored = FALSE;
3607 player->is_sleeping = FALSE;
3609 player->was_waiting = TRUE;
3610 player->was_moving = FALSE;
3611 player->was_snapping = FALSE;
3612 player->was_dropping = FALSE;
3614 player->force_dropping = FALSE;
3616 player->frame_counter_bored = -1;
3617 player->frame_counter_sleeping = -1;
3619 player->anim_delay_counter = 0;
3620 player->post_delay_counter = 0;
3622 player->dir_waiting = initial_move_dir;
3623 player->action_waiting = ACTION_DEFAULT;
3624 player->last_action_waiting = ACTION_DEFAULT;
3625 player->special_action_bored = ACTION_DEFAULT;
3626 player->special_action_sleeping = ACTION_DEFAULT;
3628 player->switch_x = -1;
3629 player->switch_y = -1;
3631 player->drop_x = -1;
3632 player->drop_y = -1;
3634 player->show_envelope = 0;
3636 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3638 player->push_delay = -1; // initialized when pushing starts
3639 player->push_delay_value = game.initial_push_delay_value;
3641 player->drop_delay = 0;
3642 player->drop_pressed_delay = 0;
3644 player->last_jx = -1;
3645 player->last_jy = -1;
3649 player->shield_normal_time_left = 0;
3650 player->shield_deadly_time_left = 0;
3652 player->inventory_infinite_element = EL_UNDEFINED;
3653 player->inventory_size = 0;
3655 if (level.use_initial_inventory[i])
3657 for (j = 0; j < level.initial_inventory_size[i]; j++)
3659 int element = level.initial_inventory_content[i][j];
3660 int collect_count = element_info[element].collect_count_initial;
3663 if (!IS_CUSTOM_ELEMENT(element))
3666 if (collect_count == 0)
3667 player->inventory_infinite_element = element;
3669 for (k = 0; k < collect_count; k++)
3670 if (player->inventory_size < MAX_INVENTORY_SIZE)
3671 player->inventory_element[player->inventory_size++] = element;
3675 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3676 SnapField(player, 0, 0);
3678 map_player_action[i] = i;
3681 network_player_action_received = FALSE;
3683 // initial null action
3684 if (network_playing)
3685 SendToServer_MovePlayer(MV_NONE);
3690 TimeLeft = level.time;
3693 ScreenMovDir = MV_NONE;
3697 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3699 game.robot_wheel_x = -1;
3700 game.robot_wheel_y = -1;
3705 game.all_players_gone = FALSE;
3707 game.LevelSolved = FALSE;
3708 game.GameOver = FALSE;
3710 game.GamePlayed = !tape.playing;
3712 game.LevelSolved_GameWon = FALSE;
3713 game.LevelSolved_GameEnd = FALSE;
3714 game.LevelSolved_SaveTape = FALSE;
3715 game.LevelSolved_SaveScore = FALSE;
3717 game.LevelSolved_CountingTime = 0;
3718 game.LevelSolved_CountingScore = 0;
3719 game.LevelSolved_CountingHealth = 0;
3721 game.panel.active = TRUE;
3723 game.no_time_limit = (level.time == 0);
3725 game.yamyam_content_nr = 0;
3726 game.robot_wheel_active = FALSE;
3727 game.magic_wall_active = FALSE;
3728 game.magic_wall_time_left = 0;
3729 game.light_time_left = 0;
3730 game.timegate_time_left = 0;
3731 game.switchgate_pos = 0;
3732 game.wind_direction = level.wind_direction_initial;
3735 game.score_final = 0;
3737 game.health = MAX_HEALTH;
3738 game.health_final = MAX_HEALTH;
3740 game.gems_still_needed = level.gems_needed;
3741 game.sokoban_fields_still_needed = 0;
3742 game.sokoban_objects_still_needed = 0;
3743 game.lights_still_needed = 0;
3744 game.players_still_needed = 0;
3745 game.friends_still_needed = 0;
3747 game.lenses_time_left = 0;
3748 game.magnify_time_left = 0;
3750 game.ball_active = level.ball_active_initial;
3751 game.ball_content_nr = 0;
3753 game.explosions_delayed = TRUE;
3755 game.envelope_active = FALSE;
3757 for (i = 0; i < NUM_BELTS; i++)
3759 game.belt_dir[i] = MV_NONE;
3760 game.belt_dir_nr[i] = 3; // not moving, next moving left
3763 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3764 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3766 #if DEBUG_INIT_PLAYER
3767 DebugPrintPlayerStatus("Player status at level initialization");
3770 SCAN_PLAYFIELD(x, y)
3772 Feld[x][y] = Last[x][y] = level.field[x][y];
3773 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3774 ChangeDelay[x][y] = 0;
3775 ChangePage[x][y] = -1;
3776 CustomValue[x][y] = 0; // initialized in InitField()
3777 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3779 WasJustMoving[x][y] = 0;
3780 WasJustFalling[x][y] = 0;
3781 CheckCollision[x][y] = 0;
3782 CheckImpact[x][y] = 0;
3784 Pushed[x][y] = FALSE;
3786 ChangeCount[x][y] = 0;
3787 ChangeEvent[x][y] = -1;
3789 ExplodePhase[x][y] = 0;
3790 ExplodeDelay[x][y] = 0;
3791 ExplodeField[x][y] = EX_TYPE_NONE;
3793 RunnerVisit[x][y] = 0;
3794 PlayerVisit[x][y] = 0;
3797 GfxRandom[x][y] = INIT_GFX_RANDOM();
3798 GfxElement[x][y] = EL_UNDEFINED;
3799 GfxAction[x][y] = ACTION_DEFAULT;
3800 GfxDir[x][y] = MV_NONE;
3801 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3804 SCAN_PLAYFIELD(x, y)
3806 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3808 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3810 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3813 InitField(x, y, TRUE);
3815 ResetGfxAnimation(x, y);
3820 for (i = 0; i < MAX_PLAYERS; i++)
3822 struct PlayerInfo *player = &stored_player[i];
3824 // set number of special actions for bored and sleeping animation
3825 player->num_special_action_bored =
3826 get_num_special_action(player->artwork_element,
3827 ACTION_BORING_1, ACTION_BORING_LAST);
3828 player->num_special_action_sleeping =
3829 get_num_special_action(player->artwork_element,
3830 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3833 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3834 emulate_sb ? EMU_SOKOBAN :
3835 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3837 // initialize type of slippery elements
3838 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3840 if (!IS_CUSTOM_ELEMENT(i))
3842 // default: elements slip down either to the left or right randomly
3843 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3845 // SP style elements prefer to slip down on the left side
3846 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3847 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3849 // BD style elements prefer to slip down on the left side
3850 if (game.emulation == EMU_BOULDERDASH)
3851 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3855 // initialize explosion and ignition delay
3856 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3858 if (!IS_CUSTOM_ELEMENT(i))
3861 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3862 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3863 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3864 int last_phase = (num_phase + 1) * delay;
3865 int half_phase = (num_phase / 2) * delay;
3867 element_info[i].explosion_delay = last_phase - 1;
3868 element_info[i].ignition_delay = half_phase;
3870 if (i == EL_BLACK_ORB)
3871 element_info[i].ignition_delay = 1;
3875 // correct non-moving belts to start moving left
3876 for (i = 0; i < NUM_BELTS; i++)
3877 if (game.belt_dir[i] == MV_NONE)
3878 game.belt_dir_nr[i] = 3; // not moving, next moving left
3880 #if USE_NEW_PLAYER_ASSIGNMENTS
3881 // use preferred player also in local single-player mode
3882 if (!network.enabled && !game.team_mode)
3884 int new_index_nr = setup.network_player_nr;
3886 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3888 for (i = 0; i < MAX_PLAYERS; i++)
3889 stored_player[i].connected_locally = FALSE;
3891 stored_player[new_index_nr].connected_locally = TRUE;
3895 for (i = 0; i < MAX_PLAYERS; i++)
3897 stored_player[i].connected = FALSE;
3899 // in network game mode, the local player might not be the first player
3900 if (stored_player[i].connected_locally)
3901 local_player = &stored_player[i];
3904 if (!network.enabled)
3905 local_player->connected = TRUE;
3909 for (i = 0; i < MAX_PLAYERS; i++)
3910 stored_player[i].connected = tape.player_participates[i];
3912 else if (network.enabled)
3914 // add team mode players connected over the network (needed for correct
3915 // assignment of player figures from level to locally playing players)
3917 for (i = 0; i < MAX_PLAYERS; i++)
3918 if (stored_player[i].connected_network)
3919 stored_player[i].connected = TRUE;
3921 else if (game.team_mode)
3923 // try to guess locally connected team mode players (needed for correct
3924 // assignment of player figures from level to locally playing players)
3926 for (i = 0; i < MAX_PLAYERS; i++)
3927 if (setup.input[i].use_joystick ||
3928 setup.input[i].key.left != KSYM_UNDEFINED)
3929 stored_player[i].connected = TRUE;
3932 #if DEBUG_INIT_PLAYER
3933 DebugPrintPlayerStatus("Player status after level initialization");
3936 #if DEBUG_INIT_PLAYER
3938 printf("Reassigning players ...\n");
3941 // check if any connected player was not found in playfield
3942 for (i = 0; i < MAX_PLAYERS; i++)
3944 struct PlayerInfo *player = &stored_player[i];
3946 if (player->connected && !player->present)
3948 struct PlayerInfo *field_player = NULL;
3950 #if DEBUG_INIT_PLAYER
3952 printf("- looking for field player for player %d ...\n", i + 1);
3955 // assign first free player found that is present in the playfield
3957 // first try: look for unmapped playfield player that is not connected
3958 for (j = 0; j < MAX_PLAYERS; j++)
3959 if (field_player == NULL &&
3960 stored_player[j].present &&
3961 !stored_player[j].mapped &&
3962 !stored_player[j].connected)
3963 field_player = &stored_player[j];
3965 // second try: look for *any* unmapped playfield player
3966 for (j = 0; j < MAX_PLAYERS; j++)
3967 if (field_player == NULL &&
3968 stored_player[j].present &&
3969 !stored_player[j].mapped)
3970 field_player = &stored_player[j];
3972 if (field_player != NULL)
3974 int jx = field_player->jx, jy = field_player->jy;
3976 #if DEBUG_INIT_PLAYER
3978 printf("- found player %d\n", field_player->index_nr + 1);
3981 player->present = FALSE;
3982 player->active = FALSE;
3984 field_player->present = TRUE;
3985 field_player->active = TRUE;
3988 player->initial_element = field_player->initial_element;
3989 player->artwork_element = field_player->artwork_element;
3991 player->block_last_field = field_player->block_last_field;
3992 player->block_delay_adjustment = field_player->block_delay_adjustment;
3995 StorePlayer[jx][jy] = field_player->element_nr;
3997 field_player->jx = field_player->last_jx = jx;
3998 field_player->jy = field_player->last_jy = jy;
4000 if (local_player == player)
4001 local_player = field_player;
4003 map_player_action[field_player->index_nr] = i;
4005 field_player->mapped = TRUE;
4007 #if DEBUG_INIT_PLAYER
4009 printf("- map_player_action[%d] == %d\n",
4010 field_player->index_nr + 1, i + 1);
4015 if (player->connected && player->present)
4016 player->mapped = TRUE;
4019 #if DEBUG_INIT_PLAYER
4020 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4025 // check if any connected player was not found in playfield
4026 for (i = 0; i < MAX_PLAYERS; i++)
4028 struct PlayerInfo *player = &stored_player[i];
4030 if (player->connected && !player->present)
4032 for (j = 0; j < MAX_PLAYERS; j++)
4034 struct PlayerInfo *field_player = &stored_player[j];
4035 int jx = field_player->jx, jy = field_player->jy;
4037 // assign first free player found that is present in the playfield
4038 if (field_player->present && !field_player->connected)
4040 player->present = TRUE;
4041 player->active = TRUE;
4043 field_player->present = FALSE;
4044 field_player->active = FALSE;
4046 player->initial_element = field_player->initial_element;
4047 player->artwork_element = field_player->artwork_element;
4049 player->block_last_field = field_player->block_last_field;
4050 player->block_delay_adjustment = field_player->block_delay_adjustment;
4052 StorePlayer[jx][jy] = player->element_nr;
4054 player->jx = player->last_jx = jx;
4055 player->jy = player->last_jy = jy;
4065 printf("::: local_player->present == %d\n", local_player->present);
4068 // set focus to local player for network games, else to all players
4069 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4070 game.centered_player_nr_next = game.centered_player_nr;
4071 game.set_centered_player = FALSE;
4072 game.set_centered_player_wrap = FALSE;
4074 if (network_playing && tape.recording)
4076 // store client dependent player focus when recording network games
4077 tape.centered_player_nr_next = game.centered_player_nr_next;
4078 tape.set_centered_player = TRUE;
4083 // when playing a tape, eliminate all players who do not participate
4085 #if USE_NEW_PLAYER_ASSIGNMENTS
4087 if (!game.team_mode)
4089 for (i = 0; i < MAX_PLAYERS; i++)
4091 if (stored_player[i].active &&
4092 !tape.player_participates[map_player_action[i]])
4094 struct PlayerInfo *player = &stored_player[i];
4095 int jx = player->jx, jy = player->jy;
4097 #if DEBUG_INIT_PLAYER
4099 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
4102 player->active = FALSE;
4103 StorePlayer[jx][jy] = 0;
4104 Feld[jx][jy] = EL_EMPTY;
4111 for (i = 0; i < MAX_PLAYERS; i++)
4113 if (stored_player[i].active &&
4114 !tape.player_participates[i])
4116 struct PlayerInfo *player = &stored_player[i];
4117 int jx = player->jx, jy = player->jy;
4119 player->active = FALSE;
4120 StorePlayer[jx][jy] = 0;
4121 Feld[jx][jy] = EL_EMPTY;
4126 else if (!network.enabled && !game.team_mode) // && !tape.playing
4128 // when in single player mode, eliminate all but the local player
4130 for (i = 0; i < MAX_PLAYERS; i++)
4132 struct PlayerInfo *player = &stored_player[i];
4134 if (player->active && player != local_player)
4136 int jx = player->jx, jy = player->jy;
4138 player->active = FALSE;
4139 player->present = FALSE;
4141 StorePlayer[jx][jy] = 0;
4142 Feld[jx][jy] = EL_EMPTY;
4147 for (i = 0; i < MAX_PLAYERS; i++)
4148 if (stored_player[i].active)
4149 game.players_still_needed++;
4151 if (level.solved_by_one_player)
4152 game.players_still_needed = 1;
4154 // when recording the game, store which players take part in the game
4157 #if USE_NEW_PLAYER_ASSIGNMENTS
4158 for (i = 0; i < MAX_PLAYERS; i++)
4159 if (stored_player[i].connected)
4160 tape.player_participates[i] = TRUE;
4162 for (i = 0; i < MAX_PLAYERS; i++)
4163 if (stored_player[i].active)
4164 tape.player_participates[i] = TRUE;
4168 #if DEBUG_INIT_PLAYER
4169 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4172 if (BorderElement == EL_EMPTY)
4175 SBX_Right = lev_fieldx - SCR_FIELDX;
4177 SBY_Lower = lev_fieldy - SCR_FIELDY;
4182 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4184 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4187 if (full_lev_fieldx <= SCR_FIELDX)
4188 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4189 if (full_lev_fieldy <= SCR_FIELDY)
4190 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4192 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4194 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4197 // if local player not found, look for custom element that might create
4198 // the player (make some assumptions about the right custom element)
4199 if (!local_player->present)
4201 int start_x = 0, start_y = 0;
4202 int found_rating = 0;
4203 int found_element = EL_UNDEFINED;
4204 int player_nr = local_player->index_nr;
4206 SCAN_PLAYFIELD(x, y)
4208 int element = Feld[x][y];
4213 if (level.use_start_element[player_nr] &&
4214 level.start_element[player_nr] == element &&
4221 found_element = element;
4224 if (!IS_CUSTOM_ELEMENT(element))
4227 if (CAN_CHANGE(element))
4229 for (i = 0; i < element_info[element].num_change_pages; i++)
4231 // check for player created from custom element as single target
4232 content = element_info[element].change_page[i].target_element;
4233 is_player = ELEM_IS_PLAYER(content);
4235 if (is_player && (found_rating < 3 ||
4236 (found_rating == 3 && element < found_element)))
4242 found_element = element;
4247 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4249 // check for player created from custom element as explosion content
4250 content = element_info[element].content.e[xx][yy];
4251 is_player = ELEM_IS_PLAYER(content);
4253 if (is_player && (found_rating < 2 ||
4254 (found_rating == 2 && element < found_element)))
4256 start_x = x + xx - 1;
4257 start_y = y + yy - 1;
4260 found_element = element;
4263 if (!CAN_CHANGE(element))
4266 for (i = 0; i < element_info[element].num_change_pages; i++)
4268 // check for player created from custom element as extended target
4270 element_info[element].change_page[i].target_content.e[xx][yy];
4272 is_player = ELEM_IS_PLAYER(content);
4274 if (is_player && (found_rating < 1 ||
4275 (found_rating == 1 && element < found_element)))
4277 start_x = x + xx - 1;
4278 start_y = y + yy - 1;
4281 found_element = element;
4287 scroll_x = SCROLL_POSITION_X(start_x);
4288 scroll_y = SCROLL_POSITION_Y(start_y);
4292 scroll_x = SCROLL_POSITION_X(local_player->jx);
4293 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4296 // !!! FIX THIS (START) !!!
4297 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4299 InitGameEngine_EM();
4301 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4303 InitGameEngine_SP();
4305 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4307 InitGameEngine_MM();
4311 DrawLevel(REDRAW_FIELD);
4314 // after drawing the level, correct some elements
4315 if (game.timegate_time_left == 0)
4316 CloseAllOpenTimegates();
4319 // blit playfield from scroll buffer to normal back buffer for fading in
4320 BlitScreenToBitmap(backbuffer);
4321 // !!! FIX THIS (END) !!!
4323 DrawMaskedBorder(fade_mask);
4328 // full screen redraw is required at this point in the following cases:
4329 // - special editor door undrawn when game was started from level editor
4330 // - drawing area (playfield) was changed and has to be removed completely
4331 redraw_mask = REDRAW_ALL;
4335 if (!game.restart_level)
4337 // copy default game door content to main double buffer
4339 // !!! CHECK AGAIN !!!
4340 SetPanelBackground();
4341 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4342 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4345 SetPanelBackground();
4346 SetDrawBackgroundMask(REDRAW_DOOR_1);
4348 UpdateAndDisplayGameControlValues();
4350 if (!game.restart_level)
4356 CreateGameButtons();
4361 // copy actual game door content to door double buffer for OpenDoor()
4362 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4364 OpenDoor(DOOR_OPEN_ALL);
4366 KeyboardAutoRepeatOffUnlessAutoplay();
4368 #if DEBUG_INIT_PLAYER
4369 DebugPrintPlayerStatus("Player status (final)");
4378 if (!game.restart_level && !tape.playing)
4380 LevelStats_incPlayed(level_nr);
4382 SaveLevelSetup_SeriesInfo();
4385 game.restart_level = FALSE;
4386 game.restart_game_message = NULL;
4387 game.request_active = FALSE;
4389 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4390 InitGameActions_MM();
4392 SaveEngineSnapshotToListInitial();
4394 if (!game.restart_level)
4396 PlaySound(SND_GAME_STARTING);
4398 if (setup.sound_music)
4403 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4404 int actual_player_x, int actual_player_y)
4406 // this is used for non-R'n'D game engines to update certain engine values
4408 // needed to determine if sounds are played within the visible screen area
4409 scroll_x = actual_scroll_x;
4410 scroll_y = actual_scroll_y;
4412 // needed to get player position for "follow finger" playing input method
4413 local_player->jx = actual_player_x;
4414 local_player->jy = actual_player_y;
4417 void InitMovDir(int x, int y)
4419 int i, element = Feld[x][y];
4420 static int xy[4][2] =
4427 static int direction[3][4] =
4429 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4430 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4431 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4440 Feld[x][y] = EL_BUG;
4441 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4444 case EL_SPACESHIP_RIGHT:
4445 case EL_SPACESHIP_UP:
4446 case EL_SPACESHIP_LEFT:
4447 case EL_SPACESHIP_DOWN:
4448 Feld[x][y] = EL_SPACESHIP;
4449 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4452 case EL_BD_BUTTERFLY_RIGHT:
4453 case EL_BD_BUTTERFLY_UP:
4454 case EL_BD_BUTTERFLY_LEFT:
4455 case EL_BD_BUTTERFLY_DOWN:
4456 Feld[x][y] = EL_BD_BUTTERFLY;
4457 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4460 case EL_BD_FIREFLY_RIGHT:
4461 case EL_BD_FIREFLY_UP:
4462 case EL_BD_FIREFLY_LEFT:
4463 case EL_BD_FIREFLY_DOWN:
4464 Feld[x][y] = EL_BD_FIREFLY;
4465 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4468 case EL_PACMAN_RIGHT:
4470 case EL_PACMAN_LEFT:
4471 case EL_PACMAN_DOWN:
4472 Feld[x][y] = EL_PACMAN;
4473 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4476 case EL_YAMYAM_LEFT:
4477 case EL_YAMYAM_RIGHT:
4479 case EL_YAMYAM_DOWN:
4480 Feld[x][y] = EL_YAMYAM;
4481 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4484 case EL_SP_SNIKSNAK:
4485 MovDir[x][y] = MV_UP;
4488 case EL_SP_ELECTRON:
4489 MovDir[x][y] = MV_LEFT;
4496 Feld[x][y] = EL_MOLE;
4497 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4500 case EL_SPRING_LEFT:
4501 case EL_SPRING_RIGHT:
4502 Feld[x][y] = EL_SPRING;
4503 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4507 if (IS_CUSTOM_ELEMENT(element))
4509 struct ElementInfo *ei = &element_info[element];
4510 int move_direction_initial = ei->move_direction_initial;
4511 int move_pattern = ei->move_pattern;
4513 if (move_direction_initial == MV_START_PREVIOUS)
4515 if (MovDir[x][y] != MV_NONE)
4518 move_direction_initial = MV_START_AUTOMATIC;
4521 if (move_direction_initial == MV_START_RANDOM)
4522 MovDir[x][y] = 1 << RND(4);
4523 else if (move_direction_initial & MV_ANY_DIRECTION)
4524 MovDir[x][y] = move_direction_initial;
4525 else if (move_pattern == MV_ALL_DIRECTIONS ||
4526 move_pattern == MV_TURNING_LEFT ||
4527 move_pattern == MV_TURNING_RIGHT ||
4528 move_pattern == MV_TURNING_LEFT_RIGHT ||
4529 move_pattern == MV_TURNING_RIGHT_LEFT ||
4530 move_pattern == MV_TURNING_RANDOM)
4531 MovDir[x][y] = 1 << RND(4);
4532 else if (move_pattern == MV_HORIZONTAL)
4533 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4534 else if (move_pattern == MV_VERTICAL)
4535 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4536 else if (move_pattern & MV_ANY_DIRECTION)
4537 MovDir[x][y] = element_info[element].move_pattern;
4538 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4539 move_pattern == MV_ALONG_RIGHT_SIDE)
4541 // use random direction as default start direction
4542 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4543 MovDir[x][y] = 1 << RND(4);
4545 for (i = 0; i < NUM_DIRECTIONS; i++)
4547 int x1 = x + xy[i][0];
4548 int y1 = y + xy[i][1];
4550 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4552 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4553 MovDir[x][y] = direction[0][i];
4555 MovDir[x][y] = direction[1][i];
4564 MovDir[x][y] = 1 << RND(4);
4566 if (element != EL_BUG &&
4567 element != EL_SPACESHIP &&
4568 element != EL_BD_BUTTERFLY &&
4569 element != EL_BD_FIREFLY)
4572 for (i = 0; i < NUM_DIRECTIONS; i++)
4574 int x1 = x + xy[i][0];
4575 int y1 = y + xy[i][1];
4577 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4579 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4581 MovDir[x][y] = direction[0][i];
4584 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4585 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4587 MovDir[x][y] = direction[1][i];
4596 GfxDir[x][y] = MovDir[x][y];
4599 void InitAmoebaNr(int x, int y)
4602 int group_nr = AmoebeNachbarNr(x, y);
4606 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4608 if (AmoebaCnt[i] == 0)
4616 AmoebaNr[x][y] = group_nr;
4617 AmoebaCnt[group_nr]++;
4618 AmoebaCnt2[group_nr]++;
4621 static void LevelSolved(void)
4623 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4624 game.players_still_needed > 0)
4627 game.LevelSolved = TRUE;
4628 game.GameOver = TRUE;
4630 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4631 game_em.lev->score :
4632 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4635 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4636 MM_HEALTH(game_mm.laser_overload_value) :
4639 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4640 game.LevelSolved_CountingScore = game.score_final;
4641 game.LevelSolved_CountingHealth = game.health_final;
4646 static int time_count_steps;
4647 static int time, time_final;
4648 static int score, score_final;
4649 static int health, health_final;
4650 static int game_over_delay_1 = 0;
4651 static int game_over_delay_2 = 0;
4652 static int game_over_delay_3 = 0;
4653 int game_over_delay_value_1 = 50;
4654 int game_over_delay_value_2 = 25;
4655 int game_over_delay_value_3 = 50;
4657 if (!game.LevelSolved_GameWon)
4661 // do not start end game actions before the player stops moving (to exit)
4662 if (local_player->active && local_player->MovPos)
4665 game.LevelSolved_GameWon = TRUE;
4666 game.LevelSolved_SaveTape = tape.recording;
4667 game.LevelSolved_SaveScore = !tape.playing;
4671 LevelStats_incSolved(level_nr);
4673 SaveLevelSetup_SeriesInfo();
4676 if (tape.auto_play) // tape might already be stopped here
4677 tape.auto_play_level_solved = TRUE;
4681 game_over_delay_1 = 0;
4682 game_over_delay_2 = 0;
4683 game_over_delay_3 = game_over_delay_value_3;
4685 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4686 score = score_final = game.score_final;
4687 health = health_final = game.health_final;
4689 if (level.score[SC_TIME_BONUS] > 0)
4694 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4696 else if (game.no_time_limit && TimePlayed < 999)
4699 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4702 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4704 game_over_delay_1 = game_over_delay_value_1;
4706 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4709 score_final += health * level.score[SC_TIME_BONUS];
4711 game_over_delay_2 = game_over_delay_value_2;
4714 game.score_final = score_final;
4715 game.health_final = health_final;
4718 if (level_editor_test_game)
4721 score = score_final;
4723 game.LevelSolved_CountingTime = time;
4724 game.LevelSolved_CountingScore = score;
4726 game_panel_controls[GAME_PANEL_TIME].value = time;
4727 game_panel_controls[GAME_PANEL_SCORE].value = score;
4729 DisplayGameControlValues();
4732 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4734 // check if last player has left the level
4735 if (game.exit_x >= 0 &&
4738 int x = game.exit_x;
4739 int y = game.exit_y;
4740 int element = Feld[x][y];
4742 // close exit door after last player
4743 if ((game.all_players_gone &&
4744 (element == EL_EXIT_OPEN ||
4745 element == EL_SP_EXIT_OPEN ||
4746 element == EL_STEEL_EXIT_OPEN)) ||
4747 element == EL_EM_EXIT_OPEN ||
4748 element == EL_EM_STEEL_EXIT_OPEN)
4752 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4753 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4754 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4755 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4756 EL_EM_STEEL_EXIT_CLOSING);
4758 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4761 // player disappears
4762 DrawLevelField(x, y);
4765 for (i = 0; i < MAX_PLAYERS; i++)
4767 struct PlayerInfo *player = &stored_player[i];
4769 if (player->present)
4771 RemovePlayer(player);
4773 // player disappears
4774 DrawLevelField(player->jx, player->jy);
4779 PlaySound(SND_GAME_WINNING);
4782 if (game_over_delay_1 > 0)
4784 game_over_delay_1--;
4789 if (time != time_final)
4791 int time_to_go = ABS(time_final - time);
4792 int time_count_dir = (time < time_final ? +1 : -1);
4794 if (time_to_go < time_count_steps)
4795 time_count_steps = 1;
4797 time += time_count_steps * time_count_dir;
4798 score += time_count_steps * level.score[SC_TIME_BONUS];
4800 game.LevelSolved_CountingTime = time;
4801 game.LevelSolved_CountingScore = score;
4803 game_panel_controls[GAME_PANEL_TIME].value = time;
4804 game_panel_controls[GAME_PANEL_SCORE].value = score;
4806 DisplayGameControlValues();
4808 if (time == time_final)
4809 StopSound(SND_GAME_LEVELTIME_BONUS);
4810 else if (setup.sound_loops)
4811 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4813 PlaySound(SND_GAME_LEVELTIME_BONUS);
4818 if (game_over_delay_2 > 0)
4820 game_over_delay_2--;
4825 if (health != health_final)
4827 int health_count_dir = (health < health_final ? +1 : -1);
4829 health += health_count_dir;
4830 score += level.score[SC_TIME_BONUS];
4832 game.LevelSolved_CountingHealth = health;
4833 game.LevelSolved_CountingScore = score;
4835 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4836 game_panel_controls[GAME_PANEL_SCORE].value = score;
4838 DisplayGameControlValues();
4840 if (health == health_final)
4841 StopSound(SND_GAME_LEVELTIME_BONUS);
4842 else if (setup.sound_loops)
4843 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4845 PlaySound(SND_GAME_LEVELTIME_BONUS);
4850 game.panel.active = FALSE;
4852 if (game_over_delay_3 > 0)
4854 game_over_delay_3--;
4864 // used instead of "level_nr" (needed for network games)
4865 int last_level_nr = levelset.level_nr;
4868 game.LevelSolved_GameEnd = TRUE;
4870 if (game.LevelSolved_SaveTape)
4872 // make sure that request dialog to save tape does not open door again
4873 if (!global.use_envelope_request)
4874 CloseDoor(DOOR_CLOSE_1);
4876 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4879 // if no tape is to be saved, close both doors simultaneously
4880 CloseDoor(DOOR_CLOSE_ALL);
4882 if (level_editor_test_game)
4884 SetGameStatus(GAME_MODE_MAIN);
4891 if (!game.LevelSolved_SaveScore)
4893 SetGameStatus(GAME_MODE_MAIN);
4900 if (level_nr == leveldir_current->handicap_level)
4902 leveldir_current->handicap_level++;
4904 SaveLevelSetup_SeriesInfo();
4907 if (setup.increment_levels &&
4908 level_nr < leveldir_current->last_level &&
4911 level_nr++; // advance to next level
4912 TapeErase(); // start with empty tape
4914 if (setup.auto_play_next_level)
4916 LoadLevel(level_nr);
4918 SaveLevelSetup_SeriesInfo();
4922 hi_pos = NewHiScore(last_level_nr);
4924 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4926 SetGameStatus(GAME_MODE_SCORES);
4928 DrawHallOfFame(last_level_nr, hi_pos);
4930 else if (setup.auto_play_next_level && setup.increment_levels &&
4931 last_level_nr < leveldir_current->last_level &&
4934 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4938 SetGameStatus(GAME_MODE_MAIN);
4944 int NewHiScore(int level_nr)
4948 boolean one_score_entry_per_name = !program.many_scores_per_name;
4950 LoadScore(level_nr);
4952 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4953 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4956 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4958 if (game.score_final > highscore[k].Score)
4960 // player has made it to the hall of fame
4962 if (k < MAX_SCORE_ENTRIES - 1)
4964 int m = MAX_SCORE_ENTRIES - 1;
4966 if (one_score_entry_per_name)
4968 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4969 if (strEqual(setup.player_name, highscore[l].Name))
4972 if (m == k) // player's new highscore overwrites his old one
4976 for (l = m; l > k; l--)
4978 strcpy(highscore[l].Name, highscore[l - 1].Name);
4979 highscore[l].Score = highscore[l - 1].Score;
4985 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4986 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4987 highscore[k].Score = game.score_final;
4992 else if (one_score_entry_per_name &&
4993 !strncmp(setup.player_name, highscore[k].Name,
4994 MAX_PLAYER_NAME_LEN))
4995 break; // player already there with a higher score
4999 SaveScore(level_nr);
5004 static int getElementMoveStepsizeExt(int x, int y, int direction)
5006 int element = Feld[x][y];
5007 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5008 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5009 int horiz_move = (dx != 0);
5010 int sign = (horiz_move ? dx : dy);
5011 int step = sign * element_info[element].move_stepsize;
5013 // special values for move stepsize for spring and things on conveyor belt
5016 if (CAN_FALL(element) &&
5017 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
5018 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5019 else if (element == EL_SPRING)
5020 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5026 static int getElementMoveStepsize(int x, int y)
5028 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5031 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5033 if (player->GfxAction != action || player->GfxDir != dir)
5035 player->GfxAction = action;
5036 player->GfxDir = dir;
5038 player->StepFrame = 0;
5042 static void ResetGfxFrame(int x, int y)
5044 // profiling showed that "autotest" spends 10~20% of its time in this function
5045 if (DrawingDeactivatedField())
5048 int element = Feld[x][y];
5049 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5051 if (graphic_info[graphic].anim_global_sync)
5052 GfxFrame[x][y] = FrameCounter;
5053 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5054 GfxFrame[x][y] = CustomValue[x][y];
5055 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5056 GfxFrame[x][y] = element_info[element].collect_score;
5057 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5058 GfxFrame[x][y] = ChangeDelay[x][y];
5061 static void ResetGfxAnimation(int x, int y)
5063 GfxAction[x][y] = ACTION_DEFAULT;
5064 GfxDir[x][y] = MovDir[x][y];
5067 ResetGfxFrame(x, y);
5070 static void ResetRandomAnimationValue(int x, int y)
5072 GfxRandom[x][y] = INIT_GFX_RANDOM();
5075 static void InitMovingField(int x, int y, int direction)
5077 int element = Feld[x][y];
5078 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5079 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5082 boolean is_moving_before, is_moving_after;
5084 // check if element was/is moving or being moved before/after mode change
5085 is_moving_before = (WasJustMoving[x][y] != 0);
5086 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5088 // reset animation only for moving elements which change direction of moving
5089 // or which just started or stopped moving
5090 // (else CEs with property "can move" / "not moving" are reset each frame)
5091 if (is_moving_before != is_moving_after ||
5092 direction != MovDir[x][y])
5093 ResetGfxAnimation(x, y);
5095 MovDir[x][y] = direction;
5096 GfxDir[x][y] = direction;
5098 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5099 direction == MV_DOWN && CAN_FALL(element) ?
5100 ACTION_FALLING : ACTION_MOVING);
5102 // this is needed for CEs with property "can move" / "not moving"
5104 if (is_moving_after)
5106 if (Feld[newx][newy] == EL_EMPTY)
5107 Feld[newx][newy] = EL_BLOCKED;
5109 MovDir[newx][newy] = MovDir[x][y];
5111 CustomValue[newx][newy] = CustomValue[x][y];
5113 GfxFrame[newx][newy] = GfxFrame[x][y];
5114 GfxRandom[newx][newy] = GfxRandom[x][y];
5115 GfxAction[newx][newy] = GfxAction[x][y];
5116 GfxDir[newx][newy] = GfxDir[x][y];
5120 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5122 int direction = MovDir[x][y];
5123 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5124 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5130 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5132 int oldx = x, oldy = y;
5133 int direction = MovDir[x][y];
5135 if (direction == MV_LEFT)
5137 else if (direction == MV_RIGHT)
5139 else if (direction == MV_UP)
5141 else if (direction == MV_DOWN)
5144 *comes_from_x = oldx;
5145 *comes_from_y = oldy;
5148 static int MovingOrBlocked2Element(int x, int y)
5150 int element = Feld[x][y];
5152 if (element == EL_BLOCKED)
5156 Blocked2Moving(x, y, &oldx, &oldy);
5157 return Feld[oldx][oldy];
5163 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5165 // like MovingOrBlocked2Element(), but if element is moving
5166 // and (x,y) is the field the moving element is just leaving,
5167 // return EL_BLOCKED instead of the element value
5168 int element = Feld[x][y];
5170 if (IS_MOVING(x, y))
5172 if (element == EL_BLOCKED)
5176 Blocked2Moving(x, y, &oldx, &oldy);
5177 return Feld[oldx][oldy];
5186 static void RemoveField(int x, int y)
5188 Feld[x][y] = EL_EMPTY;
5194 CustomValue[x][y] = 0;
5197 ChangeDelay[x][y] = 0;
5198 ChangePage[x][y] = -1;
5199 Pushed[x][y] = FALSE;
5201 GfxElement[x][y] = EL_UNDEFINED;
5202 GfxAction[x][y] = ACTION_DEFAULT;
5203 GfxDir[x][y] = MV_NONE;
5206 static void RemoveMovingField(int x, int y)
5208 int oldx = x, oldy = y, newx = x, newy = y;
5209 int element = Feld[x][y];
5210 int next_element = EL_UNDEFINED;
5212 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5215 if (IS_MOVING(x, y))
5217 Moving2Blocked(x, y, &newx, &newy);
5219 if (Feld[newx][newy] != EL_BLOCKED)
5221 // element is moving, but target field is not free (blocked), but
5222 // already occupied by something different (example: acid pool);
5223 // in this case, only remove the moving field, but not the target
5225 RemoveField(oldx, oldy);
5227 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5229 TEST_DrawLevelField(oldx, oldy);
5234 else if (element == EL_BLOCKED)
5236 Blocked2Moving(x, y, &oldx, &oldy);
5237 if (!IS_MOVING(oldx, oldy))
5241 if (element == EL_BLOCKED &&
5242 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5243 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5244 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5245 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5246 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5247 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5248 next_element = get_next_element(Feld[oldx][oldy]);
5250 RemoveField(oldx, oldy);
5251 RemoveField(newx, newy);
5253 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5255 if (next_element != EL_UNDEFINED)
5256 Feld[oldx][oldy] = next_element;
5258 TEST_DrawLevelField(oldx, oldy);
5259 TEST_DrawLevelField(newx, newy);
5262 void DrawDynamite(int x, int y)
5264 int sx = SCREENX(x), sy = SCREENY(y);
5265 int graphic = el2img(Feld[x][y]);
5268 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5271 if (IS_WALKABLE_INSIDE(Back[x][y]))
5275 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5276 else if (Store[x][y])
5277 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5279 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5281 if (Back[x][y] || Store[x][y])
5282 DrawGraphicThruMask(sx, sy, graphic, frame);
5284 DrawGraphic(sx, sy, graphic, frame);
5287 static void CheckDynamite(int x, int y)
5289 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5293 if (MovDelay[x][y] != 0)
5296 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5302 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5307 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5309 boolean num_checked_players = 0;
5312 for (i = 0; i < MAX_PLAYERS; i++)
5314 if (stored_player[i].active)
5316 int sx = stored_player[i].jx;
5317 int sy = stored_player[i].jy;
5319 if (num_checked_players == 0)
5326 *sx1 = MIN(*sx1, sx);
5327 *sy1 = MIN(*sy1, sy);
5328 *sx2 = MAX(*sx2, sx);
5329 *sy2 = MAX(*sy2, sy);
5332 num_checked_players++;
5337 static boolean checkIfAllPlayersFitToScreen_RND(void)
5339 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5341 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5343 return (sx2 - sx1 < SCR_FIELDX &&
5344 sy2 - sy1 < SCR_FIELDY);
5347 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5349 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5351 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5353 *sx = (sx1 + sx2) / 2;
5354 *sy = (sy1 + sy2) / 2;
5357 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5358 boolean center_screen, boolean quick_relocation)
5360 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5361 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5362 boolean no_delay = (tape.warp_forward);
5363 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5364 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5365 int new_scroll_x, new_scroll_y;
5367 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5369 // case 1: quick relocation inside visible screen (without scrolling)
5376 if (!level.shifted_relocation || center_screen)
5378 // relocation _with_ centering of screen
5380 new_scroll_x = SCROLL_POSITION_X(x);
5381 new_scroll_y = SCROLL_POSITION_Y(y);
5385 // relocation _without_ centering of screen
5387 int center_scroll_x = SCROLL_POSITION_X(old_x);
5388 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5389 int offset_x = x + (scroll_x - center_scroll_x);
5390 int offset_y = y + (scroll_y - center_scroll_y);
5392 // for new screen position, apply previous offset to center position
5393 new_scroll_x = SCROLL_POSITION_X(offset_x);
5394 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5397 if (quick_relocation)
5399 // case 2: quick relocation (redraw without visible scrolling)
5401 scroll_x = new_scroll_x;
5402 scroll_y = new_scroll_y;
5409 // case 3: visible relocation (with scrolling to new position)
5411 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5413 SetVideoFrameDelay(wait_delay_value);
5415 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5417 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5418 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5420 if (dx == 0 && dy == 0) // no scrolling needed at all
5426 // set values for horizontal/vertical screen scrolling (half tile size)
5427 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5428 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5429 int pos_x = dx * TILEX / 2;
5430 int pos_y = dy * TILEY / 2;
5431 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5432 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5434 ScrollLevel(dx, dy);
5437 // scroll in two steps of half tile size to make things smoother
5438 BlitScreenToBitmapExt_RND(window, fx, fy);
5440 // scroll second step to align at full tile size
5441 BlitScreenToBitmap(window);
5447 SetVideoFrameDelay(frame_delay_value_old);
5450 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5452 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5453 int player_nr = GET_PLAYER_NR(el_player);
5454 struct PlayerInfo *player = &stored_player[player_nr];
5455 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5456 boolean no_delay = (tape.warp_forward);
5457 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5458 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5459 int old_jx = player->jx;
5460 int old_jy = player->jy;
5461 int old_element = Feld[old_jx][old_jy];
5462 int element = Feld[jx][jy];
5463 boolean player_relocated = (old_jx != jx || old_jy != jy);
5465 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5466 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5467 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5468 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5469 int leave_side_horiz = move_dir_horiz;
5470 int leave_side_vert = move_dir_vert;
5471 int enter_side = enter_side_horiz | enter_side_vert;
5472 int leave_side = leave_side_horiz | leave_side_vert;
5474 if (player->buried) // do not reanimate dead player
5477 if (!player_relocated) // no need to relocate the player
5480 if (IS_PLAYER(jx, jy)) // player already placed at new position
5482 RemoveField(jx, jy); // temporarily remove newly placed player
5483 DrawLevelField(jx, jy);
5486 if (player->present)
5488 while (player->MovPos)
5490 ScrollPlayer(player, SCROLL_GO_ON);
5491 ScrollScreen(NULL, SCROLL_GO_ON);
5493 AdvanceFrameAndPlayerCounters(player->index_nr);
5497 BackToFront_WithFrameDelay(wait_delay_value);
5500 DrawPlayer(player); // needed here only to cleanup last field
5501 DrawLevelField(player->jx, player->jy); // remove player graphic
5503 player->is_moving = FALSE;
5506 if (IS_CUSTOM_ELEMENT(old_element))
5507 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5509 player->index_bit, leave_side);
5511 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5513 player->index_bit, leave_side);
5515 Feld[jx][jy] = el_player;
5516 InitPlayerField(jx, jy, el_player, TRUE);
5518 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5519 possible that the relocation target field did not contain a player element,
5520 but a walkable element, to which the new player was relocated -- in this
5521 case, restore that (already initialized!) element on the player field */
5522 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5524 Feld[jx][jy] = element; // restore previously existing element
5527 // only visually relocate centered player
5528 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5529 FALSE, level.instant_relocation);
5531 TestIfPlayerTouchesBadThing(jx, jy);
5532 TestIfPlayerTouchesCustomElement(jx, jy);
5534 if (IS_CUSTOM_ELEMENT(element))
5535 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5536 player->index_bit, enter_side);
5538 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5539 player->index_bit, enter_side);
5541 if (player->is_switching)
5543 /* ensure that relocation while still switching an element does not cause
5544 a new element to be treated as also switched directly after relocation
5545 (this is important for teleporter switches that teleport the player to
5546 a place where another teleporter switch is in the same direction, which
5547 would then incorrectly be treated as immediately switched before the
5548 direction key that caused the switch was released) */
5550 player->switch_x += jx - old_jx;
5551 player->switch_y += jy - old_jy;
5555 static void Explode(int ex, int ey, int phase, int mode)
5561 // !!! eliminate this variable !!!
5562 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5564 if (game.explosions_delayed)
5566 ExplodeField[ex][ey] = mode;
5570 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5572 int center_element = Feld[ex][ey];
5573 int artwork_element, explosion_element; // set these values later
5575 // remove things displayed in background while burning dynamite
5576 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5579 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5581 // put moving element to center field (and let it explode there)
5582 center_element = MovingOrBlocked2Element(ex, ey);
5583 RemoveMovingField(ex, ey);
5584 Feld[ex][ey] = center_element;
5587 // now "center_element" is finally determined -- set related values now
5588 artwork_element = center_element; // for custom player artwork
5589 explosion_element = center_element; // for custom player artwork
5591 if (IS_PLAYER(ex, ey))
5593 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5595 artwork_element = stored_player[player_nr].artwork_element;
5597 if (level.use_explosion_element[player_nr])
5599 explosion_element = level.explosion_element[player_nr];
5600 artwork_element = explosion_element;
5604 if (mode == EX_TYPE_NORMAL ||
5605 mode == EX_TYPE_CENTER ||
5606 mode == EX_TYPE_CROSS)
5607 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5609 last_phase = element_info[explosion_element].explosion_delay + 1;
5611 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5613 int xx = x - ex + 1;
5614 int yy = y - ey + 1;
5617 if (!IN_LEV_FIELD(x, y) ||
5618 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5619 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5622 element = Feld[x][y];
5624 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5626 element = MovingOrBlocked2Element(x, y);
5628 if (!IS_EXPLOSION_PROOF(element))
5629 RemoveMovingField(x, y);
5632 // indestructible elements can only explode in center (but not flames)
5633 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5634 mode == EX_TYPE_BORDER)) ||
5635 element == EL_FLAMES)
5638 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5639 behaviour, for example when touching a yamyam that explodes to rocks
5640 with active deadly shield, a rock is created under the player !!! */
5641 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5643 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5644 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5645 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5647 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5650 if (IS_ACTIVE_BOMB(element))
5652 // re-activate things under the bomb like gate or penguin
5653 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5660 // save walkable background elements while explosion on same tile
5661 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5662 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5663 Back[x][y] = element;
5665 // ignite explodable elements reached by other explosion
5666 if (element == EL_EXPLOSION)
5667 element = Store2[x][y];
5669 if (AmoebaNr[x][y] &&
5670 (element == EL_AMOEBA_FULL ||
5671 element == EL_BD_AMOEBA ||
5672 element == EL_AMOEBA_GROWING))
5674 AmoebaCnt[AmoebaNr[x][y]]--;
5675 AmoebaCnt2[AmoebaNr[x][y]]--;
5680 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5682 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5684 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5686 if (PLAYERINFO(ex, ey)->use_murphy)
5687 Store[x][y] = EL_EMPTY;
5690 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5691 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5692 else if (ELEM_IS_PLAYER(center_element))
5693 Store[x][y] = EL_EMPTY;
5694 else if (center_element == EL_YAMYAM)
5695 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5696 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5697 Store[x][y] = element_info[center_element].content.e[xx][yy];
5699 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5700 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5701 // otherwise) -- FIX THIS !!!
5702 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5703 Store[x][y] = element_info[element].content.e[1][1];
5705 else if (!CAN_EXPLODE(element))
5706 Store[x][y] = element_info[element].content.e[1][1];
5709 Store[x][y] = EL_EMPTY;
5711 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5712 center_element == EL_AMOEBA_TO_DIAMOND)
5713 Store2[x][y] = element;
5715 Feld[x][y] = EL_EXPLOSION;
5716 GfxElement[x][y] = artwork_element;
5718 ExplodePhase[x][y] = 1;
5719 ExplodeDelay[x][y] = last_phase;
5724 if (center_element == EL_YAMYAM)
5725 game.yamyam_content_nr =
5726 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5738 GfxFrame[x][y] = 0; // restart explosion animation
5740 last_phase = ExplodeDelay[x][y];
5742 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5744 // this can happen if the player leaves an explosion just in time
5745 if (GfxElement[x][y] == EL_UNDEFINED)
5746 GfxElement[x][y] = EL_EMPTY;
5748 border_element = Store2[x][y];
5749 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5750 border_element = StorePlayer[x][y];
5752 if (phase == element_info[border_element].ignition_delay ||
5753 phase == last_phase)
5755 boolean border_explosion = FALSE;
5757 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5758 !PLAYER_EXPLOSION_PROTECTED(x, y))
5760 KillPlayerUnlessExplosionProtected(x, y);
5761 border_explosion = TRUE;
5763 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5765 Feld[x][y] = Store2[x][y];
5768 border_explosion = TRUE;
5770 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5772 AmoebeUmwandeln(x, y);
5774 border_explosion = TRUE;
5777 // if an element just explodes due to another explosion (chain-reaction),
5778 // do not immediately end the new explosion when it was the last frame of
5779 // the explosion (as it would be done in the following "if"-statement!)
5780 if (border_explosion && phase == last_phase)
5784 if (phase == last_phase)
5788 element = Feld[x][y] = Store[x][y];
5789 Store[x][y] = Store2[x][y] = 0;
5790 GfxElement[x][y] = EL_UNDEFINED;
5792 // player can escape from explosions and might therefore be still alive
5793 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5794 element <= EL_PLAYER_IS_EXPLODING_4)
5796 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5797 int explosion_element = EL_PLAYER_1 + player_nr;
5798 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5799 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5801 if (level.use_explosion_element[player_nr])
5802 explosion_element = level.explosion_element[player_nr];
5804 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5805 element_info[explosion_element].content.e[xx][yy]);
5808 // restore probably existing indestructible background element
5809 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5810 element = Feld[x][y] = Back[x][y];
5813 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5814 GfxDir[x][y] = MV_NONE;
5815 ChangeDelay[x][y] = 0;
5816 ChangePage[x][y] = -1;
5818 CustomValue[x][y] = 0;
5820 InitField_WithBug2(x, y, FALSE);
5822 TEST_DrawLevelField(x, y);
5824 TestIfElementTouchesCustomElement(x, y);
5826 if (GFX_CRUMBLED(element))
5827 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5829 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5830 StorePlayer[x][y] = 0;
5832 if (ELEM_IS_PLAYER(element))
5833 RelocatePlayer(x, y, element);
5835 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5837 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5838 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5841 TEST_DrawLevelFieldCrumbled(x, y);
5843 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5845 DrawLevelElement(x, y, Back[x][y]);
5846 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5848 else if (IS_WALKABLE_UNDER(Back[x][y]))
5850 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5851 DrawLevelElementThruMask(x, y, Back[x][y]);
5853 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5854 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5858 static void DynaExplode(int ex, int ey)
5861 int dynabomb_element = Feld[ex][ey];
5862 int dynabomb_size = 1;
5863 boolean dynabomb_xl = FALSE;
5864 struct PlayerInfo *player;
5865 static int xy[4][2] =
5873 if (IS_ACTIVE_BOMB(dynabomb_element))
5875 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5876 dynabomb_size = player->dynabomb_size;
5877 dynabomb_xl = player->dynabomb_xl;
5878 player->dynabombs_left++;
5881 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5883 for (i = 0; i < NUM_DIRECTIONS; i++)
5885 for (j = 1; j <= dynabomb_size; j++)
5887 int x = ex + j * xy[i][0];
5888 int y = ey + j * xy[i][1];
5891 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5894 element = Feld[x][y];
5896 // do not restart explosions of fields with active bombs
5897 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5900 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5902 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5903 !IS_DIGGABLE(element) && !dynabomb_xl)
5909 void Bang(int x, int y)
5911 int element = MovingOrBlocked2Element(x, y);
5912 int explosion_type = EX_TYPE_NORMAL;
5914 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5916 struct PlayerInfo *player = PLAYERINFO(x, y);
5918 element = Feld[x][y] = player->initial_element;
5920 if (level.use_explosion_element[player->index_nr])
5922 int explosion_element = level.explosion_element[player->index_nr];
5924 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5925 explosion_type = EX_TYPE_CROSS;
5926 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5927 explosion_type = EX_TYPE_CENTER;
5935 case EL_BD_BUTTERFLY:
5938 case EL_DARK_YAMYAM:
5942 RaiseScoreElement(element);
5945 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5946 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5947 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5948 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5949 case EL_DYNABOMB_INCREASE_NUMBER:
5950 case EL_DYNABOMB_INCREASE_SIZE:
5951 case EL_DYNABOMB_INCREASE_POWER:
5952 explosion_type = EX_TYPE_DYNA;
5955 case EL_DC_LANDMINE:
5956 explosion_type = EX_TYPE_CENTER;
5961 case EL_LAMP_ACTIVE:
5962 case EL_AMOEBA_TO_DIAMOND:
5963 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5964 explosion_type = EX_TYPE_CENTER;
5968 if (element_info[element].explosion_type == EXPLODES_CROSS)
5969 explosion_type = EX_TYPE_CROSS;
5970 else if (element_info[element].explosion_type == EXPLODES_1X1)
5971 explosion_type = EX_TYPE_CENTER;
5975 if (explosion_type == EX_TYPE_DYNA)
5978 Explode(x, y, EX_PHASE_START, explosion_type);
5980 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5983 static void SplashAcid(int x, int y)
5985 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5986 (!IN_LEV_FIELD(x - 1, y - 2) ||
5987 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5988 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5990 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5991 (!IN_LEV_FIELD(x + 1, y - 2) ||
5992 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5993 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5995 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5998 static void InitBeltMovement(void)
6000 static int belt_base_element[4] =
6002 EL_CONVEYOR_BELT_1_LEFT,
6003 EL_CONVEYOR_BELT_2_LEFT,
6004 EL_CONVEYOR_BELT_3_LEFT,
6005 EL_CONVEYOR_BELT_4_LEFT
6007 static int belt_base_active_element[4] =
6009 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6010 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6011 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6012 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6017 // set frame order for belt animation graphic according to belt direction
6018 for (i = 0; i < NUM_BELTS; i++)
6022 for (j = 0; j < NUM_BELT_PARTS; j++)
6024 int element = belt_base_active_element[belt_nr] + j;
6025 int graphic_1 = el2img(element);
6026 int graphic_2 = el2panelimg(element);
6028 if (game.belt_dir[i] == MV_LEFT)
6030 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6031 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6035 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6036 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6041 SCAN_PLAYFIELD(x, y)
6043 int element = Feld[x][y];
6045 for (i = 0; i < NUM_BELTS; i++)
6047 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6049 int e_belt_nr = getBeltNrFromBeltElement(element);
6052 if (e_belt_nr == belt_nr)
6054 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
6056 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
6063 static void ToggleBeltSwitch(int x, int y)
6065 static int belt_base_element[4] =
6067 EL_CONVEYOR_BELT_1_LEFT,
6068 EL_CONVEYOR_BELT_2_LEFT,
6069 EL_CONVEYOR_BELT_3_LEFT,
6070 EL_CONVEYOR_BELT_4_LEFT
6072 static int belt_base_active_element[4] =
6074 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6075 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6076 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6077 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6079 static int belt_base_switch_element[4] =
6081 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6082 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6083 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6084 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6086 static int belt_move_dir[4] =
6094 int element = Feld[x][y];
6095 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6096 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6097 int belt_dir = belt_move_dir[belt_dir_nr];
6100 if (!IS_BELT_SWITCH(element))
6103 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6104 game.belt_dir[belt_nr] = belt_dir;
6106 if (belt_dir_nr == 3)
6109 // set frame order for belt animation graphic according to belt direction
6110 for (i = 0; i < NUM_BELT_PARTS; i++)
6112 int element = belt_base_active_element[belt_nr] + i;
6113 int graphic_1 = el2img(element);
6114 int graphic_2 = el2panelimg(element);
6116 if (belt_dir == MV_LEFT)
6118 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6119 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6123 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6124 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6128 SCAN_PLAYFIELD(xx, yy)
6130 int element = Feld[xx][yy];
6132 if (IS_BELT_SWITCH(element))
6134 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6136 if (e_belt_nr == belt_nr)
6138 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6139 TEST_DrawLevelField(xx, yy);
6142 else if (IS_BELT(element) && belt_dir != MV_NONE)
6144 int e_belt_nr = getBeltNrFromBeltElement(element);
6146 if (e_belt_nr == belt_nr)
6148 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
6150 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6151 TEST_DrawLevelField(xx, yy);
6154 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6156 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6158 if (e_belt_nr == belt_nr)
6160 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6162 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6163 TEST_DrawLevelField(xx, yy);
6169 static void ToggleSwitchgateSwitch(int x, int y)
6173 game.switchgate_pos = !game.switchgate_pos;
6175 SCAN_PLAYFIELD(xx, yy)
6177 int element = Feld[xx][yy];
6179 if (element == EL_SWITCHGATE_SWITCH_UP)
6181 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6182 TEST_DrawLevelField(xx, yy);
6184 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6186 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6187 TEST_DrawLevelField(xx, yy);
6189 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6191 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6192 TEST_DrawLevelField(xx, yy);
6194 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6196 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6197 TEST_DrawLevelField(xx, yy);
6199 else if (element == EL_SWITCHGATE_OPEN ||
6200 element == EL_SWITCHGATE_OPENING)
6202 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6204 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6206 else if (element == EL_SWITCHGATE_CLOSED ||
6207 element == EL_SWITCHGATE_CLOSING)
6209 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6211 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6216 static int getInvisibleActiveFromInvisibleElement(int element)
6218 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6219 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6220 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6224 static int getInvisibleFromInvisibleActiveElement(int element)
6226 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6227 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6228 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6232 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6236 SCAN_PLAYFIELD(x, y)
6238 int element = Feld[x][y];
6240 if (element == EL_LIGHT_SWITCH &&
6241 game.light_time_left > 0)
6243 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6244 TEST_DrawLevelField(x, y);
6246 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6247 game.light_time_left == 0)
6249 Feld[x][y] = EL_LIGHT_SWITCH;
6250 TEST_DrawLevelField(x, y);
6252 else if (element == EL_EMC_DRIPPER &&
6253 game.light_time_left > 0)
6255 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6256 TEST_DrawLevelField(x, y);
6258 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6259 game.light_time_left == 0)
6261 Feld[x][y] = EL_EMC_DRIPPER;
6262 TEST_DrawLevelField(x, y);
6264 else if (element == EL_INVISIBLE_STEELWALL ||
6265 element == EL_INVISIBLE_WALL ||
6266 element == EL_INVISIBLE_SAND)
6268 if (game.light_time_left > 0)
6269 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6271 TEST_DrawLevelField(x, y);
6273 // uncrumble neighbour fields, if needed
6274 if (element == EL_INVISIBLE_SAND)
6275 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6277 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6278 element == EL_INVISIBLE_WALL_ACTIVE ||
6279 element == EL_INVISIBLE_SAND_ACTIVE)
6281 if (game.light_time_left == 0)
6282 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6284 TEST_DrawLevelField(x, y);
6286 // re-crumble neighbour fields, if needed
6287 if (element == EL_INVISIBLE_SAND)
6288 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6293 static void RedrawAllInvisibleElementsForLenses(void)
6297 SCAN_PLAYFIELD(x, y)
6299 int element = Feld[x][y];
6301 if (element == EL_EMC_DRIPPER &&
6302 game.lenses_time_left > 0)
6304 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6305 TEST_DrawLevelField(x, y);
6307 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6308 game.lenses_time_left == 0)
6310 Feld[x][y] = EL_EMC_DRIPPER;
6311 TEST_DrawLevelField(x, y);
6313 else if (element == EL_INVISIBLE_STEELWALL ||
6314 element == EL_INVISIBLE_WALL ||
6315 element == EL_INVISIBLE_SAND)
6317 if (game.lenses_time_left > 0)
6318 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6320 TEST_DrawLevelField(x, y);
6322 // uncrumble neighbour fields, if needed
6323 if (element == EL_INVISIBLE_SAND)
6324 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6326 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6327 element == EL_INVISIBLE_WALL_ACTIVE ||
6328 element == EL_INVISIBLE_SAND_ACTIVE)
6330 if (game.lenses_time_left == 0)
6331 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6333 TEST_DrawLevelField(x, y);
6335 // re-crumble neighbour fields, if needed
6336 if (element == EL_INVISIBLE_SAND)
6337 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6342 static void RedrawAllInvisibleElementsForMagnifier(void)
6346 SCAN_PLAYFIELD(x, y)
6348 int element = Feld[x][y];
6350 if (element == EL_EMC_FAKE_GRASS &&
6351 game.magnify_time_left > 0)
6353 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6354 TEST_DrawLevelField(x, y);
6356 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6357 game.magnify_time_left == 0)
6359 Feld[x][y] = EL_EMC_FAKE_GRASS;
6360 TEST_DrawLevelField(x, y);
6362 else if (IS_GATE_GRAY(element) &&
6363 game.magnify_time_left > 0)
6365 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6366 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6367 IS_EM_GATE_GRAY(element) ?
6368 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6369 IS_EMC_GATE_GRAY(element) ?
6370 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6371 IS_DC_GATE_GRAY(element) ?
6372 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6374 TEST_DrawLevelField(x, y);
6376 else if (IS_GATE_GRAY_ACTIVE(element) &&
6377 game.magnify_time_left == 0)
6379 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6380 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6381 IS_EM_GATE_GRAY_ACTIVE(element) ?
6382 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6383 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6384 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6385 IS_DC_GATE_GRAY_ACTIVE(element) ?
6386 EL_DC_GATE_WHITE_GRAY :
6388 TEST_DrawLevelField(x, y);
6393 static void ToggleLightSwitch(int x, int y)
6395 int element = Feld[x][y];
6397 game.light_time_left =
6398 (element == EL_LIGHT_SWITCH ?
6399 level.time_light * FRAMES_PER_SECOND : 0);
6401 RedrawAllLightSwitchesAndInvisibleElements();
6404 static void ActivateTimegateSwitch(int x, int y)
6408 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6410 SCAN_PLAYFIELD(xx, yy)
6412 int element = Feld[xx][yy];
6414 if (element == EL_TIMEGATE_CLOSED ||
6415 element == EL_TIMEGATE_CLOSING)
6417 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6418 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6422 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6424 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6425 TEST_DrawLevelField(xx, yy);
6431 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6432 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6435 static void Impact(int x, int y)
6437 boolean last_line = (y == lev_fieldy - 1);
6438 boolean object_hit = FALSE;
6439 boolean impact = (last_line || object_hit);
6440 int element = Feld[x][y];
6441 int smashed = EL_STEELWALL;
6443 if (!last_line) // check if element below was hit
6445 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6448 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6449 MovDir[x][y + 1] != MV_DOWN ||
6450 MovPos[x][y + 1] <= TILEY / 2));
6452 // do not smash moving elements that left the smashed field in time
6453 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6454 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6457 #if USE_QUICKSAND_IMPACT_BUGFIX
6458 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6460 RemoveMovingField(x, y + 1);
6461 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6462 Feld[x][y + 2] = EL_ROCK;
6463 TEST_DrawLevelField(x, y + 2);
6468 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6470 RemoveMovingField(x, y + 1);
6471 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6472 Feld[x][y + 2] = EL_ROCK;
6473 TEST_DrawLevelField(x, y + 2);
6480 smashed = MovingOrBlocked2Element(x, y + 1);
6482 impact = (last_line || object_hit);
6485 if (!last_line && smashed == EL_ACID) // element falls into acid
6487 SplashAcid(x, y + 1);
6491 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6492 // only reset graphic animation if graphic really changes after impact
6494 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6496 ResetGfxAnimation(x, y);
6497 TEST_DrawLevelField(x, y);
6500 if (impact && CAN_EXPLODE_IMPACT(element))
6505 else if (impact && element == EL_PEARL &&
6506 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6508 ResetGfxAnimation(x, y);
6510 Feld[x][y] = EL_PEARL_BREAKING;
6511 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6514 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6516 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6521 if (impact && element == EL_AMOEBA_DROP)
6523 if (object_hit && IS_PLAYER(x, y + 1))
6524 KillPlayerUnlessEnemyProtected(x, y + 1);
6525 else if (object_hit && smashed == EL_PENGUIN)
6529 Feld[x][y] = EL_AMOEBA_GROWING;
6530 Store[x][y] = EL_AMOEBA_WET;
6532 ResetRandomAnimationValue(x, y);
6537 if (object_hit) // check which object was hit
6539 if ((CAN_PASS_MAGIC_WALL(element) &&
6540 (smashed == EL_MAGIC_WALL ||
6541 smashed == EL_BD_MAGIC_WALL)) ||
6542 (CAN_PASS_DC_MAGIC_WALL(element) &&
6543 smashed == EL_DC_MAGIC_WALL))
6546 int activated_magic_wall =
6547 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6548 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6549 EL_DC_MAGIC_WALL_ACTIVE);
6551 // activate magic wall / mill
6552 SCAN_PLAYFIELD(xx, yy)
6554 if (Feld[xx][yy] == smashed)
6555 Feld[xx][yy] = activated_magic_wall;
6558 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6559 game.magic_wall_active = TRUE;
6561 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6562 SND_MAGIC_WALL_ACTIVATING :
6563 smashed == EL_BD_MAGIC_WALL ?
6564 SND_BD_MAGIC_WALL_ACTIVATING :
6565 SND_DC_MAGIC_WALL_ACTIVATING));
6568 if (IS_PLAYER(x, y + 1))
6570 if (CAN_SMASH_PLAYER(element))
6572 KillPlayerUnlessEnemyProtected(x, y + 1);
6576 else if (smashed == EL_PENGUIN)
6578 if (CAN_SMASH_PLAYER(element))
6584 else if (element == EL_BD_DIAMOND)
6586 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6592 else if (((element == EL_SP_INFOTRON ||
6593 element == EL_SP_ZONK) &&
6594 (smashed == EL_SP_SNIKSNAK ||
6595 smashed == EL_SP_ELECTRON ||
6596 smashed == EL_SP_DISK_ORANGE)) ||
6597 (element == EL_SP_INFOTRON &&
6598 smashed == EL_SP_DISK_YELLOW))
6603 else if (CAN_SMASH_EVERYTHING(element))
6605 if (IS_CLASSIC_ENEMY(smashed) ||
6606 CAN_EXPLODE_SMASHED(smashed))
6611 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6613 if (smashed == EL_LAMP ||
6614 smashed == EL_LAMP_ACTIVE)
6619 else if (smashed == EL_NUT)
6621 Feld[x][y + 1] = EL_NUT_BREAKING;
6622 PlayLevelSound(x, y, SND_NUT_BREAKING);
6623 RaiseScoreElement(EL_NUT);
6626 else if (smashed == EL_PEARL)
6628 ResetGfxAnimation(x, y);
6630 Feld[x][y + 1] = EL_PEARL_BREAKING;
6631 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6634 else if (smashed == EL_DIAMOND)
6636 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6637 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6640 else if (IS_BELT_SWITCH(smashed))
6642 ToggleBeltSwitch(x, y + 1);
6644 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6645 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6646 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6647 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6649 ToggleSwitchgateSwitch(x, y + 1);
6651 else if (smashed == EL_LIGHT_SWITCH ||
6652 smashed == EL_LIGHT_SWITCH_ACTIVE)
6654 ToggleLightSwitch(x, y + 1);
6658 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6660 CheckElementChangeBySide(x, y + 1, smashed, element,
6661 CE_SWITCHED, CH_SIDE_TOP);
6662 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6668 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6673 // play sound of magic wall / mill
6675 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6676 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6677 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6679 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6680 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6681 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6682 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6683 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6684 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6689 // play sound of object that hits the ground
6690 if (last_line || object_hit)
6691 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6694 static void TurnRoundExt(int x, int y)
6706 { 0, 0 }, { 0, 0 }, { 0, 0 },
6711 int left, right, back;
6715 { MV_DOWN, MV_UP, MV_RIGHT },
6716 { MV_UP, MV_DOWN, MV_LEFT },
6718 { MV_LEFT, MV_RIGHT, MV_DOWN },
6722 { MV_RIGHT, MV_LEFT, MV_UP }
6725 int element = Feld[x][y];
6726 int move_pattern = element_info[element].move_pattern;
6728 int old_move_dir = MovDir[x][y];
6729 int left_dir = turn[old_move_dir].left;
6730 int right_dir = turn[old_move_dir].right;
6731 int back_dir = turn[old_move_dir].back;
6733 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6734 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6735 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6736 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6738 int left_x = x + left_dx, left_y = y + left_dy;
6739 int right_x = x + right_dx, right_y = y + right_dy;
6740 int move_x = x + move_dx, move_y = y + move_dy;
6744 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6746 TestIfBadThingTouchesOtherBadThing(x, y);
6748 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6749 MovDir[x][y] = right_dir;
6750 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6751 MovDir[x][y] = left_dir;
6753 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6755 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6758 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6760 TestIfBadThingTouchesOtherBadThing(x, y);
6762 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6763 MovDir[x][y] = left_dir;
6764 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6765 MovDir[x][y] = right_dir;
6767 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6769 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6772 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6774 TestIfBadThingTouchesOtherBadThing(x, y);
6776 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6777 MovDir[x][y] = left_dir;
6778 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6779 MovDir[x][y] = right_dir;
6781 if (MovDir[x][y] != old_move_dir)
6784 else if (element == EL_YAMYAM)
6786 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6787 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6789 if (can_turn_left && can_turn_right)
6790 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6791 else if (can_turn_left)
6792 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6793 else if (can_turn_right)
6794 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6796 MovDir[x][y] = back_dir;
6798 MovDelay[x][y] = 16 + 16 * RND(3);
6800 else if (element == EL_DARK_YAMYAM)
6802 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6804 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6807 if (can_turn_left && can_turn_right)
6808 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6809 else if (can_turn_left)
6810 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6811 else if (can_turn_right)
6812 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6814 MovDir[x][y] = back_dir;
6816 MovDelay[x][y] = 16 + 16 * RND(3);
6818 else if (element == EL_PACMAN)
6820 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6821 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6823 if (can_turn_left && can_turn_right)
6824 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6825 else if (can_turn_left)
6826 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6827 else if (can_turn_right)
6828 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6830 MovDir[x][y] = back_dir;
6832 MovDelay[x][y] = 6 + RND(40);
6834 else if (element == EL_PIG)
6836 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6837 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6838 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6839 boolean should_turn_left, should_turn_right, should_move_on;
6841 int rnd = RND(rnd_value);
6843 should_turn_left = (can_turn_left &&
6845 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6846 y + back_dy + left_dy)));
6847 should_turn_right = (can_turn_right &&
6849 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6850 y + back_dy + right_dy)));
6851 should_move_on = (can_move_on &&
6854 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6855 y + move_dy + left_dy) ||
6856 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6857 y + move_dy + right_dy)));
6859 if (should_turn_left || should_turn_right || should_move_on)
6861 if (should_turn_left && should_turn_right && should_move_on)
6862 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6863 rnd < 2 * rnd_value / 3 ? right_dir :
6865 else if (should_turn_left && should_turn_right)
6866 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6867 else if (should_turn_left && should_move_on)
6868 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6869 else if (should_turn_right && should_move_on)
6870 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6871 else if (should_turn_left)
6872 MovDir[x][y] = left_dir;
6873 else if (should_turn_right)
6874 MovDir[x][y] = right_dir;
6875 else if (should_move_on)
6876 MovDir[x][y] = old_move_dir;
6878 else if (can_move_on && rnd > rnd_value / 8)
6879 MovDir[x][y] = old_move_dir;
6880 else if (can_turn_left && can_turn_right)
6881 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6882 else if (can_turn_left && rnd > rnd_value / 8)
6883 MovDir[x][y] = left_dir;
6884 else if (can_turn_right && rnd > rnd_value/8)
6885 MovDir[x][y] = right_dir;
6887 MovDir[x][y] = back_dir;
6889 xx = x + move_xy[MovDir[x][y]].dx;
6890 yy = y + move_xy[MovDir[x][y]].dy;
6892 if (!IN_LEV_FIELD(xx, yy) ||
6893 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6894 MovDir[x][y] = old_move_dir;
6898 else if (element == EL_DRAGON)
6900 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6901 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6902 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6904 int rnd = RND(rnd_value);
6906 if (can_move_on && rnd > rnd_value / 8)
6907 MovDir[x][y] = old_move_dir;
6908 else if (can_turn_left && can_turn_right)
6909 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6910 else if (can_turn_left && rnd > rnd_value / 8)
6911 MovDir[x][y] = left_dir;
6912 else if (can_turn_right && rnd > rnd_value / 8)
6913 MovDir[x][y] = right_dir;
6915 MovDir[x][y] = back_dir;
6917 xx = x + move_xy[MovDir[x][y]].dx;
6918 yy = y + move_xy[MovDir[x][y]].dy;
6920 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6921 MovDir[x][y] = old_move_dir;
6925 else if (element == EL_MOLE)
6927 boolean can_move_on =
6928 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6929 IS_AMOEBOID(Feld[move_x][move_y]) ||
6930 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6933 boolean can_turn_left =
6934 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6935 IS_AMOEBOID(Feld[left_x][left_y])));
6937 boolean can_turn_right =
6938 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6939 IS_AMOEBOID(Feld[right_x][right_y])));
6941 if (can_turn_left && can_turn_right)
6942 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6943 else if (can_turn_left)
6944 MovDir[x][y] = left_dir;
6946 MovDir[x][y] = right_dir;
6949 if (MovDir[x][y] != old_move_dir)
6952 else if (element == EL_BALLOON)
6954 MovDir[x][y] = game.wind_direction;
6957 else if (element == EL_SPRING)
6959 if (MovDir[x][y] & MV_HORIZONTAL)
6961 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6962 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6964 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6965 ResetGfxAnimation(move_x, move_y);
6966 TEST_DrawLevelField(move_x, move_y);
6968 MovDir[x][y] = back_dir;
6970 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6971 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6972 MovDir[x][y] = MV_NONE;
6977 else if (element == EL_ROBOT ||
6978 element == EL_SATELLITE ||
6979 element == EL_PENGUIN ||
6980 element == EL_EMC_ANDROID)
6982 int attr_x = -1, attr_y = -1;
6984 if (game.all_players_gone)
6986 attr_x = game.exit_x;
6987 attr_y = game.exit_y;
6993 for (i = 0; i < MAX_PLAYERS; i++)
6995 struct PlayerInfo *player = &stored_player[i];
6996 int jx = player->jx, jy = player->jy;
6998 if (!player->active)
7002 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7010 if (element == EL_ROBOT &&
7011 game.robot_wheel_x >= 0 &&
7012 game.robot_wheel_y >= 0 &&
7013 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7014 game.engine_version < VERSION_IDENT(3,1,0,0)))
7016 attr_x = game.robot_wheel_x;
7017 attr_y = game.robot_wheel_y;
7020 if (element == EL_PENGUIN)
7023 static int xy[4][2] =
7031 for (i = 0; i < NUM_DIRECTIONS; i++)
7033 int ex = x + xy[i][0];
7034 int ey = y + xy[i][1];
7036 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
7037 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
7038 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
7039 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7048 MovDir[x][y] = MV_NONE;
7050 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7051 else if (attr_x > x)
7052 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7054 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7055 else if (attr_y > y)
7056 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7058 if (element == EL_ROBOT)
7062 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7063 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7064 Moving2Blocked(x, y, &newx, &newy);
7066 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7067 MovDelay[x][y] = 8 + 8 * !RND(3);
7069 MovDelay[x][y] = 16;
7071 else if (element == EL_PENGUIN)
7077 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7079 boolean first_horiz = RND(2);
7080 int new_move_dir = MovDir[x][y];
7083 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7084 Moving2Blocked(x, y, &newx, &newy);
7086 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7090 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7091 Moving2Blocked(x, y, &newx, &newy);
7093 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7096 MovDir[x][y] = old_move_dir;
7100 else if (element == EL_SATELLITE)
7106 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7108 boolean first_horiz = RND(2);
7109 int new_move_dir = MovDir[x][y];
7112 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7113 Moving2Blocked(x, y, &newx, &newy);
7115 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7119 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7120 Moving2Blocked(x, y, &newx, &newy);
7122 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7125 MovDir[x][y] = old_move_dir;
7129 else if (element == EL_EMC_ANDROID)
7131 static int check_pos[16] =
7133 -1, // 0 => (invalid)
7136 -1, // 3 => (invalid)
7138 0, // 5 => MV_LEFT | MV_UP
7139 2, // 6 => MV_RIGHT | MV_UP
7140 -1, // 7 => (invalid)
7142 6, // 9 => MV_LEFT | MV_DOWN
7143 4, // 10 => MV_RIGHT | MV_DOWN
7144 -1, // 11 => (invalid)
7145 -1, // 12 => (invalid)
7146 -1, // 13 => (invalid)
7147 -1, // 14 => (invalid)
7148 -1, // 15 => (invalid)
7156 { -1, -1, MV_LEFT | MV_UP },
7158 { +1, -1, MV_RIGHT | MV_UP },
7159 { +1, 0, MV_RIGHT },
7160 { +1, +1, MV_RIGHT | MV_DOWN },
7162 { -1, +1, MV_LEFT | MV_DOWN },
7165 int start_pos, check_order;
7166 boolean can_clone = FALSE;
7169 // check if there is any free field around current position
7170 for (i = 0; i < 8; i++)
7172 int newx = x + check_xy[i].dx;
7173 int newy = y + check_xy[i].dy;
7175 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7183 if (can_clone) // randomly find an element to clone
7187 start_pos = check_pos[RND(8)];
7188 check_order = (RND(2) ? -1 : +1);
7190 for (i = 0; i < 8; i++)
7192 int pos_raw = start_pos + i * check_order;
7193 int pos = (pos_raw + 8) % 8;
7194 int newx = x + check_xy[pos].dx;
7195 int newy = y + check_xy[pos].dy;
7197 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7199 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7200 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7202 Store[x][y] = Feld[newx][newy];
7211 if (can_clone) // randomly find a direction to move
7215 start_pos = check_pos[RND(8)];
7216 check_order = (RND(2) ? -1 : +1);
7218 for (i = 0; i < 8; i++)
7220 int pos_raw = start_pos + i * check_order;
7221 int pos = (pos_raw + 8) % 8;
7222 int newx = x + check_xy[pos].dx;
7223 int newy = y + check_xy[pos].dy;
7224 int new_move_dir = check_xy[pos].dir;
7226 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7228 MovDir[x][y] = new_move_dir;
7229 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7238 if (can_clone) // cloning and moving successful
7241 // cannot clone -- try to move towards player
7243 start_pos = check_pos[MovDir[x][y] & 0x0f];
7244 check_order = (RND(2) ? -1 : +1);
7246 for (i = 0; i < 3; i++)
7248 // first check start_pos, then previous/next or (next/previous) pos
7249 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7250 int pos = (pos_raw + 8) % 8;
7251 int newx = x + check_xy[pos].dx;
7252 int newy = y + check_xy[pos].dy;
7253 int new_move_dir = check_xy[pos].dir;
7255 if (IS_PLAYER(newx, newy))
7258 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7260 MovDir[x][y] = new_move_dir;
7261 MovDelay[x][y] = level.android_move_time * 8 + 1;
7268 else if (move_pattern == MV_TURNING_LEFT ||
7269 move_pattern == MV_TURNING_RIGHT ||
7270 move_pattern == MV_TURNING_LEFT_RIGHT ||
7271 move_pattern == MV_TURNING_RIGHT_LEFT ||
7272 move_pattern == MV_TURNING_RANDOM ||
7273 move_pattern == MV_ALL_DIRECTIONS)
7275 boolean can_turn_left =
7276 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7277 boolean can_turn_right =
7278 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7280 if (element_info[element].move_stepsize == 0) // "not moving"
7283 if (move_pattern == MV_TURNING_LEFT)
7284 MovDir[x][y] = left_dir;
7285 else if (move_pattern == MV_TURNING_RIGHT)
7286 MovDir[x][y] = right_dir;
7287 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7288 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7289 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7290 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7291 else if (move_pattern == MV_TURNING_RANDOM)
7292 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7293 can_turn_right && !can_turn_left ? right_dir :
7294 RND(2) ? left_dir : right_dir);
7295 else if (can_turn_left && can_turn_right)
7296 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7297 else if (can_turn_left)
7298 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7299 else if (can_turn_right)
7300 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7302 MovDir[x][y] = back_dir;
7304 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7306 else if (move_pattern == MV_HORIZONTAL ||
7307 move_pattern == MV_VERTICAL)
7309 if (move_pattern & old_move_dir)
7310 MovDir[x][y] = back_dir;
7311 else if (move_pattern == MV_HORIZONTAL)
7312 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7313 else if (move_pattern == MV_VERTICAL)
7314 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7316 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7318 else if (move_pattern & MV_ANY_DIRECTION)
7320 MovDir[x][y] = move_pattern;
7321 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7323 else if (move_pattern & MV_WIND_DIRECTION)
7325 MovDir[x][y] = game.wind_direction;
7326 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7328 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7330 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7331 MovDir[x][y] = left_dir;
7332 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7333 MovDir[x][y] = right_dir;
7335 if (MovDir[x][y] != old_move_dir)
7336 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7338 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7340 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7341 MovDir[x][y] = right_dir;
7342 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7343 MovDir[x][y] = left_dir;
7345 if (MovDir[x][y] != old_move_dir)
7346 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7348 else if (move_pattern == MV_TOWARDS_PLAYER ||
7349 move_pattern == MV_AWAY_FROM_PLAYER)
7351 int attr_x = -1, attr_y = -1;
7353 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7355 if (game.all_players_gone)
7357 attr_x = game.exit_x;
7358 attr_y = game.exit_y;
7364 for (i = 0; i < MAX_PLAYERS; i++)
7366 struct PlayerInfo *player = &stored_player[i];
7367 int jx = player->jx, jy = player->jy;
7369 if (!player->active)
7373 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7381 MovDir[x][y] = MV_NONE;
7383 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7384 else if (attr_x > x)
7385 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7387 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7388 else if (attr_y > y)
7389 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7391 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7393 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7395 boolean first_horiz = RND(2);
7396 int new_move_dir = MovDir[x][y];
7398 if (element_info[element].move_stepsize == 0) // "not moving"
7400 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7401 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7407 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7408 Moving2Blocked(x, y, &newx, &newy);
7410 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7414 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7415 Moving2Blocked(x, y, &newx, &newy);
7417 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7420 MovDir[x][y] = old_move_dir;
7423 else if (move_pattern == MV_WHEN_PUSHED ||
7424 move_pattern == MV_WHEN_DROPPED)
7426 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7427 MovDir[x][y] = MV_NONE;
7431 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7433 static int test_xy[7][2] =
7443 static int test_dir[7] =
7453 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7454 int move_preference = -1000000; // start with very low preference
7455 int new_move_dir = MV_NONE;
7456 int start_test = RND(4);
7459 for (i = 0; i < NUM_DIRECTIONS; i++)
7461 int move_dir = test_dir[start_test + i];
7462 int move_dir_preference;
7464 xx = x + test_xy[start_test + i][0];
7465 yy = y + test_xy[start_test + i][1];
7467 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7468 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7470 new_move_dir = move_dir;
7475 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7478 move_dir_preference = -1 * RunnerVisit[xx][yy];
7479 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7480 move_dir_preference = PlayerVisit[xx][yy];
7482 if (move_dir_preference > move_preference)
7484 // prefer field that has not been visited for the longest time
7485 move_preference = move_dir_preference;
7486 new_move_dir = move_dir;
7488 else if (move_dir_preference == move_preference &&
7489 move_dir == old_move_dir)
7491 // prefer last direction when all directions are preferred equally
7492 move_preference = move_dir_preference;
7493 new_move_dir = move_dir;
7497 MovDir[x][y] = new_move_dir;
7498 if (old_move_dir != new_move_dir)
7499 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7503 static void TurnRound(int x, int y)
7505 int direction = MovDir[x][y];
7509 GfxDir[x][y] = MovDir[x][y];
7511 if (direction != MovDir[x][y])
7515 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7517 ResetGfxFrame(x, y);
7520 static boolean JustBeingPushed(int x, int y)
7524 for (i = 0; i < MAX_PLAYERS; i++)
7526 struct PlayerInfo *player = &stored_player[i];
7528 if (player->active && player->is_pushing && player->MovPos)
7530 int next_jx = player->jx + (player->jx - player->last_jx);
7531 int next_jy = player->jy + (player->jy - player->last_jy);
7533 if (x == next_jx && y == next_jy)
7541 static void StartMoving(int x, int y)
7543 boolean started_moving = FALSE; // some elements can fall _and_ move
7544 int element = Feld[x][y];
7549 if (MovDelay[x][y] == 0)
7550 GfxAction[x][y] = ACTION_DEFAULT;
7552 if (CAN_FALL(element) && y < lev_fieldy - 1)
7554 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7555 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7556 if (JustBeingPushed(x, y))
7559 if (element == EL_QUICKSAND_FULL)
7561 if (IS_FREE(x, y + 1))
7563 InitMovingField(x, y, MV_DOWN);
7564 started_moving = TRUE;
7566 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7567 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7568 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7569 Store[x][y] = EL_ROCK;
7571 Store[x][y] = EL_ROCK;
7574 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7576 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7578 if (!MovDelay[x][y])
7580 MovDelay[x][y] = TILEY + 1;
7582 ResetGfxAnimation(x, y);
7583 ResetGfxAnimation(x, y + 1);
7588 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7589 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7596 Feld[x][y] = EL_QUICKSAND_EMPTY;
7597 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7598 Store[x][y + 1] = Store[x][y];
7601 PlayLevelSoundAction(x, y, ACTION_FILLING);
7603 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7605 if (!MovDelay[x][y])
7607 MovDelay[x][y] = TILEY + 1;
7609 ResetGfxAnimation(x, y);
7610 ResetGfxAnimation(x, y + 1);
7615 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7616 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7623 Feld[x][y] = EL_QUICKSAND_EMPTY;
7624 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7625 Store[x][y + 1] = Store[x][y];
7628 PlayLevelSoundAction(x, y, ACTION_FILLING);
7631 else if (element == EL_QUICKSAND_FAST_FULL)
7633 if (IS_FREE(x, y + 1))
7635 InitMovingField(x, y, MV_DOWN);
7636 started_moving = TRUE;
7638 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7639 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7640 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7641 Store[x][y] = EL_ROCK;
7643 Store[x][y] = EL_ROCK;
7646 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7648 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7650 if (!MovDelay[x][y])
7652 MovDelay[x][y] = TILEY + 1;
7654 ResetGfxAnimation(x, y);
7655 ResetGfxAnimation(x, y + 1);
7660 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7661 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7668 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7669 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7670 Store[x][y + 1] = Store[x][y];
7673 PlayLevelSoundAction(x, y, ACTION_FILLING);
7675 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7677 if (!MovDelay[x][y])
7679 MovDelay[x][y] = TILEY + 1;
7681 ResetGfxAnimation(x, y);
7682 ResetGfxAnimation(x, y + 1);
7687 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7688 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7695 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7696 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7697 Store[x][y + 1] = Store[x][y];
7700 PlayLevelSoundAction(x, y, ACTION_FILLING);
7703 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7704 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7706 InitMovingField(x, y, MV_DOWN);
7707 started_moving = TRUE;
7709 Feld[x][y] = EL_QUICKSAND_FILLING;
7710 Store[x][y] = element;
7712 PlayLevelSoundAction(x, y, ACTION_FILLING);
7714 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7715 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7717 InitMovingField(x, y, MV_DOWN);
7718 started_moving = TRUE;
7720 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7721 Store[x][y] = element;
7723 PlayLevelSoundAction(x, y, ACTION_FILLING);
7725 else if (element == EL_MAGIC_WALL_FULL)
7727 if (IS_FREE(x, y + 1))
7729 InitMovingField(x, y, MV_DOWN);
7730 started_moving = TRUE;
7732 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7733 Store[x][y] = EL_CHANGED(Store[x][y]);
7735 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7737 if (!MovDelay[x][y])
7738 MovDelay[x][y] = TILEY / 4 + 1;
7747 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7748 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7749 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7753 else if (element == EL_BD_MAGIC_WALL_FULL)
7755 if (IS_FREE(x, y + 1))
7757 InitMovingField(x, y, MV_DOWN);
7758 started_moving = TRUE;
7760 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7761 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7763 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7765 if (!MovDelay[x][y])
7766 MovDelay[x][y] = TILEY / 4 + 1;
7775 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7776 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7777 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7781 else if (element == EL_DC_MAGIC_WALL_FULL)
7783 if (IS_FREE(x, y + 1))
7785 InitMovingField(x, y, MV_DOWN);
7786 started_moving = TRUE;
7788 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7789 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7791 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7793 if (!MovDelay[x][y])
7794 MovDelay[x][y] = TILEY / 4 + 1;
7803 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7804 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7805 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7809 else if ((CAN_PASS_MAGIC_WALL(element) &&
7810 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7811 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7812 (CAN_PASS_DC_MAGIC_WALL(element) &&
7813 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7816 InitMovingField(x, y, MV_DOWN);
7817 started_moving = TRUE;
7820 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7821 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7822 EL_DC_MAGIC_WALL_FILLING);
7823 Store[x][y] = element;
7825 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7827 SplashAcid(x, y + 1);
7829 InitMovingField(x, y, MV_DOWN);
7830 started_moving = TRUE;
7832 Store[x][y] = EL_ACID;
7835 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7836 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7837 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7838 CAN_FALL(element) && WasJustFalling[x][y] &&
7839 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7841 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7842 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7843 (Feld[x][y + 1] == EL_BLOCKED)))
7845 /* this is needed for a special case not covered by calling "Impact()"
7846 from "ContinueMoving()": if an element moves to a tile directly below
7847 another element which was just falling on that tile (which was empty
7848 in the previous frame), the falling element above would just stop
7849 instead of smashing the element below (in previous version, the above
7850 element was just checked for "moving" instead of "falling", resulting
7851 in incorrect smashes caused by horizontal movement of the above
7852 element; also, the case of the player being the element to smash was
7853 simply not covered here... :-/ ) */
7855 CheckCollision[x][y] = 0;
7856 CheckImpact[x][y] = 0;
7860 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7862 if (MovDir[x][y] == MV_NONE)
7864 InitMovingField(x, y, MV_DOWN);
7865 started_moving = TRUE;
7868 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7870 if (WasJustFalling[x][y]) // prevent animation from being restarted
7871 MovDir[x][y] = MV_DOWN;
7873 InitMovingField(x, y, MV_DOWN);
7874 started_moving = TRUE;
7876 else if (element == EL_AMOEBA_DROP)
7878 Feld[x][y] = EL_AMOEBA_GROWING;
7879 Store[x][y] = EL_AMOEBA_WET;
7881 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7882 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7883 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7884 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7886 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7887 (IS_FREE(x - 1, y + 1) ||
7888 Feld[x - 1][y + 1] == EL_ACID));
7889 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7890 (IS_FREE(x + 1, y + 1) ||
7891 Feld[x + 1][y + 1] == EL_ACID));
7892 boolean can_fall_any = (can_fall_left || can_fall_right);
7893 boolean can_fall_both = (can_fall_left && can_fall_right);
7894 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7896 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7898 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7899 can_fall_right = FALSE;
7900 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7901 can_fall_left = FALSE;
7902 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7903 can_fall_right = FALSE;
7904 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7905 can_fall_left = FALSE;
7907 can_fall_any = (can_fall_left || can_fall_right);
7908 can_fall_both = FALSE;
7913 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7914 can_fall_right = FALSE; // slip down on left side
7916 can_fall_left = !(can_fall_right = RND(2));
7918 can_fall_both = FALSE;
7923 // if not determined otherwise, prefer left side for slipping down
7924 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7925 started_moving = TRUE;
7928 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7930 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7931 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7932 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7933 int belt_dir = game.belt_dir[belt_nr];
7935 if ((belt_dir == MV_LEFT && left_is_free) ||
7936 (belt_dir == MV_RIGHT && right_is_free))
7938 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7940 InitMovingField(x, y, belt_dir);
7941 started_moving = TRUE;
7943 Pushed[x][y] = TRUE;
7944 Pushed[nextx][y] = TRUE;
7946 GfxAction[x][y] = ACTION_DEFAULT;
7950 MovDir[x][y] = 0; // if element was moving, stop it
7955 // not "else if" because of elements that can fall and move (EL_SPRING)
7956 if (CAN_MOVE(element) && !started_moving)
7958 int move_pattern = element_info[element].move_pattern;
7961 Moving2Blocked(x, y, &newx, &newy);
7963 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7966 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7967 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7969 WasJustMoving[x][y] = 0;
7970 CheckCollision[x][y] = 0;
7972 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7974 if (Feld[x][y] != element) // element has changed
7978 if (!MovDelay[x][y]) // start new movement phase
7980 // all objects that can change their move direction after each step
7981 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7983 if (element != EL_YAMYAM &&
7984 element != EL_DARK_YAMYAM &&
7985 element != EL_PACMAN &&
7986 !(move_pattern & MV_ANY_DIRECTION) &&
7987 move_pattern != MV_TURNING_LEFT &&
7988 move_pattern != MV_TURNING_RIGHT &&
7989 move_pattern != MV_TURNING_LEFT_RIGHT &&
7990 move_pattern != MV_TURNING_RIGHT_LEFT &&
7991 move_pattern != MV_TURNING_RANDOM)
7995 if (MovDelay[x][y] && (element == EL_BUG ||
7996 element == EL_SPACESHIP ||
7997 element == EL_SP_SNIKSNAK ||
7998 element == EL_SP_ELECTRON ||
7999 element == EL_MOLE))
8000 TEST_DrawLevelField(x, y);
8004 if (MovDelay[x][y]) // wait some time before next movement
8008 if (element == EL_ROBOT ||
8009 element == EL_YAMYAM ||
8010 element == EL_DARK_YAMYAM)
8012 DrawLevelElementAnimationIfNeeded(x, y, element);
8013 PlayLevelSoundAction(x, y, ACTION_WAITING);
8015 else if (element == EL_SP_ELECTRON)
8016 DrawLevelElementAnimationIfNeeded(x, y, element);
8017 else if (element == EL_DRAGON)
8020 int dir = MovDir[x][y];
8021 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8022 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8023 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8024 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8025 dir == MV_UP ? IMG_FLAMES_1_UP :
8026 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8027 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8029 GfxAction[x][y] = ACTION_ATTACKING;
8031 if (IS_PLAYER(x, y))
8032 DrawPlayerField(x, y);
8034 TEST_DrawLevelField(x, y);
8036 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8038 for (i = 1; i <= 3; i++)
8040 int xx = x + i * dx;
8041 int yy = y + i * dy;
8042 int sx = SCREENX(xx);
8043 int sy = SCREENY(yy);
8044 int flame_graphic = graphic + (i - 1);
8046 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
8051 int flamed = MovingOrBlocked2Element(xx, yy);
8053 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8056 RemoveMovingField(xx, yy);
8058 ChangeDelay[xx][yy] = 0;
8060 Feld[xx][yy] = EL_FLAMES;
8062 if (IN_SCR_FIELD(sx, sy))
8064 TEST_DrawLevelFieldCrumbled(xx, yy);
8065 DrawGraphic(sx, sy, flame_graphic, frame);
8070 if (Feld[xx][yy] == EL_FLAMES)
8071 Feld[xx][yy] = EL_EMPTY;
8072 TEST_DrawLevelField(xx, yy);
8077 if (MovDelay[x][y]) // element still has to wait some time
8079 PlayLevelSoundAction(x, y, ACTION_WAITING);
8085 // now make next step
8087 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8089 if (DONT_COLLIDE_WITH(element) &&
8090 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8091 !PLAYER_ENEMY_PROTECTED(newx, newy))
8093 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8098 else if (CAN_MOVE_INTO_ACID(element) &&
8099 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
8100 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8101 (MovDir[x][y] == MV_DOWN ||
8102 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8104 SplashAcid(newx, newy);
8105 Store[x][y] = EL_ACID;
8107 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8109 if (Feld[newx][newy] == EL_EXIT_OPEN ||
8110 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
8111 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
8112 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8115 TEST_DrawLevelField(x, y);
8117 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8118 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8119 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8121 game.friends_still_needed--;
8122 if (!game.friends_still_needed &&
8124 game.all_players_gone)
8129 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
8131 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8132 TEST_DrawLevelField(newx, newy);
8134 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8136 else if (!IS_FREE(newx, newy))
8138 GfxAction[x][y] = ACTION_WAITING;
8140 if (IS_PLAYER(x, y))
8141 DrawPlayerField(x, y);
8143 TEST_DrawLevelField(x, y);
8148 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8150 if (IS_FOOD_PIG(Feld[newx][newy]))
8152 if (IS_MOVING(newx, newy))
8153 RemoveMovingField(newx, newy);
8156 Feld[newx][newy] = EL_EMPTY;
8157 TEST_DrawLevelField(newx, newy);
8160 PlayLevelSound(x, y, SND_PIG_DIGGING);
8162 else if (!IS_FREE(newx, newy))
8164 if (IS_PLAYER(x, y))
8165 DrawPlayerField(x, y);
8167 TEST_DrawLevelField(x, y);
8172 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8174 if (Store[x][y] != EL_EMPTY)
8176 boolean can_clone = FALSE;
8179 // check if element to clone is still there
8180 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8182 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8190 // cannot clone or target field not free anymore -- do not clone
8191 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8192 Store[x][y] = EL_EMPTY;
8195 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8197 if (IS_MV_DIAGONAL(MovDir[x][y]))
8199 int diagonal_move_dir = MovDir[x][y];
8200 int stored = Store[x][y];
8201 int change_delay = 8;
8204 // android is moving diagonally
8206 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8208 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8209 GfxElement[x][y] = EL_EMC_ANDROID;
8210 GfxAction[x][y] = ACTION_SHRINKING;
8211 GfxDir[x][y] = diagonal_move_dir;
8212 ChangeDelay[x][y] = change_delay;
8214 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8217 DrawLevelGraphicAnimation(x, y, graphic);
8218 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8220 if (Feld[newx][newy] == EL_ACID)
8222 SplashAcid(newx, newy);
8227 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8229 Store[newx][newy] = EL_EMC_ANDROID;
8230 GfxElement[newx][newy] = EL_EMC_ANDROID;
8231 GfxAction[newx][newy] = ACTION_GROWING;
8232 GfxDir[newx][newy] = diagonal_move_dir;
8233 ChangeDelay[newx][newy] = change_delay;
8235 graphic = el_act_dir2img(GfxElement[newx][newy],
8236 GfxAction[newx][newy], GfxDir[newx][newy]);
8238 DrawLevelGraphicAnimation(newx, newy, graphic);
8239 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8245 Feld[newx][newy] = EL_EMPTY;
8246 TEST_DrawLevelField(newx, newy);
8248 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8251 else if (!IS_FREE(newx, newy))
8256 else if (IS_CUSTOM_ELEMENT(element) &&
8257 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8259 if (!DigFieldByCE(newx, newy, element))
8262 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8264 RunnerVisit[x][y] = FrameCounter;
8265 PlayerVisit[x][y] /= 8; // expire player visit path
8268 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8270 if (!IS_FREE(newx, newy))
8272 if (IS_PLAYER(x, y))
8273 DrawPlayerField(x, y);
8275 TEST_DrawLevelField(x, y);
8281 boolean wanna_flame = !RND(10);
8282 int dx = newx - x, dy = newy - y;
8283 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8284 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8285 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8286 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8287 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8288 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8291 IS_CLASSIC_ENEMY(element1) ||
8292 IS_CLASSIC_ENEMY(element2)) &&
8293 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8294 element1 != EL_FLAMES && element2 != EL_FLAMES)
8296 ResetGfxAnimation(x, y);
8297 GfxAction[x][y] = ACTION_ATTACKING;
8299 if (IS_PLAYER(x, y))
8300 DrawPlayerField(x, y);
8302 TEST_DrawLevelField(x, y);
8304 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8306 MovDelay[x][y] = 50;
8308 Feld[newx][newy] = EL_FLAMES;
8309 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8310 Feld[newx1][newy1] = EL_FLAMES;
8311 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8312 Feld[newx2][newy2] = EL_FLAMES;
8318 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8319 Feld[newx][newy] == EL_DIAMOND)
8321 if (IS_MOVING(newx, newy))
8322 RemoveMovingField(newx, newy);
8325 Feld[newx][newy] = EL_EMPTY;
8326 TEST_DrawLevelField(newx, newy);
8329 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8331 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8332 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8334 if (AmoebaNr[newx][newy])
8336 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8337 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8338 Feld[newx][newy] == EL_BD_AMOEBA)
8339 AmoebaCnt[AmoebaNr[newx][newy]]--;
8342 if (IS_MOVING(newx, newy))
8344 RemoveMovingField(newx, newy);
8348 Feld[newx][newy] = EL_EMPTY;
8349 TEST_DrawLevelField(newx, newy);
8352 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8354 else if ((element == EL_PACMAN || element == EL_MOLE)
8355 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8357 if (AmoebaNr[newx][newy])
8359 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8360 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8361 Feld[newx][newy] == EL_BD_AMOEBA)
8362 AmoebaCnt[AmoebaNr[newx][newy]]--;
8365 if (element == EL_MOLE)
8367 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8368 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8370 ResetGfxAnimation(x, y);
8371 GfxAction[x][y] = ACTION_DIGGING;
8372 TEST_DrawLevelField(x, y);
8374 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8376 return; // wait for shrinking amoeba
8378 else // element == EL_PACMAN
8380 Feld[newx][newy] = EL_EMPTY;
8381 TEST_DrawLevelField(newx, newy);
8382 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8385 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8386 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8387 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8389 // wait for shrinking amoeba to completely disappear
8392 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8394 // object was running against a wall
8398 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8399 DrawLevelElementAnimation(x, y, element);
8401 if (DONT_TOUCH(element))
8402 TestIfBadThingTouchesPlayer(x, y);
8407 InitMovingField(x, y, MovDir[x][y]);
8409 PlayLevelSoundAction(x, y, ACTION_MOVING);
8413 ContinueMoving(x, y);
8416 void ContinueMoving(int x, int y)
8418 int element = Feld[x][y];
8419 struct ElementInfo *ei = &element_info[element];
8420 int direction = MovDir[x][y];
8421 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8422 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8423 int newx = x + dx, newy = y + dy;
8424 int stored = Store[x][y];
8425 int stored_new = Store[newx][newy];
8426 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8427 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8428 boolean last_line = (newy == lev_fieldy - 1);
8430 MovPos[x][y] += getElementMoveStepsize(x, y);
8432 if (pushed_by_player) // special case: moving object pushed by player
8433 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8435 if (ABS(MovPos[x][y]) < TILEX)
8437 TEST_DrawLevelField(x, y);
8439 return; // element is still moving
8442 // element reached destination field
8444 Feld[x][y] = EL_EMPTY;
8445 Feld[newx][newy] = element;
8446 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8448 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8450 element = Feld[newx][newy] = EL_ACID;
8452 else if (element == EL_MOLE)
8454 Feld[x][y] = EL_SAND;
8456 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8458 else if (element == EL_QUICKSAND_FILLING)
8460 element = Feld[newx][newy] = get_next_element(element);
8461 Store[newx][newy] = Store[x][y];
8463 else if (element == EL_QUICKSAND_EMPTYING)
8465 Feld[x][y] = get_next_element(element);
8466 element = Feld[newx][newy] = Store[x][y];
8468 else if (element == EL_QUICKSAND_FAST_FILLING)
8470 element = Feld[newx][newy] = get_next_element(element);
8471 Store[newx][newy] = Store[x][y];
8473 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8475 Feld[x][y] = get_next_element(element);
8476 element = Feld[newx][newy] = Store[x][y];
8478 else if (element == EL_MAGIC_WALL_FILLING)
8480 element = Feld[newx][newy] = get_next_element(element);
8481 if (!game.magic_wall_active)
8482 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8483 Store[newx][newy] = Store[x][y];
8485 else if (element == EL_MAGIC_WALL_EMPTYING)
8487 Feld[x][y] = get_next_element(element);
8488 if (!game.magic_wall_active)
8489 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8490 element = Feld[newx][newy] = Store[x][y];
8492 InitField(newx, newy, FALSE);
8494 else if (element == EL_BD_MAGIC_WALL_FILLING)
8496 element = Feld[newx][newy] = get_next_element(element);
8497 if (!game.magic_wall_active)
8498 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8499 Store[newx][newy] = Store[x][y];
8501 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8503 Feld[x][y] = get_next_element(element);
8504 if (!game.magic_wall_active)
8505 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8506 element = Feld[newx][newy] = Store[x][y];
8508 InitField(newx, newy, FALSE);
8510 else if (element == EL_DC_MAGIC_WALL_FILLING)
8512 element = Feld[newx][newy] = get_next_element(element);
8513 if (!game.magic_wall_active)
8514 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8515 Store[newx][newy] = Store[x][y];
8517 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8519 Feld[x][y] = get_next_element(element);
8520 if (!game.magic_wall_active)
8521 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8522 element = Feld[newx][newy] = Store[x][y];
8524 InitField(newx, newy, FALSE);
8526 else if (element == EL_AMOEBA_DROPPING)
8528 Feld[x][y] = get_next_element(element);
8529 element = Feld[newx][newy] = Store[x][y];
8531 else if (element == EL_SOKOBAN_OBJECT)
8534 Feld[x][y] = Back[x][y];
8536 if (Back[newx][newy])
8537 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8539 Back[x][y] = Back[newx][newy] = 0;
8542 Store[x][y] = EL_EMPTY;
8547 MovDelay[newx][newy] = 0;
8549 if (CAN_CHANGE_OR_HAS_ACTION(element))
8551 // copy element change control values to new field
8552 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8553 ChangePage[newx][newy] = ChangePage[x][y];
8554 ChangeCount[newx][newy] = ChangeCount[x][y];
8555 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8558 CustomValue[newx][newy] = CustomValue[x][y];
8560 ChangeDelay[x][y] = 0;
8561 ChangePage[x][y] = -1;
8562 ChangeCount[x][y] = 0;
8563 ChangeEvent[x][y] = -1;
8565 CustomValue[x][y] = 0;
8567 // copy animation control values to new field
8568 GfxFrame[newx][newy] = GfxFrame[x][y];
8569 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8570 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8571 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8573 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8575 // some elements can leave other elements behind after moving
8576 if (ei->move_leave_element != EL_EMPTY &&
8577 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8578 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8580 int move_leave_element = ei->move_leave_element;
8582 // this makes it possible to leave the removed element again
8583 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8584 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8586 Feld[x][y] = move_leave_element;
8588 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8589 MovDir[x][y] = direction;
8591 InitField(x, y, FALSE);
8593 if (GFX_CRUMBLED(Feld[x][y]))
8594 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8596 if (ELEM_IS_PLAYER(move_leave_element))
8597 RelocatePlayer(x, y, move_leave_element);
8600 // do this after checking for left-behind element
8601 ResetGfxAnimation(x, y); // reset animation values for old field
8603 if (!CAN_MOVE(element) ||
8604 (CAN_FALL(element) && direction == MV_DOWN &&
8605 (element == EL_SPRING ||
8606 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8607 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8608 GfxDir[x][y] = MovDir[newx][newy] = 0;
8610 TEST_DrawLevelField(x, y);
8611 TEST_DrawLevelField(newx, newy);
8613 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8615 // prevent pushed element from moving on in pushed direction
8616 if (pushed_by_player && CAN_MOVE(element) &&
8617 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8618 !(element_info[element].move_pattern & direction))
8619 TurnRound(newx, newy);
8621 // prevent elements on conveyor belt from moving on in last direction
8622 if (pushed_by_conveyor && CAN_FALL(element) &&
8623 direction & MV_HORIZONTAL)
8624 MovDir[newx][newy] = 0;
8626 if (!pushed_by_player)
8628 int nextx = newx + dx, nexty = newy + dy;
8629 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8631 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8633 if (CAN_FALL(element) && direction == MV_DOWN)
8634 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8636 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8637 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8639 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8640 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8643 if (DONT_TOUCH(element)) // object may be nasty to player or others
8645 TestIfBadThingTouchesPlayer(newx, newy);
8646 TestIfBadThingTouchesFriend(newx, newy);
8648 if (!IS_CUSTOM_ELEMENT(element))
8649 TestIfBadThingTouchesOtherBadThing(newx, newy);
8651 else if (element == EL_PENGUIN)
8652 TestIfFriendTouchesBadThing(newx, newy);
8654 if (DONT_GET_HIT_BY(element))
8656 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8659 // give the player one last chance (one more frame) to move away
8660 if (CAN_FALL(element) && direction == MV_DOWN &&
8661 (last_line || (!IS_FREE(x, newy + 1) &&
8662 (!IS_PLAYER(x, newy + 1) ||
8663 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8666 if (pushed_by_player && !game.use_change_when_pushing_bug)
8668 int push_side = MV_DIR_OPPOSITE(direction);
8669 struct PlayerInfo *player = PLAYERINFO(x, y);
8671 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8672 player->index_bit, push_side);
8673 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8674 player->index_bit, push_side);
8677 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8678 MovDelay[newx][newy] = 1;
8680 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8682 TestIfElementTouchesCustomElement(x, y); // empty or new element
8683 TestIfElementHitsCustomElement(newx, newy, direction);
8684 TestIfPlayerTouchesCustomElement(newx, newy);
8685 TestIfElementTouchesCustomElement(newx, newy);
8687 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8688 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8689 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8690 MV_DIR_OPPOSITE(direction));
8693 int AmoebeNachbarNr(int ax, int ay)
8696 int element = Feld[ax][ay];
8698 static int xy[4][2] =
8706 for (i = 0; i < NUM_DIRECTIONS; i++)
8708 int x = ax + xy[i][0];
8709 int y = ay + xy[i][1];
8711 if (!IN_LEV_FIELD(x, y))
8714 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8715 group_nr = AmoebaNr[x][y];
8721 static void AmoebenVereinigen(int ax, int ay)
8723 int i, x, y, xx, yy;
8724 int new_group_nr = AmoebaNr[ax][ay];
8725 static int xy[4][2] =
8733 if (new_group_nr == 0)
8736 for (i = 0; i < NUM_DIRECTIONS; i++)
8741 if (!IN_LEV_FIELD(x, y))
8744 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8745 Feld[x][y] == EL_BD_AMOEBA ||
8746 Feld[x][y] == EL_AMOEBA_DEAD) &&
8747 AmoebaNr[x][y] != new_group_nr)
8749 int old_group_nr = AmoebaNr[x][y];
8751 if (old_group_nr == 0)
8754 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8755 AmoebaCnt[old_group_nr] = 0;
8756 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8757 AmoebaCnt2[old_group_nr] = 0;
8759 SCAN_PLAYFIELD(xx, yy)
8761 if (AmoebaNr[xx][yy] == old_group_nr)
8762 AmoebaNr[xx][yy] = new_group_nr;
8768 void AmoebeUmwandeln(int ax, int ay)
8772 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8774 int group_nr = AmoebaNr[ax][ay];
8779 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8780 printf("AmoebeUmwandeln(): This should never happen!\n");
8785 SCAN_PLAYFIELD(x, y)
8787 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8790 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8794 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8795 SND_AMOEBA_TURNING_TO_GEM :
8796 SND_AMOEBA_TURNING_TO_ROCK));
8801 static int xy[4][2] =
8809 for (i = 0; i < NUM_DIRECTIONS; i++)
8814 if (!IN_LEV_FIELD(x, y))
8817 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8819 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8820 SND_AMOEBA_TURNING_TO_GEM :
8821 SND_AMOEBA_TURNING_TO_ROCK));
8828 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8831 int group_nr = AmoebaNr[ax][ay];
8832 boolean done = FALSE;
8837 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8838 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8843 SCAN_PLAYFIELD(x, y)
8845 if (AmoebaNr[x][y] == group_nr &&
8846 (Feld[x][y] == EL_AMOEBA_DEAD ||
8847 Feld[x][y] == EL_BD_AMOEBA ||
8848 Feld[x][y] == EL_AMOEBA_GROWING))
8851 Feld[x][y] = new_element;
8852 InitField(x, y, FALSE);
8853 TEST_DrawLevelField(x, y);
8859 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8860 SND_BD_AMOEBA_TURNING_TO_ROCK :
8861 SND_BD_AMOEBA_TURNING_TO_GEM));
8864 static void AmoebeWaechst(int x, int y)
8866 static unsigned int sound_delay = 0;
8867 static unsigned int sound_delay_value = 0;
8869 if (!MovDelay[x][y]) // start new growing cycle
8873 if (DelayReached(&sound_delay, sound_delay_value))
8875 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8876 sound_delay_value = 30;
8880 if (MovDelay[x][y]) // wait some time before growing bigger
8883 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8885 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8886 6 - MovDelay[x][y]);
8888 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8891 if (!MovDelay[x][y])
8893 Feld[x][y] = Store[x][y];
8895 TEST_DrawLevelField(x, y);
8900 static void AmoebaDisappearing(int x, int y)
8902 static unsigned int sound_delay = 0;
8903 static unsigned int sound_delay_value = 0;
8905 if (!MovDelay[x][y]) // start new shrinking cycle
8909 if (DelayReached(&sound_delay, sound_delay_value))
8910 sound_delay_value = 30;
8913 if (MovDelay[x][y]) // wait some time before shrinking
8916 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8918 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8919 6 - MovDelay[x][y]);
8921 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8924 if (!MovDelay[x][y])
8926 Feld[x][y] = EL_EMPTY;
8927 TEST_DrawLevelField(x, y);
8929 // don't let mole enter this field in this cycle;
8930 // (give priority to objects falling to this field from above)
8936 static void AmoebeAbleger(int ax, int ay)
8939 int element = Feld[ax][ay];
8940 int graphic = el2img(element);
8941 int newax = ax, neway = ay;
8942 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8943 static int xy[4][2] =
8951 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8953 Feld[ax][ay] = EL_AMOEBA_DEAD;
8954 TEST_DrawLevelField(ax, ay);
8958 if (IS_ANIMATED(graphic))
8959 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8961 if (!MovDelay[ax][ay]) // start making new amoeba field
8962 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8964 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8967 if (MovDelay[ax][ay])
8971 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8974 int x = ax + xy[start][0];
8975 int y = ay + xy[start][1];
8977 if (!IN_LEV_FIELD(x, y))
8980 if (IS_FREE(x, y) ||
8981 CAN_GROW_INTO(Feld[x][y]) ||
8982 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8983 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8989 if (newax == ax && neway == ay)
8992 else // normal or "filled" (BD style) amoeba
8995 boolean waiting_for_player = FALSE;
8997 for (i = 0; i < NUM_DIRECTIONS; i++)
8999 int j = (start + i) % 4;
9000 int x = ax + xy[j][0];
9001 int y = ay + xy[j][1];
9003 if (!IN_LEV_FIELD(x, y))
9006 if (IS_FREE(x, y) ||
9007 CAN_GROW_INTO(Feld[x][y]) ||
9008 Feld[x][y] == EL_QUICKSAND_EMPTY ||
9009 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
9015 else if (IS_PLAYER(x, y))
9016 waiting_for_player = TRUE;
9019 if (newax == ax && neway == ay) // amoeba cannot grow
9021 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9023 Feld[ax][ay] = EL_AMOEBA_DEAD;
9024 TEST_DrawLevelField(ax, ay);
9025 AmoebaCnt[AmoebaNr[ax][ay]]--;
9027 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9029 if (element == EL_AMOEBA_FULL)
9030 AmoebeUmwandeln(ax, ay);
9031 else if (element == EL_BD_AMOEBA)
9032 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
9037 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9039 // amoeba gets larger by growing in some direction
9041 int new_group_nr = AmoebaNr[ax][ay];
9044 if (new_group_nr == 0)
9046 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
9047 printf("AmoebeAbleger(): This should never happen!\n");
9052 AmoebaNr[newax][neway] = new_group_nr;
9053 AmoebaCnt[new_group_nr]++;
9054 AmoebaCnt2[new_group_nr]++;
9056 // if amoeba touches other amoeba(s) after growing, unify them
9057 AmoebenVereinigen(newax, neway);
9059 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9061 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
9067 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9068 (neway == lev_fieldy - 1 && newax != ax))
9070 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9071 Store[newax][neway] = element;
9073 else if (neway == ay || element == EL_EMC_DRIPPER)
9075 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9077 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9081 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9082 Feld[ax][ay] = EL_AMOEBA_DROPPING;
9083 Store[ax][ay] = EL_AMOEBA_DROP;
9084 ContinueMoving(ax, ay);
9088 TEST_DrawLevelField(newax, neway);
9091 static void Life(int ax, int ay)
9095 int element = Feld[ax][ay];
9096 int graphic = el2img(element);
9097 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9099 boolean changed = FALSE;
9101 if (IS_ANIMATED(graphic))
9102 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9107 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9108 MovDelay[ax][ay] = life_time;
9110 if (MovDelay[ax][ay]) // wait some time before next cycle
9113 if (MovDelay[ax][ay])
9117 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9119 int xx = ax+x1, yy = ay+y1;
9120 int old_element = Feld[xx][yy];
9121 int num_neighbours = 0;
9123 if (!IN_LEV_FIELD(xx, yy))
9126 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9128 int x = xx+x2, y = yy+y2;
9130 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9133 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9134 boolean is_neighbour = FALSE;
9136 if (level.use_life_bugs)
9138 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9139 (IS_FREE(x, y) && Stop[x][y]));
9142 (Last[x][y] == element || is_player_cell);
9148 boolean is_free = FALSE;
9150 if (level.use_life_bugs)
9151 is_free = (IS_FREE(xx, yy));
9153 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9155 if (xx == ax && yy == ay) // field in the middle
9157 if (num_neighbours < life_parameter[0] ||
9158 num_neighbours > life_parameter[1])
9160 Feld[xx][yy] = EL_EMPTY;
9161 if (Feld[xx][yy] != old_element)
9162 TEST_DrawLevelField(xx, yy);
9163 Stop[xx][yy] = TRUE;
9167 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9168 { // free border field
9169 if (num_neighbours >= life_parameter[2] &&
9170 num_neighbours <= life_parameter[3])
9172 Feld[xx][yy] = element;
9173 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9174 if (Feld[xx][yy] != old_element)
9175 TEST_DrawLevelField(xx, yy);
9176 Stop[xx][yy] = TRUE;
9183 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9184 SND_GAME_OF_LIFE_GROWING);
9187 static void InitRobotWheel(int x, int y)
9189 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9192 static void RunRobotWheel(int x, int y)
9194 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9197 static void StopRobotWheel(int x, int y)
9199 if (game.robot_wheel_x == x &&
9200 game.robot_wheel_y == y)
9202 game.robot_wheel_x = -1;
9203 game.robot_wheel_y = -1;
9204 game.robot_wheel_active = FALSE;
9208 static void InitTimegateWheel(int x, int y)
9210 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9213 static void RunTimegateWheel(int x, int y)
9215 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9218 static void InitMagicBallDelay(int x, int y)
9220 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9223 static void ActivateMagicBall(int bx, int by)
9227 if (level.ball_random)
9229 int pos_border = RND(8); // select one of the eight border elements
9230 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9231 int xx = pos_content % 3;
9232 int yy = pos_content / 3;
9237 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9238 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9242 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9244 int xx = x - bx + 1;
9245 int yy = y - by + 1;
9247 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9248 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9252 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9255 static void CheckExit(int x, int y)
9257 if (game.gems_still_needed > 0 ||
9258 game.sokoban_fields_still_needed > 0 ||
9259 game.sokoban_objects_still_needed > 0 ||
9260 game.lights_still_needed > 0)
9262 int element = Feld[x][y];
9263 int graphic = el2img(element);
9265 if (IS_ANIMATED(graphic))
9266 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9271 // do not re-open exit door closed after last player
9272 if (game.all_players_gone)
9275 Feld[x][y] = EL_EXIT_OPENING;
9277 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9280 static void CheckExitEM(int x, int y)
9282 if (game.gems_still_needed > 0 ||
9283 game.sokoban_fields_still_needed > 0 ||
9284 game.sokoban_objects_still_needed > 0 ||
9285 game.lights_still_needed > 0)
9287 int element = Feld[x][y];
9288 int graphic = el2img(element);
9290 if (IS_ANIMATED(graphic))
9291 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9296 // do not re-open exit door closed after last player
9297 if (game.all_players_gone)
9300 Feld[x][y] = EL_EM_EXIT_OPENING;
9302 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9305 static void CheckExitSteel(int x, int y)
9307 if (game.gems_still_needed > 0 ||
9308 game.sokoban_fields_still_needed > 0 ||
9309 game.sokoban_objects_still_needed > 0 ||
9310 game.lights_still_needed > 0)
9312 int element = Feld[x][y];
9313 int graphic = el2img(element);
9315 if (IS_ANIMATED(graphic))
9316 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9321 // do not re-open exit door closed after last player
9322 if (game.all_players_gone)
9325 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9327 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9330 static void CheckExitSteelEM(int x, int y)
9332 if (game.gems_still_needed > 0 ||
9333 game.sokoban_fields_still_needed > 0 ||
9334 game.sokoban_objects_still_needed > 0 ||
9335 game.lights_still_needed > 0)
9337 int element = Feld[x][y];
9338 int graphic = el2img(element);
9340 if (IS_ANIMATED(graphic))
9341 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9346 // do not re-open exit door closed after last player
9347 if (game.all_players_gone)
9350 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9352 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9355 static void CheckExitSP(int x, int y)
9357 if (game.gems_still_needed > 0)
9359 int element = Feld[x][y];
9360 int graphic = el2img(element);
9362 if (IS_ANIMATED(graphic))
9363 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9368 // do not re-open exit door closed after last player
9369 if (game.all_players_gone)
9372 Feld[x][y] = EL_SP_EXIT_OPENING;
9374 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9377 static void CloseAllOpenTimegates(void)
9381 SCAN_PLAYFIELD(x, y)
9383 int element = Feld[x][y];
9385 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9387 Feld[x][y] = EL_TIMEGATE_CLOSING;
9389 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9394 static void DrawTwinkleOnField(int x, int y)
9396 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9399 if (Feld[x][y] == EL_BD_DIAMOND)
9402 if (MovDelay[x][y] == 0) // next animation frame
9403 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9405 if (MovDelay[x][y] != 0) // wait some time before next frame
9409 DrawLevelElementAnimation(x, y, Feld[x][y]);
9411 if (MovDelay[x][y] != 0)
9413 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9414 10 - MovDelay[x][y]);
9416 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9421 static void MauerWaechst(int x, int y)
9425 if (!MovDelay[x][y]) // next animation frame
9426 MovDelay[x][y] = 3 * delay;
9428 if (MovDelay[x][y]) // wait some time before next frame
9432 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9434 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9435 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9437 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9440 if (!MovDelay[x][y])
9442 if (MovDir[x][y] == MV_LEFT)
9444 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9445 TEST_DrawLevelField(x - 1, y);
9447 else if (MovDir[x][y] == MV_RIGHT)
9449 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9450 TEST_DrawLevelField(x + 1, y);
9452 else if (MovDir[x][y] == MV_UP)
9454 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9455 TEST_DrawLevelField(x, y - 1);
9459 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9460 TEST_DrawLevelField(x, y + 1);
9463 Feld[x][y] = Store[x][y];
9465 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9466 TEST_DrawLevelField(x, y);
9471 static void MauerAbleger(int ax, int ay)
9473 int element = Feld[ax][ay];
9474 int graphic = el2img(element);
9475 boolean oben_frei = FALSE, unten_frei = FALSE;
9476 boolean links_frei = FALSE, rechts_frei = FALSE;
9477 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9478 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9479 boolean new_wall = FALSE;
9481 if (IS_ANIMATED(graphic))
9482 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9484 if (!MovDelay[ax][ay]) // start building new wall
9485 MovDelay[ax][ay] = 6;
9487 if (MovDelay[ax][ay]) // wait some time before building new wall
9490 if (MovDelay[ax][ay])
9494 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9496 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9498 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9500 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9503 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9504 element == EL_EXPANDABLE_WALL_ANY)
9508 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9509 Store[ax][ay-1] = element;
9510 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9511 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9512 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9513 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9518 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9519 Store[ax][ay+1] = element;
9520 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9521 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9522 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9523 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9528 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9529 element == EL_EXPANDABLE_WALL_ANY ||
9530 element == EL_EXPANDABLE_WALL ||
9531 element == EL_BD_EXPANDABLE_WALL)
9535 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9536 Store[ax-1][ay] = element;
9537 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9538 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9539 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9540 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9546 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9547 Store[ax+1][ay] = element;
9548 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9549 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9550 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9551 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9556 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9557 TEST_DrawLevelField(ax, ay);
9559 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9561 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9562 unten_massiv = TRUE;
9563 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9564 links_massiv = TRUE;
9565 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9566 rechts_massiv = TRUE;
9568 if (((oben_massiv && unten_massiv) ||
9569 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9570 element == EL_EXPANDABLE_WALL) &&
9571 ((links_massiv && rechts_massiv) ||
9572 element == EL_EXPANDABLE_WALL_VERTICAL))
9573 Feld[ax][ay] = EL_WALL;
9576 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9579 static void MauerAblegerStahl(int ax, int ay)
9581 int element = Feld[ax][ay];
9582 int graphic = el2img(element);
9583 boolean oben_frei = FALSE, unten_frei = FALSE;
9584 boolean links_frei = FALSE, rechts_frei = FALSE;
9585 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9586 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9587 boolean new_wall = FALSE;
9589 if (IS_ANIMATED(graphic))
9590 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9592 if (!MovDelay[ax][ay]) // start building new wall
9593 MovDelay[ax][ay] = 6;
9595 if (MovDelay[ax][ay]) // wait some time before building new wall
9598 if (MovDelay[ax][ay])
9602 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9604 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9606 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9608 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9611 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9612 element == EL_EXPANDABLE_STEELWALL_ANY)
9616 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9617 Store[ax][ay-1] = element;
9618 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9619 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9620 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9621 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9626 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9627 Store[ax][ay+1] = element;
9628 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9629 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9630 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9631 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9636 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9637 element == EL_EXPANDABLE_STEELWALL_ANY)
9641 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9642 Store[ax-1][ay] = element;
9643 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9644 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9645 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9646 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9652 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9653 Store[ax+1][ay] = element;
9654 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9655 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9656 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9657 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9662 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9664 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9665 unten_massiv = TRUE;
9666 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9667 links_massiv = TRUE;
9668 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9669 rechts_massiv = TRUE;
9671 if (((oben_massiv && unten_massiv) ||
9672 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9673 ((links_massiv && rechts_massiv) ||
9674 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9675 Feld[ax][ay] = EL_STEELWALL;
9678 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9681 static void CheckForDragon(int x, int y)
9684 boolean dragon_found = FALSE;
9685 static int xy[4][2] =
9693 for (i = 0; i < NUM_DIRECTIONS; i++)
9695 for (j = 0; j < 4; j++)
9697 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9699 if (IN_LEV_FIELD(xx, yy) &&
9700 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9702 if (Feld[xx][yy] == EL_DRAGON)
9703 dragon_found = TRUE;
9712 for (i = 0; i < NUM_DIRECTIONS; i++)
9714 for (j = 0; j < 3; j++)
9716 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9718 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9720 Feld[xx][yy] = EL_EMPTY;
9721 TEST_DrawLevelField(xx, yy);
9730 static void InitBuggyBase(int x, int y)
9732 int element = Feld[x][y];
9733 int activating_delay = FRAMES_PER_SECOND / 4;
9736 (element == EL_SP_BUGGY_BASE ?
9737 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9738 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9740 element == EL_SP_BUGGY_BASE_ACTIVE ?
9741 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9744 static void WarnBuggyBase(int x, int y)
9747 static int xy[4][2] =
9755 for (i = 0; i < NUM_DIRECTIONS; i++)
9757 int xx = x + xy[i][0];
9758 int yy = y + xy[i][1];
9760 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9762 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9769 static void InitTrap(int x, int y)
9771 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9774 static void ActivateTrap(int x, int y)
9776 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9779 static void ChangeActiveTrap(int x, int y)
9781 int graphic = IMG_TRAP_ACTIVE;
9783 // if new animation frame was drawn, correct crumbled sand border
9784 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9785 TEST_DrawLevelFieldCrumbled(x, y);
9788 static int getSpecialActionElement(int element, int number, int base_element)
9790 return (element != EL_EMPTY ? element :
9791 number != -1 ? base_element + number - 1 :
9795 static int getModifiedActionNumber(int value_old, int operator, int operand,
9796 int value_min, int value_max)
9798 int value_new = (operator == CA_MODE_SET ? operand :
9799 operator == CA_MODE_ADD ? value_old + operand :
9800 operator == CA_MODE_SUBTRACT ? value_old - operand :
9801 operator == CA_MODE_MULTIPLY ? value_old * operand :
9802 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9803 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9806 return (value_new < value_min ? value_min :
9807 value_new > value_max ? value_max :
9811 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9813 struct ElementInfo *ei = &element_info[element];
9814 struct ElementChangeInfo *change = &ei->change_page[page];
9815 int target_element = change->target_element;
9816 int action_type = change->action_type;
9817 int action_mode = change->action_mode;
9818 int action_arg = change->action_arg;
9819 int action_element = change->action_element;
9822 if (!change->has_action)
9825 // ---------- determine action paramater values -----------------------------
9827 int level_time_value =
9828 (level.time > 0 ? TimeLeft :
9831 int action_arg_element_raw =
9832 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9833 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9834 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9835 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9836 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9837 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9838 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9840 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9842 int action_arg_direction =
9843 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9844 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9845 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9846 change->actual_trigger_side :
9847 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9848 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9851 int action_arg_number_min =
9852 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9855 int action_arg_number_max =
9856 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9857 action_type == CA_SET_LEVEL_GEMS ? 999 :
9858 action_type == CA_SET_LEVEL_TIME ? 9999 :
9859 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9860 action_type == CA_SET_CE_VALUE ? 9999 :
9861 action_type == CA_SET_CE_SCORE ? 9999 :
9864 int action_arg_number_reset =
9865 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9866 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9867 action_type == CA_SET_LEVEL_TIME ? level.time :
9868 action_type == CA_SET_LEVEL_SCORE ? 0 :
9869 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9870 action_type == CA_SET_CE_SCORE ? 0 :
9873 int action_arg_number =
9874 (action_arg <= CA_ARG_MAX ? action_arg :
9875 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9876 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9877 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9878 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9879 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9880 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9881 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9882 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9883 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9884 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9885 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9886 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9887 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9888 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9889 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9890 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9891 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9892 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9893 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9894 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9895 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9898 int action_arg_number_old =
9899 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9900 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9901 action_type == CA_SET_LEVEL_SCORE ? game.score :
9902 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9903 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9906 int action_arg_number_new =
9907 getModifiedActionNumber(action_arg_number_old,
9908 action_mode, action_arg_number,
9909 action_arg_number_min, action_arg_number_max);
9911 int trigger_player_bits =
9912 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9913 change->actual_trigger_player_bits : change->trigger_player);
9915 int action_arg_player_bits =
9916 (action_arg >= CA_ARG_PLAYER_1 &&
9917 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9918 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9919 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9922 // ---------- execute action -----------------------------------------------
9924 switch (action_type)
9931 // ---------- level actions ----------------------------------------------
9933 case CA_RESTART_LEVEL:
9935 game.restart_level = TRUE;
9940 case CA_SHOW_ENVELOPE:
9942 int element = getSpecialActionElement(action_arg_element,
9943 action_arg_number, EL_ENVELOPE_1);
9945 if (IS_ENVELOPE(element))
9946 local_player->show_envelope = element;
9951 case CA_SET_LEVEL_TIME:
9953 if (level.time > 0) // only modify limited time value
9955 TimeLeft = action_arg_number_new;
9957 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9959 DisplayGameControlValues();
9961 if (!TimeLeft && setup.time_limit)
9962 for (i = 0; i < MAX_PLAYERS; i++)
9963 KillPlayer(&stored_player[i]);
9969 case CA_SET_LEVEL_SCORE:
9971 game.score = action_arg_number_new;
9973 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9975 DisplayGameControlValues();
9980 case CA_SET_LEVEL_GEMS:
9982 game.gems_still_needed = action_arg_number_new;
9984 game.snapshot.collected_item = TRUE;
9986 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9988 DisplayGameControlValues();
9993 case CA_SET_LEVEL_WIND:
9995 game.wind_direction = action_arg_direction;
10000 case CA_SET_LEVEL_RANDOM_SEED:
10002 // ensure that setting a new random seed while playing is predictable
10003 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10008 // ---------- player actions ---------------------------------------------
10010 case CA_MOVE_PLAYER:
10011 case CA_MOVE_PLAYER_NEW:
10013 // automatically move to the next field in specified direction
10014 for (i = 0; i < MAX_PLAYERS; i++)
10015 if (trigger_player_bits & (1 << i))
10016 if (action_type == CA_MOVE_PLAYER ||
10017 stored_player[i].MovPos == 0)
10018 stored_player[i].programmed_action = action_arg_direction;
10023 case CA_EXIT_PLAYER:
10025 for (i = 0; i < MAX_PLAYERS; i++)
10026 if (action_arg_player_bits & (1 << i))
10027 ExitPlayer(&stored_player[i]);
10029 if (game.players_still_needed == 0)
10035 case CA_KILL_PLAYER:
10037 for (i = 0; i < MAX_PLAYERS; i++)
10038 if (action_arg_player_bits & (1 << i))
10039 KillPlayer(&stored_player[i]);
10044 case CA_SET_PLAYER_KEYS:
10046 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10047 int element = getSpecialActionElement(action_arg_element,
10048 action_arg_number, EL_KEY_1);
10050 if (IS_KEY(element))
10052 for (i = 0; i < MAX_PLAYERS; i++)
10054 if (trigger_player_bits & (1 << i))
10056 stored_player[i].key[KEY_NR(element)] = key_state;
10058 DrawGameDoorValues();
10066 case CA_SET_PLAYER_SPEED:
10068 for (i = 0; i < MAX_PLAYERS; i++)
10070 if (trigger_player_bits & (1 << i))
10072 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10074 if (action_arg == CA_ARG_SPEED_FASTER &&
10075 stored_player[i].cannot_move)
10077 action_arg_number = STEPSIZE_VERY_SLOW;
10079 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10080 action_arg == CA_ARG_SPEED_FASTER)
10082 action_arg_number = 2;
10083 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10086 else if (action_arg == CA_ARG_NUMBER_RESET)
10088 action_arg_number = level.initial_player_stepsize[i];
10092 getModifiedActionNumber(move_stepsize,
10095 action_arg_number_min,
10096 action_arg_number_max);
10098 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10105 case CA_SET_PLAYER_SHIELD:
10107 for (i = 0; i < MAX_PLAYERS; i++)
10109 if (trigger_player_bits & (1 << i))
10111 if (action_arg == CA_ARG_SHIELD_OFF)
10113 stored_player[i].shield_normal_time_left = 0;
10114 stored_player[i].shield_deadly_time_left = 0;
10116 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10118 stored_player[i].shield_normal_time_left = 999999;
10120 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10122 stored_player[i].shield_normal_time_left = 999999;
10123 stored_player[i].shield_deadly_time_left = 999999;
10131 case CA_SET_PLAYER_GRAVITY:
10133 for (i = 0; i < MAX_PLAYERS; i++)
10135 if (trigger_player_bits & (1 << i))
10137 stored_player[i].gravity =
10138 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10139 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10140 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10141 stored_player[i].gravity);
10148 case CA_SET_PLAYER_ARTWORK:
10150 for (i = 0; i < MAX_PLAYERS; i++)
10152 if (trigger_player_bits & (1 << i))
10154 int artwork_element = action_arg_element;
10156 if (action_arg == CA_ARG_ELEMENT_RESET)
10158 (level.use_artwork_element[i] ? level.artwork_element[i] :
10159 stored_player[i].element_nr);
10161 if (stored_player[i].artwork_element != artwork_element)
10162 stored_player[i].Frame = 0;
10164 stored_player[i].artwork_element = artwork_element;
10166 SetPlayerWaiting(&stored_player[i], FALSE);
10168 // set number of special actions for bored and sleeping animation
10169 stored_player[i].num_special_action_bored =
10170 get_num_special_action(artwork_element,
10171 ACTION_BORING_1, ACTION_BORING_LAST);
10172 stored_player[i].num_special_action_sleeping =
10173 get_num_special_action(artwork_element,
10174 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10181 case CA_SET_PLAYER_INVENTORY:
10183 for (i = 0; i < MAX_PLAYERS; i++)
10185 struct PlayerInfo *player = &stored_player[i];
10188 if (trigger_player_bits & (1 << i))
10190 int inventory_element = action_arg_element;
10192 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10193 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10194 action_arg == CA_ARG_ELEMENT_ACTION)
10196 int element = inventory_element;
10197 int collect_count = element_info[element].collect_count_initial;
10199 if (!IS_CUSTOM_ELEMENT(element))
10202 if (collect_count == 0)
10203 player->inventory_infinite_element = element;
10205 for (k = 0; k < collect_count; k++)
10206 if (player->inventory_size < MAX_INVENTORY_SIZE)
10207 player->inventory_element[player->inventory_size++] =
10210 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10211 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10212 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10214 if (player->inventory_infinite_element != EL_UNDEFINED &&
10215 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10216 action_arg_element_raw))
10217 player->inventory_infinite_element = EL_UNDEFINED;
10219 for (k = 0, j = 0; j < player->inventory_size; j++)
10221 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10222 action_arg_element_raw))
10223 player->inventory_element[k++] = player->inventory_element[j];
10226 player->inventory_size = k;
10228 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10230 if (player->inventory_size > 0)
10232 for (j = 0; j < player->inventory_size - 1; j++)
10233 player->inventory_element[j] = player->inventory_element[j + 1];
10235 player->inventory_size--;
10238 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10240 if (player->inventory_size > 0)
10241 player->inventory_size--;
10243 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10245 player->inventory_infinite_element = EL_UNDEFINED;
10246 player->inventory_size = 0;
10248 else if (action_arg == CA_ARG_INVENTORY_RESET)
10250 player->inventory_infinite_element = EL_UNDEFINED;
10251 player->inventory_size = 0;
10253 if (level.use_initial_inventory[i])
10255 for (j = 0; j < level.initial_inventory_size[i]; j++)
10257 int element = level.initial_inventory_content[i][j];
10258 int collect_count = element_info[element].collect_count_initial;
10260 if (!IS_CUSTOM_ELEMENT(element))
10263 if (collect_count == 0)
10264 player->inventory_infinite_element = element;
10266 for (k = 0; k < collect_count; k++)
10267 if (player->inventory_size < MAX_INVENTORY_SIZE)
10268 player->inventory_element[player->inventory_size++] =
10279 // ---------- CE actions -------------------------------------------------
10281 case CA_SET_CE_VALUE:
10283 int last_ce_value = CustomValue[x][y];
10285 CustomValue[x][y] = action_arg_number_new;
10287 if (CustomValue[x][y] != last_ce_value)
10289 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10290 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10292 if (CustomValue[x][y] == 0)
10294 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10295 ChangeCount[x][y] = 0; // allow at least one more change
10297 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10298 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10305 case CA_SET_CE_SCORE:
10307 int last_ce_score = ei->collect_score;
10309 ei->collect_score = action_arg_number_new;
10311 if (ei->collect_score != last_ce_score)
10313 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10314 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10316 if (ei->collect_score == 0)
10320 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10321 ChangeCount[x][y] = 0; // allow at least one more change
10323 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10324 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10327 This is a very special case that seems to be a mixture between
10328 CheckElementChange() and CheckTriggeredElementChange(): while
10329 the first one only affects single elements that are triggered
10330 directly, the second one affects multiple elements in the playfield
10331 that are triggered indirectly by another element. This is a third
10332 case: Changing the CE score always affects multiple identical CEs,
10333 so every affected CE must be checked, not only the single CE for
10334 which the CE score was changed in the first place (as every instance
10335 of that CE shares the same CE score, and therefore also can change)!
10337 SCAN_PLAYFIELD(xx, yy)
10339 if (Feld[xx][yy] == element)
10340 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10341 CE_SCORE_GETS_ZERO);
10349 case CA_SET_CE_ARTWORK:
10351 int artwork_element = action_arg_element;
10352 boolean reset_frame = FALSE;
10355 if (action_arg == CA_ARG_ELEMENT_RESET)
10356 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10359 if (ei->gfx_element != artwork_element)
10360 reset_frame = TRUE;
10362 ei->gfx_element = artwork_element;
10364 SCAN_PLAYFIELD(xx, yy)
10366 if (Feld[xx][yy] == element)
10370 ResetGfxAnimation(xx, yy);
10371 ResetRandomAnimationValue(xx, yy);
10374 TEST_DrawLevelField(xx, yy);
10381 // ---------- engine actions ---------------------------------------------
10383 case CA_SET_ENGINE_SCAN_MODE:
10385 InitPlayfieldScanMode(action_arg);
10395 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10397 int old_element = Feld[x][y];
10398 int new_element = GetElementFromGroupElement(element);
10399 int previous_move_direction = MovDir[x][y];
10400 int last_ce_value = CustomValue[x][y];
10401 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10402 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10403 boolean add_player_onto_element = (new_element_is_player &&
10404 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10405 IS_WALKABLE(old_element));
10407 if (!add_player_onto_element)
10409 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10410 RemoveMovingField(x, y);
10414 Feld[x][y] = new_element;
10416 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10417 MovDir[x][y] = previous_move_direction;
10419 if (element_info[new_element].use_last_ce_value)
10420 CustomValue[x][y] = last_ce_value;
10422 InitField_WithBug1(x, y, FALSE);
10424 new_element = Feld[x][y]; // element may have changed
10426 ResetGfxAnimation(x, y);
10427 ResetRandomAnimationValue(x, y);
10429 TEST_DrawLevelField(x, y);
10431 if (GFX_CRUMBLED(new_element))
10432 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10435 // check if element under the player changes from accessible to unaccessible
10436 // (needed for special case of dropping element which then changes)
10437 // (must be checked after creating new element for walkable group elements)
10438 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10439 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10446 // "ChangeCount" not set yet to allow "entered by player" change one time
10447 if (new_element_is_player)
10448 RelocatePlayer(x, y, new_element);
10451 ChangeCount[x][y]++; // count number of changes in the same frame
10453 TestIfBadThingTouchesPlayer(x, y);
10454 TestIfPlayerTouchesCustomElement(x, y);
10455 TestIfElementTouchesCustomElement(x, y);
10458 static void CreateField(int x, int y, int element)
10460 CreateFieldExt(x, y, element, FALSE);
10463 static void CreateElementFromChange(int x, int y, int element)
10465 element = GET_VALID_RUNTIME_ELEMENT(element);
10467 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10469 int old_element = Feld[x][y];
10471 // prevent changed element from moving in same engine frame
10472 // unless both old and new element can either fall or move
10473 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10474 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10478 CreateFieldExt(x, y, element, TRUE);
10481 static boolean ChangeElement(int x, int y, int element, int page)
10483 struct ElementInfo *ei = &element_info[element];
10484 struct ElementChangeInfo *change = &ei->change_page[page];
10485 int ce_value = CustomValue[x][y];
10486 int ce_score = ei->collect_score;
10487 int target_element;
10488 int old_element = Feld[x][y];
10490 // always use default change event to prevent running into a loop
10491 if (ChangeEvent[x][y] == -1)
10492 ChangeEvent[x][y] = CE_DELAY;
10494 if (ChangeEvent[x][y] == CE_DELAY)
10496 // reset actual trigger element, trigger player and action element
10497 change->actual_trigger_element = EL_EMPTY;
10498 change->actual_trigger_player = EL_EMPTY;
10499 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10500 change->actual_trigger_side = CH_SIDE_NONE;
10501 change->actual_trigger_ce_value = 0;
10502 change->actual_trigger_ce_score = 0;
10505 // do not change elements more than a specified maximum number of changes
10506 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10509 ChangeCount[x][y]++; // count number of changes in the same frame
10511 if (change->explode)
10518 if (change->use_target_content)
10520 boolean complete_replace = TRUE;
10521 boolean can_replace[3][3];
10524 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10527 boolean is_walkable;
10528 boolean is_diggable;
10529 boolean is_collectible;
10530 boolean is_removable;
10531 boolean is_destructible;
10532 int ex = x + xx - 1;
10533 int ey = y + yy - 1;
10534 int content_element = change->target_content.e[xx][yy];
10537 can_replace[xx][yy] = TRUE;
10539 if (ex == x && ey == y) // do not check changing element itself
10542 if (content_element == EL_EMPTY_SPACE)
10544 can_replace[xx][yy] = FALSE; // do not replace border with space
10549 if (!IN_LEV_FIELD(ex, ey))
10551 can_replace[xx][yy] = FALSE;
10552 complete_replace = FALSE;
10559 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10560 e = MovingOrBlocked2Element(ex, ey);
10562 is_empty = (IS_FREE(ex, ey) ||
10563 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10565 is_walkable = (is_empty || IS_WALKABLE(e));
10566 is_diggable = (is_empty || IS_DIGGABLE(e));
10567 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10568 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10569 is_removable = (is_diggable || is_collectible);
10571 can_replace[xx][yy] =
10572 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10573 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10574 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10575 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10576 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10577 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10578 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10580 if (!can_replace[xx][yy])
10581 complete_replace = FALSE;
10584 if (!change->only_if_complete || complete_replace)
10586 boolean something_has_changed = FALSE;
10588 if (change->only_if_complete && change->use_random_replace &&
10589 RND(100) < change->random_percentage)
10592 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10594 int ex = x + xx - 1;
10595 int ey = y + yy - 1;
10596 int content_element;
10598 if (can_replace[xx][yy] && (!change->use_random_replace ||
10599 RND(100) < change->random_percentage))
10601 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10602 RemoveMovingField(ex, ey);
10604 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10606 content_element = change->target_content.e[xx][yy];
10607 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10608 ce_value, ce_score);
10610 CreateElementFromChange(ex, ey, target_element);
10612 something_has_changed = TRUE;
10614 // for symmetry reasons, freeze newly created border elements
10615 if (ex != x || ey != y)
10616 Stop[ex][ey] = TRUE; // no more moving in this frame
10620 if (something_has_changed)
10622 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10623 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10629 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10630 ce_value, ce_score);
10632 if (element == EL_DIAGONAL_GROWING ||
10633 element == EL_DIAGONAL_SHRINKING)
10635 target_element = Store[x][y];
10637 Store[x][y] = EL_EMPTY;
10640 CreateElementFromChange(x, y, target_element);
10642 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10643 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10646 // this uses direct change before indirect change
10647 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10652 static void HandleElementChange(int x, int y, int page)
10654 int element = MovingOrBlocked2Element(x, y);
10655 struct ElementInfo *ei = &element_info[element];
10656 struct ElementChangeInfo *change = &ei->change_page[page];
10657 boolean handle_action_before_change = FALSE;
10660 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10661 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10664 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10665 x, y, element, element_info[element].token_name);
10666 printf("HandleElementChange(): This should never happen!\n");
10671 // this can happen with classic bombs on walkable, changing elements
10672 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10677 if (ChangeDelay[x][y] == 0) // initialize element change
10679 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10681 if (change->can_change)
10683 // !!! not clear why graphic animation should be reset at all here !!!
10684 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10685 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10688 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10690 When using an animation frame delay of 1 (this only happens with
10691 "sp_zonk.moving.left/right" in the classic graphics), the default
10692 (non-moving) animation shows wrong animation frames (while the
10693 moving animation, like "sp_zonk.moving.left/right", is correct,
10694 so this graphical bug never shows up with the classic graphics).
10695 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10696 be drawn instead of the correct frames 0,1,2,3. This is caused by
10697 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10698 an element change: First when the change delay ("ChangeDelay[][]")
10699 counter has reached zero after decrementing, then a second time in
10700 the next frame (after "GfxFrame[][]" was already incremented) when
10701 "ChangeDelay[][]" is reset to the initial delay value again.
10703 This causes frame 0 to be drawn twice, while the last frame won't
10704 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10706 As some animations may already be cleverly designed around this bug
10707 (at least the "Snake Bite" snake tail animation does this), it cannot
10708 simply be fixed here without breaking such existing animations.
10709 Unfortunately, it cannot easily be detected if a graphics set was
10710 designed "before" or "after" the bug was fixed. As a workaround,
10711 a new graphics set option "game.graphics_engine_version" was added
10712 to be able to specify the game's major release version for which the
10713 graphics set was designed, which can then be used to decide if the
10714 bugfix should be used (version 4 and above) or not (version 3 or
10715 below, or if no version was specified at all, as with old sets).
10717 (The wrong/fixed animation frames can be tested with the test level set
10718 "test_gfxframe" and level "000", which contains a specially prepared
10719 custom element at level position (x/y) == (11/9) which uses the zonk
10720 animation mentioned above. Using "game.graphics_engine_version: 4"
10721 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10722 This can also be seen from the debug output for this test element.)
10725 // when a custom element is about to change (for example by change delay),
10726 // do not reset graphic animation when the custom element is moving
10727 if (game.graphics_engine_version < 4 &&
10730 ResetGfxAnimation(x, y);
10731 ResetRandomAnimationValue(x, y);
10734 if (change->pre_change_function)
10735 change->pre_change_function(x, y);
10739 ChangeDelay[x][y]--;
10741 if (ChangeDelay[x][y] != 0) // continue element change
10743 if (change->can_change)
10745 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10747 if (IS_ANIMATED(graphic))
10748 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10750 if (change->change_function)
10751 change->change_function(x, y);
10754 else // finish element change
10756 if (ChangePage[x][y] != -1) // remember page from delayed change
10758 page = ChangePage[x][y];
10759 ChangePage[x][y] = -1;
10761 change = &ei->change_page[page];
10764 if (IS_MOVING(x, y)) // never change a running system ;-)
10766 ChangeDelay[x][y] = 1; // try change after next move step
10767 ChangePage[x][y] = page; // remember page to use for change
10772 // special case: set new level random seed before changing element
10773 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10774 handle_action_before_change = TRUE;
10776 if (change->has_action && handle_action_before_change)
10777 ExecuteCustomElementAction(x, y, element, page);
10779 if (change->can_change)
10781 if (ChangeElement(x, y, element, page))
10783 if (change->post_change_function)
10784 change->post_change_function(x, y);
10788 if (change->has_action && !handle_action_before_change)
10789 ExecuteCustomElementAction(x, y, element, page);
10793 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10794 int trigger_element,
10796 int trigger_player,
10800 boolean change_done_any = FALSE;
10801 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10804 if (!(trigger_events[trigger_element][trigger_event]))
10807 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10809 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10811 int element = EL_CUSTOM_START + i;
10812 boolean change_done = FALSE;
10815 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10816 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10819 for (p = 0; p < element_info[element].num_change_pages; p++)
10821 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10823 if (change->can_change_or_has_action &&
10824 change->has_event[trigger_event] &&
10825 change->trigger_side & trigger_side &&
10826 change->trigger_player & trigger_player &&
10827 change->trigger_page & trigger_page_bits &&
10828 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10830 change->actual_trigger_element = trigger_element;
10831 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10832 change->actual_trigger_player_bits = trigger_player;
10833 change->actual_trigger_side = trigger_side;
10834 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10835 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10837 if ((change->can_change && !change_done) || change->has_action)
10841 SCAN_PLAYFIELD(x, y)
10843 if (Feld[x][y] == element)
10845 if (change->can_change && !change_done)
10847 // if element already changed in this frame, not only prevent
10848 // another element change (checked in ChangeElement()), but
10849 // also prevent additional element actions for this element
10851 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10852 !level.use_action_after_change_bug)
10855 ChangeDelay[x][y] = 1;
10856 ChangeEvent[x][y] = trigger_event;
10858 HandleElementChange(x, y, p);
10860 else if (change->has_action)
10862 // if element already changed in this frame, not only prevent
10863 // another element change (checked in ChangeElement()), but
10864 // also prevent additional element actions for this element
10866 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10867 !level.use_action_after_change_bug)
10870 ExecuteCustomElementAction(x, y, element, p);
10871 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10876 if (change->can_change)
10878 change_done = TRUE;
10879 change_done_any = TRUE;
10886 RECURSION_LOOP_DETECTION_END();
10888 return change_done_any;
10891 static boolean CheckElementChangeExt(int x, int y,
10893 int trigger_element,
10895 int trigger_player,
10898 boolean change_done = FALSE;
10901 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10902 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10905 if (Feld[x][y] == EL_BLOCKED)
10907 Blocked2Moving(x, y, &x, &y);
10908 element = Feld[x][y];
10911 // check if element has already changed or is about to change after moving
10912 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10913 Feld[x][y] != element) ||
10915 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10916 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10917 ChangePage[x][y] != -1)))
10920 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10922 for (p = 0; p < element_info[element].num_change_pages; p++)
10924 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10926 /* check trigger element for all events where the element that is checked
10927 for changing interacts with a directly adjacent element -- this is
10928 different to element changes that affect other elements to change on the
10929 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10930 boolean check_trigger_element =
10931 (trigger_event == CE_TOUCHING_X ||
10932 trigger_event == CE_HITTING_X ||
10933 trigger_event == CE_HIT_BY_X ||
10934 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10936 if (change->can_change_or_has_action &&
10937 change->has_event[trigger_event] &&
10938 change->trigger_side & trigger_side &&
10939 change->trigger_player & trigger_player &&
10940 (!check_trigger_element ||
10941 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10943 change->actual_trigger_element = trigger_element;
10944 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10945 change->actual_trigger_player_bits = trigger_player;
10946 change->actual_trigger_side = trigger_side;
10947 change->actual_trigger_ce_value = CustomValue[x][y];
10948 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10950 // special case: trigger element not at (x,y) position for some events
10951 if (check_trigger_element)
10963 { 0, 0 }, { 0, 0 }, { 0, 0 },
10967 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10968 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10970 change->actual_trigger_ce_value = CustomValue[xx][yy];
10971 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10974 if (change->can_change && !change_done)
10976 ChangeDelay[x][y] = 1;
10977 ChangeEvent[x][y] = trigger_event;
10979 HandleElementChange(x, y, p);
10981 change_done = TRUE;
10983 else if (change->has_action)
10985 ExecuteCustomElementAction(x, y, element, p);
10986 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10991 RECURSION_LOOP_DETECTION_END();
10993 return change_done;
10996 static void PlayPlayerSound(struct PlayerInfo *player)
10998 int jx = player->jx, jy = player->jy;
10999 int sound_element = player->artwork_element;
11000 int last_action = player->last_action_waiting;
11001 int action = player->action_waiting;
11003 if (player->is_waiting)
11005 if (action != last_action)
11006 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11008 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11012 if (action != last_action)
11013 StopSound(element_info[sound_element].sound[last_action]);
11015 if (last_action == ACTION_SLEEPING)
11016 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11020 static void PlayAllPlayersSound(void)
11024 for (i = 0; i < MAX_PLAYERS; i++)
11025 if (stored_player[i].active)
11026 PlayPlayerSound(&stored_player[i]);
11029 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11031 boolean last_waiting = player->is_waiting;
11032 int move_dir = player->MovDir;
11034 player->dir_waiting = move_dir;
11035 player->last_action_waiting = player->action_waiting;
11039 if (!last_waiting) // not waiting -> waiting
11041 player->is_waiting = TRUE;
11043 player->frame_counter_bored =
11045 game.player_boring_delay_fixed +
11046 GetSimpleRandom(game.player_boring_delay_random);
11047 player->frame_counter_sleeping =
11049 game.player_sleeping_delay_fixed +
11050 GetSimpleRandom(game.player_sleeping_delay_random);
11052 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11055 if (game.player_sleeping_delay_fixed +
11056 game.player_sleeping_delay_random > 0 &&
11057 player->anim_delay_counter == 0 &&
11058 player->post_delay_counter == 0 &&
11059 FrameCounter >= player->frame_counter_sleeping)
11060 player->is_sleeping = TRUE;
11061 else if (game.player_boring_delay_fixed +
11062 game.player_boring_delay_random > 0 &&
11063 FrameCounter >= player->frame_counter_bored)
11064 player->is_bored = TRUE;
11066 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11067 player->is_bored ? ACTION_BORING :
11070 if (player->is_sleeping && player->use_murphy)
11072 // special case for sleeping Murphy when leaning against non-free tile
11074 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11075 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
11076 !IS_MOVING(player->jx - 1, player->jy)))
11077 move_dir = MV_LEFT;
11078 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11079 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
11080 !IS_MOVING(player->jx + 1, player->jy)))
11081 move_dir = MV_RIGHT;
11083 player->is_sleeping = FALSE;
11085 player->dir_waiting = move_dir;
11088 if (player->is_sleeping)
11090 if (player->num_special_action_sleeping > 0)
11092 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11094 int last_special_action = player->special_action_sleeping;
11095 int num_special_action = player->num_special_action_sleeping;
11096 int special_action =
11097 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11098 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11099 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11100 last_special_action + 1 : ACTION_SLEEPING);
11101 int special_graphic =
11102 el_act_dir2img(player->artwork_element, special_action, move_dir);
11104 player->anim_delay_counter =
11105 graphic_info[special_graphic].anim_delay_fixed +
11106 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11107 player->post_delay_counter =
11108 graphic_info[special_graphic].post_delay_fixed +
11109 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11111 player->special_action_sleeping = special_action;
11114 if (player->anim_delay_counter > 0)
11116 player->action_waiting = player->special_action_sleeping;
11117 player->anim_delay_counter--;
11119 else if (player->post_delay_counter > 0)
11121 player->post_delay_counter--;
11125 else if (player->is_bored)
11127 if (player->num_special_action_bored > 0)
11129 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11131 int special_action =
11132 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11133 int special_graphic =
11134 el_act_dir2img(player->artwork_element, special_action, move_dir);
11136 player->anim_delay_counter =
11137 graphic_info[special_graphic].anim_delay_fixed +
11138 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11139 player->post_delay_counter =
11140 graphic_info[special_graphic].post_delay_fixed +
11141 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11143 player->special_action_bored = special_action;
11146 if (player->anim_delay_counter > 0)
11148 player->action_waiting = player->special_action_bored;
11149 player->anim_delay_counter--;
11151 else if (player->post_delay_counter > 0)
11153 player->post_delay_counter--;
11158 else if (last_waiting) // waiting -> not waiting
11160 player->is_waiting = FALSE;
11161 player->is_bored = FALSE;
11162 player->is_sleeping = FALSE;
11164 player->frame_counter_bored = -1;
11165 player->frame_counter_sleeping = -1;
11167 player->anim_delay_counter = 0;
11168 player->post_delay_counter = 0;
11170 player->dir_waiting = player->MovDir;
11171 player->action_waiting = ACTION_DEFAULT;
11173 player->special_action_bored = ACTION_DEFAULT;
11174 player->special_action_sleeping = ACTION_DEFAULT;
11178 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11180 if ((!player->is_moving && player->was_moving) ||
11181 (player->MovPos == 0 && player->was_moving) ||
11182 (player->is_snapping && !player->was_snapping) ||
11183 (player->is_dropping && !player->was_dropping))
11185 if (!CheckSaveEngineSnapshotToList())
11188 player->was_moving = FALSE;
11189 player->was_snapping = TRUE;
11190 player->was_dropping = TRUE;
11194 if (player->is_moving)
11195 player->was_moving = TRUE;
11197 if (!player->is_snapping)
11198 player->was_snapping = FALSE;
11200 if (!player->is_dropping)
11201 player->was_dropping = FALSE;
11205 static void CheckSingleStepMode(struct PlayerInfo *player)
11207 if (tape.single_step && tape.recording && !tape.pausing)
11209 /* as it is called "single step mode", just return to pause mode when the
11210 player stopped moving after one tile (or never starts moving at all) */
11211 if (!player->is_moving &&
11212 !player->is_pushing &&
11213 !player->is_dropping_pressed)
11214 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11217 CheckSaveEngineSnapshot(player);
11220 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11222 int left = player_action & JOY_LEFT;
11223 int right = player_action & JOY_RIGHT;
11224 int up = player_action & JOY_UP;
11225 int down = player_action & JOY_DOWN;
11226 int button1 = player_action & JOY_BUTTON_1;
11227 int button2 = player_action & JOY_BUTTON_2;
11228 int dx = (left ? -1 : right ? 1 : 0);
11229 int dy = (up ? -1 : down ? 1 : 0);
11231 if (!player->active || tape.pausing)
11237 SnapField(player, dx, dy);
11241 DropElement(player);
11243 MovePlayer(player, dx, dy);
11246 CheckSingleStepMode(player);
11248 SetPlayerWaiting(player, FALSE);
11250 return player_action;
11254 // no actions for this player (no input at player's configured device)
11256 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11257 SnapField(player, 0, 0);
11258 CheckGravityMovementWhenNotMoving(player);
11260 if (player->MovPos == 0)
11261 SetPlayerWaiting(player, TRUE);
11263 if (player->MovPos == 0) // needed for tape.playing
11264 player->is_moving = FALSE;
11266 player->is_dropping = FALSE;
11267 player->is_dropping_pressed = FALSE;
11268 player->drop_pressed_delay = 0;
11270 CheckSingleStepMode(player);
11276 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11279 if (!tape.use_mouse_actions)
11282 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11283 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11284 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11287 static void SetTapeActionFromMouseAction(byte *tape_action,
11288 struct MouseActionInfo *mouse_action)
11290 if (!tape.use_mouse_actions)
11293 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11294 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11295 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11298 static void CheckLevelSolved(void)
11300 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11302 if (game_em.level_solved &&
11303 !game_em.game_over) // game won
11307 game_em.game_over = TRUE;
11309 game.all_players_gone = TRUE;
11312 if (game_em.game_over) // game lost
11313 game.all_players_gone = TRUE;
11315 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11317 if (game_sp.level_solved &&
11318 !game_sp.game_over) // game won
11322 game_sp.game_over = TRUE;
11324 game.all_players_gone = TRUE;
11327 if (game_sp.game_over) // game lost
11328 game.all_players_gone = TRUE;
11330 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11332 if (game_mm.level_solved &&
11333 !game_mm.game_over) // game won
11337 game_mm.game_over = TRUE;
11339 game.all_players_gone = TRUE;
11342 if (game_mm.game_over) // game lost
11343 game.all_players_gone = TRUE;
11347 static void CheckLevelTime(void)
11351 if (TimeFrames >= FRAMES_PER_SECOND)
11356 for (i = 0; i < MAX_PLAYERS; i++)
11358 struct PlayerInfo *player = &stored_player[i];
11360 if (SHIELD_ON(player))
11362 player->shield_normal_time_left--;
11364 if (player->shield_deadly_time_left > 0)
11365 player->shield_deadly_time_left--;
11369 if (!game.LevelSolved && !level.use_step_counter)
11377 if (TimeLeft <= 10 && setup.time_limit)
11378 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11380 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11381 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11383 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11385 if (!TimeLeft && setup.time_limit)
11387 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11388 game_em.lev->killed_out_of_time = TRUE;
11390 for (i = 0; i < MAX_PLAYERS; i++)
11391 KillPlayer(&stored_player[i]);
11394 else if (game.no_time_limit && !game.all_players_gone)
11396 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11399 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11402 if (tape.recording || tape.playing)
11403 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11406 if (tape.recording || tape.playing)
11407 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11409 UpdateAndDisplayGameControlValues();
11412 void AdvanceFrameAndPlayerCounters(int player_nr)
11416 // advance frame counters (global frame counter and time frame counter)
11420 // advance player counters (counters for move delay, move animation etc.)
11421 for (i = 0; i < MAX_PLAYERS; i++)
11423 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11424 int move_delay_value = stored_player[i].move_delay_value;
11425 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11427 if (!advance_player_counters) // not all players may be affected
11430 if (move_frames == 0) // less than one move per game frame
11432 int stepsize = TILEX / move_delay_value;
11433 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11434 int count = (stored_player[i].is_moving ?
11435 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11437 if (count % delay == 0)
11441 stored_player[i].Frame += move_frames;
11443 if (stored_player[i].MovPos != 0)
11444 stored_player[i].StepFrame += move_frames;
11446 if (stored_player[i].move_delay > 0)
11447 stored_player[i].move_delay--;
11449 // due to bugs in previous versions, counter must count up, not down
11450 if (stored_player[i].push_delay != -1)
11451 stored_player[i].push_delay++;
11453 if (stored_player[i].drop_delay > 0)
11454 stored_player[i].drop_delay--;
11456 if (stored_player[i].is_dropping_pressed)
11457 stored_player[i].drop_pressed_delay++;
11461 void StartGameActions(boolean init_network_game, boolean record_tape,
11464 unsigned int new_random_seed = InitRND(random_seed);
11467 TapeStartRecording(new_random_seed);
11469 if (init_network_game)
11471 SendToServer_LevelFile();
11472 SendToServer_StartPlaying();
11480 static void GameActionsExt(void)
11483 static unsigned int game_frame_delay = 0;
11485 unsigned int game_frame_delay_value;
11486 byte *recorded_player_action;
11487 byte summarized_player_action = 0;
11488 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11491 // detect endless loops, caused by custom element programming
11492 if (recursion_loop_detected && recursion_loop_depth == 0)
11494 char *message = getStringCat3("Internal Error! Element ",
11495 EL_NAME(recursion_loop_element),
11496 " caused endless loop! Quit the game?");
11498 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11499 EL_NAME(recursion_loop_element));
11501 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11503 recursion_loop_detected = FALSE; // if game should be continued
11510 if (game.restart_level)
11511 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11513 CheckLevelSolved();
11515 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11518 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11521 if (game_status != GAME_MODE_PLAYING) // status might have changed
11524 game_frame_delay_value =
11525 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11527 if (tape.playing && tape.warp_forward && !tape.pausing)
11528 game_frame_delay_value = 0;
11530 SetVideoFrameDelay(game_frame_delay_value);
11532 // (de)activate virtual buttons depending on current game status
11533 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11535 if (game.all_players_gone) // if no players there to be controlled anymore
11536 SetOverlayActive(FALSE);
11537 else if (!tape.playing) // if game continues after tape stopped playing
11538 SetOverlayActive(TRUE);
11543 // ---------- main game synchronization point ----------
11545 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11547 printf("::: skip == %d\n", skip);
11550 // ---------- main game synchronization point ----------
11552 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11556 if (network_playing && !network_player_action_received)
11558 // try to get network player actions in time
11560 // last chance to get network player actions without main loop delay
11561 HandleNetworking();
11563 // game was quit by network peer
11564 if (game_status != GAME_MODE_PLAYING)
11567 // check if network player actions still missing and game still running
11568 if (!network_player_action_received && !checkGameEnded())
11569 return; // failed to get network player actions in time
11571 // do not yet reset "network_player_action_received" (for tape.pausing)
11577 // at this point we know that we really continue executing the game
11579 network_player_action_received = FALSE;
11581 // when playing tape, read previously recorded player input from tape data
11582 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11584 local_player->effective_mouse_action = local_player->mouse_action;
11586 if (recorded_player_action != NULL)
11587 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11588 recorded_player_action);
11590 // TapePlayAction() may return NULL when toggling to "pause before death"
11594 if (tape.set_centered_player)
11596 game.centered_player_nr_next = tape.centered_player_nr_next;
11597 game.set_centered_player = TRUE;
11600 for (i = 0; i < MAX_PLAYERS; i++)
11602 summarized_player_action |= stored_player[i].action;
11604 if (!network_playing && (game.team_mode || tape.playing))
11605 stored_player[i].effective_action = stored_player[i].action;
11608 if (network_playing && !checkGameEnded())
11609 SendToServer_MovePlayer(summarized_player_action);
11611 // summarize all actions at local players mapped input device position
11612 // (this allows using different input devices in single player mode)
11613 if (!network.enabled && !game.team_mode)
11614 stored_player[map_player_action[local_player->index_nr]].effective_action =
11615 summarized_player_action;
11617 // summarize all actions at centered player in local team mode
11618 if (tape.recording &&
11619 setup.team_mode && !network.enabled &&
11620 setup.input_on_focus &&
11621 game.centered_player_nr != -1)
11623 for (i = 0; i < MAX_PLAYERS; i++)
11624 stored_player[map_player_action[i]].effective_action =
11625 (i == game.centered_player_nr ? summarized_player_action : 0);
11628 if (recorded_player_action != NULL)
11629 for (i = 0; i < MAX_PLAYERS; i++)
11630 stored_player[i].effective_action = recorded_player_action[i];
11632 for (i = 0; i < MAX_PLAYERS; i++)
11634 tape_action[i] = stored_player[i].effective_action;
11636 /* (this may happen in the RND game engine if a player was not present on
11637 the playfield on level start, but appeared later from a custom element */
11638 if (setup.team_mode &&
11641 !tape.player_participates[i])
11642 tape.player_participates[i] = TRUE;
11645 SetTapeActionFromMouseAction(tape_action,
11646 &local_player->effective_mouse_action);
11648 // only record actions from input devices, but not programmed actions
11649 if (tape.recording)
11650 TapeRecordAction(tape_action);
11652 // remember if game was played (especially after tape stopped playing)
11653 if (!tape.playing && summarized_player_action)
11654 game.GamePlayed = TRUE;
11656 #if USE_NEW_PLAYER_ASSIGNMENTS
11657 // !!! also map player actions in single player mode !!!
11658 // if (game.team_mode)
11661 byte mapped_action[MAX_PLAYERS];
11663 #if DEBUG_PLAYER_ACTIONS
11665 for (i = 0; i < MAX_PLAYERS; i++)
11666 printf(" %d, ", stored_player[i].effective_action);
11669 for (i = 0; i < MAX_PLAYERS; i++)
11670 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11672 for (i = 0; i < MAX_PLAYERS; i++)
11673 stored_player[i].effective_action = mapped_action[i];
11675 #if DEBUG_PLAYER_ACTIONS
11677 for (i = 0; i < MAX_PLAYERS; i++)
11678 printf(" %d, ", stored_player[i].effective_action);
11682 #if DEBUG_PLAYER_ACTIONS
11686 for (i = 0; i < MAX_PLAYERS; i++)
11687 printf(" %d, ", stored_player[i].effective_action);
11693 for (i = 0; i < MAX_PLAYERS; i++)
11695 // allow engine snapshot in case of changed movement attempt
11696 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11697 (stored_player[i].effective_action & KEY_MOTION))
11698 game.snapshot.changed_action = TRUE;
11700 // allow engine snapshot in case of snapping/dropping attempt
11701 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11702 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11703 game.snapshot.changed_action = TRUE;
11705 game.snapshot.last_action[i] = stored_player[i].effective_action;
11708 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11710 GameActions_EM_Main();
11712 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11714 GameActions_SP_Main();
11716 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11718 GameActions_MM_Main();
11722 GameActions_RND_Main();
11725 BlitScreenToBitmap(backbuffer);
11727 CheckLevelSolved();
11730 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11732 if (global.show_frames_per_second)
11734 static unsigned int fps_counter = 0;
11735 static int fps_frames = 0;
11736 unsigned int fps_delay_ms = Counter() - fps_counter;
11740 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11742 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11745 fps_counter = Counter();
11747 // always draw FPS to screen after FPS value was updated
11748 redraw_mask |= REDRAW_FPS;
11751 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11752 if (GetDrawDeactivationMask() == REDRAW_NONE)
11753 redraw_mask |= REDRAW_FPS;
11757 static void GameActions_CheckSaveEngineSnapshot(void)
11759 if (!game.snapshot.save_snapshot)
11762 // clear flag for saving snapshot _before_ saving snapshot
11763 game.snapshot.save_snapshot = FALSE;
11765 SaveEngineSnapshotToList();
11768 void GameActions(void)
11772 GameActions_CheckSaveEngineSnapshot();
11775 void GameActions_EM_Main(void)
11777 byte effective_action[MAX_PLAYERS];
11778 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11781 for (i = 0; i < MAX_PLAYERS; i++)
11782 effective_action[i] = stored_player[i].effective_action;
11784 GameActions_EM(effective_action, warp_mode);
11787 void GameActions_SP_Main(void)
11789 byte effective_action[MAX_PLAYERS];
11790 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11793 for (i = 0; i < MAX_PLAYERS; i++)
11794 effective_action[i] = stored_player[i].effective_action;
11796 GameActions_SP(effective_action, warp_mode);
11798 for (i = 0; i < MAX_PLAYERS; i++)
11800 if (stored_player[i].force_dropping)
11801 stored_player[i].action |= KEY_BUTTON_DROP;
11803 stored_player[i].force_dropping = FALSE;
11807 void GameActions_MM_Main(void)
11809 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11811 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11814 void GameActions_RND_Main(void)
11819 void GameActions_RND(void)
11821 static struct MouseActionInfo mouse_action_last = { 0 };
11822 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11823 int magic_wall_x = 0, magic_wall_y = 0;
11824 int i, x, y, element, graphic, last_gfx_frame;
11826 InitPlayfieldScanModeVars();
11828 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11830 SCAN_PLAYFIELD(x, y)
11832 ChangeCount[x][y] = 0;
11833 ChangeEvent[x][y] = -1;
11837 if (game.set_centered_player)
11839 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11841 // switching to "all players" only possible if all players fit to screen
11842 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11844 game.centered_player_nr_next = game.centered_player_nr;
11845 game.set_centered_player = FALSE;
11848 // do not switch focus to non-existing (or non-active) player
11849 if (game.centered_player_nr_next >= 0 &&
11850 !stored_player[game.centered_player_nr_next].active)
11852 game.centered_player_nr_next = game.centered_player_nr;
11853 game.set_centered_player = FALSE;
11857 if (game.set_centered_player &&
11858 ScreenMovPos == 0) // screen currently aligned at tile position
11862 if (game.centered_player_nr_next == -1)
11864 setScreenCenteredToAllPlayers(&sx, &sy);
11868 sx = stored_player[game.centered_player_nr_next].jx;
11869 sy = stored_player[game.centered_player_nr_next].jy;
11872 game.centered_player_nr = game.centered_player_nr_next;
11873 game.set_centered_player = FALSE;
11875 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11876 DrawGameDoorValues();
11879 for (i = 0; i < MAX_PLAYERS; i++)
11881 int actual_player_action = stored_player[i].effective_action;
11884 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11885 - rnd_equinox_tetrachloride 048
11886 - rnd_equinox_tetrachloride_ii 096
11887 - rnd_emanuel_schmieg 002
11888 - doctor_sloan_ww 001, 020
11890 if (stored_player[i].MovPos == 0)
11891 CheckGravityMovement(&stored_player[i]);
11894 // overwrite programmed action with tape action
11895 if (stored_player[i].programmed_action)
11896 actual_player_action = stored_player[i].programmed_action;
11898 PlayerActions(&stored_player[i], actual_player_action);
11900 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11903 ScrollScreen(NULL, SCROLL_GO_ON);
11905 /* for backwards compatibility, the following code emulates a fixed bug that
11906 occured when pushing elements (causing elements that just made their last
11907 pushing step to already (if possible) make their first falling step in the
11908 same game frame, which is bad); this code is also needed to use the famous
11909 "spring push bug" which is used in older levels and might be wanted to be
11910 used also in newer levels, but in this case the buggy pushing code is only
11911 affecting the "spring" element and no other elements */
11913 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11915 for (i = 0; i < MAX_PLAYERS; i++)
11917 struct PlayerInfo *player = &stored_player[i];
11918 int x = player->jx;
11919 int y = player->jy;
11921 if (player->active && player->is_pushing && player->is_moving &&
11923 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11924 Feld[x][y] == EL_SPRING))
11926 ContinueMoving(x, y);
11928 // continue moving after pushing (this is actually a bug)
11929 if (!IS_MOVING(x, y))
11930 Stop[x][y] = FALSE;
11935 SCAN_PLAYFIELD(x, y)
11937 Last[x][y] = Feld[x][y];
11939 ChangeCount[x][y] = 0;
11940 ChangeEvent[x][y] = -1;
11942 // this must be handled before main playfield loop
11943 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11946 if (MovDelay[x][y] <= 0)
11950 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11953 if (MovDelay[x][y] <= 0)
11956 TEST_DrawLevelField(x, y);
11958 TestIfElementTouchesCustomElement(x, y); // for empty space
11963 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11965 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11966 printf("GameActions(): This should never happen!\n");
11968 ChangePage[x][y] = -1;
11972 Stop[x][y] = FALSE;
11973 if (WasJustMoving[x][y] > 0)
11974 WasJustMoving[x][y]--;
11975 if (WasJustFalling[x][y] > 0)
11976 WasJustFalling[x][y]--;
11977 if (CheckCollision[x][y] > 0)
11978 CheckCollision[x][y]--;
11979 if (CheckImpact[x][y] > 0)
11980 CheckImpact[x][y]--;
11984 /* reset finished pushing action (not done in ContinueMoving() to allow
11985 continuous pushing animation for elements with zero push delay) */
11986 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11988 ResetGfxAnimation(x, y);
11989 TEST_DrawLevelField(x, y);
11993 if (IS_BLOCKED(x, y))
11997 Blocked2Moving(x, y, &oldx, &oldy);
11998 if (!IS_MOVING(oldx, oldy))
12000 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
12001 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
12002 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
12003 printf("GameActions(): This should never happen!\n");
12009 if (mouse_action.button)
12011 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12013 x = mouse_action.lx;
12014 y = mouse_action.ly;
12015 element = Feld[x][y];
12019 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12020 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12023 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12024 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12027 SCAN_PLAYFIELD(x, y)
12029 element = Feld[x][y];
12030 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12031 last_gfx_frame = GfxFrame[x][y];
12033 ResetGfxFrame(x, y);
12035 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12036 DrawLevelGraphicAnimation(x, y, graphic);
12038 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12039 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12040 ResetRandomAnimationValue(x, y);
12042 SetRandomAnimationValue(x, y);
12044 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12046 if (IS_INACTIVE(element))
12048 if (IS_ANIMATED(graphic))
12049 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12054 // this may take place after moving, so 'element' may have changed
12055 if (IS_CHANGING(x, y) &&
12056 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12058 int page = element_info[element].event_page_nr[CE_DELAY];
12060 HandleElementChange(x, y, page);
12062 element = Feld[x][y];
12063 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12066 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12070 element = Feld[x][y];
12071 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12073 if (IS_ANIMATED(graphic) &&
12074 !IS_MOVING(x, y) &&
12076 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12078 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12079 TEST_DrawTwinkleOnField(x, y);
12081 else if (element == EL_ACID)
12084 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12086 else if ((element == EL_EXIT_OPEN ||
12087 element == EL_EM_EXIT_OPEN ||
12088 element == EL_SP_EXIT_OPEN ||
12089 element == EL_STEEL_EXIT_OPEN ||
12090 element == EL_EM_STEEL_EXIT_OPEN ||
12091 element == EL_SP_TERMINAL ||
12092 element == EL_SP_TERMINAL_ACTIVE ||
12093 element == EL_EXTRA_TIME ||
12094 element == EL_SHIELD_NORMAL ||
12095 element == EL_SHIELD_DEADLY) &&
12096 IS_ANIMATED(graphic))
12097 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12098 else if (IS_MOVING(x, y))
12099 ContinueMoving(x, y);
12100 else if (IS_ACTIVE_BOMB(element))
12101 CheckDynamite(x, y);
12102 else if (element == EL_AMOEBA_GROWING)
12103 AmoebeWaechst(x, y);
12104 else if (element == EL_AMOEBA_SHRINKING)
12105 AmoebaDisappearing(x, y);
12107 #if !USE_NEW_AMOEBA_CODE
12108 else if (IS_AMOEBALIVE(element))
12109 AmoebeAbleger(x, y);
12112 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12114 else if (element == EL_EXIT_CLOSED)
12116 else if (element == EL_EM_EXIT_CLOSED)
12118 else if (element == EL_STEEL_EXIT_CLOSED)
12119 CheckExitSteel(x, y);
12120 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12121 CheckExitSteelEM(x, y);
12122 else if (element == EL_SP_EXIT_CLOSED)
12124 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12125 element == EL_EXPANDABLE_STEELWALL_GROWING)
12126 MauerWaechst(x, y);
12127 else if (element == EL_EXPANDABLE_WALL ||
12128 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12129 element == EL_EXPANDABLE_WALL_VERTICAL ||
12130 element == EL_EXPANDABLE_WALL_ANY ||
12131 element == EL_BD_EXPANDABLE_WALL)
12132 MauerAbleger(x, y);
12133 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12134 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12135 element == EL_EXPANDABLE_STEELWALL_ANY)
12136 MauerAblegerStahl(x, y);
12137 else if (element == EL_FLAMES)
12138 CheckForDragon(x, y);
12139 else if (element == EL_EXPLOSION)
12140 ; // drawing of correct explosion animation is handled separately
12141 else if (element == EL_ELEMENT_SNAPPING ||
12142 element == EL_DIAGONAL_SHRINKING ||
12143 element == EL_DIAGONAL_GROWING)
12145 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12147 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12149 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12150 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12152 if (IS_BELT_ACTIVE(element))
12153 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12155 if (game.magic_wall_active)
12157 int jx = local_player->jx, jy = local_player->jy;
12159 // play the element sound at the position nearest to the player
12160 if ((element == EL_MAGIC_WALL_FULL ||
12161 element == EL_MAGIC_WALL_ACTIVE ||
12162 element == EL_MAGIC_WALL_EMPTYING ||
12163 element == EL_BD_MAGIC_WALL_FULL ||
12164 element == EL_BD_MAGIC_WALL_ACTIVE ||
12165 element == EL_BD_MAGIC_WALL_EMPTYING ||
12166 element == EL_DC_MAGIC_WALL_FULL ||
12167 element == EL_DC_MAGIC_WALL_ACTIVE ||
12168 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12169 ABS(x - jx) + ABS(y - jy) <
12170 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12178 #if USE_NEW_AMOEBA_CODE
12179 // new experimental amoeba growth stuff
12180 if (!(FrameCounter % 8))
12182 static unsigned int random = 1684108901;
12184 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12186 x = RND(lev_fieldx);
12187 y = RND(lev_fieldy);
12188 element = Feld[x][y];
12190 if (!IS_PLAYER(x,y) &&
12191 (element == EL_EMPTY ||
12192 CAN_GROW_INTO(element) ||
12193 element == EL_QUICKSAND_EMPTY ||
12194 element == EL_QUICKSAND_FAST_EMPTY ||
12195 element == EL_ACID_SPLASH_LEFT ||
12196 element == EL_ACID_SPLASH_RIGHT))
12198 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12199 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12200 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12201 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12202 Feld[x][y] = EL_AMOEBA_DROP;
12205 random = random * 129 + 1;
12210 game.explosions_delayed = FALSE;
12212 SCAN_PLAYFIELD(x, y)
12214 element = Feld[x][y];
12216 if (ExplodeField[x][y])
12217 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12218 else if (element == EL_EXPLOSION)
12219 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12221 ExplodeField[x][y] = EX_TYPE_NONE;
12224 game.explosions_delayed = TRUE;
12226 if (game.magic_wall_active)
12228 if (!(game.magic_wall_time_left % 4))
12230 int element = Feld[magic_wall_x][magic_wall_y];
12232 if (element == EL_BD_MAGIC_WALL_FULL ||
12233 element == EL_BD_MAGIC_WALL_ACTIVE ||
12234 element == EL_BD_MAGIC_WALL_EMPTYING)
12235 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12236 else if (element == EL_DC_MAGIC_WALL_FULL ||
12237 element == EL_DC_MAGIC_WALL_ACTIVE ||
12238 element == EL_DC_MAGIC_WALL_EMPTYING)
12239 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12241 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12244 if (game.magic_wall_time_left > 0)
12246 game.magic_wall_time_left--;
12248 if (!game.magic_wall_time_left)
12250 SCAN_PLAYFIELD(x, y)
12252 element = Feld[x][y];
12254 if (element == EL_MAGIC_WALL_ACTIVE ||
12255 element == EL_MAGIC_WALL_FULL)
12257 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12258 TEST_DrawLevelField(x, y);
12260 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12261 element == EL_BD_MAGIC_WALL_FULL)
12263 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12264 TEST_DrawLevelField(x, y);
12266 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12267 element == EL_DC_MAGIC_WALL_FULL)
12269 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12270 TEST_DrawLevelField(x, y);
12274 game.magic_wall_active = FALSE;
12279 if (game.light_time_left > 0)
12281 game.light_time_left--;
12283 if (game.light_time_left == 0)
12284 RedrawAllLightSwitchesAndInvisibleElements();
12287 if (game.timegate_time_left > 0)
12289 game.timegate_time_left--;
12291 if (game.timegate_time_left == 0)
12292 CloseAllOpenTimegates();
12295 if (game.lenses_time_left > 0)
12297 game.lenses_time_left--;
12299 if (game.lenses_time_left == 0)
12300 RedrawAllInvisibleElementsForLenses();
12303 if (game.magnify_time_left > 0)
12305 game.magnify_time_left--;
12307 if (game.magnify_time_left == 0)
12308 RedrawAllInvisibleElementsForMagnifier();
12311 for (i = 0; i < MAX_PLAYERS; i++)
12313 struct PlayerInfo *player = &stored_player[i];
12315 if (SHIELD_ON(player))
12317 if (player->shield_deadly_time_left)
12318 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12319 else if (player->shield_normal_time_left)
12320 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12324 #if USE_DELAYED_GFX_REDRAW
12325 SCAN_PLAYFIELD(x, y)
12327 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12329 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12330 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12332 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12333 DrawLevelField(x, y);
12335 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12336 DrawLevelFieldCrumbled(x, y);
12338 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12339 DrawLevelFieldCrumbledNeighbours(x, y);
12341 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12342 DrawTwinkleOnField(x, y);
12345 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12350 PlayAllPlayersSound();
12352 for (i = 0; i < MAX_PLAYERS; i++)
12354 struct PlayerInfo *player = &stored_player[i];
12356 if (player->show_envelope != 0 && (!player->active ||
12357 player->MovPos == 0))
12359 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12361 player->show_envelope = 0;
12365 // use random number generator in every frame to make it less predictable
12366 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12369 mouse_action_last = mouse_action;
12372 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12374 int min_x = x, min_y = y, max_x = x, max_y = y;
12377 for (i = 0; i < MAX_PLAYERS; i++)
12379 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12381 if (!stored_player[i].active || &stored_player[i] == player)
12384 min_x = MIN(min_x, jx);
12385 min_y = MIN(min_y, jy);
12386 max_x = MAX(max_x, jx);
12387 max_y = MAX(max_y, jy);
12390 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12393 static boolean AllPlayersInVisibleScreen(void)
12397 for (i = 0; i < MAX_PLAYERS; i++)
12399 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12401 if (!stored_player[i].active)
12404 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12411 void ScrollLevel(int dx, int dy)
12413 int scroll_offset = 2 * TILEX_VAR;
12416 BlitBitmap(drawto_field, drawto_field,
12417 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12418 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12419 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12420 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12421 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12422 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12426 x = (dx == 1 ? BX1 : BX2);
12427 for (y = BY1; y <= BY2; y++)
12428 DrawScreenField(x, y);
12433 y = (dy == 1 ? BY1 : BY2);
12434 for (x = BX1; x <= BX2; x++)
12435 DrawScreenField(x, y);
12438 redraw_mask |= REDRAW_FIELD;
12441 static boolean canFallDown(struct PlayerInfo *player)
12443 int jx = player->jx, jy = player->jy;
12445 return (IN_LEV_FIELD(jx, jy + 1) &&
12446 (IS_FREE(jx, jy + 1) ||
12447 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12448 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12449 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12452 static boolean canPassField(int x, int y, int move_dir)
12454 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12455 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12456 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12457 int nextx = x + dx;
12458 int nexty = y + dy;
12459 int element = Feld[x][y];
12461 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12462 !CAN_MOVE(element) &&
12463 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12464 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12465 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12468 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12470 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12471 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12472 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12476 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12477 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12478 (IS_DIGGABLE(Feld[newx][newy]) ||
12479 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12480 canPassField(newx, newy, move_dir)));
12483 static void CheckGravityMovement(struct PlayerInfo *player)
12485 if (player->gravity && !player->programmed_action)
12487 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12488 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12489 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12490 int jx = player->jx, jy = player->jy;
12491 boolean player_is_moving_to_valid_field =
12492 (!player_is_snapping &&
12493 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12494 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12495 boolean player_can_fall_down = canFallDown(player);
12497 if (player_can_fall_down &&
12498 !player_is_moving_to_valid_field)
12499 player->programmed_action = MV_DOWN;
12503 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12505 return CheckGravityMovement(player);
12507 if (player->gravity && !player->programmed_action)
12509 int jx = player->jx, jy = player->jy;
12510 boolean field_under_player_is_free =
12511 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12512 boolean player_is_standing_on_valid_field =
12513 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12514 (IS_WALKABLE(Feld[jx][jy]) &&
12515 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12517 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12518 player->programmed_action = MV_DOWN;
12523 MovePlayerOneStep()
12524 -----------------------------------------------------------------------------
12525 dx, dy: direction (non-diagonal) to try to move the player to
12526 real_dx, real_dy: direction as read from input device (can be diagonal)
12529 boolean MovePlayerOneStep(struct PlayerInfo *player,
12530 int dx, int dy, int real_dx, int real_dy)
12532 int jx = player->jx, jy = player->jy;
12533 int new_jx = jx + dx, new_jy = jy + dy;
12535 boolean player_can_move = !player->cannot_move;
12537 if (!player->active || (!dx && !dy))
12538 return MP_NO_ACTION;
12540 player->MovDir = (dx < 0 ? MV_LEFT :
12541 dx > 0 ? MV_RIGHT :
12543 dy > 0 ? MV_DOWN : MV_NONE);
12545 if (!IN_LEV_FIELD(new_jx, new_jy))
12546 return MP_NO_ACTION;
12548 if (!player_can_move)
12550 if (player->MovPos == 0)
12552 player->is_moving = FALSE;
12553 player->is_digging = FALSE;
12554 player->is_collecting = FALSE;
12555 player->is_snapping = FALSE;
12556 player->is_pushing = FALSE;
12560 if (!network.enabled && game.centered_player_nr == -1 &&
12561 !AllPlayersInSight(player, new_jx, new_jy))
12562 return MP_NO_ACTION;
12564 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12565 if (can_move != MP_MOVING)
12568 // check if DigField() has caused relocation of the player
12569 if (player->jx != jx || player->jy != jy)
12570 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12572 StorePlayer[jx][jy] = 0;
12573 player->last_jx = jx;
12574 player->last_jy = jy;
12575 player->jx = new_jx;
12576 player->jy = new_jy;
12577 StorePlayer[new_jx][new_jy] = player->element_nr;
12579 if (player->move_delay_value_next != -1)
12581 player->move_delay_value = player->move_delay_value_next;
12582 player->move_delay_value_next = -1;
12586 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12588 player->step_counter++;
12590 PlayerVisit[jx][jy] = FrameCounter;
12592 player->is_moving = TRUE;
12595 // should better be called in MovePlayer(), but this breaks some tapes
12596 ScrollPlayer(player, SCROLL_INIT);
12602 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12604 int jx = player->jx, jy = player->jy;
12605 int old_jx = jx, old_jy = jy;
12606 int moved = MP_NO_ACTION;
12608 if (!player->active)
12613 if (player->MovPos == 0)
12615 player->is_moving = FALSE;
12616 player->is_digging = FALSE;
12617 player->is_collecting = FALSE;
12618 player->is_snapping = FALSE;
12619 player->is_pushing = FALSE;
12625 if (player->move_delay > 0)
12628 player->move_delay = -1; // set to "uninitialized" value
12630 // store if player is automatically moved to next field
12631 player->is_auto_moving = (player->programmed_action != MV_NONE);
12633 // remove the last programmed player action
12634 player->programmed_action = 0;
12636 if (player->MovPos)
12638 // should only happen if pre-1.2 tape recordings are played
12639 // this is only for backward compatibility
12641 int original_move_delay_value = player->move_delay_value;
12644 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12648 // scroll remaining steps with finest movement resolution
12649 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12651 while (player->MovPos)
12653 ScrollPlayer(player, SCROLL_GO_ON);
12654 ScrollScreen(NULL, SCROLL_GO_ON);
12656 AdvanceFrameAndPlayerCounters(player->index_nr);
12659 BackToFront_WithFrameDelay(0);
12662 player->move_delay_value = original_move_delay_value;
12665 player->is_active = FALSE;
12667 if (player->last_move_dir & MV_HORIZONTAL)
12669 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12670 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12674 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12675 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12678 if (!moved && !player->is_active)
12680 player->is_moving = FALSE;
12681 player->is_digging = FALSE;
12682 player->is_collecting = FALSE;
12683 player->is_snapping = FALSE;
12684 player->is_pushing = FALSE;
12690 if (moved & MP_MOVING && !ScreenMovPos &&
12691 (player->index_nr == game.centered_player_nr ||
12692 game.centered_player_nr == -1))
12694 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12696 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12698 // actual player has left the screen -- scroll in that direction
12699 if (jx != old_jx) // player has moved horizontally
12700 scroll_x += (jx - old_jx);
12701 else // player has moved vertically
12702 scroll_y += (jy - old_jy);
12706 int offset_raw = game.scroll_delay_value;
12708 if (jx != old_jx) // player has moved horizontally
12710 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12711 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12712 int new_scroll_x = jx - MIDPOSX + offset_x;
12714 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12715 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12716 scroll_x = new_scroll_x;
12718 // don't scroll over playfield boundaries
12719 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12721 // don't scroll more than one field at a time
12722 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12724 // don't scroll against the player's moving direction
12725 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12726 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12727 scroll_x = old_scroll_x;
12729 else // player has moved vertically
12731 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12732 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12733 int new_scroll_y = jy - MIDPOSY + offset_y;
12735 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12736 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12737 scroll_y = new_scroll_y;
12739 // don't scroll over playfield boundaries
12740 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12742 // don't scroll more than one field at a time
12743 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12745 // don't scroll against the player's moving direction
12746 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12747 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12748 scroll_y = old_scroll_y;
12752 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12754 if (!network.enabled && game.centered_player_nr == -1 &&
12755 !AllPlayersInVisibleScreen())
12757 scroll_x = old_scroll_x;
12758 scroll_y = old_scroll_y;
12762 ScrollScreen(player, SCROLL_INIT);
12763 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12768 player->StepFrame = 0;
12770 if (moved & MP_MOVING)
12772 if (old_jx != jx && old_jy == jy)
12773 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12774 else if (old_jx == jx && old_jy != jy)
12775 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12777 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12779 player->last_move_dir = player->MovDir;
12780 player->is_moving = TRUE;
12781 player->is_snapping = FALSE;
12782 player->is_switching = FALSE;
12783 player->is_dropping = FALSE;
12784 player->is_dropping_pressed = FALSE;
12785 player->drop_pressed_delay = 0;
12788 // should better be called here than above, but this breaks some tapes
12789 ScrollPlayer(player, SCROLL_INIT);
12794 CheckGravityMovementWhenNotMoving(player);
12796 player->is_moving = FALSE;
12798 /* at this point, the player is allowed to move, but cannot move right now
12799 (e.g. because of something blocking the way) -- ensure that the player
12800 is also allowed to move in the next frame (in old versions before 3.1.1,
12801 the player was forced to wait again for eight frames before next try) */
12803 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12804 player->move_delay = 0; // allow direct movement in the next frame
12807 if (player->move_delay == -1) // not yet initialized by DigField()
12808 player->move_delay = player->move_delay_value;
12810 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12812 TestIfPlayerTouchesBadThing(jx, jy);
12813 TestIfPlayerTouchesCustomElement(jx, jy);
12816 if (!player->active)
12817 RemovePlayer(player);
12822 void ScrollPlayer(struct PlayerInfo *player, int mode)
12824 int jx = player->jx, jy = player->jy;
12825 int last_jx = player->last_jx, last_jy = player->last_jy;
12826 int move_stepsize = TILEX / player->move_delay_value;
12828 if (!player->active)
12831 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12834 if (mode == SCROLL_INIT)
12836 player->actual_frame_counter = FrameCounter;
12837 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12839 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12840 Feld[last_jx][last_jy] == EL_EMPTY)
12842 int last_field_block_delay = 0; // start with no blocking at all
12843 int block_delay_adjustment = player->block_delay_adjustment;
12845 // if player blocks last field, add delay for exactly one move
12846 if (player->block_last_field)
12848 last_field_block_delay += player->move_delay_value;
12850 // when blocking enabled, prevent moving up despite gravity
12851 if (player->gravity && player->MovDir == MV_UP)
12852 block_delay_adjustment = -1;
12855 // add block delay adjustment (also possible when not blocking)
12856 last_field_block_delay += block_delay_adjustment;
12858 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12859 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12862 if (player->MovPos != 0) // player has not yet reached destination
12865 else if (!FrameReached(&player->actual_frame_counter, 1))
12868 if (player->MovPos != 0)
12870 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12871 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12873 // before DrawPlayer() to draw correct player graphic for this case
12874 if (player->MovPos == 0)
12875 CheckGravityMovement(player);
12878 if (player->MovPos == 0) // player reached destination field
12880 if (player->move_delay_reset_counter > 0)
12882 player->move_delay_reset_counter--;
12884 if (player->move_delay_reset_counter == 0)
12886 // continue with normal speed after quickly moving through gate
12887 HALVE_PLAYER_SPEED(player);
12889 // be able to make the next move without delay
12890 player->move_delay = 0;
12894 player->last_jx = jx;
12895 player->last_jy = jy;
12897 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12898 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12899 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12900 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12901 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12902 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12903 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12904 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12906 ExitPlayer(player);
12908 if (game.players_still_needed == 0 &&
12909 (game.friends_still_needed == 0 ||
12910 IS_SP_ELEMENT(Feld[jx][jy])))
12914 // this breaks one level: "machine", level 000
12916 int move_direction = player->MovDir;
12917 int enter_side = MV_DIR_OPPOSITE(move_direction);
12918 int leave_side = move_direction;
12919 int old_jx = last_jx;
12920 int old_jy = last_jy;
12921 int old_element = Feld[old_jx][old_jy];
12922 int new_element = Feld[jx][jy];
12924 if (IS_CUSTOM_ELEMENT(old_element))
12925 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12927 player->index_bit, leave_side);
12929 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12930 CE_PLAYER_LEAVES_X,
12931 player->index_bit, leave_side);
12933 if (IS_CUSTOM_ELEMENT(new_element))
12934 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12935 player->index_bit, enter_side);
12937 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12938 CE_PLAYER_ENTERS_X,
12939 player->index_bit, enter_side);
12941 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12942 CE_MOVE_OF_X, move_direction);
12945 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12947 TestIfPlayerTouchesBadThing(jx, jy);
12948 TestIfPlayerTouchesCustomElement(jx, jy);
12950 /* needed because pushed element has not yet reached its destination,
12951 so it would trigger a change event at its previous field location */
12952 if (!player->is_pushing)
12953 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12955 if (!player->active)
12956 RemovePlayer(player);
12959 if (!game.LevelSolved && level.use_step_counter)
12969 if (TimeLeft <= 10 && setup.time_limit)
12970 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12972 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12974 DisplayGameControlValues();
12976 if (!TimeLeft && setup.time_limit)
12977 for (i = 0; i < MAX_PLAYERS; i++)
12978 KillPlayer(&stored_player[i]);
12980 else if (game.no_time_limit && !game.all_players_gone)
12982 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12984 DisplayGameControlValues();
12988 if (tape.single_step && tape.recording && !tape.pausing &&
12989 !player->programmed_action)
12990 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12992 if (!player->programmed_action)
12993 CheckSaveEngineSnapshot(player);
12997 void ScrollScreen(struct PlayerInfo *player, int mode)
12999 static unsigned int screen_frame_counter = 0;
13001 if (mode == SCROLL_INIT)
13003 // set scrolling step size according to actual player's moving speed
13004 ScrollStepSize = TILEX / player->move_delay_value;
13006 screen_frame_counter = FrameCounter;
13007 ScreenMovDir = player->MovDir;
13008 ScreenMovPos = player->MovPos;
13009 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13012 else if (!FrameReached(&screen_frame_counter, 1))
13017 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13018 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13019 redraw_mask |= REDRAW_FIELD;
13022 ScreenMovDir = MV_NONE;
13025 void TestIfPlayerTouchesCustomElement(int x, int y)
13027 static int xy[4][2] =
13034 static int trigger_sides[4][2] =
13036 // center side border side
13037 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13038 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13039 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13040 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13042 static int touch_dir[4] =
13044 MV_LEFT | MV_RIGHT,
13049 int center_element = Feld[x][y]; // should always be non-moving!
13052 for (i = 0; i < NUM_DIRECTIONS; i++)
13054 int xx = x + xy[i][0];
13055 int yy = y + xy[i][1];
13056 int center_side = trigger_sides[i][0];
13057 int border_side = trigger_sides[i][1];
13058 int border_element;
13060 if (!IN_LEV_FIELD(xx, yy))
13063 if (IS_PLAYER(x, y)) // player found at center element
13065 struct PlayerInfo *player = PLAYERINFO(x, y);
13067 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13068 border_element = Feld[xx][yy]; // may be moving!
13069 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13070 border_element = Feld[xx][yy];
13071 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13072 border_element = MovingOrBlocked2Element(xx, yy);
13074 continue; // center and border element do not touch
13076 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13077 player->index_bit, border_side);
13078 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13079 CE_PLAYER_TOUCHES_X,
13080 player->index_bit, border_side);
13083 /* use player element that is initially defined in the level playfield,
13084 not the player element that corresponds to the runtime player number
13085 (example: a level that contains EL_PLAYER_3 as the only player would
13086 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13087 int player_element = PLAYERINFO(x, y)->initial_element;
13089 CheckElementChangeBySide(xx, yy, border_element, player_element,
13090 CE_TOUCHING_X, border_side);
13093 else if (IS_PLAYER(xx, yy)) // player found at border element
13095 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13097 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13099 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13100 continue; // center and border element do not touch
13103 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13104 player->index_bit, center_side);
13105 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13106 CE_PLAYER_TOUCHES_X,
13107 player->index_bit, center_side);
13110 /* use player element that is initially defined in the level playfield,
13111 not the player element that corresponds to the runtime player number
13112 (example: a level that contains EL_PLAYER_3 as the only player would
13113 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13114 int player_element = PLAYERINFO(xx, yy)->initial_element;
13116 CheckElementChangeBySide(x, y, center_element, player_element,
13117 CE_TOUCHING_X, center_side);
13125 void TestIfElementTouchesCustomElement(int x, int y)
13127 static int xy[4][2] =
13134 static int trigger_sides[4][2] =
13136 // center side border side
13137 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13138 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13139 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13140 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13142 static int touch_dir[4] =
13144 MV_LEFT | MV_RIGHT,
13149 boolean change_center_element = FALSE;
13150 int center_element = Feld[x][y]; // should always be non-moving!
13151 int border_element_old[NUM_DIRECTIONS];
13154 for (i = 0; i < NUM_DIRECTIONS; i++)
13156 int xx = x + xy[i][0];
13157 int yy = y + xy[i][1];
13158 int border_element;
13160 border_element_old[i] = -1;
13162 if (!IN_LEV_FIELD(xx, yy))
13165 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13166 border_element = Feld[xx][yy]; // may be moving!
13167 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13168 border_element = Feld[xx][yy];
13169 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13170 border_element = MovingOrBlocked2Element(xx, yy);
13172 continue; // center and border element do not touch
13174 border_element_old[i] = border_element;
13177 for (i = 0; i < NUM_DIRECTIONS; i++)
13179 int xx = x + xy[i][0];
13180 int yy = y + xy[i][1];
13181 int center_side = trigger_sides[i][0];
13182 int border_element = border_element_old[i];
13184 if (border_element == -1)
13187 // check for change of border element
13188 CheckElementChangeBySide(xx, yy, border_element, center_element,
13189 CE_TOUCHING_X, center_side);
13191 // (center element cannot be player, so we dont have to check this here)
13194 for (i = 0; i < NUM_DIRECTIONS; i++)
13196 int xx = x + xy[i][0];
13197 int yy = y + xy[i][1];
13198 int border_side = trigger_sides[i][1];
13199 int border_element = border_element_old[i];
13201 if (border_element == -1)
13204 // check for change of center element (but change it only once)
13205 if (!change_center_element)
13206 change_center_element =
13207 CheckElementChangeBySide(x, y, center_element, border_element,
13208 CE_TOUCHING_X, border_side);
13210 if (IS_PLAYER(xx, yy))
13212 /* use player element that is initially defined in the level playfield,
13213 not the player element that corresponds to the runtime player number
13214 (example: a level that contains EL_PLAYER_3 as the only player would
13215 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13216 int player_element = PLAYERINFO(xx, yy)->initial_element;
13218 CheckElementChangeBySide(x, y, center_element, player_element,
13219 CE_TOUCHING_X, border_side);
13224 void TestIfElementHitsCustomElement(int x, int y, int direction)
13226 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13227 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13228 int hitx = x + dx, hity = y + dy;
13229 int hitting_element = Feld[x][y];
13230 int touched_element;
13232 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13235 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13236 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13238 if (IN_LEV_FIELD(hitx, hity))
13240 int opposite_direction = MV_DIR_OPPOSITE(direction);
13241 int hitting_side = direction;
13242 int touched_side = opposite_direction;
13243 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13244 MovDir[hitx][hity] != direction ||
13245 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13251 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13252 CE_HITTING_X, touched_side);
13254 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13255 CE_HIT_BY_X, hitting_side);
13257 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13258 CE_HIT_BY_SOMETHING, opposite_direction);
13260 if (IS_PLAYER(hitx, hity))
13262 /* use player element that is initially defined in the level playfield,
13263 not the player element that corresponds to the runtime player number
13264 (example: a level that contains EL_PLAYER_3 as the only player would
13265 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13266 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13268 CheckElementChangeBySide(x, y, hitting_element, player_element,
13269 CE_HITTING_X, touched_side);
13274 // "hitting something" is also true when hitting the playfield border
13275 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13276 CE_HITTING_SOMETHING, direction);
13279 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13281 int i, kill_x = -1, kill_y = -1;
13283 int bad_element = -1;
13284 static int test_xy[4][2] =
13291 static int test_dir[4] =
13299 for (i = 0; i < NUM_DIRECTIONS; i++)
13301 int test_x, test_y, test_move_dir, test_element;
13303 test_x = good_x + test_xy[i][0];
13304 test_y = good_y + test_xy[i][1];
13306 if (!IN_LEV_FIELD(test_x, test_y))
13310 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13312 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13314 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13315 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13317 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13318 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13322 bad_element = test_element;
13328 if (kill_x != -1 || kill_y != -1)
13330 if (IS_PLAYER(good_x, good_y))
13332 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13334 if (player->shield_deadly_time_left > 0 &&
13335 !IS_INDESTRUCTIBLE(bad_element))
13336 Bang(kill_x, kill_y);
13337 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13338 KillPlayer(player);
13341 Bang(good_x, good_y);
13345 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13347 int i, kill_x = -1, kill_y = -1;
13348 int bad_element = Feld[bad_x][bad_y];
13349 static int test_xy[4][2] =
13356 static int touch_dir[4] =
13358 MV_LEFT | MV_RIGHT,
13363 static int test_dir[4] =
13371 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13374 for (i = 0; i < NUM_DIRECTIONS; i++)
13376 int test_x, test_y, test_move_dir, test_element;
13378 test_x = bad_x + test_xy[i][0];
13379 test_y = bad_y + test_xy[i][1];
13381 if (!IN_LEV_FIELD(test_x, test_y))
13385 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13387 test_element = Feld[test_x][test_y];
13389 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13390 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13392 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13393 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13395 // good thing is player or penguin that does not move away
13396 if (IS_PLAYER(test_x, test_y))
13398 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13400 if (bad_element == EL_ROBOT && player->is_moving)
13401 continue; // robot does not kill player if he is moving
13403 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13405 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13406 continue; // center and border element do not touch
13414 else if (test_element == EL_PENGUIN)
13424 if (kill_x != -1 || kill_y != -1)
13426 if (IS_PLAYER(kill_x, kill_y))
13428 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13430 if (player->shield_deadly_time_left > 0 &&
13431 !IS_INDESTRUCTIBLE(bad_element))
13432 Bang(bad_x, bad_y);
13433 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13434 KillPlayer(player);
13437 Bang(kill_x, kill_y);
13441 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13443 int bad_element = Feld[bad_x][bad_y];
13444 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13445 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13446 int test_x = bad_x + dx, test_y = bad_y + dy;
13447 int test_move_dir, test_element;
13448 int kill_x = -1, kill_y = -1;
13450 if (!IN_LEV_FIELD(test_x, test_y))
13454 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13456 test_element = Feld[test_x][test_y];
13458 if (test_move_dir != bad_move_dir)
13460 // good thing can be player or penguin that does not move away
13461 if (IS_PLAYER(test_x, test_y))
13463 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13465 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13466 player as being hit when he is moving towards the bad thing, because
13467 the "get hit by" condition would be lost after the player stops) */
13468 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13469 return; // player moves away from bad thing
13474 else if (test_element == EL_PENGUIN)
13481 if (kill_x != -1 || kill_y != -1)
13483 if (IS_PLAYER(kill_x, kill_y))
13485 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13487 if (player->shield_deadly_time_left > 0 &&
13488 !IS_INDESTRUCTIBLE(bad_element))
13489 Bang(bad_x, bad_y);
13490 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13491 KillPlayer(player);
13494 Bang(kill_x, kill_y);
13498 void TestIfPlayerTouchesBadThing(int x, int y)
13500 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13503 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13505 TestIfGoodThingHitsBadThing(x, y, move_dir);
13508 void TestIfBadThingTouchesPlayer(int x, int y)
13510 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13513 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13515 TestIfBadThingHitsGoodThing(x, y, move_dir);
13518 void TestIfFriendTouchesBadThing(int x, int y)
13520 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13523 void TestIfBadThingTouchesFriend(int x, int y)
13525 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13528 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13530 int i, kill_x = bad_x, kill_y = bad_y;
13531 static int xy[4][2] =
13539 for (i = 0; i < NUM_DIRECTIONS; i++)
13543 x = bad_x + xy[i][0];
13544 y = bad_y + xy[i][1];
13545 if (!IN_LEV_FIELD(x, y))
13548 element = Feld[x][y];
13549 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13550 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13558 if (kill_x != bad_x || kill_y != bad_y)
13559 Bang(bad_x, bad_y);
13562 void KillPlayer(struct PlayerInfo *player)
13564 int jx = player->jx, jy = player->jy;
13566 if (!player->active)
13570 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13571 player->killed, player->active, player->reanimated);
13574 /* the following code was introduced to prevent an infinite loop when calling
13576 -> CheckTriggeredElementChangeExt()
13577 -> ExecuteCustomElementAction()
13579 -> (infinitely repeating the above sequence of function calls)
13580 which occurs when killing the player while having a CE with the setting
13581 "kill player X when explosion of <player X>"; the solution using a new
13582 field "player->killed" was chosen for backwards compatibility, although
13583 clever use of the fields "player->active" etc. would probably also work */
13585 if (player->killed)
13589 player->killed = TRUE;
13591 // remove accessible field at the player's position
13592 Feld[jx][jy] = EL_EMPTY;
13594 // deactivate shield (else Bang()/Explode() would not work right)
13595 player->shield_normal_time_left = 0;
13596 player->shield_deadly_time_left = 0;
13599 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13600 player->killed, player->active, player->reanimated);
13606 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13607 player->killed, player->active, player->reanimated);
13610 if (player->reanimated) // killed player may have been reanimated
13611 player->killed = player->reanimated = FALSE;
13613 BuryPlayer(player);
13616 static void KillPlayerUnlessEnemyProtected(int x, int y)
13618 if (!PLAYER_ENEMY_PROTECTED(x, y))
13619 KillPlayer(PLAYERINFO(x, y));
13622 static void KillPlayerUnlessExplosionProtected(int x, int y)
13624 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13625 KillPlayer(PLAYERINFO(x, y));
13628 void BuryPlayer(struct PlayerInfo *player)
13630 int jx = player->jx, jy = player->jy;
13632 if (!player->active)
13635 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13636 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13638 RemovePlayer(player);
13640 player->buried = TRUE;
13642 if (game.all_players_gone)
13643 game.GameOver = TRUE;
13646 void RemovePlayer(struct PlayerInfo *player)
13648 int jx = player->jx, jy = player->jy;
13649 int i, found = FALSE;
13651 player->present = FALSE;
13652 player->active = FALSE;
13654 // required for some CE actions (even if the player is not active anymore)
13655 player->MovPos = 0;
13657 if (!ExplodeField[jx][jy])
13658 StorePlayer[jx][jy] = 0;
13660 if (player->is_moving)
13661 TEST_DrawLevelField(player->last_jx, player->last_jy);
13663 for (i = 0; i < MAX_PLAYERS; i++)
13664 if (stored_player[i].active)
13669 game.all_players_gone = TRUE;
13670 game.GameOver = TRUE;
13673 game.exit_x = game.robot_wheel_x = jx;
13674 game.exit_y = game.robot_wheel_y = jy;
13677 void ExitPlayer(struct PlayerInfo *player)
13679 DrawPlayer(player); // needed here only to cleanup last field
13680 RemovePlayer(player);
13682 if (game.players_still_needed > 0)
13683 game.players_still_needed--;
13686 static void setFieldForSnapping(int x, int y, int element, int direction)
13688 struct ElementInfo *ei = &element_info[element];
13689 int direction_bit = MV_DIR_TO_BIT(direction);
13690 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13691 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13692 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13694 Feld[x][y] = EL_ELEMENT_SNAPPING;
13695 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13697 ResetGfxAnimation(x, y);
13699 GfxElement[x][y] = element;
13700 GfxAction[x][y] = action;
13701 GfxDir[x][y] = direction;
13702 GfxFrame[x][y] = -1;
13706 =============================================================================
13707 checkDiagonalPushing()
13708 -----------------------------------------------------------------------------
13709 check if diagonal input device direction results in pushing of object
13710 (by checking if the alternative direction is walkable, diggable, ...)
13711 =============================================================================
13714 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13715 int x, int y, int real_dx, int real_dy)
13717 int jx, jy, dx, dy, xx, yy;
13719 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13722 // diagonal direction: check alternative direction
13727 xx = jx + (dx == 0 ? real_dx : 0);
13728 yy = jy + (dy == 0 ? real_dy : 0);
13730 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13734 =============================================================================
13736 -----------------------------------------------------------------------------
13737 x, y: field next to player (non-diagonal) to try to dig to
13738 real_dx, real_dy: direction as read from input device (can be diagonal)
13739 =============================================================================
13742 static int DigField(struct PlayerInfo *player,
13743 int oldx, int oldy, int x, int y,
13744 int real_dx, int real_dy, int mode)
13746 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13747 boolean player_was_pushing = player->is_pushing;
13748 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13749 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13750 int jx = oldx, jy = oldy;
13751 int dx = x - jx, dy = y - jy;
13752 int nextx = x + dx, nexty = y + dy;
13753 int move_direction = (dx == -1 ? MV_LEFT :
13754 dx == +1 ? MV_RIGHT :
13756 dy == +1 ? MV_DOWN : MV_NONE);
13757 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13758 int dig_side = MV_DIR_OPPOSITE(move_direction);
13759 int old_element = Feld[jx][jy];
13760 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13763 if (is_player) // function can also be called by EL_PENGUIN
13765 if (player->MovPos == 0)
13767 player->is_digging = FALSE;
13768 player->is_collecting = FALSE;
13771 if (player->MovPos == 0) // last pushing move finished
13772 player->is_pushing = FALSE;
13774 if (mode == DF_NO_PUSH) // player just stopped pushing
13776 player->is_switching = FALSE;
13777 player->push_delay = -1;
13779 return MP_NO_ACTION;
13783 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13784 old_element = Back[jx][jy];
13786 // in case of element dropped at player position, check background
13787 else if (Back[jx][jy] != EL_EMPTY &&
13788 game.engine_version >= VERSION_IDENT(2,2,0,0))
13789 old_element = Back[jx][jy];
13791 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13792 return MP_NO_ACTION; // field has no opening in this direction
13794 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13795 return MP_NO_ACTION; // field has no opening in this direction
13797 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13801 Feld[jx][jy] = player->artwork_element;
13802 InitMovingField(jx, jy, MV_DOWN);
13803 Store[jx][jy] = EL_ACID;
13804 ContinueMoving(jx, jy);
13805 BuryPlayer(player);
13807 return MP_DONT_RUN_INTO;
13810 if (player_can_move && DONT_RUN_INTO(element))
13812 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13814 return MP_DONT_RUN_INTO;
13817 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13818 return MP_NO_ACTION;
13820 collect_count = element_info[element].collect_count_initial;
13822 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13823 return MP_NO_ACTION;
13825 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13826 player_can_move = player_can_move_or_snap;
13828 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13829 game.engine_version >= VERSION_IDENT(2,2,0,0))
13831 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13832 player->index_bit, dig_side);
13833 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13834 player->index_bit, dig_side);
13836 if (element == EL_DC_LANDMINE)
13839 if (Feld[x][y] != element) // field changed by snapping
13842 return MP_NO_ACTION;
13845 if (player->gravity && is_player && !player->is_auto_moving &&
13846 canFallDown(player) && move_direction != MV_DOWN &&
13847 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13848 return MP_NO_ACTION; // player cannot walk here due to gravity
13850 if (player_can_move &&
13851 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13853 int sound_element = SND_ELEMENT(element);
13854 int sound_action = ACTION_WALKING;
13856 if (IS_RND_GATE(element))
13858 if (!player->key[RND_GATE_NR(element)])
13859 return MP_NO_ACTION;
13861 else if (IS_RND_GATE_GRAY(element))
13863 if (!player->key[RND_GATE_GRAY_NR(element)])
13864 return MP_NO_ACTION;
13866 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13868 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13869 return MP_NO_ACTION;
13871 else if (element == EL_EXIT_OPEN ||
13872 element == EL_EM_EXIT_OPEN ||
13873 element == EL_EM_EXIT_OPENING ||
13874 element == EL_STEEL_EXIT_OPEN ||
13875 element == EL_EM_STEEL_EXIT_OPEN ||
13876 element == EL_EM_STEEL_EXIT_OPENING ||
13877 element == EL_SP_EXIT_OPEN ||
13878 element == EL_SP_EXIT_OPENING)
13880 sound_action = ACTION_PASSING; // player is passing exit
13882 else if (element == EL_EMPTY)
13884 sound_action = ACTION_MOVING; // nothing to walk on
13887 // play sound from background or player, whatever is available
13888 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13889 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13891 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13893 else if (player_can_move &&
13894 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13896 if (!ACCESS_FROM(element, opposite_direction))
13897 return MP_NO_ACTION; // field not accessible from this direction
13899 if (CAN_MOVE(element)) // only fixed elements can be passed!
13900 return MP_NO_ACTION;
13902 if (IS_EM_GATE(element))
13904 if (!player->key[EM_GATE_NR(element)])
13905 return MP_NO_ACTION;
13907 else if (IS_EM_GATE_GRAY(element))
13909 if (!player->key[EM_GATE_GRAY_NR(element)])
13910 return MP_NO_ACTION;
13912 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13914 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13915 return MP_NO_ACTION;
13917 else if (IS_EMC_GATE(element))
13919 if (!player->key[EMC_GATE_NR(element)])
13920 return MP_NO_ACTION;
13922 else if (IS_EMC_GATE_GRAY(element))
13924 if (!player->key[EMC_GATE_GRAY_NR(element)])
13925 return MP_NO_ACTION;
13927 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13929 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13930 return MP_NO_ACTION;
13932 else if (element == EL_DC_GATE_WHITE ||
13933 element == EL_DC_GATE_WHITE_GRAY ||
13934 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13936 if (player->num_white_keys == 0)
13937 return MP_NO_ACTION;
13939 player->num_white_keys--;
13941 else if (IS_SP_PORT(element))
13943 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13944 element == EL_SP_GRAVITY_PORT_RIGHT ||
13945 element == EL_SP_GRAVITY_PORT_UP ||
13946 element == EL_SP_GRAVITY_PORT_DOWN)
13947 player->gravity = !player->gravity;
13948 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13949 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13950 element == EL_SP_GRAVITY_ON_PORT_UP ||
13951 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13952 player->gravity = TRUE;
13953 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13954 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13955 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13956 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13957 player->gravity = FALSE;
13960 // automatically move to the next field with double speed
13961 player->programmed_action = move_direction;
13963 if (player->move_delay_reset_counter == 0)
13965 player->move_delay_reset_counter = 2; // two double speed steps
13967 DOUBLE_PLAYER_SPEED(player);
13970 PlayLevelSoundAction(x, y, ACTION_PASSING);
13972 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13976 if (mode != DF_SNAP)
13978 GfxElement[x][y] = GFX_ELEMENT(element);
13979 player->is_digging = TRUE;
13982 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13984 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13985 player->index_bit, dig_side);
13987 if (mode == DF_SNAP)
13989 if (level.block_snap_field)
13990 setFieldForSnapping(x, y, element, move_direction);
13992 TestIfElementTouchesCustomElement(x, y); // for empty space
13994 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13995 player->index_bit, dig_side);
13998 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14002 if (is_player && mode != DF_SNAP)
14004 GfxElement[x][y] = element;
14005 player->is_collecting = TRUE;
14008 if (element == EL_SPEED_PILL)
14010 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14012 else if (element == EL_EXTRA_TIME && level.time > 0)
14014 TimeLeft += level.extra_time;
14016 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14018 DisplayGameControlValues();
14020 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14022 player->shield_normal_time_left += level.shield_normal_time;
14023 if (element == EL_SHIELD_DEADLY)
14024 player->shield_deadly_time_left += level.shield_deadly_time;
14026 else if (element == EL_DYNAMITE ||
14027 element == EL_EM_DYNAMITE ||
14028 element == EL_SP_DISK_RED)
14030 if (player->inventory_size < MAX_INVENTORY_SIZE)
14031 player->inventory_element[player->inventory_size++] = element;
14033 DrawGameDoorValues();
14035 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14037 player->dynabomb_count++;
14038 player->dynabombs_left++;
14040 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14042 player->dynabomb_size++;
14044 else if (element == EL_DYNABOMB_INCREASE_POWER)
14046 player->dynabomb_xl = TRUE;
14048 else if (IS_KEY(element))
14050 player->key[KEY_NR(element)] = TRUE;
14052 DrawGameDoorValues();
14054 else if (element == EL_DC_KEY_WHITE)
14056 player->num_white_keys++;
14058 // display white keys?
14059 // DrawGameDoorValues();
14061 else if (IS_ENVELOPE(element))
14063 player->show_envelope = element;
14065 else if (element == EL_EMC_LENSES)
14067 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14069 RedrawAllInvisibleElementsForLenses();
14071 else if (element == EL_EMC_MAGNIFIER)
14073 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14075 RedrawAllInvisibleElementsForMagnifier();
14077 else if (IS_DROPPABLE(element) ||
14078 IS_THROWABLE(element)) // can be collected and dropped
14082 if (collect_count == 0)
14083 player->inventory_infinite_element = element;
14085 for (i = 0; i < collect_count; i++)
14086 if (player->inventory_size < MAX_INVENTORY_SIZE)
14087 player->inventory_element[player->inventory_size++] = element;
14089 DrawGameDoorValues();
14091 else if (collect_count > 0)
14093 game.gems_still_needed -= collect_count;
14094 if (game.gems_still_needed < 0)
14095 game.gems_still_needed = 0;
14097 game.snapshot.collected_item = TRUE;
14099 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14101 DisplayGameControlValues();
14104 RaiseScoreElement(element);
14105 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14108 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14109 player->index_bit, dig_side);
14111 if (mode == DF_SNAP)
14113 if (level.block_snap_field)
14114 setFieldForSnapping(x, y, element, move_direction);
14116 TestIfElementTouchesCustomElement(x, y); // for empty space
14118 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14119 player->index_bit, dig_side);
14122 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14124 if (mode == DF_SNAP && element != EL_BD_ROCK)
14125 return MP_NO_ACTION;
14127 if (CAN_FALL(element) && dy)
14128 return MP_NO_ACTION;
14130 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14131 !(element == EL_SPRING && level.use_spring_bug))
14132 return MP_NO_ACTION;
14134 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14135 ((move_direction & MV_VERTICAL &&
14136 ((element_info[element].move_pattern & MV_LEFT &&
14137 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14138 (element_info[element].move_pattern & MV_RIGHT &&
14139 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14140 (move_direction & MV_HORIZONTAL &&
14141 ((element_info[element].move_pattern & MV_UP &&
14142 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14143 (element_info[element].move_pattern & MV_DOWN &&
14144 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14145 return MP_NO_ACTION;
14147 // do not push elements already moving away faster than player
14148 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14149 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14150 return MP_NO_ACTION;
14152 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14154 if (player->push_delay_value == -1 || !player_was_pushing)
14155 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14157 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14159 if (player->push_delay_value == -1)
14160 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14162 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14164 if (!player->is_pushing)
14165 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14168 player->is_pushing = TRUE;
14169 player->is_active = TRUE;
14171 if (!(IN_LEV_FIELD(nextx, nexty) &&
14172 (IS_FREE(nextx, nexty) ||
14173 (IS_SB_ELEMENT(element) &&
14174 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14175 (IS_CUSTOM_ELEMENT(element) &&
14176 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14177 return MP_NO_ACTION;
14179 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14180 return MP_NO_ACTION;
14182 if (player->push_delay == -1) // new pushing; restart delay
14183 player->push_delay = 0;
14185 if (player->push_delay < player->push_delay_value &&
14186 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14187 element != EL_SPRING && element != EL_BALLOON)
14189 // make sure that there is no move delay before next try to push
14190 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14191 player->move_delay = 0;
14193 return MP_NO_ACTION;
14196 if (IS_CUSTOM_ELEMENT(element) &&
14197 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14199 if (!DigFieldByCE(nextx, nexty, element))
14200 return MP_NO_ACTION;
14203 if (IS_SB_ELEMENT(element))
14205 boolean sokoban_task_solved = FALSE;
14207 if (element == EL_SOKOBAN_FIELD_FULL)
14209 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14211 IncrementSokobanFieldsNeeded();
14212 IncrementSokobanObjectsNeeded();
14215 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14217 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14219 DecrementSokobanFieldsNeeded();
14220 DecrementSokobanObjectsNeeded();
14222 // sokoban object was pushed from empty field to sokoban field
14223 if (Back[x][y] == EL_EMPTY)
14224 sokoban_task_solved = TRUE;
14227 Feld[x][y] = EL_SOKOBAN_OBJECT;
14229 if (Back[x][y] == Back[nextx][nexty])
14230 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14231 else if (Back[x][y] != 0)
14232 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14235 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14238 if (sokoban_task_solved &&
14239 game.sokoban_fields_still_needed == 0 &&
14240 game.sokoban_objects_still_needed == 0 &&
14241 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14243 game.players_still_needed = 0;
14247 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14251 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14253 InitMovingField(x, y, move_direction);
14254 GfxAction[x][y] = ACTION_PUSHING;
14256 if (mode == DF_SNAP)
14257 ContinueMoving(x, y);
14259 MovPos[x][y] = (dx != 0 ? dx : dy);
14261 Pushed[x][y] = TRUE;
14262 Pushed[nextx][nexty] = TRUE;
14264 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14265 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14267 player->push_delay_value = -1; // get new value later
14269 // check for element change _after_ element has been pushed
14270 if (game.use_change_when_pushing_bug)
14272 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14273 player->index_bit, dig_side);
14274 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14275 player->index_bit, dig_side);
14278 else if (IS_SWITCHABLE(element))
14280 if (PLAYER_SWITCHING(player, x, y))
14282 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14283 player->index_bit, dig_side);
14288 player->is_switching = TRUE;
14289 player->switch_x = x;
14290 player->switch_y = y;
14292 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14294 if (element == EL_ROBOT_WHEEL)
14296 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14298 game.robot_wheel_x = x;
14299 game.robot_wheel_y = y;
14300 game.robot_wheel_active = TRUE;
14302 TEST_DrawLevelField(x, y);
14304 else if (element == EL_SP_TERMINAL)
14308 SCAN_PLAYFIELD(xx, yy)
14310 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14314 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14316 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14318 ResetGfxAnimation(xx, yy);
14319 TEST_DrawLevelField(xx, yy);
14323 else if (IS_BELT_SWITCH(element))
14325 ToggleBeltSwitch(x, y);
14327 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14328 element == EL_SWITCHGATE_SWITCH_DOWN ||
14329 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14330 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14332 ToggleSwitchgateSwitch(x, y);
14334 else if (element == EL_LIGHT_SWITCH ||
14335 element == EL_LIGHT_SWITCH_ACTIVE)
14337 ToggleLightSwitch(x, y);
14339 else if (element == EL_TIMEGATE_SWITCH ||
14340 element == EL_DC_TIMEGATE_SWITCH)
14342 ActivateTimegateSwitch(x, y);
14344 else if (element == EL_BALLOON_SWITCH_LEFT ||
14345 element == EL_BALLOON_SWITCH_RIGHT ||
14346 element == EL_BALLOON_SWITCH_UP ||
14347 element == EL_BALLOON_SWITCH_DOWN ||
14348 element == EL_BALLOON_SWITCH_NONE ||
14349 element == EL_BALLOON_SWITCH_ANY)
14351 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14352 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14353 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14354 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14355 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14358 else if (element == EL_LAMP)
14360 Feld[x][y] = EL_LAMP_ACTIVE;
14361 game.lights_still_needed--;
14363 ResetGfxAnimation(x, y);
14364 TEST_DrawLevelField(x, y);
14366 else if (element == EL_TIME_ORB_FULL)
14368 Feld[x][y] = EL_TIME_ORB_EMPTY;
14370 if (level.time > 0 || level.use_time_orb_bug)
14372 TimeLeft += level.time_orb_time;
14373 game.no_time_limit = FALSE;
14375 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14377 DisplayGameControlValues();
14380 ResetGfxAnimation(x, y);
14381 TEST_DrawLevelField(x, y);
14383 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14384 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14388 game.ball_active = !game.ball_active;
14390 SCAN_PLAYFIELD(xx, yy)
14392 int e = Feld[xx][yy];
14394 if (game.ball_active)
14396 if (e == EL_EMC_MAGIC_BALL)
14397 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14398 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14399 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14403 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14404 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14405 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14406 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14411 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14412 player->index_bit, dig_side);
14414 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14415 player->index_bit, dig_side);
14417 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14418 player->index_bit, dig_side);
14424 if (!PLAYER_SWITCHING(player, x, y))
14426 player->is_switching = TRUE;
14427 player->switch_x = x;
14428 player->switch_y = y;
14430 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14431 player->index_bit, dig_side);
14432 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14433 player->index_bit, dig_side);
14435 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14436 player->index_bit, dig_side);
14437 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14438 player->index_bit, dig_side);
14441 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14442 player->index_bit, dig_side);
14443 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14444 player->index_bit, dig_side);
14446 return MP_NO_ACTION;
14449 player->push_delay = -1;
14451 if (is_player) // function can also be called by EL_PENGUIN
14453 if (Feld[x][y] != element) // really digged/collected something
14455 player->is_collecting = !player->is_digging;
14456 player->is_active = TRUE;
14463 static boolean DigFieldByCE(int x, int y, int digging_element)
14465 int element = Feld[x][y];
14467 if (!IS_FREE(x, y))
14469 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14470 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14473 // no element can dig solid indestructible elements
14474 if (IS_INDESTRUCTIBLE(element) &&
14475 !IS_DIGGABLE(element) &&
14476 !IS_COLLECTIBLE(element))
14479 if (AmoebaNr[x][y] &&
14480 (element == EL_AMOEBA_FULL ||
14481 element == EL_BD_AMOEBA ||
14482 element == EL_AMOEBA_GROWING))
14484 AmoebaCnt[AmoebaNr[x][y]]--;
14485 AmoebaCnt2[AmoebaNr[x][y]]--;
14488 if (IS_MOVING(x, y))
14489 RemoveMovingField(x, y);
14493 TEST_DrawLevelField(x, y);
14496 // if digged element was about to explode, prevent the explosion
14497 ExplodeField[x][y] = EX_TYPE_NONE;
14499 PlayLevelSoundAction(x, y, action);
14502 Store[x][y] = EL_EMPTY;
14504 // this makes it possible to leave the removed element again
14505 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14506 Store[x][y] = element;
14511 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14513 int jx = player->jx, jy = player->jy;
14514 int x = jx + dx, y = jy + dy;
14515 int snap_direction = (dx == -1 ? MV_LEFT :
14516 dx == +1 ? MV_RIGHT :
14518 dy == +1 ? MV_DOWN : MV_NONE);
14519 boolean can_continue_snapping = (level.continuous_snapping &&
14520 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14522 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14525 if (!player->active || !IN_LEV_FIELD(x, y))
14533 if (player->MovPos == 0)
14534 player->is_pushing = FALSE;
14536 player->is_snapping = FALSE;
14538 if (player->MovPos == 0)
14540 player->is_moving = FALSE;
14541 player->is_digging = FALSE;
14542 player->is_collecting = FALSE;
14548 // prevent snapping with already pressed snap key when not allowed
14549 if (player->is_snapping && !can_continue_snapping)
14552 player->MovDir = snap_direction;
14554 if (player->MovPos == 0)
14556 player->is_moving = FALSE;
14557 player->is_digging = FALSE;
14558 player->is_collecting = FALSE;
14561 player->is_dropping = FALSE;
14562 player->is_dropping_pressed = FALSE;
14563 player->drop_pressed_delay = 0;
14565 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14568 player->is_snapping = TRUE;
14569 player->is_active = TRUE;
14571 if (player->MovPos == 0)
14573 player->is_moving = FALSE;
14574 player->is_digging = FALSE;
14575 player->is_collecting = FALSE;
14578 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14579 TEST_DrawLevelField(player->last_jx, player->last_jy);
14581 TEST_DrawLevelField(x, y);
14586 static boolean DropElement(struct PlayerInfo *player)
14588 int old_element, new_element;
14589 int dropx = player->jx, dropy = player->jy;
14590 int drop_direction = player->MovDir;
14591 int drop_side = drop_direction;
14592 int drop_element = get_next_dropped_element(player);
14594 /* do not drop an element on top of another element; when holding drop key
14595 pressed without moving, dropped element must move away before the next
14596 element can be dropped (this is especially important if the next element
14597 is dynamite, which can be placed on background for historical reasons) */
14598 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14601 if (IS_THROWABLE(drop_element))
14603 dropx += GET_DX_FROM_DIR(drop_direction);
14604 dropy += GET_DY_FROM_DIR(drop_direction);
14606 if (!IN_LEV_FIELD(dropx, dropy))
14610 old_element = Feld[dropx][dropy]; // old element at dropping position
14611 new_element = drop_element; // default: no change when dropping
14613 // check if player is active, not moving and ready to drop
14614 if (!player->active || player->MovPos || player->drop_delay > 0)
14617 // check if player has anything that can be dropped
14618 if (new_element == EL_UNDEFINED)
14621 // only set if player has anything that can be dropped
14622 player->is_dropping_pressed = TRUE;
14624 // check if drop key was pressed long enough for EM style dynamite
14625 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14628 // check if anything can be dropped at the current position
14629 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14632 // collected custom elements can only be dropped on empty fields
14633 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14636 if (old_element != EL_EMPTY)
14637 Back[dropx][dropy] = old_element; // store old element on this field
14639 ResetGfxAnimation(dropx, dropy);
14640 ResetRandomAnimationValue(dropx, dropy);
14642 if (player->inventory_size > 0 ||
14643 player->inventory_infinite_element != EL_UNDEFINED)
14645 if (player->inventory_size > 0)
14647 player->inventory_size--;
14649 DrawGameDoorValues();
14651 if (new_element == EL_DYNAMITE)
14652 new_element = EL_DYNAMITE_ACTIVE;
14653 else if (new_element == EL_EM_DYNAMITE)
14654 new_element = EL_EM_DYNAMITE_ACTIVE;
14655 else if (new_element == EL_SP_DISK_RED)
14656 new_element = EL_SP_DISK_RED_ACTIVE;
14659 Feld[dropx][dropy] = new_element;
14661 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14662 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14663 el2img(Feld[dropx][dropy]), 0);
14665 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14667 // needed if previous element just changed to "empty" in the last frame
14668 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14670 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14671 player->index_bit, drop_side);
14672 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14674 player->index_bit, drop_side);
14676 TestIfElementTouchesCustomElement(dropx, dropy);
14678 else // player is dropping a dyna bomb
14680 player->dynabombs_left--;
14682 Feld[dropx][dropy] = new_element;
14684 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14685 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14686 el2img(Feld[dropx][dropy]), 0);
14688 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14691 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14692 InitField_WithBug1(dropx, dropy, FALSE);
14694 new_element = Feld[dropx][dropy]; // element might have changed
14696 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14697 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14699 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14700 MovDir[dropx][dropy] = drop_direction;
14702 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14704 // do not cause impact style collision by dropping elements that can fall
14705 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14708 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14709 player->is_dropping = TRUE;
14711 player->drop_pressed_delay = 0;
14712 player->is_dropping_pressed = FALSE;
14714 player->drop_x = dropx;
14715 player->drop_y = dropy;
14720 // ----------------------------------------------------------------------------
14721 // game sound playing functions
14722 // ----------------------------------------------------------------------------
14724 static int *loop_sound_frame = NULL;
14725 static int *loop_sound_volume = NULL;
14727 void InitPlayLevelSound(void)
14729 int num_sounds = getSoundListSize();
14731 checked_free(loop_sound_frame);
14732 checked_free(loop_sound_volume);
14734 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14735 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14738 static void PlayLevelSound(int x, int y, int nr)
14740 int sx = SCREENX(x), sy = SCREENY(y);
14741 int volume, stereo_position;
14742 int max_distance = 8;
14743 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14745 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14746 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14749 if (!IN_LEV_FIELD(x, y) ||
14750 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14751 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14754 volume = SOUND_MAX_VOLUME;
14756 if (!IN_SCR_FIELD(sx, sy))
14758 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14759 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14761 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14764 stereo_position = (SOUND_MAX_LEFT +
14765 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14766 (SCR_FIELDX + 2 * max_distance));
14768 if (IS_LOOP_SOUND(nr))
14770 /* This assures that quieter loop sounds do not overwrite louder ones,
14771 while restarting sound volume comparison with each new game frame. */
14773 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14776 loop_sound_volume[nr] = volume;
14777 loop_sound_frame[nr] = FrameCounter;
14780 PlaySoundExt(nr, volume, stereo_position, type);
14783 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14785 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14786 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14787 y < LEVELY(BY1) ? LEVELY(BY1) :
14788 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14792 static void PlayLevelSoundAction(int x, int y, int action)
14794 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14797 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14799 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14801 if (sound_effect != SND_UNDEFINED)
14802 PlayLevelSound(x, y, sound_effect);
14805 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14808 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14810 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14811 PlayLevelSound(x, y, sound_effect);
14814 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14816 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14818 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14819 PlayLevelSound(x, y, sound_effect);
14822 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14824 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14826 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14827 StopSound(sound_effect);
14830 static int getLevelMusicNr(void)
14832 if (levelset.music[level_nr] != MUS_UNDEFINED)
14833 return levelset.music[level_nr]; // from config file
14835 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14838 static void FadeLevelSounds(void)
14843 static void FadeLevelMusic(void)
14845 int music_nr = getLevelMusicNr();
14846 char *curr_music = getCurrentlyPlayingMusicFilename();
14847 char *next_music = getMusicInfoEntryFilename(music_nr);
14849 if (!strEqual(curr_music, next_music))
14853 void FadeLevelSoundsAndMusic(void)
14859 static void PlayLevelMusic(void)
14861 int music_nr = getLevelMusicNr();
14862 char *curr_music = getCurrentlyPlayingMusicFilename();
14863 char *next_music = getMusicInfoEntryFilename(music_nr);
14865 if (!strEqual(curr_music, next_music))
14866 PlayMusicLoop(music_nr);
14869 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14871 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14873 int x = xx - offset;
14874 int y = yy - offset;
14879 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14883 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14887 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14891 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14895 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14899 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14903 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14906 case SOUND_android_clone:
14907 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14910 case SOUND_android_move:
14911 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14915 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14919 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14923 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14926 case SOUND_eater_eat:
14927 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14931 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14934 case SOUND_collect:
14935 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14938 case SOUND_diamond:
14939 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14943 // !!! CHECK THIS !!!
14945 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14947 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14951 case SOUND_wonderfall:
14952 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14956 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14960 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14964 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14968 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14972 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14976 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14980 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14984 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14987 case SOUND_exit_open:
14988 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14991 case SOUND_exit_leave:
14992 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14995 case SOUND_dynamite:
14996 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15000 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15004 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15008 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15012 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15016 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15020 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15024 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15029 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15031 int element = map_element_SP_to_RND(element_sp);
15032 int action = map_action_SP_to_RND(action_sp);
15033 int offset = (setup.sp_show_border_elements ? 0 : 1);
15034 int x = xx - offset;
15035 int y = yy - offset;
15037 PlayLevelSoundElementAction(x, y, element, action);
15040 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15042 int element = map_element_MM_to_RND(element_mm);
15043 int action = map_action_MM_to_RND(action_mm);
15045 int x = xx - offset;
15046 int y = yy - offset;
15048 if (!IS_MM_ELEMENT(element))
15049 element = EL_MM_DEFAULT;
15051 PlayLevelSoundElementAction(x, y, element, action);
15054 void PlaySound_MM(int sound_mm)
15056 int sound = map_sound_MM_to_RND(sound_mm);
15058 if (sound == SND_UNDEFINED)
15064 void PlaySoundLoop_MM(int sound_mm)
15066 int sound = map_sound_MM_to_RND(sound_mm);
15068 if (sound == SND_UNDEFINED)
15071 PlaySoundLoop(sound);
15074 void StopSound_MM(int sound_mm)
15076 int sound = map_sound_MM_to_RND(sound_mm);
15078 if (sound == SND_UNDEFINED)
15084 void RaiseScore(int value)
15086 game.score += value;
15088 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15090 DisplayGameControlValues();
15093 void RaiseScoreElement(int element)
15098 case EL_BD_DIAMOND:
15099 case EL_EMERALD_YELLOW:
15100 case EL_EMERALD_RED:
15101 case EL_EMERALD_PURPLE:
15102 case EL_SP_INFOTRON:
15103 RaiseScore(level.score[SC_EMERALD]);
15106 RaiseScore(level.score[SC_DIAMOND]);
15109 RaiseScore(level.score[SC_CRYSTAL]);
15112 RaiseScore(level.score[SC_PEARL]);
15115 case EL_BD_BUTTERFLY:
15116 case EL_SP_ELECTRON:
15117 RaiseScore(level.score[SC_BUG]);
15120 case EL_BD_FIREFLY:
15121 case EL_SP_SNIKSNAK:
15122 RaiseScore(level.score[SC_SPACESHIP]);
15125 case EL_DARK_YAMYAM:
15126 RaiseScore(level.score[SC_YAMYAM]);
15129 RaiseScore(level.score[SC_ROBOT]);
15132 RaiseScore(level.score[SC_PACMAN]);
15135 RaiseScore(level.score[SC_NUT]);
15138 case EL_EM_DYNAMITE:
15139 case EL_SP_DISK_RED:
15140 case EL_DYNABOMB_INCREASE_NUMBER:
15141 case EL_DYNABOMB_INCREASE_SIZE:
15142 case EL_DYNABOMB_INCREASE_POWER:
15143 RaiseScore(level.score[SC_DYNAMITE]);
15145 case EL_SHIELD_NORMAL:
15146 case EL_SHIELD_DEADLY:
15147 RaiseScore(level.score[SC_SHIELD]);
15149 case EL_EXTRA_TIME:
15150 RaiseScore(level.extra_time_score);
15164 case EL_DC_KEY_WHITE:
15165 RaiseScore(level.score[SC_KEY]);
15168 RaiseScore(element_info[element].collect_score);
15173 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15175 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15177 // closing door required in case of envelope style request dialogs
15180 // prevent short reactivation of overlay buttons while closing door
15181 SetOverlayActive(FALSE);
15183 CloseDoor(DOOR_CLOSE_1);
15186 if (network.enabled)
15187 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15191 FadeSkipNextFadeIn();
15193 SetGameStatus(GAME_MODE_MAIN);
15198 else // continue playing the game
15200 if (tape.playing && tape.deactivate_display)
15201 TapeDeactivateDisplayOff(TRUE);
15203 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15205 if (tape.playing && tape.deactivate_display)
15206 TapeDeactivateDisplayOn();
15210 void RequestQuitGame(boolean ask_if_really_quit)
15212 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15213 boolean skip_request = game.all_players_gone || quick_quit;
15215 RequestQuitGameExt(skip_request, quick_quit,
15216 "Do you really want to quit the game?");
15219 void RequestRestartGame(char *message)
15221 game.restart_game_message = NULL;
15223 boolean has_started_game = hasStartedNetworkGame();
15224 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15226 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15228 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15232 SetGameStatus(GAME_MODE_MAIN);
15238 void CheckGameOver(void)
15240 static boolean last_game_over = FALSE;
15241 static int game_over_delay = 0;
15242 int game_over_delay_value = 50;
15243 boolean game_over = checkGameFailed();
15245 // do not handle game over if request dialog is already active
15246 if (game.request_active)
15249 // do not ask to play again if game was never actually played
15250 if (!game.GamePlayed)
15255 last_game_over = FALSE;
15256 game_over_delay = game_over_delay_value;
15261 if (game_over_delay > 0)
15268 if (last_game_over != game_over)
15269 game.restart_game_message = (hasStartedNetworkGame() ?
15270 "Game over! Play it again?" :
15273 last_game_over = game_over;
15276 boolean checkGameSolved(void)
15278 // set for all game engines if level was solved
15279 return game.LevelSolved_GameEnd;
15282 boolean checkGameFailed(void)
15284 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15285 return (game_em.game_over && !game_em.level_solved);
15286 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15287 return (game_sp.game_over && !game_sp.level_solved);
15288 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15289 return (game_mm.game_over && !game_mm.level_solved);
15290 else // GAME_ENGINE_TYPE_RND
15291 return (game.GameOver && !game.LevelSolved);
15294 boolean checkGameEnded(void)
15296 return (checkGameSolved() || checkGameFailed());
15300 // ----------------------------------------------------------------------------
15301 // random generator functions
15302 // ----------------------------------------------------------------------------
15304 unsigned int InitEngineRandom_RND(int seed)
15306 game.num_random_calls = 0;
15308 return InitEngineRandom(seed);
15311 unsigned int RND(int max)
15315 game.num_random_calls++;
15317 return GetEngineRandom(max);
15324 // ----------------------------------------------------------------------------
15325 // game engine snapshot handling functions
15326 // ----------------------------------------------------------------------------
15328 struct EngineSnapshotInfo
15330 // runtime values for custom element collect score
15331 int collect_score[NUM_CUSTOM_ELEMENTS];
15333 // runtime values for group element choice position
15334 int choice_pos[NUM_GROUP_ELEMENTS];
15336 // runtime values for belt position animations
15337 int belt_graphic[4][NUM_BELT_PARTS];
15338 int belt_anim_mode[4][NUM_BELT_PARTS];
15341 static struct EngineSnapshotInfo engine_snapshot_rnd;
15342 static char *snapshot_level_identifier = NULL;
15343 static int snapshot_level_nr = -1;
15345 static void SaveEngineSnapshotValues_RND(void)
15347 static int belt_base_active_element[4] =
15349 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15350 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15351 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15352 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15356 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15358 int element = EL_CUSTOM_START + i;
15360 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15363 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15365 int element = EL_GROUP_START + i;
15367 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15370 for (i = 0; i < 4; i++)
15372 for (j = 0; j < NUM_BELT_PARTS; j++)
15374 int element = belt_base_active_element[i] + j;
15375 int graphic = el2img(element);
15376 int anim_mode = graphic_info[graphic].anim_mode;
15378 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15379 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15384 static void LoadEngineSnapshotValues_RND(void)
15386 unsigned int num_random_calls = game.num_random_calls;
15389 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15391 int element = EL_CUSTOM_START + i;
15393 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15396 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15398 int element = EL_GROUP_START + i;
15400 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15403 for (i = 0; i < 4; i++)
15405 for (j = 0; j < NUM_BELT_PARTS; j++)
15407 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15408 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15410 graphic_info[graphic].anim_mode = anim_mode;
15414 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15416 InitRND(tape.random_seed);
15417 for (i = 0; i < num_random_calls; i++)
15421 if (game.num_random_calls != num_random_calls)
15423 Error(ERR_INFO, "number of random calls out of sync");
15424 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15425 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15426 Error(ERR_EXIT, "this should not happen -- please debug");
15430 void FreeEngineSnapshotSingle(void)
15432 FreeSnapshotSingle();
15434 setString(&snapshot_level_identifier, NULL);
15435 snapshot_level_nr = -1;
15438 void FreeEngineSnapshotList(void)
15440 FreeSnapshotList();
15443 static ListNode *SaveEngineSnapshotBuffers(void)
15445 ListNode *buffers = NULL;
15447 // copy some special values to a structure better suited for the snapshot
15449 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15450 SaveEngineSnapshotValues_RND();
15451 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15452 SaveEngineSnapshotValues_EM();
15453 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15454 SaveEngineSnapshotValues_SP(&buffers);
15455 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15456 SaveEngineSnapshotValues_MM(&buffers);
15458 // save values stored in special snapshot structure
15460 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15461 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15462 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15463 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15464 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15465 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15466 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15467 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15469 // save further RND engine values
15471 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15472 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15473 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15475 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15476 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15477 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15478 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15479 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15481 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15482 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15483 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15485 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15487 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15488 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15490 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15491 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15492 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15493 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15494 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15495 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15496 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15497 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15498 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15499 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15500 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15501 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15502 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15503 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15504 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15505 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15506 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15507 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15509 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15510 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15512 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15513 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15514 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15516 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15517 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15519 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15520 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15521 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15522 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15523 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15525 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15526 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15529 ListNode *node = engine_snapshot_list_rnd;
15532 while (node != NULL)
15534 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15539 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15545 void SaveEngineSnapshotSingle(void)
15547 ListNode *buffers = SaveEngineSnapshotBuffers();
15549 // finally save all snapshot buffers to single snapshot
15550 SaveSnapshotSingle(buffers);
15552 // save level identification information
15553 setString(&snapshot_level_identifier, leveldir_current->identifier);
15554 snapshot_level_nr = level_nr;
15557 boolean CheckSaveEngineSnapshotToList(void)
15559 boolean save_snapshot =
15560 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15561 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15562 game.snapshot.changed_action) ||
15563 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15564 game.snapshot.collected_item));
15566 game.snapshot.changed_action = FALSE;
15567 game.snapshot.collected_item = FALSE;
15568 game.snapshot.save_snapshot = save_snapshot;
15570 return save_snapshot;
15573 void SaveEngineSnapshotToList(void)
15575 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15579 ListNode *buffers = SaveEngineSnapshotBuffers();
15581 // finally save all snapshot buffers to snapshot list
15582 SaveSnapshotToList(buffers);
15585 void SaveEngineSnapshotToListInitial(void)
15587 FreeEngineSnapshotList();
15589 SaveEngineSnapshotToList();
15592 static void LoadEngineSnapshotValues(void)
15594 // restore special values from snapshot structure
15596 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15597 LoadEngineSnapshotValues_RND();
15598 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15599 LoadEngineSnapshotValues_EM();
15600 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15601 LoadEngineSnapshotValues_SP();
15602 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15603 LoadEngineSnapshotValues_MM();
15606 void LoadEngineSnapshotSingle(void)
15608 LoadSnapshotSingle();
15610 LoadEngineSnapshotValues();
15613 static void LoadEngineSnapshot_Undo(int steps)
15615 LoadSnapshotFromList_Older(steps);
15617 LoadEngineSnapshotValues();
15620 static void LoadEngineSnapshot_Redo(int steps)
15622 LoadSnapshotFromList_Newer(steps);
15624 LoadEngineSnapshotValues();
15627 boolean CheckEngineSnapshotSingle(void)
15629 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15630 snapshot_level_nr == level_nr);
15633 boolean CheckEngineSnapshotList(void)
15635 return CheckSnapshotList();
15639 // ---------- new game button stuff -------------------------------------------
15646 boolean *setup_value;
15647 boolean allowed_on_tape;
15648 boolean is_touch_button;
15650 } gamebutton_info[NUM_GAME_BUTTONS] =
15653 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15654 GAME_CTRL_ID_STOP, NULL,
15655 TRUE, FALSE, "stop game"
15658 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15659 GAME_CTRL_ID_PAUSE, NULL,
15660 TRUE, FALSE, "pause game"
15663 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15664 GAME_CTRL_ID_PLAY, NULL,
15665 TRUE, FALSE, "play game"
15668 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15669 GAME_CTRL_ID_UNDO, NULL,
15670 TRUE, FALSE, "undo step"
15673 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15674 GAME_CTRL_ID_REDO, NULL,
15675 TRUE, FALSE, "redo step"
15678 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15679 GAME_CTRL_ID_SAVE, NULL,
15680 TRUE, FALSE, "save game"
15683 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15684 GAME_CTRL_ID_PAUSE2, NULL,
15685 TRUE, FALSE, "pause game"
15688 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15689 GAME_CTRL_ID_LOAD, NULL,
15690 TRUE, FALSE, "load game"
15693 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15694 GAME_CTRL_ID_PANEL_STOP, NULL,
15695 FALSE, FALSE, "stop game"
15698 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15699 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15700 FALSE, FALSE, "pause game"
15703 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15704 GAME_CTRL_ID_PANEL_PLAY, NULL,
15705 FALSE, FALSE, "play game"
15708 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15709 GAME_CTRL_ID_TOUCH_STOP, NULL,
15710 FALSE, TRUE, "stop game"
15713 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15714 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15715 FALSE, TRUE, "pause game"
15718 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15719 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15720 TRUE, FALSE, "background music on/off"
15723 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15724 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15725 TRUE, FALSE, "sound loops on/off"
15728 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15729 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15730 TRUE, FALSE, "normal sounds on/off"
15733 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15734 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15735 FALSE, FALSE, "background music on/off"
15738 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15739 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15740 FALSE, FALSE, "sound loops on/off"
15743 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15744 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15745 FALSE, FALSE, "normal sounds on/off"
15749 void CreateGameButtons(void)
15753 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15755 int graphic = gamebutton_info[i].graphic;
15756 struct GraphicInfo *gfx = &graphic_info[graphic];
15757 struct XY *pos = gamebutton_info[i].pos;
15758 struct GadgetInfo *gi;
15761 unsigned int event_mask;
15762 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15763 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15764 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15765 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15766 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15767 int gd_x = gfx->src_x;
15768 int gd_y = gfx->src_y;
15769 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15770 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15771 int gd_xa = gfx->src_x + gfx->active_xoffset;
15772 int gd_ya = gfx->src_y + gfx->active_yoffset;
15773 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15774 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15775 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15776 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15779 if (gfx->bitmap == NULL)
15781 game_gadget[id] = NULL;
15786 if (id == GAME_CTRL_ID_STOP ||
15787 id == GAME_CTRL_ID_PANEL_STOP ||
15788 id == GAME_CTRL_ID_TOUCH_STOP ||
15789 id == GAME_CTRL_ID_PLAY ||
15790 id == GAME_CTRL_ID_PANEL_PLAY ||
15791 id == GAME_CTRL_ID_SAVE ||
15792 id == GAME_CTRL_ID_LOAD)
15794 button_type = GD_TYPE_NORMAL_BUTTON;
15796 event_mask = GD_EVENT_RELEASED;
15798 else if (id == GAME_CTRL_ID_UNDO ||
15799 id == GAME_CTRL_ID_REDO)
15801 button_type = GD_TYPE_NORMAL_BUTTON;
15803 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15807 button_type = GD_TYPE_CHECK_BUTTON;
15808 checked = (gamebutton_info[i].setup_value != NULL ?
15809 *gamebutton_info[i].setup_value : FALSE);
15810 event_mask = GD_EVENT_PRESSED;
15813 gi = CreateGadget(GDI_CUSTOM_ID, id,
15814 GDI_IMAGE_ID, graphic,
15815 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15818 GDI_WIDTH, gfx->width,
15819 GDI_HEIGHT, gfx->height,
15820 GDI_TYPE, button_type,
15821 GDI_STATE, GD_BUTTON_UNPRESSED,
15822 GDI_CHECKED, checked,
15823 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15824 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15825 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15826 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15827 GDI_DIRECT_DRAW, FALSE,
15828 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15829 GDI_EVENT_MASK, event_mask,
15830 GDI_CALLBACK_ACTION, HandleGameButtons,
15834 Error(ERR_EXIT, "cannot create gadget");
15836 game_gadget[id] = gi;
15840 void FreeGameButtons(void)
15844 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15845 FreeGadget(game_gadget[i]);
15848 static void UnmapGameButtonsAtSamePosition(int id)
15852 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15854 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15855 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15856 UnmapGadget(game_gadget[i]);
15859 static void UnmapGameButtonsAtSamePosition_All(void)
15861 if (setup.show_snapshot_buttons)
15863 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15864 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15865 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15869 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15870 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15871 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15873 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15874 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15875 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15879 static void MapGameButtonsAtSamePosition(int id)
15883 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15885 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15886 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15887 MapGadget(game_gadget[i]);
15889 UnmapGameButtonsAtSamePosition_All();
15892 void MapUndoRedoButtons(void)
15894 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15895 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15897 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15898 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15901 void UnmapUndoRedoButtons(void)
15903 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15904 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15906 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15907 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15910 void ModifyPauseButtons(void)
15914 GAME_CTRL_ID_PAUSE,
15915 GAME_CTRL_ID_PAUSE2,
15916 GAME_CTRL_ID_PANEL_PAUSE,
15917 GAME_CTRL_ID_TOUCH_PAUSE,
15922 for (i = 0; ids[i] > -1; i++)
15923 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15926 static void MapGameButtonsExt(boolean on_tape)
15930 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15931 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15932 i != GAME_CTRL_ID_UNDO &&
15933 i != GAME_CTRL_ID_REDO)
15934 MapGadget(game_gadget[i]);
15936 UnmapGameButtonsAtSamePosition_All();
15938 RedrawGameButtons();
15941 static void UnmapGameButtonsExt(boolean on_tape)
15945 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15946 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15947 UnmapGadget(game_gadget[i]);
15950 static void RedrawGameButtonsExt(boolean on_tape)
15954 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15955 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15956 RedrawGadget(game_gadget[i]);
15959 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15964 gi->checked = state;
15967 static void RedrawSoundButtonGadget(int id)
15969 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15970 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15971 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15972 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15973 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15974 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15977 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15978 RedrawGadget(game_gadget[id2]);
15981 void MapGameButtons(void)
15983 MapGameButtonsExt(FALSE);
15986 void UnmapGameButtons(void)
15988 UnmapGameButtonsExt(FALSE);
15991 void RedrawGameButtons(void)
15993 RedrawGameButtonsExt(FALSE);
15996 void MapGameButtonsOnTape(void)
15998 MapGameButtonsExt(TRUE);
16001 void UnmapGameButtonsOnTape(void)
16003 UnmapGameButtonsExt(TRUE);
16006 void RedrawGameButtonsOnTape(void)
16008 RedrawGameButtonsExt(TRUE);
16011 static void GameUndoRedoExt(void)
16013 ClearPlayerAction();
16015 tape.pausing = TRUE;
16018 UpdateAndDisplayGameControlValues();
16020 DrawCompleteVideoDisplay();
16021 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16022 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16023 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16028 static void GameUndo(int steps)
16030 if (!CheckEngineSnapshotList())
16033 LoadEngineSnapshot_Undo(steps);
16038 static void GameRedo(int steps)
16040 if (!CheckEngineSnapshotList())
16043 LoadEngineSnapshot_Redo(steps);
16048 static void HandleGameButtonsExt(int id, int button)
16050 static boolean game_undo_executed = FALSE;
16051 int steps = BUTTON_STEPSIZE(button);
16052 boolean handle_game_buttons =
16053 (game_status == GAME_MODE_PLAYING ||
16054 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16056 if (!handle_game_buttons)
16061 case GAME_CTRL_ID_STOP:
16062 case GAME_CTRL_ID_PANEL_STOP:
16063 case GAME_CTRL_ID_TOUCH_STOP:
16064 if (game_status == GAME_MODE_MAIN)
16070 RequestQuitGame(TRUE);
16074 case GAME_CTRL_ID_PAUSE:
16075 case GAME_CTRL_ID_PAUSE2:
16076 case GAME_CTRL_ID_PANEL_PAUSE:
16077 case GAME_CTRL_ID_TOUCH_PAUSE:
16078 if (network.enabled && game_status == GAME_MODE_PLAYING)
16081 SendToServer_ContinuePlaying();
16083 SendToServer_PausePlaying();
16086 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16088 game_undo_executed = FALSE;
16092 case GAME_CTRL_ID_PLAY:
16093 case GAME_CTRL_ID_PANEL_PLAY:
16094 if (game_status == GAME_MODE_MAIN)
16096 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16098 else if (tape.pausing)
16100 if (network.enabled)
16101 SendToServer_ContinuePlaying();
16103 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16107 case GAME_CTRL_ID_UNDO:
16108 // Important: When using "save snapshot when collecting an item" mode,
16109 // load last (current) snapshot for first "undo" after pressing "pause"
16110 // (else the last-but-one snapshot would be loaded, because the snapshot
16111 // pointer already points to the last snapshot when pressing "pause",
16112 // which is fine for "every step/move" mode, but not for "every collect")
16113 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16114 !game_undo_executed)
16117 game_undo_executed = TRUE;
16122 case GAME_CTRL_ID_REDO:
16126 case GAME_CTRL_ID_SAVE:
16130 case GAME_CTRL_ID_LOAD:
16134 case SOUND_CTRL_ID_MUSIC:
16135 case SOUND_CTRL_ID_PANEL_MUSIC:
16136 if (setup.sound_music)
16138 setup.sound_music = FALSE;
16142 else if (audio.music_available)
16144 setup.sound = setup.sound_music = TRUE;
16146 SetAudioMode(setup.sound);
16148 if (game_status == GAME_MODE_PLAYING)
16152 RedrawSoundButtonGadget(id);
16156 case SOUND_CTRL_ID_LOOPS:
16157 case SOUND_CTRL_ID_PANEL_LOOPS:
16158 if (setup.sound_loops)
16159 setup.sound_loops = FALSE;
16160 else if (audio.loops_available)
16162 setup.sound = setup.sound_loops = TRUE;
16164 SetAudioMode(setup.sound);
16167 RedrawSoundButtonGadget(id);
16171 case SOUND_CTRL_ID_SIMPLE:
16172 case SOUND_CTRL_ID_PANEL_SIMPLE:
16173 if (setup.sound_simple)
16174 setup.sound_simple = FALSE;
16175 else if (audio.sound_available)
16177 setup.sound = setup.sound_simple = TRUE;
16179 SetAudioMode(setup.sound);
16182 RedrawSoundButtonGadget(id);
16191 static void HandleGameButtons(struct GadgetInfo *gi)
16193 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16196 void HandleSoundButtonKeys(Key key)
16198 if (key == setup.shortcut.sound_simple)
16199 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16200 else if (key == setup.shortcut.sound_loops)
16201 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16202 else if (key == setup.shortcut.sound_music)
16203 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);