1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://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.1.4.2.
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,1,4,1)) ||
2889 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2890 tape.game_version <= VERSION_IDENT(4,1,4,1)));
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.1.4.2, 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,1,4,2)));
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_old_explosions =
2994 (game.engine_version < VERSION_IDENT(4,1,4,2));
2996 game_em.use_old_android =
2997 (game.engine_version < VERSION_IDENT(4,1,4,2));
2999 game_em.use_old_push_elements =
3000 (game.engine_version < VERSION_IDENT(4,1,4,2));
3002 game_em.use_old_push_into_acid =
3003 (game.engine_version < VERSION_IDENT(4,1,4,2));
3005 game_em.use_wrap_around =
3006 (game.engine_version > VERSION_IDENT(4,1,4,1));
3008 // --------------------------------------------------------------------------
3010 // set maximal allowed number of custom element changes per game frame
3011 game.max_num_changes_per_frame = 1;
3013 // default scan direction: scan playfield from top/left to bottom/right
3014 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3016 // dynamically adjust element properties according to game engine version
3017 InitElementPropertiesEngine(game.engine_version);
3019 // ---------- initialize special element properties -------------------------
3021 // "EL_AMOEBA_DROPPING" missed property "can fall" between 2.0.1 and 4.1.4.1
3022 if (use_amoeba_dropping_cannot_fall_bug)
3023 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3025 // ---------- initialize player's initial move delay ------------------------
3027 // dynamically adjust player properties according to level information
3028 for (i = 0; i < MAX_PLAYERS; i++)
3029 game.initial_move_delay_value[i] =
3030 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3032 // dynamically adjust player properties according to game engine version
3033 for (i = 0; i < MAX_PLAYERS; i++)
3034 game.initial_move_delay[i] =
3035 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3036 game.initial_move_delay_value[i] : 0);
3038 // ---------- initialize player's initial push delay ------------------------
3040 // dynamically adjust player properties according to game engine version
3041 game.initial_push_delay_value =
3042 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3044 // ---------- initialize changing elements ----------------------------------
3046 // initialize changing elements information
3047 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3049 struct ElementInfo *ei = &element_info[i];
3051 // this pointer might have been changed in the level editor
3052 ei->change = &ei->change_page[0];
3054 if (!IS_CUSTOM_ELEMENT(i))
3056 ei->change->target_element = EL_EMPTY_SPACE;
3057 ei->change->delay_fixed = 0;
3058 ei->change->delay_random = 0;
3059 ei->change->delay_frames = 1;
3062 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3064 ei->has_change_event[j] = FALSE;
3066 ei->event_page_nr[j] = 0;
3067 ei->event_page[j] = &ei->change_page[0];
3071 // add changing elements from pre-defined list
3072 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3074 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3075 struct ElementInfo *ei = &element_info[ch_delay->element];
3077 ei->change->target_element = ch_delay->target_element;
3078 ei->change->delay_fixed = ch_delay->change_delay;
3080 ei->change->pre_change_function = ch_delay->pre_change_function;
3081 ei->change->change_function = ch_delay->change_function;
3082 ei->change->post_change_function = ch_delay->post_change_function;
3084 ei->change->can_change = TRUE;
3085 ei->change->can_change_or_has_action = TRUE;
3087 ei->has_change_event[CE_DELAY] = TRUE;
3089 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3090 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3093 // ---------- initialize internal run-time variables ------------------------
3095 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3097 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3099 for (j = 0; j < ei->num_change_pages; j++)
3101 ei->change_page[j].can_change_or_has_action =
3102 (ei->change_page[j].can_change |
3103 ei->change_page[j].has_action);
3107 // add change events from custom element configuration
3108 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3110 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3112 for (j = 0; j < ei->num_change_pages; j++)
3114 if (!ei->change_page[j].can_change_or_has_action)
3117 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3119 // only add event page for the first page found with this event
3120 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3122 ei->has_change_event[k] = TRUE;
3124 ei->event_page_nr[k] = j;
3125 ei->event_page[k] = &ei->change_page[j];
3131 // ---------- initialize reference elements in change conditions ------------
3133 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3135 int element = EL_CUSTOM_START + i;
3136 struct ElementInfo *ei = &element_info[element];
3138 for (j = 0; j < ei->num_change_pages; j++)
3140 int trigger_element = ei->change_page[j].initial_trigger_element;
3142 if (trigger_element >= EL_PREV_CE_8 &&
3143 trigger_element <= EL_NEXT_CE_8)
3144 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3146 ei->change_page[j].trigger_element = trigger_element;
3150 // ---------- initialize run-time trigger player and element ----------------
3152 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3154 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3156 for (j = 0; j < ei->num_change_pages; j++)
3158 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3159 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3160 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3161 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3162 ei->change_page[j].actual_trigger_ce_value = 0;
3163 ei->change_page[j].actual_trigger_ce_score = 0;
3167 // ---------- initialize trigger events -------------------------------------
3169 // initialize trigger events information
3170 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3171 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3172 trigger_events[i][j] = FALSE;
3174 // add trigger events from element change event properties
3175 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3177 struct ElementInfo *ei = &element_info[i];
3179 for (j = 0; j < ei->num_change_pages; j++)
3181 if (!ei->change_page[j].can_change_or_has_action)
3184 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3186 int trigger_element = ei->change_page[j].trigger_element;
3188 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3190 if (ei->change_page[j].has_event[k])
3192 if (IS_GROUP_ELEMENT(trigger_element))
3194 struct ElementGroupInfo *group =
3195 element_info[trigger_element].group;
3197 for (l = 0; l < group->num_elements_resolved; l++)
3198 trigger_events[group->element_resolved[l]][k] = TRUE;
3200 else if (trigger_element == EL_ANY_ELEMENT)
3201 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3202 trigger_events[l][k] = TRUE;
3204 trigger_events[trigger_element][k] = TRUE;
3211 // ---------- initialize push delay -----------------------------------------
3213 // initialize push delay values to default
3214 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3216 if (!IS_CUSTOM_ELEMENT(i))
3218 // set default push delay values (corrected since version 3.0.7-1)
3219 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3221 element_info[i].push_delay_fixed = 2;
3222 element_info[i].push_delay_random = 8;
3226 element_info[i].push_delay_fixed = 8;
3227 element_info[i].push_delay_random = 8;
3232 // set push delay value for certain elements from pre-defined list
3233 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3235 int e = push_delay_list[i].element;
3237 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3238 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3241 // set push delay value for Supaplex elements for newer engine versions
3242 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3244 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3246 if (IS_SP_ELEMENT(i))
3248 // set SP push delay to just enough to push under a falling zonk
3249 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3251 element_info[i].push_delay_fixed = delay;
3252 element_info[i].push_delay_random = 0;
3257 // ---------- initialize move stepsize --------------------------------------
3259 // initialize move stepsize values to default
3260 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3261 if (!IS_CUSTOM_ELEMENT(i))
3262 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3264 // set move stepsize value for certain elements from pre-defined list
3265 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3267 int e = move_stepsize_list[i].element;
3269 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3271 // set move stepsize value for certain elements for older engine versions
3272 if (use_old_move_stepsize_for_magic_wall)
3274 if (e == EL_MAGIC_WALL_FILLING ||
3275 e == EL_MAGIC_WALL_EMPTYING ||
3276 e == EL_BD_MAGIC_WALL_FILLING ||
3277 e == EL_BD_MAGIC_WALL_EMPTYING)
3278 element_info[e].move_stepsize *= 2;
3282 // ---------- initialize collect score --------------------------------------
3284 // initialize collect score values for custom elements from initial value
3285 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3286 if (IS_CUSTOM_ELEMENT(i))
3287 element_info[i].collect_score = element_info[i].collect_score_initial;
3289 // ---------- initialize collect count --------------------------------------
3291 // initialize collect count values for non-custom elements
3292 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3293 if (!IS_CUSTOM_ELEMENT(i))
3294 element_info[i].collect_count_initial = 0;
3296 // add collect count values for all elements from pre-defined list
3297 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3298 element_info[collect_count_list[i].element].collect_count_initial =
3299 collect_count_list[i].count;
3301 // ---------- initialize access direction -----------------------------------
3303 // initialize access direction values to default (access from every side)
3304 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3305 if (!IS_CUSTOM_ELEMENT(i))
3306 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3308 // set access direction value for certain elements from pre-defined list
3309 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3310 element_info[access_direction_list[i].element].access_direction =
3311 access_direction_list[i].direction;
3313 // ---------- initialize explosion content ----------------------------------
3314 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3316 if (IS_CUSTOM_ELEMENT(i))
3319 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3321 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3323 element_info[i].content.e[x][y] =
3324 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3325 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3326 i == EL_PLAYER_3 ? EL_EMERALD :
3327 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3328 i == EL_MOLE ? EL_EMERALD_RED :
3329 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3330 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3331 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3332 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3333 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3334 i == EL_WALL_EMERALD ? EL_EMERALD :
3335 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3336 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3337 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3338 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3339 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3340 i == EL_WALL_PEARL ? EL_PEARL :
3341 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3346 // ---------- initialize recursion detection --------------------------------
3347 recursion_loop_depth = 0;
3348 recursion_loop_detected = FALSE;
3349 recursion_loop_element = EL_UNDEFINED;
3351 // ---------- initialize graphics engine ------------------------------------
3352 game.scroll_delay_value =
3353 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3354 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3355 !setup.forced_scroll_delay ? 0 :
3356 setup.scroll_delay ? setup.scroll_delay_value : 0);
3357 game.scroll_delay_value =
3358 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3360 // ---------- initialize game engine snapshots ------------------------------
3361 for (i = 0; i < MAX_PLAYERS; i++)
3362 game.snapshot.last_action[i] = 0;
3363 game.snapshot.changed_action = FALSE;
3364 game.snapshot.collected_item = FALSE;
3365 game.snapshot.mode =
3366 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3367 SNAPSHOT_MODE_EVERY_STEP :
3368 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3369 SNAPSHOT_MODE_EVERY_MOVE :
3370 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3371 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3372 game.snapshot.save_snapshot = FALSE;
3374 // ---------- initialize level time for Supaplex engine ---------------------
3375 // Supaplex levels with time limit currently unsupported -- should be added
3376 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3379 // ---------- initialize flags for handling game actions --------------------
3381 // set flags for game actions to default values
3382 game.use_key_actions = TRUE;
3383 game.use_mouse_actions = FALSE;
3385 // when using Mirror Magic game engine, handle mouse events only
3386 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3388 game.use_key_actions = FALSE;
3389 game.use_mouse_actions = TRUE;
3392 // check for custom elements with mouse click events
3393 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3395 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3397 int element = EL_CUSTOM_START + i;
3399 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3400 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3401 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3402 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3403 game.use_mouse_actions = TRUE;
3408 static int get_num_special_action(int element, int action_first,
3411 int num_special_action = 0;
3414 for (i = action_first; i <= action_last; i++)
3416 boolean found = FALSE;
3418 for (j = 0; j < NUM_DIRECTIONS; j++)
3419 if (el_act_dir2img(element, i, j) !=
3420 el_act_dir2img(element, ACTION_DEFAULT, j))
3424 num_special_action++;
3429 return num_special_action;
3433 // ============================================================================
3435 // ----------------------------------------------------------------------------
3436 // initialize and start new game
3437 // ============================================================================
3439 #if DEBUG_INIT_PLAYER
3440 static void DebugPrintPlayerStatus(char *message)
3447 printf("%s:\n", message);
3449 for (i = 0; i < MAX_PLAYERS; i++)
3451 struct PlayerInfo *player = &stored_player[i];
3453 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3457 player->connected_locally,
3458 player->connected_network,
3461 if (local_player == player)
3462 printf(" (local player)");
3471 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3472 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3473 int fade_mask = REDRAW_FIELD;
3475 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3476 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3477 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3478 int initial_move_dir = MV_DOWN;
3481 // required here to update video display before fading (FIX THIS)
3482 DrawMaskedBorder(REDRAW_DOOR_2);
3484 if (!game.restart_level)
3485 CloseDoor(DOOR_CLOSE_1);
3487 SetGameStatus(GAME_MODE_PLAYING);
3489 if (level_editor_test_game)
3490 FadeSkipNextFadeOut();
3492 FadeSetEnterScreen();
3495 fade_mask = REDRAW_ALL;
3497 FadeLevelSoundsAndMusic();
3499 ExpireSoundLoops(TRUE);
3503 if (level_editor_test_game)
3504 FadeSkipNextFadeIn();
3506 // needed if different viewport properties defined for playing
3507 ChangeViewportPropertiesIfNeeded();
3511 DrawCompleteVideoDisplay();
3513 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3516 InitGameControlValues();
3518 // initialize tape actions from game when recording tape
3521 tape.use_key_actions = game.use_key_actions;
3522 tape.use_mouse_actions = game.use_mouse_actions;
3525 // don't play tapes over network
3526 network_playing = (network.enabled && !tape.playing);
3528 for (i = 0; i < MAX_PLAYERS; i++)
3530 struct PlayerInfo *player = &stored_player[i];
3532 player->index_nr = i;
3533 player->index_bit = (1 << i);
3534 player->element_nr = EL_PLAYER_1 + i;
3536 player->present = FALSE;
3537 player->active = FALSE;
3538 player->mapped = FALSE;
3540 player->killed = FALSE;
3541 player->reanimated = FALSE;
3542 player->buried = FALSE;
3545 player->effective_action = 0;
3546 player->programmed_action = 0;
3547 player->snap_action = 0;
3549 player->mouse_action.lx = 0;
3550 player->mouse_action.ly = 0;
3551 player->mouse_action.button = 0;
3552 player->mouse_action.button_hint = 0;
3554 player->effective_mouse_action.lx = 0;
3555 player->effective_mouse_action.ly = 0;
3556 player->effective_mouse_action.button = 0;
3557 player->effective_mouse_action.button_hint = 0;
3559 for (j = 0; j < MAX_NUM_KEYS; j++)
3560 player->key[j] = FALSE;
3562 player->num_white_keys = 0;
3564 player->dynabomb_count = 0;
3565 player->dynabomb_size = 1;
3566 player->dynabombs_left = 0;
3567 player->dynabomb_xl = FALSE;
3569 player->MovDir = initial_move_dir;
3572 player->GfxDir = initial_move_dir;
3573 player->GfxAction = ACTION_DEFAULT;
3575 player->StepFrame = 0;
3577 player->initial_element = player->element_nr;
3578 player->artwork_element =
3579 (level.use_artwork_element[i] ? level.artwork_element[i] :
3580 player->element_nr);
3581 player->use_murphy = FALSE;
3583 player->block_last_field = FALSE; // initialized in InitPlayerField()
3584 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3586 player->gravity = level.initial_player_gravity[i];
3588 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3590 player->actual_frame_counter = 0;
3592 player->step_counter = 0;
3594 player->last_move_dir = initial_move_dir;
3596 player->is_active = FALSE;
3598 player->is_waiting = FALSE;
3599 player->is_moving = FALSE;
3600 player->is_auto_moving = FALSE;
3601 player->is_digging = FALSE;
3602 player->is_snapping = FALSE;
3603 player->is_collecting = FALSE;
3604 player->is_pushing = FALSE;
3605 player->is_switching = FALSE;
3606 player->is_dropping = FALSE;
3607 player->is_dropping_pressed = FALSE;
3609 player->is_bored = FALSE;
3610 player->is_sleeping = FALSE;
3612 player->was_waiting = TRUE;
3613 player->was_moving = FALSE;
3614 player->was_snapping = FALSE;
3615 player->was_dropping = FALSE;
3617 player->force_dropping = FALSE;
3619 player->frame_counter_bored = -1;
3620 player->frame_counter_sleeping = -1;
3622 player->anim_delay_counter = 0;
3623 player->post_delay_counter = 0;
3625 player->dir_waiting = initial_move_dir;
3626 player->action_waiting = ACTION_DEFAULT;
3627 player->last_action_waiting = ACTION_DEFAULT;
3628 player->special_action_bored = ACTION_DEFAULT;
3629 player->special_action_sleeping = ACTION_DEFAULT;
3631 player->switch_x = -1;
3632 player->switch_y = -1;
3634 player->drop_x = -1;
3635 player->drop_y = -1;
3637 player->show_envelope = 0;
3639 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3641 player->push_delay = -1; // initialized when pushing starts
3642 player->push_delay_value = game.initial_push_delay_value;
3644 player->drop_delay = 0;
3645 player->drop_pressed_delay = 0;
3647 player->last_jx = -1;
3648 player->last_jy = -1;
3652 player->shield_normal_time_left = 0;
3653 player->shield_deadly_time_left = 0;
3655 player->inventory_infinite_element = EL_UNDEFINED;
3656 player->inventory_size = 0;
3658 if (level.use_initial_inventory[i])
3660 for (j = 0; j < level.initial_inventory_size[i]; j++)
3662 int element = level.initial_inventory_content[i][j];
3663 int collect_count = element_info[element].collect_count_initial;
3666 if (!IS_CUSTOM_ELEMENT(element))
3669 if (collect_count == 0)
3670 player->inventory_infinite_element = element;
3672 for (k = 0; k < collect_count; k++)
3673 if (player->inventory_size < MAX_INVENTORY_SIZE)
3674 player->inventory_element[player->inventory_size++] = element;
3678 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3679 SnapField(player, 0, 0);
3681 map_player_action[i] = i;
3684 network_player_action_received = FALSE;
3686 // initial null action
3687 if (network_playing)
3688 SendToServer_MovePlayer(MV_NONE);
3693 TimeLeft = level.time;
3696 ScreenMovDir = MV_NONE;
3700 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3702 game.robot_wheel_x = -1;
3703 game.robot_wheel_y = -1;
3708 game.all_players_gone = FALSE;
3710 game.LevelSolved = FALSE;
3711 game.GameOver = FALSE;
3713 game.GamePlayed = !tape.playing;
3715 game.LevelSolved_GameWon = FALSE;
3716 game.LevelSolved_GameEnd = FALSE;
3717 game.LevelSolved_SaveTape = FALSE;
3718 game.LevelSolved_SaveScore = FALSE;
3720 game.LevelSolved_CountingTime = 0;
3721 game.LevelSolved_CountingScore = 0;
3722 game.LevelSolved_CountingHealth = 0;
3724 game.panel.active = TRUE;
3726 game.no_time_limit = (level.time == 0);
3728 game.yamyam_content_nr = 0;
3729 game.robot_wheel_active = FALSE;
3730 game.magic_wall_active = FALSE;
3731 game.magic_wall_time_left = 0;
3732 game.light_time_left = 0;
3733 game.timegate_time_left = 0;
3734 game.switchgate_pos = 0;
3735 game.wind_direction = level.wind_direction_initial;
3738 game.score_final = 0;
3740 game.health = MAX_HEALTH;
3741 game.health_final = MAX_HEALTH;
3743 game.gems_still_needed = level.gems_needed;
3744 game.sokoban_fields_still_needed = 0;
3745 game.sokoban_objects_still_needed = 0;
3746 game.lights_still_needed = 0;
3747 game.players_still_needed = 0;
3748 game.friends_still_needed = 0;
3750 game.lenses_time_left = 0;
3751 game.magnify_time_left = 0;
3753 game.ball_active = level.ball_active_initial;
3754 game.ball_content_nr = 0;
3756 game.explosions_delayed = TRUE;
3758 game.envelope_active = FALSE;
3760 for (i = 0; i < NUM_BELTS; i++)
3762 game.belt_dir[i] = MV_NONE;
3763 game.belt_dir_nr[i] = 3; // not moving, next moving left
3766 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3767 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3769 #if DEBUG_INIT_PLAYER
3770 DebugPrintPlayerStatus("Player status at level initialization");
3773 SCAN_PLAYFIELD(x, y)
3775 Feld[x][y] = Last[x][y] = level.field[x][y];
3776 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3777 ChangeDelay[x][y] = 0;
3778 ChangePage[x][y] = -1;
3779 CustomValue[x][y] = 0; // initialized in InitField()
3780 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3782 WasJustMoving[x][y] = 0;
3783 WasJustFalling[x][y] = 0;
3784 CheckCollision[x][y] = 0;
3785 CheckImpact[x][y] = 0;
3787 Pushed[x][y] = FALSE;
3789 ChangeCount[x][y] = 0;
3790 ChangeEvent[x][y] = -1;
3792 ExplodePhase[x][y] = 0;
3793 ExplodeDelay[x][y] = 0;
3794 ExplodeField[x][y] = EX_TYPE_NONE;
3796 RunnerVisit[x][y] = 0;
3797 PlayerVisit[x][y] = 0;
3800 GfxRandom[x][y] = INIT_GFX_RANDOM();
3801 GfxElement[x][y] = EL_UNDEFINED;
3802 GfxAction[x][y] = ACTION_DEFAULT;
3803 GfxDir[x][y] = MV_NONE;
3804 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3807 SCAN_PLAYFIELD(x, y)
3809 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3811 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3813 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3816 InitField(x, y, TRUE);
3818 ResetGfxAnimation(x, y);
3823 for (i = 0; i < MAX_PLAYERS; i++)
3825 struct PlayerInfo *player = &stored_player[i];
3827 // set number of special actions for bored and sleeping animation
3828 player->num_special_action_bored =
3829 get_num_special_action(player->artwork_element,
3830 ACTION_BORING_1, ACTION_BORING_LAST);
3831 player->num_special_action_sleeping =
3832 get_num_special_action(player->artwork_element,
3833 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3836 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3837 emulate_sb ? EMU_SOKOBAN :
3838 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3840 // initialize type of slippery elements
3841 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3843 if (!IS_CUSTOM_ELEMENT(i))
3845 // default: elements slip down either to the left or right randomly
3846 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3848 // SP style elements prefer to slip down on the left side
3849 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3850 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3852 // BD style elements prefer to slip down on the left side
3853 if (game.emulation == EMU_BOULDERDASH)
3854 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3858 // initialize explosion and ignition delay
3859 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3861 if (!IS_CUSTOM_ELEMENT(i))
3864 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3865 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3866 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3867 int last_phase = (num_phase + 1) * delay;
3868 int half_phase = (num_phase / 2) * delay;
3870 element_info[i].explosion_delay = last_phase - 1;
3871 element_info[i].ignition_delay = half_phase;
3873 if (i == EL_BLACK_ORB)
3874 element_info[i].ignition_delay = 1;
3878 // correct non-moving belts to start moving left
3879 for (i = 0; i < NUM_BELTS; i++)
3880 if (game.belt_dir[i] == MV_NONE)
3881 game.belt_dir_nr[i] = 3; // not moving, next moving left
3883 #if USE_NEW_PLAYER_ASSIGNMENTS
3884 // use preferred player also in local single-player mode
3885 if (!network.enabled && !game.team_mode)
3887 int new_index_nr = setup.network_player_nr;
3889 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3891 for (i = 0; i < MAX_PLAYERS; i++)
3892 stored_player[i].connected_locally = FALSE;
3894 stored_player[new_index_nr].connected_locally = TRUE;
3898 for (i = 0; i < MAX_PLAYERS; i++)
3900 stored_player[i].connected = FALSE;
3902 // in network game mode, the local player might not be the first player
3903 if (stored_player[i].connected_locally)
3904 local_player = &stored_player[i];
3907 if (!network.enabled)
3908 local_player->connected = TRUE;
3912 for (i = 0; i < MAX_PLAYERS; i++)
3913 stored_player[i].connected = tape.player_participates[i];
3915 else if (network.enabled)
3917 // add team mode players connected over the network (needed for correct
3918 // assignment of player figures from level to locally playing players)
3920 for (i = 0; i < MAX_PLAYERS; i++)
3921 if (stored_player[i].connected_network)
3922 stored_player[i].connected = TRUE;
3924 else if (game.team_mode)
3926 // try to guess locally connected team mode players (needed for correct
3927 // assignment of player figures from level to locally playing players)
3929 for (i = 0; i < MAX_PLAYERS; i++)
3930 if (setup.input[i].use_joystick ||
3931 setup.input[i].key.left != KSYM_UNDEFINED)
3932 stored_player[i].connected = TRUE;
3935 #if DEBUG_INIT_PLAYER
3936 DebugPrintPlayerStatus("Player status after level initialization");
3939 #if DEBUG_INIT_PLAYER
3941 printf("Reassigning players ...\n");
3944 // check if any connected player was not found in playfield
3945 for (i = 0; i < MAX_PLAYERS; i++)
3947 struct PlayerInfo *player = &stored_player[i];
3949 if (player->connected && !player->present)
3951 struct PlayerInfo *field_player = NULL;
3953 #if DEBUG_INIT_PLAYER
3955 printf("- looking for field player for player %d ...\n", i + 1);
3958 // assign first free player found that is present in the playfield
3960 // first try: look for unmapped playfield player that is not connected
3961 for (j = 0; j < MAX_PLAYERS; j++)
3962 if (field_player == NULL &&
3963 stored_player[j].present &&
3964 !stored_player[j].mapped &&
3965 !stored_player[j].connected)
3966 field_player = &stored_player[j];
3968 // second try: look for *any* unmapped playfield player
3969 for (j = 0; j < MAX_PLAYERS; j++)
3970 if (field_player == NULL &&
3971 stored_player[j].present &&
3972 !stored_player[j].mapped)
3973 field_player = &stored_player[j];
3975 if (field_player != NULL)
3977 int jx = field_player->jx, jy = field_player->jy;
3979 #if DEBUG_INIT_PLAYER
3981 printf("- found player %d\n", field_player->index_nr + 1);
3984 player->present = FALSE;
3985 player->active = FALSE;
3987 field_player->present = TRUE;
3988 field_player->active = TRUE;
3991 player->initial_element = field_player->initial_element;
3992 player->artwork_element = field_player->artwork_element;
3994 player->block_last_field = field_player->block_last_field;
3995 player->block_delay_adjustment = field_player->block_delay_adjustment;
3998 StorePlayer[jx][jy] = field_player->element_nr;
4000 field_player->jx = field_player->last_jx = jx;
4001 field_player->jy = field_player->last_jy = jy;
4003 if (local_player == player)
4004 local_player = field_player;
4006 map_player_action[field_player->index_nr] = i;
4008 field_player->mapped = TRUE;
4010 #if DEBUG_INIT_PLAYER
4012 printf("- map_player_action[%d] == %d\n",
4013 field_player->index_nr + 1, i + 1);
4018 if (player->connected && player->present)
4019 player->mapped = TRUE;
4022 #if DEBUG_INIT_PLAYER
4023 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4028 // check if any connected player was not found in playfield
4029 for (i = 0; i < MAX_PLAYERS; i++)
4031 struct PlayerInfo *player = &stored_player[i];
4033 if (player->connected && !player->present)
4035 for (j = 0; j < MAX_PLAYERS; j++)
4037 struct PlayerInfo *field_player = &stored_player[j];
4038 int jx = field_player->jx, jy = field_player->jy;
4040 // assign first free player found that is present in the playfield
4041 if (field_player->present && !field_player->connected)
4043 player->present = TRUE;
4044 player->active = TRUE;
4046 field_player->present = FALSE;
4047 field_player->active = FALSE;
4049 player->initial_element = field_player->initial_element;
4050 player->artwork_element = field_player->artwork_element;
4052 player->block_last_field = field_player->block_last_field;
4053 player->block_delay_adjustment = field_player->block_delay_adjustment;
4055 StorePlayer[jx][jy] = player->element_nr;
4057 player->jx = player->last_jx = jx;
4058 player->jy = player->last_jy = jy;
4068 printf("::: local_player->present == %d\n", local_player->present);
4071 // set focus to local player for network games, else to all players
4072 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4073 game.centered_player_nr_next = game.centered_player_nr;
4074 game.set_centered_player = FALSE;
4075 game.set_centered_player_wrap = FALSE;
4077 if (network_playing && tape.recording)
4079 // store client dependent player focus when recording network games
4080 tape.centered_player_nr_next = game.centered_player_nr_next;
4081 tape.set_centered_player = TRUE;
4086 // when playing a tape, eliminate all players who do not participate
4088 #if USE_NEW_PLAYER_ASSIGNMENTS
4090 if (!game.team_mode)
4092 for (i = 0; i < MAX_PLAYERS; i++)
4094 if (stored_player[i].active &&
4095 !tape.player_participates[map_player_action[i]])
4097 struct PlayerInfo *player = &stored_player[i];
4098 int jx = player->jx, jy = player->jy;
4100 #if DEBUG_INIT_PLAYER
4102 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
4105 player->active = FALSE;
4106 StorePlayer[jx][jy] = 0;
4107 Feld[jx][jy] = EL_EMPTY;
4114 for (i = 0; i < MAX_PLAYERS; i++)
4116 if (stored_player[i].active &&
4117 !tape.player_participates[i])
4119 struct PlayerInfo *player = &stored_player[i];
4120 int jx = player->jx, jy = player->jy;
4122 player->active = FALSE;
4123 StorePlayer[jx][jy] = 0;
4124 Feld[jx][jy] = EL_EMPTY;
4129 else if (!network.enabled && !game.team_mode) // && !tape.playing
4131 // when in single player mode, eliminate all but the local player
4133 for (i = 0; i < MAX_PLAYERS; i++)
4135 struct PlayerInfo *player = &stored_player[i];
4137 if (player->active && player != local_player)
4139 int jx = player->jx, jy = player->jy;
4141 player->active = FALSE;
4142 player->present = FALSE;
4144 StorePlayer[jx][jy] = 0;
4145 Feld[jx][jy] = EL_EMPTY;
4150 for (i = 0; i < MAX_PLAYERS; i++)
4151 if (stored_player[i].active)
4152 game.players_still_needed++;
4154 if (level.solved_by_one_player)
4155 game.players_still_needed = 1;
4157 // when recording the game, store which players take part in the game
4160 #if USE_NEW_PLAYER_ASSIGNMENTS
4161 for (i = 0; i < MAX_PLAYERS; i++)
4162 if (stored_player[i].connected)
4163 tape.player_participates[i] = TRUE;
4165 for (i = 0; i < MAX_PLAYERS; i++)
4166 if (stored_player[i].active)
4167 tape.player_participates[i] = TRUE;
4171 #if DEBUG_INIT_PLAYER
4172 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4175 if (BorderElement == EL_EMPTY)
4178 SBX_Right = lev_fieldx - SCR_FIELDX;
4180 SBY_Lower = lev_fieldy - SCR_FIELDY;
4185 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4187 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4190 if (full_lev_fieldx <= SCR_FIELDX)
4191 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4192 if (full_lev_fieldy <= SCR_FIELDY)
4193 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4195 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4197 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4200 // if local player not found, look for custom element that might create
4201 // the player (make some assumptions about the right custom element)
4202 if (!local_player->present)
4204 int start_x = 0, start_y = 0;
4205 int found_rating = 0;
4206 int found_element = EL_UNDEFINED;
4207 int player_nr = local_player->index_nr;
4209 SCAN_PLAYFIELD(x, y)
4211 int element = Feld[x][y];
4216 if (level.use_start_element[player_nr] &&
4217 level.start_element[player_nr] == element &&
4224 found_element = element;
4227 if (!IS_CUSTOM_ELEMENT(element))
4230 if (CAN_CHANGE(element))
4232 for (i = 0; i < element_info[element].num_change_pages; i++)
4234 // check for player created from custom element as single target
4235 content = element_info[element].change_page[i].target_element;
4236 is_player = ELEM_IS_PLAYER(content);
4238 if (is_player && (found_rating < 3 ||
4239 (found_rating == 3 && element < found_element)))
4245 found_element = element;
4250 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4252 // check for player created from custom element as explosion content
4253 content = element_info[element].content.e[xx][yy];
4254 is_player = ELEM_IS_PLAYER(content);
4256 if (is_player && (found_rating < 2 ||
4257 (found_rating == 2 && element < found_element)))
4259 start_x = x + xx - 1;
4260 start_y = y + yy - 1;
4263 found_element = element;
4266 if (!CAN_CHANGE(element))
4269 for (i = 0; i < element_info[element].num_change_pages; i++)
4271 // check for player created from custom element as extended target
4273 element_info[element].change_page[i].target_content.e[xx][yy];
4275 is_player = ELEM_IS_PLAYER(content);
4277 if (is_player && (found_rating < 1 ||
4278 (found_rating == 1 && element < found_element)))
4280 start_x = x + xx - 1;
4281 start_y = y + yy - 1;
4284 found_element = element;
4290 scroll_x = SCROLL_POSITION_X(start_x);
4291 scroll_y = SCROLL_POSITION_Y(start_y);
4295 scroll_x = SCROLL_POSITION_X(local_player->jx);
4296 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4299 // !!! FIX THIS (START) !!!
4300 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4302 InitGameEngine_EM();
4304 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4306 InitGameEngine_SP();
4308 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4310 InitGameEngine_MM();
4314 DrawLevel(REDRAW_FIELD);
4317 // after drawing the level, correct some elements
4318 if (game.timegate_time_left == 0)
4319 CloseAllOpenTimegates();
4322 // blit playfield from scroll buffer to normal back buffer for fading in
4323 BlitScreenToBitmap(backbuffer);
4324 // !!! FIX THIS (END) !!!
4326 DrawMaskedBorder(fade_mask);
4331 // full screen redraw is required at this point in the following cases:
4332 // - special editor door undrawn when game was started from level editor
4333 // - drawing area (playfield) was changed and has to be removed completely
4334 redraw_mask = REDRAW_ALL;
4338 if (!game.restart_level)
4340 // copy default game door content to main double buffer
4342 // !!! CHECK AGAIN !!!
4343 SetPanelBackground();
4344 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4345 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4348 SetPanelBackground();
4349 SetDrawBackgroundMask(REDRAW_DOOR_1);
4351 UpdateAndDisplayGameControlValues();
4353 if (!game.restart_level)
4359 CreateGameButtons();
4364 // copy actual game door content to door double buffer for OpenDoor()
4365 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4367 OpenDoor(DOOR_OPEN_ALL);
4369 KeyboardAutoRepeatOffUnlessAutoplay();
4371 #if DEBUG_INIT_PLAYER
4372 DebugPrintPlayerStatus("Player status (final)");
4381 if (!game.restart_level && !tape.playing)
4383 LevelStats_incPlayed(level_nr);
4385 SaveLevelSetup_SeriesInfo();
4388 game.restart_level = FALSE;
4389 game.restart_game_message = NULL;
4390 game.request_active = FALSE;
4392 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4393 InitGameActions_MM();
4395 SaveEngineSnapshotToListInitial();
4397 if (!game.restart_level)
4399 PlaySound(SND_GAME_STARTING);
4401 if (setup.sound_music)
4406 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4407 int actual_player_x, int actual_player_y)
4409 // this is used for non-R'n'D game engines to update certain engine values
4411 // needed to determine if sounds are played within the visible screen area
4412 scroll_x = actual_scroll_x;
4413 scroll_y = actual_scroll_y;
4415 // needed to get player position for "follow finger" playing input method
4416 local_player->jx = actual_player_x;
4417 local_player->jy = actual_player_y;
4420 void InitMovDir(int x, int y)
4422 int i, element = Feld[x][y];
4423 static int xy[4][2] =
4430 static int direction[3][4] =
4432 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4433 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4434 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4443 Feld[x][y] = EL_BUG;
4444 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4447 case EL_SPACESHIP_RIGHT:
4448 case EL_SPACESHIP_UP:
4449 case EL_SPACESHIP_LEFT:
4450 case EL_SPACESHIP_DOWN:
4451 Feld[x][y] = EL_SPACESHIP;
4452 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4455 case EL_BD_BUTTERFLY_RIGHT:
4456 case EL_BD_BUTTERFLY_UP:
4457 case EL_BD_BUTTERFLY_LEFT:
4458 case EL_BD_BUTTERFLY_DOWN:
4459 Feld[x][y] = EL_BD_BUTTERFLY;
4460 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4463 case EL_BD_FIREFLY_RIGHT:
4464 case EL_BD_FIREFLY_UP:
4465 case EL_BD_FIREFLY_LEFT:
4466 case EL_BD_FIREFLY_DOWN:
4467 Feld[x][y] = EL_BD_FIREFLY;
4468 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4471 case EL_PACMAN_RIGHT:
4473 case EL_PACMAN_LEFT:
4474 case EL_PACMAN_DOWN:
4475 Feld[x][y] = EL_PACMAN;
4476 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4479 case EL_YAMYAM_LEFT:
4480 case EL_YAMYAM_RIGHT:
4482 case EL_YAMYAM_DOWN:
4483 Feld[x][y] = EL_YAMYAM;
4484 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4487 case EL_SP_SNIKSNAK:
4488 MovDir[x][y] = MV_UP;
4491 case EL_SP_ELECTRON:
4492 MovDir[x][y] = MV_LEFT;
4499 Feld[x][y] = EL_MOLE;
4500 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4503 case EL_SPRING_LEFT:
4504 case EL_SPRING_RIGHT:
4505 Feld[x][y] = EL_SPRING;
4506 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4510 if (IS_CUSTOM_ELEMENT(element))
4512 struct ElementInfo *ei = &element_info[element];
4513 int move_direction_initial = ei->move_direction_initial;
4514 int move_pattern = ei->move_pattern;
4516 if (move_direction_initial == MV_START_PREVIOUS)
4518 if (MovDir[x][y] != MV_NONE)
4521 move_direction_initial = MV_START_AUTOMATIC;
4524 if (move_direction_initial == MV_START_RANDOM)
4525 MovDir[x][y] = 1 << RND(4);
4526 else if (move_direction_initial & MV_ANY_DIRECTION)
4527 MovDir[x][y] = move_direction_initial;
4528 else if (move_pattern == MV_ALL_DIRECTIONS ||
4529 move_pattern == MV_TURNING_LEFT ||
4530 move_pattern == MV_TURNING_RIGHT ||
4531 move_pattern == MV_TURNING_LEFT_RIGHT ||
4532 move_pattern == MV_TURNING_RIGHT_LEFT ||
4533 move_pattern == MV_TURNING_RANDOM)
4534 MovDir[x][y] = 1 << RND(4);
4535 else if (move_pattern == MV_HORIZONTAL)
4536 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4537 else if (move_pattern == MV_VERTICAL)
4538 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4539 else if (move_pattern & MV_ANY_DIRECTION)
4540 MovDir[x][y] = element_info[element].move_pattern;
4541 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4542 move_pattern == MV_ALONG_RIGHT_SIDE)
4544 // use random direction as default start direction
4545 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4546 MovDir[x][y] = 1 << RND(4);
4548 for (i = 0; i < NUM_DIRECTIONS; i++)
4550 int x1 = x + xy[i][0];
4551 int y1 = y + xy[i][1];
4553 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4555 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4556 MovDir[x][y] = direction[0][i];
4558 MovDir[x][y] = direction[1][i];
4567 MovDir[x][y] = 1 << RND(4);
4569 if (element != EL_BUG &&
4570 element != EL_SPACESHIP &&
4571 element != EL_BD_BUTTERFLY &&
4572 element != EL_BD_FIREFLY)
4575 for (i = 0; i < NUM_DIRECTIONS; i++)
4577 int x1 = x + xy[i][0];
4578 int y1 = y + xy[i][1];
4580 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4582 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4584 MovDir[x][y] = direction[0][i];
4587 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4588 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4590 MovDir[x][y] = direction[1][i];
4599 GfxDir[x][y] = MovDir[x][y];
4602 void InitAmoebaNr(int x, int y)
4605 int group_nr = AmoebeNachbarNr(x, y);
4609 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4611 if (AmoebaCnt[i] == 0)
4619 AmoebaNr[x][y] = group_nr;
4620 AmoebaCnt[group_nr]++;
4621 AmoebaCnt2[group_nr]++;
4624 static void LevelSolved(void)
4626 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4627 game.players_still_needed > 0)
4630 game.LevelSolved = TRUE;
4631 game.GameOver = TRUE;
4633 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4634 game_em.lev->score :
4635 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4638 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4639 MM_HEALTH(game_mm.laser_overload_value) :
4642 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4643 game.LevelSolved_CountingScore = game.score_final;
4644 game.LevelSolved_CountingHealth = game.health_final;
4649 static int time_count_steps;
4650 static int time, time_final;
4651 static int score, score_final;
4652 static int health, health_final;
4653 static int game_over_delay_1 = 0;
4654 static int game_over_delay_2 = 0;
4655 static int game_over_delay_3 = 0;
4656 int game_over_delay_value_1 = 50;
4657 int game_over_delay_value_2 = 25;
4658 int game_over_delay_value_3 = 50;
4660 if (!game.LevelSolved_GameWon)
4664 // do not start end game actions before the player stops moving (to exit)
4665 if (local_player->active && local_player->MovPos)
4668 game.LevelSolved_GameWon = TRUE;
4669 game.LevelSolved_SaveTape = tape.recording;
4670 game.LevelSolved_SaveScore = !tape.playing;
4674 LevelStats_incSolved(level_nr);
4676 SaveLevelSetup_SeriesInfo();
4679 if (tape.auto_play) // tape might already be stopped here
4680 tape.auto_play_level_solved = TRUE;
4684 game_over_delay_1 = 0;
4685 game_over_delay_2 = 0;
4686 game_over_delay_3 = game_over_delay_value_3;
4688 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4689 score = score_final = game.score_final;
4690 health = health_final = game.health_final;
4692 if (level.score[SC_TIME_BONUS] > 0)
4697 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4699 else if (game.no_time_limit && TimePlayed < 999)
4702 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4705 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4707 game_over_delay_1 = game_over_delay_value_1;
4709 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4712 score_final += health * level.score[SC_TIME_BONUS];
4714 game_over_delay_2 = game_over_delay_value_2;
4717 game.score_final = score_final;
4718 game.health_final = health_final;
4721 if (level_editor_test_game)
4724 score = score_final;
4726 game.LevelSolved_CountingTime = time;
4727 game.LevelSolved_CountingScore = score;
4729 game_panel_controls[GAME_PANEL_TIME].value = time;
4730 game_panel_controls[GAME_PANEL_SCORE].value = score;
4732 DisplayGameControlValues();
4735 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4737 // check if last player has left the level
4738 if (game.exit_x >= 0 &&
4741 int x = game.exit_x;
4742 int y = game.exit_y;
4743 int element = Feld[x][y];
4745 // close exit door after last player
4746 if ((game.all_players_gone &&
4747 (element == EL_EXIT_OPEN ||
4748 element == EL_SP_EXIT_OPEN ||
4749 element == EL_STEEL_EXIT_OPEN)) ||
4750 element == EL_EM_EXIT_OPEN ||
4751 element == EL_EM_STEEL_EXIT_OPEN)
4755 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4756 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4757 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4758 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4759 EL_EM_STEEL_EXIT_CLOSING);
4761 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4764 // player disappears
4765 DrawLevelField(x, y);
4768 for (i = 0; i < MAX_PLAYERS; i++)
4770 struct PlayerInfo *player = &stored_player[i];
4772 if (player->present)
4774 RemovePlayer(player);
4776 // player disappears
4777 DrawLevelField(player->jx, player->jy);
4782 PlaySound(SND_GAME_WINNING);
4785 if (game_over_delay_1 > 0)
4787 game_over_delay_1--;
4792 if (time != time_final)
4794 int time_to_go = ABS(time_final - time);
4795 int time_count_dir = (time < time_final ? +1 : -1);
4797 if (time_to_go < time_count_steps)
4798 time_count_steps = 1;
4800 time += time_count_steps * time_count_dir;
4801 score += time_count_steps * level.score[SC_TIME_BONUS];
4803 game.LevelSolved_CountingTime = time;
4804 game.LevelSolved_CountingScore = score;
4806 game_panel_controls[GAME_PANEL_TIME].value = time;
4807 game_panel_controls[GAME_PANEL_SCORE].value = score;
4809 DisplayGameControlValues();
4811 if (time == time_final)
4812 StopSound(SND_GAME_LEVELTIME_BONUS);
4813 else if (setup.sound_loops)
4814 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4816 PlaySound(SND_GAME_LEVELTIME_BONUS);
4821 if (game_over_delay_2 > 0)
4823 game_over_delay_2--;
4828 if (health != health_final)
4830 int health_count_dir = (health < health_final ? +1 : -1);
4832 health += health_count_dir;
4833 score += level.score[SC_TIME_BONUS];
4835 game.LevelSolved_CountingHealth = health;
4836 game.LevelSolved_CountingScore = score;
4838 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4839 game_panel_controls[GAME_PANEL_SCORE].value = score;
4841 DisplayGameControlValues();
4843 if (health == health_final)
4844 StopSound(SND_GAME_LEVELTIME_BONUS);
4845 else if (setup.sound_loops)
4846 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4848 PlaySound(SND_GAME_LEVELTIME_BONUS);
4853 game.panel.active = FALSE;
4855 if (game_over_delay_3 > 0)
4857 game_over_delay_3--;
4867 // used instead of "level_nr" (needed for network games)
4868 int last_level_nr = levelset.level_nr;
4871 game.LevelSolved_GameEnd = TRUE;
4873 if (game.LevelSolved_SaveTape)
4875 // make sure that request dialog to save tape does not open door again
4876 if (!global.use_envelope_request)
4877 CloseDoor(DOOR_CLOSE_1);
4879 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4882 // if no tape is to be saved, close both doors simultaneously
4883 CloseDoor(DOOR_CLOSE_ALL);
4885 if (level_editor_test_game)
4887 SetGameStatus(GAME_MODE_MAIN);
4894 if (!game.LevelSolved_SaveScore)
4896 SetGameStatus(GAME_MODE_MAIN);
4903 if (level_nr == leveldir_current->handicap_level)
4905 leveldir_current->handicap_level++;
4907 SaveLevelSetup_SeriesInfo();
4910 if (setup.increment_levels &&
4911 level_nr < leveldir_current->last_level &&
4914 level_nr++; // advance to next level
4915 TapeErase(); // start with empty tape
4917 if (setup.auto_play_next_level)
4919 LoadLevel(level_nr);
4921 SaveLevelSetup_SeriesInfo();
4925 hi_pos = NewHiScore(last_level_nr);
4927 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4929 SetGameStatus(GAME_MODE_SCORES);
4931 DrawHallOfFame(last_level_nr, hi_pos);
4933 else if (setup.auto_play_next_level && setup.increment_levels &&
4934 last_level_nr < leveldir_current->last_level &&
4937 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4941 SetGameStatus(GAME_MODE_MAIN);
4947 int NewHiScore(int level_nr)
4951 boolean one_score_entry_per_name = !program.many_scores_per_name;
4953 LoadScore(level_nr);
4955 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4956 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4959 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4961 if (game.score_final > highscore[k].Score)
4963 // player has made it to the hall of fame
4965 if (k < MAX_SCORE_ENTRIES - 1)
4967 int m = MAX_SCORE_ENTRIES - 1;
4969 if (one_score_entry_per_name)
4971 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4972 if (strEqual(setup.player_name, highscore[l].Name))
4975 if (m == k) // player's new highscore overwrites his old one
4979 for (l = m; l > k; l--)
4981 strcpy(highscore[l].Name, highscore[l - 1].Name);
4982 highscore[l].Score = highscore[l - 1].Score;
4988 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4989 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4990 highscore[k].Score = game.score_final;
4995 else if (one_score_entry_per_name &&
4996 !strncmp(setup.player_name, highscore[k].Name,
4997 MAX_PLAYER_NAME_LEN))
4998 break; // player already there with a higher score
5002 SaveScore(level_nr);
5007 static int getElementMoveStepsizeExt(int x, int y, int direction)
5009 int element = Feld[x][y];
5010 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5011 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5012 int horiz_move = (dx != 0);
5013 int sign = (horiz_move ? dx : dy);
5014 int step = sign * element_info[element].move_stepsize;
5016 // special values for move stepsize for spring and things on conveyor belt
5019 if (CAN_FALL(element) &&
5020 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
5021 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5022 else if (element == EL_SPRING)
5023 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5029 static int getElementMoveStepsize(int x, int y)
5031 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5034 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5036 if (player->GfxAction != action || player->GfxDir != dir)
5038 player->GfxAction = action;
5039 player->GfxDir = dir;
5041 player->StepFrame = 0;
5045 static void ResetGfxFrame(int x, int y)
5047 // profiling showed that "autotest" spends 10~20% of its time in this function
5048 if (DrawingDeactivatedField())
5051 int element = Feld[x][y];
5052 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5054 if (graphic_info[graphic].anim_global_sync)
5055 GfxFrame[x][y] = FrameCounter;
5056 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5057 GfxFrame[x][y] = CustomValue[x][y];
5058 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5059 GfxFrame[x][y] = element_info[element].collect_score;
5060 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5061 GfxFrame[x][y] = ChangeDelay[x][y];
5064 static void ResetGfxAnimation(int x, int y)
5066 GfxAction[x][y] = ACTION_DEFAULT;
5067 GfxDir[x][y] = MovDir[x][y];
5070 ResetGfxFrame(x, y);
5073 static void ResetRandomAnimationValue(int x, int y)
5075 GfxRandom[x][y] = INIT_GFX_RANDOM();
5078 static void InitMovingField(int x, int y, int direction)
5080 int element = Feld[x][y];
5081 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5082 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5085 boolean is_moving_before, is_moving_after;
5087 // check if element was/is moving or being moved before/after mode change
5088 is_moving_before = (WasJustMoving[x][y] != 0);
5089 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5091 // reset animation only for moving elements which change direction of moving
5092 // or which just started or stopped moving
5093 // (else CEs with property "can move" / "not moving" are reset each frame)
5094 if (is_moving_before != is_moving_after ||
5095 direction != MovDir[x][y])
5096 ResetGfxAnimation(x, y);
5098 MovDir[x][y] = direction;
5099 GfxDir[x][y] = direction;
5101 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5102 direction == MV_DOWN && CAN_FALL(element) ?
5103 ACTION_FALLING : ACTION_MOVING);
5105 // this is needed for CEs with property "can move" / "not moving"
5107 if (is_moving_after)
5109 if (Feld[newx][newy] == EL_EMPTY)
5110 Feld[newx][newy] = EL_BLOCKED;
5112 MovDir[newx][newy] = MovDir[x][y];
5114 CustomValue[newx][newy] = CustomValue[x][y];
5116 GfxFrame[newx][newy] = GfxFrame[x][y];
5117 GfxRandom[newx][newy] = GfxRandom[x][y];
5118 GfxAction[newx][newy] = GfxAction[x][y];
5119 GfxDir[newx][newy] = GfxDir[x][y];
5123 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5125 int direction = MovDir[x][y];
5126 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5127 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5133 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5135 int oldx = x, oldy = y;
5136 int direction = MovDir[x][y];
5138 if (direction == MV_LEFT)
5140 else if (direction == MV_RIGHT)
5142 else if (direction == MV_UP)
5144 else if (direction == MV_DOWN)
5147 *comes_from_x = oldx;
5148 *comes_from_y = oldy;
5151 static int MovingOrBlocked2Element(int x, int y)
5153 int element = Feld[x][y];
5155 if (element == EL_BLOCKED)
5159 Blocked2Moving(x, y, &oldx, &oldy);
5160 return Feld[oldx][oldy];
5166 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5168 // like MovingOrBlocked2Element(), but if element is moving
5169 // and (x,y) is the field the moving element is just leaving,
5170 // return EL_BLOCKED instead of the element value
5171 int element = Feld[x][y];
5173 if (IS_MOVING(x, y))
5175 if (element == EL_BLOCKED)
5179 Blocked2Moving(x, y, &oldx, &oldy);
5180 return Feld[oldx][oldy];
5189 static void RemoveField(int x, int y)
5191 Feld[x][y] = EL_EMPTY;
5197 CustomValue[x][y] = 0;
5200 ChangeDelay[x][y] = 0;
5201 ChangePage[x][y] = -1;
5202 Pushed[x][y] = FALSE;
5204 GfxElement[x][y] = EL_UNDEFINED;
5205 GfxAction[x][y] = ACTION_DEFAULT;
5206 GfxDir[x][y] = MV_NONE;
5209 static void RemoveMovingField(int x, int y)
5211 int oldx = x, oldy = y, newx = x, newy = y;
5212 int element = Feld[x][y];
5213 int next_element = EL_UNDEFINED;
5215 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5218 if (IS_MOVING(x, y))
5220 Moving2Blocked(x, y, &newx, &newy);
5222 if (Feld[newx][newy] != EL_BLOCKED)
5224 // element is moving, but target field is not free (blocked), but
5225 // already occupied by something different (example: acid pool);
5226 // in this case, only remove the moving field, but not the target
5228 RemoveField(oldx, oldy);
5230 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5232 TEST_DrawLevelField(oldx, oldy);
5237 else if (element == EL_BLOCKED)
5239 Blocked2Moving(x, y, &oldx, &oldy);
5240 if (!IS_MOVING(oldx, oldy))
5244 if (element == EL_BLOCKED &&
5245 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5246 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5247 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5248 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5249 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5250 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5251 next_element = get_next_element(Feld[oldx][oldy]);
5253 RemoveField(oldx, oldy);
5254 RemoveField(newx, newy);
5256 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5258 if (next_element != EL_UNDEFINED)
5259 Feld[oldx][oldy] = next_element;
5261 TEST_DrawLevelField(oldx, oldy);
5262 TEST_DrawLevelField(newx, newy);
5265 void DrawDynamite(int x, int y)
5267 int sx = SCREENX(x), sy = SCREENY(y);
5268 int graphic = el2img(Feld[x][y]);
5271 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5274 if (IS_WALKABLE_INSIDE(Back[x][y]))
5278 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5279 else if (Store[x][y])
5280 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5282 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5284 if (Back[x][y] || Store[x][y])
5285 DrawGraphicThruMask(sx, sy, graphic, frame);
5287 DrawGraphic(sx, sy, graphic, frame);
5290 static void CheckDynamite(int x, int y)
5292 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5296 if (MovDelay[x][y] != 0)
5299 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5305 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5310 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5312 boolean num_checked_players = 0;
5315 for (i = 0; i < MAX_PLAYERS; i++)
5317 if (stored_player[i].active)
5319 int sx = stored_player[i].jx;
5320 int sy = stored_player[i].jy;
5322 if (num_checked_players == 0)
5329 *sx1 = MIN(*sx1, sx);
5330 *sy1 = MIN(*sy1, sy);
5331 *sx2 = MAX(*sx2, sx);
5332 *sy2 = MAX(*sy2, sy);
5335 num_checked_players++;
5340 static boolean checkIfAllPlayersFitToScreen_RND(void)
5342 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5344 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5346 return (sx2 - sx1 < SCR_FIELDX &&
5347 sy2 - sy1 < SCR_FIELDY);
5350 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5352 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5354 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5356 *sx = (sx1 + sx2) / 2;
5357 *sy = (sy1 + sy2) / 2;
5360 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5361 boolean center_screen, boolean quick_relocation)
5363 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5364 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5365 boolean no_delay = (tape.warp_forward);
5366 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5367 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5368 int new_scroll_x, new_scroll_y;
5370 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5372 // case 1: quick relocation inside visible screen (without scrolling)
5379 if (!level.shifted_relocation || center_screen)
5381 // relocation _with_ centering of screen
5383 new_scroll_x = SCROLL_POSITION_X(x);
5384 new_scroll_y = SCROLL_POSITION_Y(y);
5388 // relocation _without_ centering of screen
5390 int center_scroll_x = SCROLL_POSITION_X(old_x);
5391 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5392 int offset_x = x + (scroll_x - center_scroll_x);
5393 int offset_y = y + (scroll_y - center_scroll_y);
5395 // for new screen position, apply previous offset to center position
5396 new_scroll_x = SCROLL_POSITION_X(offset_x);
5397 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5400 if (quick_relocation)
5402 // case 2: quick relocation (redraw without visible scrolling)
5404 scroll_x = new_scroll_x;
5405 scroll_y = new_scroll_y;
5412 // case 3: visible relocation (with scrolling to new position)
5414 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5416 SetVideoFrameDelay(wait_delay_value);
5418 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5420 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5421 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5423 if (dx == 0 && dy == 0) // no scrolling needed at all
5429 // set values for horizontal/vertical screen scrolling (half tile size)
5430 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5431 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5432 int pos_x = dx * TILEX / 2;
5433 int pos_y = dy * TILEY / 2;
5434 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5435 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5437 ScrollLevel(dx, dy);
5440 // scroll in two steps of half tile size to make things smoother
5441 BlitScreenToBitmapExt_RND(window, fx, fy);
5443 // scroll second step to align at full tile size
5444 BlitScreenToBitmap(window);
5450 SetVideoFrameDelay(frame_delay_value_old);
5453 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5455 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5456 int player_nr = GET_PLAYER_NR(el_player);
5457 struct PlayerInfo *player = &stored_player[player_nr];
5458 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5459 boolean no_delay = (tape.warp_forward);
5460 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5461 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5462 int old_jx = player->jx;
5463 int old_jy = player->jy;
5464 int old_element = Feld[old_jx][old_jy];
5465 int element = Feld[jx][jy];
5466 boolean player_relocated = (old_jx != jx || old_jy != jy);
5468 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5469 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5470 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5471 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5472 int leave_side_horiz = move_dir_horiz;
5473 int leave_side_vert = move_dir_vert;
5474 int enter_side = enter_side_horiz | enter_side_vert;
5475 int leave_side = leave_side_horiz | leave_side_vert;
5477 if (player->buried) // do not reanimate dead player
5480 if (!player_relocated) // no need to relocate the player
5483 if (IS_PLAYER(jx, jy)) // player already placed at new position
5485 RemoveField(jx, jy); // temporarily remove newly placed player
5486 DrawLevelField(jx, jy);
5489 if (player->present)
5491 while (player->MovPos)
5493 ScrollPlayer(player, SCROLL_GO_ON);
5494 ScrollScreen(NULL, SCROLL_GO_ON);
5496 AdvanceFrameAndPlayerCounters(player->index_nr);
5500 BackToFront_WithFrameDelay(wait_delay_value);
5503 DrawPlayer(player); // needed here only to cleanup last field
5504 DrawLevelField(player->jx, player->jy); // remove player graphic
5506 player->is_moving = FALSE;
5509 if (IS_CUSTOM_ELEMENT(old_element))
5510 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5512 player->index_bit, leave_side);
5514 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5516 player->index_bit, leave_side);
5518 Feld[jx][jy] = el_player;
5519 InitPlayerField(jx, jy, el_player, TRUE);
5521 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5522 possible that the relocation target field did not contain a player element,
5523 but a walkable element, to which the new player was relocated -- in this
5524 case, restore that (already initialized!) element on the player field */
5525 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5527 Feld[jx][jy] = element; // restore previously existing element
5530 // only visually relocate centered player
5531 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5532 FALSE, level.instant_relocation);
5534 TestIfPlayerTouchesBadThing(jx, jy);
5535 TestIfPlayerTouchesCustomElement(jx, jy);
5537 if (IS_CUSTOM_ELEMENT(element))
5538 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5539 player->index_bit, enter_side);
5541 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5542 player->index_bit, enter_side);
5544 if (player->is_switching)
5546 /* ensure that relocation while still switching an element does not cause
5547 a new element to be treated as also switched directly after relocation
5548 (this is important for teleporter switches that teleport the player to
5549 a place where another teleporter switch is in the same direction, which
5550 would then incorrectly be treated as immediately switched before the
5551 direction key that caused the switch was released) */
5553 player->switch_x += jx - old_jx;
5554 player->switch_y += jy - old_jy;
5558 static void Explode(int ex, int ey, int phase, int mode)
5564 // !!! eliminate this variable !!!
5565 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5567 if (game.explosions_delayed)
5569 ExplodeField[ex][ey] = mode;
5573 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5575 int center_element = Feld[ex][ey];
5576 int artwork_element, explosion_element; // set these values later
5578 // remove things displayed in background while burning dynamite
5579 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5582 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5584 // put moving element to center field (and let it explode there)
5585 center_element = MovingOrBlocked2Element(ex, ey);
5586 RemoveMovingField(ex, ey);
5587 Feld[ex][ey] = center_element;
5590 // now "center_element" is finally determined -- set related values now
5591 artwork_element = center_element; // for custom player artwork
5592 explosion_element = center_element; // for custom player artwork
5594 if (IS_PLAYER(ex, ey))
5596 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5598 artwork_element = stored_player[player_nr].artwork_element;
5600 if (level.use_explosion_element[player_nr])
5602 explosion_element = level.explosion_element[player_nr];
5603 artwork_element = explosion_element;
5607 if (mode == EX_TYPE_NORMAL ||
5608 mode == EX_TYPE_CENTER ||
5609 mode == EX_TYPE_CROSS)
5610 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5612 last_phase = element_info[explosion_element].explosion_delay + 1;
5614 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5616 int xx = x - ex + 1;
5617 int yy = y - ey + 1;
5620 if (!IN_LEV_FIELD(x, y) ||
5621 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5622 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5625 element = Feld[x][y];
5627 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5629 element = MovingOrBlocked2Element(x, y);
5631 if (!IS_EXPLOSION_PROOF(element))
5632 RemoveMovingField(x, y);
5635 // indestructible elements can only explode in center (but not flames)
5636 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5637 mode == EX_TYPE_BORDER)) ||
5638 element == EL_FLAMES)
5641 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5642 behaviour, for example when touching a yamyam that explodes to rocks
5643 with active deadly shield, a rock is created under the player !!! */
5644 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5646 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5647 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5648 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5650 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5653 if (IS_ACTIVE_BOMB(element))
5655 // re-activate things under the bomb like gate or penguin
5656 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5663 // save walkable background elements while explosion on same tile
5664 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5665 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5666 Back[x][y] = element;
5668 // ignite explodable elements reached by other explosion
5669 if (element == EL_EXPLOSION)
5670 element = Store2[x][y];
5672 if (AmoebaNr[x][y] &&
5673 (element == EL_AMOEBA_FULL ||
5674 element == EL_BD_AMOEBA ||
5675 element == EL_AMOEBA_GROWING))
5677 AmoebaCnt[AmoebaNr[x][y]]--;
5678 AmoebaCnt2[AmoebaNr[x][y]]--;
5683 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5685 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5687 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5689 if (PLAYERINFO(ex, ey)->use_murphy)
5690 Store[x][y] = EL_EMPTY;
5693 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5694 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5695 else if (ELEM_IS_PLAYER(center_element))
5696 Store[x][y] = EL_EMPTY;
5697 else if (center_element == EL_YAMYAM)
5698 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5699 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5700 Store[x][y] = element_info[center_element].content.e[xx][yy];
5702 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5703 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5704 // otherwise) -- FIX THIS !!!
5705 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5706 Store[x][y] = element_info[element].content.e[1][1];
5708 else if (!CAN_EXPLODE(element))
5709 Store[x][y] = element_info[element].content.e[1][1];
5712 Store[x][y] = EL_EMPTY;
5714 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5715 center_element == EL_AMOEBA_TO_DIAMOND)
5716 Store2[x][y] = element;
5718 Feld[x][y] = EL_EXPLOSION;
5719 GfxElement[x][y] = artwork_element;
5721 ExplodePhase[x][y] = 1;
5722 ExplodeDelay[x][y] = last_phase;
5727 if (center_element == EL_YAMYAM)
5728 game.yamyam_content_nr =
5729 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5741 GfxFrame[x][y] = 0; // restart explosion animation
5743 last_phase = ExplodeDelay[x][y];
5745 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5747 // this can happen if the player leaves an explosion just in time
5748 if (GfxElement[x][y] == EL_UNDEFINED)
5749 GfxElement[x][y] = EL_EMPTY;
5751 border_element = Store2[x][y];
5752 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5753 border_element = StorePlayer[x][y];
5755 if (phase == element_info[border_element].ignition_delay ||
5756 phase == last_phase)
5758 boolean border_explosion = FALSE;
5760 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5761 !PLAYER_EXPLOSION_PROTECTED(x, y))
5763 KillPlayerUnlessExplosionProtected(x, y);
5764 border_explosion = TRUE;
5766 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5768 Feld[x][y] = Store2[x][y];
5771 border_explosion = TRUE;
5773 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5775 AmoebeUmwandeln(x, y);
5777 border_explosion = TRUE;
5780 // if an element just explodes due to another explosion (chain-reaction),
5781 // do not immediately end the new explosion when it was the last frame of
5782 // the explosion (as it would be done in the following "if"-statement!)
5783 if (border_explosion && phase == last_phase)
5787 if (phase == last_phase)
5791 element = Feld[x][y] = Store[x][y];
5792 Store[x][y] = Store2[x][y] = 0;
5793 GfxElement[x][y] = EL_UNDEFINED;
5795 // player can escape from explosions and might therefore be still alive
5796 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5797 element <= EL_PLAYER_IS_EXPLODING_4)
5799 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5800 int explosion_element = EL_PLAYER_1 + player_nr;
5801 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5802 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5804 if (level.use_explosion_element[player_nr])
5805 explosion_element = level.explosion_element[player_nr];
5807 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5808 element_info[explosion_element].content.e[xx][yy]);
5811 // restore probably existing indestructible background element
5812 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5813 element = Feld[x][y] = Back[x][y];
5816 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5817 GfxDir[x][y] = MV_NONE;
5818 ChangeDelay[x][y] = 0;
5819 ChangePage[x][y] = -1;
5821 CustomValue[x][y] = 0;
5823 InitField_WithBug2(x, y, FALSE);
5825 TEST_DrawLevelField(x, y);
5827 TestIfElementTouchesCustomElement(x, y);
5829 if (GFX_CRUMBLED(element))
5830 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5832 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5833 StorePlayer[x][y] = 0;
5835 if (ELEM_IS_PLAYER(element))
5836 RelocatePlayer(x, y, element);
5838 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5840 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5841 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5844 TEST_DrawLevelFieldCrumbled(x, y);
5846 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5848 DrawLevelElement(x, y, Back[x][y]);
5849 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5851 else if (IS_WALKABLE_UNDER(Back[x][y]))
5853 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5854 DrawLevelElementThruMask(x, y, Back[x][y]);
5856 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5857 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5861 static void DynaExplode(int ex, int ey)
5864 int dynabomb_element = Feld[ex][ey];
5865 int dynabomb_size = 1;
5866 boolean dynabomb_xl = FALSE;
5867 struct PlayerInfo *player;
5868 static int xy[4][2] =
5876 if (IS_ACTIVE_BOMB(dynabomb_element))
5878 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5879 dynabomb_size = player->dynabomb_size;
5880 dynabomb_xl = player->dynabomb_xl;
5881 player->dynabombs_left++;
5884 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5886 for (i = 0; i < NUM_DIRECTIONS; i++)
5888 for (j = 1; j <= dynabomb_size; j++)
5890 int x = ex + j * xy[i][0];
5891 int y = ey + j * xy[i][1];
5894 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5897 element = Feld[x][y];
5899 // do not restart explosions of fields with active bombs
5900 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5903 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5905 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5906 !IS_DIGGABLE(element) && !dynabomb_xl)
5912 void Bang(int x, int y)
5914 int element = MovingOrBlocked2Element(x, y);
5915 int explosion_type = EX_TYPE_NORMAL;
5917 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5919 struct PlayerInfo *player = PLAYERINFO(x, y);
5921 element = Feld[x][y] = player->initial_element;
5923 if (level.use_explosion_element[player->index_nr])
5925 int explosion_element = level.explosion_element[player->index_nr];
5927 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5928 explosion_type = EX_TYPE_CROSS;
5929 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5930 explosion_type = EX_TYPE_CENTER;
5938 case EL_BD_BUTTERFLY:
5941 case EL_DARK_YAMYAM:
5945 RaiseScoreElement(element);
5948 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5949 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5950 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5951 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5952 case EL_DYNABOMB_INCREASE_NUMBER:
5953 case EL_DYNABOMB_INCREASE_SIZE:
5954 case EL_DYNABOMB_INCREASE_POWER:
5955 explosion_type = EX_TYPE_DYNA;
5958 case EL_DC_LANDMINE:
5959 explosion_type = EX_TYPE_CENTER;
5964 case EL_LAMP_ACTIVE:
5965 case EL_AMOEBA_TO_DIAMOND:
5966 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5967 explosion_type = EX_TYPE_CENTER;
5971 if (element_info[element].explosion_type == EXPLODES_CROSS)
5972 explosion_type = EX_TYPE_CROSS;
5973 else if (element_info[element].explosion_type == EXPLODES_1X1)
5974 explosion_type = EX_TYPE_CENTER;
5978 if (explosion_type == EX_TYPE_DYNA)
5981 Explode(x, y, EX_PHASE_START, explosion_type);
5983 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5986 static void SplashAcid(int x, int y)
5988 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5989 (!IN_LEV_FIELD(x - 1, y - 2) ||
5990 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5991 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5993 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5994 (!IN_LEV_FIELD(x + 1, y - 2) ||
5995 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5996 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5998 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6001 static void InitBeltMovement(void)
6003 static int belt_base_element[4] =
6005 EL_CONVEYOR_BELT_1_LEFT,
6006 EL_CONVEYOR_BELT_2_LEFT,
6007 EL_CONVEYOR_BELT_3_LEFT,
6008 EL_CONVEYOR_BELT_4_LEFT
6010 static int belt_base_active_element[4] =
6012 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6013 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6014 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6015 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6020 // set frame order for belt animation graphic according to belt direction
6021 for (i = 0; i < NUM_BELTS; i++)
6025 for (j = 0; j < NUM_BELT_PARTS; j++)
6027 int element = belt_base_active_element[belt_nr] + j;
6028 int graphic_1 = el2img(element);
6029 int graphic_2 = el2panelimg(element);
6031 if (game.belt_dir[i] == MV_LEFT)
6033 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6034 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6038 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6039 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6044 SCAN_PLAYFIELD(x, y)
6046 int element = Feld[x][y];
6048 for (i = 0; i < NUM_BELTS; i++)
6050 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6052 int e_belt_nr = getBeltNrFromBeltElement(element);
6055 if (e_belt_nr == belt_nr)
6057 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
6059 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
6066 static void ToggleBeltSwitch(int x, int y)
6068 static int belt_base_element[4] =
6070 EL_CONVEYOR_BELT_1_LEFT,
6071 EL_CONVEYOR_BELT_2_LEFT,
6072 EL_CONVEYOR_BELT_3_LEFT,
6073 EL_CONVEYOR_BELT_4_LEFT
6075 static int belt_base_active_element[4] =
6077 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6078 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6079 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6080 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6082 static int belt_base_switch_element[4] =
6084 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6085 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6086 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6087 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6089 static int belt_move_dir[4] =
6097 int element = Feld[x][y];
6098 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6099 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6100 int belt_dir = belt_move_dir[belt_dir_nr];
6103 if (!IS_BELT_SWITCH(element))
6106 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6107 game.belt_dir[belt_nr] = belt_dir;
6109 if (belt_dir_nr == 3)
6112 // set frame order for belt animation graphic according to belt direction
6113 for (i = 0; i < NUM_BELT_PARTS; i++)
6115 int element = belt_base_active_element[belt_nr] + i;
6116 int graphic_1 = el2img(element);
6117 int graphic_2 = el2panelimg(element);
6119 if (belt_dir == MV_LEFT)
6121 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6122 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6126 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6127 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6131 SCAN_PLAYFIELD(xx, yy)
6133 int element = Feld[xx][yy];
6135 if (IS_BELT_SWITCH(element))
6137 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6139 if (e_belt_nr == belt_nr)
6141 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6142 TEST_DrawLevelField(xx, yy);
6145 else if (IS_BELT(element) && belt_dir != MV_NONE)
6147 int e_belt_nr = getBeltNrFromBeltElement(element);
6149 if (e_belt_nr == belt_nr)
6151 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
6153 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6154 TEST_DrawLevelField(xx, yy);
6157 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6159 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6161 if (e_belt_nr == belt_nr)
6163 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6165 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6166 TEST_DrawLevelField(xx, yy);
6172 static void ToggleSwitchgateSwitch(int x, int y)
6176 game.switchgate_pos = !game.switchgate_pos;
6178 SCAN_PLAYFIELD(xx, yy)
6180 int element = Feld[xx][yy];
6182 if (element == EL_SWITCHGATE_SWITCH_UP)
6184 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6185 TEST_DrawLevelField(xx, yy);
6187 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6189 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6190 TEST_DrawLevelField(xx, yy);
6192 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6194 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6195 TEST_DrawLevelField(xx, yy);
6197 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6199 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6200 TEST_DrawLevelField(xx, yy);
6202 else if (element == EL_SWITCHGATE_OPEN ||
6203 element == EL_SWITCHGATE_OPENING)
6205 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6207 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6209 else if (element == EL_SWITCHGATE_CLOSED ||
6210 element == EL_SWITCHGATE_CLOSING)
6212 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6214 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6219 static int getInvisibleActiveFromInvisibleElement(int element)
6221 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6222 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6223 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6227 static int getInvisibleFromInvisibleActiveElement(int element)
6229 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6230 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6231 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6235 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6239 SCAN_PLAYFIELD(x, y)
6241 int element = Feld[x][y];
6243 if (element == EL_LIGHT_SWITCH &&
6244 game.light_time_left > 0)
6246 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6247 TEST_DrawLevelField(x, y);
6249 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6250 game.light_time_left == 0)
6252 Feld[x][y] = EL_LIGHT_SWITCH;
6253 TEST_DrawLevelField(x, y);
6255 else if (element == EL_EMC_DRIPPER &&
6256 game.light_time_left > 0)
6258 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6259 TEST_DrawLevelField(x, y);
6261 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6262 game.light_time_left == 0)
6264 Feld[x][y] = EL_EMC_DRIPPER;
6265 TEST_DrawLevelField(x, y);
6267 else if (element == EL_INVISIBLE_STEELWALL ||
6268 element == EL_INVISIBLE_WALL ||
6269 element == EL_INVISIBLE_SAND)
6271 if (game.light_time_left > 0)
6272 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6274 TEST_DrawLevelField(x, y);
6276 // uncrumble neighbour fields, if needed
6277 if (element == EL_INVISIBLE_SAND)
6278 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6280 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6281 element == EL_INVISIBLE_WALL_ACTIVE ||
6282 element == EL_INVISIBLE_SAND_ACTIVE)
6284 if (game.light_time_left == 0)
6285 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6287 TEST_DrawLevelField(x, y);
6289 // re-crumble neighbour fields, if needed
6290 if (element == EL_INVISIBLE_SAND)
6291 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6296 static void RedrawAllInvisibleElementsForLenses(void)
6300 SCAN_PLAYFIELD(x, y)
6302 int element = Feld[x][y];
6304 if (element == EL_EMC_DRIPPER &&
6305 game.lenses_time_left > 0)
6307 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6308 TEST_DrawLevelField(x, y);
6310 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6311 game.lenses_time_left == 0)
6313 Feld[x][y] = EL_EMC_DRIPPER;
6314 TEST_DrawLevelField(x, y);
6316 else if (element == EL_INVISIBLE_STEELWALL ||
6317 element == EL_INVISIBLE_WALL ||
6318 element == EL_INVISIBLE_SAND)
6320 if (game.lenses_time_left > 0)
6321 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6323 TEST_DrawLevelField(x, y);
6325 // uncrumble neighbour fields, if needed
6326 if (element == EL_INVISIBLE_SAND)
6327 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6329 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6330 element == EL_INVISIBLE_WALL_ACTIVE ||
6331 element == EL_INVISIBLE_SAND_ACTIVE)
6333 if (game.lenses_time_left == 0)
6334 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6336 TEST_DrawLevelField(x, y);
6338 // re-crumble neighbour fields, if needed
6339 if (element == EL_INVISIBLE_SAND)
6340 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6345 static void RedrawAllInvisibleElementsForMagnifier(void)
6349 SCAN_PLAYFIELD(x, y)
6351 int element = Feld[x][y];
6353 if (element == EL_EMC_FAKE_GRASS &&
6354 game.magnify_time_left > 0)
6356 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6357 TEST_DrawLevelField(x, y);
6359 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6360 game.magnify_time_left == 0)
6362 Feld[x][y] = EL_EMC_FAKE_GRASS;
6363 TEST_DrawLevelField(x, y);
6365 else if (IS_GATE_GRAY(element) &&
6366 game.magnify_time_left > 0)
6368 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6369 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6370 IS_EM_GATE_GRAY(element) ?
6371 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6372 IS_EMC_GATE_GRAY(element) ?
6373 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6374 IS_DC_GATE_GRAY(element) ?
6375 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6377 TEST_DrawLevelField(x, y);
6379 else if (IS_GATE_GRAY_ACTIVE(element) &&
6380 game.magnify_time_left == 0)
6382 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6383 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6384 IS_EM_GATE_GRAY_ACTIVE(element) ?
6385 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6386 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6387 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6388 IS_DC_GATE_GRAY_ACTIVE(element) ?
6389 EL_DC_GATE_WHITE_GRAY :
6391 TEST_DrawLevelField(x, y);
6396 static void ToggleLightSwitch(int x, int y)
6398 int element = Feld[x][y];
6400 game.light_time_left =
6401 (element == EL_LIGHT_SWITCH ?
6402 level.time_light * FRAMES_PER_SECOND : 0);
6404 RedrawAllLightSwitchesAndInvisibleElements();
6407 static void ActivateTimegateSwitch(int x, int y)
6411 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6413 SCAN_PLAYFIELD(xx, yy)
6415 int element = Feld[xx][yy];
6417 if (element == EL_TIMEGATE_CLOSED ||
6418 element == EL_TIMEGATE_CLOSING)
6420 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6421 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6425 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6427 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6428 TEST_DrawLevelField(xx, yy);
6434 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6435 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6438 static void Impact(int x, int y)
6440 boolean last_line = (y == lev_fieldy - 1);
6441 boolean object_hit = FALSE;
6442 boolean impact = (last_line || object_hit);
6443 int element = Feld[x][y];
6444 int smashed = EL_STEELWALL;
6446 if (!last_line) // check if element below was hit
6448 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6451 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6452 MovDir[x][y + 1] != MV_DOWN ||
6453 MovPos[x][y + 1] <= TILEY / 2));
6455 // do not smash moving elements that left the smashed field in time
6456 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6457 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6460 #if USE_QUICKSAND_IMPACT_BUGFIX
6461 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6463 RemoveMovingField(x, y + 1);
6464 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6465 Feld[x][y + 2] = EL_ROCK;
6466 TEST_DrawLevelField(x, y + 2);
6471 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6473 RemoveMovingField(x, y + 1);
6474 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6475 Feld[x][y + 2] = EL_ROCK;
6476 TEST_DrawLevelField(x, y + 2);
6483 smashed = MovingOrBlocked2Element(x, y + 1);
6485 impact = (last_line || object_hit);
6488 if (!last_line && smashed == EL_ACID) // element falls into acid
6490 SplashAcid(x, y + 1);
6494 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6495 // only reset graphic animation if graphic really changes after impact
6497 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6499 ResetGfxAnimation(x, y);
6500 TEST_DrawLevelField(x, y);
6503 if (impact && CAN_EXPLODE_IMPACT(element))
6508 else if (impact && element == EL_PEARL &&
6509 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6511 ResetGfxAnimation(x, y);
6513 Feld[x][y] = EL_PEARL_BREAKING;
6514 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6517 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6519 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6524 if (impact && element == EL_AMOEBA_DROP)
6526 if (object_hit && IS_PLAYER(x, y + 1))
6527 KillPlayerUnlessEnemyProtected(x, y + 1);
6528 else if (object_hit && smashed == EL_PENGUIN)
6532 Feld[x][y] = EL_AMOEBA_GROWING;
6533 Store[x][y] = EL_AMOEBA_WET;
6535 ResetRandomAnimationValue(x, y);
6540 if (object_hit) // check which object was hit
6542 if ((CAN_PASS_MAGIC_WALL(element) &&
6543 (smashed == EL_MAGIC_WALL ||
6544 smashed == EL_BD_MAGIC_WALL)) ||
6545 (CAN_PASS_DC_MAGIC_WALL(element) &&
6546 smashed == EL_DC_MAGIC_WALL))
6549 int activated_magic_wall =
6550 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6551 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6552 EL_DC_MAGIC_WALL_ACTIVE);
6554 // activate magic wall / mill
6555 SCAN_PLAYFIELD(xx, yy)
6557 if (Feld[xx][yy] == smashed)
6558 Feld[xx][yy] = activated_magic_wall;
6561 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6562 game.magic_wall_active = TRUE;
6564 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6565 SND_MAGIC_WALL_ACTIVATING :
6566 smashed == EL_BD_MAGIC_WALL ?
6567 SND_BD_MAGIC_WALL_ACTIVATING :
6568 SND_DC_MAGIC_WALL_ACTIVATING));
6571 if (IS_PLAYER(x, y + 1))
6573 if (CAN_SMASH_PLAYER(element))
6575 KillPlayerUnlessEnemyProtected(x, y + 1);
6579 else if (smashed == EL_PENGUIN)
6581 if (CAN_SMASH_PLAYER(element))
6587 else if (element == EL_BD_DIAMOND)
6589 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6595 else if (((element == EL_SP_INFOTRON ||
6596 element == EL_SP_ZONK) &&
6597 (smashed == EL_SP_SNIKSNAK ||
6598 smashed == EL_SP_ELECTRON ||
6599 smashed == EL_SP_DISK_ORANGE)) ||
6600 (element == EL_SP_INFOTRON &&
6601 smashed == EL_SP_DISK_YELLOW))
6606 else if (CAN_SMASH_EVERYTHING(element))
6608 if (IS_CLASSIC_ENEMY(smashed) ||
6609 CAN_EXPLODE_SMASHED(smashed))
6614 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6616 if (smashed == EL_LAMP ||
6617 smashed == EL_LAMP_ACTIVE)
6622 else if (smashed == EL_NUT)
6624 Feld[x][y + 1] = EL_NUT_BREAKING;
6625 PlayLevelSound(x, y, SND_NUT_BREAKING);
6626 RaiseScoreElement(EL_NUT);
6629 else if (smashed == EL_PEARL)
6631 ResetGfxAnimation(x, y);
6633 Feld[x][y + 1] = EL_PEARL_BREAKING;
6634 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6637 else if (smashed == EL_DIAMOND)
6639 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6640 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6643 else if (IS_BELT_SWITCH(smashed))
6645 ToggleBeltSwitch(x, y + 1);
6647 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6648 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6649 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6650 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6652 ToggleSwitchgateSwitch(x, y + 1);
6654 else if (smashed == EL_LIGHT_SWITCH ||
6655 smashed == EL_LIGHT_SWITCH_ACTIVE)
6657 ToggleLightSwitch(x, y + 1);
6661 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6663 CheckElementChangeBySide(x, y + 1, smashed, element,
6664 CE_SWITCHED, CH_SIDE_TOP);
6665 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6671 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6676 // play sound of magic wall / mill
6678 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6679 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6680 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6682 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6683 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6684 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6685 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6686 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6687 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6692 // play sound of object that hits the ground
6693 if (last_line || object_hit)
6694 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6697 static void TurnRoundExt(int x, int y)
6709 { 0, 0 }, { 0, 0 }, { 0, 0 },
6714 int left, right, back;
6718 { MV_DOWN, MV_UP, MV_RIGHT },
6719 { MV_UP, MV_DOWN, MV_LEFT },
6721 { MV_LEFT, MV_RIGHT, MV_DOWN },
6725 { MV_RIGHT, MV_LEFT, MV_UP }
6728 int element = Feld[x][y];
6729 int move_pattern = element_info[element].move_pattern;
6731 int old_move_dir = MovDir[x][y];
6732 int left_dir = turn[old_move_dir].left;
6733 int right_dir = turn[old_move_dir].right;
6734 int back_dir = turn[old_move_dir].back;
6736 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6737 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6738 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6739 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6741 int left_x = x + left_dx, left_y = y + left_dy;
6742 int right_x = x + right_dx, right_y = y + right_dy;
6743 int move_x = x + move_dx, move_y = y + move_dy;
6747 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6749 TestIfBadThingTouchesOtherBadThing(x, y);
6751 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6752 MovDir[x][y] = right_dir;
6753 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6754 MovDir[x][y] = left_dir;
6756 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6758 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6761 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6763 TestIfBadThingTouchesOtherBadThing(x, y);
6765 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6766 MovDir[x][y] = left_dir;
6767 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6768 MovDir[x][y] = right_dir;
6770 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6772 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6775 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6777 TestIfBadThingTouchesOtherBadThing(x, y);
6779 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6780 MovDir[x][y] = left_dir;
6781 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6782 MovDir[x][y] = right_dir;
6784 if (MovDir[x][y] != old_move_dir)
6787 else if (element == EL_YAMYAM)
6789 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6790 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6792 if (can_turn_left && can_turn_right)
6793 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6794 else if (can_turn_left)
6795 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6796 else if (can_turn_right)
6797 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6799 MovDir[x][y] = back_dir;
6801 MovDelay[x][y] = 16 + 16 * RND(3);
6803 else if (element == EL_DARK_YAMYAM)
6805 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6807 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6810 if (can_turn_left && can_turn_right)
6811 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6812 else if (can_turn_left)
6813 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6814 else if (can_turn_right)
6815 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6817 MovDir[x][y] = back_dir;
6819 MovDelay[x][y] = 16 + 16 * RND(3);
6821 else if (element == EL_PACMAN)
6823 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6824 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6826 if (can_turn_left && can_turn_right)
6827 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6828 else if (can_turn_left)
6829 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6830 else if (can_turn_right)
6831 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6833 MovDir[x][y] = back_dir;
6835 MovDelay[x][y] = 6 + RND(40);
6837 else if (element == EL_PIG)
6839 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6840 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6841 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6842 boolean should_turn_left, should_turn_right, should_move_on;
6844 int rnd = RND(rnd_value);
6846 should_turn_left = (can_turn_left &&
6848 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6849 y + back_dy + left_dy)));
6850 should_turn_right = (can_turn_right &&
6852 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6853 y + back_dy + right_dy)));
6854 should_move_on = (can_move_on &&
6857 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6858 y + move_dy + left_dy) ||
6859 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6860 y + move_dy + right_dy)));
6862 if (should_turn_left || should_turn_right || should_move_on)
6864 if (should_turn_left && should_turn_right && should_move_on)
6865 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6866 rnd < 2 * rnd_value / 3 ? right_dir :
6868 else if (should_turn_left && should_turn_right)
6869 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6870 else if (should_turn_left && should_move_on)
6871 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6872 else if (should_turn_right && should_move_on)
6873 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6874 else if (should_turn_left)
6875 MovDir[x][y] = left_dir;
6876 else if (should_turn_right)
6877 MovDir[x][y] = right_dir;
6878 else if (should_move_on)
6879 MovDir[x][y] = old_move_dir;
6881 else if (can_move_on && rnd > rnd_value / 8)
6882 MovDir[x][y] = old_move_dir;
6883 else if (can_turn_left && can_turn_right)
6884 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6885 else if (can_turn_left && rnd > rnd_value / 8)
6886 MovDir[x][y] = left_dir;
6887 else if (can_turn_right && rnd > rnd_value/8)
6888 MovDir[x][y] = right_dir;
6890 MovDir[x][y] = back_dir;
6892 xx = x + move_xy[MovDir[x][y]].dx;
6893 yy = y + move_xy[MovDir[x][y]].dy;
6895 if (!IN_LEV_FIELD(xx, yy) ||
6896 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6897 MovDir[x][y] = old_move_dir;
6901 else if (element == EL_DRAGON)
6903 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6904 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6905 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6907 int rnd = RND(rnd_value);
6909 if (can_move_on && rnd > rnd_value / 8)
6910 MovDir[x][y] = old_move_dir;
6911 else if (can_turn_left && can_turn_right)
6912 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6913 else if (can_turn_left && rnd > rnd_value / 8)
6914 MovDir[x][y] = left_dir;
6915 else if (can_turn_right && rnd > rnd_value / 8)
6916 MovDir[x][y] = right_dir;
6918 MovDir[x][y] = back_dir;
6920 xx = x + move_xy[MovDir[x][y]].dx;
6921 yy = y + move_xy[MovDir[x][y]].dy;
6923 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6924 MovDir[x][y] = old_move_dir;
6928 else if (element == EL_MOLE)
6930 boolean can_move_on =
6931 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6932 IS_AMOEBOID(Feld[move_x][move_y]) ||
6933 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6936 boolean can_turn_left =
6937 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6938 IS_AMOEBOID(Feld[left_x][left_y])));
6940 boolean can_turn_right =
6941 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6942 IS_AMOEBOID(Feld[right_x][right_y])));
6944 if (can_turn_left && can_turn_right)
6945 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6946 else if (can_turn_left)
6947 MovDir[x][y] = left_dir;
6949 MovDir[x][y] = right_dir;
6952 if (MovDir[x][y] != old_move_dir)
6955 else if (element == EL_BALLOON)
6957 MovDir[x][y] = game.wind_direction;
6960 else if (element == EL_SPRING)
6962 if (MovDir[x][y] & MV_HORIZONTAL)
6964 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6965 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6967 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6968 ResetGfxAnimation(move_x, move_y);
6969 TEST_DrawLevelField(move_x, move_y);
6971 MovDir[x][y] = back_dir;
6973 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6974 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6975 MovDir[x][y] = MV_NONE;
6980 else if (element == EL_ROBOT ||
6981 element == EL_SATELLITE ||
6982 element == EL_PENGUIN ||
6983 element == EL_EMC_ANDROID)
6985 int attr_x = -1, attr_y = -1;
6987 if (game.all_players_gone)
6989 attr_x = game.exit_x;
6990 attr_y = game.exit_y;
6996 for (i = 0; i < MAX_PLAYERS; i++)
6998 struct PlayerInfo *player = &stored_player[i];
6999 int jx = player->jx, jy = player->jy;
7001 if (!player->active)
7005 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7013 if (element == EL_ROBOT &&
7014 game.robot_wheel_x >= 0 &&
7015 game.robot_wheel_y >= 0 &&
7016 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7017 game.engine_version < VERSION_IDENT(3,1,0,0)))
7019 attr_x = game.robot_wheel_x;
7020 attr_y = game.robot_wheel_y;
7023 if (element == EL_PENGUIN)
7026 static int xy[4][2] =
7034 for (i = 0; i < NUM_DIRECTIONS; i++)
7036 int ex = x + xy[i][0];
7037 int ey = y + xy[i][1];
7039 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
7040 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
7041 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
7042 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7051 MovDir[x][y] = MV_NONE;
7053 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7054 else if (attr_x > x)
7055 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7057 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7058 else if (attr_y > y)
7059 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7061 if (element == EL_ROBOT)
7065 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7066 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7067 Moving2Blocked(x, y, &newx, &newy);
7069 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7070 MovDelay[x][y] = 8 + 8 * !RND(3);
7072 MovDelay[x][y] = 16;
7074 else if (element == EL_PENGUIN)
7080 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7082 boolean first_horiz = RND(2);
7083 int new_move_dir = MovDir[x][y];
7086 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7087 Moving2Blocked(x, y, &newx, &newy);
7089 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7093 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7094 Moving2Blocked(x, y, &newx, &newy);
7096 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7099 MovDir[x][y] = old_move_dir;
7103 else if (element == EL_SATELLITE)
7109 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7111 boolean first_horiz = RND(2);
7112 int new_move_dir = MovDir[x][y];
7115 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7116 Moving2Blocked(x, y, &newx, &newy);
7118 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7122 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7123 Moving2Blocked(x, y, &newx, &newy);
7125 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7128 MovDir[x][y] = old_move_dir;
7132 else if (element == EL_EMC_ANDROID)
7134 static int check_pos[16] =
7136 -1, // 0 => (invalid)
7139 -1, // 3 => (invalid)
7141 0, // 5 => MV_LEFT | MV_UP
7142 2, // 6 => MV_RIGHT | MV_UP
7143 -1, // 7 => (invalid)
7145 6, // 9 => MV_LEFT | MV_DOWN
7146 4, // 10 => MV_RIGHT | MV_DOWN
7147 -1, // 11 => (invalid)
7148 -1, // 12 => (invalid)
7149 -1, // 13 => (invalid)
7150 -1, // 14 => (invalid)
7151 -1, // 15 => (invalid)
7159 { -1, -1, MV_LEFT | MV_UP },
7161 { +1, -1, MV_RIGHT | MV_UP },
7162 { +1, 0, MV_RIGHT },
7163 { +1, +1, MV_RIGHT | MV_DOWN },
7165 { -1, +1, MV_LEFT | MV_DOWN },
7168 int start_pos, check_order;
7169 boolean can_clone = FALSE;
7172 // check if there is any free field around current position
7173 for (i = 0; i < 8; i++)
7175 int newx = x + check_xy[i].dx;
7176 int newy = y + check_xy[i].dy;
7178 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7186 if (can_clone) // randomly find an element to clone
7190 start_pos = check_pos[RND(8)];
7191 check_order = (RND(2) ? -1 : +1);
7193 for (i = 0; i < 8; i++)
7195 int pos_raw = start_pos + i * check_order;
7196 int pos = (pos_raw + 8) % 8;
7197 int newx = x + check_xy[pos].dx;
7198 int newy = y + check_xy[pos].dy;
7200 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7202 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7203 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7205 Store[x][y] = Feld[newx][newy];
7214 if (can_clone) // randomly find a direction to move
7218 start_pos = check_pos[RND(8)];
7219 check_order = (RND(2) ? -1 : +1);
7221 for (i = 0; i < 8; i++)
7223 int pos_raw = start_pos + i * check_order;
7224 int pos = (pos_raw + 8) % 8;
7225 int newx = x + check_xy[pos].dx;
7226 int newy = y + check_xy[pos].dy;
7227 int new_move_dir = check_xy[pos].dir;
7229 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7231 MovDir[x][y] = new_move_dir;
7232 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7241 if (can_clone) // cloning and moving successful
7244 // cannot clone -- try to move towards player
7246 start_pos = check_pos[MovDir[x][y] & 0x0f];
7247 check_order = (RND(2) ? -1 : +1);
7249 for (i = 0; i < 3; i++)
7251 // first check start_pos, then previous/next or (next/previous) pos
7252 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7253 int pos = (pos_raw + 8) % 8;
7254 int newx = x + check_xy[pos].dx;
7255 int newy = y + check_xy[pos].dy;
7256 int new_move_dir = check_xy[pos].dir;
7258 if (IS_PLAYER(newx, newy))
7261 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7263 MovDir[x][y] = new_move_dir;
7264 MovDelay[x][y] = level.android_move_time * 8 + 1;
7271 else if (move_pattern == MV_TURNING_LEFT ||
7272 move_pattern == MV_TURNING_RIGHT ||
7273 move_pattern == MV_TURNING_LEFT_RIGHT ||
7274 move_pattern == MV_TURNING_RIGHT_LEFT ||
7275 move_pattern == MV_TURNING_RANDOM ||
7276 move_pattern == MV_ALL_DIRECTIONS)
7278 boolean can_turn_left =
7279 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7280 boolean can_turn_right =
7281 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7283 if (element_info[element].move_stepsize == 0) // "not moving"
7286 if (move_pattern == MV_TURNING_LEFT)
7287 MovDir[x][y] = left_dir;
7288 else if (move_pattern == MV_TURNING_RIGHT)
7289 MovDir[x][y] = right_dir;
7290 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7291 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7292 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7293 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7294 else if (move_pattern == MV_TURNING_RANDOM)
7295 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7296 can_turn_right && !can_turn_left ? right_dir :
7297 RND(2) ? left_dir : right_dir);
7298 else if (can_turn_left && can_turn_right)
7299 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7300 else if (can_turn_left)
7301 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7302 else if (can_turn_right)
7303 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7305 MovDir[x][y] = back_dir;
7307 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7309 else if (move_pattern == MV_HORIZONTAL ||
7310 move_pattern == MV_VERTICAL)
7312 if (move_pattern & old_move_dir)
7313 MovDir[x][y] = back_dir;
7314 else if (move_pattern == MV_HORIZONTAL)
7315 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7316 else if (move_pattern == MV_VERTICAL)
7317 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7319 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7321 else if (move_pattern & MV_ANY_DIRECTION)
7323 MovDir[x][y] = move_pattern;
7324 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7326 else if (move_pattern & MV_WIND_DIRECTION)
7328 MovDir[x][y] = game.wind_direction;
7329 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7331 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7333 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7334 MovDir[x][y] = left_dir;
7335 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7336 MovDir[x][y] = right_dir;
7338 if (MovDir[x][y] != old_move_dir)
7339 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7341 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7343 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7344 MovDir[x][y] = right_dir;
7345 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7346 MovDir[x][y] = left_dir;
7348 if (MovDir[x][y] != old_move_dir)
7349 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7351 else if (move_pattern == MV_TOWARDS_PLAYER ||
7352 move_pattern == MV_AWAY_FROM_PLAYER)
7354 int attr_x = -1, attr_y = -1;
7356 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7358 if (game.all_players_gone)
7360 attr_x = game.exit_x;
7361 attr_y = game.exit_y;
7367 for (i = 0; i < MAX_PLAYERS; i++)
7369 struct PlayerInfo *player = &stored_player[i];
7370 int jx = player->jx, jy = player->jy;
7372 if (!player->active)
7376 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7384 MovDir[x][y] = MV_NONE;
7386 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7387 else if (attr_x > x)
7388 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7390 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7391 else if (attr_y > y)
7392 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7394 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7396 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7398 boolean first_horiz = RND(2);
7399 int new_move_dir = MovDir[x][y];
7401 if (element_info[element].move_stepsize == 0) // "not moving"
7403 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7404 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7410 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7411 Moving2Blocked(x, y, &newx, &newy);
7413 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7417 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7418 Moving2Blocked(x, y, &newx, &newy);
7420 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7423 MovDir[x][y] = old_move_dir;
7426 else if (move_pattern == MV_WHEN_PUSHED ||
7427 move_pattern == MV_WHEN_DROPPED)
7429 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7430 MovDir[x][y] = MV_NONE;
7434 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7436 static int test_xy[7][2] =
7446 static int test_dir[7] =
7456 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7457 int move_preference = -1000000; // start with very low preference
7458 int new_move_dir = MV_NONE;
7459 int start_test = RND(4);
7462 for (i = 0; i < NUM_DIRECTIONS; i++)
7464 int move_dir = test_dir[start_test + i];
7465 int move_dir_preference;
7467 xx = x + test_xy[start_test + i][0];
7468 yy = y + test_xy[start_test + i][1];
7470 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7471 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7473 new_move_dir = move_dir;
7478 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7481 move_dir_preference = -1 * RunnerVisit[xx][yy];
7482 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7483 move_dir_preference = PlayerVisit[xx][yy];
7485 if (move_dir_preference > move_preference)
7487 // prefer field that has not been visited for the longest time
7488 move_preference = move_dir_preference;
7489 new_move_dir = move_dir;
7491 else if (move_dir_preference == move_preference &&
7492 move_dir == old_move_dir)
7494 // prefer last direction when all directions are preferred equally
7495 move_preference = move_dir_preference;
7496 new_move_dir = move_dir;
7500 MovDir[x][y] = new_move_dir;
7501 if (old_move_dir != new_move_dir)
7502 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7506 static void TurnRound(int x, int y)
7508 int direction = MovDir[x][y];
7512 GfxDir[x][y] = MovDir[x][y];
7514 if (direction != MovDir[x][y])
7518 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7520 ResetGfxFrame(x, y);
7523 static boolean JustBeingPushed(int x, int y)
7527 for (i = 0; i < MAX_PLAYERS; i++)
7529 struct PlayerInfo *player = &stored_player[i];
7531 if (player->active && player->is_pushing && player->MovPos)
7533 int next_jx = player->jx + (player->jx - player->last_jx);
7534 int next_jy = player->jy + (player->jy - player->last_jy);
7536 if (x == next_jx && y == next_jy)
7544 static void StartMoving(int x, int y)
7546 boolean started_moving = FALSE; // some elements can fall _and_ move
7547 int element = Feld[x][y];
7552 if (MovDelay[x][y] == 0)
7553 GfxAction[x][y] = ACTION_DEFAULT;
7555 if (CAN_FALL(element) && y < lev_fieldy - 1)
7557 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7558 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7559 if (JustBeingPushed(x, y))
7562 if (element == EL_QUICKSAND_FULL)
7564 if (IS_FREE(x, y + 1))
7566 InitMovingField(x, y, MV_DOWN);
7567 started_moving = TRUE;
7569 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7570 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7571 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7572 Store[x][y] = EL_ROCK;
7574 Store[x][y] = EL_ROCK;
7577 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7579 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7581 if (!MovDelay[x][y])
7583 MovDelay[x][y] = TILEY + 1;
7585 ResetGfxAnimation(x, y);
7586 ResetGfxAnimation(x, y + 1);
7591 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7592 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7599 Feld[x][y] = EL_QUICKSAND_EMPTY;
7600 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7601 Store[x][y + 1] = Store[x][y];
7604 PlayLevelSoundAction(x, y, ACTION_FILLING);
7606 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7608 if (!MovDelay[x][y])
7610 MovDelay[x][y] = TILEY + 1;
7612 ResetGfxAnimation(x, y);
7613 ResetGfxAnimation(x, y + 1);
7618 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7619 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7626 Feld[x][y] = EL_QUICKSAND_EMPTY;
7627 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7628 Store[x][y + 1] = Store[x][y];
7631 PlayLevelSoundAction(x, y, ACTION_FILLING);
7634 else if (element == EL_QUICKSAND_FAST_FULL)
7636 if (IS_FREE(x, y + 1))
7638 InitMovingField(x, y, MV_DOWN);
7639 started_moving = TRUE;
7641 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7642 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7643 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7644 Store[x][y] = EL_ROCK;
7646 Store[x][y] = EL_ROCK;
7649 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7651 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7653 if (!MovDelay[x][y])
7655 MovDelay[x][y] = TILEY + 1;
7657 ResetGfxAnimation(x, y);
7658 ResetGfxAnimation(x, y + 1);
7663 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7664 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7671 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7672 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7673 Store[x][y + 1] = Store[x][y];
7676 PlayLevelSoundAction(x, y, ACTION_FILLING);
7678 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7680 if (!MovDelay[x][y])
7682 MovDelay[x][y] = TILEY + 1;
7684 ResetGfxAnimation(x, y);
7685 ResetGfxAnimation(x, y + 1);
7690 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7691 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7698 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7699 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7700 Store[x][y + 1] = Store[x][y];
7703 PlayLevelSoundAction(x, y, ACTION_FILLING);
7706 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7707 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7709 InitMovingField(x, y, MV_DOWN);
7710 started_moving = TRUE;
7712 Feld[x][y] = EL_QUICKSAND_FILLING;
7713 Store[x][y] = element;
7715 PlayLevelSoundAction(x, y, ACTION_FILLING);
7717 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7718 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7720 InitMovingField(x, y, MV_DOWN);
7721 started_moving = TRUE;
7723 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7724 Store[x][y] = element;
7726 PlayLevelSoundAction(x, y, ACTION_FILLING);
7728 else if (element == EL_MAGIC_WALL_FULL)
7730 if (IS_FREE(x, y + 1))
7732 InitMovingField(x, y, MV_DOWN);
7733 started_moving = TRUE;
7735 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7736 Store[x][y] = EL_CHANGED(Store[x][y]);
7738 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7740 if (!MovDelay[x][y])
7741 MovDelay[x][y] = TILEY / 4 + 1;
7750 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7751 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7752 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7756 else if (element == EL_BD_MAGIC_WALL_FULL)
7758 if (IS_FREE(x, y + 1))
7760 InitMovingField(x, y, MV_DOWN);
7761 started_moving = TRUE;
7763 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7764 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7766 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7768 if (!MovDelay[x][y])
7769 MovDelay[x][y] = TILEY / 4 + 1;
7778 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7779 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7780 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7784 else if (element == EL_DC_MAGIC_WALL_FULL)
7786 if (IS_FREE(x, y + 1))
7788 InitMovingField(x, y, MV_DOWN);
7789 started_moving = TRUE;
7791 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7792 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7794 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7796 if (!MovDelay[x][y])
7797 MovDelay[x][y] = TILEY / 4 + 1;
7806 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7807 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7808 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7812 else if ((CAN_PASS_MAGIC_WALL(element) &&
7813 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7814 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7815 (CAN_PASS_DC_MAGIC_WALL(element) &&
7816 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7819 InitMovingField(x, y, MV_DOWN);
7820 started_moving = TRUE;
7823 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7824 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7825 EL_DC_MAGIC_WALL_FILLING);
7826 Store[x][y] = element;
7828 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7830 SplashAcid(x, y + 1);
7832 InitMovingField(x, y, MV_DOWN);
7833 started_moving = TRUE;
7835 Store[x][y] = EL_ACID;
7838 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7839 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7840 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7841 CAN_FALL(element) && WasJustFalling[x][y] &&
7842 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7844 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7845 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7846 (Feld[x][y + 1] == EL_BLOCKED)))
7848 /* this is needed for a special case not covered by calling "Impact()"
7849 from "ContinueMoving()": if an element moves to a tile directly below
7850 another element which was just falling on that tile (which was empty
7851 in the previous frame), the falling element above would just stop
7852 instead of smashing the element below (in previous version, the above
7853 element was just checked for "moving" instead of "falling", resulting
7854 in incorrect smashes caused by horizontal movement of the above
7855 element; also, the case of the player being the element to smash was
7856 simply not covered here... :-/ ) */
7858 CheckCollision[x][y] = 0;
7859 CheckImpact[x][y] = 0;
7863 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7865 if (MovDir[x][y] == MV_NONE)
7867 InitMovingField(x, y, MV_DOWN);
7868 started_moving = TRUE;
7871 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7873 if (WasJustFalling[x][y]) // prevent animation from being restarted
7874 MovDir[x][y] = MV_DOWN;
7876 InitMovingField(x, y, MV_DOWN);
7877 started_moving = TRUE;
7879 else if (element == EL_AMOEBA_DROP)
7881 Feld[x][y] = EL_AMOEBA_GROWING;
7882 Store[x][y] = EL_AMOEBA_WET;
7884 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7885 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7886 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7887 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7889 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7890 (IS_FREE(x - 1, y + 1) ||
7891 Feld[x - 1][y + 1] == EL_ACID));
7892 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7893 (IS_FREE(x + 1, y + 1) ||
7894 Feld[x + 1][y + 1] == EL_ACID));
7895 boolean can_fall_any = (can_fall_left || can_fall_right);
7896 boolean can_fall_both = (can_fall_left && can_fall_right);
7897 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7899 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7901 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7902 can_fall_right = FALSE;
7903 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7904 can_fall_left = FALSE;
7905 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7906 can_fall_right = FALSE;
7907 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7908 can_fall_left = FALSE;
7910 can_fall_any = (can_fall_left || can_fall_right);
7911 can_fall_both = FALSE;
7916 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7917 can_fall_right = FALSE; // slip down on left side
7919 can_fall_left = !(can_fall_right = RND(2));
7921 can_fall_both = FALSE;
7926 // if not determined otherwise, prefer left side for slipping down
7927 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7928 started_moving = TRUE;
7931 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7933 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7934 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7935 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7936 int belt_dir = game.belt_dir[belt_nr];
7938 if ((belt_dir == MV_LEFT && left_is_free) ||
7939 (belt_dir == MV_RIGHT && right_is_free))
7941 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7943 InitMovingField(x, y, belt_dir);
7944 started_moving = TRUE;
7946 Pushed[x][y] = TRUE;
7947 Pushed[nextx][y] = TRUE;
7949 GfxAction[x][y] = ACTION_DEFAULT;
7953 MovDir[x][y] = 0; // if element was moving, stop it
7958 // not "else if" because of elements that can fall and move (EL_SPRING)
7959 if (CAN_MOVE(element) && !started_moving)
7961 int move_pattern = element_info[element].move_pattern;
7964 Moving2Blocked(x, y, &newx, &newy);
7966 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7969 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7970 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7972 WasJustMoving[x][y] = 0;
7973 CheckCollision[x][y] = 0;
7975 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7977 if (Feld[x][y] != element) // element has changed
7981 if (!MovDelay[x][y]) // start new movement phase
7983 // all objects that can change their move direction after each step
7984 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7986 if (element != EL_YAMYAM &&
7987 element != EL_DARK_YAMYAM &&
7988 element != EL_PACMAN &&
7989 !(move_pattern & MV_ANY_DIRECTION) &&
7990 move_pattern != MV_TURNING_LEFT &&
7991 move_pattern != MV_TURNING_RIGHT &&
7992 move_pattern != MV_TURNING_LEFT_RIGHT &&
7993 move_pattern != MV_TURNING_RIGHT_LEFT &&
7994 move_pattern != MV_TURNING_RANDOM)
7998 if (MovDelay[x][y] && (element == EL_BUG ||
7999 element == EL_SPACESHIP ||
8000 element == EL_SP_SNIKSNAK ||
8001 element == EL_SP_ELECTRON ||
8002 element == EL_MOLE))
8003 TEST_DrawLevelField(x, y);
8007 if (MovDelay[x][y]) // wait some time before next movement
8011 if (element == EL_ROBOT ||
8012 element == EL_YAMYAM ||
8013 element == EL_DARK_YAMYAM)
8015 DrawLevelElementAnimationIfNeeded(x, y, element);
8016 PlayLevelSoundAction(x, y, ACTION_WAITING);
8018 else if (element == EL_SP_ELECTRON)
8019 DrawLevelElementAnimationIfNeeded(x, y, element);
8020 else if (element == EL_DRAGON)
8023 int dir = MovDir[x][y];
8024 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8025 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8026 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8027 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8028 dir == MV_UP ? IMG_FLAMES_1_UP :
8029 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8030 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8032 GfxAction[x][y] = ACTION_ATTACKING;
8034 if (IS_PLAYER(x, y))
8035 DrawPlayerField(x, y);
8037 TEST_DrawLevelField(x, y);
8039 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8041 for (i = 1; i <= 3; i++)
8043 int xx = x + i * dx;
8044 int yy = y + i * dy;
8045 int sx = SCREENX(xx);
8046 int sy = SCREENY(yy);
8047 int flame_graphic = graphic + (i - 1);
8049 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
8054 int flamed = MovingOrBlocked2Element(xx, yy);
8056 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8059 RemoveMovingField(xx, yy);
8061 ChangeDelay[xx][yy] = 0;
8063 Feld[xx][yy] = EL_FLAMES;
8065 if (IN_SCR_FIELD(sx, sy))
8067 TEST_DrawLevelFieldCrumbled(xx, yy);
8068 DrawGraphic(sx, sy, flame_graphic, frame);
8073 if (Feld[xx][yy] == EL_FLAMES)
8074 Feld[xx][yy] = EL_EMPTY;
8075 TEST_DrawLevelField(xx, yy);
8080 if (MovDelay[x][y]) // element still has to wait some time
8082 PlayLevelSoundAction(x, y, ACTION_WAITING);
8088 // now make next step
8090 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8092 if (DONT_COLLIDE_WITH(element) &&
8093 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8094 !PLAYER_ENEMY_PROTECTED(newx, newy))
8096 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8101 else if (CAN_MOVE_INTO_ACID(element) &&
8102 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
8103 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8104 (MovDir[x][y] == MV_DOWN ||
8105 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8107 SplashAcid(newx, newy);
8108 Store[x][y] = EL_ACID;
8110 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8112 if (Feld[newx][newy] == EL_EXIT_OPEN ||
8113 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
8114 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
8115 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8118 TEST_DrawLevelField(x, y);
8120 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8121 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8122 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8124 game.friends_still_needed--;
8125 if (!game.friends_still_needed &&
8127 game.all_players_gone)
8132 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
8134 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8135 TEST_DrawLevelField(newx, newy);
8137 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8139 else if (!IS_FREE(newx, newy))
8141 GfxAction[x][y] = ACTION_WAITING;
8143 if (IS_PLAYER(x, y))
8144 DrawPlayerField(x, y);
8146 TEST_DrawLevelField(x, y);
8151 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8153 if (IS_FOOD_PIG(Feld[newx][newy]))
8155 if (IS_MOVING(newx, newy))
8156 RemoveMovingField(newx, newy);
8159 Feld[newx][newy] = EL_EMPTY;
8160 TEST_DrawLevelField(newx, newy);
8163 PlayLevelSound(x, y, SND_PIG_DIGGING);
8165 else if (!IS_FREE(newx, newy))
8167 if (IS_PLAYER(x, y))
8168 DrawPlayerField(x, y);
8170 TEST_DrawLevelField(x, y);
8175 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8177 if (Store[x][y] != EL_EMPTY)
8179 boolean can_clone = FALSE;
8182 // check if element to clone is still there
8183 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8185 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8193 // cannot clone or target field not free anymore -- do not clone
8194 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8195 Store[x][y] = EL_EMPTY;
8198 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8200 if (IS_MV_DIAGONAL(MovDir[x][y]))
8202 int diagonal_move_dir = MovDir[x][y];
8203 int stored = Store[x][y];
8204 int change_delay = 8;
8207 // android is moving diagonally
8209 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8211 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8212 GfxElement[x][y] = EL_EMC_ANDROID;
8213 GfxAction[x][y] = ACTION_SHRINKING;
8214 GfxDir[x][y] = diagonal_move_dir;
8215 ChangeDelay[x][y] = change_delay;
8217 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8220 DrawLevelGraphicAnimation(x, y, graphic);
8221 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8223 if (Feld[newx][newy] == EL_ACID)
8225 SplashAcid(newx, newy);
8230 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8232 Store[newx][newy] = EL_EMC_ANDROID;
8233 GfxElement[newx][newy] = EL_EMC_ANDROID;
8234 GfxAction[newx][newy] = ACTION_GROWING;
8235 GfxDir[newx][newy] = diagonal_move_dir;
8236 ChangeDelay[newx][newy] = change_delay;
8238 graphic = el_act_dir2img(GfxElement[newx][newy],
8239 GfxAction[newx][newy], GfxDir[newx][newy]);
8241 DrawLevelGraphicAnimation(newx, newy, graphic);
8242 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8248 Feld[newx][newy] = EL_EMPTY;
8249 TEST_DrawLevelField(newx, newy);
8251 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8254 else if (!IS_FREE(newx, newy))
8259 else if (IS_CUSTOM_ELEMENT(element) &&
8260 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8262 if (!DigFieldByCE(newx, newy, element))
8265 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8267 RunnerVisit[x][y] = FrameCounter;
8268 PlayerVisit[x][y] /= 8; // expire player visit path
8271 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8273 if (!IS_FREE(newx, newy))
8275 if (IS_PLAYER(x, y))
8276 DrawPlayerField(x, y);
8278 TEST_DrawLevelField(x, y);
8284 boolean wanna_flame = !RND(10);
8285 int dx = newx - x, dy = newy - y;
8286 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8287 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8288 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8289 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8290 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8291 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8294 IS_CLASSIC_ENEMY(element1) ||
8295 IS_CLASSIC_ENEMY(element2)) &&
8296 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8297 element1 != EL_FLAMES && element2 != EL_FLAMES)
8299 ResetGfxAnimation(x, y);
8300 GfxAction[x][y] = ACTION_ATTACKING;
8302 if (IS_PLAYER(x, y))
8303 DrawPlayerField(x, y);
8305 TEST_DrawLevelField(x, y);
8307 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8309 MovDelay[x][y] = 50;
8311 Feld[newx][newy] = EL_FLAMES;
8312 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8313 Feld[newx1][newy1] = EL_FLAMES;
8314 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8315 Feld[newx2][newy2] = EL_FLAMES;
8321 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8322 Feld[newx][newy] == EL_DIAMOND)
8324 if (IS_MOVING(newx, newy))
8325 RemoveMovingField(newx, newy);
8328 Feld[newx][newy] = EL_EMPTY;
8329 TEST_DrawLevelField(newx, newy);
8332 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8334 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8335 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8337 if (AmoebaNr[newx][newy])
8339 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8340 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8341 Feld[newx][newy] == EL_BD_AMOEBA)
8342 AmoebaCnt[AmoebaNr[newx][newy]]--;
8345 if (IS_MOVING(newx, newy))
8347 RemoveMovingField(newx, newy);
8351 Feld[newx][newy] = EL_EMPTY;
8352 TEST_DrawLevelField(newx, newy);
8355 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8357 else if ((element == EL_PACMAN || element == EL_MOLE)
8358 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8360 if (AmoebaNr[newx][newy])
8362 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8363 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8364 Feld[newx][newy] == EL_BD_AMOEBA)
8365 AmoebaCnt[AmoebaNr[newx][newy]]--;
8368 if (element == EL_MOLE)
8370 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8371 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8373 ResetGfxAnimation(x, y);
8374 GfxAction[x][y] = ACTION_DIGGING;
8375 TEST_DrawLevelField(x, y);
8377 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8379 return; // wait for shrinking amoeba
8381 else // element == EL_PACMAN
8383 Feld[newx][newy] = EL_EMPTY;
8384 TEST_DrawLevelField(newx, newy);
8385 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8388 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8389 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8390 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8392 // wait for shrinking amoeba to completely disappear
8395 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8397 // object was running against a wall
8401 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8402 DrawLevelElementAnimation(x, y, element);
8404 if (DONT_TOUCH(element))
8405 TestIfBadThingTouchesPlayer(x, y);
8410 InitMovingField(x, y, MovDir[x][y]);
8412 PlayLevelSoundAction(x, y, ACTION_MOVING);
8416 ContinueMoving(x, y);
8419 void ContinueMoving(int x, int y)
8421 int element = Feld[x][y];
8422 struct ElementInfo *ei = &element_info[element];
8423 int direction = MovDir[x][y];
8424 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8425 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8426 int newx = x + dx, newy = y + dy;
8427 int stored = Store[x][y];
8428 int stored_new = Store[newx][newy];
8429 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8430 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8431 boolean last_line = (newy == lev_fieldy - 1);
8433 MovPos[x][y] += getElementMoveStepsize(x, y);
8435 if (pushed_by_player) // special case: moving object pushed by player
8436 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8438 if (ABS(MovPos[x][y]) < TILEX)
8440 TEST_DrawLevelField(x, y);
8442 return; // element is still moving
8445 // element reached destination field
8447 Feld[x][y] = EL_EMPTY;
8448 Feld[newx][newy] = element;
8449 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8451 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8453 element = Feld[newx][newy] = EL_ACID;
8455 else if (element == EL_MOLE)
8457 Feld[x][y] = EL_SAND;
8459 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8461 else if (element == EL_QUICKSAND_FILLING)
8463 element = Feld[newx][newy] = get_next_element(element);
8464 Store[newx][newy] = Store[x][y];
8466 else if (element == EL_QUICKSAND_EMPTYING)
8468 Feld[x][y] = get_next_element(element);
8469 element = Feld[newx][newy] = Store[x][y];
8471 else if (element == EL_QUICKSAND_FAST_FILLING)
8473 element = Feld[newx][newy] = get_next_element(element);
8474 Store[newx][newy] = Store[x][y];
8476 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8478 Feld[x][y] = get_next_element(element);
8479 element = Feld[newx][newy] = Store[x][y];
8481 else if (element == EL_MAGIC_WALL_FILLING)
8483 element = Feld[newx][newy] = get_next_element(element);
8484 if (!game.magic_wall_active)
8485 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8486 Store[newx][newy] = Store[x][y];
8488 else if (element == EL_MAGIC_WALL_EMPTYING)
8490 Feld[x][y] = get_next_element(element);
8491 if (!game.magic_wall_active)
8492 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8493 element = Feld[newx][newy] = Store[x][y];
8495 InitField(newx, newy, FALSE);
8497 else if (element == EL_BD_MAGIC_WALL_FILLING)
8499 element = Feld[newx][newy] = get_next_element(element);
8500 if (!game.magic_wall_active)
8501 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8502 Store[newx][newy] = Store[x][y];
8504 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8506 Feld[x][y] = get_next_element(element);
8507 if (!game.magic_wall_active)
8508 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8509 element = Feld[newx][newy] = Store[x][y];
8511 InitField(newx, newy, FALSE);
8513 else if (element == EL_DC_MAGIC_WALL_FILLING)
8515 element = Feld[newx][newy] = get_next_element(element);
8516 if (!game.magic_wall_active)
8517 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8518 Store[newx][newy] = Store[x][y];
8520 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8522 Feld[x][y] = get_next_element(element);
8523 if (!game.magic_wall_active)
8524 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8525 element = Feld[newx][newy] = Store[x][y];
8527 InitField(newx, newy, FALSE);
8529 else if (element == EL_AMOEBA_DROPPING)
8531 Feld[x][y] = get_next_element(element);
8532 element = Feld[newx][newy] = Store[x][y];
8534 else if (element == EL_SOKOBAN_OBJECT)
8537 Feld[x][y] = Back[x][y];
8539 if (Back[newx][newy])
8540 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8542 Back[x][y] = Back[newx][newy] = 0;
8545 Store[x][y] = EL_EMPTY;
8550 MovDelay[newx][newy] = 0;
8552 if (CAN_CHANGE_OR_HAS_ACTION(element))
8554 // copy element change control values to new field
8555 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8556 ChangePage[newx][newy] = ChangePage[x][y];
8557 ChangeCount[newx][newy] = ChangeCount[x][y];
8558 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8561 CustomValue[newx][newy] = CustomValue[x][y];
8563 ChangeDelay[x][y] = 0;
8564 ChangePage[x][y] = -1;
8565 ChangeCount[x][y] = 0;
8566 ChangeEvent[x][y] = -1;
8568 CustomValue[x][y] = 0;
8570 // copy animation control values to new field
8571 GfxFrame[newx][newy] = GfxFrame[x][y];
8572 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8573 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8574 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8576 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8578 // some elements can leave other elements behind after moving
8579 if (ei->move_leave_element != EL_EMPTY &&
8580 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8581 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8583 int move_leave_element = ei->move_leave_element;
8585 // this makes it possible to leave the removed element again
8586 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8587 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8589 Feld[x][y] = move_leave_element;
8591 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8592 MovDir[x][y] = direction;
8594 InitField(x, y, FALSE);
8596 if (GFX_CRUMBLED(Feld[x][y]))
8597 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8599 if (ELEM_IS_PLAYER(move_leave_element))
8600 RelocatePlayer(x, y, move_leave_element);
8603 // do this after checking for left-behind element
8604 ResetGfxAnimation(x, y); // reset animation values for old field
8606 if (!CAN_MOVE(element) ||
8607 (CAN_FALL(element) && direction == MV_DOWN &&
8608 (element == EL_SPRING ||
8609 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8610 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8611 GfxDir[x][y] = MovDir[newx][newy] = 0;
8613 TEST_DrawLevelField(x, y);
8614 TEST_DrawLevelField(newx, newy);
8616 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8618 // prevent pushed element from moving on in pushed direction
8619 if (pushed_by_player && CAN_MOVE(element) &&
8620 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8621 !(element_info[element].move_pattern & direction))
8622 TurnRound(newx, newy);
8624 // prevent elements on conveyor belt from moving on in last direction
8625 if (pushed_by_conveyor && CAN_FALL(element) &&
8626 direction & MV_HORIZONTAL)
8627 MovDir[newx][newy] = 0;
8629 if (!pushed_by_player)
8631 int nextx = newx + dx, nexty = newy + dy;
8632 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8634 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8636 if (CAN_FALL(element) && direction == MV_DOWN)
8637 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8639 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8640 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8642 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8643 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8646 if (DONT_TOUCH(element)) // object may be nasty to player or others
8648 TestIfBadThingTouchesPlayer(newx, newy);
8649 TestIfBadThingTouchesFriend(newx, newy);
8651 if (!IS_CUSTOM_ELEMENT(element))
8652 TestIfBadThingTouchesOtherBadThing(newx, newy);
8654 else if (element == EL_PENGUIN)
8655 TestIfFriendTouchesBadThing(newx, newy);
8657 if (DONT_GET_HIT_BY(element))
8659 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8662 // give the player one last chance (one more frame) to move away
8663 if (CAN_FALL(element) && direction == MV_DOWN &&
8664 (last_line || (!IS_FREE(x, newy + 1) &&
8665 (!IS_PLAYER(x, newy + 1) ||
8666 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8669 if (pushed_by_player && !game.use_change_when_pushing_bug)
8671 int push_side = MV_DIR_OPPOSITE(direction);
8672 struct PlayerInfo *player = PLAYERINFO(x, y);
8674 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8675 player->index_bit, push_side);
8676 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8677 player->index_bit, push_side);
8680 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8681 MovDelay[newx][newy] = 1;
8683 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8685 TestIfElementTouchesCustomElement(x, y); // empty or new element
8686 TestIfElementHitsCustomElement(newx, newy, direction);
8687 TestIfPlayerTouchesCustomElement(newx, newy);
8688 TestIfElementTouchesCustomElement(newx, newy);
8690 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8691 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8692 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8693 MV_DIR_OPPOSITE(direction));
8696 int AmoebeNachbarNr(int ax, int ay)
8699 int element = Feld[ax][ay];
8701 static int xy[4][2] =
8709 for (i = 0; i < NUM_DIRECTIONS; i++)
8711 int x = ax + xy[i][0];
8712 int y = ay + xy[i][1];
8714 if (!IN_LEV_FIELD(x, y))
8717 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8718 group_nr = AmoebaNr[x][y];
8724 static void AmoebenVereinigen(int ax, int ay)
8726 int i, x, y, xx, yy;
8727 int new_group_nr = AmoebaNr[ax][ay];
8728 static int xy[4][2] =
8736 if (new_group_nr == 0)
8739 for (i = 0; i < NUM_DIRECTIONS; i++)
8744 if (!IN_LEV_FIELD(x, y))
8747 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8748 Feld[x][y] == EL_BD_AMOEBA ||
8749 Feld[x][y] == EL_AMOEBA_DEAD) &&
8750 AmoebaNr[x][y] != new_group_nr)
8752 int old_group_nr = AmoebaNr[x][y];
8754 if (old_group_nr == 0)
8757 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8758 AmoebaCnt[old_group_nr] = 0;
8759 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8760 AmoebaCnt2[old_group_nr] = 0;
8762 SCAN_PLAYFIELD(xx, yy)
8764 if (AmoebaNr[xx][yy] == old_group_nr)
8765 AmoebaNr[xx][yy] = new_group_nr;
8771 void AmoebeUmwandeln(int ax, int ay)
8775 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8777 int group_nr = AmoebaNr[ax][ay];
8782 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8783 printf("AmoebeUmwandeln(): This should never happen!\n");
8788 SCAN_PLAYFIELD(x, y)
8790 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8793 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8797 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8798 SND_AMOEBA_TURNING_TO_GEM :
8799 SND_AMOEBA_TURNING_TO_ROCK));
8804 static int xy[4][2] =
8812 for (i = 0; i < NUM_DIRECTIONS; i++)
8817 if (!IN_LEV_FIELD(x, y))
8820 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8822 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8823 SND_AMOEBA_TURNING_TO_GEM :
8824 SND_AMOEBA_TURNING_TO_ROCK));
8831 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8834 int group_nr = AmoebaNr[ax][ay];
8835 boolean done = FALSE;
8840 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8841 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8846 SCAN_PLAYFIELD(x, y)
8848 if (AmoebaNr[x][y] == group_nr &&
8849 (Feld[x][y] == EL_AMOEBA_DEAD ||
8850 Feld[x][y] == EL_BD_AMOEBA ||
8851 Feld[x][y] == EL_AMOEBA_GROWING))
8854 Feld[x][y] = new_element;
8855 InitField(x, y, FALSE);
8856 TEST_DrawLevelField(x, y);
8862 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8863 SND_BD_AMOEBA_TURNING_TO_ROCK :
8864 SND_BD_AMOEBA_TURNING_TO_GEM));
8867 static void AmoebeWaechst(int x, int y)
8869 static unsigned int sound_delay = 0;
8870 static unsigned int sound_delay_value = 0;
8872 if (!MovDelay[x][y]) // start new growing cycle
8876 if (DelayReached(&sound_delay, sound_delay_value))
8878 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8879 sound_delay_value = 30;
8883 if (MovDelay[x][y]) // wait some time before growing bigger
8886 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8888 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8889 6 - MovDelay[x][y]);
8891 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8894 if (!MovDelay[x][y])
8896 Feld[x][y] = Store[x][y];
8898 TEST_DrawLevelField(x, y);
8903 static void AmoebaDisappearing(int x, int y)
8905 static unsigned int sound_delay = 0;
8906 static unsigned int sound_delay_value = 0;
8908 if (!MovDelay[x][y]) // start new shrinking cycle
8912 if (DelayReached(&sound_delay, sound_delay_value))
8913 sound_delay_value = 30;
8916 if (MovDelay[x][y]) // wait some time before shrinking
8919 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8921 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8922 6 - MovDelay[x][y]);
8924 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8927 if (!MovDelay[x][y])
8929 Feld[x][y] = EL_EMPTY;
8930 TEST_DrawLevelField(x, y);
8932 // don't let mole enter this field in this cycle;
8933 // (give priority to objects falling to this field from above)
8939 static void AmoebeAbleger(int ax, int ay)
8942 int element = Feld[ax][ay];
8943 int graphic = el2img(element);
8944 int newax = ax, neway = ay;
8945 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8946 static int xy[4][2] =
8954 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8956 Feld[ax][ay] = EL_AMOEBA_DEAD;
8957 TEST_DrawLevelField(ax, ay);
8961 if (IS_ANIMATED(graphic))
8962 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8964 if (!MovDelay[ax][ay]) // start making new amoeba field
8965 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8967 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8970 if (MovDelay[ax][ay])
8974 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8977 int x = ax + xy[start][0];
8978 int y = ay + xy[start][1];
8980 if (!IN_LEV_FIELD(x, y))
8983 if (IS_FREE(x, y) ||
8984 CAN_GROW_INTO(Feld[x][y]) ||
8985 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8986 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8992 if (newax == ax && neway == ay)
8995 else // normal or "filled" (BD style) amoeba
8998 boolean waiting_for_player = FALSE;
9000 for (i = 0; i < NUM_DIRECTIONS; i++)
9002 int j = (start + i) % 4;
9003 int x = ax + xy[j][0];
9004 int y = ay + xy[j][1];
9006 if (!IN_LEV_FIELD(x, y))
9009 if (IS_FREE(x, y) ||
9010 CAN_GROW_INTO(Feld[x][y]) ||
9011 Feld[x][y] == EL_QUICKSAND_EMPTY ||
9012 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
9018 else if (IS_PLAYER(x, y))
9019 waiting_for_player = TRUE;
9022 if (newax == ax && neway == ay) // amoeba cannot grow
9024 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9026 Feld[ax][ay] = EL_AMOEBA_DEAD;
9027 TEST_DrawLevelField(ax, ay);
9028 AmoebaCnt[AmoebaNr[ax][ay]]--;
9030 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9032 if (element == EL_AMOEBA_FULL)
9033 AmoebeUmwandeln(ax, ay);
9034 else if (element == EL_BD_AMOEBA)
9035 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
9040 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9042 // amoeba gets larger by growing in some direction
9044 int new_group_nr = AmoebaNr[ax][ay];
9047 if (new_group_nr == 0)
9049 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
9050 printf("AmoebeAbleger(): This should never happen!\n");
9055 AmoebaNr[newax][neway] = new_group_nr;
9056 AmoebaCnt[new_group_nr]++;
9057 AmoebaCnt2[new_group_nr]++;
9059 // if amoeba touches other amoeba(s) after growing, unify them
9060 AmoebenVereinigen(newax, neway);
9062 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9064 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
9070 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9071 (neway == lev_fieldy - 1 && newax != ax))
9073 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9074 Store[newax][neway] = element;
9076 else if (neway == ay || element == EL_EMC_DRIPPER)
9078 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9080 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9084 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9085 Feld[ax][ay] = EL_AMOEBA_DROPPING;
9086 Store[ax][ay] = EL_AMOEBA_DROP;
9087 ContinueMoving(ax, ay);
9091 TEST_DrawLevelField(newax, neway);
9094 static void Life(int ax, int ay)
9098 int element = Feld[ax][ay];
9099 int graphic = el2img(element);
9100 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9102 boolean changed = FALSE;
9104 if (IS_ANIMATED(graphic))
9105 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9110 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9111 MovDelay[ax][ay] = life_time;
9113 if (MovDelay[ax][ay]) // wait some time before next cycle
9116 if (MovDelay[ax][ay])
9120 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9122 int xx = ax+x1, yy = ay+y1;
9123 int old_element = Feld[xx][yy];
9124 int num_neighbours = 0;
9126 if (!IN_LEV_FIELD(xx, yy))
9129 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9131 int x = xx+x2, y = yy+y2;
9133 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9136 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9137 boolean is_neighbour = FALSE;
9139 if (level.use_life_bugs)
9141 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9142 (IS_FREE(x, y) && Stop[x][y]));
9145 (Last[x][y] == element || is_player_cell);
9151 boolean is_free = FALSE;
9153 if (level.use_life_bugs)
9154 is_free = (IS_FREE(xx, yy));
9156 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9158 if (xx == ax && yy == ay) // field in the middle
9160 if (num_neighbours < life_parameter[0] ||
9161 num_neighbours > life_parameter[1])
9163 Feld[xx][yy] = EL_EMPTY;
9164 if (Feld[xx][yy] != old_element)
9165 TEST_DrawLevelField(xx, yy);
9166 Stop[xx][yy] = TRUE;
9170 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9171 { // free border field
9172 if (num_neighbours >= life_parameter[2] &&
9173 num_neighbours <= life_parameter[3])
9175 Feld[xx][yy] = element;
9176 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9177 if (Feld[xx][yy] != old_element)
9178 TEST_DrawLevelField(xx, yy);
9179 Stop[xx][yy] = TRUE;
9186 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9187 SND_GAME_OF_LIFE_GROWING);
9190 static void InitRobotWheel(int x, int y)
9192 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9195 static void RunRobotWheel(int x, int y)
9197 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9200 static void StopRobotWheel(int x, int y)
9202 if (game.robot_wheel_x == x &&
9203 game.robot_wheel_y == y)
9205 game.robot_wheel_x = -1;
9206 game.robot_wheel_y = -1;
9207 game.robot_wheel_active = FALSE;
9211 static void InitTimegateWheel(int x, int y)
9213 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9216 static void RunTimegateWheel(int x, int y)
9218 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9221 static void InitMagicBallDelay(int x, int y)
9223 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9226 static void ActivateMagicBall(int bx, int by)
9230 if (level.ball_random)
9232 int pos_border = RND(8); // select one of the eight border elements
9233 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9234 int xx = pos_content % 3;
9235 int yy = pos_content / 3;
9240 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9241 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9245 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9247 int xx = x - bx + 1;
9248 int yy = y - by + 1;
9250 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9251 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9255 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9258 static void CheckExit(int x, int y)
9260 if (game.gems_still_needed > 0 ||
9261 game.sokoban_fields_still_needed > 0 ||
9262 game.sokoban_objects_still_needed > 0 ||
9263 game.lights_still_needed > 0)
9265 int element = Feld[x][y];
9266 int graphic = el2img(element);
9268 if (IS_ANIMATED(graphic))
9269 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9274 // do not re-open exit door closed after last player
9275 if (game.all_players_gone)
9278 Feld[x][y] = EL_EXIT_OPENING;
9280 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9283 static void CheckExitEM(int x, int y)
9285 if (game.gems_still_needed > 0 ||
9286 game.sokoban_fields_still_needed > 0 ||
9287 game.sokoban_objects_still_needed > 0 ||
9288 game.lights_still_needed > 0)
9290 int element = Feld[x][y];
9291 int graphic = el2img(element);
9293 if (IS_ANIMATED(graphic))
9294 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9299 // do not re-open exit door closed after last player
9300 if (game.all_players_gone)
9303 Feld[x][y] = EL_EM_EXIT_OPENING;
9305 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9308 static void CheckExitSteel(int x, int y)
9310 if (game.gems_still_needed > 0 ||
9311 game.sokoban_fields_still_needed > 0 ||
9312 game.sokoban_objects_still_needed > 0 ||
9313 game.lights_still_needed > 0)
9315 int element = Feld[x][y];
9316 int graphic = el2img(element);
9318 if (IS_ANIMATED(graphic))
9319 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9324 // do not re-open exit door closed after last player
9325 if (game.all_players_gone)
9328 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9330 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9333 static void CheckExitSteelEM(int x, int y)
9335 if (game.gems_still_needed > 0 ||
9336 game.sokoban_fields_still_needed > 0 ||
9337 game.sokoban_objects_still_needed > 0 ||
9338 game.lights_still_needed > 0)
9340 int element = Feld[x][y];
9341 int graphic = el2img(element);
9343 if (IS_ANIMATED(graphic))
9344 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9349 // do not re-open exit door closed after last player
9350 if (game.all_players_gone)
9353 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9355 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9358 static void CheckExitSP(int x, int y)
9360 if (game.gems_still_needed > 0)
9362 int element = Feld[x][y];
9363 int graphic = el2img(element);
9365 if (IS_ANIMATED(graphic))
9366 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9371 // do not re-open exit door closed after last player
9372 if (game.all_players_gone)
9375 Feld[x][y] = EL_SP_EXIT_OPENING;
9377 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9380 static void CloseAllOpenTimegates(void)
9384 SCAN_PLAYFIELD(x, y)
9386 int element = Feld[x][y];
9388 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9390 Feld[x][y] = EL_TIMEGATE_CLOSING;
9392 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9397 static void DrawTwinkleOnField(int x, int y)
9399 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9402 if (Feld[x][y] == EL_BD_DIAMOND)
9405 if (MovDelay[x][y] == 0) // next animation frame
9406 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9408 if (MovDelay[x][y] != 0) // wait some time before next frame
9412 DrawLevelElementAnimation(x, y, Feld[x][y]);
9414 if (MovDelay[x][y] != 0)
9416 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9417 10 - MovDelay[x][y]);
9419 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9424 static void MauerWaechst(int x, int y)
9428 if (!MovDelay[x][y]) // next animation frame
9429 MovDelay[x][y] = 3 * delay;
9431 if (MovDelay[x][y]) // wait some time before next frame
9435 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9437 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9438 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9440 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9443 if (!MovDelay[x][y])
9445 if (MovDir[x][y] == MV_LEFT)
9447 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9448 TEST_DrawLevelField(x - 1, y);
9450 else if (MovDir[x][y] == MV_RIGHT)
9452 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9453 TEST_DrawLevelField(x + 1, y);
9455 else if (MovDir[x][y] == MV_UP)
9457 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9458 TEST_DrawLevelField(x, y - 1);
9462 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9463 TEST_DrawLevelField(x, y + 1);
9466 Feld[x][y] = Store[x][y];
9468 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9469 TEST_DrawLevelField(x, y);
9474 static void MauerAbleger(int ax, int ay)
9476 int element = Feld[ax][ay];
9477 int graphic = el2img(element);
9478 boolean oben_frei = FALSE, unten_frei = FALSE;
9479 boolean links_frei = FALSE, rechts_frei = FALSE;
9480 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9481 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9482 boolean new_wall = FALSE;
9484 if (IS_ANIMATED(graphic))
9485 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9487 if (!MovDelay[ax][ay]) // start building new wall
9488 MovDelay[ax][ay] = 6;
9490 if (MovDelay[ax][ay]) // wait some time before building new wall
9493 if (MovDelay[ax][ay])
9497 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9499 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9501 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9503 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9506 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9507 element == EL_EXPANDABLE_WALL_ANY)
9511 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9512 Store[ax][ay-1] = element;
9513 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9514 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9515 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9516 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9521 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9522 Store[ax][ay+1] = element;
9523 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9524 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9525 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9526 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9531 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9532 element == EL_EXPANDABLE_WALL_ANY ||
9533 element == EL_EXPANDABLE_WALL ||
9534 element == EL_BD_EXPANDABLE_WALL)
9538 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9539 Store[ax-1][ay] = element;
9540 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9541 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9542 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9543 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9549 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9550 Store[ax+1][ay] = element;
9551 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9552 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9553 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9554 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9559 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9560 TEST_DrawLevelField(ax, ay);
9562 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9564 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9565 unten_massiv = TRUE;
9566 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9567 links_massiv = TRUE;
9568 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9569 rechts_massiv = TRUE;
9571 if (((oben_massiv && unten_massiv) ||
9572 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9573 element == EL_EXPANDABLE_WALL) &&
9574 ((links_massiv && rechts_massiv) ||
9575 element == EL_EXPANDABLE_WALL_VERTICAL))
9576 Feld[ax][ay] = EL_WALL;
9579 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9582 static void MauerAblegerStahl(int ax, int ay)
9584 int element = Feld[ax][ay];
9585 int graphic = el2img(element);
9586 boolean oben_frei = FALSE, unten_frei = FALSE;
9587 boolean links_frei = FALSE, rechts_frei = FALSE;
9588 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9589 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9590 boolean new_wall = FALSE;
9592 if (IS_ANIMATED(graphic))
9593 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9595 if (!MovDelay[ax][ay]) // start building new wall
9596 MovDelay[ax][ay] = 6;
9598 if (MovDelay[ax][ay]) // wait some time before building new wall
9601 if (MovDelay[ax][ay])
9605 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9607 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9609 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9611 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9614 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9615 element == EL_EXPANDABLE_STEELWALL_ANY)
9619 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9620 Store[ax][ay-1] = element;
9621 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9622 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9623 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9624 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9629 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9630 Store[ax][ay+1] = element;
9631 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9632 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9633 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9634 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9639 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9640 element == EL_EXPANDABLE_STEELWALL_ANY)
9644 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9645 Store[ax-1][ay] = element;
9646 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9647 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9648 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9649 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9655 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9656 Store[ax+1][ay] = element;
9657 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9658 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9659 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9660 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9665 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9667 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9668 unten_massiv = TRUE;
9669 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9670 links_massiv = TRUE;
9671 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9672 rechts_massiv = TRUE;
9674 if (((oben_massiv && unten_massiv) ||
9675 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9676 ((links_massiv && rechts_massiv) ||
9677 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9678 Feld[ax][ay] = EL_STEELWALL;
9681 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9684 static void CheckForDragon(int x, int y)
9687 boolean dragon_found = FALSE;
9688 static int xy[4][2] =
9696 for (i = 0; i < NUM_DIRECTIONS; i++)
9698 for (j = 0; j < 4; j++)
9700 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9702 if (IN_LEV_FIELD(xx, yy) &&
9703 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9705 if (Feld[xx][yy] == EL_DRAGON)
9706 dragon_found = TRUE;
9715 for (i = 0; i < NUM_DIRECTIONS; i++)
9717 for (j = 0; j < 3; j++)
9719 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9721 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9723 Feld[xx][yy] = EL_EMPTY;
9724 TEST_DrawLevelField(xx, yy);
9733 static void InitBuggyBase(int x, int y)
9735 int element = Feld[x][y];
9736 int activating_delay = FRAMES_PER_SECOND / 4;
9739 (element == EL_SP_BUGGY_BASE ?
9740 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9741 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9743 element == EL_SP_BUGGY_BASE_ACTIVE ?
9744 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9747 static void WarnBuggyBase(int x, int y)
9750 static int xy[4][2] =
9758 for (i = 0; i < NUM_DIRECTIONS; i++)
9760 int xx = x + xy[i][0];
9761 int yy = y + xy[i][1];
9763 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9765 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9772 static void InitTrap(int x, int y)
9774 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9777 static void ActivateTrap(int x, int y)
9779 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9782 static void ChangeActiveTrap(int x, int y)
9784 int graphic = IMG_TRAP_ACTIVE;
9786 // if new animation frame was drawn, correct crumbled sand border
9787 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9788 TEST_DrawLevelFieldCrumbled(x, y);
9791 static int getSpecialActionElement(int element, int number, int base_element)
9793 return (element != EL_EMPTY ? element :
9794 number != -1 ? base_element + number - 1 :
9798 static int getModifiedActionNumber(int value_old, int operator, int operand,
9799 int value_min, int value_max)
9801 int value_new = (operator == CA_MODE_SET ? operand :
9802 operator == CA_MODE_ADD ? value_old + operand :
9803 operator == CA_MODE_SUBTRACT ? value_old - operand :
9804 operator == CA_MODE_MULTIPLY ? value_old * operand :
9805 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9806 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9809 return (value_new < value_min ? value_min :
9810 value_new > value_max ? value_max :
9814 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9816 struct ElementInfo *ei = &element_info[element];
9817 struct ElementChangeInfo *change = &ei->change_page[page];
9818 int target_element = change->target_element;
9819 int action_type = change->action_type;
9820 int action_mode = change->action_mode;
9821 int action_arg = change->action_arg;
9822 int action_element = change->action_element;
9825 if (!change->has_action)
9828 // ---------- determine action paramater values -----------------------------
9830 int level_time_value =
9831 (level.time > 0 ? TimeLeft :
9834 int action_arg_element_raw =
9835 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9836 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9837 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9838 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9839 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9840 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9841 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9843 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9845 int action_arg_direction =
9846 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9847 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9848 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9849 change->actual_trigger_side :
9850 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9851 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9854 int action_arg_number_min =
9855 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9858 int action_arg_number_max =
9859 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9860 action_type == CA_SET_LEVEL_GEMS ? 999 :
9861 action_type == CA_SET_LEVEL_TIME ? 9999 :
9862 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9863 action_type == CA_SET_CE_VALUE ? 9999 :
9864 action_type == CA_SET_CE_SCORE ? 9999 :
9867 int action_arg_number_reset =
9868 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9869 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9870 action_type == CA_SET_LEVEL_TIME ? level.time :
9871 action_type == CA_SET_LEVEL_SCORE ? 0 :
9872 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9873 action_type == CA_SET_CE_SCORE ? 0 :
9876 int action_arg_number =
9877 (action_arg <= CA_ARG_MAX ? action_arg :
9878 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9879 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9880 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9881 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9882 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9883 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9884 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9885 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9886 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9887 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9888 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9889 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9890 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9891 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9892 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9893 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9894 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9895 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9896 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9897 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9898 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9901 int action_arg_number_old =
9902 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9903 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9904 action_type == CA_SET_LEVEL_SCORE ? game.score :
9905 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9906 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9909 int action_arg_number_new =
9910 getModifiedActionNumber(action_arg_number_old,
9911 action_mode, action_arg_number,
9912 action_arg_number_min, action_arg_number_max);
9914 int trigger_player_bits =
9915 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9916 change->actual_trigger_player_bits : change->trigger_player);
9918 int action_arg_player_bits =
9919 (action_arg >= CA_ARG_PLAYER_1 &&
9920 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9921 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9922 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9925 // ---------- execute action -----------------------------------------------
9927 switch (action_type)
9934 // ---------- level actions ----------------------------------------------
9936 case CA_RESTART_LEVEL:
9938 game.restart_level = TRUE;
9943 case CA_SHOW_ENVELOPE:
9945 int element = getSpecialActionElement(action_arg_element,
9946 action_arg_number, EL_ENVELOPE_1);
9948 if (IS_ENVELOPE(element))
9949 local_player->show_envelope = element;
9954 case CA_SET_LEVEL_TIME:
9956 if (level.time > 0) // only modify limited time value
9958 TimeLeft = action_arg_number_new;
9960 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9962 DisplayGameControlValues();
9964 if (!TimeLeft && setup.time_limit)
9965 for (i = 0; i < MAX_PLAYERS; i++)
9966 KillPlayer(&stored_player[i]);
9972 case CA_SET_LEVEL_SCORE:
9974 game.score = action_arg_number_new;
9976 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9978 DisplayGameControlValues();
9983 case CA_SET_LEVEL_GEMS:
9985 game.gems_still_needed = action_arg_number_new;
9987 game.snapshot.collected_item = TRUE;
9989 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9991 DisplayGameControlValues();
9996 case CA_SET_LEVEL_WIND:
9998 game.wind_direction = action_arg_direction;
10003 case CA_SET_LEVEL_RANDOM_SEED:
10005 // ensure that setting a new random seed while playing is predictable
10006 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10011 // ---------- player actions ---------------------------------------------
10013 case CA_MOVE_PLAYER:
10014 case CA_MOVE_PLAYER_NEW:
10016 // automatically move to the next field in specified direction
10017 for (i = 0; i < MAX_PLAYERS; i++)
10018 if (trigger_player_bits & (1 << i))
10019 if (action_type == CA_MOVE_PLAYER ||
10020 stored_player[i].MovPos == 0)
10021 stored_player[i].programmed_action = action_arg_direction;
10026 case CA_EXIT_PLAYER:
10028 for (i = 0; i < MAX_PLAYERS; i++)
10029 if (action_arg_player_bits & (1 << i))
10030 ExitPlayer(&stored_player[i]);
10032 if (game.players_still_needed == 0)
10038 case CA_KILL_PLAYER:
10040 for (i = 0; i < MAX_PLAYERS; i++)
10041 if (action_arg_player_bits & (1 << i))
10042 KillPlayer(&stored_player[i]);
10047 case CA_SET_PLAYER_KEYS:
10049 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10050 int element = getSpecialActionElement(action_arg_element,
10051 action_arg_number, EL_KEY_1);
10053 if (IS_KEY(element))
10055 for (i = 0; i < MAX_PLAYERS; i++)
10057 if (trigger_player_bits & (1 << i))
10059 stored_player[i].key[KEY_NR(element)] = key_state;
10061 DrawGameDoorValues();
10069 case CA_SET_PLAYER_SPEED:
10071 for (i = 0; i < MAX_PLAYERS; i++)
10073 if (trigger_player_bits & (1 << i))
10075 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10077 if (action_arg == CA_ARG_SPEED_FASTER &&
10078 stored_player[i].cannot_move)
10080 action_arg_number = STEPSIZE_VERY_SLOW;
10082 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10083 action_arg == CA_ARG_SPEED_FASTER)
10085 action_arg_number = 2;
10086 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10089 else if (action_arg == CA_ARG_NUMBER_RESET)
10091 action_arg_number = level.initial_player_stepsize[i];
10095 getModifiedActionNumber(move_stepsize,
10098 action_arg_number_min,
10099 action_arg_number_max);
10101 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10108 case CA_SET_PLAYER_SHIELD:
10110 for (i = 0; i < MAX_PLAYERS; i++)
10112 if (trigger_player_bits & (1 << i))
10114 if (action_arg == CA_ARG_SHIELD_OFF)
10116 stored_player[i].shield_normal_time_left = 0;
10117 stored_player[i].shield_deadly_time_left = 0;
10119 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10121 stored_player[i].shield_normal_time_left = 999999;
10123 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10125 stored_player[i].shield_normal_time_left = 999999;
10126 stored_player[i].shield_deadly_time_left = 999999;
10134 case CA_SET_PLAYER_GRAVITY:
10136 for (i = 0; i < MAX_PLAYERS; i++)
10138 if (trigger_player_bits & (1 << i))
10140 stored_player[i].gravity =
10141 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10142 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10143 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10144 stored_player[i].gravity);
10151 case CA_SET_PLAYER_ARTWORK:
10153 for (i = 0; i < MAX_PLAYERS; i++)
10155 if (trigger_player_bits & (1 << i))
10157 int artwork_element = action_arg_element;
10159 if (action_arg == CA_ARG_ELEMENT_RESET)
10161 (level.use_artwork_element[i] ? level.artwork_element[i] :
10162 stored_player[i].element_nr);
10164 if (stored_player[i].artwork_element != artwork_element)
10165 stored_player[i].Frame = 0;
10167 stored_player[i].artwork_element = artwork_element;
10169 SetPlayerWaiting(&stored_player[i], FALSE);
10171 // set number of special actions for bored and sleeping animation
10172 stored_player[i].num_special_action_bored =
10173 get_num_special_action(artwork_element,
10174 ACTION_BORING_1, ACTION_BORING_LAST);
10175 stored_player[i].num_special_action_sleeping =
10176 get_num_special_action(artwork_element,
10177 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10184 case CA_SET_PLAYER_INVENTORY:
10186 for (i = 0; i < MAX_PLAYERS; i++)
10188 struct PlayerInfo *player = &stored_player[i];
10191 if (trigger_player_bits & (1 << i))
10193 int inventory_element = action_arg_element;
10195 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10196 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10197 action_arg == CA_ARG_ELEMENT_ACTION)
10199 int element = inventory_element;
10200 int collect_count = element_info[element].collect_count_initial;
10202 if (!IS_CUSTOM_ELEMENT(element))
10205 if (collect_count == 0)
10206 player->inventory_infinite_element = element;
10208 for (k = 0; k < collect_count; k++)
10209 if (player->inventory_size < MAX_INVENTORY_SIZE)
10210 player->inventory_element[player->inventory_size++] =
10213 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10214 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10215 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10217 if (player->inventory_infinite_element != EL_UNDEFINED &&
10218 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10219 action_arg_element_raw))
10220 player->inventory_infinite_element = EL_UNDEFINED;
10222 for (k = 0, j = 0; j < player->inventory_size; j++)
10224 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10225 action_arg_element_raw))
10226 player->inventory_element[k++] = player->inventory_element[j];
10229 player->inventory_size = k;
10231 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10233 if (player->inventory_size > 0)
10235 for (j = 0; j < player->inventory_size - 1; j++)
10236 player->inventory_element[j] = player->inventory_element[j + 1];
10238 player->inventory_size--;
10241 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10243 if (player->inventory_size > 0)
10244 player->inventory_size--;
10246 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10248 player->inventory_infinite_element = EL_UNDEFINED;
10249 player->inventory_size = 0;
10251 else if (action_arg == CA_ARG_INVENTORY_RESET)
10253 player->inventory_infinite_element = EL_UNDEFINED;
10254 player->inventory_size = 0;
10256 if (level.use_initial_inventory[i])
10258 for (j = 0; j < level.initial_inventory_size[i]; j++)
10260 int element = level.initial_inventory_content[i][j];
10261 int collect_count = element_info[element].collect_count_initial;
10263 if (!IS_CUSTOM_ELEMENT(element))
10266 if (collect_count == 0)
10267 player->inventory_infinite_element = element;
10269 for (k = 0; k < collect_count; k++)
10270 if (player->inventory_size < MAX_INVENTORY_SIZE)
10271 player->inventory_element[player->inventory_size++] =
10282 // ---------- CE actions -------------------------------------------------
10284 case CA_SET_CE_VALUE:
10286 int last_ce_value = CustomValue[x][y];
10288 CustomValue[x][y] = action_arg_number_new;
10290 if (CustomValue[x][y] != last_ce_value)
10292 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10293 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10295 if (CustomValue[x][y] == 0)
10297 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10298 ChangeCount[x][y] = 0; // allow at least one more change
10300 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10301 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10308 case CA_SET_CE_SCORE:
10310 int last_ce_score = ei->collect_score;
10312 ei->collect_score = action_arg_number_new;
10314 if (ei->collect_score != last_ce_score)
10316 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10317 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10319 if (ei->collect_score == 0)
10323 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10324 ChangeCount[x][y] = 0; // allow at least one more change
10326 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10327 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10330 This is a very special case that seems to be a mixture between
10331 CheckElementChange() and CheckTriggeredElementChange(): while
10332 the first one only affects single elements that are triggered
10333 directly, the second one affects multiple elements in the playfield
10334 that are triggered indirectly by another element. This is a third
10335 case: Changing the CE score always affects multiple identical CEs,
10336 so every affected CE must be checked, not only the single CE for
10337 which the CE score was changed in the first place (as every instance
10338 of that CE shares the same CE score, and therefore also can change)!
10340 SCAN_PLAYFIELD(xx, yy)
10342 if (Feld[xx][yy] == element)
10343 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10344 CE_SCORE_GETS_ZERO);
10352 case CA_SET_CE_ARTWORK:
10354 int artwork_element = action_arg_element;
10355 boolean reset_frame = FALSE;
10358 if (action_arg == CA_ARG_ELEMENT_RESET)
10359 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10362 if (ei->gfx_element != artwork_element)
10363 reset_frame = TRUE;
10365 ei->gfx_element = artwork_element;
10367 SCAN_PLAYFIELD(xx, yy)
10369 if (Feld[xx][yy] == element)
10373 ResetGfxAnimation(xx, yy);
10374 ResetRandomAnimationValue(xx, yy);
10377 TEST_DrawLevelField(xx, yy);
10384 // ---------- engine actions ---------------------------------------------
10386 case CA_SET_ENGINE_SCAN_MODE:
10388 InitPlayfieldScanMode(action_arg);
10398 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10400 int old_element = Feld[x][y];
10401 int new_element = GetElementFromGroupElement(element);
10402 int previous_move_direction = MovDir[x][y];
10403 int last_ce_value = CustomValue[x][y];
10404 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10405 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10406 boolean add_player_onto_element = (new_element_is_player &&
10407 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10408 IS_WALKABLE(old_element));
10410 if (!add_player_onto_element)
10412 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10413 RemoveMovingField(x, y);
10417 Feld[x][y] = new_element;
10419 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10420 MovDir[x][y] = previous_move_direction;
10422 if (element_info[new_element].use_last_ce_value)
10423 CustomValue[x][y] = last_ce_value;
10425 InitField_WithBug1(x, y, FALSE);
10427 new_element = Feld[x][y]; // element may have changed
10429 ResetGfxAnimation(x, y);
10430 ResetRandomAnimationValue(x, y);
10432 TEST_DrawLevelField(x, y);
10434 if (GFX_CRUMBLED(new_element))
10435 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10438 // check if element under the player changes from accessible to unaccessible
10439 // (needed for special case of dropping element which then changes)
10440 // (must be checked after creating new element for walkable group elements)
10441 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10442 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10449 // "ChangeCount" not set yet to allow "entered by player" change one time
10450 if (new_element_is_player)
10451 RelocatePlayer(x, y, new_element);
10454 ChangeCount[x][y]++; // count number of changes in the same frame
10456 TestIfBadThingTouchesPlayer(x, y);
10457 TestIfPlayerTouchesCustomElement(x, y);
10458 TestIfElementTouchesCustomElement(x, y);
10461 static void CreateField(int x, int y, int element)
10463 CreateFieldExt(x, y, element, FALSE);
10466 static void CreateElementFromChange(int x, int y, int element)
10468 element = GET_VALID_RUNTIME_ELEMENT(element);
10470 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10472 int old_element = Feld[x][y];
10474 // prevent changed element from moving in same engine frame
10475 // unless both old and new element can either fall or move
10476 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10477 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10481 CreateFieldExt(x, y, element, TRUE);
10484 static boolean ChangeElement(int x, int y, int element, int page)
10486 struct ElementInfo *ei = &element_info[element];
10487 struct ElementChangeInfo *change = &ei->change_page[page];
10488 int ce_value = CustomValue[x][y];
10489 int ce_score = ei->collect_score;
10490 int target_element;
10491 int old_element = Feld[x][y];
10493 // always use default change event to prevent running into a loop
10494 if (ChangeEvent[x][y] == -1)
10495 ChangeEvent[x][y] = CE_DELAY;
10497 if (ChangeEvent[x][y] == CE_DELAY)
10499 // reset actual trigger element, trigger player and action element
10500 change->actual_trigger_element = EL_EMPTY;
10501 change->actual_trigger_player = EL_EMPTY;
10502 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10503 change->actual_trigger_side = CH_SIDE_NONE;
10504 change->actual_trigger_ce_value = 0;
10505 change->actual_trigger_ce_score = 0;
10508 // do not change elements more than a specified maximum number of changes
10509 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10512 ChangeCount[x][y]++; // count number of changes in the same frame
10514 if (change->explode)
10521 if (change->use_target_content)
10523 boolean complete_replace = TRUE;
10524 boolean can_replace[3][3];
10527 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10530 boolean is_walkable;
10531 boolean is_diggable;
10532 boolean is_collectible;
10533 boolean is_removable;
10534 boolean is_destructible;
10535 int ex = x + xx - 1;
10536 int ey = y + yy - 1;
10537 int content_element = change->target_content.e[xx][yy];
10540 can_replace[xx][yy] = TRUE;
10542 if (ex == x && ey == y) // do not check changing element itself
10545 if (content_element == EL_EMPTY_SPACE)
10547 can_replace[xx][yy] = FALSE; // do not replace border with space
10552 if (!IN_LEV_FIELD(ex, ey))
10554 can_replace[xx][yy] = FALSE;
10555 complete_replace = FALSE;
10562 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10563 e = MovingOrBlocked2Element(ex, ey);
10565 is_empty = (IS_FREE(ex, ey) ||
10566 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10568 is_walkable = (is_empty || IS_WALKABLE(e));
10569 is_diggable = (is_empty || IS_DIGGABLE(e));
10570 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10571 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10572 is_removable = (is_diggable || is_collectible);
10574 can_replace[xx][yy] =
10575 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10576 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10577 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10578 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10579 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10580 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10581 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10583 if (!can_replace[xx][yy])
10584 complete_replace = FALSE;
10587 if (!change->only_if_complete || complete_replace)
10589 boolean something_has_changed = FALSE;
10591 if (change->only_if_complete && change->use_random_replace &&
10592 RND(100) < change->random_percentage)
10595 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10597 int ex = x + xx - 1;
10598 int ey = y + yy - 1;
10599 int content_element;
10601 if (can_replace[xx][yy] && (!change->use_random_replace ||
10602 RND(100) < change->random_percentage))
10604 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10605 RemoveMovingField(ex, ey);
10607 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10609 content_element = change->target_content.e[xx][yy];
10610 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10611 ce_value, ce_score);
10613 CreateElementFromChange(ex, ey, target_element);
10615 something_has_changed = TRUE;
10617 // for symmetry reasons, freeze newly created border elements
10618 if (ex != x || ey != y)
10619 Stop[ex][ey] = TRUE; // no more moving in this frame
10623 if (something_has_changed)
10625 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10626 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10632 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10633 ce_value, ce_score);
10635 if (element == EL_DIAGONAL_GROWING ||
10636 element == EL_DIAGONAL_SHRINKING)
10638 target_element = Store[x][y];
10640 Store[x][y] = EL_EMPTY;
10643 CreateElementFromChange(x, y, target_element);
10645 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10646 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10649 // this uses direct change before indirect change
10650 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10655 static void HandleElementChange(int x, int y, int page)
10657 int element = MovingOrBlocked2Element(x, y);
10658 struct ElementInfo *ei = &element_info[element];
10659 struct ElementChangeInfo *change = &ei->change_page[page];
10660 boolean handle_action_before_change = FALSE;
10663 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10664 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10667 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10668 x, y, element, element_info[element].token_name);
10669 printf("HandleElementChange(): This should never happen!\n");
10674 // this can happen with classic bombs on walkable, changing elements
10675 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10680 if (ChangeDelay[x][y] == 0) // initialize element change
10682 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10684 if (change->can_change)
10686 // !!! not clear why graphic animation should be reset at all here !!!
10687 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10688 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10691 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10693 When using an animation frame delay of 1 (this only happens with
10694 "sp_zonk.moving.left/right" in the classic graphics), the default
10695 (non-moving) animation shows wrong animation frames (while the
10696 moving animation, like "sp_zonk.moving.left/right", is correct,
10697 so this graphical bug never shows up with the classic graphics).
10698 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10699 be drawn instead of the correct frames 0,1,2,3. This is caused by
10700 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10701 an element change: First when the change delay ("ChangeDelay[][]")
10702 counter has reached zero after decrementing, then a second time in
10703 the next frame (after "GfxFrame[][]" was already incremented) when
10704 "ChangeDelay[][]" is reset to the initial delay value again.
10706 This causes frame 0 to be drawn twice, while the last frame won't
10707 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10709 As some animations may already be cleverly designed around this bug
10710 (at least the "Snake Bite" snake tail animation does this), it cannot
10711 simply be fixed here without breaking such existing animations.
10712 Unfortunately, it cannot easily be detected if a graphics set was
10713 designed "before" or "after" the bug was fixed. As a workaround,
10714 a new graphics set option "game.graphics_engine_version" was added
10715 to be able to specify the game's major release version for which the
10716 graphics set was designed, which can then be used to decide if the
10717 bugfix should be used (version 4 and above) or not (version 3 or
10718 below, or if no version was specified at all, as with old sets).
10720 (The wrong/fixed animation frames can be tested with the test level set
10721 "test_gfxframe" and level "000", which contains a specially prepared
10722 custom element at level position (x/y) == (11/9) which uses the zonk
10723 animation mentioned above. Using "game.graphics_engine_version: 4"
10724 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10725 This can also be seen from the debug output for this test element.)
10728 // when a custom element is about to change (for example by change delay),
10729 // do not reset graphic animation when the custom element is moving
10730 if (game.graphics_engine_version < 4 &&
10733 ResetGfxAnimation(x, y);
10734 ResetRandomAnimationValue(x, y);
10737 if (change->pre_change_function)
10738 change->pre_change_function(x, y);
10742 ChangeDelay[x][y]--;
10744 if (ChangeDelay[x][y] != 0) // continue element change
10746 if (change->can_change)
10748 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10750 if (IS_ANIMATED(graphic))
10751 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10753 if (change->change_function)
10754 change->change_function(x, y);
10757 else // finish element change
10759 if (ChangePage[x][y] != -1) // remember page from delayed change
10761 page = ChangePage[x][y];
10762 ChangePage[x][y] = -1;
10764 change = &ei->change_page[page];
10767 if (IS_MOVING(x, y)) // never change a running system ;-)
10769 ChangeDelay[x][y] = 1; // try change after next move step
10770 ChangePage[x][y] = page; // remember page to use for change
10775 // special case: set new level random seed before changing element
10776 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10777 handle_action_before_change = TRUE;
10779 if (change->has_action && handle_action_before_change)
10780 ExecuteCustomElementAction(x, y, element, page);
10782 if (change->can_change)
10784 if (ChangeElement(x, y, element, page))
10786 if (change->post_change_function)
10787 change->post_change_function(x, y);
10791 if (change->has_action && !handle_action_before_change)
10792 ExecuteCustomElementAction(x, y, element, page);
10796 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10797 int trigger_element,
10799 int trigger_player,
10803 boolean change_done_any = FALSE;
10804 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10807 if (!(trigger_events[trigger_element][trigger_event]))
10810 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10812 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10814 int element = EL_CUSTOM_START + i;
10815 boolean change_done = FALSE;
10818 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10819 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10822 for (p = 0; p < element_info[element].num_change_pages; p++)
10824 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10826 if (change->can_change_or_has_action &&
10827 change->has_event[trigger_event] &&
10828 change->trigger_side & trigger_side &&
10829 change->trigger_player & trigger_player &&
10830 change->trigger_page & trigger_page_bits &&
10831 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10833 change->actual_trigger_element = trigger_element;
10834 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10835 change->actual_trigger_player_bits = trigger_player;
10836 change->actual_trigger_side = trigger_side;
10837 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10838 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10840 if ((change->can_change && !change_done) || change->has_action)
10844 SCAN_PLAYFIELD(x, y)
10846 if (Feld[x][y] == element)
10848 if (change->can_change && !change_done)
10850 // if element already changed in this frame, not only prevent
10851 // another element change (checked in ChangeElement()), but
10852 // also prevent additional element actions for this element
10854 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10855 !level.use_action_after_change_bug)
10858 ChangeDelay[x][y] = 1;
10859 ChangeEvent[x][y] = trigger_event;
10861 HandleElementChange(x, y, p);
10863 else if (change->has_action)
10865 // if element already changed in this frame, not only prevent
10866 // another element change (checked in ChangeElement()), but
10867 // also prevent additional element actions for this element
10869 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10870 !level.use_action_after_change_bug)
10873 ExecuteCustomElementAction(x, y, element, p);
10874 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10879 if (change->can_change)
10881 change_done = TRUE;
10882 change_done_any = TRUE;
10889 RECURSION_LOOP_DETECTION_END();
10891 return change_done_any;
10894 static boolean CheckElementChangeExt(int x, int y,
10896 int trigger_element,
10898 int trigger_player,
10901 boolean change_done = FALSE;
10904 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10905 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10908 if (Feld[x][y] == EL_BLOCKED)
10910 Blocked2Moving(x, y, &x, &y);
10911 element = Feld[x][y];
10914 // check if element has already changed or is about to change after moving
10915 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10916 Feld[x][y] != element) ||
10918 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10919 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10920 ChangePage[x][y] != -1)))
10923 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10925 for (p = 0; p < element_info[element].num_change_pages; p++)
10927 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10929 /* check trigger element for all events where the element that is checked
10930 for changing interacts with a directly adjacent element -- this is
10931 different to element changes that affect other elements to change on the
10932 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10933 boolean check_trigger_element =
10934 (trigger_event == CE_TOUCHING_X ||
10935 trigger_event == CE_HITTING_X ||
10936 trigger_event == CE_HIT_BY_X ||
10937 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10939 if (change->can_change_or_has_action &&
10940 change->has_event[trigger_event] &&
10941 change->trigger_side & trigger_side &&
10942 change->trigger_player & trigger_player &&
10943 (!check_trigger_element ||
10944 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10946 change->actual_trigger_element = trigger_element;
10947 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10948 change->actual_trigger_player_bits = trigger_player;
10949 change->actual_trigger_side = trigger_side;
10950 change->actual_trigger_ce_value = CustomValue[x][y];
10951 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10953 // special case: trigger element not at (x,y) position for some events
10954 if (check_trigger_element)
10966 { 0, 0 }, { 0, 0 }, { 0, 0 },
10970 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10971 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10973 change->actual_trigger_ce_value = CustomValue[xx][yy];
10974 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10977 if (change->can_change && !change_done)
10979 ChangeDelay[x][y] = 1;
10980 ChangeEvent[x][y] = trigger_event;
10982 HandleElementChange(x, y, p);
10984 change_done = TRUE;
10986 else if (change->has_action)
10988 ExecuteCustomElementAction(x, y, element, p);
10989 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10994 RECURSION_LOOP_DETECTION_END();
10996 return change_done;
10999 static void PlayPlayerSound(struct PlayerInfo *player)
11001 int jx = player->jx, jy = player->jy;
11002 int sound_element = player->artwork_element;
11003 int last_action = player->last_action_waiting;
11004 int action = player->action_waiting;
11006 if (player->is_waiting)
11008 if (action != last_action)
11009 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11011 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11015 if (action != last_action)
11016 StopSound(element_info[sound_element].sound[last_action]);
11018 if (last_action == ACTION_SLEEPING)
11019 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11023 static void PlayAllPlayersSound(void)
11027 for (i = 0; i < MAX_PLAYERS; i++)
11028 if (stored_player[i].active)
11029 PlayPlayerSound(&stored_player[i]);
11032 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11034 boolean last_waiting = player->is_waiting;
11035 int move_dir = player->MovDir;
11037 player->dir_waiting = move_dir;
11038 player->last_action_waiting = player->action_waiting;
11042 if (!last_waiting) // not waiting -> waiting
11044 player->is_waiting = TRUE;
11046 player->frame_counter_bored =
11048 game.player_boring_delay_fixed +
11049 GetSimpleRandom(game.player_boring_delay_random);
11050 player->frame_counter_sleeping =
11052 game.player_sleeping_delay_fixed +
11053 GetSimpleRandom(game.player_sleeping_delay_random);
11055 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11058 if (game.player_sleeping_delay_fixed +
11059 game.player_sleeping_delay_random > 0 &&
11060 player->anim_delay_counter == 0 &&
11061 player->post_delay_counter == 0 &&
11062 FrameCounter >= player->frame_counter_sleeping)
11063 player->is_sleeping = TRUE;
11064 else if (game.player_boring_delay_fixed +
11065 game.player_boring_delay_random > 0 &&
11066 FrameCounter >= player->frame_counter_bored)
11067 player->is_bored = TRUE;
11069 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11070 player->is_bored ? ACTION_BORING :
11073 if (player->is_sleeping && player->use_murphy)
11075 // special case for sleeping Murphy when leaning against non-free tile
11077 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11078 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
11079 !IS_MOVING(player->jx - 1, player->jy)))
11080 move_dir = MV_LEFT;
11081 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11082 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
11083 !IS_MOVING(player->jx + 1, player->jy)))
11084 move_dir = MV_RIGHT;
11086 player->is_sleeping = FALSE;
11088 player->dir_waiting = move_dir;
11091 if (player->is_sleeping)
11093 if (player->num_special_action_sleeping > 0)
11095 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11097 int last_special_action = player->special_action_sleeping;
11098 int num_special_action = player->num_special_action_sleeping;
11099 int special_action =
11100 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11101 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11102 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11103 last_special_action + 1 : ACTION_SLEEPING);
11104 int special_graphic =
11105 el_act_dir2img(player->artwork_element, special_action, move_dir);
11107 player->anim_delay_counter =
11108 graphic_info[special_graphic].anim_delay_fixed +
11109 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11110 player->post_delay_counter =
11111 graphic_info[special_graphic].post_delay_fixed +
11112 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11114 player->special_action_sleeping = special_action;
11117 if (player->anim_delay_counter > 0)
11119 player->action_waiting = player->special_action_sleeping;
11120 player->anim_delay_counter--;
11122 else if (player->post_delay_counter > 0)
11124 player->post_delay_counter--;
11128 else if (player->is_bored)
11130 if (player->num_special_action_bored > 0)
11132 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11134 int special_action =
11135 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11136 int special_graphic =
11137 el_act_dir2img(player->artwork_element, special_action, move_dir);
11139 player->anim_delay_counter =
11140 graphic_info[special_graphic].anim_delay_fixed +
11141 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11142 player->post_delay_counter =
11143 graphic_info[special_graphic].post_delay_fixed +
11144 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11146 player->special_action_bored = special_action;
11149 if (player->anim_delay_counter > 0)
11151 player->action_waiting = player->special_action_bored;
11152 player->anim_delay_counter--;
11154 else if (player->post_delay_counter > 0)
11156 player->post_delay_counter--;
11161 else if (last_waiting) // waiting -> not waiting
11163 player->is_waiting = FALSE;
11164 player->is_bored = FALSE;
11165 player->is_sleeping = FALSE;
11167 player->frame_counter_bored = -1;
11168 player->frame_counter_sleeping = -1;
11170 player->anim_delay_counter = 0;
11171 player->post_delay_counter = 0;
11173 player->dir_waiting = player->MovDir;
11174 player->action_waiting = ACTION_DEFAULT;
11176 player->special_action_bored = ACTION_DEFAULT;
11177 player->special_action_sleeping = ACTION_DEFAULT;
11181 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11183 if ((!player->is_moving && player->was_moving) ||
11184 (player->MovPos == 0 && player->was_moving) ||
11185 (player->is_snapping && !player->was_snapping) ||
11186 (player->is_dropping && !player->was_dropping))
11188 if (!CheckSaveEngineSnapshotToList())
11191 player->was_moving = FALSE;
11192 player->was_snapping = TRUE;
11193 player->was_dropping = TRUE;
11197 if (player->is_moving)
11198 player->was_moving = TRUE;
11200 if (!player->is_snapping)
11201 player->was_snapping = FALSE;
11203 if (!player->is_dropping)
11204 player->was_dropping = FALSE;
11208 static void CheckSingleStepMode(struct PlayerInfo *player)
11210 if (tape.single_step && tape.recording && !tape.pausing)
11212 /* as it is called "single step mode", just return to pause mode when the
11213 player stopped moving after one tile (or never starts moving at all) */
11214 if (!player->is_moving &&
11215 !player->is_pushing &&
11216 !player->is_dropping_pressed)
11217 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11220 CheckSaveEngineSnapshot(player);
11223 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11225 int left = player_action & JOY_LEFT;
11226 int right = player_action & JOY_RIGHT;
11227 int up = player_action & JOY_UP;
11228 int down = player_action & JOY_DOWN;
11229 int button1 = player_action & JOY_BUTTON_1;
11230 int button2 = player_action & JOY_BUTTON_2;
11231 int dx = (left ? -1 : right ? 1 : 0);
11232 int dy = (up ? -1 : down ? 1 : 0);
11234 if (!player->active || tape.pausing)
11240 SnapField(player, dx, dy);
11244 DropElement(player);
11246 MovePlayer(player, dx, dy);
11249 CheckSingleStepMode(player);
11251 SetPlayerWaiting(player, FALSE);
11253 return player_action;
11257 // no actions for this player (no input at player's configured device)
11259 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11260 SnapField(player, 0, 0);
11261 CheckGravityMovementWhenNotMoving(player);
11263 if (player->MovPos == 0)
11264 SetPlayerWaiting(player, TRUE);
11266 if (player->MovPos == 0) // needed for tape.playing
11267 player->is_moving = FALSE;
11269 player->is_dropping = FALSE;
11270 player->is_dropping_pressed = FALSE;
11271 player->drop_pressed_delay = 0;
11273 CheckSingleStepMode(player);
11279 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11282 if (!tape.use_mouse_actions)
11285 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11286 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11287 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11290 static void SetTapeActionFromMouseAction(byte *tape_action,
11291 struct MouseActionInfo *mouse_action)
11293 if (!tape.use_mouse_actions)
11296 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11297 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11298 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11301 static void CheckLevelSolved(void)
11303 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11305 if (game_em.level_solved &&
11306 !game_em.game_over) // game won
11310 game_em.game_over = TRUE;
11312 game.all_players_gone = TRUE;
11315 if (game_em.game_over) // game lost
11316 game.all_players_gone = TRUE;
11318 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11320 if (game_sp.level_solved &&
11321 !game_sp.game_over) // game won
11325 game_sp.game_over = TRUE;
11327 game.all_players_gone = TRUE;
11330 if (game_sp.game_over) // game lost
11331 game.all_players_gone = TRUE;
11333 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11335 if (game_mm.level_solved &&
11336 !game_mm.game_over) // game won
11340 game_mm.game_over = TRUE;
11342 game.all_players_gone = TRUE;
11345 if (game_mm.game_over) // game lost
11346 game.all_players_gone = TRUE;
11350 static void CheckLevelTime(void)
11354 if (TimeFrames >= FRAMES_PER_SECOND)
11359 for (i = 0; i < MAX_PLAYERS; i++)
11361 struct PlayerInfo *player = &stored_player[i];
11363 if (SHIELD_ON(player))
11365 player->shield_normal_time_left--;
11367 if (player->shield_deadly_time_left > 0)
11368 player->shield_deadly_time_left--;
11372 if (!game.LevelSolved && !level.use_step_counter)
11380 if (TimeLeft <= 10 && setup.time_limit)
11381 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11383 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11384 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11386 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11388 if (!TimeLeft && setup.time_limit)
11390 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11391 game_em.lev->killed_out_of_time = TRUE;
11393 for (i = 0; i < MAX_PLAYERS; i++)
11394 KillPlayer(&stored_player[i]);
11397 else if (game.no_time_limit && !game.all_players_gone)
11399 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11402 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11405 if (tape.recording || tape.playing)
11406 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11409 if (tape.recording || tape.playing)
11410 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11412 UpdateAndDisplayGameControlValues();
11415 void AdvanceFrameAndPlayerCounters(int player_nr)
11419 // advance frame counters (global frame counter and time frame counter)
11423 // advance player counters (counters for move delay, move animation etc.)
11424 for (i = 0; i < MAX_PLAYERS; i++)
11426 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11427 int move_delay_value = stored_player[i].move_delay_value;
11428 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11430 if (!advance_player_counters) // not all players may be affected
11433 if (move_frames == 0) // less than one move per game frame
11435 int stepsize = TILEX / move_delay_value;
11436 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11437 int count = (stored_player[i].is_moving ?
11438 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11440 if (count % delay == 0)
11444 stored_player[i].Frame += move_frames;
11446 if (stored_player[i].MovPos != 0)
11447 stored_player[i].StepFrame += move_frames;
11449 if (stored_player[i].move_delay > 0)
11450 stored_player[i].move_delay--;
11452 // due to bugs in previous versions, counter must count up, not down
11453 if (stored_player[i].push_delay != -1)
11454 stored_player[i].push_delay++;
11456 if (stored_player[i].drop_delay > 0)
11457 stored_player[i].drop_delay--;
11459 if (stored_player[i].is_dropping_pressed)
11460 stored_player[i].drop_pressed_delay++;
11464 void StartGameActions(boolean init_network_game, boolean record_tape,
11467 unsigned int new_random_seed = InitRND(random_seed);
11470 TapeStartRecording(new_random_seed);
11472 if (init_network_game)
11474 SendToServer_LevelFile();
11475 SendToServer_StartPlaying();
11483 static void GameActionsExt(void)
11486 static unsigned int game_frame_delay = 0;
11488 unsigned int game_frame_delay_value;
11489 byte *recorded_player_action;
11490 byte summarized_player_action = 0;
11491 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11494 // detect endless loops, caused by custom element programming
11495 if (recursion_loop_detected && recursion_loop_depth == 0)
11497 char *message = getStringCat3("Internal Error! Element ",
11498 EL_NAME(recursion_loop_element),
11499 " caused endless loop! Quit the game?");
11501 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11502 EL_NAME(recursion_loop_element));
11504 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11506 recursion_loop_detected = FALSE; // if game should be continued
11513 if (game.restart_level)
11514 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11516 CheckLevelSolved();
11518 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11521 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11524 if (game_status != GAME_MODE_PLAYING) // status might have changed
11527 game_frame_delay_value =
11528 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11530 if (tape.playing && tape.warp_forward && !tape.pausing)
11531 game_frame_delay_value = 0;
11533 SetVideoFrameDelay(game_frame_delay_value);
11535 // (de)activate virtual buttons depending on current game status
11536 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11538 if (game.all_players_gone) // if no players there to be controlled anymore
11539 SetOverlayActive(FALSE);
11540 else if (!tape.playing) // if game continues after tape stopped playing
11541 SetOverlayActive(TRUE);
11546 // ---------- main game synchronization point ----------
11548 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11550 printf("::: skip == %d\n", skip);
11553 // ---------- main game synchronization point ----------
11555 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11559 if (network_playing && !network_player_action_received)
11561 // try to get network player actions in time
11563 // last chance to get network player actions without main loop delay
11564 HandleNetworking();
11566 // game was quit by network peer
11567 if (game_status != GAME_MODE_PLAYING)
11570 // check if network player actions still missing and game still running
11571 if (!network_player_action_received && !checkGameEnded())
11572 return; // failed to get network player actions in time
11574 // do not yet reset "network_player_action_received" (for tape.pausing)
11580 // at this point we know that we really continue executing the game
11582 network_player_action_received = FALSE;
11584 // when playing tape, read previously recorded player input from tape data
11585 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11587 local_player->effective_mouse_action = local_player->mouse_action;
11589 if (recorded_player_action != NULL)
11590 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11591 recorded_player_action);
11593 // TapePlayAction() may return NULL when toggling to "pause before death"
11597 if (tape.set_centered_player)
11599 game.centered_player_nr_next = tape.centered_player_nr_next;
11600 game.set_centered_player = TRUE;
11603 for (i = 0; i < MAX_PLAYERS; i++)
11605 summarized_player_action |= stored_player[i].action;
11607 if (!network_playing && (game.team_mode || tape.playing))
11608 stored_player[i].effective_action = stored_player[i].action;
11611 if (network_playing && !checkGameEnded())
11612 SendToServer_MovePlayer(summarized_player_action);
11614 // summarize all actions at local players mapped input device position
11615 // (this allows using different input devices in single player mode)
11616 if (!network.enabled && !game.team_mode)
11617 stored_player[map_player_action[local_player->index_nr]].effective_action =
11618 summarized_player_action;
11620 // summarize all actions at centered player in local team mode
11621 if (tape.recording &&
11622 setup.team_mode && !network.enabled &&
11623 setup.input_on_focus &&
11624 game.centered_player_nr != -1)
11626 for (i = 0; i < MAX_PLAYERS; i++)
11627 stored_player[map_player_action[i]].effective_action =
11628 (i == game.centered_player_nr ? summarized_player_action : 0);
11631 if (recorded_player_action != NULL)
11632 for (i = 0; i < MAX_PLAYERS; i++)
11633 stored_player[i].effective_action = recorded_player_action[i];
11635 for (i = 0; i < MAX_PLAYERS; i++)
11637 tape_action[i] = stored_player[i].effective_action;
11639 /* (this may happen in the RND game engine if a player was not present on
11640 the playfield on level start, but appeared later from a custom element */
11641 if (setup.team_mode &&
11644 !tape.player_participates[i])
11645 tape.player_participates[i] = TRUE;
11648 SetTapeActionFromMouseAction(tape_action,
11649 &local_player->effective_mouse_action);
11651 // only record actions from input devices, but not programmed actions
11652 if (tape.recording)
11653 TapeRecordAction(tape_action);
11655 // remember if game was played (especially after tape stopped playing)
11656 if (!tape.playing && summarized_player_action)
11657 game.GamePlayed = TRUE;
11659 #if USE_NEW_PLAYER_ASSIGNMENTS
11660 // !!! also map player actions in single player mode !!!
11661 // if (game.team_mode)
11664 byte mapped_action[MAX_PLAYERS];
11666 #if DEBUG_PLAYER_ACTIONS
11668 for (i = 0; i < MAX_PLAYERS; i++)
11669 printf(" %d, ", stored_player[i].effective_action);
11672 for (i = 0; i < MAX_PLAYERS; i++)
11673 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11675 for (i = 0; i < MAX_PLAYERS; i++)
11676 stored_player[i].effective_action = mapped_action[i];
11678 #if DEBUG_PLAYER_ACTIONS
11680 for (i = 0; i < MAX_PLAYERS; i++)
11681 printf(" %d, ", stored_player[i].effective_action);
11685 #if DEBUG_PLAYER_ACTIONS
11689 for (i = 0; i < MAX_PLAYERS; i++)
11690 printf(" %d, ", stored_player[i].effective_action);
11696 for (i = 0; i < MAX_PLAYERS; i++)
11698 // allow engine snapshot in case of changed movement attempt
11699 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11700 (stored_player[i].effective_action & KEY_MOTION))
11701 game.snapshot.changed_action = TRUE;
11703 // allow engine snapshot in case of snapping/dropping attempt
11704 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11705 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11706 game.snapshot.changed_action = TRUE;
11708 game.snapshot.last_action[i] = stored_player[i].effective_action;
11711 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11713 GameActions_EM_Main();
11715 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11717 GameActions_SP_Main();
11719 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11721 GameActions_MM_Main();
11725 GameActions_RND_Main();
11728 BlitScreenToBitmap(backbuffer);
11730 CheckLevelSolved();
11733 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11735 if (global.show_frames_per_second)
11737 static unsigned int fps_counter = 0;
11738 static int fps_frames = 0;
11739 unsigned int fps_delay_ms = Counter() - fps_counter;
11743 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11745 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11748 fps_counter = Counter();
11750 // always draw FPS to screen after FPS value was updated
11751 redraw_mask |= REDRAW_FPS;
11754 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11755 if (GetDrawDeactivationMask() == REDRAW_NONE)
11756 redraw_mask |= REDRAW_FPS;
11760 static void GameActions_CheckSaveEngineSnapshot(void)
11762 if (!game.snapshot.save_snapshot)
11765 // clear flag for saving snapshot _before_ saving snapshot
11766 game.snapshot.save_snapshot = FALSE;
11768 SaveEngineSnapshotToList();
11771 void GameActions(void)
11775 GameActions_CheckSaveEngineSnapshot();
11778 void GameActions_EM_Main(void)
11780 byte effective_action[MAX_PLAYERS];
11781 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11784 for (i = 0; i < MAX_PLAYERS; i++)
11785 effective_action[i] = stored_player[i].effective_action;
11787 GameActions_EM(effective_action, warp_mode);
11790 void GameActions_SP_Main(void)
11792 byte effective_action[MAX_PLAYERS];
11793 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11796 for (i = 0; i < MAX_PLAYERS; i++)
11797 effective_action[i] = stored_player[i].effective_action;
11799 GameActions_SP(effective_action, warp_mode);
11801 for (i = 0; i < MAX_PLAYERS; i++)
11803 if (stored_player[i].force_dropping)
11804 stored_player[i].action |= KEY_BUTTON_DROP;
11806 stored_player[i].force_dropping = FALSE;
11810 void GameActions_MM_Main(void)
11812 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11814 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11817 void GameActions_RND_Main(void)
11822 void GameActions_RND(void)
11824 static struct MouseActionInfo mouse_action_last = { 0 };
11825 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11826 int magic_wall_x = 0, magic_wall_y = 0;
11827 int i, x, y, element, graphic, last_gfx_frame;
11829 InitPlayfieldScanModeVars();
11831 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11833 SCAN_PLAYFIELD(x, y)
11835 ChangeCount[x][y] = 0;
11836 ChangeEvent[x][y] = -1;
11840 if (game.set_centered_player)
11842 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11844 // switching to "all players" only possible if all players fit to screen
11845 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11847 game.centered_player_nr_next = game.centered_player_nr;
11848 game.set_centered_player = FALSE;
11851 // do not switch focus to non-existing (or non-active) player
11852 if (game.centered_player_nr_next >= 0 &&
11853 !stored_player[game.centered_player_nr_next].active)
11855 game.centered_player_nr_next = game.centered_player_nr;
11856 game.set_centered_player = FALSE;
11860 if (game.set_centered_player &&
11861 ScreenMovPos == 0) // screen currently aligned at tile position
11865 if (game.centered_player_nr_next == -1)
11867 setScreenCenteredToAllPlayers(&sx, &sy);
11871 sx = stored_player[game.centered_player_nr_next].jx;
11872 sy = stored_player[game.centered_player_nr_next].jy;
11875 game.centered_player_nr = game.centered_player_nr_next;
11876 game.set_centered_player = FALSE;
11878 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11879 DrawGameDoorValues();
11882 for (i = 0; i < MAX_PLAYERS; i++)
11884 int actual_player_action = stored_player[i].effective_action;
11887 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11888 - rnd_equinox_tetrachloride 048
11889 - rnd_equinox_tetrachloride_ii 096
11890 - rnd_emanuel_schmieg 002
11891 - doctor_sloan_ww 001, 020
11893 if (stored_player[i].MovPos == 0)
11894 CheckGravityMovement(&stored_player[i]);
11897 // overwrite programmed action with tape action
11898 if (stored_player[i].programmed_action)
11899 actual_player_action = stored_player[i].programmed_action;
11901 PlayerActions(&stored_player[i], actual_player_action);
11903 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11906 ScrollScreen(NULL, SCROLL_GO_ON);
11908 /* for backwards compatibility, the following code emulates a fixed bug that
11909 occured when pushing elements (causing elements that just made their last
11910 pushing step to already (if possible) make their first falling step in the
11911 same game frame, which is bad); this code is also needed to use the famous
11912 "spring push bug" which is used in older levels and might be wanted to be
11913 used also in newer levels, but in this case the buggy pushing code is only
11914 affecting the "spring" element and no other elements */
11916 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11918 for (i = 0; i < MAX_PLAYERS; i++)
11920 struct PlayerInfo *player = &stored_player[i];
11921 int x = player->jx;
11922 int y = player->jy;
11924 if (player->active && player->is_pushing && player->is_moving &&
11926 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11927 Feld[x][y] == EL_SPRING))
11929 ContinueMoving(x, y);
11931 // continue moving after pushing (this is actually a bug)
11932 if (!IS_MOVING(x, y))
11933 Stop[x][y] = FALSE;
11938 SCAN_PLAYFIELD(x, y)
11940 Last[x][y] = Feld[x][y];
11942 ChangeCount[x][y] = 0;
11943 ChangeEvent[x][y] = -1;
11945 // this must be handled before main playfield loop
11946 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11949 if (MovDelay[x][y] <= 0)
11953 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11956 if (MovDelay[x][y] <= 0)
11959 TEST_DrawLevelField(x, y);
11961 TestIfElementTouchesCustomElement(x, y); // for empty space
11966 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11968 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11969 printf("GameActions(): This should never happen!\n");
11971 ChangePage[x][y] = -1;
11975 Stop[x][y] = FALSE;
11976 if (WasJustMoving[x][y] > 0)
11977 WasJustMoving[x][y]--;
11978 if (WasJustFalling[x][y] > 0)
11979 WasJustFalling[x][y]--;
11980 if (CheckCollision[x][y] > 0)
11981 CheckCollision[x][y]--;
11982 if (CheckImpact[x][y] > 0)
11983 CheckImpact[x][y]--;
11987 /* reset finished pushing action (not done in ContinueMoving() to allow
11988 continuous pushing animation for elements with zero push delay) */
11989 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11991 ResetGfxAnimation(x, y);
11992 TEST_DrawLevelField(x, y);
11996 if (IS_BLOCKED(x, y))
12000 Blocked2Moving(x, y, &oldx, &oldy);
12001 if (!IS_MOVING(oldx, oldy))
12003 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
12004 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
12005 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
12006 printf("GameActions(): This should never happen!\n");
12012 if (mouse_action.button)
12014 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12016 x = mouse_action.lx;
12017 y = mouse_action.ly;
12018 element = Feld[x][y];
12022 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12023 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12026 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12027 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12030 SCAN_PLAYFIELD(x, y)
12032 element = Feld[x][y];
12033 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12034 last_gfx_frame = GfxFrame[x][y];
12036 ResetGfxFrame(x, y);
12038 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12039 DrawLevelGraphicAnimation(x, y, graphic);
12041 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12042 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12043 ResetRandomAnimationValue(x, y);
12045 SetRandomAnimationValue(x, y);
12047 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12049 if (IS_INACTIVE(element))
12051 if (IS_ANIMATED(graphic))
12052 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12057 // this may take place after moving, so 'element' may have changed
12058 if (IS_CHANGING(x, y) &&
12059 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12061 int page = element_info[element].event_page_nr[CE_DELAY];
12063 HandleElementChange(x, y, page);
12065 element = Feld[x][y];
12066 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12069 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12073 element = Feld[x][y];
12074 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12076 if (IS_ANIMATED(graphic) &&
12077 !IS_MOVING(x, y) &&
12079 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12081 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12082 TEST_DrawTwinkleOnField(x, y);
12084 else if (element == EL_ACID)
12087 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12089 else if ((element == EL_EXIT_OPEN ||
12090 element == EL_EM_EXIT_OPEN ||
12091 element == EL_SP_EXIT_OPEN ||
12092 element == EL_STEEL_EXIT_OPEN ||
12093 element == EL_EM_STEEL_EXIT_OPEN ||
12094 element == EL_SP_TERMINAL ||
12095 element == EL_SP_TERMINAL_ACTIVE ||
12096 element == EL_EXTRA_TIME ||
12097 element == EL_SHIELD_NORMAL ||
12098 element == EL_SHIELD_DEADLY) &&
12099 IS_ANIMATED(graphic))
12100 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12101 else if (IS_MOVING(x, y))
12102 ContinueMoving(x, y);
12103 else if (IS_ACTIVE_BOMB(element))
12104 CheckDynamite(x, y);
12105 else if (element == EL_AMOEBA_GROWING)
12106 AmoebeWaechst(x, y);
12107 else if (element == EL_AMOEBA_SHRINKING)
12108 AmoebaDisappearing(x, y);
12110 #if !USE_NEW_AMOEBA_CODE
12111 else if (IS_AMOEBALIVE(element))
12112 AmoebeAbleger(x, y);
12115 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12117 else if (element == EL_EXIT_CLOSED)
12119 else if (element == EL_EM_EXIT_CLOSED)
12121 else if (element == EL_STEEL_EXIT_CLOSED)
12122 CheckExitSteel(x, y);
12123 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12124 CheckExitSteelEM(x, y);
12125 else if (element == EL_SP_EXIT_CLOSED)
12127 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12128 element == EL_EXPANDABLE_STEELWALL_GROWING)
12129 MauerWaechst(x, y);
12130 else if (element == EL_EXPANDABLE_WALL ||
12131 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12132 element == EL_EXPANDABLE_WALL_VERTICAL ||
12133 element == EL_EXPANDABLE_WALL_ANY ||
12134 element == EL_BD_EXPANDABLE_WALL)
12135 MauerAbleger(x, y);
12136 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12137 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12138 element == EL_EXPANDABLE_STEELWALL_ANY)
12139 MauerAblegerStahl(x, y);
12140 else if (element == EL_FLAMES)
12141 CheckForDragon(x, y);
12142 else if (element == EL_EXPLOSION)
12143 ; // drawing of correct explosion animation is handled separately
12144 else if (element == EL_ELEMENT_SNAPPING ||
12145 element == EL_DIAGONAL_SHRINKING ||
12146 element == EL_DIAGONAL_GROWING)
12148 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12150 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12152 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12153 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12155 if (IS_BELT_ACTIVE(element))
12156 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12158 if (game.magic_wall_active)
12160 int jx = local_player->jx, jy = local_player->jy;
12162 // play the element sound at the position nearest to the player
12163 if ((element == EL_MAGIC_WALL_FULL ||
12164 element == EL_MAGIC_WALL_ACTIVE ||
12165 element == EL_MAGIC_WALL_EMPTYING ||
12166 element == EL_BD_MAGIC_WALL_FULL ||
12167 element == EL_BD_MAGIC_WALL_ACTIVE ||
12168 element == EL_BD_MAGIC_WALL_EMPTYING ||
12169 element == EL_DC_MAGIC_WALL_FULL ||
12170 element == EL_DC_MAGIC_WALL_ACTIVE ||
12171 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12172 ABS(x - jx) + ABS(y - jy) <
12173 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12181 #if USE_NEW_AMOEBA_CODE
12182 // new experimental amoeba growth stuff
12183 if (!(FrameCounter % 8))
12185 static unsigned int random = 1684108901;
12187 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12189 x = RND(lev_fieldx);
12190 y = RND(lev_fieldy);
12191 element = Feld[x][y];
12193 if (!IS_PLAYER(x,y) &&
12194 (element == EL_EMPTY ||
12195 CAN_GROW_INTO(element) ||
12196 element == EL_QUICKSAND_EMPTY ||
12197 element == EL_QUICKSAND_FAST_EMPTY ||
12198 element == EL_ACID_SPLASH_LEFT ||
12199 element == EL_ACID_SPLASH_RIGHT))
12201 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12202 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12203 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12204 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12205 Feld[x][y] = EL_AMOEBA_DROP;
12208 random = random * 129 + 1;
12213 game.explosions_delayed = FALSE;
12215 SCAN_PLAYFIELD(x, y)
12217 element = Feld[x][y];
12219 if (ExplodeField[x][y])
12220 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12221 else if (element == EL_EXPLOSION)
12222 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12224 ExplodeField[x][y] = EX_TYPE_NONE;
12227 game.explosions_delayed = TRUE;
12229 if (game.magic_wall_active)
12231 if (!(game.magic_wall_time_left % 4))
12233 int element = Feld[magic_wall_x][magic_wall_y];
12235 if (element == EL_BD_MAGIC_WALL_FULL ||
12236 element == EL_BD_MAGIC_WALL_ACTIVE ||
12237 element == EL_BD_MAGIC_WALL_EMPTYING)
12238 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12239 else if (element == EL_DC_MAGIC_WALL_FULL ||
12240 element == EL_DC_MAGIC_WALL_ACTIVE ||
12241 element == EL_DC_MAGIC_WALL_EMPTYING)
12242 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12244 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12247 if (game.magic_wall_time_left > 0)
12249 game.magic_wall_time_left--;
12251 if (!game.magic_wall_time_left)
12253 SCAN_PLAYFIELD(x, y)
12255 element = Feld[x][y];
12257 if (element == EL_MAGIC_WALL_ACTIVE ||
12258 element == EL_MAGIC_WALL_FULL)
12260 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12261 TEST_DrawLevelField(x, y);
12263 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12264 element == EL_BD_MAGIC_WALL_FULL)
12266 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12267 TEST_DrawLevelField(x, y);
12269 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12270 element == EL_DC_MAGIC_WALL_FULL)
12272 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12273 TEST_DrawLevelField(x, y);
12277 game.magic_wall_active = FALSE;
12282 if (game.light_time_left > 0)
12284 game.light_time_left--;
12286 if (game.light_time_left == 0)
12287 RedrawAllLightSwitchesAndInvisibleElements();
12290 if (game.timegate_time_left > 0)
12292 game.timegate_time_left--;
12294 if (game.timegate_time_left == 0)
12295 CloseAllOpenTimegates();
12298 if (game.lenses_time_left > 0)
12300 game.lenses_time_left--;
12302 if (game.lenses_time_left == 0)
12303 RedrawAllInvisibleElementsForLenses();
12306 if (game.magnify_time_left > 0)
12308 game.magnify_time_left--;
12310 if (game.magnify_time_left == 0)
12311 RedrawAllInvisibleElementsForMagnifier();
12314 for (i = 0; i < MAX_PLAYERS; i++)
12316 struct PlayerInfo *player = &stored_player[i];
12318 if (SHIELD_ON(player))
12320 if (player->shield_deadly_time_left)
12321 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12322 else if (player->shield_normal_time_left)
12323 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12327 #if USE_DELAYED_GFX_REDRAW
12328 SCAN_PLAYFIELD(x, y)
12330 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12332 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12333 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12335 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12336 DrawLevelField(x, y);
12338 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12339 DrawLevelFieldCrumbled(x, y);
12341 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12342 DrawLevelFieldCrumbledNeighbours(x, y);
12344 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12345 DrawTwinkleOnField(x, y);
12348 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12353 PlayAllPlayersSound();
12355 for (i = 0; i < MAX_PLAYERS; i++)
12357 struct PlayerInfo *player = &stored_player[i];
12359 if (player->show_envelope != 0 && (!player->active ||
12360 player->MovPos == 0))
12362 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12364 player->show_envelope = 0;
12368 // use random number generator in every frame to make it less predictable
12369 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12372 mouse_action_last = mouse_action;
12375 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12377 int min_x = x, min_y = y, max_x = x, max_y = y;
12380 for (i = 0; i < MAX_PLAYERS; i++)
12382 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12384 if (!stored_player[i].active || &stored_player[i] == player)
12387 min_x = MIN(min_x, jx);
12388 min_y = MIN(min_y, jy);
12389 max_x = MAX(max_x, jx);
12390 max_y = MAX(max_y, jy);
12393 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12396 static boolean AllPlayersInVisibleScreen(void)
12400 for (i = 0; i < MAX_PLAYERS; i++)
12402 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12404 if (!stored_player[i].active)
12407 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12414 void ScrollLevel(int dx, int dy)
12416 int scroll_offset = 2 * TILEX_VAR;
12419 BlitBitmap(drawto_field, drawto_field,
12420 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12421 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12422 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12423 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12424 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12425 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12429 x = (dx == 1 ? BX1 : BX2);
12430 for (y = BY1; y <= BY2; y++)
12431 DrawScreenField(x, y);
12436 y = (dy == 1 ? BY1 : BY2);
12437 for (x = BX1; x <= BX2; x++)
12438 DrawScreenField(x, y);
12441 redraw_mask |= REDRAW_FIELD;
12444 static boolean canFallDown(struct PlayerInfo *player)
12446 int jx = player->jx, jy = player->jy;
12448 return (IN_LEV_FIELD(jx, jy + 1) &&
12449 (IS_FREE(jx, jy + 1) ||
12450 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12451 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12452 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12455 static boolean canPassField(int x, int y, int move_dir)
12457 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12458 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12459 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12460 int nextx = x + dx;
12461 int nexty = y + dy;
12462 int element = Feld[x][y];
12464 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12465 !CAN_MOVE(element) &&
12466 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12467 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12468 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12471 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12473 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12474 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12475 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12479 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12480 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12481 (IS_DIGGABLE(Feld[newx][newy]) ||
12482 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12483 canPassField(newx, newy, move_dir)));
12486 static void CheckGravityMovement(struct PlayerInfo *player)
12488 if (player->gravity && !player->programmed_action)
12490 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12491 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12492 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12493 int jx = player->jx, jy = player->jy;
12494 boolean player_is_moving_to_valid_field =
12495 (!player_is_snapping &&
12496 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12497 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12498 boolean player_can_fall_down = canFallDown(player);
12500 if (player_can_fall_down &&
12501 !player_is_moving_to_valid_field)
12502 player->programmed_action = MV_DOWN;
12506 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12508 return CheckGravityMovement(player);
12510 if (player->gravity && !player->programmed_action)
12512 int jx = player->jx, jy = player->jy;
12513 boolean field_under_player_is_free =
12514 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12515 boolean player_is_standing_on_valid_field =
12516 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12517 (IS_WALKABLE(Feld[jx][jy]) &&
12518 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12520 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12521 player->programmed_action = MV_DOWN;
12526 MovePlayerOneStep()
12527 -----------------------------------------------------------------------------
12528 dx, dy: direction (non-diagonal) to try to move the player to
12529 real_dx, real_dy: direction as read from input device (can be diagonal)
12532 boolean MovePlayerOneStep(struct PlayerInfo *player,
12533 int dx, int dy, int real_dx, int real_dy)
12535 int jx = player->jx, jy = player->jy;
12536 int new_jx = jx + dx, new_jy = jy + dy;
12538 boolean player_can_move = !player->cannot_move;
12540 if (!player->active || (!dx && !dy))
12541 return MP_NO_ACTION;
12543 player->MovDir = (dx < 0 ? MV_LEFT :
12544 dx > 0 ? MV_RIGHT :
12546 dy > 0 ? MV_DOWN : MV_NONE);
12548 if (!IN_LEV_FIELD(new_jx, new_jy))
12549 return MP_NO_ACTION;
12551 if (!player_can_move)
12553 if (player->MovPos == 0)
12555 player->is_moving = FALSE;
12556 player->is_digging = FALSE;
12557 player->is_collecting = FALSE;
12558 player->is_snapping = FALSE;
12559 player->is_pushing = FALSE;
12563 if (!network.enabled && game.centered_player_nr == -1 &&
12564 !AllPlayersInSight(player, new_jx, new_jy))
12565 return MP_NO_ACTION;
12567 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12568 if (can_move != MP_MOVING)
12571 // check if DigField() has caused relocation of the player
12572 if (player->jx != jx || player->jy != jy)
12573 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12575 StorePlayer[jx][jy] = 0;
12576 player->last_jx = jx;
12577 player->last_jy = jy;
12578 player->jx = new_jx;
12579 player->jy = new_jy;
12580 StorePlayer[new_jx][new_jy] = player->element_nr;
12582 if (player->move_delay_value_next != -1)
12584 player->move_delay_value = player->move_delay_value_next;
12585 player->move_delay_value_next = -1;
12589 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12591 player->step_counter++;
12593 PlayerVisit[jx][jy] = FrameCounter;
12595 player->is_moving = TRUE;
12598 // should better be called in MovePlayer(), but this breaks some tapes
12599 ScrollPlayer(player, SCROLL_INIT);
12605 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12607 int jx = player->jx, jy = player->jy;
12608 int old_jx = jx, old_jy = jy;
12609 int moved = MP_NO_ACTION;
12611 if (!player->active)
12616 if (player->MovPos == 0)
12618 player->is_moving = FALSE;
12619 player->is_digging = FALSE;
12620 player->is_collecting = FALSE;
12621 player->is_snapping = FALSE;
12622 player->is_pushing = FALSE;
12628 if (player->move_delay > 0)
12631 player->move_delay = -1; // set to "uninitialized" value
12633 // store if player is automatically moved to next field
12634 player->is_auto_moving = (player->programmed_action != MV_NONE);
12636 // remove the last programmed player action
12637 player->programmed_action = 0;
12639 if (player->MovPos)
12641 // should only happen if pre-1.2 tape recordings are played
12642 // this is only for backward compatibility
12644 int original_move_delay_value = player->move_delay_value;
12647 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12651 // scroll remaining steps with finest movement resolution
12652 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12654 while (player->MovPos)
12656 ScrollPlayer(player, SCROLL_GO_ON);
12657 ScrollScreen(NULL, SCROLL_GO_ON);
12659 AdvanceFrameAndPlayerCounters(player->index_nr);
12662 BackToFront_WithFrameDelay(0);
12665 player->move_delay_value = original_move_delay_value;
12668 player->is_active = FALSE;
12670 if (player->last_move_dir & MV_HORIZONTAL)
12672 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12673 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12677 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12678 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12681 if (!moved && !player->is_active)
12683 player->is_moving = FALSE;
12684 player->is_digging = FALSE;
12685 player->is_collecting = FALSE;
12686 player->is_snapping = FALSE;
12687 player->is_pushing = FALSE;
12693 if (moved & MP_MOVING && !ScreenMovPos &&
12694 (player->index_nr == game.centered_player_nr ||
12695 game.centered_player_nr == -1))
12697 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12699 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12701 // actual player has left the screen -- scroll in that direction
12702 if (jx != old_jx) // player has moved horizontally
12703 scroll_x += (jx - old_jx);
12704 else // player has moved vertically
12705 scroll_y += (jy - old_jy);
12709 int offset_raw = game.scroll_delay_value;
12711 if (jx != old_jx) // player has moved horizontally
12713 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12714 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12715 int new_scroll_x = jx - MIDPOSX + offset_x;
12717 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12718 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12719 scroll_x = new_scroll_x;
12721 // don't scroll over playfield boundaries
12722 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12724 // don't scroll more than one field at a time
12725 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12727 // don't scroll against the player's moving direction
12728 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12729 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12730 scroll_x = old_scroll_x;
12732 else // player has moved vertically
12734 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12735 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12736 int new_scroll_y = jy - MIDPOSY + offset_y;
12738 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12739 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12740 scroll_y = new_scroll_y;
12742 // don't scroll over playfield boundaries
12743 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12745 // don't scroll more than one field at a time
12746 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12748 // don't scroll against the player's moving direction
12749 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12750 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12751 scroll_y = old_scroll_y;
12755 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12757 if (!network.enabled && game.centered_player_nr == -1 &&
12758 !AllPlayersInVisibleScreen())
12760 scroll_x = old_scroll_x;
12761 scroll_y = old_scroll_y;
12765 ScrollScreen(player, SCROLL_INIT);
12766 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12771 player->StepFrame = 0;
12773 if (moved & MP_MOVING)
12775 if (old_jx != jx && old_jy == jy)
12776 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12777 else if (old_jx == jx && old_jy != jy)
12778 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12780 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12782 player->last_move_dir = player->MovDir;
12783 player->is_moving = TRUE;
12784 player->is_snapping = FALSE;
12785 player->is_switching = FALSE;
12786 player->is_dropping = FALSE;
12787 player->is_dropping_pressed = FALSE;
12788 player->drop_pressed_delay = 0;
12791 // should better be called here than above, but this breaks some tapes
12792 ScrollPlayer(player, SCROLL_INIT);
12797 CheckGravityMovementWhenNotMoving(player);
12799 player->is_moving = FALSE;
12801 /* at this point, the player is allowed to move, but cannot move right now
12802 (e.g. because of something blocking the way) -- ensure that the player
12803 is also allowed to move in the next frame (in old versions before 3.1.1,
12804 the player was forced to wait again for eight frames before next try) */
12806 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12807 player->move_delay = 0; // allow direct movement in the next frame
12810 if (player->move_delay == -1) // not yet initialized by DigField()
12811 player->move_delay = player->move_delay_value;
12813 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12815 TestIfPlayerTouchesBadThing(jx, jy);
12816 TestIfPlayerTouchesCustomElement(jx, jy);
12819 if (!player->active)
12820 RemovePlayer(player);
12825 void ScrollPlayer(struct PlayerInfo *player, int mode)
12827 int jx = player->jx, jy = player->jy;
12828 int last_jx = player->last_jx, last_jy = player->last_jy;
12829 int move_stepsize = TILEX / player->move_delay_value;
12831 if (!player->active)
12834 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12837 if (mode == SCROLL_INIT)
12839 player->actual_frame_counter = FrameCounter;
12840 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12842 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12843 Feld[last_jx][last_jy] == EL_EMPTY)
12845 int last_field_block_delay = 0; // start with no blocking at all
12846 int block_delay_adjustment = player->block_delay_adjustment;
12848 // if player blocks last field, add delay for exactly one move
12849 if (player->block_last_field)
12851 last_field_block_delay += player->move_delay_value;
12853 // when blocking enabled, prevent moving up despite gravity
12854 if (player->gravity && player->MovDir == MV_UP)
12855 block_delay_adjustment = -1;
12858 // add block delay adjustment (also possible when not blocking)
12859 last_field_block_delay += block_delay_adjustment;
12861 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12862 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12865 if (player->MovPos != 0) // player has not yet reached destination
12868 else if (!FrameReached(&player->actual_frame_counter, 1))
12871 if (player->MovPos != 0)
12873 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12874 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12876 // before DrawPlayer() to draw correct player graphic for this case
12877 if (player->MovPos == 0)
12878 CheckGravityMovement(player);
12881 if (player->MovPos == 0) // player reached destination field
12883 if (player->move_delay_reset_counter > 0)
12885 player->move_delay_reset_counter--;
12887 if (player->move_delay_reset_counter == 0)
12889 // continue with normal speed after quickly moving through gate
12890 HALVE_PLAYER_SPEED(player);
12892 // be able to make the next move without delay
12893 player->move_delay = 0;
12897 player->last_jx = jx;
12898 player->last_jy = jy;
12900 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12901 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12902 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12903 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12904 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12905 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12906 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12907 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12909 ExitPlayer(player);
12911 if (game.players_still_needed == 0 &&
12912 (game.friends_still_needed == 0 ||
12913 IS_SP_ELEMENT(Feld[jx][jy])))
12917 // this breaks one level: "machine", level 000
12919 int move_direction = player->MovDir;
12920 int enter_side = MV_DIR_OPPOSITE(move_direction);
12921 int leave_side = move_direction;
12922 int old_jx = last_jx;
12923 int old_jy = last_jy;
12924 int old_element = Feld[old_jx][old_jy];
12925 int new_element = Feld[jx][jy];
12927 if (IS_CUSTOM_ELEMENT(old_element))
12928 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12930 player->index_bit, leave_side);
12932 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12933 CE_PLAYER_LEAVES_X,
12934 player->index_bit, leave_side);
12936 if (IS_CUSTOM_ELEMENT(new_element))
12937 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12938 player->index_bit, enter_side);
12940 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12941 CE_PLAYER_ENTERS_X,
12942 player->index_bit, enter_side);
12944 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12945 CE_MOVE_OF_X, move_direction);
12948 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12950 TestIfPlayerTouchesBadThing(jx, jy);
12951 TestIfPlayerTouchesCustomElement(jx, jy);
12953 /* needed because pushed element has not yet reached its destination,
12954 so it would trigger a change event at its previous field location */
12955 if (!player->is_pushing)
12956 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12958 if (!player->active)
12959 RemovePlayer(player);
12962 if (!game.LevelSolved && level.use_step_counter)
12972 if (TimeLeft <= 10 && setup.time_limit)
12973 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12975 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12977 DisplayGameControlValues();
12979 if (!TimeLeft && setup.time_limit)
12980 for (i = 0; i < MAX_PLAYERS; i++)
12981 KillPlayer(&stored_player[i]);
12983 else if (game.no_time_limit && !game.all_players_gone)
12985 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12987 DisplayGameControlValues();
12991 if (tape.single_step && tape.recording && !tape.pausing &&
12992 !player->programmed_action)
12993 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12995 if (!player->programmed_action)
12996 CheckSaveEngineSnapshot(player);
13000 void ScrollScreen(struct PlayerInfo *player, int mode)
13002 static unsigned int screen_frame_counter = 0;
13004 if (mode == SCROLL_INIT)
13006 // set scrolling step size according to actual player's moving speed
13007 ScrollStepSize = TILEX / player->move_delay_value;
13009 screen_frame_counter = FrameCounter;
13010 ScreenMovDir = player->MovDir;
13011 ScreenMovPos = player->MovPos;
13012 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13015 else if (!FrameReached(&screen_frame_counter, 1))
13020 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13021 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13022 redraw_mask |= REDRAW_FIELD;
13025 ScreenMovDir = MV_NONE;
13028 void TestIfPlayerTouchesCustomElement(int x, int y)
13030 static int xy[4][2] =
13037 static int trigger_sides[4][2] =
13039 // center side border side
13040 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13041 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13042 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13043 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13045 static int touch_dir[4] =
13047 MV_LEFT | MV_RIGHT,
13052 int center_element = Feld[x][y]; // should always be non-moving!
13055 for (i = 0; i < NUM_DIRECTIONS; i++)
13057 int xx = x + xy[i][0];
13058 int yy = y + xy[i][1];
13059 int center_side = trigger_sides[i][0];
13060 int border_side = trigger_sides[i][1];
13061 int border_element;
13063 if (!IN_LEV_FIELD(xx, yy))
13066 if (IS_PLAYER(x, y)) // player found at center element
13068 struct PlayerInfo *player = PLAYERINFO(x, y);
13070 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13071 border_element = Feld[xx][yy]; // may be moving!
13072 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13073 border_element = Feld[xx][yy];
13074 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13075 border_element = MovingOrBlocked2Element(xx, yy);
13077 continue; // center and border element do not touch
13079 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13080 player->index_bit, border_side);
13081 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13082 CE_PLAYER_TOUCHES_X,
13083 player->index_bit, border_side);
13086 /* use player element that is initially defined in the level playfield,
13087 not the player element that corresponds to the runtime player number
13088 (example: a level that contains EL_PLAYER_3 as the only player would
13089 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13090 int player_element = PLAYERINFO(x, y)->initial_element;
13092 CheckElementChangeBySide(xx, yy, border_element, player_element,
13093 CE_TOUCHING_X, border_side);
13096 else if (IS_PLAYER(xx, yy)) // player found at border element
13098 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13100 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13102 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13103 continue; // center and border element do not touch
13106 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13107 player->index_bit, center_side);
13108 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13109 CE_PLAYER_TOUCHES_X,
13110 player->index_bit, center_side);
13113 /* use player element that is initially defined in the level playfield,
13114 not the player element that corresponds to the runtime player number
13115 (example: a level that contains EL_PLAYER_3 as the only player would
13116 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13117 int player_element = PLAYERINFO(xx, yy)->initial_element;
13119 CheckElementChangeBySide(x, y, center_element, player_element,
13120 CE_TOUCHING_X, center_side);
13128 void TestIfElementTouchesCustomElement(int x, int y)
13130 static int xy[4][2] =
13137 static int trigger_sides[4][2] =
13139 // center side border side
13140 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13141 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13142 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13143 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13145 static int touch_dir[4] =
13147 MV_LEFT | MV_RIGHT,
13152 boolean change_center_element = FALSE;
13153 int center_element = Feld[x][y]; // should always be non-moving!
13154 int border_element_old[NUM_DIRECTIONS];
13157 for (i = 0; i < NUM_DIRECTIONS; i++)
13159 int xx = x + xy[i][0];
13160 int yy = y + xy[i][1];
13161 int border_element;
13163 border_element_old[i] = -1;
13165 if (!IN_LEV_FIELD(xx, yy))
13168 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13169 border_element = Feld[xx][yy]; // may be moving!
13170 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13171 border_element = Feld[xx][yy];
13172 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13173 border_element = MovingOrBlocked2Element(xx, yy);
13175 continue; // center and border element do not touch
13177 border_element_old[i] = border_element;
13180 for (i = 0; i < NUM_DIRECTIONS; i++)
13182 int xx = x + xy[i][0];
13183 int yy = y + xy[i][1];
13184 int center_side = trigger_sides[i][0];
13185 int border_element = border_element_old[i];
13187 if (border_element == -1)
13190 // check for change of border element
13191 CheckElementChangeBySide(xx, yy, border_element, center_element,
13192 CE_TOUCHING_X, center_side);
13194 // (center element cannot be player, so we dont have to check this here)
13197 for (i = 0; i < NUM_DIRECTIONS; i++)
13199 int xx = x + xy[i][0];
13200 int yy = y + xy[i][1];
13201 int border_side = trigger_sides[i][1];
13202 int border_element = border_element_old[i];
13204 if (border_element == -1)
13207 // check for change of center element (but change it only once)
13208 if (!change_center_element)
13209 change_center_element =
13210 CheckElementChangeBySide(x, y, center_element, border_element,
13211 CE_TOUCHING_X, border_side);
13213 if (IS_PLAYER(xx, yy))
13215 /* use player element that is initially defined in the level playfield,
13216 not the player element that corresponds to the runtime player number
13217 (example: a level that contains EL_PLAYER_3 as the only player would
13218 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13219 int player_element = PLAYERINFO(xx, yy)->initial_element;
13221 CheckElementChangeBySide(x, y, center_element, player_element,
13222 CE_TOUCHING_X, border_side);
13227 void TestIfElementHitsCustomElement(int x, int y, int direction)
13229 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13230 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13231 int hitx = x + dx, hity = y + dy;
13232 int hitting_element = Feld[x][y];
13233 int touched_element;
13235 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13238 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13239 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13241 if (IN_LEV_FIELD(hitx, hity))
13243 int opposite_direction = MV_DIR_OPPOSITE(direction);
13244 int hitting_side = direction;
13245 int touched_side = opposite_direction;
13246 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13247 MovDir[hitx][hity] != direction ||
13248 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13254 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13255 CE_HITTING_X, touched_side);
13257 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13258 CE_HIT_BY_X, hitting_side);
13260 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13261 CE_HIT_BY_SOMETHING, opposite_direction);
13263 if (IS_PLAYER(hitx, hity))
13265 /* use player element that is initially defined in the level playfield,
13266 not the player element that corresponds to the runtime player number
13267 (example: a level that contains EL_PLAYER_3 as the only player would
13268 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13269 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13271 CheckElementChangeBySide(x, y, hitting_element, player_element,
13272 CE_HITTING_X, touched_side);
13277 // "hitting something" is also true when hitting the playfield border
13278 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13279 CE_HITTING_SOMETHING, direction);
13282 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13284 int i, kill_x = -1, kill_y = -1;
13286 int bad_element = -1;
13287 static int test_xy[4][2] =
13294 static int test_dir[4] =
13302 for (i = 0; i < NUM_DIRECTIONS; i++)
13304 int test_x, test_y, test_move_dir, test_element;
13306 test_x = good_x + test_xy[i][0];
13307 test_y = good_y + test_xy[i][1];
13309 if (!IN_LEV_FIELD(test_x, test_y))
13313 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13315 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13317 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13318 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13320 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13321 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13325 bad_element = test_element;
13331 if (kill_x != -1 || kill_y != -1)
13333 if (IS_PLAYER(good_x, good_y))
13335 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13337 if (player->shield_deadly_time_left > 0 &&
13338 !IS_INDESTRUCTIBLE(bad_element))
13339 Bang(kill_x, kill_y);
13340 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13341 KillPlayer(player);
13344 Bang(good_x, good_y);
13348 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13350 int i, kill_x = -1, kill_y = -1;
13351 int bad_element = Feld[bad_x][bad_y];
13352 static int test_xy[4][2] =
13359 static int touch_dir[4] =
13361 MV_LEFT | MV_RIGHT,
13366 static int test_dir[4] =
13374 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13377 for (i = 0; i < NUM_DIRECTIONS; i++)
13379 int test_x, test_y, test_move_dir, test_element;
13381 test_x = bad_x + test_xy[i][0];
13382 test_y = bad_y + test_xy[i][1];
13384 if (!IN_LEV_FIELD(test_x, test_y))
13388 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13390 test_element = Feld[test_x][test_y];
13392 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13393 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13395 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13396 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13398 // good thing is player or penguin that does not move away
13399 if (IS_PLAYER(test_x, test_y))
13401 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13403 if (bad_element == EL_ROBOT && player->is_moving)
13404 continue; // robot does not kill player if he is moving
13406 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13408 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13409 continue; // center and border element do not touch
13417 else if (test_element == EL_PENGUIN)
13427 if (kill_x != -1 || kill_y != -1)
13429 if (IS_PLAYER(kill_x, kill_y))
13431 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13433 if (player->shield_deadly_time_left > 0 &&
13434 !IS_INDESTRUCTIBLE(bad_element))
13435 Bang(bad_x, bad_y);
13436 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13437 KillPlayer(player);
13440 Bang(kill_x, kill_y);
13444 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13446 int bad_element = Feld[bad_x][bad_y];
13447 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13448 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13449 int test_x = bad_x + dx, test_y = bad_y + dy;
13450 int test_move_dir, test_element;
13451 int kill_x = -1, kill_y = -1;
13453 if (!IN_LEV_FIELD(test_x, test_y))
13457 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13459 test_element = Feld[test_x][test_y];
13461 if (test_move_dir != bad_move_dir)
13463 // good thing can be player or penguin that does not move away
13464 if (IS_PLAYER(test_x, test_y))
13466 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13468 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13469 player as being hit when he is moving towards the bad thing, because
13470 the "get hit by" condition would be lost after the player stops) */
13471 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13472 return; // player moves away from bad thing
13477 else if (test_element == EL_PENGUIN)
13484 if (kill_x != -1 || kill_y != -1)
13486 if (IS_PLAYER(kill_x, kill_y))
13488 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13490 if (player->shield_deadly_time_left > 0 &&
13491 !IS_INDESTRUCTIBLE(bad_element))
13492 Bang(bad_x, bad_y);
13493 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13494 KillPlayer(player);
13497 Bang(kill_x, kill_y);
13501 void TestIfPlayerTouchesBadThing(int x, int y)
13503 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13506 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13508 TestIfGoodThingHitsBadThing(x, y, move_dir);
13511 void TestIfBadThingTouchesPlayer(int x, int y)
13513 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13516 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13518 TestIfBadThingHitsGoodThing(x, y, move_dir);
13521 void TestIfFriendTouchesBadThing(int x, int y)
13523 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13526 void TestIfBadThingTouchesFriend(int x, int y)
13528 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13531 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13533 int i, kill_x = bad_x, kill_y = bad_y;
13534 static int xy[4][2] =
13542 for (i = 0; i < NUM_DIRECTIONS; i++)
13546 x = bad_x + xy[i][0];
13547 y = bad_y + xy[i][1];
13548 if (!IN_LEV_FIELD(x, y))
13551 element = Feld[x][y];
13552 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13553 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13561 if (kill_x != bad_x || kill_y != bad_y)
13562 Bang(bad_x, bad_y);
13565 void KillPlayer(struct PlayerInfo *player)
13567 int jx = player->jx, jy = player->jy;
13569 if (!player->active)
13573 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13574 player->killed, player->active, player->reanimated);
13577 /* the following code was introduced to prevent an infinite loop when calling
13579 -> CheckTriggeredElementChangeExt()
13580 -> ExecuteCustomElementAction()
13582 -> (infinitely repeating the above sequence of function calls)
13583 which occurs when killing the player while having a CE with the setting
13584 "kill player X when explosion of <player X>"; the solution using a new
13585 field "player->killed" was chosen for backwards compatibility, although
13586 clever use of the fields "player->active" etc. would probably also work */
13588 if (player->killed)
13592 player->killed = TRUE;
13594 // remove accessible field at the player's position
13595 Feld[jx][jy] = EL_EMPTY;
13597 // deactivate shield (else Bang()/Explode() would not work right)
13598 player->shield_normal_time_left = 0;
13599 player->shield_deadly_time_left = 0;
13602 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13603 player->killed, player->active, player->reanimated);
13609 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13610 player->killed, player->active, player->reanimated);
13613 if (player->reanimated) // killed player may have been reanimated
13614 player->killed = player->reanimated = FALSE;
13616 BuryPlayer(player);
13619 static void KillPlayerUnlessEnemyProtected(int x, int y)
13621 if (!PLAYER_ENEMY_PROTECTED(x, y))
13622 KillPlayer(PLAYERINFO(x, y));
13625 static void KillPlayerUnlessExplosionProtected(int x, int y)
13627 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13628 KillPlayer(PLAYERINFO(x, y));
13631 void BuryPlayer(struct PlayerInfo *player)
13633 int jx = player->jx, jy = player->jy;
13635 if (!player->active)
13638 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13639 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13641 RemovePlayer(player);
13643 player->buried = TRUE;
13645 if (game.all_players_gone)
13646 game.GameOver = TRUE;
13649 void RemovePlayer(struct PlayerInfo *player)
13651 int jx = player->jx, jy = player->jy;
13652 int i, found = FALSE;
13654 player->present = FALSE;
13655 player->active = FALSE;
13657 // required for some CE actions (even if the player is not active anymore)
13658 player->MovPos = 0;
13660 if (!ExplodeField[jx][jy])
13661 StorePlayer[jx][jy] = 0;
13663 if (player->is_moving)
13664 TEST_DrawLevelField(player->last_jx, player->last_jy);
13666 for (i = 0; i < MAX_PLAYERS; i++)
13667 if (stored_player[i].active)
13672 game.all_players_gone = TRUE;
13673 game.GameOver = TRUE;
13676 game.exit_x = game.robot_wheel_x = jx;
13677 game.exit_y = game.robot_wheel_y = jy;
13680 void ExitPlayer(struct PlayerInfo *player)
13682 DrawPlayer(player); // needed here only to cleanup last field
13683 RemovePlayer(player);
13685 if (game.players_still_needed > 0)
13686 game.players_still_needed--;
13689 static void setFieldForSnapping(int x, int y, int element, int direction)
13691 struct ElementInfo *ei = &element_info[element];
13692 int direction_bit = MV_DIR_TO_BIT(direction);
13693 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13694 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13695 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13697 Feld[x][y] = EL_ELEMENT_SNAPPING;
13698 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13700 ResetGfxAnimation(x, y);
13702 GfxElement[x][y] = element;
13703 GfxAction[x][y] = action;
13704 GfxDir[x][y] = direction;
13705 GfxFrame[x][y] = -1;
13709 =============================================================================
13710 checkDiagonalPushing()
13711 -----------------------------------------------------------------------------
13712 check if diagonal input device direction results in pushing of object
13713 (by checking if the alternative direction is walkable, diggable, ...)
13714 =============================================================================
13717 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13718 int x, int y, int real_dx, int real_dy)
13720 int jx, jy, dx, dy, xx, yy;
13722 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13725 // diagonal direction: check alternative direction
13730 xx = jx + (dx == 0 ? real_dx : 0);
13731 yy = jy + (dy == 0 ? real_dy : 0);
13733 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13737 =============================================================================
13739 -----------------------------------------------------------------------------
13740 x, y: field next to player (non-diagonal) to try to dig to
13741 real_dx, real_dy: direction as read from input device (can be diagonal)
13742 =============================================================================
13745 static int DigField(struct PlayerInfo *player,
13746 int oldx, int oldy, int x, int y,
13747 int real_dx, int real_dy, int mode)
13749 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13750 boolean player_was_pushing = player->is_pushing;
13751 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13752 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13753 int jx = oldx, jy = oldy;
13754 int dx = x - jx, dy = y - jy;
13755 int nextx = x + dx, nexty = y + dy;
13756 int move_direction = (dx == -1 ? MV_LEFT :
13757 dx == +1 ? MV_RIGHT :
13759 dy == +1 ? MV_DOWN : MV_NONE);
13760 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13761 int dig_side = MV_DIR_OPPOSITE(move_direction);
13762 int old_element = Feld[jx][jy];
13763 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13766 if (is_player) // function can also be called by EL_PENGUIN
13768 if (player->MovPos == 0)
13770 player->is_digging = FALSE;
13771 player->is_collecting = FALSE;
13774 if (player->MovPos == 0) // last pushing move finished
13775 player->is_pushing = FALSE;
13777 if (mode == DF_NO_PUSH) // player just stopped pushing
13779 player->is_switching = FALSE;
13780 player->push_delay = -1;
13782 return MP_NO_ACTION;
13786 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13787 old_element = Back[jx][jy];
13789 // in case of element dropped at player position, check background
13790 else if (Back[jx][jy] != EL_EMPTY &&
13791 game.engine_version >= VERSION_IDENT(2,2,0,0))
13792 old_element = Back[jx][jy];
13794 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13795 return MP_NO_ACTION; // field has no opening in this direction
13797 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13798 return MP_NO_ACTION; // field has no opening in this direction
13800 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13804 Feld[jx][jy] = player->artwork_element;
13805 InitMovingField(jx, jy, MV_DOWN);
13806 Store[jx][jy] = EL_ACID;
13807 ContinueMoving(jx, jy);
13808 BuryPlayer(player);
13810 return MP_DONT_RUN_INTO;
13813 if (player_can_move && DONT_RUN_INTO(element))
13815 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13817 return MP_DONT_RUN_INTO;
13820 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13821 return MP_NO_ACTION;
13823 collect_count = element_info[element].collect_count_initial;
13825 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13826 return MP_NO_ACTION;
13828 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13829 player_can_move = player_can_move_or_snap;
13831 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13832 game.engine_version >= VERSION_IDENT(2,2,0,0))
13834 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13835 player->index_bit, dig_side);
13836 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13837 player->index_bit, dig_side);
13839 if (element == EL_DC_LANDMINE)
13842 if (Feld[x][y] != element) // field changed by snapping
13845 return MP_NO_ACTION;
13848 if (player->gravity && is_player && !player->is_auto_moving &&
13849 canFallDown(player) && move_direction != MV_DOWN &&
13850 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13851 return MP_NO_ACTION; // player cannot walk here due to gravity
13853 if (player_can_move &&
13854 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13856 int sound_element = SND_ELEMENT(element);
13857 int sound_action = ACTION_WALKING;
13859 if (IS_RND_GATE(element))
13861 if (!player->key[RND_GATE_NR(element)])
13862 return MP_NO_ACTION;
13864 else if (IS_RND_GATE_GRAY(element))
13866 if (!player->key[RND_GATE_GRAY_NR(element)])
13867 return MP_NO_ACTION;
13869 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13871 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13872 return MP_NO_ACTION;
13874 else if (element == EL_EXIT_OPEN ||
13875 element == EL_EM_EXIT_OPEN ||
13876 element == EL_EM_EXIT_OPENING ||
13877 element == EL_STEEL_EXIT_OPEN ||
13878 element == EL_EM_STEEL_EXIT_OPEN ||
13879 element == EL_EM_STEEL_EXIT_OPENING ||
13880 element == EL_SP_EXIT_OPEN ||
13881 element == EL_SP_EXIT_OPENING)
13883 sound_action = ACTION_PASSING; // player is passing exit
13885 else if (element == EL_EMPTY)
13887 sound_action = ACTION_MOVING; // nothing to walk on
13890 // play sound from background or player, whatever is available
13891 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13892 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13894 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13896 else if (player_can_move &&
13897 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13899 if (!ACCESS_FROM(element, opposite_direction))
13900 return MP_NO_ACTION; // field not accessible from this direction
13902 if (CAN_MOVE(element)) // only fixed elements can be passed!
13903 return MP_NO_ACTION;
13905 if (IS_EM_GATE(element))
13907 if (!player->key[EM_GATE_NR(element)])
13908 return MP_NO_ACTION;
13910 else if (IS_EM_GATE_GRAY(element))
13912 if (!player->key[EM_GATE_GRAY_NR(element)])
13913 return MP_NO_ACTION;
13915 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13917 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13918 return MP_NO_ACTION;
13920 else if (IS_EMC_GATE(element))
13922 if (!player->key[EMC_GATE_NR(element)])
13923 return MP_NO_ACTION;
13925 else if (IS_EMC_GATE_GRAY(element))
13927 if (!player->key[EMC_GATE_GRAY_NR(element)])
13928 return MP_NO_ACTION;
13930 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13932 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13933 return MP_NO_ACTION;
13935 else if (element == EL_DC_GATE_WHITE ||
13936 element == EL_DC_GATE_WHITE_GRAY ||
13937 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13939 if (player->num_white_keys == 0)
13940 return MP_NO_ACTION;
13942 player->num_white_keys--;
13944 else if (IS_SP_PORT(element))
13946 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13947 element == EL_SP_GRAVITY_PORT_RIGHT ||
13948 element == EL_SP_GRAVITY_PORT_UP ||
13949 element == EL_SP_GRAVITY_PORT_DOWN)
13950 player->gravity = !player->gravity;
13951 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13952 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13953 element == EL_SP_GRAVITY_ON_PORT_UP ||
13954 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13955 player->gravity = TRUE;
13956 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13957 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13958 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13959 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13960 player->gravity = FALSE;
13963 // automatically move to the next field with double speed
13964 player->programmed_action = move_direction;
13966 if (player->move_delay_reset_counter == 0)
13968 player->move_delay_reset_counter = 2; // two double speed steps
13970 DOUBLE_PLAYER_SPEED(player);
13973 PlayLevelSoundAction(x, y, ACTION_PASSING);
13975 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13979 if (mode != DF_SNAP)
13981 GfxElement[x][y] = GFX_ELEMENT(element);
13982 player->is_digging = TRUE;
13985 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13987 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13988 player->index_bit, dig_side);
13990 if (mode == DF_SNAP)
13992 if (level.block_snap_field)
13993 setFieldForSnapping(x, y, element, move_direction);
13995 TestIfElementTouchesCustomElement(x, y); // for empty space
13997 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13998 player->index_bit, dig_side);
14001 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14005 if (is_player && mode != DF_SNAP)
14007 GfxElement[x][y] = element;
14008 player->is_collecting = TRUE;
14011 if (element == EL_SPEED_PILL)
14013 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14015 else if (element == EL_EXTRA_TIME && level.time > 0)
14017 TimeLeft += level.extra_time;
14019 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14021 DisplayGameControlValues();
14023 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14025 player->shield_normal_time_left += level.shield_normal_time;
14026 if (element == EL_SHIELD_DEADLY)
14027 player->shield_deadly_time_left += level.shield_deadly_time;
14029 else if (element == EL_DYNAMITE ||
14030 element == EL_EM_DYNAMITE ||
14031 element == EL_SP_DISK_RED)
14033 if (player->inventory_size < MAX_INVENTORY_SIZE)
14034 player->inventory_element[player->inventory_size++] = element;
14036 DrawGameDoorValues();
14038 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14040 player->dynabomb_count++;
14041 player->dynabombs_left++;
14043 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14045 player->dynabomb_size++;
14047 else if (element == EL_DYNABOMB_INCREASE_POWER)
14049 player->dynabomb_xl = TRUE;
14051 else if (IS_KEY(element))
14053 player->key[KEY_NR(element)] = TRUE;
14055 DrawGameDoorValues();
14057 else if (element == EL_DC_KEY_WHITE)
14059 player->num_white_keys++;
14061 // display white keys?
14062 // DrawGameDoorValues();
14064 else if (IS_ENVELOPE(element))
14066 player->show_envelope = element;
14068 else if (element == EL_EMC_LENSES)
14070 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14072 RedrawAllInvisibleElementsForLenses();
14074 else if (element == EL_EMC_MAGNIFIER)
14076 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14078 RedrawAllInvisibleElementsForMagnifier();
14080 else if (IS_DROPPABLE(element) ||
14081 IS_THROWABLE(element)) // can be collected and dropped
14085 if (collect_count == 0)
14086 player->inventory_infinite_element = element;
14088 for (i = 0; i < collect_count; i++)
14089 if (player->inventory_size < MAX_INVENTORY_SIZE)
14090 player->inventory_element[player->inventory_size++] = element;
14092 DrawGameDoorValues();
14094 else if (collect_count > 0)
14096 game.gems_still_needed -= collect_count;
14097 if (game.gems_still_needed < 0)
14098 game.gems_still_needed = 0;
14100 game.snapshot.collected_item = TRUE;
14102 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14104 DisplayGameControlValues();
14107 RaiseScoreElement(element);
14108 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14111 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14112 player->index_bit, dig_side);
14114 if (mode == DF_SNAP)
14116 if (level.block_snap_field)
14117 setFieldForSnapping(x, y, element, move_direction);
14119 TestIfElementTouchesCustomElement(x, y); // for empty space
14121 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14122 player->index_bit, dig_side);
14125 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14127 if (mode == DF_SNAP && element != EL_BD_ROCK)
14128 return MP_NO_ACTION;
14130 if (CAN_FALL(element) && dy)
14131 return MP_NO_ACTION;
14133 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14134 !(element == EL_SPRING && level.use_spring_bug))
14135 return MP_NO_ACTION;
14137 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14138 ((move_direction & MV_VERTICAL &&
14139 ((element_info[element].move_pattern & MV_LEFT &&
14140 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14141 (element_info[element].move_pattern & MV_RIGHT &&
14142 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14143 (move_direction & MV_HORIZONTAL &&
14144 ((element_info[element].move_pattern & MV_UP &&
14145 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14146 (element_info[element].move_pattern & MV_DOWN &&
14147 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14148 return MP_NO_ACTION;
14150 // do not push elements already moving away faster than player
14151 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14152 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14153 return MP_NO_ACTION;
14155 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14157 if (player->push_delay_value == -1 || !player_was_pushing)
14158 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14160 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14162 if (player->push_delay_value == -1)
14163 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14165 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14167 if (!player->is_pushing)
14168 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14171 player->is_pushing = TRUE;
14172 player->is_active = TRUE;
14174 if (!(IN_LEV_FIELD(nextx, nexty) &&
14175 (IS_FREE(nextx, nexty) ||
14176 (IS_SB_ELEMENT(element) &&
14177 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14178 (IS_CUSTOM_ELEMENT(element) &&
14179 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14180 return MP_NO_ACTION;
14182 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14183 return MP_NO_ACTION;
14185 if (player->push_delay == -1) // new pushing; restart delay
14186 player->push_delay = 0;
14188 if (player->push_delay < player->push_delay_value &&
14189 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14190 element != EL_SPRING && element != EL_BALLOON)
14192 // make sure that there is no move delay before next try to push
14193 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14194 player->move_delay = 0;
14196 return MP_NO_ACTION;
14199 if (IS_CUSTOM_ELEMENT(element) &&
14200 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14202 if (!DigFieldByCE(nextx, nexty, element))
14203 return MP_NO_ACTION;
14206 if (IS_SB_ELEMENT(element))
14208 boolean sokoban_task_solved = FALSE;
14210 if (element == EL_SOKOBAN_FIELD_FULL)
14212 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14214 IncrementSokobanFieldsNeeded();
14215 IncrementSokobanObjectsNeeded();
14218 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14220 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14222 DecrementSokobanFieldsNeeded();
14223 DecrementSokobanObjectsNeeded();
14225 // sokoban object was pushed from empty field to sokoban field
14226 if (Back[x][y] == EL_EMPTY)
14227 sokoban_task_solved = TRUE;
14230 Feld[x][y] = EL_SOKOBAN_OBJECT;
14232 if (Back[x][y] == Back[nextx][nexty])
14233 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14234 else if (Back[x][y] != 0)
14235 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14238 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14241 if (sokoban_task_solved &&
14242 game.sokoban_fields_still_needed == 0 &&
14243 game.sokoban_objects_still_needed == 0 &&
14244 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14246 game.players_still_needed = 0;
14250 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14254 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14256 InitMovingField(x, y, move_direction);
14257 GfxAction[x][y] = ACTION_PUSHING;
14259 if (mode == DF_SNAP)
14260 ContinueMoving(x, y);
14262 MovPos[x][y] = (dx != 0 ? dx : dy);
14264 Pushed[x][y] = TRUE;
14265 Pushed[nextx][nexty] = TRUE;
14267 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14268 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14270 player->push_delay_value = -1; // get new value later
14272 // check for element change _after_ element has been pushed
14273 if (game.use_change_when_pushing_bug)
14275 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14276 player->index_bit, dig_side);
14277 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14278 player->index_bit, dig_side);
14281 else if (IS_SWITCHABLE(element))
14283 if (PLAYER_SWITCHING(player, x, y))
14285 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14286 player->index_bit, dig_side);
14291 player->is_switching = TRUE;
14292 player->switch_x = x;
14293 player->switch_y = y;
14295 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14297 if (element == EL_ROBOT_WHEEL)
14299 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14301 game.robot_wheel_x = x;
14302 game.robot_wheel_y = y;
14303 game.robot_wheel_active = TRUE;
14305 TEST_DrawLevelField(x, y);
14307 else if (element == EL_SP_TERMINAL)
14311 SCAN_PLAYFIELD(xx, yy)
14313 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14317 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14319 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14321 ResetGfxAnimation(xx, yy);
14322 TEST_DrawLevelField(xx, yy);
14326 else if (IS_BELT_SWITCH(element))
14328 ToggleBeltSwitch(x, y);
14330 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14331 element == EL_SWITCHGATE_SWITCH_DOWN ||
14332 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14333 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14335 ToggleSwitchgateSwitch(x, y);
14337 else if (element == EL_LIGHT_SWITCH ||
14338 element == EL_LIGHT_SWITCH_ACTIVE)
14340 ToggleLightSwitch(x, y);
14342 else if (element == EL_TIMEGATE_SWITCH ||
14343 element == EL_DC_TIMEGATE_SWITCH)
14345 ActivateTimegateSwitch(x, y);
14347 else if (element == EL_BALLOON_SWITCH_LEFT ||
14348 element == EL_BALLOON_SWITCH_RIGHT ||
14349 element == EL_BALLOON_SWITCH_UP ||
14350 element == EL_BALLOON_SWITCH_DOWN ||
14351 element == EL_BALLOON_SWITCH_NONE ||
14352 element == EL_BALLOON_SWITCH_ANY)
14354 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14355 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14356 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14357 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14358 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14361 else if (element == EL_LAMP)
14363 Feld[x][y] = EL_LAMP_ACTIVE;
14364 game.lights_still_needed--;
14366 ResetGfxAnimation(x, y);
14367 TEST_DrawLevelField(x, y);
14369 else if (element == EL_TIME_ORB_FULL)
14371 Feld[x][y] = EL_TIME_ORB_EMPTY;
14373 if (level.time > 0 || level.use_time_orb_bug)
14375 TimeLeft += level.time_orb_time;
14376 game.no_time_limit = FALSE;
14378 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14380 DisplayGameControlValues();
14383 ResetGfxAnimation(x, y);
14384 TEST_DrawLevelField(x, y);
14386 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14387 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14391 game.ball_active = !game.ball_active;
14393 SCAN_PLAYFIELD(xx, yy)
14395 int e = Feld[xx][yy];
14397 if (game.ball_active)
14399 if (e == EL_EMC_MAGIC_BALL)
14400 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14401 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14402 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14406 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14407 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14408 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14409 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14414 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14415 player->index_bit, dig_side);
14417 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14418 player->index_bit, dig_side);
14420 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14421 player->index_bit, dig_side);
14427 if (!PLAYER_SWITCHING(player, x, y))
14429 player->is_switching = TRUE;
14430 player->switch_x = x;
14431 player->switch_y = y;
14433 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14434 player->index_bit, dig_side);
14435 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14436 player->index_bit, dig_side);
14438 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14439 player->index_bit, dig_side);
14440 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14441 player->index_bit, dig_side);
14444 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14445 player->index_bit, dig_side);
14446 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14447 player->index_bit, dig_side);
14449 return MP_NO_ACTION;
14452 player->push_delay = -1;
14454 if (is_player) // function can also be called by EL_PENGUIN
14456 if (Feld[x][y] != element) // really digged/collected something
14458 player->is_collecting = !player->is_digging;
14459 player->is_active = TRUE;
14466 static boolean DigFieldByCE(int x, int y, int digging_element)
14468 int element = Feld[x][y];
14470 if (!IS_FREE(x, y))
14472 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14473 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14476 // no element can dig solid indestructible elements
14477 if (IS_INDESTRUCTIBLE(element) &&
14478 !IS_DIGGABLE(element) &&
14479 !IS_COLLECTIBLE(element))
14482 if (AmoebaNr[x][y] &&
14483 (element == EL_AMOEBA_FULL ||
14484 element == EL_BD_AMOEBA ||
14485 element == EL_AMOEBA_GROWING))
14487 AmoebaCnt[AmoebaNr[x][y]]--;
14488 AmoebaCnt2[AmoebaNr[x][y]]--;
14491 if (IS_MOVING(x, y))
14492 RemoveMovingField(x, y);
14496 TEST_DrawLevelField(x, y);
14499 // if digged element was about to explode, prevent the explosion
14500 ExplodeField[x][y] = EX_TYPE_NONE;
14502 PlayLevelSoundAction(x, y, action);
14505 Store[x][y] = EL_EMPTY;
14507 // this makes it possible to leave the removed element again
14508 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14509 Store[x][y] = element;
14514 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14516 int jx = player->jx, jy = player->jy;
14517 int x = jx + dx, y = jy + dy;
14518 int snap_direction = (dx == -1 ? MV_LEFT :
14519 dx == +1 ? MV_RIGHT :
14521 dy == +1 ? MV_DOWN : MV_NONE);
14522 boolean can_continue_snapping = (level.continuous_snapping &&
14523 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14525 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14528 if (!player->active || !IN_LEV_FIELD(x, y))
14536 if (player->MovPos == 0)
14537 player->is_pushing = FALSE;
14539 player->is_snapping = FALSE;
14541 if (player->MovPos == 0)
14543 player->is_moving = FALSE;
14544 player->is_digging = FALSE;
14545 player->is_collecting = FALSE;
14551 // prevent snapping with already pressed snap key when not allowed
14552 if (player->is_snapping && !can_continue_snapping)
14555 player->MovDir = snap_direction;
14557 if (player->MovPos == 0)
14559 player->is_moving = FALSE;
14560 player->is_digging = FALSE;
14561 player->is_collecting = FALSE;
14564 player->is_dropping = FALSE;
14565 player->is_dropping_pressed = FALSE;
14566 player->drop_pressed_delay = 0;
14568 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14571 player->is_snapping = TRUE;
14572 player->is_active = TRUE;
14574 if (player->MovPos == 0)
14576 player->is_moving = FALSE;
14577 player->is_digging = FALSE;
14578 player->is_collecting = FALSE;
14581 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14582 TEST_DrawLevelField(player->last_jx, player->last_jy);
14584 TEST_DrawLevelField(x, y);
14589 static boolean DropElement(struct PlayerInfo *player)
14591 int old_element, new_element;
14592 int dropx = player->jx, dropy = player->jy;
14593 int drop_direction = player->MovDir;
14594 int drop_side = drop_direction;
14595 int drop_element = get_next_dropped_element(player);
14597 /* do not drop an element on top of another element; when holding drop key
14598 pressed without moving, dropped element must move away before the next
14599 element can be dropped (this is especially important if the next element
14600 is dynamite, which can be placed on background for historical reasons) */
14601 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14604 if (IS_THROWABLE(drop_element))
14606 dropx += GET_DX_FROM_DIR(drop_direction);
14607 dropy += GET_DY_FROM_DIR(drop_direction);
14609 if (!IN_LEV_FIELD(dropx, dropy))
14613 old_element = Feld[dropx][dropy]; // old element at dropping position
14614 new_element = drop_element; // default: no change when dropping
14616 // check if player is active, not moving and ready to drop
14617 if (!player->active || player->MovPos || player->drop_delay > 0)
14620 // check if player has anything that can be dropped
14621 if (new_element == EL_UNDEFINED)
14624 // only set if player has anything that can be dropped
14625 player->is_dropping_pressed = TRUE;
14627 // check if drop key was pressed long enough for EM style dynamite
14628 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14631 // check if anything can be dropped at the current position
14632 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14635 // collected custom elements can only be dropped on empty fields
14636 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14639 if (old_element != EL_EMPTY)
14640 Back[dropx][dropy] = old_element; // store old element on this field
14642 ResetGfxAnimation(dropx, dropy);
14643 ResetRandomAnimationValue(dropx, dropy);
14645 if (player->inventory_size > 0 ||
14646 player->inventory_infinite_element != EL_UNDEFINED)
14648 if (player->inventory_size > 0)
14650 player->inventory_size--;
14652 DrawGameDoorValues();
14654 if (new_element == EL_DYNAMITE)
14655 new_element = EL_DYNAMITE_ACTIVE;
14656 else if (new_element == EL_EM_DYNAMITE)
14657 new_element = EL_EM_DYNAMITE_ACTIVE;
14658 else if (new_element == EL_SP_DISK_RED)
14659 new_element = EL_SP_DISK_RED_ACTIVE;
14662 Feld[dropx][dropy] = new_element;
14664 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14665 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14666 el2img(Feld[dropx][dropy]), 0);
14668 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14670 // needed if previous element just changed to "empty" in the last frame
14671 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14673 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14674 player->index_bit, drop_side);
14675 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14677 player->index_bit, drop_side);
14679 TestIfElementTouchesCustomElement(dropx, dropy);
14681 else // player is dropping a dyna bomb
14683 player->dynabombs_left--;
14685 Feld[dropx][dropy] = new_element;
14687 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14688 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14689 el2img(Feld[dropx][dropy]), 0);
14691 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14694 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14695 InitField_WithBug1(dropx, dropy, FALSE);
14697 new_element = Feld[dropx][dropy]; // element might have changed
14699 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14700 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14702 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14703 MovDir[dropx][dropy] = drop_direction;
14705 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14707 // do not cause impact style collision by dropping elements that can fall
14708 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14711 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14712 player->is_dropping = TRUE;
14714 player->drop_pressed_delay = 0;
14715 player->is_dropping_pressed = FALSE;
14717 player->drop_x = dropx;
14718 player->drop_y = dropy;
14723 // ----------------------------------------------------------------------------
14724 // game sound playing functions
14725 // ----------------------------------------------------------------------------
14727 static int *loop_sound_frame = NULL;
14728 static int *loop_sound_volume = NULL;
14730 void InitPlayLevelSound(void)
14732 int num_sounds = getSoundListSize();
14734 checked_free(loop_sound_frame);
14735 checked_free(loop_sound_volume);
14737 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14738 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14741 static void PlayLevelSound(int x, int y, int nr)
14743 int sx = SCREENX(x), sy = SCREENY(y);
14744 int volume, stereo_position;
14745 int max_distance = 8;
14746 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14748 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14749 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14752 if (!IN_LEV_FIELD(x, y) ||
14753 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14754 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14757 volume = SOUND_MAX_VOLUME;
14759 if (!IN_SCR_FIELD(sx, sy))
14761 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14762 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14764 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14767 stereo_position = (SOUND_MAX_LEFT +
14768 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14769 (SCR_FIELDX + 2 * max_distance));
14771 if (IS_LOOP_SOUND(nr))
14773 /* This assures that quieter loop sounds do not overwrite louder ones,
14774 while restarting sound volume comparison with each new game frame. */
14776 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14779 loop_sound_volume[nr] = volume;
14780 loop_sound_frame[nr] = FrameCounter;
14783 PlaySoundExt(nr, volume, stereo_position, type);
14786 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14788 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14789 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14790 y < LEVELY(BY1) ? LEVELY(BY1) :
14791 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14795 static void PlayLevelSoundAction(int x, int y, int action)
14797 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14800 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14802 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14804 if (sound_effect != SND_UNDEFINED)
14805 PlayLevelSound(x, y, sound_effect);
14808 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14811 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14813 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14814 PlayLevelSound(x, y, sound_effect);
14817 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14819 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14821 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14822 PlayLevelSound(x, y, sound_effect);
14825 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14827 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14829 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14830 StopSound(sound_effect);
14833 static int getLevelMusicNr(void)
14835 if (levelset.music[level_nr] != MUS_UNDEFINED)
14836 return levelset.music[level_nr]; // from config file
14838 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14841 static void FadeLevelSounds(void)
14846 static void FadeLevelMusic(void)
14848 int music_nr = getLevelMusicNr();
14849 char *curr_music = getCurrentlyPlayingMusicFilename();
14850 char *next_music = getMusicInfoEntryFilename(music_nr);
14852 if (!strEqual(curr_music, next_music))
14856 void FadeLevelSoundsAndMusic(void)
14862 static void PlayLevelMusic(void)
14864 int music_nr = getLevelMusicNr();
14865 char *curr_music = getCurrentlyPlayingMusicFilename();
14866 char *next_music = getMusicInfoEntryFilename(music_nr);
14868 if (!strEqual(curr_music, next_music))
14869 PlayMusicLoop(music_nr);
14872 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14874 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14876 int x = xx - offset;
14877 int y = yy - offset;
14882 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14886 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14890 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14894 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14898 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14902 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14906 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14909 case SOUND_android_clone:
14910 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14913 case SOUND_android_move:
14914 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14918 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14922 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14926 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14929 case SOUND_eater_eat:
14930 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14934 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14937 case SOUND_collect:
14938 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14941 case SOUND_diamond:
14942 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14946 // !!! CHECK THIS !!!
14948 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14950 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14954 case SOUND_wonderfall:
14955 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14959 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14963 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14967 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14971 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14975 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14979 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14983 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14987 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14990 case SOUND_exit_open:
14991 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14994 case SOUND_exit_leave:
14995 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14998 case SOUND_dynamite:
14999 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15003 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15007 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15011 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15015 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15019 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15023 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15027 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15032 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15034 int element = map_element_SP_to_RND(element_sp);
15035 int action = map_action_SP_to_RND(action_sp);
15036 int offset = (setup.sp_show_border_elements ? 0 : 1);
15037 int x = xx - offset;
15038 int y = yy - offset;
15040 PlayLevelSoundElementAction(x, y, element, action);
15043 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15045 int element = map_element_MM_to_RND(element_mm);
15046 int action = map_action_MM_to_RND(action_mm);
15048 int x = xx - offset;
15049 int y = yy - offset;
15051 if (!IS_MM_ELEMENT(element))
15052 element = EL_MM_DEFAULT;
15054 PlayLevelSoundElementAction(x, y, element, action);
15057 void PlaySound_MM(int sound_mm)
15059 int sound = map_sound_MM_to_RND(sound_mm);
15061 if (sound == SND_UNDEFINED)
15067 void PlaySoundLoop_MM(int sound_mm)
15069 int sound = map_sound_MM_to_RND(sound_mm);
15071 if (sound == SND_UNDEFINED)
15074 PlaySoundLoop(sound);
15077 void StopSound_MM(int sound_mm)
15079 int sound = map_sound_MM_to_RND(sound_mm);
15081 if (sound == SND_UNDEFINED)
15087 void RaiseScore(int value)
15089 game.score += value;
15091 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15093 DisplayGameControlValues();
15096 void RaiseScoreElement(int element)
15101 case EL_BD_DIAMOND:
15102 case EL_EMERALD_YELLOW:
15103 case EL_EMERALD_RED:
15104 case EL_EMERALD_PURPLE:
15105 case EL_SP_INFOTRON:
15106 RaiseScore(level.score[SC_EMERALD]);
15109 RaiseScore(level.score[SC_DIAMOND]);
15112 RaiseScore(level.score[SC_CRYSTAL]);
15115 RaiseScore(level.score[SC_PEARL]);
15118 case EL_BD_BUTTERFLY:
15119 case EL_SP_ELECTRON:
15120 RaiseScore(level.score[SC_BUG]);
15123 case EL_BD_FIREFLY:
15124 case EL_SP_SNIKSNAK:
15125 RaiseScore(level.score[SC_SPACESHIP]);
15128 case EL_DARK_YAMYAM:
15129 RaiseScore(level.score[SC_YAMYAM]);
15132 RaiseScore(level.score[SC_ROBOT]);
15135 RaiseScore(level.score[SC_PACMAN]);
15138 RaiseScore(level.score[SC_NUT]);
15141 case EL_EM_DYNAMITE:
15142 case EL_SP_DISK_RED:
15143 case EL_DYNABOMB_INCREASE_NUMBER:
15144 case EL_DYNABOMB_INCREASE_SIZE:
15145 case EL_DYNABOMB_INCREASE_POWER:
15146 RaiseScore(level.score[SC_DYNAMITE]);
15148 case EL_SHIELD_NORMAL:
15149 case EL_SHIELD_DEADLY:
15150 RaiseScore(level.score[SC_SHIELD]);
15152 case EL_EXTRA_TIME:
15153 RaiseScore(level.extra_time_score);
15167 case EL_DC_KEY_WHITE:
15168 RaiseScore(level.score[SC_KEY]);
15171 RaiseScore(element_info[element].collect_score);
15176 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15178 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15180 // closing door required in case of envelope style request dialogs
15183 // prevent short reactivation of overlay buttons while closing door
15184 SetOverlayActive(FALSE);
15186 CloseDoor(DOOR_CLOSE_1);
15189 if (network.enabled)
15190 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15194 FadeSkipNextFadeIn();
15196 SetGameStatus(GAME_MODE_MAIN);
15201 else // continue playing the game
15203 if (tape.playing && tape.deactivate_display)
15204 TapeDeactivateDisplayOff(TRUE);
15206 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15208 if (tape.playing && tape.deactivate_display)
15209 TapeDeactivateDisplayOn();
15213 void RequestQuitGame(boolean ask_if_really_quit)
15215 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15216 boolean skip_request = game.all_players_gone || quick_quit;
15218 RequestQuitGameExt(skip_request, quick_quit,
15219 "Do you really want to quit the game?");
15222 void RequestRestartGame(char *message)
15224 game.restart_game_message = NULL;
15226 boolean has_started_game = hasStartedNetworkGame();
15227 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15229 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15231 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15235 SetGameStatus(GAME_MODE_MAIN);
15241 void CheckGameOver(void)
15243 static boolean last_game_over = FALSE;
15244 static int game_over_delay = 0;
15245 int game_over_delay_value = 50;
15246 boolean game_over = checkGameFailed();
15248 // do not handle game over if request dialog is already active
15249 if (game.request_active)
15252 // do not ask to play again if game was never actually played
15253 if (!game.GamePlayed)
15258 last_game_over = FALSE;
15259 game_over_delay = game_over_delay_value;
15264 if (game_over_delay > 0)
15271 if (last_game_over != game_over)
15272 game.restart_game_message = (hasStartedNetworkGame() ?
15273 "Game over! Play it again?" :
15276 last_game_over = game_over;
15279 boolean checkGameSolved(void)
15281 // set for all game engines if level was solved
15282 return game.LevelSolved_GameEnd;
15285 boolean checkGameFailed(void)
15287 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15288 return (game_em.game_over && !game_em.level_solved);
15289 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15290 return (game_sp.game_over && !game_sp.level_solved);
15291 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15292 return (game_mm.game_over && !game_mm.level_solved);
15293 else // GAME_ENGINE_TYPE_RND
15294 return (game.GameOver && !game.LevelSolved);
15297 boolean checkGameEnded(void)
15299 return (checkGameSolved() || checkGameFailed());
15303 // ----------------------------------------------------------------------------
15304 // random generator functions
15305 // ----------------------------------------------------------------------------
15307 unsigned int InitEngineRandom_RND(int seed)
15309 game.num_random_calls = 0;
15311 return InitEngineRandom(seed);
15314 unsigned int RND(int max)
15318 game.num_random_calls++;
15320 return GetEngineRandom(max);
15327 // ----------------------------------------------------------------------------
15328 // game engine snapshot handling functions
15329 // ----------------------------------------------------------------------------
15331 struct EngineSnapshotInfo
15333 // runtime values for custom element collect score
15334 int collect_score[NUM_CUSTOM_ELEMENTS];
15336 // runtime values for group element choice position
15337 int choice_pos[NUM_GROUP_ELEMENTS];
15339 // runtime values for belt position animations
15340 int belt_graphic[4][NUM_BELT_PARTS];
15341 int belt_anim_mode[4][NUM_BELT_PARTS];
15344 static struct EngineSnapshotInfo engine_snapshot_rnd;
15345 static char *snapshot_level_identifier = NULL;
15346 static int snapshot_level_nr = -1;
15348 static void SaveEngineSnapshotValues_RND(void)
15350 static int belt_base_active_element[4] =
15352 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15353 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15354 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15355 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15359 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15361 int element = EL_CUSTOM_START + i;
15363 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15366 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15368 int element = EL_GROUP_START + i;
15370 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15373 for (i = 0; i < 4; i++)
15375 for (j = 0; j < NUM_BELT_PARTS; j++)
15377 int element = belt_base_active_element[i] + j;
15378 int graphic = el2img(element);
15379 int anim_mode = graphic_info[graphic].anim_mode;
15381 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15382 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15387 static void LoadEngineSnapshotValues_RND(void)
15389 unsigned int num_random_calls = game.num_random_calls;
15392 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15394 int element = EL_CUSTOM_START + i;
15396 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15399 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15401 int element = EL_GROUP_START + i;
15403 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15406 for (i = 0; i < 4; i++)
15408 for (j = 0; j < NUM_BELT_PARTS; j++)
15410 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15411 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15413 graphic_info[graphic].anim_mode = anim_mode;
15417 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15419 InitRND(tape.random_seed);
15420 for (i = 0; i < num_random_calls; i++)
15424 if (game.num_random_calls != num_random_calls)
15426 Error(ERR_INFO, "number of random calls out of sync");
15427 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15428 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15429 Error(ERR_EXIT, "this should not happen -- please debug");
15433 void FreeEngineSnapshotSingle(void)
15435 FreeSnapshotSingle();
15437 setString(&snapshot_level_identifier, NULL);
15438 snapshot_level_nr = -1;
15441 void FreeEngineSnapshotList(void)
15443 FreeSnapshotList();
15446 static ListNode *SaveEngineSnapshotBuffers(void)
15448 ListNode *buffers = NULL;
15450 // copy some special values to a structure better suited for the snapshot
15452 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15453 SaveEngineSnapshotValues_RND();
15454 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15455 SaveEngineSnapshotValues_EM();
15456 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15457 SaveEngineSnapshotValues_SP(&buffers);
15458 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15459 SaveEngineSnapshotValues_MM(&buffers);
15461 // save values stored in special snapshot structure
15463 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15464 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15465 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15466 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15467 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15468 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15469 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15470 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15472 // save further RND engine values
15474 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15475 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15476 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15478 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15479 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15480 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15481 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15482 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15484 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15485 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15486 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15488 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15490 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15491 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15493 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15494 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15495 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15496 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15497 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15498 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15499 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15500 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15501 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15502 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15503 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15504 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15505 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15506 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15507 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15508 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15509 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15510 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15512 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15513 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15515 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15516 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15517 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15519 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15520 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15522 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15523 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15524 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15525 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15526 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15528 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15529 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15532 ListNode *node = engine_snapshot_list_rnd;
15535 while (node != NULL)
15537 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15542 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15548 void SaveEngineSnapshotSingle(void)
15550 ListNode *buffers = SaveEngineSnapshotBuffers();
15552 // finally save all snapshot buffers to single snapshot
15553 SaveSnapshotSingle(buffers);
15555 // save level identification information
15556 setString(&snapshot_level_identifier, leveldir_current->identifier);
15557 snapshot_level_nr = level_nr;
15560 boolean CheckSaveEngineSnapshotToList(void)
15562 boolean save_snapshot =
15563 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15564 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15565 game.snapshot.changed_action) ||
15566 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15567 game.snapshot.collected_item));
15569 game.snapshot.changed_action = FALSE;
15570 game.snapshot.collected_item = FALSE;
15571 game.snapshot.save_snapshot = save_snapshot;
15573 return save_snapshot;
15576 void SaveEngineSnapshotToList(void)
15578 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15582 ListNode *buffers = SaveEngineSnapshotBuffers();
15584 // finally save all snapshot buffers to snapshot list
15585 SaveSnapshotToList(buffers);
15588 void SaveEngineSnapshotToListInitial(void)
15590 FreeEngineSnapshotList();
15592 SaveEngineSnapshotToList();
15595 static void LoadEngineSnapshotValues(void)
15597 // restore special values from snapshot structure
15599 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15600 LoadEngineSnapshotValues_RND();
15601 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15602 LoadEngineSnapshotValues_EM();
15603 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15604 LoadEngineSnapshotValues_SP();
15605 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15606 LoadEngineSnapshotValues_MM();
15609 void LoadEngineSnapshotSingle(void)
15611 LoadSnapshotSingle();
15613 LoadEngineSnapshotValues();
15616 static void LoadEngineSnapshot_Undo(int steps)
15618 LoadSnapshotFromList_Older(steps);
15620 LoadEngineSnapshotValues();
15623 static void LoadEngineSnapshot_Redo(int steps)
15625 LoadSnapshotFromList_Newer(steps);
15627 LoadEngineSnapshotValues();
15630 boolean CheckEngineSnapshotSingle(void)
15632 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15633 snapshot_level_nr == level_nr);
15636 boolean CheckEngineSnapshotList(void)
15638 return CheckSnapshotList();
15642 // ---------- new game button stuff -------------------------------------------
15649 boolean *setup_value;
15650 boolean allowed_on_tape;
15651 boolean is_touch_button;
15653 } gamebutton_info[NUM_GAME_BUTTONS] =
15656 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15657 GAME_CTRL_ID_STOP, NULL,
15658 TRUE, FALSE, "stop game"
15661 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15662 GAME_CTRL_ID_PAUSE, NULL,
15663 TRUE, FALSE, "pause game"
15666 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15667 GAME_CTRL_ID_PLAY, NULL,
15668 TRUE, FALSE, "play game"
15671 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15672 GAME_CTRL_ID_UNDO, NULL,
15673 TRUE, FALSE, "undo step"
15676 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15677 GAME_CTRL_ID_REDO, NULL,
15678 TRUE, FALSE, "redo step"
15681 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15682 GAME_CTRL_ID_SAVE, NULL,
15683 TRUE, FALSE, "save game"
15686 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15687 GAME_CTRL_ID_PAUSE2, NULL,
15688 TRUE, FALSE, "pause game"
15691 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15692 GAME_CTRL_ID_LOAD, NULL,
15693 TRUE, FALSE, "load game"
15696 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15697 GAME_CTRL_ID_PANEL_STOP, NULL,
15698 FALSE, FALSE, "stop game"
15701 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15702 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15703 FALSE, FALSE, "pause game"
15706 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15707 GAME_CTRL_ID_PANEL_PLAY, NULL,
15708 FALSE, FALSE, "play game"
15711 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15712 GAME_CTRL_ID_TOUCH_STOP, NULL,
15713 FALSE, TRUE, "stop game"
15716 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15717 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15718 FALSE, TRUE, "pause game"
15721 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15722 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15723 TRUE, FALSE, "background music on/off"
15726 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15727 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15728 TRUE, FALSE, "sound loops on/off"
15731 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15732 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15733 TRUE, FALSE, "normal sounds on/off"
15736 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15737 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15738 FALSE, FALSE, "background music on/off"
15741 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15742 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15743 FALSE, FALSE, "sound loops on/off"
15746 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15747 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15748 FALSE, FALSE, "normal sounds on/off"
15752 void CreateGameButtons(void)
15756 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15758 int graphic = gamebutton_info[i].graphic;
15759 struct GraphicInfo *gfx = &graphic_info[graphic];
15760 struct XY *pos = gamebutton_info[i].pos;
15761 struct GadgetInfo *gi;
15764 unsigned int event_mask;
15765 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15766 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15767 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15768 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15769 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15770 int gd_x = gfx->src_x;
15771 int gd_y = gfx->src_y;
15772 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15773 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15774 int gd_xa = gfx->src_x + gfx->active_xoffset;
15775 int gd_ya = gfx->src_y + gfx->active_yoffset;
15776 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15777 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15778 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15779 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15782 if (gfx->bitmap == NULL)
15784 game_gadget[id] = NULL;
15789 if (id == GAME_CTRL_ID_STOP ||
15790 id == GAME_CTRL_ID_PANEL_STOP ||
15791 id == GAME_CTRL_ID_TOUCH_STOP ||
15792 id == GAME_CTRL_ID_PLAY ||
15793 id == GAME_CTRL_ID_PANEL_PLAY ||
15794 id == GAME_CTRL_ID_SAVE ||
15795 id == GAME_CTRL_ID_LOAD)
15797 button_type = GD_TYPE_NORMAL_BUTTON;
15799 event_mask = GD_EVENT_RELEASED;
15801 else if (id == GAME_CTRL_ID_UNDO ||
15802 id == GAME_CTRL_ID_REDO)
15804 button_type = GD_TYPE_NORMAL_BUTTON;
15806 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15810 button_type = GD_TYPE_CHECK_BUTTON;
15811 checked = (gamebutton_info[i].setup_value != NULL ?
15812 *gamebutton_info[i].setup_value : FALSE);
15813 event_mask = GD_EVENT_PRESSED;
15816 gi = CreateGadget(GDI_CUSTOM_ID, id,
15817 GDI_IMAGE_ID, graphic,
15818 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15821 GDI_WIDTH, gfx->width,
15822 GDI_HEIGHT, gfx->height,
15823 GDI_TYPE, button_type,
15824 GDI_STATE, GD_BUTTON_UNPRESSED,
15825 GDI_CHECKED, checked,
15826 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15827 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15828 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15829 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15830 GDI_DIRECT_DRAW, FALSE,
15831 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15832 GDI_EVENT_MASK, event_mask,
15833 GDI_CALLBACK_ACTION, HandleGameButtons,
15837 Error(ERR_EXIT, "cannot create gadget");
15839 game_gadget[id] = gi;
15843 void FreeGameButtons(void)
15847 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15848 FreeGadget(game_gadget[i]);
15851 static void UnmapGameButtonsAtSamePosition(int id)
15855 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15857 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15858 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15859 UnmapGadget(game_gadget[i]);
15862 static void UnmapGameButtonsAtSamePosition_All(void)
15864 if (setup.show_snapshot_buttons)
15866 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15867 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15868 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15872 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15873 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15874 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15876 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15877 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15878 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15882 static void MapGameButtonsAtSamePosition(int id)
15886 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15888 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15889 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15890 MapGadget(game_gadget[i]);
15892 UnmapGameButtonsAtSamePosition_All();
15895 void MapUndoRedoButtons(void)
15897 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15898 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15900 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15901 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15904 void UnmapUndoRedoButtons(void)
15906 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15907 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15909 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15910 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15913 void ModifyPauseButtons(void)
15917 GAME_CTRL_ID_PAUSE,
15918 GAME_CTRL_ID_PAUSE2,
15919 GAME_CTRL_ID_PANEL_PAUSE,
15920 GAME_CTRL_ID_TOUCH_PAUSE,
15925 for (i = 0; ids[i] > -1; i++)
15926 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15929 static void MapGameButtonsExt(boolean on_tape)
15933 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15934 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15935 i != GAME_CTRL_ID_UNDO &&
15936 i != GAME_CTRL_ID_REDO)
15937 MapGadget(game_gadget[i]);
15939 UnmapGameButtonsAtSamePosition_All();
15941 RedrawGameButtons();
15944 static void UnmapGameButtonsExt(boolean on_tape)
15948 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15949 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15950 UnmapGadget(game_gadget[i]);
15953 static void RedrawGameButtonsExt(boolean on_tape)
15957 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15958 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15959 RedrawGadget(game_gadget[i]);
15962 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15967 gi->checked = state;
15970 static void RedrawSoundButtonGadget(int id)
15972 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15973 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15974 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15975 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15976 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15977 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15980 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15981 RedrawGadget(game_gadget[id2]);
15984 void MapGameButtons(void)
15986 MapGameButtonsExt(FALSE);
15989 void UnmapGameButtons(void)
15991 UnmapGameButtonsExt(FALSE);
15994 void RedrawGameButtons(void)
15996 RedrawGameButtonsExt(FALSE);
15999 void MapGameButtonsOnTape(void)
16001 MapGameButtonsExt(TRUE);
16004 void UnmapGameButtonsOnTape(void)
16006 UnmapGameButtonsExt(TRUE);
16009 void RedrawGameButtonsOnTape(void)
16011 RedrawGameButtonsExt(TRUE);
16014 static void GameUndoRedoExt(void)
16016 ClearPlayerAction();
16018 tape.pausing = TRUE;
16021 UpdateAndDisplayGameControlValues();
16023 DrawCompleteVideoDisplay();
16024 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16025 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16026 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16031 static void GameUndo(int steps)
16033 if (!CheckEngineSnapshotList())
16036 LoadEngineSnapshot_Undo(steps);
16041 static void GameRedo(int steps)
16043 if (!CheckEngineSnapshotList())
16046 LoadEngineSnapshot_Redo(steps);
16051 static void HandleGameButtonsExt(int id, int button)
16053 static boolean game_undo_executed = FALSE;
16054 int steps = BUTTON_STEPSIZE(button);
16055 boolean handle_game_buttons =
16056 (game_status == GAME_MODE_PLAYING ||
16057 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16059 if (!handle_game_buttons)
16064 case GAME_CTRL_ID_STOP:
16065 case GAME_CTRL_ID_PANEL_STOP:
16066 case GAME_CTRL_ID_TOUCH_STOP:
16067 if (game_status == GAME_MODE_MAIN)
16073 RequestQuitGame(TRUE);
16077 case GAME_CTRL_ID_PAUSE:
16078 case GAME_CTRL_ID_PAUSE2:
16079 case GAME_CTRL_ID_PANEL_PAUSE:
16080 case GAME_CTRL_ID_TOUCH_PAUSE:
16081 if (network.enabled && game_status == GAME_MODE_PLAYING)
16084 SendToServer_ContinuePlaying();
16086 SendToServer_PausePlaying();
16089 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16091 game_undo_executed = FALSE;
16095 case GAME_CTRL_ID_PLAY:
16096 case GAME_CTRL_ID_PANEL_PLAY:
16097 if (game_status == GAME_MODE_MAIN)
16099 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16101 else if (tape.pausing)
16103 if (network.enabled)
16104 SendToServer_ContinuePlaying();
16106 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16110 case GAME_CTRL_ID_UNDO:
16111 // Important: When using "save snapshot when collecting an item" mode,
16112 // load last (current) snapshot for first "undo" after pressing "pause"
16113 // (else the last-but-one snapshot would be loaded, because the snapshot
16114 // pointer already points to the last snapshot when pressing "pause",
16115 // which is fine for "every step/move" mode, but not for "every collect")
16116 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16117 !game_undo_executed)
16120 game_undo_executed = TRUE;
16125 case GAME_CTRL_ID_REDO:
16129 case GAME_CTRL_ID_SAVE:
16133 case GAME_CTRL_ID_LOAD:
16137 case SOUND_CTRL_ID_MUSIC:
16138 case SOUND_CTRL_ID_PANEL_MUSIC:
16139 if (setup.sound_music)
16141 setup.sound_music = FALSE;
16145 else if (audio.music_available)
16147 setup.sound = setup.sound_music = TRUE;
16149 SetAudioMode(setup.sound);
16151 if (game_status == GAME_MODE_PLAYING)
16155 RedrawSoundButtonGadget(id);
16159 case SOUND_CTRL_ID_LOOPS:
16160 case SOUND_CTRL_ID_PANEL_LOOPS:
16161 if (setup.sound_loops)
16162 setup.sound_loops = FALSE;
16163 else if (audio.loops_available)
16165 setup.sound = setup.sound_loops = TRUE;
16167 SetAudioMode(setup.sound);
16170 RedrawSoundButtonGadget(id);
16174 case SOUND_CTRL_ID_SIMPLE:
16175 case SOUND_CTRL_ID_PANEL_SIMPLE:
16176 if (setup.sound_simple)
16177 setup.sound_simple = FALSE;
16178 else if (audio.sound_available)
16180 setup.sound = setup.sound_simple = TRUE;
16182 SetAudioMode(setup.sound);
16185 RedrawSoundButtonGadget(id);
16194 static void HandleGameButtons(struct GadgetInfo *gi)
16196 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16199 void HandleSoundButtonKeys(Key key)
16201 if (key == setup.shortcut.sound_simple)
16202 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16203 else if (key == setup.shortcut.sound_loops)
16204 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16205 else if (key == setup.shortcut.sound_music)
16206 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);