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:
1883 case EL_AMOEBA_FULL:
1888 case EL_AMOEBA_DROP:
1889 if (y == lev_fieldy - 1)
1891 Feld[x][y] = EL_AMOEBA_GROWING;
1892 Store[x][y] = EL_AMOEBA_WET;
1896 case EL_DYNAMITE_ACTIVE:
1897 case EL_SP_DISK_RED_ACTIVE:
1898 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1899 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1900 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1901 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1902 MovDelay[x][y] = 96;
1905 case EL_EM_DYNAMITE_ACTIVE:
1906 MovDelay[x][y] = 32;
1910 game.lights_still_needed++;
1914 game.friends_still_needed++;
1919 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1922 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1923 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1924 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1925 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1926 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1927 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1928 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1929 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1930 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1931 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1932 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1933 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1936 int belt_nr = getBeltNrFromBeltSwitchElement(Feld[x][y]);
1937 int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]);
1938 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[x][y]);
1940 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1942 game.belt_dir[belt_nr] = belt_dir;
1943 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1945 else // more than one switch -- set it like the first switch
1947 Feld[x][y] = Feld[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1952 case EL_LIGHT_SWITCH_ACTIVE:
1954 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1957 case EL_INVISIBLE_STEELWALL:
1958 case EL_INVISIBLE_WALL:
1959 case EL_INVISIBLE_SAND:
1960 if (game.light_time_left > 0 ||
1961 game.lenses_time_left > 0)
1962 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
1965 case EL_EMC_MAGIC_BALL:
1966 if (game.ball_active)
1967 Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1970 case EL_EMC_MAGIC_BALL_SWITCH:
1971 if (game.ball_active)
1972 Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1975 case EL_TRIGGER_PLAYER:
1976 case EL_TRIGGER_ELEMENT:
1977 case EL_TRIGGER_CE_VALUE:
1978 case EL_TRIGGER_CE_SCORE:
1980 case EL_ANY_ELEMENT:
1981 case EL_CURRENT_CE_VALUE:
1982 case EL_CURRENT_CE_SCORE:
1999 // reference elements should not be used on the playfield
2000 Feld[x][y] = EL_EMPTY;
2004 if (IS_CUSTOM_ELEMENT(element))
2006 if (CAN_MOVE(element))
2009 if (!element_info[element].use_last_ce_value || init_game)
2010 CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
2012 else if (IS_GROUP_ELEMENT(element))
2014 Feld[x][y] = GetElementFromGroupElement(element);
2016 InitField(x, y, init_game);
2023 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2026 static void InitField_WithBug1(int x, int y, boolean init_game)
2028 InitField(x, y, init_game);
2030 // not needed to call InitMovDir() -- already done by InitField()!
2031 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2032 CAN_MOVE(Feld[x][y]))
2036 static void InitField_WithBug2(int x, int y, boolean init_game)
2038 int old_element = Feld[x][y];
2040 InitField(x, y, init_game);
2042 // not needed to call InitMovDir() -- already done by InitField()!
2043 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2044 CAN_MOVE(old_element) &&
2045 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2048 /* this case is in fact a combination of not less than three bugs:
2049 first, it calls InitMovDir() for elements that can move, although this is
2050 already done by InitField(); then, it checks the element that was at this
2051 field _before_ the call to InitField() (which can change it); lastly, it
2052 was not called for "mole with direction" elements, which were treated as
2053 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2057 static int get_key_element_from_nr(int key_nr)
2059 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2060 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2061 EL_EM_KEY_1 : EL_KEY_1);
2063 return key_base_element + key_nr;
2066 static int get_next_dropped_element(struct PlayerInfo *player)
2068 return (player->inventory_size > 0 ?
2069 player->inventory_element[player->inventory_size - 1] :
2070 player->inventory_infinite_element != EL_UNDEFINED ?
2071 player->inventory_infinite_element :
2072 player->dynabombs_left > 0 ?
2073 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2077 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2079 // pos >= 0: get element from bottom of the stack;
2080 // pos < 0: get element from top of the stack
2084 int min_inventory_size = -pos;
2085 int inventory_pos = player->inventory_size - min_inventory_size;
2086 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2088 return (player->inventory_size >= min_inventory_size ?
2089 player->inventory_element[inventory_pos] :
2090 player->inventory_infinite_element != EL_UNDEFINED ?
2091 player->inventory_infinite_element :
2092 player->dynabombs_left >= min_dynabombs_left ?
2093 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2098 int min_dynabombs_left = pos + 1;
2099 int min_inventory_size = pos + 1 - player->dynabombs_left;
2100 int inventory_pos = pos - player->dynabombs_left;
2102 return (player->inventory_infinite_element != EL_UNDEFINED ?
2103 player->inventory_infinite_element :
2104 player->dynabombs_left >= min_dynabombs_left ?
2105 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2106 player->inventory_size >= min_inventory_size ?
2107 player->inventory_element[inventory_pos] :
2112 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2114 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2115 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2118 if (gpo1->sort_priority != gpo2->sort_priority)
2119 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2121 compare_result = gpo1->nr - gpo2->nr;
2123 return compare_result;
2126 int getPlayerInventorySize(int player_nr)
2128 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2129 return game_em.ply[player_nr]->dynamite;
2130 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2131 return game_sp.red_disk_count;
2133 return stored_player[player_nr].inventory_size;
2136 static void InitGameControlValues(void)
2140 for (i = 0; game_panel_controls[i].nr != -1; i++)
2142 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2143 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2144 struct TextPosInfo *pos = gpc->pos;
2146 int type = gpc->type;
2150 Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
2151 Error(ERR_EXIT, "this should not happen -- please debug");
2154 // force update of game controls after initialization
2155 gpc->value = gpc->last_value = -1;
2156 gpc->frame = gpc->last_frame = -1;
2157 gpc->gfx_frame = -1;
2159 // determine panel value width for later calculation of alignment
2160 if (type == TYPE_INTEGER || type == TYPE_STRING)
2162 pos->width = pos->size * getFontWidth(pos->font);
2163 pos->height = getFontHeight(pos->font);
2165 else if (type == TYPE_ELEMENT)
2167 pos->width = pos->size;
2168 pos->height = pos->size;
2171 // fill structure for game panel draw order
2173 gpo->sort_priority = pos->sort_priority;
2176 // sort game panel controls according to sort_priority and control number
2177 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2178 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2181 static void UpdatePlayfieldElementCount(void)
2183 boolean use_element_count = FALSE;
2186 // first check if it is needed at all to calculate playfield element count
2187 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2188 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2189 use_element_count = TRUE;
2191 if (!use_element_count)
2194 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2195 element_info[i].element_count = 0;
2197 SCAN_PLAYFIELD(x, y)
2199 element_info[Feld[x][y]].element_count++;
2202 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2203 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2204 if (IS_IN_GROUP(j, i))
2205 element_info[EL_GROUP_START + i].element_count +=
2206 element_info[j].element_count;
2209 static void UpdateGameControlValues(void)
2212 int time = (game.LevelSolved ?
2213 game.LevelSolved_CountingTime :
2214 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2216 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2217 game_sp.time_played :
2218 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2219 game_mm.energy_left :
2220 game.no_time_limit ? TimePlayed : TimeLeft);
2221 int score = (game.LevelSolved ?
2222 game.LevelSolved_CountingScore :
2223 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2224 game_em.lev->score :
2225 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2227 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2230 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2231 game_em.lev->gems_needed :
2232 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2233 game_sp.infotrons_still_needed :
2234 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2235 game_mm.kettles_still_needed :
2236 game.gems_still_needed);
2237 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2238 game_em.lev->gems_needed > 0 :
2239 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2240 game_sp.infotrons_still_needed > 0 :
2241 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2242 game_mm.kettles_still_needed > 0 ||
2243 game_mm.lights_still_needed > 0 :
2244 game.gems_still_needed > 0 ||
2245 game.sokoban_fields_still_needed > 0 ||
2246 game.sokoban_objects_still_needed > 0 ||
2247 game.lights_still_needed > 0);
2248 int health = (game.LevelSolved ?
2249 game.LevelSolved_CountingHealth :
2250 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2251 MM_HEALTH(game_mm.laser_overload_value) :
2254 UpdatePlayfieldElementCount();
2256 // update game panel control values
2258 // used instead of "level_nr" (for network games)
2259 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2260 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2262 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2263 for (i = 0; i < MAX_NUM_KEYS; i++)
2264 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2265 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2266 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2268 if (game.centered_player_nr == -1)
2270 for (i = 0; i < MAX_PLAYERS; i++)
2272 // only one player in Supaplex game engine
2273 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2276 for (k = 0; k < MAX_NUM_KEYS; k++)
2278 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2280 if (game_em.ply[i]->keys & (1 << k))
2281 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2282 get_key_element_from_nr(k);
2284 else if (stored_player[i].key[k])
2285 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2286 get_key_element_from_nr(k);
2289 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2290 getPlayerInventorySize(i);
2292 if (stored_player[i].num_white_keys > 0)
2293 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2296 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2297 stored_player[i].num_white_keys;
2302 int player_nr = game.centered_player_nr;
2304 for (k = 0; k < MAX_NUM_KEYS; k++)
2306 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2308 if (game_em.ply[player_nr]->keys & (1 << k))
2309 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2310 get_key_element_from_nr(k);
2312 else if (stored_player[player_nr].key[k])
2313 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2314 get_key_element_from_nr(k);
2317 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2318 getPlayerInventorySize(player_nr);
2320 if (stored_player[player_nr].num_white_keys > 0)
2321 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2323 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2324 stored_player[player_nr].num_white_keys;
2327 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2329 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2330 get_inventory_element_from_pos(local_player, i);
2331 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2332 get_inventory_element_from_pos(local_player, -i - 1);
2335 game_panel_controls[GAME_PANEL_SCORE].value = score;
2336 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2338 game_panel_controls[GAME_PANEL_TIME].value = time;
2340 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2341 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2342 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2344 if (level.time == 0)
2345 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2347 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2349 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2350 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2352 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2354 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2355 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2357 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2358 local_player->shield_normal_time_left;
2359 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2360 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2362 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2363 local_player->shield_deadly_time_left;
2365 game_panel_controls[GAME_PANEL_EXIT].value =
2366 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2368 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2369 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2370 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2371 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2372 EL_EMC_MAGIC_BALL_SWITCH);
2374 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2375 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2376 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2377 game.light_time_left;
2379 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2380 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2381 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2382 game.timegate_time_left;
2384 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2385 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2387 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2388 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2389 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2390 game.lenses_time_left;
2392 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2393 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2394 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2395 game.magnify_time_left;
2397 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2398 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2399 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2400 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2401 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2402 EL_BALLOON_SWITCH_NONE);
2404 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2405 local_player->dynabomb_count;
2406 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2407 local_player->dynabomb_size;
2408 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2409 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2411 game_panel_controls[GAME_PANEL_PENGUINS].value =
2412 game.friends_still_needed;
2414 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2415 game.sokoban_objects_still_needed;
2416 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2417 game.sokoban_fields_still_needed;
2419 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2420 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2422 for (i = 0; i < NUM_BELTS; i++)
2424 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2425 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2426 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2427 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2428 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2431 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2432 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2433 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2434 game.magic_wall_time_left;
2436 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2437 local_player->gravity;
2439 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2440 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2442 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2443 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2444 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2445 game.panel.element[i].id : EL_UNDEFINED);
2447 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2448 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2449 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2450 element_info[game.panel.element_count[i].id].element_count : 0);
2452 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2453 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2454 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2455 element_info[game.panel.ce_score[i].id].collect_score : 0);
2457 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2458 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2459 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2460 element_info[game.panel.ce_score_element[i].id].collect_score :
2463 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2464 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2465 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2467 // update game panel control frames
2469 for (i = 0; game_panel_controls[i].nr != -1; i++)
2471 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2473 if (gpc->type == TYPE_ELEMENT)
2475 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2477 int last_anim_random_frame = gfx.anim_random_frame;
2478 int element = gpc->value;
2479 int graphic = el2panelimg(element);
2481 if (gpc->value != gpc->last_value)
2484 gpc->gfx_random = INIT_GFX_RANDOM();
2490 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2491 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2492 gpc->gfx_random = INIT_GFX_RANDOM();
2495 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2496 gfx.anim_random_frame = gpc->gfx_random;
2498 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2499 gpc->gfx_frame = element_info[element].collect_score;
2501 gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
2504 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2505 gfx.anim_random_frame = last_anim_random_frame;
2508 else if (gpc->type == TYPE_GRAPHIC)
2510 if (gpc->graphic != IMG_UNDEFINED)
2512 int last_anim_random_frame = gfx.anim_random_frame;
2513 int graphic = gpc->graphic;
2515 if (gpc->value != gpc->last_value)
2518 gpc->gfx_random = INIT_GFX_RANDOM();
2524 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2525 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2526 gpc->gfx_random = INIT_GFX_RANDOM();
2529 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2530 gfx.anim_random_frame = gpc->gfx_random;
2532 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2534 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2535 gfx.anim_random_frame = last_anim_random_frame;
2541 static void DisplayGameControlValues(void)
2543 boolean redraw_panel = FALSE;
2546 for (i = 0; game_panel_controls[i].nr != -1; i++)
2548 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2550 if (PANEL_DEACTIVATED(gpc->pos))
2553 if (gpc->value == gpc->last_value &&
2554 gpc->frame == gpc->last_frame)
2557 redraw_panel = TRUE;
2563 // copy default game door content to main double buffer
2565 // !!! CHECK AGAIN !!!
2566 SetPanelBackground();
2567 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2568 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2570 // redraw game control buttons
2571 RedrawGameButtons();
2573 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2575 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2577 int nr = game_panel_order[i].nr;
2578 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2579 struct TextPosInfo *pos = gpc->pos;
2580 int type = gpc->type;
2581 int value = gpc->value;
2582 int frame = gpc->frame;
2583 int size = pos->size;
2584 int font = pos->font;
2585 boolean draw_masked = pos->draw_masked;
2586 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2588 if (PANEL_DEACTIVATED(pos))
2591 gpc->last_value = value;
2592 gpc->last_frame = frame;
2594 if (type == TYPE_INTEGER)
2596 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2597 nr == GAME_PANEL_TIME)
2599 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2601 if (use_dynamic_size) // use dynamic number of digits
2603 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2604 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2605 int size2 = size1 + 1;
2606 int font1 = pos->font;
2607 int font2 = pos->font_alt;
2609 size = (value < value_change ? size1 : size2);
2610 font = (value < value_change ? font1 : font2);
2614 // correct text size if "digits" is zero or less
2616 size = strlen(int2str(value, size));
2618 // dynamically correct text alignment
2619 pos->width = size * getFontWidth(font);
2621 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2622 int2str(value, size), font, mask_mode);
2624 else if (type == TYPE_ELEMENT)
2626 int element, graphic;
2630 int dst_x = PANEL_XPOS(pos);
2631 int dst_y = PANEL_YPOS(pos);
2633 if (value != EL_UNDEFINED && value != EL_EMPTY)
2636 graphic = el2panelimg(value);
2638 // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
2640 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2643 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2646 width = graphic_info[graphic].width * size / TILESIZE;
2647 height = graphic_info[graphic].height * size / TILESIZE;
2650 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2653 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2657 else if (type == TYPE_GRAPHIC)
2659 int graphic = gpc->graphic;
2660 int graphic_active = gpc->graphic_active;
2664 int dst_x = PANEL_XPOS(pos);
2665 int dst_y = PANEL_YPOS(pos);
2666 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2667 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2669 if (graphic != IMG_UNDEFINED && !skip)
2671 if (pos->style == STYLE_REVERSE)
2672 value = 100 - value;
2674 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2676 if (pos->direction & MV_HORIZONTAL)
2678 width = graphic_info[graphic_active].width * value / 100;
2679 height = graphic_info[graphic_active].height;
2681 if (pos->direction == MV_LEFT)
2683 src_x += graphic_info[graphic_active].width - width;
2684 dst_x += graphic_info[graphic_active].width - width;
2689 width = graphic_info[graphic_active].width;
2690 height = graphic_info[graphic_active].height * value / 100;
2692 if (pos->direction == MV_UP)
2694 src_y += graphic_info[graphic_active].height - height;
2695 dst_y += graphic_info[graphic_active].height - height;
2700 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2703 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2706 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2708 if (pos->direction & MV_HORIZONTAL)
2710 if (pos->direction == MV_RIGHT)
2717 dst_x = PANEL_XPOS(pos);
2720 width = graphic_info[graphic].width - width;
2724 if (pos->direction == MV_DOWN)
2731 dst_y = PANEL_YPOS(pos);
2734 height = graphic_info[graphic].height - height;
2738 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2741 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2745 else if (type == TYPE_STRING)
2747 boolean active = (value != 0);
2748 char *state_normal = "off";
2749 char *state_active = "on";
2750 char *state = (active ? state_active : state_normal);
2751 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2752 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2753 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2754 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2756 if (nr == GAME_PANEL_GRAVITY_STATE)
2758 int font1 = pos->font; // (used for normal state)
2759 int font2 = pos->font_alt; // (used for active state)
2761 font = (active ? font2 : font1);
2770 // don't truncate output if "chars" is zero or less
2773 // dynamically correct text alignment
2774 pos->width = size * getFontWidth(font);
2777 s_cut = getStringCopyN(s, size);
2779 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2780 s_cut, font, mask_mode);
2786 redraw_mask |= REDRAW_DOOR_1;
2789 SetGameStatus(GAME_MODE_PLAYING);
2792 void UpdateAndDisplayGameControlValues(void)
2794 if (tape.deactivate_display)
2797 UpdateGameControlValues();
2798 DisplayGameControlValues();
2802 static void UpdateGameDoorValues(void)
2804 UpdateGameControlValues();
2808 void DrawGameDoorValues(void)
2810 DisplayGameControlValues();
2814 // ============================================================================
2816 // ----------------------------------------------------------------------------
2817 // initialize game engine due to level / tape version number
2818 // ============================================================================
2820 static void InitGameEngine(void)
2822 int i, j, k, l, x, y;
2824 // set game engine from tape file when re-playing, else from level file
2825 game.engine_version = (tape.playing ? tape.engine_version :
2826 level.game_version);
2828 // set single or multi-player game mode (needed for re-playing tapes)
2829 game.team_mode = setup.team_mode;
2833 int num_players = 0;
2835 for (i = 0; i < MAX_PLAYERS; i++)
2836 if (tape.player_participates[i])
2839 // multi-player tapes contain input data for more than one player
2840 game.team_mode = (num_players > 1);
2844 printf("level %d: level.game_version == %06d\n", level_nr,
2845 level.game_version);
2846 printf(" tape.file_version == %06d\n",
2848 printf(" tape.game_version == %06d\n",
2850 printf(" tape.engine_version == %06d\n",
2851 tape.engine_version);
2852 printf(" => game.engine_version == %06d [tape mode: %s]\n",
2853 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2856 // --------------------------------------------------------------------------
2857 // set flags for bugs and changes according to active game engine version
2858 // --------------------------------------------------------------------------
2862 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2864 Bug was introduced in version:
2867 Bug was fixed in version:
2871 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2872 but the property "can fall" was missing, which caused some levels to be
2873 unsolvable. This was fixed in version 4.1.4.2.
2875 Affected levels/tapes:
2876 An example for a tape that was fixed by this bugfix is tape 029 from the
2877 level set "rnd_sam_bateman".
2878 The wrong behaviour will still be used for all levels or tapes that were
2879 created/recorded with it. An example for this is tape 023 from the level
2880 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2883 boolean use_amoeba_dropping_cannot_fall_bug =
2884 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2885 game.engine_version <= VERSION_IDENT(4,1,4,1)) ||
2887 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2888 tape.game_version <= VERSION_IDENT(4,1,4,1)));
2891 Summary of bugfix/change:
2892 Fixed move speed of elements entering or leaving magic wall.
2894 Fixed/changed in version:
2898 Before 2.0.1, move speed of elements entering or leaving magic wall was
2899 twice as fast as it is now.
2900 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2902 Affected levels/tapes:
2903 The first condition is generally needed for all levels/tapes before version
2904 2.0.1, which might use the old behaviour before it was changed; known tapes
2905 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2906 The second condition is an exception from the above case and is needed for
2907 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2908 above, but before it was known that this change would break tapes like the
2909 above and was fixed in 4.1.4.2, so that the changed behaviour was active
2910 although the engine version while recording maybe was before 2.0.1. There
2911 are a lot of tapes that are affected by this exception, like tape 006 from
2912 the level set "rnd_conor_mancone".
2915 boolean use_old_move_stepsize_for_magic_wall =
2916 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2918 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2919 tape.game_version < VERSION_IDENT(4,1,4,2)));
2922 Summary of bugfix/change:
2923 Fixed handling for custom elements that change when pushed by the player.
2925 Fixed/changed in version:
2929 Before 3.1.0, custom elements that "change when pushing" changed directly
2930 after the player started pushing them (until then handled in "DigField()").
2931 Since 3.1.0, these custom elements are not changed until the "pushing"
2932 move of the element is finished (now handled in "ContinueMoving()").
2934 Affected levels/tapes:
2935 The first condition is generally needed for all levels/tapes before version
2936 3.1.0, which might use the old behaviour before it was changed; known tapes
2937 that are affected are some tapes from the level set "Walpurgis Gardens" by
2939 The second condition is an exception from the above case and is needed for
2940 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2941 above (including some development versions of 3.1.0), but before it was
2942 known that this change would break tapes like the above and was fixed in
2943 3.1.1, so that the changed behaviour was active although the engine version
2944 while recording maybe was before 3.1.0. There is at least one tape that is
2945 affected by this exception, which is the tape for the one-level set "Bug
2946 Machine" by Juergen Bonhagen.
2949 game.use_change_when_pushing_bug =
2950 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2952 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2953 tape.game_version < VERSION_IDENT(3,1,1,0)));
2956 Summary of bugfix/change:
2957 Fixed handling for blocking the field the player leaves when moving.
2959 Fixed/changed in version:
2963 Before 3.1.1, when "block last field when moving" was enabled, the field
2964 the player is leaving when moving was blocked for the time of the move,
2965 and was directly unblocked afterwards. This resulted in the last field
2966 being blocked for exactly one less than the number of frames of one player
2967 move. Additionally, even when blocking was disabled, the last field was
2968 blocked for exactly one frame.
2969 Since 3.1.1, due to changes in player movement handling, the last field
2970 is not blocked at all when blocking is disabled. When blocking is enabled,
2971 the last field is blocked for exactly the number of frames of one player
2972 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2973 last field is blocked for exactly one more than the number of frames of
2976 Affected levels/tapes:
2977 (!!! yet to be determined -- probably many !!!)
2980 game.use_block_last_field_bug =
2981 (game.engine_version < VERSION_IDENT(3,1,1,0));
2983 /* various special flags and settings for native Emerald Mine game engine */
2985 game_em.use_single_button =
2986 (game.engine_version > VERSION_IDENT(4,0,0,2));
2988 game_em.use_snap_key_bug =
2989 (game.engine_version < VERSION_IDENT(4,0,1,0));
2991 game_em.use_old_explosions =
2992 (game.engine_version < VERSION_IDENT(4,1,4,2));
2994 game_em.use_wrap_around =
2995 (game.engine_version > VERSION_IDENT(4,1,4,1));
2997 // --------------------------------------------------------------------------
2999 // set maximal allowed number of custom element changes per game frame
3000 game.max_num_changes_per_frame = 1;
3002 // default scan direction: scan playfield from top/left to bottom/right
3003 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3005 // dynamically adjust element properties according to game engine version
3006 InitElementPropertiesEngine(game.engine_version);
3008 // ---------- initialize special element properties -------------------------
3010 // "EL_AMOEBA_DROPPING" missed property "can fall" between 2.0.1 and 4.1.4.1
3011 if (use_amoeba_dropping_cannot_fall_bug)
3012 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3014 // ---------- initialize player's initial move delay ------------------------
3016 // dynamically adjust player properties according to level information
3017 for (i = 0; i < MAX_PLAYERS; i++)
3018 game.initial_move_delay_value[i] =
3019 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3021 // dynamically adjust player properties according to game engine version
3022 for (i = 0; i < MAX_PLAYERS; i++)
3023 game.initial_move_delay[i] =
3024 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3025 game.initial_move_delay_value[i] : 0);
3027 // ---------- initialize player's initial push delay ------------------------
3029 // dynamically adjust player properties according to game engine version
3030 game.initial_push_delay_value =
3031 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3033 // ---------- initialize changing elements ----------------------------------
3035 // initialize changing elements information
3036 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3038 struct ElementInfo *ei = &element_info[i];
3040 // this pointer might have been changed in the level editor
3041 ei->change = &ei->change_page[0];
3043 if (!IS_CUSTOM_ELEMENT(i))
3045 ei->change->target_element = EL_EMPTY_SPACE;
3046 ei->change->delay_fixed = 0;
3047 ei->change->delay_random = 0;
3048 ei->change->delay_frames = 1;
3051 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3053 ei->has_change_event[j] = FALSE;
3055 ei->event_page_nr[j] = 0;
3056 ei->event_page[j] = &ei->change_page[0];
3060 // add changing elements from pre-defined list
3061 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3063 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3064 struct ElementInfo *ei = &element_info[ch_delay->element];
3066 ei->change->target_element = ch_delay->target_element;
3067 ei->change->delay_fixed = ch_delay->change_delay;
3069 ei->change->pre_change_function = ch_delay->pre_change_function;
3070 ei->change->change_function = ch_delay->change_function;
3071 ei->change->post_change_function = ch_delay->post_change_function;
3073 ei->change->can_change = TRUE;
3074 ei->change->can_change_or_has_action = TRUE;
3076 ei->has_change_event[CE_DELAY] = TRUE;
3078 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3079 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3082 // ---------- initialize internal run-time variables ------------------------
3084 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3086 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3088 for (j = 0; j < ei->num_change_pages; j++)
3090 ei->change_page[j].can_change_or_has_action =
3091 (ei->change_page[j].can_change |
3092 ei->change_page[j].has_action);
3096 // add change events from custom element configuration
3097 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3099 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3101 for (j = 0; j < ei->num_change_pages; j++)
3103 if (!ei->change_page[j].can_change_or_has_action)
3106 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3108 // only add event page for the first page found with this event
3109 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3111 ei->has_change_event[k] = TRUE;
3113 ei->event_page_nr[k] = j;
3114 ei->event_page[k] = &ei->change_page[j];
3120 // ---------- initialize reference elements in change conditions ------------
3122 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3124 int element = EL_CUSTOM_START + i;
3125 struct ElementInfo *ei = &element_info[element];
3127 for (j = 0; j < ei->num_change_pages; j++)
3129 int trigger_element = ei->change_page[j].initial_trigger_element;
3131 if (trigger_element >= EL_PREV_CE_8 &&
3132 trigger_element <= EL_NEXT_CE_8)
3133 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3135 ei->change_page[j].trigger_element = trigger_element;
3139 // ---------- initialize run-time trigger player and element ----------------
3141 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3143 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3145 for (j = 0; j < ei->num_change_pages; j++)
3147 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3148 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3149 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3150 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3151 ei->change_page[j].actual_trigger_ce_value = 0;
3152 ei->change_page[j].actual_trigger_ce_score = 0;
3156 // ---------- initialize trigger events -------------------------------------
3158 // initialize trigger events information
3159 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3160 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3161 trigger_events[i][j] = FALSE;
3163 // add trigger events from element change event properties
3164 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3166 struct ElementInfo *ei = &element_info[i];
3168 for (j = 0; j < ei->num_change_pages; j++)
3170 if (!ei->change_page[j].can_change_or_has_action)
3173 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3175 int trigger_element = ei->change_page[j].trigger_element;
3177 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3179 if (ei->change_page[j].has_event[k])
3181 if (IS_GROUP_ELEMENT(trigger_element))
3183 struct ElementGroupInfo *group =
3184 element_info[trigger_element].group;
3186 for (l = 0; l < group->num_elements_resolved; l++)
3187 trigger_events[group->element_resolved[l]][k] = TRUE;
3189 else if (trigger_element == EL_ANY_ELEMENT)
3190 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3191 trigger_events[l][k] = TRUE;
3193 trigger_events[trigger_element][k] = TRUE;
3200 // ---------- initialize push delay -----------------------------------------
3202 // initialize push delay values to default
3203 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3205 if (!IS_CUSTOM_ELEMENT(i))
3207 // set default push delay values (corrected since version 3.0.7-1)
3208 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3210 element_info[i].push_delay_fixed = 2;
3211 element_info[i].push_delay_random = 8;
3215 element_info[i].push_delay_fixed = 8;
3216 element_info[i].push_delay_random = 8;
3221 // set push delay value for certain elements from pre-defined list
3222 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3224 int e = push_delay_list[i].element;
3226 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3227 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3230 // set push delay value for Supaplex elements for newer engine versions
3231 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3233 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3235 if (IS_SP_ELEMENT(i))
3237 // set SP push delay to just enough to push under a falling zonk
3238 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3240 element_info[i].push_delay_fixed = delay;
3241 element_info[i].push_delay_random = 0;
3246 // ---------- initialize move stepsize --------------------------------------
3248 // initialize move stepsize values to default
3249 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3250 if (!IS_CUSTOM_ELEMENT(i))
3251 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3253 // set move stepsize value for certain elements from pre-defined list
3254 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3256 int e = move_stepsize_list[i].element;
3258 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3260 // set move stepsize value for certain elements for older engine versions
3261 if (use_old_move_stepsize_for_magic_wall)
3263 if (e == EL_MAGIC_WALL_FILLING ||
3264 e == EL_MAGIC_WALL_EMPTYING ||
3265 e == EL_BD_MAGIC_WALL_FILLING ||
3266 e == EL_BD_MAGIC_WALL_EMPTYING)
3267 element_info[e].move_stepsize *= 2;
3271 // ---------- initialize collect score --------------------------------------
3273 // initialize collect score values for custom elements from initial value
3274 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3275 if (IS_CUSTOM_ELEMENT(i))
3276 element_info[i].collect_score = element_info[i].collect_score_initial;
3278 // ---------- initialize collect count --------------------------------------
3280 // initialize collect count values for non-custom elements
3281 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3282 if (!IS_CUSTOM_ELEMENT(i))
3283 element_info[i].collect_count_initial = 0;
3285 // add collect count values for all elements from pre-defined list
3286 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3287 element_info[collect_count_list[i].element].collect_count_initial =
3288 collect_count_list[i].count;
3290 // ---------- initialize access direction -----------------------------------
3292 // initialize access direction values to default (access from every side)
3293 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3294 if (!IS_CUSTOM_ELEMENT(i))
3295 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3297 // set access direction value for certain elements from pre-defined list
3298 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3299 element_info[access_direction_list[i].element].access_direction =
3300 access_direction_list[i].direction;
3302 // ---------- initialize explosion content ----------------------------------
3303 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3305 if (IS_CUSTOM_ELEMENT(i))
3308 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3310 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3312 element_info[i].content.e[x][y] =
3313 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3314 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3315 i == EL_PLAYER_3 ? EL_EMERALD :
3316 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3317 i == EL_MOLE ? EL_EMERALD_RED :
3318 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3319 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3320 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3321 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3322 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3323 i == EL_WALL_EMERALD ? EL_EMERALD :
3324 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3325 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3326 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3327 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3328 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3329 i == EL_WALL_PEARL ? EL_PEARL :
3330 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3335 // ---------- initialize recursion detection --------------------------------
3336 recursion_loop_depth = 0;
3337 recursion_loop_detected = FALSE;
3338 recursion_loop_element = EL_UNDEFINED;
3340 // ---------- initialize graphics engine ------------------------------------
3341 game.scroll_delay_value =
3342 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3343 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3344 !setup.forced_scroll_delay ? 0 :
3345 setup.scroll_delay ? setup.scroll_delay_value : 0);
3346 game.scroll_delay_value =
3347 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3349 // ---------- initialize game engine snapshots ------------------------------
3350 for (i = 0; i < MAX_PLAYERS; i++)
3351 game.snapshot.last_action[i] = 0;
3352 game.snapshot.changed_action = FALSE;
3353 game.snapshot.collected_item = FALSE;
3354 game.snapshot.mode =
3355 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3356 SNAPSHOT_MODE_EVERY_STEP :
3357 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3358 SNAPSHOT_MODE_EVERY_MOVE :
3359 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3360 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3361 game.snapshot.save_snapshot = FALSE;
3363 // ---------- initialize level time for Supaplex engine ---------------------
3364 // Supaplex levels with time limit currently unsupported -- should be added
3365 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3368 // ---------- initialize flags for handling game actions --------------------
3370 // set flags for game actions to default values
3371 game.use_key_actions = TRUE;
3372 game.use_mouse_actions = FALSE;
3374 // when using Mirror Magic game engine, handle mouse events only
3375 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3377 game.use_key_actions = FALSE;
3378 game.use_mouse_actions = TRUE;
3381 // check for custom elements with mouse click events
3382 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3384 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3386 int element = EL_CUSTOM_START + i;
3388 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3389 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3390 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3391 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3392 game.use_mouse_actions = TRUE;
3397 static int get_num_special_action(int element, int action_first,
3400 int num_special_action = 0;
3403 for (i = action_first; i <= action_last; i++)
3405 boolean found = FALSE;
3407 for (j = 0; j < NUM_DIRECTIONS; j++)
3408 if (el_act_dir2img(element, i, j) !=
3409 el_act_dir2img(element, ACTION_DEFAULT, j))
3413 num_special_action++;
3418 return num_special_action;
3422 // ============================================================================
3424 // ----------------------------------------------------------------------------
3425 // initialize and start new game
3426 // ============================================================================
3428 #if DEBUG_INIT_PLAYER
3429 static void DebugPrintPlayerStatus(char *message)
3436 printf("%s:\n", message);
3438 for (i = 0; i < MAX_PLAYERS; i++)
3440 struct PlayerInfo *player = &stored_player[i];
3442 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3446 player->connected_locally,
3447 player->connected_network,
3450 if (local_player == player)
3451 printf(" (local player)");
3460 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3461 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3462 int fade_mask = REDRAW_FIELD;
3464 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3465 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3466 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3467 int initial_move_dir = MV_DOWN;
3470 // required here to update video display before fading (FIX THIS)
3471 DrawMaskedBorder(REDRAW_DOOR_2);
3473 if (!game.restart_level)
3474 CloseDoor(DOOR_CLOSE_1);
3476 SetGameStatus(GAME_MODE_PLAYING);
3478 if (level_editor_test_game)
3479 FadeSkipNextFadeOut();
3481 FadeSetEnterScreen();
3484 fade_mask = REDRAW_ALL;
3486 FadeLevelSoundsAndMusic();
3488 ExpireSoundLoops(TRUE);
3492 if (level_editor_test_game)
3493 FadeSkipNextFadeIn();
3495 // needed if different viewport properties defined for playing
3496 ChangeViewportPropertiesIfNeeded();
3500 DrawCompleteVideoDisplay();
3502 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3505 InitGameControlValues();
3507 // initialize tape actions from game when recording tape
3510 tape.use_key_actions = game.use_key_actions;
3511 tape.use_mouse_actions = game.use_mouse_actions;
3514 // don't play tapes over network
3515 network_playing = (network.enabled && !tape.playing);
3517 for (i = 0; i < MAX_PLAYERS; i++)
3519 struct PlayerInfo *player = &stored_player[i];
3521 player->index_nr = i;
3522 player->index_bit = (1 << i);
3523 player->element_nr = EL_PLAYER_1 + i;
3525 player->present = FALSE;
3526 player->active = FALSE;
3527 player->mapped = FALSE;
3529 player->killed = FALSE;
3530 player->reanimated = FALSE;
3531 player->buried = FALSE;
3534 player->effective_action = 0;
3535 player->programmed_action = 0;
3536 player->snap_action = 0;
3538 player->mouse_action.lx = 0;
3539 player->mouse_action.ly = 0;
3540 player->mouse_action.button = 0;
3541 player->mouse_action.button_hint = 0;
3543 player->effective_mouse_action.lx = 0;
3544 player->effective_mouse_action.ly = 0;
3545 player->effective_mouse_action.button = 0;
3546 player->effective_mouse_action.button_hint = 0;
3548 for (j = 0; j < MAX_NUM_KEYS; j++)
3549 player->key[j] = FALSE;
3551 player->num_white_keys = 0;
3553 player->dynabomb_count = 0;
3554 player->dynabomb_size = 1;
3555 player->dynabombs_left = 0;
3556 player->dynabomb_xl = FALSE;
3558 player->MovDir = initial_move_dir;
3561 player->GfxDir = initial_move_dir;
3562 player->GfxAction = ACTION_DEFAULT;
3564 player->StepFrame = 0;
3566 player->initial_element = player->element_nr;
3567 player->artwork_element =
3568 (level.use_artwork_element[i] ? level.artwork_element[i] :
3569 player->element_nr);
3570 player->use_murphy = FALSE;
3572 player->block_last_field = FALSE; // initialized in InitPlayerField()
3573 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3575 player->gravity = level.initial_player_gravity[i];
3577 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3579 player->actual_frame_counter = 0;
3581 player->step_counter = 0;
3583 player->last_move_dir = initial_move_dir;
3585 player->is_active = FALSE;
3587 player->is_waiting = FALSE;
3588 player->is_moving = FALSE;
3589 player->is_auto_moving = FALSE;
3590 player->is_digging = FALSE;
3591 player->is_snapping = FALSE;
3592 player->is_collecting = FALSE;
3593 player->is_pushing = FALSE;
3594 player->is_switching = FALSE;
3595 player->is_dropping = FALSE;
3596 player->is_dropping_pressed = FALSE;
3598 player->is_bored = FALSE;
3599 player->is_sleeping = FALSE;
3601 player->was_waiting = TRUE;
3602 player->was_moving = FALSE;
3603 player->was_snapping = FALSE;
3604 player->was_dropping = FALSE;
3606 player->force_dropping = FALSE;
3608 player->frame_counter_bored = -1;
3609 player->frame_counter_sleeping = -1;
3611 player->anim_delay_counter = 0;
3612 player->post_delay_counter = 0;
3614 player->dir_waiting = initial_move_dir;
3615 player->action_waiting = ACTION_DEFAULT;
3616 player->last_action_waiting = ACTION_DEFAULT;
3617 player->special_action_bored = ACTION_DEFAULT;
3618 player->special_action_sleeping = ACTION_DEFAULT;
3620 player->switch_x = -1;
3621 player->switch_y = -1;
3623 player->drop_x = -1;
3624 player->drop_y = -1;
3626 player->show_envelope = 0;
3628 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3630 player->push_delay = -1; // initialized when pushing starts
3631 player->push_delay_value = game.initial_push_delay_value;
3633 player->drop_delay = 0;
3634 player->drop_pressed_delay = 0;
3636 player->last_jx = -1;
3637 player->last_jy = -1;
3641 player->shield_normal_time_left = 0;
3642 player->shield_deadly_time_left = 0;
3644 player->inventory_infinite_element = EL_UNDEFINED;
3645 player->inventory_size = 0;
3647 if (level.use_initial_inventory[i])
3649 for (j = 0; j < level.initial_inventory_size[i]; j++)
3651 int element = level.initial_inventory_content[i][j];
3652 int collect_count = element_info[element].collect_count_initial;
3655 if (!IS_CUSTOM_ELEMENT(element))
3658 if (collect_count == 0)
3659 player->inventory_infinite_element = element;
3661 for (k = 0; k < collect_count; k++)
3662 if (player->inventory_size < MAX_INVENTORY_SIZE)
3663 player->inventory_element[player->inventory_size++] = element;
3667 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3668 SnapField(player, 0, 0);
3670 map_player_action[i] = i;
3673 network_player_action_received = FALSE;
3675 // initial null action
3676 if (network_playing)
3677 SendToServer_MovePlayer(MV_NONE);
3682 TimeLeft = level.time;
3685 ScreenMovDir = MV_NONE;
3689 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3691 game.robot_wheel_x = -1;
3692 game.robot_wheel_y = -1;
3697 game.all_players_gone = FALSE;
3699 game.LevelSolved = FALSE;
3700 game.GameOver = FALSE;
3702 game.GamePlayed = !tape.playing;
3704 game.LevelSolved_GameWon = FALSE;
3705 game.LevelSolved_GameEnd = FALSE;
3706 game.LevelSolved_SaveTape = FALSE;
3707 game.LevelSolved_SaveScore = FALSE;
3709 game.LevelSolved_CountingTime = 0;
3710 game.LevelSolved_CountingScore = 0;
3711 game.LevelSolved_CountingHealth = 0;
3713 game.panel.active = TRUE;
3715 game.no_time_limit = (level.time == 0);
3717 game.yamyam_content_nr = 0;
3718 game.robot_wheel_active = FALSE;
3719 game.magic_wall_active = FALSE;
3720 game.magic_wall_time_left = 0;
3721 game.light_time_left = 0;
3722 game.timegate_time_left = 0;
3723 game.switchgate_pos = 0;
3724 game.wind_direction = level.wind_direction_initial;
3727 game.score_final = 0;
3729 game.health = MAX_HEALTH;
3730 game.health_final = MAX_HEALTH;
3732 game.gems_still_needed = level.gems_needed;
3733 game.sokoban_fields_still_needed = 0;
3734 game.sokoban_objects_still_needed = 0;
3735 game.lights_still_needed = 0;
3736 game.players_still_needed = 0;
3737 game.friends_still_needed = 0;
3739 game.lenses_time_left = 0;
3740 game.magnify_time_left = 0;
3742 game.ball_active = level.ball_active_initial;
3743 game.ball_content_nr = 0;
3745 game.explosions_delayed = TRUE;
3747 game.envelope_active = FALSE;
3749 for (i = 0; i < NUM_BELTS; i++)
3751 game.belt_dir[i] = MV_NONE;
3752 game.belt_dir_nr[i] = 3; // not moving, next moving left
3755 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3756 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3758 #if DEBUG_INIT_PLAYER
3759 DebugPrintPlayerStatus("Player status at level initialization");
3762 SCAN_PLAYFIELD(x, y)
3764 Feld[x][y] = Last[x][y] = level.field[x][y];
3765 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3766 ChangeDelay[x][y] = 0;
3767 ChangePage[x][y] = -1;
3768 CustomValue[x][y] = 0; // initialized in InitField()
3769 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3771 WasJustMoving[x][y] = 0;
3772 WasJustFalling[x][y] = 0;
3773 CheckCollision[x][y] = 0;
3774 CheckImpact[x][y] = 0;
3776 Pushed[x][y] = FALSE;
3778 ChangeCount[x][y] = 0;
3779 ChangeEvent[x][y] = -1;
3781 ExplodePhase[x][y] = 0;
3782 ExplodeDelay[x][y] = 0;
3783 ExplodeField[x][y] = EX_TYPE_NONE;
3785 RunnerVisit[x][y] = 0;
3786 PlayerVisit[x][y] = 0;
3789 GfxRandom[x][y] = INIT_GFX_RANDOM();
3790 GfxElement[x][y] = EL_UNDEFINED;
3791 GfxAction[x][y] = ACTION_DEFAULT;
3792 GfxDir[x][y] = MV_NONE;
3793 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3796 SCAN_PLAYFIELD(x, y)
3798 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3800 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3802 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3805 InitField(x, y, TRUE);
3807 ResetGfxAnimation(x, y);
3812 for (i = 0; i < MAX_PLAYERS; i++)
3814 struct PlayerInfo *player = &stored_player[i];
3816 // set number of special actions for bored and sleeping animation
3817 player->num_special_action_bored =
3818 get_num_special_action(player->artwork_element,
3819 ACTION_BORING_1, ACTION_BORING_LAST);
3820 player->num_special_action_sleeping =
3821 get_num_special_action(player->artwork_element,
3822 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3825 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3826 emulate_sb ? EMU_SOKOBAN :
3827 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3829 // initialize type of slippery elements
3830 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3832 if (!IS_CUSTOM_ELEMENT(i))
3834 // default: elements slip down either to the left or right randomly
3835 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3837 // SP style elements prefer to slip down on the left side
3838 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3839 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3841 // BD style elements prefer to slip down on the left side
3842 if (game.emulation == EMU_BOULDERDASH)
3843 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3847 // initialize explosion and ignition delay
3848 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3850 if (!IS_CUSTOM_ELEMENT(i))
3853 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3854 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3855 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3856 int last_phase = (num_phase + 1) * delay;
3857 int half_phase = (num_phase / 2) * delay;
3859 element_info[i].explosion_delay = last_phase - 1;
3860 element_info[i].ignition_delay = half_phase;
3862 if (i == EL_BLACK_ORB)
3863 element_info[i].ignition_delay = 1;
3867 // correct non-moving belts to start moving left
3868 for (i = 0; i < NUM_BELTS; i++)
3869 if (game.belt_dir[i] == MV_NONE)
3870 game.belt_dir_nr[i] = 3; // not moving, next moving left
3872 #if USE_NEW_PLAYER_ASSIGNMENTS
3873 // use preferred player also in local single-player mode
3874 if (!network.enabled && !game.team_mode)
3876 int new_index_nr = setup.network_player_nr;
3878 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3880 for (i = 0; i < MAX_PLAYERS; i++)
3881 stored_player[i].connected_locally = FALSE;
3883 stored_player[new_index_nr].connected_locally = TRUE;
3887 for (i = 0; i < MAX_PLAYERS; i++)
3889 stored_player[i].connected = FALSE;
3891 // in network game mode, the local player might not be the first player
3892 if (stored_player[i].connected_locally)
3893 local_player = &stored_player[i];
3896 if (!network.enabled)
3897 local_player->connected = TRUE;
3901 for (i = 0; i < MAX_PLAYERS; i++)
3902 stored_player[i].connected = tape.player_participates[i];
3904 else if (network.enabled)
3906 // add team mode players connected over the network (needed for correct
3907 // assignment of player figures from level to locally playing players)
3909 for (i = 0; i < MAX_PLAYERS; i++)
3910 if (stored_player[i].connected_network)
3911 stored_player[i].connected = TRUE;
3913 else if (game.team_mode)
3915 // try to guess locally connected team mode players (needed for correct
3916 // assignment of player figures from level to locally playing players)
3918 for (i = 0; i < MAX_PLAYERS; i++)
3919 if (setup.input[i].use_joystick ||
3920 setup.input[i].key.left != KSYM_UNDEFINED)
3921 stored_player[i].connected = TRUE;
3924 #if DEBUG_INIT_PLAYER
3925 DebugPrintPlayerStatus("Player status after level initialization");
3928 #if DEBUG_INIT_PLAYER
3930 printf("Reassigning players ...\n");
3933 // check if any connected player was not found in playfield
3934 for (i = 0; i < MAX_PLAYERS; i++)
3936 struct PlayerInfo *player = &stored_player[i];
3938 if (player->connected && !player->present)
3940 struct PlayerInfo *field_player = NULL;
3942 #if DEBUG_INIT_PLAYER
3944 printf("- looking for field player for player %d ...\n", i + 1);
3947 // assign first free player found that is present in the playfield
3949 // first try: look for unmapped playfield player that is not connected
3950 for (j = 0; j < MAX_PLAYERS; j++)
3951 if (field_player == NULL &&
3952 stored_player[j].present &&
3953 !stored_player[j].mapped &&
3954 !stored_player[j].connected)
3955 field_player = &stored_player[j];
3957 // second try: look for *any* unmapped playfield player
3958 for (j = 0; j < MAX_PLAYERS; j++)
3959 if (field_player == NULL &&
3960 stored_player[j].present &&
3961 !stored_player[j].mapped)
3962 field_player = &stored_player[j];
3964 if (field_player != NULL)
3966 int jx = field_player->jx, jy = field_player->jy;
3968 #if DEBUG_INIT_PLAYER
3970 printf("- found player %d\n", field_player->index_nr + 1);
3973 player->present = FALSE;
3974 player->active = FALSE;
3976 field_player->present = TRUE;
3977 field_player->active = TRUE;
3980 player->initial_element = field_player->initial_element;
3981 player->artwork_element = field_player->artwork_element;
3983 player->block_last_field = field_player->block_last_field;
3984 player->block_delay_adjustment = field_player->block_delay_adjustment;
3987 StorePlayer[jx][jy] = field_player->element_nr;
3989 field_player->jx = field_player->last_jx = jx;
3990 field_player->jy = field_player->last_jy = jy;
3992 if (local_player == player)
3993 local_player = field_player;
3995 map_player_action[field_player->index_nr] = i;
3997 field_player->mapped = TRUE;
3999 #if DEBUG_INIT_PLAYER
4001 printf("- map_player_action[%d] == %d\n",
4002 field_player->index_nr + 1, i + 1);
4007 if (player->connected && player->present)
4008 player->mapped = TRUE;
4011 #if DEBUG_INIT_PLAYER
4012 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4017 // check if any connected player was not found in playfield
4018 for (i = 0; i < MAX_PLAYERS; i++)
4020 struct PlayerInfo *player = &stored_player[i];
4022 if (player->connected && !player->present)
4024 for (j = 0; j < MAX_PLAYERS; j++)
4026 struct PlayerInfo *field_player = &stored_player[j];
4027 int jx = field_player->jx, jy = field_player->jy;
4029 // assign first free player found that is present in the playfield
4030 if (field_player->present && !field_player->connected)
4032 player->present = TRUE;
4033 player->active = TRUE;
4035 field_player->present = FALSE;
4036 field_player->active = FALSE;
4038 player->initial_element = field_player->initial_element;
4039 player->artwork_element = field_player->artwork_element;
4041 player->block_last_field = field_player->block_last_field;
4042 player->block_delay_adjustment = field_player->block_delay_adjustment;
4044 StorePlayer[jx][jy] = player->element_nr;
4046 player->jx = player->last_jx = jx;
4047 player->jy = player->last_jy = jy;
4057 printf("::: local_player->present == %d\n", local_player->present);
4060 // set focus to local player for network games, else to all players
4061 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4062 game.centered_player_nr_next = game.centered_player_nr;
4063 game.set_centered_player = FALSE;
4064 game.set_centered_player_wrap = FALSE;
4066 if (network_playing && tape.recording)
4068 // store client dependent player focus when recording network games
4069 tape.centered_player_nr_next = game.centered_player_nr_next;
4070 tape.set_centered_player = TRUE;
4075 // when playing a tape, eliminate all players who do not participate
4077 #if USE_NEW_PLAYER_ASSIGNMENTS
4079 if (!game.team_mode)
4081 for (i = 0; i < MAX_PLAYERS; i++)
4083 if (stored_player[i].active &&
4084 !tape.player_participates[map_player_action[i]])
4086 struct PlayerInfo *player = &stored_player[i];
4087 int jx = player->jx, jy = player->jy;
4089 #if DEBUG_INIT_PLAYER
4091 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
4094 player->active = FALSE;
4095 StorePlayer[jx][jy] = 0;
4096 Feld[jx][jy] = EL_EMPTY;
4103 for (i = 0; i < MAX_PLAYERS; i++)
4105 if (stored_player[i].active &&
4106 !tape.player_participates[i])
4108 struct PlayerInfo *player = &stored_player[i];
4109 int jx = player->jx, jy = player->jy;
4111 player->active = FALSE;
4112 StorePlayer[jx][jy] = 0;
4113 Feld[jx][jy] = EL_EMPTY;
4118 else if (!network.enabled && !game.team_mode) // && !tape.playing
4120 // when in single player mode, eliminate all but the local player
4122 for (i = 0; i < MAX_PLAYERS; i++)
4124 struct PlayerInfo *player = &stored_player[i];
4126 if (player->active && player != local_player)
4128 int jx = player->jx, jy = player->jy;
4130 player->active = FALSE;
4131 player->present = FALSE;
4133 StorePlayer[jx][jy] = 0;
4134 Feld[jx][jy] = EL_EMPTY;
4139 for (i = 0; i < MAX_PLAYERS; i++)
4140 if (stored_player[i].active)
4141 game.players_still_needed++;
4143 if (level.solved_by_one_player)
4144 game.players_still_needed = 1;
4146 // when recording the game, store which players take part in the game
4149 #if USE_NEW_PLAYER_ASSIGNMENTS
4150 for (i = 0; i < MAX_PLAYERS; i++)
4151 if (stored_player[i].connected)
4152 tape.player_participates[i] = TRUE;
4154 for (i = 0; i < MAX_PLAYERS; i++)
4155 if (stored_player[i].active)
4156 tape.player_participates[i] = TRUE;
4160 #if DEBUG_INIT_PLAYER
4161 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4164 if (BorderElement == EL_EMPTY)
4167 SBX_Right = lev_fieldx - SCR_FIELDX;
4169 SBY_Lower = lev_fieldy - SCR_FIELDY;
4174 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4176 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4179 if (full_lev_fieldx <= SCR_FIELDX)
4180 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4181 if (full_lev_fieldy <= SCR_FIELDY)
4182 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4184 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4186 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4189 // if local player not found, look for custom element that might create
4190 // the player (make some assumptions about the right custom element)
4191 if (!local_player->present)
4193 int start_x = 0, start_y = 0;
4194 int found_rating = 0;
4195 int found_element = EL_UNDEFINED;
4196 int player_nr = local_player->index_nr;
4198 SCAN_PLAYFIELD(x, y)
4200 int element = Feld[x][y];
4205 if (level.use_start_element[player_nr] &&
4206 level.start_element[player_nr] == element &&
4213 found_element = element;
4216 if (!IS_CUSTOM_ELEMENT(element))
4219 if (CAN_CHANGE(element))
4221 for (i = 0; i < element_info[element].num_change_pages; i++)
4223 // check for player created from custom element as single target
4224 content = element_info[element].change_page[i].target_element;
4225 is_player = ELEM_IS_PLAYER(content);
4227 if (is_player && (found_rating < 3 ||
4228 (found_rating == 3 && element < found_element)))
4234 found_element = element;
4239 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4241 // check for player created from custom element as explosion content
4242 content = element_info[element].content.e[xx][yy];
4243 is_player = ELEM_IS_PLAYER(content);
4245 if (is_player && (found_rating < 2 ||
4246 (found_rating == 2 && element < found_element)))
4248 start_x = x + xx - 1;
4249 start_y = y + yy - 1;
4252 found_element = element;
4255 if (!CAN_CHANGE(element))
4258 for (i = 0; i < element_info[element].num_change_pages; i++)
4260 // check for player created from custom element as extended target
4262 element_info[element].change_page[i].target_content.e[xx][yy];
4264 is_player = ELEM_IS_PLAYER(content);
4266 if (is_player && (found_rating < 1 ||
4267 (found_rating == 1 && element < found_element)))
4269 start_x = x + xx - 1;
4270 start_y = y + yy - 1;
4273 found_element = element;
4279 scroll_x = SCROLL_POSITION_X(start_x);
4280 scroll_y = SCROLL_POSITION_Y(start_y);
4284 scroll_x = SCROLL_POSITION_X(local_player->jx);
4285 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4288 // !!! FIX THIS (START) !!!
4289 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4291 InitGameEngine_EM();
4293 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4295 InitGameEngine_SP();
4297 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4299 InitGameEngine_MM();
4303 DrawLevel(REDRAW_FIELD);
4306 // after drawing the level, correct some elements
4307 if (game.timegate_time_left == 0)
4308 CloseAllOpenTimegates();
4311 // blit playfield from scroll buffer to normal back buffer for fading in
4312 BlitScreenToBitmap(backbuffer);
4313 // !!! FIX THIS (END) !!!
4315 DrawMaskedBorder(fade_mask);
4320 // full screen redraw is required at this point in the following cases:
4321 // - special editor door undrawn when game was started from level editor
4322 // - drawing area (playfield) was changed and has to be removed completely
4323 redraw_mask = REDRAW_ALL;
4327 if (!game.restart_level)
4329 // copy default game door content to main double buffer
4331 // !!! CHECK AGAIN !!!
4332 SetPanelBackground();
4333 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4334 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4337 SetPanelBackground();
4338 SetDrawBackgroundMask(REDRAW_DOOR_1);
4340 UpdateAndDisplayGameControlValues();
4342 if (!game.restart_level)
4348 CreateGameButtons();
4353 // copy actual game door content to door double buffer for OpenDoor()
4354 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4356 OpenDoor(DOOR_OPEN_ALL);
4358 KeyboardAutoRepeatOffUnlessAutoplay();
4360 #if DEBUG_INIT_PLAYER
4361 DebugPrintPlayerStatus("Player status (final)");
4370 if (!game.restart_level && !tape.playing)
4372 LevelStats_incPlayed(level_nr);
4374 SaveLevelSetup_SeriesInfo();
4377 game.restart_level = FALSE;
4378 game.restart_game_message = NULL;
4379 game.request_active = FALSE;
4381 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4382 InitGameActions_MM();
4384 SaveEngineSnapshotToListInitial();
4386 if (!game.restart_level)
4388 PlaySound(SND_GAME_STARTING);
4390 if (setup.sound_music)
4395 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4396 int actual_player_x, int actual_player_y)
4398 // this is used for non-R'n'D game engines to update certain engine values
4400 // needed to determine if sounds are played within the visible screen area
4401 scroll_x = actual_scroll_x;
4402 scroll_y = actual_scroll_y;
4404 // needed to get player position for "follow finger" playing input method
4405 local_player->jx = actual_player_x;
4406 local_player->jy = actual_player_y;
4409 void InitMovDir(int x, int y)
4411 int i, element = Feld[x][y];
4412 static int xy[4][2] =
4419 static int direction[3][4] =
4421 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4422 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4423 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4432 Feld[x][y] = EL_BUG;
4433 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4436 case EL_SPACESHIP_RIGHT:
4437 case EL_SPACESHIP_UP:
4438 case EL_SPACESHIP_LEFT:
4439 case EL_SPACESHIP_DOWN:
4440 Feld[x][y] = EL_SPACESHIP;
4441 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4444 case EL_BD_BUTTERFLY_RIGHT:
4445 case EL_BD_BUTTERFLY_UP:
4446 case EL_BD_BUTTERFLY_LEFT:
4447 case EL_BD_BUTTERFLY_DOWN:
4448 Feld[x][y] = EL_BD_BUTTERFLY;
4449 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4452 case EL_BD_FIREFLY_RIGHT:
4453 case EL_BD_FIREFLY_UP:
4454 case EL_BD_FIREFLY_LEFT:
4455 case EL_BD_FIREFLY_DOWN:
4456 Feld[x][y] = EL_BD_FIREFLY;
4457 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4460 case EL_PACMAN_RIGHT:
4462 case EL_PACMAN_LEFT:
4463 case EL_PACMAN_DOWN:
4464 Feld[x][y] = EL_PACMAN;
4465 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4468 case EL_YAMYAM_LEFT:
4469 case EL_YAMYAM_RIGHT:
4471 case EL_YAMYAM_DOWN:
4472 Feld[x][y] = EL_YAMYAM;
4473 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4476 case EL_SP_SNIKSNAK:
4477 MovDir[x][y] = MV_UP;
4480 case EL_SP_ELECTRON:
4481 MovDir[x][y] = MV_LEFT;
4488 Feld[x][y] = EL_MOLE;
4489 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4493 if (IS_CUSTOM_ELEMENT(element))
4495 struct ElementInfo *ei = &element_info[element];
4496 int move_direction_initial = ei->move_direction_initial;
4497 int move_pattern = ei->move_pattern;
4499 if (move_direction_initial == MV_START_PREVIOUS)
4501 if (MovDir[x][y] != MV_NONE)
4504 move_direction_initial = MV_START_AUTOMATIC;
4507 if (move_direction_initial == MV_START_RANDOM)
4508 MovDir[x][y] = 1 << RND(4);
4509 else if (move_direction_initial & MV_ANY_DIRECTION)
4510 MovDir[x][y] = move_direction_initial;
4511 else if (move_pattern == MV_ALL_DIRECTIONS ||
4512 move_pattern == MV_TURNING_LEFT ||
4513 move_pattern == MV_TURNING_RIGHT ||
4514 move_pattern == MV_TURNING_LEFT_RIGHT ||
4515 move_pattern == MV_TURNING_RIGHT_LEFT ||
4516 move_pattern == MV_TURNING_RANDOM)
4517 MovDir[x][y] = 1 << RND(4);
4518 else if (move_pattern == MV_HORIZONTAL)
4519 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4520 else if (move_pattern == MV_VERTICAL)
4521 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4522 else if (move_pattern & MV_ANY_DIRECTION)
4523 MovDir[x][y] = element_info[element].move_pattern;
4524 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4525 move_pattern == MV_ALONG_RIGHT_SIDE)
4527 // use random direction as default start direction
4528 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4529 MovDir[x][y] = 1 << RND(4);
4531 for (i = 0; i < NUM_DIRECTIONS; i++)
4533 int x1 = x + xy[i][0];
4534 int y1 = y + xy[i][1];
4536 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4538 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4539 MovDir[x][y] = direction[0][i];
4541 MovDir[x][y] = direction[1][i];
4550 MovDir[x][y] = 1 << RND(4);
4552 if (element != EL_BUG &&
4553 element != EL_SPACESHIP &&
4554 element != EL_BD_BUTTERFLY &&
4555 element != EL_BD_FIREFLY)
4558 for (i = 0; i < NUM_DIRECTIONS; i++)
4560 int x1 = x + xy[i][0];
4561 int y1 = y + xy[i][1];
4563 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4565 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4567 MovDir[x][y] = direction[0][i];
4570 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4571 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4573 MovDir[x][y] = direction[1][i];
4582 GfxDir[x][y] = MovDir[x][y];
4585 void InitAmoebaNr(int x, int y)
4588 int group_nr = AmoebeNachbarNr(x, y);
4592 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4594 if (AmoebaCnt[i] == 0)
4602 AmoebaNr[x][y] = group_nr;
4603 AmoebaCnt[group_nr]++;
4604 AmoebaCnt2[group_nr]++;
4607 static void LevelSolved(void)
4609 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4610 game.players_still_needed > 0)
4613 game.LevelSolved = TRUE;
4614 game.GameOver = TRUE;
4616 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4617 game_em.lev->score :
4618 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4621 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4622 MM_HEALTH(game_mm.laser_overload_value) :
4625 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4626 game.LevelSolved_CountingScore = game.score_final;
4627 game.LevelSolved_CountingHealth = game.health_final;
4632 static int time_count_steps;
4633 static int time, time_final;
4634 static int score, score_final;
4635 static int health, health_final;
4636 static int game_over_delay_1 = 0;
4637 static int game_over_delay_2 = 0;
4638 static int game_over_delay_3 = 0;
4639 int game_over_delay_value_1 = 50;
4640 int game_over_delay_value_2 = 25;
4641 int game_over_delay_value_3 = 50;
4643 if (!game.LevelSolved_GameWon)
4647 // do not start end game actions before the player stops moving (to exit)
4648 if (local_player->active && local_player->MovPos)
4651 game.LevelSolved_GameWon = TRUE;
4652 game.LevelSolved_SaveTape = tape.recording;
4653 game.LevelSolved_SaveScore = !tape.playing;
4657 LevelStats_incSolved(level_nr);
4659 SaveLevelSetup_SeriesInfo();
4662 if (tape.auto_play) // tape might already be stopped here
4663 tape.auto_play_level_solved = TRUE;
4667 game_over_delay_1 = 0;
4668 game_over_delay_2 = 0;
4669 game_over_delay_3 = game_over_delay_value_3;
4671 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4672 score = score_final = game.score_final;
4673 health = health_final = game.health_final;
4675 if (level.score[SC_TIME_BONUS] > 0)
4680 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4682 else if (game.no_time_limit && TimePlayed < 999)
4685 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4688 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4690 game_over_delay_1 = game_over_delay_value_1;
4692 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4695 score_final += health * level.score[SC_TIME_BONUS];
4697 game_over_delay_2 = game_over_delay_value_2;
4700 game.score_final = score_final;
4701 game.health_final = health_final;
4704 if (level_editor_test_game)
4707 score = score_final;
4709 game.LevelSolved_CountingTime = time;
4710 game.LevelSolved_CountingScore = score;
4712 game_panel_controls[GAME_PANEL_TIME].value = time;
4713 game_panel_controls[GAME_PANEL_SCORE].value = score;
4715 DisplayGameControlValues();
4718 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4720 // check if last player has left the level
4721 if (game.exit_x >= 0 &&
4724 int x = game.exit_x;
4725 int y = game.exit_y;
4726 int element = Feld[x][y];
4728 // close exit door after last player
4729 if ((game.all_players_gone &&
4730 (element == EL_EXIT_OPEN ||
4731 element == EL_SP_EXIT_OPEN ||
4732 element == EL_STEEL_EXIT_OPEN)) ||
4733 element == EL_EM_EXIT_OPEN ||
4734 element == EL_EM_STEEL_EXIT_OPEN)
4738 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4739 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4740 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4741 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4742 EL_EM_STEEL_EXIT_CLOSING);
4744 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4747 // player disappears
4748 DrawLevelField(x, y);
4751 for (i = 0; i < MAX_PLAYERS; i++)
4753 struct PlayerInfo *player = &stored_player[i];
4755 if (player->present)
4757 RemovePlayer(player);
4759 // player disappears
4760 DrawLevelField(player->jx, player->jy);
4765 PlaySound(SND_GAME_WINNING);
4768 if (game_over_delay_1 > 0)
4770 game_over_delay_1--;
4775 if (time != time_final)
4777 int time_to_go = ABS(time_final - time);
4778 int time_count_dir = (time < time_final ? +1 : -1);
4780 if (time_to_go < time_count_steps)
4781 time_count_steps = 1;
4783 time += time_count_steps * time_count_dir;
4784 score += time_count_steps * level.score[SC_TIME_BONUS];
4786 game.LevelSolved_CountingTime = time;
4787 game.LevelSolved_CountingScore = score;
4789 game_panel_controls[GAME_PANEL_TIME].value = time;
4790 game_panel_controls[GAME_PANEL_SCORE].value = score;
4792 DisplayGameControlValues();
4794 if (time == time_final)
4795 StopSound(SND_GAME_LEVELTIME_BONUS);
4796 else if (setup.sound_loops)
4797 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4799 PlaySound(SND_GAME_LEVELTIME_BONUS);
4804 if (game_over_delay_2 > 0)
4806 game_over_delay_2--;
4811 if (health != health_final)
4813 int health_count_dir = (health < health_final ? +1 : -1);
4815 health += health_count_dir;
4816 score += level.score[SC_TIME_BONUS];
4818 game.LevelSolved_CountingHealth = health;
4819 game.LevelSolved_CountingScore = score;
4821 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4822 game_panel_controls[GAME_PANEL_SCORE].value = score;
4824 DisplayGameControlValues();
4826 if (health == health_final)
4827 StopSound(SND_GAME_LEVELTIME_BONUS);
4828 else if (setup.sound_loops)
4829 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4831 PlaySound(SND_GAME_LEVELTIME_BONUS);
4836 game.panel.active = FALSE;
4838 if (game_over_delay_3 > 0)
4840 game_over_delay_3--;
4850 // used instead of "level_nr" (needed for network games)
4851 int last_level_nr = levelset.level_nr;
4854 game.LevelSolved_GameEnd = TRUE;
4856 if (game.LevelSolved_SaveTape)
4858 // make sure that request dialog to save tape does not open door again
4859 if (!global.use_envelope_request)
4860 CloseDoor(DOOR_CLOSE_1);
4862 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4865 // if no tape is to be saved, close both doors simultaneously
4866 CloseDoor(DOOR_CLOSE_ALL);
4868 if (level_editor_test_game)
4870 SetGameStatus(GAME_MODE_MAIN);
4877 if (!game.LevelSolved_SaveScore)
4879 SetGameStatus(GAME_MODE_MAIN);
4886 if (level_nr == leveldir_current->handicap_level)
4888 leveldir_current->handicap_level++;
4890 SaveLevelSetup_SeriesInfo();
4893 if (setup.increment_levels &&
4894 level_nr < leveldir_current->last_level &&
4897 level_nr++; // advance to next level
4898 TapeErase(); // start with empty tape
4900 if (setup.auto_play_next_level)
4902 LoadLevel(level_nr);
4904 SaveLevelSetup_SeriesInfo();
4908 hi_pos = NewHiScore(last_level_nr);
4910 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4912 SetGameStatus(GAME_MODE_SCORES);
4914 DrawHallOfFame(last_level_nr, hi_pos);
4916 else if (setup.auto_play_next_level && setup.increment_levels &&
4917 last_level_nr < leveldir_current->last_level &&
4920 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4924 SetGameStatus(GAME_MODE_MAIN);
4930 int NewHiScore(int level_nr)
4934 boolean one_score_entry_per_name = !program.many_scores_per_name;
4936 LoadScore(level_nr);
4938 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4939 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4942 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4944 if (game.score_final > highscore[k].Score)
4946 // player has made it to the hall of fame
4948 if (k < MAX_SCORE_ENTRIES - 1)
4950 int m = MAX_SCORE_ENTRIES - 1;
4952 if (one_score_entry_per_name)
4954 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4955 if (strEqual(setup.player_name, highscore[l].Name))
4958 if (m == k) // player's new highscore overwrites his old one
4962 for (l = m; l > k; l--)
4964 strcpy(highscore[l].Name, highscore[l - 1].Name);
4965 highscore[l].Score = highscore[l - 1].Score;
4971 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4972 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4973 highscore[k].Score = game.score_final;
4978 else if (one_score_entry_per_name &&
4979 !strncmp(setup.player_name, highscore[k].Name,
4980 MAX_PLAYER_NAME_LEN))
4981 break; // player already there with a higher score
4985 SaveScore(level_nr);
4990 static int getElementMoveStepsizeExt(int x, int y, int direction)
4992 int element = Feld[x][y];
4993 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4994 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4995 int horiz_move = (dx != 0);
4996 int sign = (horiz_move ? dx : dy);
4997 int step = sign * element_info[element].move_stepsize;
4999 // special values for move stepsize for spring and things on conveyor belt
5002 if (CAN_FALL(element) &&
5003 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
5004 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5005 else if (element == EL_SPRING)
5006 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5012 static int getElementMoveStepsize(int x, int y)
5014 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5017 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5019 if (player->GfxAction != action || player->GfxDir != dir)
5021 player->GfxAction = action;
5022 player->GfxDir = dir;
5024 player->StepFrame = 0;
5028 static void ResetGfxFrame(int x, int y)
5030 // profiling showed that "autotest" spends 10~20% of its time in this function
5031 if (DrawingDeactivatedField())
5034 int element = Feld[x][y];
5035 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5037 if (graphic_info[graphic].anim_global_sync)
5038 GfxFrame[x][y] = FrameCounter;
5039 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5040 GfxFrame[x][y] = CustomValue[x][y];
5041 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5042 GfxFrame[x][y] = element_info[element].collect_score;
5043 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5044 GfxFrame[x][y] = ChangeDelay[x][y];
5047 static void ResetGfxAnimation(int x, int y)
5049 GfxAction[x][y] = ACTION_DEFAULT;
5050 GfxDir[x][y] = MovDir[x][y];
5053 ResetGfxFrame(x, y);
5056 static void ResetRandomAnimationValue(int x, int y)
5058 GfxRandom[x][y] = INIT_GFX_RANDOM();
5061 static void InitMovingField(int x, int y, int direction)
5063 int element = Feld[x][y];
5064 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5065 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5068 boolean is_moving_before, is_moving_after;
5070 // check if element was/is moving or being moved before/after mode change
5071 is_moving_before = (WasJustMoving[x][y] != 0);
5072 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5074 // reset animation only for moving elements which change direction of moving
5075 // or which just started or stopped moving
5076 // (else CEs with property "can move" / "not moving" are reset each frame)
5077 if (is_moving_before != is_moving_after ||
5078 direction != MovDir[x][y])
5079 ResetGfxAnimation(x, y);
5081 MovDir[x][y] = direction;
5082 GfxDir[x][y] = direction;
5084 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5085 direction == MV_DOWN && CAN_FALL(element) ?
5086 ACTION_FALLING : ACTION_MOVING);
5088 // this is needed for CEs with property "can move" / "not moving"
5090 if (is_moving_after)
5092 if (Feld[newx][newy] == EL_EMPTY)
5093 Feld[newx][newy] = EL_BLOCKED;
5095 MovDir[newx][newy] = MovDir[x][y];
5097 CustomValue[newx][newy] = CustomValue[x][y];
5099 GfxFrame[newx][newy] = GfxFrame[x][y];
5100 GfxRandom[newx][newy] = GfxRandom[x][y];
5101 GfxAction[newx][newy] = GfxAction[x][y];
5102 GfxDir[newx][newy] = GfxDir[x][y];
5106 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5108 int direction = MovDir[x][y];
5109 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5110 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5116 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5118 int oldx = x, oldy = y;
5119 int direction = MovDir[x][y];
5121 if (direction == MV_LEFT)
5123 else if (direction == MV_RIGHT)
5125 else if (direction == MV_UP)
5127 else if (direction == MV_DOWN)
5130 *comes_from_x = oldx;
5131 *comes_from_y = oldy;
5134 static int MovingOrBlocked2Element(int x, int y)
5136 int element = Feld[x][y];
5138 if (element == EL_BLOCKED)
5142 Blocked2Moving(x, y, &oldx, &oldy);
5143 return Feld[oldx][oldy];
5149 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5151 // like MovingOrBlocked2Element(), but if element is moving
5152 // and (x,y) is the field the moving element is just leaving,
5153 // return EL_BLOCKED instead of the element value
5154 int element = Feld[x][y];
5156 if (IS_MOVING(x, y))
5158 if (element == EL_BLOCKED)
5162 Blocked2Moving(x, y, &oldx, &oldy);
5163 return Feld[oldx][oldy];
5172 static void RemoveField(int x, int y)
5174 Feld[x][y] = EL_EMPTY;
5180 CustomValue[x][y] = 0;
5183 ChangeDelay[x][y] = 0;
5184 ChangePage[x][y] = -1;
5185 Pushed[x][y] = FALSE;
5187 GfxElement[x][y] = EL_UNDEFINED;
5188 GfxAction[x][y] = ACTION_DEFAULT;
5189 GfxDir[x][y] = MV_NONE;
5192 static void RemoveMovingField(int x, int y)
5194 int oldx = x, oldy = y, newx = x, newy = y;
5195 int element = Feld[x][y];
5196 int next_element = EL_UNDEFINED;
5198 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5201 if (IS_MOVING(x, y))
5203 Moving2Blocked(x, y, &newx, &newy);
5205 if (Feld[newx][newy] != EL_BLOCKED)
5207 // element is moving, but target field is not free (blocked), but
5208 // already occupied by something different (example: acid pool);
5209 // in this case, only remove the moving field, but not the target
5211 RemoveField(oldx, oldy);
5213 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5215 TEST_DrawLevelField(oldx, oldy);
5220 else if (element == EL_BLOCKED)
5222 Blocked2Moving(x, y, &oldx, &oldy);
5223 if (!IS_MOVING(oldx, oldy))
5227 if (element == EL_BLOCKED &&
5228 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5229 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5230 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5231 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5232 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5233 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5234 next_element = get_next_element(Feld[oldx][oldy]);
5236 RemoveField(oldx, oldy);
5237 RemoveField(newx, newy);
5239 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5241 if (next_element != EL_UNDEFINED)
5242 Feld[oldx][oldy] = next_element;
5244 TEST_DrawLevelField(oldx, oldy);
5245 TEST_DrawLevelField(newx, newy);
5248 void DrawDynamite(int x, int y)
5250 int sx = SCREENX(x), sy = SCREENY(y);
5251 int graphic = el2img(Feld[x][y]);
5254 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5257 if (IS_WALKABLE_INSIDE(Back[x][y]))
5261 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5262 else if (Store[x][y])
5263 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5265 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5267 if (Back[x][y] || Store[x][y])
5268 DrawGraphicThruMask(sx, sy, graphic, frame);
5270 DrawGraphic(sx, sy, graphic, frame);
5273 static void CheckDynamite(int x, int y)
5275 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5279 if (MovDelay[x][y] != 0)
5282 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5288 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5293 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5295 boolean num_checked_players = 0;
5298 for (i = 0; i < MAX_PLAYERS; i++)
5300 if (stored_player[i].active)
5302 int sx = stored_player[i].jx;
5303 int sy = stored_player[i].jy;
5305 if (num_checked_players == 0)
5312 *sx1 = MIN(*sx1, sx);
5313 *sy1 = MIN(*sy1, sy);
5314 *sx2 = MAX(*sx2, sx);
5315 *sy2 = MAX(*sy2, sy);
5318 num_checked_players++;
5323 static boolean checkIfAllPlayersFitToScreen_RND(void)
5325 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5327 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5329 return (sx2 - sx1 < SCR_FIELDX &&
5330 sy2 - sy1 < SCR_FIELDY);
5333 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5335 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5337 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5339 *sx = (sx1 + sx2) / 2;
5340 *sy = (sy1 + sy2) / 2;
5343 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5344 boolean center_screen, boolean quick_relocation)
5346 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5347 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5348 boolean no_delay = (tape.warp_forward);
5349 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5350 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5351 int new_scroll_x, new_scroll_y;
5353 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5355 // case 1: quick relocation inside visible screen (without scrolling)
5362 if (!level.shifted_relocation || center_screen)
5364 // relocation _with_ centering of screen
5366 new_scroll_x = SCROLL_POSITION_X(x);
5367 new_scroll_y = SCROLL_POSITION_Y(y);
5371 // relocation _without_ centering of screen
5373 int center_scroll_x = SCROLL_POSITION_X(old_x);
5374 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5375 int offset_x = x + (scroll_x - center_scroll_x);
5376 int offset_y = y + (scroll_y - center_scroll_y);
5378 // for new screen position, apply previous offset to center position
5379 new_scroll_x = SCROLL_POSITION_X(offset_x);
5380 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5383 if (quick_relocation)
5385 // case 2: quick relocation (redraw without visible scrolling)
5387 scroll_x = new_scroll_x;
5388 scroll_y = new_scroll_y;
5395 // case 3: visible relocation (with scrolling to new position)
5397 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5399 SetVideoFrameDelay(wait_delay_value);
5401 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5403 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5404 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5406 if (dx == 0 && dy == 0) // no scrolling needed at all
5412 // set values for horizontal/vertical screen scrolling (half tile size)
5413 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5414 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5415 int pos_x = dx * TILEX / 2;
5416 int pos_y = dy * TILEY / 2;
5417 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5418 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5420 ScrollLevel(dx, dy);
5423 // scroll in two steps of half tile size to make things smoother
5424 BlitScreenToBitmapExt_RND(window, fx, fy);
5426 // scroll second step to align at full tile size
5427 BlitScreenToBitmap(window);
5433 SetVideoFrameDelay(frame_delay_value_old);
5436 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5438 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5439 int player_nr = GET_PLAYER_NR(el_player);
5440 struct PlayerInfo *player = &stored_player[player_nr];
5441 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5442 boolean no_delay = (tape.warp_forward);
5443 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5444 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5445 int old_jx = player->jx;
5446 int old_jy = player->jy;
5447 int old_element = Feld[old_jx][old_jy];
5448 int element = Feld[jx][jy];
5449 boolean player_relocated = (old_jx != jx || old_jy != jy);
5451 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5452 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5453 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5454 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5455 int leave_side_horiz = move_dir_horiz;
5456 int leave_side_vert = move_dir_vert;
5457 int enter_side = enter_side_horiz | enter_side_vert;
5458 int leave_side = leave_side_horiz | leave_side_vert;
5460 if (player->buried) // do not reanimate dead player
5463 if (!player_relocated) // no need to relocate the player
5466 if (IS_PLAYER(jx, jy)) // player already placed at new position
5468 RemoveField(jx, jy); // temporarily remove newly placed player
5469 DrawLevelField(jx, jy);
5472 if (player->present)
5474 while (player->MovPos)
5476 ScrollPlayer(player, SCROLL_GO_ON);
5477 ScrollScreen(NULL, SCROLL_GO_ON);
5479 AdvanceFrameAndPlayerCounters(player->index_nr);
5483 BackToFront_WithFrameDelay(wait_delay_value);
5486 DrawPlayer(player); // needed here only to cleanup last field
5487 DrawLevelField(player->jx, player->jy); // remove player graphic
5489 player->is_moving = FALSE;
5492 if (IS_CUSTOM_ELEMENT(old_element))
5493 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5495 player->index_bit, leave_side);
5497 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5499 player->index_bit, leave_side);
5501 Feld[jx][jy] = el_player;
5502 InitPlayerField(jx, jy, el_player, TRUE);
5504 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5505 possible that the relocation target field did not contain a player element,
5506 but a walkable element, to which the new player was relocated -- in this
5507 case, restore that (already initialized!) element on the player field */
5508 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5510 Feld[jx][jy] = element; // restore previously existing element
5513 // only visually relocate centered player
5514 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5515 FALSE, level.instant_relocation);
5517 TestIfPlayerTouchesBadThing(jx, jy);
5518 TestIfPlayerTouchesCustomElement(jx, jy);
5520 if (IS_CUSTOM_ELEMENT(element))
5521 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5522 player->index_bit, enter_side);
5524 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5525 player->index_bit, enter_side);
5527 if (player->is_switching)
5529 /* ensure that relocation while still switching an element does not cause
5530 a new element to be treated as also switched directly after relocation
5531 (this is important for teleporter switches that teleport the player to
5532 a place where another teleporter switch is in the same direction, which
5533 would then incorrectly be treated as immediately switched before the
5534 direction key that caused the switch was released) */
5536 player->switch_x += jx - old_jx;
5537 player->switch_y += jy - old_jy;
5541 static void Explode(int ex, int ey, int phase, int mode)
5547 // !!! eliminate this variable !!!
5548 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5550 if (game.explosions_delayed)
5552 ExplodeField[ex][ey] = mode;
5556 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5558 int center_element = Feld[ex][ey];
5559 int artwork_element, explosion_element; // set these values later
5561 // remove things displayed in background while burning dynamite
5562 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5565 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5567 // put moving element to center field (and let it explode there)
5568 center_element = MovingOrBlocked2Element(ex, ey);
5569 RemoveMovingField(ex, ey);
5570 Feld[ex][ey] = center_element;
5573 // now "center_element" is finally determined -- set related values now
5574 artwork_element = center_element; // for custom player artwork
5575 explosion_element = center_element; // for custom player artwork
5577 if (IS_PLAYER(ex, ey))
5579 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5581 artwork_element = stored_player[player_nr].artwork_element;
5583 if (level.use_explosion_element[player_nr])
5585 explosion_element = level.explosion_element[player_nr];
5586 artwork_element = explosion_element;
5590 if (mode == EX_TYPE_NORMAL ||
5591 mode == EX_TYPE_CENTER ||
5592 mode == EX_TYPE_CROSS)
5593 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5595 last_phase = element_info[explosion_element].explosion_delay + 1;
5597 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5599 int xx = x - ex + 1;
5600 int yy = y - ey + 1;
5603 if (!IN_LEV_FIELD(x, y) ||
5604 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5605 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5608 element = Feld[x][y];
5610 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5612 element = MovingOrBlocked2Element(x, y);
5614 if (!IS_EXPLOSION_PROOF(element))
5615 RemoveMovingField(x, y);
5618 // indestructible elements can only explode in center (but not flames)
5619 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5620 mode == EX_TYPE_BORDER)) ||
5621 element == EL_FLAMES)
5624 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5625 behaviour, for example when touching a yamyam that explodes to rocks
5626 with active deadly shield, a rock is created under the player !!! */
5627 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5629 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5630 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5631 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5633 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5636 if (IS_ACTIVE_BOMB(element))
5638 // re-activate things under the bomb like gate or penguin
5639 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5646 // save walkable background elements while explosion on same tile
5647 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5648 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5649 Back[x][y] = element;
5651 // ignite explodable elements reached by other explosion
5652 if (element == EL_EXPLOSION)
5653 element = Store2[x][y];
5655 if (AmoebaNr[x][y] &&
5656 (element == EL_AMOEBA_FULL ||
5657 element == EL_BD_AMOEBA ||
5658 element == EL_AMOEBA_GROWING))
5660 AmoebaCnt[AmoebaNr[x][y]]--;
5661 AmoebaCnt2[AmoebaNr[x][y]]--;
5666 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5668 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5670 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5672 if (PLAYERINFO(ex, ey)->use_murphy)
5673 Store[x][y] = EL_EMPTY;
5676 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5677 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5678 else if (ELEM_IS_PLAYER(center_element))
5679 Store[x][y] = EL_EMPTY;
5680 else if (center_element == EL_YAMYAM)
5681 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5682 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5683 Store[x][y] = element_info[center_element].content.e[xx][yy];
5685 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5686 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5687 // otherwise) -- FIX THIS !!!
5688 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5689 Store[x][y] = element_info[element].content.e[1][1];
5691 else if (!CAN_EXPLODE(element))
5692 Store[x][y] = element_info[element].content.e[1][1];
5695 Store[x][y] = EL_EMPTY;
5697 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5698 center_element == EL_AMOEBA_TO_DIAMOND)
5699 Store2[x][y] = element;
5701 Feld[x][y] = EL_EXPLOSION;
5702 GfxElement[x][y] = artwork_element;
5704 ExplodePhase[x][y] = 1;
5705 ExplodeDelay[x][y] = last_phase;
5710 if (center_element == EL_YAMYAM)
5711 game.yamyam_content_nr =
5712 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5724 GfxFrame[x][y] = 0; // restart explosion animation
5726 last_phase = ExplodeDelay[x][y];
5728 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5730 // this can happen if the player leaves an explosion just in time
5731 if (GfxElement[x][y] == EL_UNDEFINED)
5732 GfxElement[x][y] = EL_EMPTY;
5734 border_element = Store2[x][y];
5735 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5736 border_element = StorePlayer[x][y];
5738 if (phase == element_info[border_element].ignition_delay ||
5739 phase == last_phase)
5741 boolean border_explosion = FALSE;
5743 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5744 !PLAYER_EXPLOSION_PROTECTED(x, y))
5746 KillPlayerUnlessExplosionProtected(x, y);
5747 border_explosion = TRUE;
5749 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5751 Feld[x][y] = Store2[x][y];
5754 border_explosion = TRUE;
5756 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5758 AmoebeUmwandeln(x, y);
5760 border_explosion = TRUE;
5763 // if an element just explodes due to another explosion (chain-reaction),
5764 // do not immediately end the new explosion when it was the last frame of
5765 // the explosion (as it would be done in the following "if"-statement!)
5766 if (border_explosion && phase == last_phase)
5770 if (phase == last_phase)
5774 element = Feld[x][y] = Store[x][y];
5775 Store[x][y] = Store2[x][y] = 0;
5776 GfxElement[x][y] = EL_UNDEFINED;
5778 // player can escape from explosions and might therefore be still alive
5779 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5780 element <= EL_PLAYER_IS_EXPLODING_4)
5782 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5783 int explosion_element = EL_PLAYER_1 + player_nr;
5784 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5785 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5787 if (level.use_explosion_element[player_nr])
5788 explosion_element = level.explosion_element[player_nr];
5790 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5791 element_info[explosion_element].content.e[xx][yy]);
5794 // restore probably existing indestructible background element
5795 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5796 element = Feld[x][y] = Back[x][y];
5799 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5800 GfxDir[x][y] = MV_NONE;
5801 ChangeDelay[x][y] = 0;
5802 ChangePage[x][y] = -1;
5804 CustomValue[x][y] = 0;
5806 InitField_WithBug2(x, y, FALSE);
5808 TEST_DrawLevelField(x, y);
5810 TestIfElementTouchesCustomElement(x, y);
5812 if (GFX_CRUMBLED(element))
5813 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5815 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5816 StorePlayer[x][y] = 0;
5818 if (ELEM_IS_PLAYER(element))
5819 RelocatePlayer(x, y, element);
5821 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5823 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5824 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5827 TEST_DrawLevelFieldCrumbled(x, y);
5829 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5831 DrawLevelElement(x, y, Back[x][y]);
5832 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5834 else if (IS_WALKABLE_UNDER(Back[x][y]))
5836 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5837 DrawLevelElementThruMask(x, y, Back[x][y]);
5839 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5840 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5844 static void DynaExplode(int ex, int ey)
5847 int dynabomb_element = Feld[ex][ey];
5848 int dynabomb_size = 1;
5849 boolean dynabomb_xl = FALSE;
5850 struct PlayerInfo *player;
5851 static int xy[4][2] =
5859 if (IS_ACTIVE_BOMB(dynabomb_element))
5861 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5862 dynabomb_size = player->dynabomb_size;
5863 dynabomb_xl = player->dynabomb_xl;
5864 player->dynabombs_left++;
5867 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5869 for (i = 0; i < NUM_DIRECTIONS; i++)
5871 for (j = 1; j <= dynabomb_size; j++)
5873 int x = ex + j * xy[i][0];
5874 int y = ey + j * xy[i][1];
5877 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5880 element = Feld[x][y];
5882 // do not restart explosions of fields with active bombs
5883 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5886 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5888 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5889 !IS_DIGGABLE(element) && !dynabomb_xl)
5895 void Bang(int x, int y)
5897 int element = MovingOrBlocked2Element(x, y);
5898 int explosion_type = EX_TYPE_NORMAL;
5900 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5902 struct PlayerInfo *player = PLAYERINFO(x, y);
5904 element = Feld[x][y] = player->initial_element;
5906 if (level.use_explosion_element[player->index_nr])
5908 int explosion_element = level.explosion_element[player->index_nr];
5910 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5911 explosion_type = EX_TYPE_CROSS;
5912 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5913 explosion_type = EX_TYPE_CENTER;
5921 case EL_BD_BUTTERFLY:
5924 case EL_DARK_YAMYAM:
5928 RaiseScoreElement(element);
5931 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5932 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5933 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5934 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5935 case EL_DYNABOMB_INCREASE_NUMBER:
5936 case EL_DYNABOMB_INCREASE_SIZE:
5937 case EL_DYNABOMB_INCREASE_POWER:
5938 explosion_type = EX_TYPE_DYNA;
5941 case EL_DC_LANDMINE:
5942 explosion_type = EX_TYPE_CENTER;
5947 case EL_LAMP_ACTIVE:
5948 case EL_AMOEBA_TO_DIAMOND:
5949 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5950 explosion_type = EX_TYPE_CENTER;
5954 if (element_info[element].explosion_type == EXPLODES_CROSS)
5955 explosion_type = EX_TYPE_CROSS;
5956 else if (element_info[element].explosion_type == EXPLODES_1X1)
5957 explosion_type = EX_TYPE_CENTER;
5961 if (explosion_type == EX_TYPE_DYNA)
5964 Explode(x, y, EX_PHASE_START, explosion_type);
5966 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5969 static void SplashAcid(int x, int y)
5971 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5972 (!IN_LEV_FIELD(x - 1, y - 2) ||
5973 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5974 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5976 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5977 (!IN_LEV_FIELD(x + 1, y - 2) ||
5978 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5979 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5981 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5984 static void InitBeltMovement(void)
5986 static int belt_base_element[4] =
5988 EL_CONVEYOR_BELT_1_LEFT,
5989 EL_CONVEYOR_BELT_2_LEFT,
5990 EL_CONVEYOR_BELT_3_LEFT,
5991 EL_CONVEYOR_BELT_4_LEFT
5993 static int belt_base_active_element[4] =
5995 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5996 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5997 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5998 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6003 // set frame order for belt animation graphic according to belt direction
6004 for (i = 0; i < NUM_BELTS; i++)
6008 for (j = 0; j < NUM_BELT_PARTS; j++)
6010 int element = belt_base_active_element[belt_nr] + j;
6011 int graphic_1 = el2img(element);
6012 int graphic_2 = el2panelimg(element);
6014 if (game.belt_dir[i] == MV_LEFT)
6016 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6017 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6021 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6022 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6027 SCAN_PLAYFIELD(x, y)
6029 int element = Feld[x][y];
6031 for (i = 0; i < NUM_BELTS; i++)
6033 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6035 int e_belt_nr = getBeltNrFromBeltElement(element);
6038 if (e_belt_nr == belt_nr)
6040 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
6042 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
6049 static void ToggleBeltSwitch(int x, int y)
6051 static int belt_base_element[4] =
6053 EL_CONVEYOR_BELT_1_LEFT,
6054 EL_CONVEYOR_BELT_2_LEFT,
6055 EL_CONVEYOR_BELT_3_LEFT,
6056 EL_CONVEYOR_BELT_4_LEFT
6058 static int belt_base_active_element[4] =
6060 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6061 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6062 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6063 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6065 static int belt_base_switch_element[4] =
6067 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6068 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6069 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6070 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6072 static int belt_move_dir[4] =
6080 int element = Feld[x][y];
6081 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6082 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6083 int belt_dir = belt_move_dir[belt_dir_nr];
6086 if (!IS_BELT_SWITCH(element))
6089 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6090 game.belt_dir[belt_nr] = belt_dir;
6092 if (belt_dir_nr == 3)
6095 // set frame order for belt animation graphic according to belt direction
6096 for (i = 0; i < NUM_BELT_PARTS; i++)
6098 int element = belt_base_active_element[belt_nr] + i;
6099 int graphic_1 = el2img(element);
6100 int graphic_2 = el2panelimg(element);
6102 if (belt_dir == MV_LEFT)
6104 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6105 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6109 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6110 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6114 SCAN_PLAYFIELD(xx, yy)
6116 int element = Feld[xx][yy];
6118 if (IS_BELT_SWITCH(element))
6120 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6122 if (e_belt_nr == belt_nr)
6124 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6125 TEST_DrawLevelField(xx, yy);
6128 else if (IS_BELT(element) && belt_dir != MV_NONE)
6130 int e_belt_nr = getBeltNrFromBeltElement(element);
6132 if (e_belt_nr == belt_nr)
6134 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
6136 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6137 TEST_DrawLevelField(xx, yy);
6140 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6142 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6144 if (e_belt_nr == belt_nr)
6146 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6148 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6149 TEST_DrawLevelField(xx, yy);
6155 static void ToggleSwitchgateSwitch(int x, int y)
6159 game.switchgate_pos = !game.switchgate_pos;
6161 SCAN_PLAYFIELD(xx, yy)
6163 int element = Feld[xx][yy];
6165 if (element == EL_SWITCHGATE_SWITCH_UP)
6167 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6168 TEST_DrawLevelField(xx, yy);
6170 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6172 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6173 TEST_DrawLevelField(xx, yy);
6175 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6177 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6178 TEST_DrawLevelField(xx, yy);
6180 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6182 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6183 TEST_DrawLevelField(xx, yy);
6185 else if (element == EL_SWITCHGATE_OPEN ||
6186 element == EL_SWITCHGATE_OPENING)
6188 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6190 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6192 else if (element == EL_SWITCHGATE_CLOSED ||
6193 element == EL_SWITCHGATE_CLOSING)
6195 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6197 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6202 static int getInvisibleActiveFromInvisibleElement(int element)
6204 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6205 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6206 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6210 static int getInvisibleFromInvisibleActiveElement(int element)
6212 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6213 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6214 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6218 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6222 SCAN_PLAYFIELD(x, y)
6224 int element = Feld[x][y];
6226 if (element == EL_LIGHT_SWITCH &&
6227 game.light_time_left > 0)
6229 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6230 TEST_DrawLevelField(x, y);
6232 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6233 game.light_time_left == 0)
6235 Feld[x][y] = EL_LIGHT_SWITCH;
6236 TEST_DrawLevelField(x, y);
6238 else if (element == EL_EMC_DRIPPER &&
6239 game.light_time_left > 0)
6241 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6242 TEST_DrawLevelField(x, y);
6244 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6245 game.light_time_left == 0)
6247 Feld[x][y] = EL_EMC_DRIPPER;
6248 TEST_DrawLevelField(x, y);
6250 else if (element == EL_INVISIBLE_STEELWALL ||
6251 element == EL_INVISIBLE_WALL ||
6252 element == EL_INVISIBLE_SAND)
6254 if (game.light_time_left > 0)
6255 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6257 TEST_DrawLevelField(x, y);
6259 // uncrumble neighbour fields, if needed
6260 if (element == EL_INVISIBLE_SAND)
6261 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6263 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6264 element == EL_INVISIBLE_WALL_ACTIVE ||
6265 element == EL_INVISIBLE_SAND_ACTIVE)
6267 if (game.light_time_left == 0)
6268 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6270 TEST_DrawLevelField(x, y);
6272 // re-crumble neighbour fields, if needed
6273 if (element == EL_INVISIBLE_SAND)
6274 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6279 static void RedrawAllInvisibleElementsForLenses(void)
6283 SCAN_PLAYFIELD(x, y)
6285 int element = Feld[x][y];
6287 if (element == EL_EMC_DRIPPER &&
6288 game.lenses_time_left > 0)
6290 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6291 TEST_DrawLevelField(x, y);
6293 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6294 game.lenses_time_left == 0)
6296 Feld[x][y] = EL_EMC_DRIPPER;
6297 TEST_DrawLevelField(x, y);
6299 else if (element == EL_INVISIBLE_STEELWALL ||
6300 element == EL_INVISIBLE_WALL ||
6301 element == EL_INVISIBLE_SAND)
6303 if (game.lenses_time_left > 0)
6304 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6306 TEST_DrawLevelField(x, y);
6308 // uncrumble neighbour fields, if needed
6309 if (element == EL_INVISIBLE_SAND)
6310 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6312 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6313 element == EL_INVISIBLE_WALL_ACTIVE ||
6314 element == EL_INVISIBLE_SAND_ACTIVE)
6316 if (game.lenses_time_left == 0)
6317 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6319 TEST_DrawLevelField(x, y);
6321 // re-crumble neighbour fields, if needed
6322 if (element == EL_INVISIBLE_SAND)
6323 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6328 static void RedrawAllInvisibleElementsForMagnifier(void)
6332 SCAN_PLAYFIELD(x, y)
6334 int element = Feld[x][y];
6336 if (element == EL_EMC_FAKE_GRASS &&
6337 game.magnify_time_left > 0)
6339 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6340 TEST_DrawLevelField(x, y);
6342 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6343 game.magnify_time_left == 0)
6345 Feld[x][y] = EL_EMC_FAKE_GRASS;
6346 TEST_DrawLevelField(x, y);
6348 else if (IS_GATE_GRAY(element) &&
6349 game.magnify_time_left > 0)
6351 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6352 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6353 IS_EM_GATE_GRAY(element) ?
6354 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6355 IS_EMC_GATE_GRAY(element) ?
6356 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6357 IS_DC_GATE_GRAY(element) ?
6358 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6360 TEST_DrawLevelField(x, y);
6362 else if (IS_GATE_GRAY_ACTIVE(element) &&
6363 game.magnify_time_left == 0)
6365 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6366 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6367 IS_EM_GATE_GRAY_ACTIVE(element) ?
6368 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6369 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6370 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6371 IS_DC_GATE_GRAY_ACTIVE(element) ?
6372 EL_DC_GATE_WHITE_GRAY :
6374 TEST_DrawLevelField(x, y);
6379 static void ToggleLightSwitch(int x, int y)
6381 int element = Feld[x][y];
6383 game.light_time_left =
6384 (element == EL_LIGHT_SWITCH ?
6385 level.time_light * FRAMES_PER_SECOND : 0);
6387 RedrawAllLightSwitchesAndInvisibleElements();
6390 static void ActivateTimegateSwitch(int x, int y)
6394 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6396 SCAN_PLAYFIELD(xx, yy)
6398 int element = Feld[xx][yy];
6400 if (element == EL_TIMEGATE_CLOSED ||
6401 element == EL_TIMEGATE_CLOSING)
6403 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6404 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6408 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6410 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6411 TEST_DrawLevelField(xx, yy);
6417 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6418 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6421 static void Impact(int x, int y)
6423 boolean last_line = (y == lev_fieldy - 1);
6424 boolean object_hit = FALSE;
6425 boolean impact = (last_line || object_hit);
6426 int element = Feld[x][y];
6427 int smashed = EL_STEELWALL;
6429 if (!last_line) // check if element below was hit
6431 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6434 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6435 MovDir[x][y + 1] != MV_DOWN ||
6436 MovPos[x][y + 1] <= TILEY / 2));
6438 // do not smash moving elements that left the smashed field in time
6439 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6440 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6443 #if USE_QUICKSAND_IMPACT_BUGFIX
6444 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6446 RemoveMovingField(x, y + 1);
6447 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6448 Feld[x][y + 2] = EL_ROCK;
6449 TEST_DrawLevelField(x, y + 2);
6454 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6456 RemoveMovingField(x, y + 1);
6457 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6458 Feld[x][y + 2] = EL_ROCK;
6459 TEST_DrawLevelField(x, y + 2);
6466 smashed = MovingOrBlocked2Element(x, y + 1);
6468 impact = (last_line || object_hit);
6471 if (!last_line && smashed == EL_ACID) // element falls into acid
6473 SplashAcid(x, y + 1);
6477 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6478 // only reset graphic animation if graphic really changes after impact
6480 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6482 ResetGfxAnimation(x, y);
6483 TEST_DrawLevelField(x, y);
6486 if (impact && CAN_EXPLODE_IMPACT(element))
6491 else if (impact && element == EL_PEARL &&
6492 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6494 ResetGfxAnimation(x, y);
6496 Feld[x][y] = EL_PEARL_BREAKING;
6497 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6500 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6502 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6507 if (impact && element == EL_AMOEBA_DROP)
6509 if (object_hit && IS_PLAYER(x, y + 1))
6510 KillPlayerUnlessEnemyProtected(x, y + 1);
6511 else if (object_hit && smashed == EL_PENGUIN)
6515 Feld[x][y] = EL_AMOEBA_GROWING;
6516 Store[x][y] = EL_AMOEBA_WET;
6518 ResetRandomAnimationValue(x, y);
6523 if (object_hit) // check which object was hit
6525 if ((CAN_PASS_MAGIC_WALL(element) &&
6526 (smashed == EL_MAGIC_WALL ||
6527 smashed == EL_BD_MAGIC_WALL)) ||
6528 (CAN_PASS_DC_MAGIC_WALL(element) &&
6529 smashed == EL_DC_MAGIC_WALL))
6532 int activated_magic_wall =
6533 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6534 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6535 EL_DC_MAGIC_WALL_ACTIVE);
6537 // activate magic wall / mill
6538 SCAN_PLAYFIELD(xx, yy)
6540 if (Feld[xx][yy] == smashed)
6541 Feld[xx][yy] = activated_magic_wall;
6544 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6545 game.magic_wall_active = TRUE;
6547 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6548 SND_MAGIC_WALL_ACTIVATING :
6549 smashed == EL_BD_MAGIC_WALL ?
6550 SND_BD_MAGIC_WALL_ACTIVATING :
6551 SND_DC_MAGIC_WALL_ACTIVATING));
6554 if (IS_PLAYER(x, y + 1))
6556 if (CAN_SMASH_PLAYER(element))
6558 KillPlayerUnlessEnemyProtected(x, y + 1);
6562 else if (smashed == EL_PENGUIN)
6564 if (CAN_SMASH_PLAYER(element))
6570 else if (element == EL_BD_DIAMOND)
6572 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6578 else if (((element == EL_SP_INFOTRON ||
6579 element == EL_SP_ZONK) &&
6580 (smashed == EL_SP_SNIKSNAK ||
6581 smashed == EL_SP_ELECTRON ||
6582 smashed == EL_SP_DISK_ORANGE)) ||
6583 (element == EL_SP_INFOTRON &&
6584 smashed == EL_SP_DISK_YELLOW))
6589 else if (CAN_SMASH_EVERYTHING(element))
6591 if (IS_CLASSIC_ENEMY(smashed) ||
6592 CAN_EXPLODE_SMASHED(smashed))
6597 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6599 if (smashed == EL_LAMP ||
6600 smashed == EL_LAMP_ACTIVE)
6605 else if (smashed == EL_NUT)
6607 Feld[x][y + 1] = EL_NUT_BREAKING;
6608 PlayLevelSound(x, y, SND_NUT_BREAKING);
6609 RaiseScoreElement(EL_NUT);
6612 else if (smashed == EL_PEARL)
6614 ResetGfxAnimation(x, y);
6616 Feld[x][y + 1] = EL_PEARL_BREAKING;
6617 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6620 else if (smashed == EL_DIAMOND)
6622 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6623 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6626 else if (IS_BELT_SWITCH(smashed))
6628 ToggleBeltSwitch(x, y + 1);
6630 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6631 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6632 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6633 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6635 ToggleSwitchgateSwitch(x, y + 1);
6637 else if (smashed == EL_LIGHT_SWITCH ||
6638 smashed == EL_LIGHT_SWITCH_ACTIVE)
6640 ToggleLightSwitch(x, y + 1);
6644 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6646 CheckElementChangeBySide(x, y + 1, smashed, element,
6647 CE_SWITCHED, CH_SIDE_TOP);
6648 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6654 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6659 // play sound of magic wall / mill
6661 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6662 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6663 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6665 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6666 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6667 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6668 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6669 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6670 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6675 // play sound of object that hits the ground
6676 if (last_line || object_hit)
6677 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6680 static void TurnRoundExt(int x, int y)
6692 { 0, 0 }, { 0, 0 }, { 0, 0 },
6697 int left, right, back;
6701 { MV_DOWN, MV_UP, MV_RIGHT },
6702 { MV_UP, MV_DOWN, MV_LEFT },
6704 { MV_LEFT, MV_RIGHT, MV_DOWN },
6708 { MV_RIGHT, MV_LEFT, MV_UP }
6711 int element = Feld[x][y];
6712 int move_pattern = element_info[element].move_pattern;
6714 int old_move_dir = MovDir[x][y];
6715 int left_dir = turn[old_move_dir].left;
6716 int right_dir = turn[old_move_dir].right;
6717 int back_dir = turn[old_move_dir].back;
6719 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6720 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6721 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6722 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6724 int left_x = x + left_dx, left_y = y + left_dy;
6725 int right_x = x + right_dx, right_y = y + right_dy;
6726 int move_x = x + move_dx, move_y = y + move_dy;
6730 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6732 TestIfBadThingTouchesOtherBadThing(x, y);
6734 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6735 MovDir[x][y] = right_dir;
6736 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6737 MovDir[x][y] = left_dir;
6739 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6741 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6744 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6746 TestIfBadThingTouchesOtherBadThing(x, y);
6748 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6749 MovDir[x][y] = left_dir;
6750 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6751 MovDir[x][y] = right_dir;
6753 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6755 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6758 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6760 TestIfBadThingTouchesOtherBadThing(x, y);
6762 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6763 MovDir[x][y] = left_dir;
6764 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6765 MovDir[x][y] = right_dir;
6767 if (MovDir[x][y] != old_move_dir)
6770 else if (element == EL_YAMYAM)
6772 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6773 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6775 if (can_turn_left && can_turn_right)
6776 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6777 else if (can_turn_left)
6778 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6779 else if (can_turn_right)
6780 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6782 MovDir[x][y] = back_dir;
6784 MovDelay[x][y] = 16 + 16 * RND(3);
6786 else if (element == EL_DARK_YAMYAM)
6788 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6790 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6793 if (can_turn_left && can_turn_right)
6794 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6795 else if (can_turn_left)
6796 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6797 else if (can_turn_right)
6798 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6800 MovDir[x][y] = back_dir;
6802 MovDelay[x][y] = 16 + 16 * RND(3);
6804 else if (element == EL_PACMAN)
6806 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6807 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6809 if (can_turn_left && can_turn_right)
6810 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6811 else if (can_turn_left)
6812 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6813 else if (can_turn_right)
6814 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6816 MovDir[x][y] = back_dir;
6818 MovDelay[x][y] = 6 + RND(40);
6820 else if (element == EL_PIG)
6822 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6823 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6824 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6825 boolean should_turn_left, should_turn_right, should_move_on;
6827 int rnd = RND(rnd_value);
6829 should_turn_left = (can_turn_left &&
6831 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6832 y + back_dy + left_dy)));
6833 should_turn_right = (can_turn_right &&
6835 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6836 y + back_dy + right_dy)));
6837 should_move_on = (can_move_on &&
6840 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6841 y + move_dy + left_dy) ||
6842 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6843 y + move_dy + right_dy)));
6845 if (should_turn_left || should_turn_right || should_move_on)
6847 if (should_turn_left && should_turn_right && should_move_on)
6848 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6849 rnd < 2 * rnd_value / 3 ? right_dir :
6851 else if (should_turn_left && should_turn_right)
6852 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6853 else if (should_turn_left && should_move_on)
6854 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6855 else if (should_turn_right && should_move_on)
6856 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6857 else if (should_turn_left)
6858 MovDir[x][y] = left_dir;
6859 else if (should_turn_right)
6860 MovDir[x][y] = right_dir;
6861 else if (should_move_on)
6862 MovDir[x][y] = old_move_dir;
6864 else if (can_move_on && rnd > rnd_value / 8)
6865 MovDir[x][y] = old_move_dir;
6866 else if (can_turn_left && can_turn_right)
6867 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6868 else if (can_turn_left && rnd > rnd_value / 8)
6869 MovDir[x][y] = left_dir;
6870 else if (can_turn_right && rnd > rnd_value/8)
6871 MovDir[x][y] = right_dir;
6873 MovDir[x][y] = back_dir;
6875 xx = x + move_xy[MovDir[x][y]].dx;
6876 yy = y + move_xy[MovDir[x][y]].dy;
6878 if (!IN_LEV_FIELD(xx, yy) ||
6879 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6880 MovDir[x][y] = old_move_dir;
6884 else if (element == EL_DRAGON)
6886 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6887 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6888 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6890 int rnd = RND(rnd_value);
6892 if (can_move_on && rnd > rnd_value / 8)
6893 MovDir[x][y] = old_move_dir;
6894 else if (can_turn_left && can_turn_right)
6895 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6896 else if (can_turn_left && rnd > rnd_value / 8)
6897 MovDir[x][y] = left_dir;
6898 else if (can_turn_right && rnd > rnd_value / 8)
6899 MovDir[x][y] = right_dir;
6901 MovDir[x][y] = back_dir;
6903 xx = x + move_xy[MovDir[x][y]].dx;
6904 yy = y + move_xy[MovDir[x][y]].dy;
6906 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6907 MovDir[x][y] = old_move_dir;
6911 else if (element == EL_MOLE)
6913 boolean can_move_on =
6914 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6915 IS_AMOEBOID(Feld[move_x][move_y]) ||
6916 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6919 boolean can_turn_left =
6920 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6921 IS_AMOEBOID(Feld[left_x][left_y])));
6923 boolean can_turn_right =
6924 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6925 IS_AMOEBOID(Feld[right_x][right_y])));
6927 if (can_turn_left && can_turn_right)
6928 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6929 else if (can_turn_left)
6930 MovDir[x][y] = left_dir;
6932 MovDir[x][y] = right_dir;
6935 if (MovDir[x][y] != old_move_dir)
6938 else if (element == EL_BALLOON)
6940 MovDir[x][y] = game.wind_direction;
6943 else if (element == EL_SPRING)
6945 if (MovDir[x][y] & MV_HORIZONTAL)
6947 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6948 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6950 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6951 ResetGfxAnimation(move_x, move_y);
6952 TEST_DrawLevelField(move_x, move_y);
6954 MovDir[x][y] = back_dir;
6956 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6957 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6958 MovDir[x][y] = MV_NONE;
6963 else if (element == EL_ROBOT ||
6964 element == EL_SATELLITE ||
6965 element == EL_PENGUIN ||
6966 element == EL_EMC_ANDROID)
6968 int attr_x = -1, attr_y = -1;
6970 if (game.all_players_gone)
6972 attr_x = game.exit_x;
6973 attr_y = game.exit_y;
6979 for (i = 0; i < MAX_PLAYERS; i++)
6981 struct PlayerInfo *player = &stored_player[i];
6982 int jx = player->jx, jy = player->jy;
6984 if (!player->active)
6988 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6996 if (element == EL_ROBOT &&
6997 game.robot_wheel_x >= 0 &&
6998 game.robot_wheel_y >= 0 &&
6999 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7000 game.engine_version < VERSION_IDENT(3,1,0,0)))
7002 attr_x = game.robot_wheel_x;
7003 attr_y = game.robot_wheel_y;
7006 if (element == EL_PENGUIN)
7009 static int xy[4][2] =
7017 for (i = 0; i < NUM_DIRECTIONS; i++)
7019 int ex = x + xy[i][0];
7020 int ey = y + xy[i][1];
7022 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
7023 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
7024 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
7025 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7034 MovDir[x][y] = MV_NONE;
7036 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7037 else if (attr_x > x)
7038 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7040 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7041 else if (attr_y > y)
7042 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7044 if (element == EL_ROBOT)
7048 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7049 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7050 Moving2Blocked(x, y, &newx, &newy);
7052 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7053 MovDelay[x][y] = 8 + 8 * !RND(3);
7055 MovDelay[x][y] = 16;
7057 else if (element == EL_PENGUIN)
7063 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7065 boolean first_horiz = RND(2);
7066 int new_move_dir = MovDir[x][y];
7069 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7070 Moving2Blocked(x, y, &newx, &newy);
7072 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7076 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7077 Moving2Blocked(x, y, &newx, &newy);
7079 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7082 MovDir[x][y] = old_move_dir;
7086 else if (element == EL_SATELLITE)
7092 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7094 boolean first_horiz = RND(2);
7095 int new_move_dir = MovDir[x][y];
7098 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7099 Moving2Blocked(x, y, &newx, &newy);
7101 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7105 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7106 Moving2Blocked(x, y, &newx, &newy);
7108 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7111 MovDir[x][y] = old_move_dir;
7115 else if (element == EL_EMC_ANDROID)
7117 static int check_pos[16] =
7119 -1, // 0 => (invalid)
7122 -1, // 3 => (invalid)
7124 0, // 5 => MV_LEFT | MV_UP
7125 2, // 6 => MV_RIGHT | MV_UP
7126 -1, // 7 => (invalid)
7128 6, // 9 => MV_LEFT | MV_DOWN
7129 4, // 10 => MV_RIGHT | MV_DOWN
7130 -1, // 11 => (invalid)
7131 -1, // 12 => (invalid)
7132 -1, // 13 => (invalid)
7133 -1, // 14 => (invalid)
7134 -1, // 15 => (invalid)
7142 { -1, -1, MV_LEFT | MV_UP },
7144 { +1, -1, MV_RIGHT | MV_UP },
7145 { +1, 0, MV_RIGHT },
7146 { +1, +1, MV_RIGHT | MV_DOWN },
7148 { -1, +1, MV_LEFT | MV_DOWN },
7151 int start_pos, check_order;
7152 boolean can_clone = FALSE;
7155 // check if there is any free field around current position
7156 for (i = 0; i < 8; i++)
7158 int newx = x + check_xy[i].dx;
7159 int newy = y + check_xy[i].dy;
7161 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7169 if (can_clone) // randomly find an element to clone
7173 start_pos = check_pos[RND(8)];
7174 check_order = (RND(2) ? -1 : +1);
7176 for (i = 0; i < 8; i++)
7178 int pos_raw = start_pos + i * check_order;
7179 int pos = (pos_raw + 8) % 8;
7180 int newx = x + check_xy[pos].dx;
7181 int newy = y + check_xy[pos].dy;
7183 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7185 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7186 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7188 Store[x][y] = Feld[newx][newy];
7197 if (can_clone) // randomly find a direction to move
7201 start_pos = check_pos[RND(8)];
7202 check_order = (RND(2) ? -1 : +1);
7204 for (i = 0; i < 8; i++)
7206 int pos_raw = start_pos + i * check_order;
7207 int pos = (pos_raw + 8) % 8;
7208 int newx = x + check_xy[pos].dx;
7209 int newy = y + check_xy[pos].dy;
7210 int new_move_dir = check_xy[pos].dir;
7212 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7214 MovDir[x][y] = new_move_dir;
7215 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7224 if (can_clone) // cloning and moving successful
7227 // cannot clone -- try to move towards player
7229 start_pos = check_pos[MovDir[x][y] & 0x0f];
7230 check_order = (RND(2) ? -1 : +1);
7232 for (i = 0; i < 3; i++)
7234 // first check start_pos, then previous/next or (next/previous) pos
7235 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7236 int pos = (pos_raw + 8) % 8;
7237 int newx = x + check_xy[pos].dx;
7238 int newy = y + check_xy[pos].dy;
7239 int new_move_dir = check_xy[pos].dir;
7241 if (IS_PLAYER(newx, newy))
7244 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7246 MovDir[x][y] = new_move_dir;
7247 MovDelay[x][y] = level.android_move_time * 8 + 1;
7254 else if (move_pattern == MV_TURNING_LEFT ||
7255 move_pattern == MV_TURNING_RIGHT ||
7256 move_pattern == MV_TURNING_LEFT_RIGHT ||
7257 move_pattern == MV_TURNING_RIGHT_LEFT ||
7258 move_pattern == MV_TURNING_RANDOM ||
7259 move_pattern == MV_ALL_DIRECTIONS)
7261 boolean can_turn_left =
7262 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7263 boolean can_turn_right =
7264 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7266 if (element_info[element].move_stepsize == 0) // "not moving"
7269 if (move_pattern == MV_TURNING_LEFT)
7270 MovDir[x][y] = left_dir;
7271 else if (move_pattern == MV_TURNING_RIGHT)
7272 MovDir[x][y] = right_dir;
7273 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7274 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7275 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7276 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7277 else if (move_pattern == MV_TURNING_RANDOM)
7278 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7279 can_turn_right && !can_turn_left ? right_dir :
7280 RND(2) ? left_dir : right_dir);
7281 else if (can_turn_left && can_turn_right)
7282 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7283 else if (can_turn_left)
7284 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7285 else if (can_turn_right)
7286 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7288 MovDir[x][y] = back_dir;
7290 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7292 else if (move_pattern == MV_HORIZONTAL ||
7293 move_pattern == MV_VERTICAL)
7295 if (move_pattern & old_move_dir)
7296 MovDir[x][y] = back_dir;
7297 else if (move_pattern == MV_HORIZONTAL)
7298 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7299 else if (move_pattern == MV_VERTICAL)
7300 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7302 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7304 else if (move_pattern & MV_ANY_DIRECTION)
7306 MovDir[x][y] = move_pattern;
7307 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7309 else if (move_pattern & MV_WIND_DIRECTION)
7311 MovDir[x][y] = game.wind_direction;
7312 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7314 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7316 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7317 MovDir[x][y] = left_dir;
7318 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7319 MovDir[x][y] = right_dir;
7321 if (MovDir[x][y] != old_move_dir)
7322 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7324 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7326 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7327 MovDir[x][y] = right_dir;
7328 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7329 MovDir[x][y] = left_dir;
7331 if (MovDir[x][y] != old_move_dir)
7332 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7334 else if (move_pattern == MV_TOWARDS_PLAYER ||
7335 move_pattern == MV_AWAY_FROM_PLAYER)
7337 int attr_x = -1, attr_y = -1;
7339 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7341 if (game.all_players_gone)
7343 attr_x = game.exit_x;
7344 attr_y = game.exit_y;
7350 for (i = 0; i < MAX_PLAYERS; i++)
7352 struct PlayerInfo *player = &stored_player[i];
7353 int jx = player->jx, jy = player->jy;
7355 if (!player->active)
7359 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7367 MovDir[x][y] = MV_NONE;
7369 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7370 else if (attr_x > x)
7371 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7373 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7374 else if (attr_y > y)
7375 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7377 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7379 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7381 boolean first_horiz = RND(2);
7382 int new_move_dir = MovDir[x][y];
7384 if (element_info[element].move_stepsize == 0) // "not moving"
7386 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7387 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7393 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7394 Moving2Blocked(x, y, &newx, &newy);
7396 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7400 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7401 Moving2Blocked(x, y, &newx, &newy);
7403 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7406 MovDir[x][y] = old_move_dir;
7409 else if (move_pattern == MV_WHEN_PUSHED ||
7410 move_pattern == MV_WHEN_DROPPED)
7412 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7413 MovDir[x][y] = MV_NONE;
7417 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7419 static int test_xy[7][2] =
7429 static int test_dir[7] =
7439 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7440 int move_preference = -1000000; // start with very low preference
7441 int new_move_dir = MV_NONE;
7442 int start_test = RND(4);
7445 for (i = 0; i < NUM_DIRECTIONS; i++)
7447 int move_dir = test_dir[start_test + i];
7448 int move_dir_preference;
7450 xx = x + test_xy[start_test + i][0];
7451 yy = y + test_xy[start_test + i][1];
7453 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7454 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7456 new_move_dir = move_dir;
7461 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7464 move_dir_preference = -1 * RunnerVisit[xx][yy];
7465 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7466 move_dir_preference = PlayerVisit[xx][yy];
7468 if (move_dir_preference > move_preference)
7470 // prefer field that has not been visited for the longest time
7471 move_preference = move_dir_preference;
7472 new_move_dir = move_dir;
7474 else if (move_dir_preference == move_preference &&
7475 move_dir == old_move_dir)
7477 // prefer last direction when all directions are preferred equally
7478 move_preference = move_dir_preference;
7479 new_move_dir = move_dir;
7483 MovDir[x][y] = new_move_dir;
7484 if (old_move_dir != new_move_dir)
7485 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7489 static void TurnRound(int x, int y)
7491 int direction = MovDir[x][y];
7495 GfxDir[x][y] = MovDir[x][y];
7497 if (direction != MovDir[x][y])
7501 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7503 ResetGfxFrame(x, y);
7506 static boolean JustBeingPushed(int x, int y)
7510 for (i = 0; i < MAX_PLAYERS; i++)
7512 struct PlayerInfo *player = &stored_player[i];
7514 if (player->active && player->is_pushing && player->MovPos)
7516 int next_jx = player->jx + (player->jx - player->last_jx);
7517 int next_jy = player->jy + (player->jy - player->last_jy);
7519 if (x == next_jx && y == next_jy)
7527 static void StartMoving(int x, int y)
7529 boolean started_moving = FALSE; // some elements can fall _and_ move
7530 int element = Feld[x][y];
7535 if (MovDelay[x][y] == 0)
7536 GfxAction[x][y] = ACTION_DEFAULT;
7538 if (CAN_FALL(element) && y < lev_fieldy - 1)
7540 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7541 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7542 if (JustBeingPushed(x, y))
7545 if (element == EL_QUICKSAND_FULL)
7547 if (IS_FREE(x, y + 1))
7549 InitMovingField(x, y, MV_DOWN);
7550 started_moving = TRUE;
7552 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7553 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7554 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7555 Store[x][y] = EL_ROCK;
7557 Store[x][y] = EL_ROCK;
7560 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7562 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7564 if (!MovDelay[x][y])
7566 MovDelay[x][y] = TILEY + 1;
7568 ResetGfxAnimation(x, y);
7569 ResetGfxAnimation(x, y + 1);
7574 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7575 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7582 Feld[x][y] = EL_QUICKSAND_EMPTY;
7583 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7584 Store[x][y + 1] = Store[x][y];
7587 PlayLevelSoundAction(x, y, ACTION_FILLING);
7589 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7591 if (!MovDelay[x][y])
7593 MovDelay[x][y] = TILEY + 1;
7595 ResetGfxAnimation(x, y);
7596 ResetGfxAnimation(x, y + 1);
7601 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7602 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7609 Feld[x][y] = EL_QUICKSAND_EMPTY;
7610 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7611 Store[x][y + 1] = Store[x][y];
7614 PlayLevelSoundAction(x, y, ACTION_FILLING);
7617 else if (element == EL_QUICKSAND_FAST_FULL)
7619 if (IS_FREE(x, y + 1))
7621 InitMovingField(x, y, MV_DOWN);
7622 started_moving = TRUE;
7624 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7625 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7626 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7627 Store[x][y] = EL_ROCK;
7629 Store[x][y] = EL_ROCK;
7632 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7634 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7636 if (!MovDelay[x][y])
7638 MovDelay[x][y] = TILEY + 1;
7640 ResetGfxAnimation(x, y);
7641 ResetGfxAnimation(x, y + 1);
7646 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7647 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7654 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7655 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7656 Store[x][y + 1] = Store[x][y];
7659 PlayLevelSoundAction(x, y, ACTION_FILLING);
7661 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7663 if (!MovDelay[x][y])
7665 MovDelay[x][y] = TILEY + 1;
7667 ResetGfxAnimation(x, y);
7668 ResetGfxAnimation(x, y + 1);
7673 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7674 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7681 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7682 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7683 Store[x][y + 1] = Store[x][y];
7686 PlayLevelSoundAction(x, y, ACTION_FILLING);
7689 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7690 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7692 InitMovingField(x, y, MV_DOWN);
7693 started_moving = TRUE;
7695 Feld[x][y] = EL_QUICKSAND_FILLING;
7696 Store[x][y] = element;
7698 PlayLevelSoundAction(x, y, ACTION_FILLING);
7700 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7701 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7703 InitMovingField(x, y, MV_DOWN);
7704 started_moving = TRUE;
7706 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7707 Store[x][y] = element;
7709 PlayLevelSoundAction(x, y, ACTION_FILLING);
7711 else if (element == EL_MAGIC_WALL_FULL)
7713 if (IS_FREE(x, y + 1))
7715 InitMovingField(x, y, MV_DOWN);
7716 started_moving = TRUE;
7718 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7719 Store[x][y] = EL_CHANGED(Store[x][y]);
7721 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7723 if (!MovDelay[x][y])
7724 MovDelay[x][y] = TILEY / 4 + 1;
7733 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7734 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7735 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7739 else if (element == EL_BD_MAGIC_WALL_FULL)
7741 if (IS_FREE(x, y + 1))
7743 InitMovingField(x, y, MV_DOWN);
7744 started_moving = TRUE;
7746 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7747 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7749 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7751 if (!MovDelay[x][y])
7752 MovDelay[x][y] = TILEY / 4 + 1;
7761 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7762 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7763 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7767 else if (element == EL_DC_MAGIC_WALL_FULL)
7769 if (IS_FREE(x, y + 1))
7771 InitMovingField(x, y, MV_DOWN);
7772 started_moving = TRUE;
7774 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7775 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7777 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7779 if (!MovDelay[x][y])
7780 MovDelay[x][y] = TILEY / 4 + 1;
7789 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7790 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7791 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7795 else if ((CAN_PASS_MAGIC_WALL(element) &&
7796 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7797 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7798 (CAN_PASS_DC_MAGIC_WALL(element) &&
7799 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7802 InitMovingField(x, y, MV_DOWN);
7803 started_moving = TRUE;
7806 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7807 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7808 EL_DC_MAGIC_WALL_FILLING);
7809 Store[x][y] = element;
7811 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7813 SplashAcid(x, y + 1);
7815 InitMovingField(x, y, MV_DOWN);
7816 started_moving = TRUE;
7818 Store[x][y] = EL_ACID;
7821 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7822 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7823 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7824 CAN_FALL(element) && WasJustFalling[x][y] &&
7825 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7827 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7828 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7829 (Feld[x][y + 1] == EL_BLOCKED)))
7831 /* this is needed for a special case not covered by calling "Impact()"
7832 from "ContinueMoving()": if an element moves to a tile directly below
7833 another element which was just falling on that tile (which was empty
7834 in the previous frame), the falling element above would just stop
7835 instead of smashing the element below (in previous version, the above
7836 element was just checked for "moving" instead of "falling", resulting
7837 in incorrect smashes caused by horizontal movement of the above
7838 element; also, the case of the player being the element to smash was
7839 simply not covered here... :-/ ) */
7841 CheckCollision[x][y] = 0;
7842 CheckImpact[x][y] = 0;
7846 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7848 if (MovDir[x][y] == MV_NONE)
7850 InitMovingField(x, y, MV_DOWN);
7851 started_moving = TRUE;
7854 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7856 if (WasJustFalling[x][y]) // prevent animation from being restarted
7857 MovDir[x][y] = MV_DOWN;
7859 InitMovingField(x, y, MV_DOWN);
7860 started_moving = TRUE;
7862 else if (element == EL_AMOEBA_DROP)
7864 Feld[x][y] = EL_AMOEBA_GROWING;
7865 Store[x][y] = EL_AMOEBA_WET;
7867 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7868 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7869 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7870 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7872 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7873 (IS_FREE(x - 1, y + 1) ||
7874 Feld[x - 1][y + 1] == EL_ACID));
7875 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7876 (IS_FREE(x + 1, y + 1) ||
7877 Feld[x + 1][y + 1] == EL_ACID));
7878 boolean can_fall_any = (can_fall_left || can_fall_right);
7879 boolean can_fall_both = (can_fall_left && can_fall_right);
7880 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7882 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7884 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7885 can_fall_right = FALSE;
7886 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7887 can_fall_left = FALSE;
7888 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7889 can_fall_right = FALSE;
7890 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7891 can_fall_left = FALSE;
7893 can_fall_any = (can_fall_left || can_fall_right);
7894 can_fall_both = FALSE;
7899 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7900 can_fall_right = FALSE; // slip down on left side
7902 can_fall_left = !(can_fall_right = RND(2));
7904 can_fall_both = FALSE;
7909 // if not determined otherwise, prefer left side for slipping down
7910 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7911 started_moving = TRUE;
7914 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7916 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7917 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7918 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7919 int belt_dir = game.belt_dir[belt_nr];
7921 if ((belt_dir == MV_LEFT && left_is_free) ||
7922 (belt_dir == MV_RIGHT && right_is_free))
7924 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7926 InitMovingField(x, y, belt_dir);
7927 started_moving = TRUE;
7929 Pushed[x][y] = TRUE;
7930 Pushed[nextx][y] = TRUE;
7932 GfxAction[x][y] = ACTION_DEFAULT;
7936 MovDir[x][y] = 0; // if element was moving, stop it
7941 // not "else if" because of elements that can fall and move (EL_SPRING)
7942 if (CAN_MOVE(element) && !started_moving)
7944 int move_pattern = element_info[element].move_pattern;
7947 Moving2Blocked(x, y, &newx, &newy);
7949 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7952 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7953 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7955 WasJustMoving[x][y] = 0;
7956 CheckCollision[x][y] = 0;
7958 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7960 if (Feld[x][y] != element) // element has changed
7964 if (!MovDelay[x][y]) // start new movement phase
7966 // all objects that can change their move direction after each step
7967 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7969 if (element != EL_YAMYAM &&
7970 element != EL_DARK_YAMYAM &&
7971 element != EL_PACMAN &&
7972 !(move_pattern & MV_ANY_DIRECTION) &&
7973 move_pattern != MV_TURNING_LEFT &&
7974 move_pattern != MV_TURNING_RIGHT &&
7975 move_pattern != MV_TURNING_LEFT_RIGHT &&
7976 move_pattern != MV_TURNING_RIGHT_LEFT &&
7977 move_pattern != MV_TURNING_RANDOM)
7981 if (MovDelay[x][y] && (element == EL_BUG ||
7982 element == EL_SPACESHIP ||
7983 element == EL_SP_SNIKSNAK ||
7984 element == EL_SP_ELECTRON ||
7985 element == EL_MOLE))
7986 TEST_DrawLevelField(x, y);
7990 if (MovDelay[x][y]) // wait some time before next movement
7994 if (element == EL_ROBOT ||
7995 element == EL_YAMYAM ||
7996 element == EL_DARK_YAMYAM)
7998 DrawLevelElementAnimationIfNeeded(x, y, element);
7999 PlayLevelSoundAction(x, y, ACTION_WAITING);
8001 else if (element == EL_SP_ELECTRON)
8002 DrawLevelElementAnimationIfNeeded(x, y, element);
8003 else if (element == EL_DRAGON)
8006 int dir = MovDir[x][y];
8007 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8008 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8009 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8010 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8011 dir == MV_UP ? IMG_FLAMES_1_UP :
8012 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8013 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8015 GfxAction[x][y] = ACTION_ATTACKING;
8017 if (IS_PLAYER(x, y))
8018 DrawPlayerField(x, y);
8020 TEST_DrawLevelField(x, y);
8022 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8024 for (i = 1; i <= 3; i++)
8026 int xx = x + i * dx;
8027 int yy = y + i * dy;
8028 int sx = SCREENX(xx);
8029 int sy = SCREENY(yy);
8030 int flame_graphic = graphic + (i - 1);
8032 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
8037 int flamed = MovingOrBlocked2Element(xx, yy);
8039 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8042 RemoveMovingField(xx, yy);
8044 ChangeDelay[xx][yy] = 0;
8046 Feld[xx][yy] = EL_FLAMES;
8048 if (IN_SCR_FIELD(sx, sy))
8050 TEST_DrawLevelFieldCrumbled(xx, yy);
8051 DrawGraphic(sx, sy, flame_graphic, frame);
8056 if (Feld[xx][yy] == EL_FLAMES)
8057 Feld[xx][yy] = EL_EMPTY;
8058 TEST_DrawLevelField(xx, yy);
8063 if (MovDelay[x][y]) // element still has to wait some time
8065 PlayLevelSoundAction(x, y, ACTION_WAITING);
8071 // now make next step
8073 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8075 if (DONT_COLLIDE_WITH(element) &&
8076 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8077 !PLAYER_ENEMY_PROTECTED(newx, newy))
8079 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8084 else if (CAN_MOVE_INTO_ACID(element) &&
8085 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
8086 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8087 (MovDir[x][y] == MV_DOWN ||
8088 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8090 SplashAcid(newx, newy);
8091 Store[x][y] = EL_ACID;
8093 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8095 if (Feld[newx][newy] == EL_EXIT_OPEN ||
8096 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
8097 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
8098 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8101 TEST_DrawLevelField(x, y);
8103 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8104 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8105 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8107 game.friends_still_needed--;
8108 if (!game.friends_still_needed &&
8110 game.all_players_gone)
8115 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
8117 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8118 TEST_DrawLevelField(newx, newy);
8120 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8122 else if (!IS_FREE(newx, newy))
8124 GfxAction[x][y] = ACTION_WAITING;
8126 if (IS_PLAYER(x, y))
8127 DrawPlayerField(x, y);
8129 TEST_DrawLevelField(x, y);
8134 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8136 if (IS_FOOD_PIG(Feld[newx][newy]))
8138 if (IS_MOVING(newx, newy))
8139 RemoveMovingField(newx, newy);
8142 Feld[newx][newy] = EL_EMPTY;
8143 TEST_DrawLevelField(newx, newy);
8146 PlayLevelSound(x, y, SND_PIG_DIGGING);
8148 else if (!IS_FREE(newx, newy))
8150 if (IS_PLAYER(x, y))
8151 DrawPlayerField(x, y);
8153 TEST_DrawLevelField(x, y);
8158 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8160 if (Store[x][y] != EL_EMPTY)
8162 boolean can_clone = FALSE;
8165 // check if element to clone is still there
8166 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8168 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8176 // cannot clone or target field not free anymore -- do not clone
8177 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8178 Store[x][y] = EL_EMPTY;
8181 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8183 if (IS_MV_DIAGONAL(MovDir[x][y]))
8185 int diagonal_move_dir = MovDir[x][y];
8186 int stored = Store[x][y];
8187 int change_delay = 8;
8190 // android is moving diagonally
8192 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8194 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8195 GfxElement[x][y] = EL_EMC_ANDROID;
8196 GfxAction[x][y] = ACTION_SHRINKING;
8197 GfxDir[x][y] = diagonal_move_dir;
8198 ChangeDelay[x][y] = change_delay;
8200 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8203 DrawLevelGraphicAnimation(x, y, graphic);
8204 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8206 if (Feld[newx][newy] == EL_ACID)
8208 SplashAcid(newx, newy);
8213 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8215 Store[newx][newy] = EL_EMC_ANDROID;
8216 GfxElement[newx][newy] = EL_EMC_ANDROID;
8217 GfxAction[newx][newy] = ACTION_GROWING;
8218 GfxDir[newx][newy] = diagonal_move_dir;
8219 ChangeDelay[newx][newy] = change_delay;
8221 graphic = el_act_dir2img(GfxElement[newx][newy],
8222 GfxAction[newx][newy], GfxDir[newx][newy]);
8224 DrawLevelGraphicAnimation(newx, newy, graphic);
8225 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8231 Feld[newx][newy] = EL_EMPTY;
8232 TEST_DrawLevelField(newx, newy);
8234 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8237 else if (!IS_FREE(newx, newy))
8242 else if (IS_CUSTOM_ELEMENT(element) &&
8243 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8245 if (!DigFieldByCE(newx, newy, element))
8248 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8250 RunnerVisit[x][y] = FrameCounter;
8251 PlayerVisit[x][y] /= 8; // expire player visit path
8254 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8256 if (!IS_FREE(newx, newy))
8258 if (IS_PLAYER(x, y))
8259 DrawPlayerField(x, y);
8261 TEST_DrawLevelField(x, y);
8267 boolean wanna_flame = !RND(10);
8268 int dx = newx - x, dy = newy - y;
8269 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8270 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8271 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8272 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8273 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8274 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8277 IS_CLASSIC_ENEMY(element1) ||
8278 IS_CLASSIC_ENEMY(element2)) &&
8279 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8280 element1 != EL_FLAMES && element2 != EL_FLAMES)
8282 ResetGfxAnimation(x, y);
8283 GfxAction[x][y] = ACTION_ATTACKING;
8285 if (IS_PLAYER(x, y))
8286 DrawPlayerField(x, y);
8288 TEST_DrawLevelField(x, y);
8290 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8292 MovDelay[x][y] = 50;
8294 Feld[newx][newy] = EL_FLAMES;
8295 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8296 Feld[newx1][newy1] = EL_FLAMES;
8297 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8298 Feld[newx2][newy2] = EL_FLAMES;
8304 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8305 Feld[newx][newy] == EL_DIAMOND)
8307 if (IS_MOVING(newx, newy))
8308 RemoveMovingField(newx, newy);
8311 Feld[newx][newy] = EL_EMPTY;
8312 TEST_DrawLevelField(newx, newy);
8315 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8317 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8318 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8320 if (AmoebaNr[newx][newy])
8322 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8323 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8324 Feld[newx][newy] == EL_BD_AMOEBA)
8325 AmoebaCnt[AmoebaNr[newx][newy]]--;
8328 if (IS_MOVING(newx, newy))
8330 RemoveMovingField(newx, newy);
8334 Feld[newx][newy] = EL_EMPTY;
8335 TEST_DrawLevelField(newx, newy);
8338 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8340 else if ((element == EL_PACMAN || element == EL_MOLE)
8341 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8343 if (AmoebaNr[newx][newy])
8345 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8346 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8347 Feld[newx][newy] == EL_BD_AMOEBA)
8348 AmoebaCnt[AmoebaNr[newx][newy]]--;
8351 if (element == EL_MOLE)
8353 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8354 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8356 ResetGfxAnimation(x, y);
8357 GfxAction[x][y] = ACTION_DIGGING;
8358 TEST_DrawLevelField(x, y);
8360 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8362 return; // wait for shrinking amoeba
8364 else // element == EL_PACMAN
8366 Feld[newx][newy] = EL_EMPTY;
8367 TEST_DrawLevelField(newx, newy);
8368 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8371 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8372 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8373 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8375 // wait for shrinking amoeba to completely disappear
8378 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8380 // object was running against a wall
8384 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8385 DrawLevelElementAnimation(x, y, element);
8387 if (DONT_TOUCH(element))
8388 TestIfBadThingTouchesPlayer(x, y);
8393 InitMovingField(x, y, MovDir[x][y]);
8395 PlayLevelSoundAction(x, y, ACTION_MOVING);
8399 ContinueMoving(x, y);
8402 void ContinueMoving(int x, int y)
8404 int element = Feld[x][y];
8405 struct ElementInfo *ei = &element_info[element];
8406 int direction = MovDir[x][y];
8407 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8408 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8409 int newx = x + dx, newy = y + dy;
8410 int stored = Store[x][y];
8411 int stored_new = Store[newx][newy];
8412 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8413 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8414 boolean last_line = (newy == lev_fieldy - 1);
8416 MovPos[x][y] += getElementMoveStepsize(x, y);
8418 if (pushed_by_player) // special case: moving object pushed by player
8419 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8421 if (ABS(MovPos[x][y]) < TILEX)
8423 TEST_DrawLevelField(x, y);
8425 return; // element is still moving
8428 // element reached destination field
8430 Feld[x][y] = EL_EMPTY;
8431 Feld[newx][newy] = element;
8432 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8434 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8436 element = Feld[newx][newy] = EL_ACID;
8438 else if (element == EL_MOLE)
8440 Feld[x][y] = EL_SAND;
8442 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8444 else if (element == EL_QUICKSAND_FILLING)
8446 element = Feld[newx][newy] = get_next_element(element);
8447 Store[newx][newy] = Store[x][y];
8449 else if (element == EL_QUICKSAND_EMPTYING)
8451 Feld[x][y] = get_next_element(element);
8452 element = Feld[newx][newy] = Store[x][y];
8454 else if (element == EL_QUICKSAND_FAST_FILLING)
8456 element = Feld[newx][newy] = get_next_element(element);
8457 Store[newx][newy] = Store[x][y];
8459 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8461 Feld[x][y] = get_next_element(element);
8462 element = Feld[newx][newy] = Store[x][y];
8464 else if (element == EL_MAGIC_WALL_FILLING)
8466 element = Feld[newx][newy] = get_next_element(element);
8467 if (!game.magic_wall_active)
8468 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8469 Store[newx][newy] = Store[x][y];
8471 else if (element == EL_MAGIC_WALL_EMPTYING)
8473 Feld[x][y] = get_next_element(element);
8474 if (!game.magic_wall_active)
8475 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8476 element = Feld[newx][newy] = Store[x][y];
8478 InitField(newx, newy, FALSE);
8480 else if (element == EL_BD_MAGIC_WALL_FILLING)
8482 element = Feld[newx][newy] = get_next_element(element);
8483 if (!game.magic_wall_active)
8484 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8485 Store[newx][newy] = Store[x][y];
8487 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8489 Feld[x][y] = get_next_element(element);
8490 if (!game.magic_wall_active)
8491 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8492 element = Feld[newx][newy] = Store[x][y];
8494 InitField(newx, newy, FALSE);
8496 else if (element == EL_DC_MAGIC_WALL_FILLING)
8498 element = Feld[newx][newy] = get_next_element(element);
8499 if (!game.magic_wall_active)
8500 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8501 Store[newx][newy] = Store[x][y];
8503 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8505 Feld[x][y] = get_next_element(element);
8506 if (!game.magic_wall_active)
8507 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8508 element = Feld[newx][newy] = Store[x][y];
8510 InitField(newx, newy, FALSE);
8512 else if (element == EL_AMOEBA_DROPPING)
8514 Feld[x][y] = get_next_element(element);
8515 element = Feld[newx][newy] = Store[x][y];
8517 else if (element == EL_SOKOBAN_OBJECT)
8520 Feld[x][y] = Back[x][y];
8522 if (Back[newx][newy])
8523 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8525 Back[x][y] = Back[newx][newy] = 0;
8528 Store[x][y] = EL_EMPTY;
8533 MovDelay[newx][newy] = 0;
8535 if (CAN_CHANGE_OR_HAS_ACTION(element))
8537 // copy element change control values to new field
8538 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8539 ChangePage[newx][newy] = ChangePage[x][y];
8540 ChangeCount[newx][newy] = ChangeCount[x][y];
8541 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8544 CustomValue[newx][newy] = CustomValue[x][y];
8546 ChangeDelay[x][y] = 0;
8547 ChangePage[x][y] = -1;
8548 ChangeCount[x][y] = 0;
8549 ChangeEvent[x][y] = -1;
8551 CustomValue[x][y] = 0;
8553 // copy animation control values to new field
8554 GfxFrame[newx][newy] = GfxFrame[x][y];
8555 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8556 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8557 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8559 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8561 // some elements can leave other elements behind after moving
8562 if (ei->move_leave_element != EL_EMPTY &&
8563 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8564 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8566 int move_leave_element = ei->move_leave_element;
8568 // this makes it possible to leave the removed element again
8569 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8570 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8572 Feld[x][y] = move_leave_element;
8574 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8575 MovDir[x][y] = direction;
8577 InitField(x, y, FALSE);
8579 if (GFX_CRUMBLED(Feld[x][y]))
8580 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8582 if (ELEM_IS_PLAYER(move_leave_element))
8583 RelocatePlayer(x, y, move_leave_element);
8586 // do this after checking for left-behind element
8587 ResetGfxAnimation(x, y); // reset animation values for old field
8589 if (!CAN_MOVE(element) ||
8590 (CAN_FALL(element) && direction == MV_DOWN &&
8591 (element == EL_SPRING ||
8592 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8593 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8594 GfxDir[x][y] = MovDir[newx][newy] = 0;
8596 TEST_DrawLevelField(x, y);
8597 TEST_DrawLevelField(newx, newy);
8599 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8601 // prevent pushed element from moving on in pushed direction
8602 if (pushed_by_player && CAN_MOVE(element) &&
8603 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8604 !(element_info[element].move_pattern & direction))
8605 TurnRound(newx, newy);
8607 // prevent elements on conveyor belt from moving on in last direction
8608 if (pushed_by_conveyor && CAN_FALL(element) &&
8609 direction & MV_HORIZONTAL)
8610 MovDir[newx][newy] = 0;
8612 if (!pushed_by_player)
8614 int nextx = newx + dx, nexty = newy + dy;
8615 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8617 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8619 if (CAN_FALL(element) && direction == MV_DOWN)
8620 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8622 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8623 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8625 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8626 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8629 if (DONT_TOUCH(element)) // object may be nasty to player or others
8631 TestIfBadThingTouchesPlayer(newx, newy);
8632 TestIfBadThingTouchesFriend(newx, newy);
8634 if (!IS_CUSTOM_ELEMENT(element))
8635 TestIfBadThingTouchesOtherBadThing(newx, newy);
8637 else if (element == EL_PENGUIN)
8638 TestIfFriendTouchesBadThing(newx, newy);
8640 if (DONT_GET_HIT_BY(element))
8642 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8645 // give the player one last chance (one more frame) to move away
8646 if (CAN_FALL(element) && direction == MV_DOWN &&
8647 (last_line || (!IS_FREE(x, newy + 1) &&
8648 (!IS_PLAYER(x, newy + 1) ||
8649 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8652 if (pushed_by_player && !game.use_change_when_pushing_bug)
8654 int push_side = MV_DIR_OPPOSITE(direction);
8655 struct PlayerInfo *player = PLAYERINFO(x, y);
8657 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8658 player->index_bit, push_side);
8659 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8660 player->index_bit, push_side);
8663 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8664 MovDelay[newx][newy] = 1;
8666 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8668 TestIfElementTouchesCustomElement(x, y); // empty or new element
8669 TestIfElementHitsCustomElement(newx, newy, direction);
8670 TestIfPlayerTouchesCustomElement(newx, newy);
8671 TestIfElementTouchesCustomElement(newx, newy);
8673 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8674 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8675 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8676 MV_DIR_OPPOSITE(direction));
8679 int AmoebeNachbarNr(int ax, int ay)
8682 int element = Feld[ax][ay];
8684 static int xy[4][2] =
8692 for (i = 0; i < NUM_DIRECTIONS; i++)
8694 int x = ax + xy[i][0];
8695 int y = ay + xy[i][1];
8697 if (!IN_LEV_FIELD(x, y))
8700 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8701 group_nr = AmoebaNr[x][y];
8707 static void AmoebenVereinigen(int ax, int ay)
8709 int i, x, y, xx, yy;
8710 int new_group_nr = AmoebaNr[ax][ay];
8711 static int xy[4][2] =
8719 if (new_group_nr == 0)
8722 for (i = 0; i < NUM_DIRECTIONS; i++)
8727 if (!IN_LEV_FIELD(x, y))
8730 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8731 Feld[x][y] == EL_BD_AMOEBA ||
8732 Feld[x][y] == EL_AMOEBA_DEAD) &&
8733 AmoebaNr[x][y] != new_group_nr)
8735 int old_group_nr = AmoebaNr[x][y];
8737 if (old_group_nr == 0)
8740 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8741 AmoebaCnt[old_group_nr] = 0;
8742 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8743 AmoebaCnt2[old_group_nr] = 0;
8745 SCAN_PLAYFIELD(xx, yy)
8747 if (AmoebaNr[xx][yy] == old_group_nr)
8748 AmoebaNr[xx][yy] = new_group_nr;
8754 void AmoebeUmwandeln(int ax, int ay)
8758 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8760 int group_nr = AmoebaNr[ax][ay];
8765 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8766 printf("AmoebeUmwandeln(): This should never happen!\n");
8771 SCAN_PLAYFIELD(x, y)
8773 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8776 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8780 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8781 SND_AMOEBA_TURNING_TO_GEM :
8782 SND_AMOEBA_TURNING_TO_ROCK));
8787 static int xy[4][2] =
8795 for (i = 0; i < NUM_DIRECTIONS; i++)
8800 if (!IN_LEV_FIELD(x, y))
8803 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8805 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8806 SND_AMOEBA_TURNING_TO_GEM :
8807 SND_AMOEBA_TURNING_TO_ROCK));
8814 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8817 int group_nr = AmoebaNr[ax][ay];
8818 boolean done = FALSE;
8823 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8824 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8829 SCAN_PLAYFIELD(x, y)
8831 if (AmoebaNr[x][y] == group_nr &&
8832 (Feld[x][y] == EL_AMOEBA_DEAD ||
8833 Feld[x][y] == EL_BD_AMOEBA ||
8834 Feld[x][y] == EL_AMOEBA_GROWING))
8837 Feld[x][y] = new_element;
8838 InitField(x, y, FALSE);
8839 TEST_DrawLevelField(x, y);
8845 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8846 SND_BD_AMOEBA_TURNING_TO_ROCK :
8847 SND_BD_AMOEBA_TURNING_TO_GEM));
8850 static void AmoebeWaechst(int x, int y)
8852 static unsigned int sound_delay = 0;
8853 static unsigned int sound_delay_value = 0;
8855 if (!MovDelay[x][y]) // start new growing cycle
8859 if (DelayReached(&sound_delay, sound_delay_value))
8861 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8862 sound_delay_value = 30;
8866 if (MovDelay[x][y]) // wait some time before growing bigger
8869 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8871 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8872 6 - MovDelay[x][y]);
8874 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8877 if (!MovDelay[x][y])
8879 Feld[x][y] = Store[x][y];
8881 TEST_DrawLevelField(x, y);
8886 static void AmoebaDisappearing(int x, int y)
8888 static unsigned int sound_delay = 0;
8889 static unsigned int sound_delay_value = 0;
8891 if (!MovDelay[x][y]) // start new shrinking cycle
8895 if (DelayReached(&sound_delay, sound_delay_value))
8896 sound_delay_value = 30;
8899 if (MovDelay[x][y]) // wait some time before shrinking
8902 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8904 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8905 6 - MovDelay[x][y]);
8907 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8910 if (!MovDelay[x][y])
8912 Feld[x][y] = EL_EMPTY;
8913 TEST_DrawLevelField(x, y);
8915 // don't let mole enter this field in this cycle;
8916 // (give priority to objects falling to this field from above)
8922 static void AmoebeAbleger(int ax, int ay)
8925 int element = Feld[ax][ay];
8926 int graphic = el2img(element);
8927 int newax = ax, neway = ay;
8928 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8929 static int xy[4][2] =
8937 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8939 Feld[ax][ay] = EL_AMOEBA_DEAD;
8940 TEST_DrawLevelField(ax, ay);
8944 if (IS_ANIMATED(graphic))
8945 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8947 if (!MovDelay[ax][ay]) // start making new amoeba field
8948 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8950 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8953 if (MovDelay[ax][ay])
8957 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8960 int x = ax + xy[start][0];
8961 int y = ay + xy[start][1];
8963 if (!IN_LEV_FIELD(x, y))
8966 if (IS_FREE(x, y) ||
8967 CAN_GROW_INTO(Feld[x][y]) ||
8968 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8969 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8975 if (newax == ax && neway == ay)
8978 else // normal or "filled" (BD style) amoeba
8981 boolean waiting_for_player = FALSE;
8983 for (i = 0; i < NUM_DIRECTIONS; i++)
8985 int j = (start + i) % 4;
8986 int x = ax + xy[j][0];
8987 int y = ay + xy[j][1];
8989 if (!IN_LEV_FIELD(x, y))
8992 if (IS_FREE(x, y) ||
8993 CAN_GROW_INTO(Feld[x][y]) ||
8994 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8995 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
9001 else if (IS_PLAYER(x, y))
9002 waiting_for_player = TRUE;
9005 if (newax == ax && neway == ay) // amoeba cannot grow
9007 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9009 Feld[ax][ay] = EL_AMOEBA_DEAD;
9010 TEST_DrawLevelField(ax, ay);
9011 AmoebaCnt[AmoebaNr[ax][ay]]--;
9013 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9015 if (element == EL_AMOEBA_FULL)
9016 AmoebeUmwandeln(ax, ay);
9017 else if (element == EL_BD_AMOEBA)
9018 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
9023 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9025 // amoeba gets larger by growing in some direction
9027 int new_group_nr = AmoebaNr[ax][ay];
9030 if (new_group_nr == 0)
9032 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
9033 printf("AmoebeAbleger(): This should never happen!\n");
9038 AmoebaNr[newax][neway] = new_group_nr;
9039 AmoebaCnt[new_group_nr]++;
9040 AmoebaCnt2[new_group_nr]++;
9042 // if amoeba touches other amoeba(s) after growing, unify them
9043 AmoebenVereinigen(newax, neway);
9045 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9047 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
9053 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9054 (neway == lev_fieldy - 1 && newax != ax))
9056 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9057 Store[newax][neway] = element;
9059 else if (neway == ay || element == EL_EMC_DRIPPER)
9061 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9063 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9067 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9068 Feld[ax][ay] = EL_AMOEBA_DROPPING;
9069 Store[ax][ay] = EL_AMOEBA_DROP;
9070 ContinueMoving(ax, ay);
9074 TEST_DrawLevelField(newax, neway);
9077 static void Life(int ax, int ay)
9081 int element = Feld[ax][ay];
9082 int graphic = el2img(element);
9083 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9085 boolean changed = FALSE;
9087 if (IS_ANIMATED(graphic))
9088 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9093 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9094 MovDelay[ax][ay] = life_time;
9096 if (MovDelay[ax][ay]) // wait some time before next cycle
9099 if (MovDelay[ax][ay])
9103 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9105 int xx = ax+x1, yy = ay+y1;
9106 int old_element = Feld[xx][yy];
9107 int num_neighbours = 0;
9109 if (!IN_LEV_FIELD(xx, yy))
9112 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9114 int x = xx+x2, y = yy+y2;
9116 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9119 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9120 boolean is_neighbour = FALSE;
9122 if (level.use_life_bugs)
9124 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9125 (IS_FREE(x, y) && Stop[x][y]));
9128 (Last[x][y] == element || is_player_cell);
9134 boolean is_free = FALSE;
9136 if (level.use_life_bugs)
9137 is_free = (IS_FREE(xx, yy));
9139 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9141 if (xx == ax && yy == ay) // field in the middle
9143 if (num_neighbours < life_parameter[0] ||
9144 num_neighbours > life_parameter[1])
9146 Feld[xx][yy] = EL_EMPTY;
9147 if (Feld[xx][yy] != old_element)
9148 TEST_DrawLevelField(xx, yy);
9149 Stop[xx][yy] = TRUE;
9153 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9154 { // free border field
9155 if (num_neighbours >= life_parameter[2] &&
9156 num_neighbours <= life_parameter[3])
9158 Feld[xx][yy] = element;
9159 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9160 if (Feld[xx][yy] != old_element)
9161 TEST_DrawLevelField(xx, yy);
9162 Stop[xx][yy] = TRUE;
9169 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9170 SND_GAME_OF_LIFE_GROWING);
9173 static void InitRobotWheel(int x, int y)
9175 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9178 static void RunRobotWheel(int x, int y)
9180 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9183 static void StopRobotWheel(int x, int y)
9185 if (game.robot_wheel_x == x &&
9186 game.robot_wheel_y == y)
9188 game.robot_wheel_x = -1;
9189 game.robot_wheel_y = -1;
9190 game.robot_wheel_active = FALSE;
9194 static void InitTimegateWheel(int x, int y)
9196 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9199 static void RunTimegateWheel(int x, int y)
9201 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9204 static void InitMagicBallDelay(int x, int y)
9206 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9209 static void ActivateMagicBall(int bx, int by)
9213 if (level.ball_random)
9215 int pos_border = RND(8); // select one of the eight border elements
9216 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9217 int xx = pos_content % 3;
9218 int yy = pos_content / 3;
9223 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9224 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9228 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9230 int xx = x - bx + 1;
9231 int yy = y - by + 1;
9233 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9234 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9238 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9241 static void CheckExit(int x, int y)
9243 if (game.gems_still_needed > 0 ||
9244 game.sokoban_fields_still_needed > 0 ||
9245 game.sokoban_objects_still_needed > 0 ||
9246 game.lights_still_needed > 0)
9248 int element = Feld[x][y];
9249 int graphic = el2img(element);
9251 if (IS_ANIMATED(graphic))
9252 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9257 // do not re-open exit door closed after last player
9258 if (game.all_players_gone)
9261 Feld[x][y] = EL_EXIT_OPENING;
9263 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9266 static void CheckExitEM(int x, int y)
9268 if (game.gems_still_needed > 0 ||
9269 game.sokoban_fields_still_needed > 0 ||
9270 game.sokoban_objects_still_needed > 0 ||
9271 game.lights_still_needed > 0)
9273 int element = Feld[x][y];
9274 int graphic = el2img(element);
9276 if (IS_ANIMATED(graphic))
9277 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9282 // do not re-open exit door closed after last player
9283 if (game.all_players_gone)
9286 Feld[x][y] = EL_EM_EXIT_OPENING;
9288 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9291 static void CheckExitSteel(int x, int y)
9293 if (game.gems_still_needed > 0 ||
9294 game.sokoban_fields_still_needed > 0 ||
9295 game.sokoban_objects_still_needed > 0 ||
9296 game.lights_still_needed > 0)
9298 int element = Feld[x][y];
9299 int graphic = el2img(element);
9301 if (IS_ANIMATED(graphic))
9302 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9307 // do not re-open exit door closed after last player
9308 if (game.all_players_gone)
9311 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9313 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9316 static void CheckExitSteelEM(int x, int y)
9318 if (game.gems_still_needed > 0 ||
9319 game.sokoban_fields_still_needed > 0 ||
9320 game.sokoban_objects_still_needed > 0 ||
9321 game.lights_still_needed > 0)
9323 int element = Feld[x][y];
9324 int graphic = el2img(element);
9326 if (IS_ANIMATED(graphic))
9327 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9332 // do not re-open exit door closed after last player
9333 if (game.all_players_gone)
9336 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9338 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9341 static void CheckExitSP(int x, int y)
9343 if (game.gems_still_needed > 0)
9345 int element = Feld[x][y];
9346 int graphic = el2img(element);
9348 if (IS_ANIMATED(graphic))
9349 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9354 // do not re-open exit door closed after last player
9355 if (game.all_players_gone)
9358 Feld[x][y] = EL_SP_EXIT_OPENING;
9360 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9363 static void CloseAllOpenTimegates(void)
9367 SCAN_PLAYFIELD(x, y)
9369 int element = Feld[x][y];
9371 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9373 Feld[x][y] = EL_TIMEGATE_CLOSING;
9375 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9380 static void DrawTwinkleOnField(int x, int y)
9382 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9385 if (Feld[x][y] == EL_BD_DIAMOND)
9388 if (MovDelay[x][y] == 0) // next animation frame
9389 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9391 if (MovDelay[x][y] != 0) // wait some time before next frame
9395 DrawLevelElementAnimation(x, y, Feld[x][y]);
9397 if (MovDelay[x][y] != 0)
9399 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9400 10 - MovDelay[x][y]);
9402 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9407 static void MauerWaechst(int x, int y)
9411 if (!MovDelay[x][y]) // next animation frame
9412 MovDelay[x][y] = 3 * delay;
9414 if (MovDelay[x][y]) // wait some time before next frame
9418 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9420 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9421 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9423 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9426 if (!MovDelay[x][y])
9428 if (MovDir[x][y] == MV_LEFT)
9430 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9431 TEST_DrawLevelField(x - 1, y);
9433 else if (MovDir[x][y] == MV_RIGHT)
9435 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9436 TEST_DrawLevelField(x + 1, y);
9438 else if (MovDir[x][y] == MV_UP)
9440 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9441 TEST_DrawLevelField(x, y - 1);
9445 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9446 TEST_DrawLevelField(x, y + 1);
9449 Feld[x][y] = Store[x][y];
9451 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9452 TEST_DrawLevelField(x, y);
9457 static void MauerAbleger(int ax, int ay)
9459 int element = Feld[ax][ay];
9460 int graphic = el2img(element);
9461 boolean oben_frei = FALSE, unten_frei = FALSE;
9462 boolean links_frei = FALSE, rechts_frei = FALSE;
9463 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9464 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9465 boolean new_wall = FALSE;
9467 if (IS_ANIMATED(graphic))
9468 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9470 if (!MovDelay[ax][ay]) // start building new wall
9471 MovDelay[ax][ay] = 6;
9473 if (MovDelay[ax][ay]) // wait some time before building new wall
9476 if (MovDelay[ax][ay])
9480 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9482 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9484 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9486 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9489 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9490 element == EL_EXPANDABLE_WALL_ANY)
9494 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9495 Store[ax][ay-1] = element;
9496 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9497 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9498 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9499 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9504 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9505 Store[ax][ay+1] = element;
9506 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9507 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9508 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9509 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9514 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9515 element == EL_EXPANDABLE_WALL_ANY ||
9516 element == EL_EXPANDABLE_WALL ||
9517 element == EL_BD_EXPANDABLE_WALL)
9521 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9522 Store[ax-1][ay] = element;
9523 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9524 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9525 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9526 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9532 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9533 Store[ax+1][ay] = element;
9534 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9535 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9536 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9537 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9542 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9543 TEST_DrawLevelField(ax, ay);
9545 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9547 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9548 unten_massiv = TRUE;
9549 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9550 links_massiv = TRUE;
9551 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9552 rechts_massiv = TRUE;
9554 if (((oben_massiv && unten_massiv) ||
9555 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9556 element == EL_EXPANDABLE_WALL) &&
9557 ((links_massiv && rechts_massiv) ||
9558 element == EL_EXPANDABLE_WALL_VERTICAL))
9559 Feld[ax][ay] = EL_WALL;
9562 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9565 static void MauerAblegerStahl(int ax, int ay)
9567 int element = Feld[ax][ay];
9568 int graphic = el2img(element);
9569 boolean oben_frei = FALSE, unten_frei = FALSE;
9570 boolean links_frei = FALSE, rechts_frei = FALSE;
9571 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9572 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9573 boolean new_wall = FALSE;
9575 if (IS_ANIMATED(graphic))
9576 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9578 if (!MovDelay[ax][ay]) // start building new wall
9579 MovDelay[ax][ay] = 6;
9581 if (MovDelay[ax][ay]) // wait some time before building new wall
9584 if (MovDelay[ax][ay])
9588 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9590 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9592 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9594 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9597 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9598 element == EL_EXPANDABLE_STEELWALL_ANY)
9602 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9603 Store[ax][ay-1] = element;
9604 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9605 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9606 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9607 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9612 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9613 Store[ax][ay+1] = element;
9614 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9615 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9616 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9617 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9622 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9623 element == EL_EXPANDABLE_STEELWALL_ANY)
9627 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9628 Store[ax-1][ay] = element;
9629 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9630 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9631 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9632 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9638 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9639 Store[ax+1][ay] = element;
9640 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9641 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9642 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9643 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9648 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9650 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9651 unten_massiv = TRUE;
9652 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9653 links_massiv = TRUE;
9654 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9655 rechts_massiv = TRUE;
9657 if (((oben_massiv && unten_massiv) ||
9658 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9659 ((links_massiv && rechts_massiv) ||
9660 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9661 Feld[ax][ay] = EL_STEELWALL;
9664 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9667 static void CheckForDragon(int x, int y)
9670 boolean dragon_found = FALSE;
9671 static int xy[4][2] =
9679 for (i = 0; i < NUM_DIRECTIONS; i++)
9681 for (j = 0; j < 4; j++)
9683 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9685 if (IN_LEV_FIELD(xx, yy) &&
9686 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9688 if (Feld[xx][yy] == EL_DRAGON)
9689 dragon_found = TRUE;
9698 for (i = 0; i < NUM_DIRECTIONS; i++)
9700 for (j = 0; j < 3; j++)
9702 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9704 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9706 Feld[xx][yy] = EL_EMPTY;
9707 TEST_DrawLevelField(xx, yy);
9716 static void InitBuggyBase(int x, int y)
9718 int element = Feld[x][y];
9719 int activating_delay = FRAMES_PER_SECOND / 4;
9722 (element == EL_SP_BUGGY_BASE ?
9723 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9724 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9726 element == EL_SP_BUGGY_BASE_ACTIVE ?
9727 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9730 static void WarnBuggyBase(int x, int y)
9733 static int xy[4][2] =
9741 for (i = 0; i < NUM_DIRECTIONS; i++)
9743 int xx = x + xy[i][0];
9744 int yy = y + xy[i][1];
9746 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9748 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9755 static void InitTrap(int x, int y)
9757 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9760 static void ActivateTrap(int x, int y)
9762 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9765 static void ChangeActiveTrap(int x, int y)
9767 int graphic = IMG_TRAP_ACTIVE;
9769 // if new animation frame was drawn, correct crumbled sand border
9770 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9771 TEST_DrawLevelFieldCrumbled(x, y);
9774 static int getSpecialActionElement(int element, int number, int base_element)
9776 return (element != EL_EMPTY ? element :
9777 number != -1 ? base_element + number - 1 :
9781 static int getModifiedActionNumber(int value_old, int operator, int operand,
9782 int value_min, int value_max)
9784 int value_new = (operator == CA_MODE_SET ? operand :
9785 operator == CA_MODE_ADD ? value_old + operand :
9786 operator == CA_MODE_SUBTRACT ? value_old - operand :
9787 operator == CA_MODE_MULTIPLY ? value_old * operand :
9788 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9789 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9792 return (value_new < value_min ? value_min :
9793 value_new > value_max ? value_max :
9797 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9799 struct ElementInfo *ei = &element_info[element];
9800 struct ElementChangeInfo *change = &ei->change_page[page];
9801 int target_element = change->target_element;
9802 int action_type = change->action_type;
9803 int action_mode = change->action_mode;
9804 int action_arg = change->action_arg;
9805 int action_element = change->action_element;
9808 if (!change->has_action)
9811 // ---------- determine action paramater values -----------------------------
9813 int level_time_value =
9814 (level.time > 0 ? TimeLeft :
9817 int action_arg_element_raw =
9818 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9819 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9820 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9821 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9822 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9823 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9824 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9826 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9828 int action_arg_direction =
9829 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9830 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9831 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9832 change->actual_trigger_side :
9833 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9834 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9837 int action_arg_number_min =
9838 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9841 int action_arg_number_max =
9842 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9843 action_type == CA_SET_LEVEL_GEMS ? 999 :
9844 action_type == CA_SET_LEVEL_TIME ? 9999 :
9845 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9846 action_type == CA_SET_CE_VALUE ? 9999 :
9847 action_type == CA_SET_CE_SCORE ? 9999 :
9850 int action_arg_number_reset =
9851 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9852 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9853 action_type == CA_SET_LEVEL_TIME ? level.time :
9854 action_type == CA_SET_LEVEL_SCORE ? 0 :
9855 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9856 action_type == CA_SET_CE_SCORE ? 0 :
9859 int action_arg_number =
9860 (action_arg <= CA_ARG_MAX ? action_arg :
9861 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9862 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9863 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9864 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9865 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9866 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9867 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9868 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9869 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9870 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9871 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9872 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9873 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9874 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9875 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9876 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9877 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9878 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9879 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9880 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9881 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9884 int action_arg_number_old =
9885 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9886 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9887 action_type == CA_SET_LEVEL_SCORE ? game.score :
9888 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9889 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9892 int action_arg_number_new =
9893 getModifiedActionNumber(action_arg_number_old,
9894 action_mode, action_arg_number,
9895 action_arg_number_min, action_arg_number_max);
9897 int trigger_player_bits =
9898 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9899 change->actual_trigger_player_bits : change->trigger_player);
9901 int action_arg_player_bits =
9902 (action_arg >= CA_ARG_PLAYER_1 &&
9903 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9904 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9905 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9908 // ---------- execute action -----------------------------------------------
9910 switch (action_type)
9917 // ---------- level actions ----------------------------------------------
9919 case CA_RESTART_LEVEL:
9921 game.restart_level = TRUE;
9926 case CA_SHOW_ENVELOPE:
9928 int element = getSpecialActionElement(action_arg_element,
9929 action_arg_number, EL_ENVELOPE_1);
9931 if (IS_ENVELOPE(element))
9932 local_player->show_envelope = element;
9937 case CA_SET_LEVEL_TIME:
9939 if (level.time > 0) // only modify limited time value
9941 TimeLeft = action_arg_number_new;
9943 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9945 DisplayGameControlValues();
9947 if (!TimeLeft && setup.time_limit)
9948 for (i = 0; i < MAX_PLAYERS; i++)
9949 KillPlayer(&stored_player[i]);
9955 case CA_SET_LEVEL_SCORE:
9957 game.score = action_arg_number_new;
9959 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9961 DisplayGameControlValues();
9966 case CA_SET_LEVEL_GEMS:
9968 game.gems_still_needed = action_arg_number_new;
9970 game.snapshot.collected_item = TRUE;
9972 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9974 DisplayGameControlValues();
9979 case CA_SET_LEVEL_WIND:
9981 game.wind_direction = action_arg_direction;
9986 case CA_SET_LEVEL_RANDOM_SEED:
9988 // ensure that setting a new random seed while playing is predictable
9989 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9994 // ---------- player actions ---------------------------------------------
9996 case CA_MOVE_PLAYER:
9997 case CA_MOVE_PLAYER_NEW:
9999 // automatically move to the next field in specified direction
10000 for (i = 0; i < MAX_PLAYERS; i++)
10001 if (trigger_player_bits & (1 << i))
10002 if (action_type == CA_MOVE_PLAYER ||
10003 stored_player[i].MovPos == 0)
10004 stored_player[i].programmed_action = action_arg_direction;
10009 case CA_EXIT_PLAYER:
10011 for (i = 0; i < MAX_PLAYERS; i++)
10012 if (action_arg_player_bits & (1 << i))
10013 ExitPlayer(&stored_player[i]);
10015 if (game.players_still_needed == 0)
10021 case CA_KILL_PLAYER:
10023 for (i = 0; i < MAX_PLAYERS; i++)
10024 if (action_arg_player_bits & (1 << i))
10025 KillPlayer(&stored_player[i]);
10030 case CA_SET_PLAYER_KEYS:
10032 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10033 int element = getSpecialActionElement(action_arg_element,
10034 action_arg_number, EL_KEY_1);
10036 if (IS_KEY(element))
10038 for (i = 0; i < MAX_PLAYERS; i++)
10040 if (trigger_player_bits & (1 << i))
10042 stored_player[i].key[KEY_NR(element)] = key_state;
10044 DrawGameDoorValues();
10052 case CA_SET_PLAYER_SPEED:
10054 for (i = 0; i < MAX_PLAYERS; i++)
10056 if (trigger_player_bits & (1 << i))
10058 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10060 if (action_arg == CA_ARG_SPEED_FASTER &&
10061 stored_player[i].cannot_move)
10063 action_arg_number = STEPSIZE_VERY_SLOW;
10065 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10066 action_arg == CA_ARG_SPEED_FASTER)
10068 action_arg_number = 2;
10069 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10072 else if (action_arg == CA_ARG_NUMBER_RESET)
10074 action_arg_number = level.initial_player_stepsize[i];
10078 getModifiedActionNumber(move_stepsize,
10081 action_arg_number_min,
10082 action_arg_number_max);
10084 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10091 case CA_SET_PLAYER_SHIELD:
10093 for (i = 0; i < MAX_PLAYERS; i++)
10095 if (trigger_player_bits & (1 << i))
10097 if (action_arg == CA_ARG_SHIELD_OFF)
10099 stored_player[i].shield_normal_time_left = 0;
10100 stored_player[i].shield_deadly_time_left = 0;
10102 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10104 stored_player[i].shield_normal_time_left = 999999;
10106 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10108 stored_player[i].shield_normal_time_left = 999999;
10109 stored_player[i].shield_deadly_time_left = 999999;
10117 case CA_SET_PLAYER_GRAVITY:
10119 for (i = 0; i < MAX_PLAYERS; i++)
10121 if (trigger_player_bits & (1 << i))
10123 stored_player[i].gravity =
10124 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10125 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10126 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10127 stored_player[i].gravity);
10134 case CA_SET_PLAYER_ARTWORK:
10136 for (i = 0; i < MAX_PLAYERS; i++)
10138 if (trigger_player_bits & (1 << i))
10140 int artwork_element = action_arg_element;
10142 if (action_arg == CA_ARG_ELEMENT_RESET)
10144 (level.use_artwork_element[i] ? level.artwork_element[i] :
10145 stored_player[i].element_nr);
10147 if (stored_player[i].artwork_element != artwork_element)
10148 stored_player[i].Frame = 0;
10150 stored_player[i].artwork_element = artwork_element;
10152 SetPlayerWaiting(&stored_player[i], FALSE);
10154 // set number of special actions for bored and sleeping animation
10155 stored_player[i].num_special_action_bored =
10156 get_num_special_action(artwork_element,
10157 ACTION_BORING_1, ACTION_BORING_LAST);
10158 stored_player[i].num_special_action_sleeping =
10159 get_num_special_action(artwork_element,
10160 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10167 case CA_SET_PLAYER_INVENTORY:
10169 for (i = 0; i < MAX_PLAYERS; i++)
10171 struct PlayerInfo *player = &stored_player[i];
10174 if (trigger_player_bits & (1 << i))
10176 int inventory_element = action_arg_element;
10178 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10179 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10180 action_arg == CA_ARG_ELEMENT_ACTION)
10182 int element = inventory_element;
10183 int collect_count = element_info[element].collect_count_initial;
10185 if (!IS_CUSTOM_ELEMENT(element))
10188 if (collect_count == 0)
10189 player->inventory_infinite_element = element;
10191 for (k = 0; k < collect_count; k++)
10192 if (player->inventory_size < MAX_INVENTORY_SIZE)
10193 player->inventory_element[player->inventory_size++] =
10196 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10197 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10198 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10200 if (player->inventory_infinite_element != EL_UNDEFINED &&
10201 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10202 action_arg_element_raw))
10203 player->inventory_infinite_element = EL_UNDEFINED;
10205 for (k = 0, j = 0; j < player->inventory_size; j++)
10207 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10208 action_arg_element_raw))
10209 player->inventory_element[k++] = player->inventory_element[j];
10212 player->inventory_size = k;
10214 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10216 if (player->inventory_size > 0)
10218 for (j = 0; j < player->inventory_size - 1; j++)
10219 player->inventory_element[j] = player->inventory_element[j + 1];
10221 player->inventory_size--;
10224 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10226 if (player->inventory_size > 0)
10227 player->inventory_size--;
10229 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10231 player->inventory_infinite_element = EL_UNDEFINED;
10232 player->inventory_size = 0;
10234 else if (action_arg == CA_ARG_INVENTORY_RESET)
10236 player->inventory_infinite_element = EL_UNDEFINED;
10237 player->inventory_size = 0;
10239 if (level.use_initial_inventory[i])
10241 for (j = 0; j < level.initial_inventory_size[i]; j++)
10243 int element = level.initial_inventory_content[i][j];
10244 int collect_count = element_info[element].collect_count_initial;
10246 if (!IS_CUSTOM_ELEMENT(element))
10249 if (collect_count == 0)
10250 player->inventory_infinite_element = element;
10252 for (k = 0; k < collect_count; k++)
10253 if (player->inventory_size < MAX_INVENTORY_SIZE)
10254 player->inventory_element[player->inventory_size++] =
10265 // ---------- CE actions -------------------------------------------------
10267 case CA_SET_CE_VALUE:
10269 int last_ce_value = CustomValue[x][y];
10271 CustomValue[x][y] = action_arg_number_new;
10273 if (CustomValue[x][y] != last_ce_value)
10275 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10276 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10278 if (CustomValue[x][y] == 0)
10280 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10281 ChangeCount[x][y] = 0; // allow at least one more change
10283 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10284 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10291 case CA_SET_CE_SCORE:
10293 int last_ce_score = ei->collect_score;
10295 ei->collect_score = action_arg_number_new;
10297 if (ei->collect_score != last_ce_score)
10299 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10300 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10302 if (ei->collect_score == 0)
10306 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10307 ChangeCount[x][y] = 0; // allow at least one more change
10309 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10310 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10313 This is a very special case that seems to be a mixture between
10314 CheckElementChange() and CheckTriggeredElementChange(): while
10315 the first one only affects single elements that are triggered
10316 directly, the second one affects multiple elements in the playfield
10317 that are triggered indirectly by another element. This is a third
10318 case: Changing the CE score always affects multiple identical CEs,
10319 so every affected CE must be checked, not only the single CE for
10320 which the CE score was changed in the first place (as every instance
10321 of that CE shares the same CE score, and therefore also can change)!
10323 SCAN_PLAYFIELD(xx, yy)
10325 if (Feld[xx][yy] == element)
10326 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10327 CE_SCORE_GETS_ZERO);
10335 case CA_SET_CE_ARTWORK:
10337 int artwork_element = action_arg_element;
10338 boolean reset_frame = FALSE;
10341 if (action_arg == CA_ARG_ELEMENT_RESET)
10342 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10345 if (ei->gfx_element != artwork_element)
10346 reset_frame = TRUE;
10348 ei->gfx_element = artwork_element;
10350 SCAN_PLAYFIELD(xx, yy)
10352 if (Feld[xx][yy] == element)
10356 ResetGfxAnimation(xx, yy);
10357 ResetRandomAnimationValue(xx, yy);
10360 TEST_DrawLevelField(xx, yy);
10367 // ---------- engine actions ---------------------------------------------
10369 case CA_SET_ENGINE_SCAN_MODE:
10371 InitPlayfieldScanMode(action_arg);
10381 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10383 int old_element = Feld[x][y];
10384 int new_element = GetElementFromGroupElement(element);
10385 int previous_move_direction = MovDir[x][y];
10386 int last_ce_value = CustomValue[x][y];
10387 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10388 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10389 boolean add_player_onto_element = (new_element_is_player &&
10390 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10391 IS_WALKABLE(old_element));
10393 if (!add_player_onto_element)
10395 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10396 RemoveMovingField(x, y);
10400 Feld[x][y] = new_element;
10402 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10403 MovDir[x][y] = previous_move_direction;
10405 if (element_info[new_element].use_last_ce_value)
10406 CustomValue[x][y] = last_ce_value;
10408 InitField_WithBug1(x, y, FALSE);
10410 new_element = Feld[x][y]; // element may have changed
10412 ResetGfxAnimation(x, y);
10413 ResetRandomAnimationValue(x, y);
10415 TEST_DrawLevelField(x, y);
10417 if (GFX_CRUMBLED(new_element))
10418 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10421 // check if element under the player changes from accessible to unaccessible
10422 // (needed for special case of dropping element which then changes)
10423 // (must be checked after creating new element for walkable group elements)
10424 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10425 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10432 // "ChangeCount" not set yet to allow "entered by player" change one time
10433 if (new_element_is_player)
10434 RelocatePlayer(x, y, new_element);
10437 ChangeCount[x][y]++; // count number of changes in the same frame
10439 TestIfBadThingTouchesPlayer(x, y);
10440 TestIfPlayerTouchesCustomElement(x, y);
10441 TestIfElementTouchesCustomElement(x, y);
10444 static void CreateField(int x, int y, int element)
10446 CreateFieldExt(x, y, element, FALSE);
10449 static void CreateElementFromChange(int x, int y, int element)
10451 element = GET_VALID_RUNTIME_ELEMENT(element);
10453 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10455 int old_element = Feld[x][y];
10457 // prevent changed element from moving in same engine frame
10458 // unless both old and new element can either fall or move
10459 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10460 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10464 CreateFieldExt(x, y, element, TRUE);
10467 static boolean ChangeElement(int x, int y, int element, int page)
10469 struct ElementInfo *ei = &element_info[element];
10470 struct ElementChangeInfo *change = &ei->change_page[page];
10471 int ce_value = CustomValue[x][y];
10472 int ce_score = ei->collect_score;
10473 int target_element;
10474 int old_element = Feld[x][y];
10476 // always use default change event to prevent running into a loop
10477 if (ChangeEvent[x][y] == -1)
10478 ChangeEvent[x][y] = CE_DELAY;
10480 if (ChangeEvent[x][y] == CE_DELAY)
10482 // reset actual trigger element, trigger player and action element
10483 change->actual_trigger_element = EL_EMPTY;
10484 change->actual_trigger_player = EL_EMPTY;
10485 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10486 change->actual_trigger_side = CH_SIDE_NONE;
10487 change->actual_trigger_ce_value = 0;
10488 change->actual_trigger_ce_score = 0;
10491 // do not change elements more than a specified maximum number of changes
10492 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10495 ChangeCount[x][y]++; // count number of changes in the same frame
10497 if (change->explode)
10504 if (change->use_target_content)
10506 boolean complete_replace = TRUE;
10507 boolean can_replace[3][3];
10510 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10513 boolean is_walkable;
10514 boolean is_diggable;
10515 boolean is_collectible;
10516 boolean is_removable;
10517 boolean is_destructible;
10518 int ex = x + xx - 1;
10519 int ey = y + yy - 1;
10520 int content_element = change->target_content.e[xx][yy];
10523 can_replace[xx][yy] = TRUE;
10525 if (ex == x && ey == y) // do not check changing element itself
10528 if (content_element == EL_EMPTY_SPACE)
10530 can_replace[xx][yy] = FALSE; // do not replace border with space
10535 if (!IN_LEV_FIELD(ex, ey))
10537 can_replace[xx][yy] = FALSE;
10538 complete_replace = FALSE;
10545 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10546 e = MovingOrBlocked2Element(ex, ey);
10548 is_empty = (IS_FREE(ex, ey) ||
10549 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10551 is_walkable = (is_empty || IS_WALKABLE(e));
10552 is_diggable = (is_empty || IS_DIGGABLE(e));
10553 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10554 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10555 is_removable = (is_diggable || is_collectible);
10557 can_replace[xx][yy] =
10558 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10559 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10560 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10561 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10562 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10563 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10564 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10566 if (!can_replace[xx][yy])
10567 complete_replace = FALSE;
10570 if (!change->only_if_complete || complete_replace)
10572 boolean something_has_changed = FALSE;
10574 if (change->only_if_complete && change->use_random_replace &&
10575 RND(100) < change->random_percentage)
10578 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10580 int ex = x + xx - 1;
10581 int ey = y + yy - 1;
10582 int content_element;
10584 if (can_replace[xx][yy] && (!change->use_random_replace ||
10585 RND(100) < change->random_percentage))
10587 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10588 RemoveMovingField(ex, ey);
10590 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10592 content_element = change->target_content.e[xx][yy];
10593 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10594 ce_value, ce_score);
10596 CreateElementFromChange(ex, ey, target_element);
10598 something_has_changed = TRUE;
10600 // for symmetry reasons, freeze newly created border elements
10601 if (ex != x || ey != y)
10602 Stop[ex][ey] = TRUE; // no more moving in this frame
10606 if (something_has_changed)
10608 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10609 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10615 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10616 ce_value, ce_score);
10618 if (element == EL_DIAGONAL_GROWING ||
10619 element == EL_DIAGONAL_SHRINKING)
10621 target_element = Store[x][y];
10623 Store[x][y] = EL_EMPTY;
10626 CreateElementFromChange(x, y, target_element);
10628 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10629 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10632 // this uses direct change before indirect change
10633 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10638 static void HandleElementChange(int x, int y, int page)
10640 int element = MovingOrBlocked2Element(x, y);
10641 struct ElementInfo *ei = &element_info[element];
10642 struct ElementChangeInfo *change = &ei->change_page[page];
10643 boolean handle_action_before_change = FALSE;
10646 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10647 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10650 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10651 x, y, element, element_info[element].token_name);
10652 printf("HandleElementChange(): This should never happen!\n");
10657 // this can happen with classic bombs on walkable, changing elements
10658 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10663 if (ChangeDelay[x][y] == 0) // initialize element change
10665 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10667 if (change->can_change)
10669 // !!! not clear why graphic animation should be reset at all here !!!
10670 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10671 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10674 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10676 When using an animation frame delay of 1 (this only happens with
10677 "sp_zonk.moving.left/right" in the classic graphics), the default
10678 (non-moving) animation shows wrong animation frames (while the
10679 moving animation, like "sp_zonk.moving.left/right", is correct,
10680 so this graphical bug never shows up with the classic graphics).
10681 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10682 be drawn instead of the correct frames 0,1,2,3. This is caused by
10683 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10684 an element change: First when the change delay ("ChangeDelay[][]")
10685 counter has reached zero after decrementing, then a second time in
10686 the next frame (after "GfxFrame[][]" was already incremented) when
10687 "ChangeDelay[][]" is reset to the initial delay value again.
10689 This causes frame 0 to be drawn twice, while the last frame won't
10690 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10692 As some animations may already be cleverly designed around this bug
10693 (at least the "Snake Bite" snake tail animation does this), it cannot
10694 simply be fixed here without breaking such existing animations.
10695 Unfortunately, it cannot easily be detected if a graphics set was
10696 designed "before" or "after" the bug was fixed. As a workaround,
10697 a new graphics set option "game.graphics_engine_version" was added
10698 to be able to specify the game's major release version for which the
10699 graphics set was designed, which can then be used to decide if the
10700 bugfix should be used (version 4 and above) or not (version 3 or
10701 below, or if no version was specified at all, as with old sets).
10703 (The wrong/fixed animation frames can be tested with the test level set
10704 "test_gfxframe" and level "000", which contains a specially prepared
10705 custom element at level position (x/y) == (11/9) which uses the zonk
10706 animation mentioned above. Using "game.graphics_engine_version: 4"
10707 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10708 This can also be seen from the debug output for this test element.)
10711 // when a custom element is about to change (for example by change delay),
10712 // do not reset graphic animation when the custom element is moving
10713 if (game.graphics_engine_version < 4 &&
10716 ResetGfxAnimation(x, y);
10717 ResetRandomAnimationValue(x, y);
10720 if (change->pre_change_function)
10721 change->pre_change_function(x, y);
10725 ChangeDelay[x][y]--;
10727 if (ChangeDelay[x][y] != 0) // continue element change
10729 if (change->can_change)
10731 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10733 if (IS_ANIMATED(graphic))
10734 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10736 if (change->change_function)
10737 change->change_function(x, y);
10740 else // finish element change
10742 if (ChangePage[x][y] != -1) // remember page from delayed change
10744 page = ChangePage[x][y];
10745 ChangePage[x][y] = -1;
10747 change = &ei->change_page[page];
10750 if (IS_MOVING(x, y)) // never change a running system ;-)
10752 ChangeDelay[x][y] = 1; // try change after next move step
10753 ChangePage[x][y] = page; // remember page to use for change
10758 // special case: set new level random seed before changing element
10759 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10760 handle_action_before_change = TRUE;
10762 if (change->has_action && handle_action_before_change)
10763 ExecuteCustomElementAction(x, y, element, page);
10765 if (change->can_change)
10767 if (ChangeElement(x, y, element, page))
10769 if (change->post_change_function)
10770 change->post_change_function(x, y);
10774 if (change->has_action && !handle_action_before_change)
10775 ExecuteCustomElementAction(x, y, element, page);
10779 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10780 int trigger_element,
10782 int trigger_player,
10786 boolean change_done_any = FALSE;
10787 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10790 if (!(trigger_events[trigger_element][trigger_event]))
10793 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10795 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10797 int element = EL_CUSTOM_START + i;
10798 boolean change_done = FALSE;
10801 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10802 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10805 for (p = 0; p < element_info[element].num_change_pages; p++)
10807 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10809 if (change->can_change_or_has_action &&
10810 change->has_event[trigger_event] &&
10811 change->trigger_side & trigger_side &&
10812 change->trigger_player & trigger_player &&
10813 change->trigger_page & trigger_page_bits &&
10814 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10816 change->actual_trigger_element = trigger_element;
10817 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10818 change->actual_trigger_player_bits = trigger_player;
10819 change->actual_trigger_side = trigger_side;
10820 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10821 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10823 if ((change->can_change && !change_done) || change->has_action)
10827 SCAN_PLAYFIELD(x, y)
10829 if (Feld[x][y] == element)
10831 if (change->can_change && !change_done)
10833 // if element already changed in this frame, not only prevent
10834 // another element change (checked in ChangeElement()), but
10835 // also prevent additional element actions for this element
10837 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10838 !level.use_action_after_change_bug)
10841 ChangeDelay[x][y] = 1;
10842 ChangeEvent[x][y] = trigger_event;
10844 HandleElementChange(x, y, p);
10846 else if (change->has_action)
10848 // if element already changed in this frame, not only prevent
10849 // another element change (checked in ChangeElement()), but
10850 // also prevent additional element actions for this element
10852 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10853 !level.use_action_after_change_bug)
10856 ExecuteCustomElementAction(x, y, element, p);
10857 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10862 if (change->can_change)
10864 change_done = TRUE;
10865 change_done_any = TRUE;
10872 RECURSION_LOOP_DETECTION_END();
10874 return change_done_any;
10877 static boolean CheckElementChangeExt(int x, int y,
10879 int trigger_element,
10881 int trigger_player,
10884 boolean change_done = FALSE;
10887 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10888 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10891 if (Feld[x][y] == EL_BLOCKED)
10893 Blocked2Moving(x, y, &x, &y);
10894 element = Feld[x][y];
10897 // check if element has already changed or is about to change after moving
10898 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10899 Feld[x][y] != element) ||
10901 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10902 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10903 ChangePage[x][y] != -1)))
10906 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10908 for (p = 0; p < element_info[element].num_change_pages; p++)
10910 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10912 /* check trigger element for all events where the element that is checked
10913 for changing interacts with a directly adjacent element -- this is
10914 different to element changes that affect other elements to change on the
10915 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10916 boolean check_trigger_element =
10917 (trigger_event == CE_TOUCHING_X ||
10918 trigger_event == CE_HITTING_X ||
10919 trigger_event == CE_HIT_BY_X ||
10920 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10922 if (change->can_change_or_has_action &&
10923 change->has_event[trigger_event] &&
10924 change->trigger_side & trigger_side &&
10925 change->trigger_player & trigger_player &&
10926 (!check_trigger_element ||
10927 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10929 change->actual_trigger_element = trigger_element;
10930 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10931 change->actual_trigger_player_bits = trigger_player;
10932 change->actual_trigger_side = trigger_side;
10933 change->actual_trigger_ce_value = CustomValue[x][y];
10934 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10936 // special case: trigger element not at (x,y) position for some events
10937 if (check_trigger_element)
10949 { 0, 0 }, { 0, 0 }, { 0, 0 },
10953 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10954 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10956 change->actual_trigger_ce_value = CustomValue[xx][yy];
10957 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10960 if (change->can_change && !change_done)
10962 ChangeDelay[x][y] = 1;
10963 ChangeEvent[x][y] = trigger_event;
10965 HandleElementChange(x, y, p);
10967 change_done = TRUE;
10969 else if (change->has_action)
10971 ExecuteCustomElementAction(x, y, element, p);
10972 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10977 RECURSION_LOOP_DETECTION_END();
10979 return change_done;
10982 static void PlayPlayerSound(struct PlayerInfo *player)
10984 int jx = player->jx, jy = player->jy;
10985 int sound_element = player->artwork_element;
10986 int last_action = player->last_action_waiting;
10987 int action = player->action_waiting;
10989 if (player->is_waiting)
10991 if (action != last_action)
10992 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10994 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10998 if (action != last_action)
10999 StopSound(element_info[sound_element].sound[last_action]);
11001 if (last_action == ACTION_SLEEPING)
11002 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11006 static void PlayAllPlayersSound(void)
11010 for (i = 0; i < MAX_PLAYERS; i++)
11011 if (stored_player[i].active)
11012 PlayPlayerSound(&stored_player[i]);
11015 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11017 boolean last_waiting = player->is_waiting;
11018 int move_dir = player->MovDir;
11020 player->dir_waiting = move_dir;
11021 player->last_action_waiting = player->action_waiting;
11025 if (!last_waiting) // not waiting -> waiting
11027 player->is_waiting = TRUE;
11029 player->frame_counter_bored =
11031 game.player_boring_delay_fixed +
11032 GetSimpleRandom(game.player_boring_delay_random);
11033 player->frame_counter_sleeping =
11035 game.player_sleeping_delay_fixed +
11036 GetSimpleRandom(game.player_sleeping_delay_random);
11038 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11041 if (game.player_sleeping_delay_fixed +
11042 game.player_sleeping_delay_random > 0 &&
11043 player->anim_delay_counter == 0 &&
11044 player->post_delay_counter == 0 &&
11045 FrameCounter >= player->frame_counter_sleeping)
11046 player->is_sleeping = TRUE;
11047 else if (game.player_boring_delay_fixed +
11048 game.player_boring_delay_random > 0 &&
11049 FrameCounter >= player->frame_counter_bored)
11050 player->is_bored = TRUE;
11052 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11053 player->is_bored ? ACTION_BORING :
11056 if (player->is_sleeping && player->use_murphy)
11058 // special case for sleeping Murphy when leaning against non-free tile
11060 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11061 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
11062 !IS_MOVING(player->jx - 1, player->jy)))
11063 move_dir = MV_LEFT;
11064 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11065 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
11066 !IS_MOVING(player->jx + 1, player->jy)))
11067 move_dir = MV_RIGHT;
11069 player->is_sleeping = FALSE;
11071 player->dir_waiting = move_dir;
11074 if (player->is_sleeping)
11076 if (player->num_special_action_sleeping > 0)
11078 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11080 int last_special_action = player->special_action_sleeping;
11081 int num_special_action = player->num_special_action_sleeping;
11082 int special_action =
11083 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11084 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11085 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11086 last_special_action + 1 : ACTION_SLEEPING);
11087 int special_graphic =
11088 el_act_dir2img(player->artwork_element, special_action, move_dir);
11090 player->anim_delay_counter =
11091 graphic_info[special_graphic].anim_delay_fixed +
11092 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11093 player->post_delay_counter =
11094 graphic_info[special_graphic].post_delay_fixed +
11095 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11097 player->special_action_sleeping = special_action;
11100 if (player->anim_delay_counter > 0)
11102 player->action_waiting = player->special_action_sleeping;
11103 player->anim_delay_counter--;
11105 else if (player->post_delay_counter > 0)
11107 player->post_delay_counter--;
11111 else if (player->is_bored)
11113 if (player->num_special_action_bored > 0)
11115 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11117 int special_action =
11118 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11119 int special_graphic =
11120 el_act_dir2img(player->artwork_element, special_action, move_dir);
11122 player->anim_delay_counter =
11123 graphic_info[special_graphic].anim_delay_fixed +
11124 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11125 player->post_delay_counter =
11126 graphic_info[special_graphic].post_delay_fixed +
11127 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11129 player->special_action_bored = special_action;
11132 if (player->anim_delay_counter > 0)
11134 player->action_waiting = player->special_action_bored;
11135 player->anim_delay_counter--;
11137 else if (player->post_delay_counter > 0)
11139 player->post_delay_counter--;
11144 else if (last_waiting) // waiting -> not waiting
11146 player->is_waiting = FALSE;
11147 player->is_bored = FALSE;
11148 player->is_sleeping = FALSE;
11150 player->frame_counter_bored = -1;
11151 player->frame_counter_sleeping = -1;
11153 player->anim_delay_counter = 0;
11154 player->post_delay_counter = 0;
11156 player->dir_waiting = player->MovDir;
11157 player->action_waiting = ACTION_DEFAULT;
11159 player->special_action_bored = ACTION_DEFAULT;
11160 player->special_action_sleeping = ACTION_DEFAULT;
11164 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11166 if ((!player->is_moving && player->was_moving) ||
11167 (player->MovPos == 0 && player->was_moving) ||
11168 (player->is_snapping && !player->was_snapping) ||
11169 (player->is_dropping && !player->was_dropping))
11171 if (!CheckSaveEngineSnapshotToList())
11174 player->was_moving = FALSE;
11175 player->was_snapping = TRUE;
11176 player->was_dropping = TRUE;
11180 if (player->is_moving)
11181 player->was_moving = TRUE;
11183 if (!player->is_snapping)
11184 player->was_snapping = FALSE;
11186 if (!player->is_dropping)
11187 player->was_dropping = FALSE;
11191 static void CheckSingleStepMode(struct PlayerInfo *player)
11193 if (tape.single_step && tape.recording && !tape.pausing)
11195 /* as it is called "single step mode", just return to pause mode when the
11196 player stopped moving after one tile (or never starts moving at all) */
11197 if (!player->is_moving &&
11198 !player->is_pushing &&
11199 !player->is_dropping_pressed)
11200 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11203 CheckSaveEngineSnapshot(player);
11206 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11208 int left = player_action & JOY_LEFT;
11209 int right = player_action & JOY_RIGHT;
11210 int up = player_action & JOY_UP;
11211 int down = player_action & JOY_DOWN;
11212 int button1 = player_action & JOY_BUTTON_1;
11213 int button2 = player_action & JOY_BUTTON_2;
11214 int dx = (left ? -1 : right ? 1 : 0);
11215 int dy = (up ? -1 : down ? 1 : 0);
11217 if (!player->active || tape.pausing)
11223 SnapField(player, dx, dy);
11227 DropElement(player);
11229 MovePlayer(player, dx, dy);
11232 CheckSingleStepMode(player);
11234 SetPlayerWaiting(player, FALSE);
11236 return player_action;
11240 // no actions for this player (no input at player's configured device)
11242 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11243 SnapField(player, 0, 0);
11244 CheckGravityMovementWhenNotMoving(player);
11246 if (player->MovPos == 0)
11247 SetPlayerWaiting(player, TRUE);
11249 if (player->MovPos == 0) // needed for tape.playing
11250 player->is_moving = FALSE;
11252 player->is_dropping = FALSE;
11253 player->is_dropping_pressed = FALSE;
11254 player->drop_pressed_delay = 0;
11256 CheckSingleStepMode(player);
11262 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11265 if (!tape.use_mouse_actions)
11268 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11269 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11270 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11273 static void SetTapeActionFromMouseAction(byte *tape_action,
11274 struct MouseActionInfo *mouse_action)
11276 if (!tape.use_mouse_actions)
11279 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11280 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11281 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11284 static void CheckLevelSolved(void)
11286 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11288 if (game_em.level_solved &&
11289 !game_em.game_over) // game won
11293 game_em.game_over = TRUE;
11295 game.all_players_gone = TRUE;
11298 if (game_em.game_over) // game lost
11299 game.all_players_gone = TRUE;
11301 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11303 if (game_sp.level_solved &&
11304 !game_sp.game_over) // game won
11308 game_sp.game_over = TRUE;
11310 game.all_players_gone = TRUE;
11313 if (game_sp.game_over) // game lost
11314 game.all_players_gone = TRUE;
11316 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11318 if (game_mm.level_solved &&
11319 !game_mm.game_over) // game won
11323 game_mm.game_over = TRUE;
11325 game.all_players_gone = TRUE;
11328 if (game_mm.game_over) // game lost
11329 game.all_players_gone = TRUE;
11333 static void CheckLevelTime(void)
11337 if (TimeFrames >= FRAMES_PER_SECOND)
11342 for (i = 0; i < MAX_PLAYERS; i++)
11344 struct PlayerInfo *player = &stored_player[i];
11346 if (SHIELD_ON(player))
11348 player->shield_normal_time_left--;
11350 if (player->shield_deadly_time_left > 0)
11351 player->shield_deadly_time_left--;
11355 if (!game.LevelSolved && !level.use_step_counter)
11363 if (TimeLeft <= 10 && setup.time_limit)
11364 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11366 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11367 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11369 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11371 if (!TimeLeft && setup.time_limit)
11373 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11374 game_em.lev->killed_out_of_time = TRUE;
11376 for (i = 0; i < MAX_PLAYERS; i++)
11377 KillPlayer(&stored_player[i]);
11380 else if (game.no_time_limit && !game.all_players_gone)
11382 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11385 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11388 if (tape.recording || tape.playing)
11389 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11392 if (tape.recording || tape.playing)
11393 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11395 UpdateAndDisplayGameControlValues();
11398 void AdvanceFrameAndPlayerCounters(int player_nr)
11402 // advance frame counters (global frame counter and time frame counter)
11406 // advance player counters (counters for move delay, move animation etc.)
11407 for (i = 0; i < MAX_PLAYERS; i++)
11409 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11410 int move_delay_value = stored_player[i].move_delay_value;
11411 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11413 if (!advance_player_counters) // not all players may be affected
11416 if (move_frames == 0) // less than one move per game frame
11418 int stepsize = TILEX / move_delay_value;
11419 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11420 int count = (stored_player[i].is_moving ?
11421 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11423 if (count % delay == 0)
11427 stored_player[i].Frame += move_frames;
11429 if (stored_player[i].MovPos != 0)
11430 stored_player[i].StepFrame += move_frames;
11432 if (stored_player[i].move_delay > 0)
11433 stored_player[i].move_delay--;
11435 // due to bugs in previous versions, counter must count up, not down
11436 if (stored_player[i].push_delay != -1)
11437 stored_player[i].push_delay++;
11439 if (stored_player[i].drop_delay > 0)
11440 stored_player[i].drop_delay--;
11442 if (stored_player[i].is_dropping_pressed)
11443 stored_player[i].drop_pressed_delay++;
11447 void StartGameActions(boolean init_network_game, boolean record_tape,
11450 unsigned int new_random_seed = InitRND(random_seed);
11453 TapeStartRecording(new_random_seed);
11455 if (init_network_game)
11457 SendToServer_LevelFile();
11458 SendToServer_StartPlaying();
11466 static void GameActionsExt(void)
11469 static unsigned int game_frame_delay = 0;
11471 unsigned int game_frame_delay_value;
11472 byte *recorded_player_action;
11473 byte summarized_player_action = 0;
11474 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11477 // detect endless loops, caused by custom element programming
11478 if (recursion_loop_detected && recursion_loop_depth == 0)
11480 char *message = getStringCat3("Internal Error! Element ",
11481 EL_NAME(recursion_loop_element),
11482 " caused endless loop! Quit the game?");
11484 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11485 EL_NAME(recursion_loop_element));
11487 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11489 recursion_loop_detected = FALSE; // if game should be continued
11496 if (game.restart_level)
11497 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11499 CheckLevelSolved();
11501 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11504 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11507 if (game_status != GAME_MODE_PLAYING) // status might have changed
11510 game_frame_delay_value =
11511 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11513 if (tape.playing && tape.warp_forward && !tape.pausing)
11514 game_frame_delay_value = 0;
11516 SetVideoFrameDelay(game_frame_delay_value);
11518 // (de)activate virtual buttons depending on current game status
11519 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11521 if (game.all_players_gone) // if no players there to be controlled anymore
11522 SetOverlayActive(FALSE);
11523 else if (!tape.playing) // if game continues after tape stopped playing
11524 SetOverlayActive(TRUE);
11529 // ---------- main game synchronization point ----------
11531 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11533 printf("::: skip == %d\n", skip);
11536 // ---------- main game synchronization point ----------
11538 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11542 if (network_playing && !network_player_action_received)
11544 // try to get network player actions in time
11546 // last chance to get network player actions without main loop delay
11547 HandleNetworking();
11549 // game was quit by network peer
11550 if (game_status != GAME_MODE_PLAYING)
11553 // check if network player actions still missing and game still running
11554 if (!network_player_action_received && !checkGameEnded())
11555 return; // failed to get network player actions in time
11557 // do not yet reset "network_player_action_received" (for tape.pausing)
11563 // at this point we know that we really continue executing the game
11565 network_player_action_received = FALSE;
11567 // when playing tape, read previously recorded player input from tape data
11568 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11570 local_player->effective_mouse_action = local_player->mouse_action;
11572 if (recorded_player_action != NULL)
11573 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11574 recorded_player_action);
11576 // TapePlayAction() may return NULL when toggling to "pause before death"
11580 if (tape.set_centered_player)
11582 game.centered_player_nr_next = tape.centered_player_nr_next;
11583 game.set_centered_player = TRUE;
11586 for (i = 0; i < MAX_PLAYERS; i++)
11588 summarized_player_action |= stored_player[i].action;
11590 if (!network_playing && (game.team_mode || tape.playing))
11591 stored_player[i].effective_action = stored_player[i].action;
11594 if (network_playing && !checkGameEnded())
11595 SendToServer_MovePlayer(summarized_player_action);
11597 // summarize all actions at local players mapped input device position
11598 // (this allows using different input devices in single player mode)
11599 if (!network.enabled && !game.team_mode)
11600 stored_player[map_player_action[local_player->index_nr]].effective_action =
11601 summarized_player_action;
11603 // summarize all actions at centered player in local team mode
11604 if (tape.recording &&
11605 setup.team_mode && !network.enabled &&
11606 setup.input_on_focus &&
11607 game.centered_player_nr != -1)
11609 for (i = 0; i < MAX_PLAYERS; i++)
11610 stored_player[map_player_action[i]].effective_action =
11611 (i == game.centered_player_nr ? summarized_player_action : 0);
11614 if (recorded_player_action != NULL)
11615 for (i = 0; i < MAX_PLAYERS; i++)
11616 stored_player[i].effective_action = recorded_player_action[i];
11618 for (i = 0; i < MAX_PLAYERS; i++)
11620 tape_action[i] = stored_player[i].effective_action;
11622 /* (this may happen in the RND game engine if a player was not present on
11623 the playfield on level start, but appeared later from a custom element */
11624 if (setup.team_mode &&
11627 !tape.player_participates[i])
11628 tape.player_participates[i] = TRUE;
11631 SetTapeActionFromMouseAction(tape_action,
11632 &local_player->effective_mouse_action);
11634 // only record actions from input devices, but not programmed actions
11635 if (tape.recording)
11636 TapeRecordAction(tape_action);
11638 // remember if game was played (especially after tape stopped playing)
11639 if (!tape.playing && summarized_player_action)
11640 game.GamePlayed = TRUE;
11642 #if USE_NEW_PLAYER_ASSIGNMENTS
11643 // !!! also map player actions in single player mode !!!
11644 // if (game.team_mode)
11647 byte mapped_action[MAX_PLAYERS];
11649 #if DEBUG_PLAYER_ACTIONS
11651 for (i = 0; i < MAX_PLAYERS; i++)
11652 printf(" %d, ", stored_player[i].effective_action);
11655 for (i = 0; i < MAX_PLAYERS; i++)
11656 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11658 for (i = 0; i < MAX_PLAYERS; i++)
11659 stored_player[i].effective_action = mapped_action[i];
11661 #if DEBUG_PLAYER_ACTIONS
11663 for (i = 0; i < MAX_PLAYERS; i++)
11664 printf(" %d, ", stored_player[i].effective_action);
11668 #if DEBUG_PLAYER_ACTIONS
11672 for (i = 0; i < MAX_PLAYERS; i++)
11673 printf(" %d, ", stored_player[i].effective_action);
11679 for (i = 0; i < MAX_PLAYERS; i++)
11681 // allow engine snapshot in case of changed movement attempt
11682 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11683 (stored_player[i].effective_action & KEY_MOTION))
11684 game.snapshot.changed_action = TRUE;
11686 // allow engine snapshot in case of snapping/dropping attempt
11687 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11688 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11689 game.snapshot.changed_action = TRUE;
11691 game.snapshot.last_action[i] = stored_player[i].effective_action;
11694 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11696 GameActions_EM_Main();
11698 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11700 GameActions_SP_Main();
11702 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11704 GameActions_MM_Main();
11708 GameActions_RND_Main();
11711 BlitScreenToBitmap(backbuffer);
11713 CheckLevelSolved();
11716 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11718 if (global.show_frames_per_second)
11720 static unsigned int fps_counter = 0;
11721 static int fps_frames = 0;
11722 unsigned int fps_delay_ms = Counter() - fps_counter;
11726 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11728 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11731 fps_counter = Counter();
11733 // always draw FPS to screen after FPS value was updated
11734 redraw_mask |= REDRAW_FPS;
11737 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11738 if (GetDrawDeactivationMask() == REDRAW_NONE)
11739 redraw_mask |= REDRAW_FPS;
11743 static void GameActions_CheckSaveEngineSnapshot(void)
11745 if (!game.snapshot.save_snapshot)
11748 // clear flag for saving snapshot _before_ saving snapshot
11749 game.snapshot.save_snapshot = FALSE;
11751 SaveEngineSnapshotToList();
11754 void GameActions(void)
11758 GameActions_CheckSaveEngineSnapshot();
11761 void GameActions_EM_Main(void)
11763 byte effective_action[MAX_PLAYERS];
11764 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11767 for (i = 0; i < MAX_PLAYERS; i++)
11768 effective_action[i] = stored_player[i].effective_action;
11770 GameActions_EM(effective_action, warp_mode);
11773 void GameActions_SP_Main(void)
11775 byte effective_action[MAX_PLAYERS];
11776 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11779 for (i = 0; i < MAX_PLAYERS; i++)
11780 effective_action[i] = stored_player[i].effective_action;
11782 GameActions_SP(effective_action, warp_mode);
11784 for (i = 0; i < MAX_PLAYERS; i++)
11786 if (stored_player[i].force_dropping)
11787 stored_player[i].action |= KEY_BUTTON_DROP;
11789 stored_player[i].force_dropping = FALSE;
11793 void GameActions_MM_Main(void)
11795 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11797 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11800 void GameActions_RND_Main(void)
11805 void GameActions_RND(void)
11807 static struct MouseActionInfo mouse_action_last = { 0 };
11808 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11809 int magic_wall_x = 0, magic_wall_y = 0;
11810 int i, x, y, element, graphic, last_gfx_frame;
11812 InitPlayfieldScanModeVars();
11814 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11816 SCAN_PLAYFIELD(x, y)
11818 ChangeCount[x][y] = 0;
11819 ChangeEvent[x][y] = -1;
11823 if (game.set_centered_player)
11825 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11827 // switching to "all players" only possible if all players fit to screen
11828 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11830 game.centered_player_nr_next = game.centered_player_nr;
11831 game.set_centered_player = FALSE;
11834 // do not switch focus to non-existing (or non-active) player
11835 if (game.centered_player_nr_next >= 0 &&
11836 !stored_player[game.centered_player_nr_next].active)
11838 game.centered_player_nr_next = game.centered_player_nr;
11839 game.set_centered_player = FALSE;
11843 if (game.set_centered_player &&
11844 ScreenMovPos == 0) // screen currently aligned at tile position
11848 if (game.centered_player_nr_next == -1)
11850 setScreenCenteredToAllPlayers(&sx, &sy);
11854 sx = stored_player[game.centered_player_nr_next].jx;
11855 sy = stored_player[game.centered_player_nr_next].jy;
11858 game.centered_player_nr = game.centered_player_nr_next;
11859 game.set_centered_player = FALSE;
11861 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11862 DrawGameDoorValues();
11865 for (i = 0; i < MAX_PLAYERS; i++)
11867 int actual_player_action = stored_player[i].effective_action;
11870 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11871 - rnd_equinox_tetrachloride 048
11872 - rnd_equinox_tetrachloride_ii 096
11873 - rnd_emanuel_schmieg 002
11874 - doctor_sloan_ww 001, 020
11876 if (stored_player[i].MovPos == 0)
11877 CheckGravityMovement(&stored_player[i]);
11880 // overwrite programmed action with tape action
11881 if (stored_player[i].programmed_action)
11882 actual_player_action = stored_player[i].programmed_action;
11884 PlayerActions(&stored_player[i], actual_player_action);
11886 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11889 ScrollScreen(NULL, SCROLL_GO_ON);
11891 /* for backwards compatibility, the following code emulates a fixed bug that
11892 occured when pushing elements (causing elements that just made their last
11893 pushing step to already (if possible) make their first falling step in the
11894 same game frame, which is bad); this code is also needed to use the famous
11895 "spring push bug" which is used in older levels and might be wanted to be
11896 used also in newer levels, but in this case the buggy pushing code is only
11897 affecting the "spring" element and no other elements */
11899 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11901 for (i = 0; i < MAX_PLAYERS; i++)
11903 struct PlayerInfo *player = &stored_player[i];
11904 int x = player->jx;
11905 int y = player->jy;
11907 if (player->active && player->is_pushing && player->is_moving &&
11909 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11910 Feld[x][y] == EL_SPRING))
11912 ContinueMoving(x, y);
11914 // continue moving after pushing (this is actually a bug)
11915 if (!IS_MOVING(x, y))
11916 Stop[x][y] = FALSE;
11921 SCAN_PLAYFIELD(x, y)
11923 Last[x][y] = Feld[x][y];
11925 ChangeCount[x][y] = 0;
11926 ChangeEvent[x][y] = -1;
11928 // this must be handled before main playfield loop
11929 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11932 if (MovDelay[x][y] <= 0)
11936 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11939 if (MovDelay[x][y] <= 0)
11942 TEST_DrawLevelField(x, y);
11944 TestIfElementTouchesCustomElement(x, y); // for empty space
11949 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11951 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11952 printf("GameActions(): This should never happen!\n");
11954 ChangePage[x][y] = -1;
11958 Stop[x][y] = FALSE;
11959 if (WasJustMoving[x][y] > 0)
11960 WasJustMoving[x][y]--;
11961 if (WasJustFalling[x][y] > 0)
11962 WasJustFalling[x][y]--;
11963 if (CheckCollision[x][y] > 0)
11964 CheckCollision[x][y]--;
11965 if (CheckImpact[x][y] > 0)
11966 CheckImpact[x][y]--;
11970 /* reset finished pushing action (not done in ContinueMoving() to allow
11971 continuous pushing animation for elements with zero push delay) */
11972 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11974 ResetGfxAnimation(x, y);
11975 TEST_DrawLevelField(x, y);
11979 if (IS_BLOCKED(x, y))
11983 Blocked2Moving(x, y, &oldx, &oldy);
11984 if (!IS_MOVING(oldx, oldy))
11986 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11987 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11988 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11989 printf("GameActions(): This should never happen!\n");
11995 if (mouse_action.button)
11997 int new_button = (mouse_action.button && mouse_action_last.button == 0);
11999 x = mouse_action.lx;
12000 y = mouse_action.ly;
12001 element = Feld[x][y];
12005 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12006 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12009 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12010 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12013 SCAN_PLAYFIELD(x, y)
12015 element = Feld[x][y];
12016 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12017 last_gfx_frame = GfxFrame[x][y];
12019 ResetGfxFrame(x, y);
12021 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12022 DrawLevelGraphicAnimation(x, y, graphic);
12024 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12025 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12026 ResetRandomAnimationValue(x, y);
12028 SetRandomAnimationValue(x, y);
12030 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12032 if (IS_INACTIVE(element))
12034 if (IS_ANIMATED(graphic))
12035 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12040 // this may take place after moving, so 'element' may have changed
12041 if (IS_CHANGING(x, y) &&
12042 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12044 int page = element_info[element].event_page_nr[CE_DELAY];
12046 HandleElementChange(x, y, page);
12048 element = Feld[x][y];
12049 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12052 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12056 element = Feld[x][y];
12057 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12059 if (IS_ANIMATED(graphic) &&
12060 !IS_MOVING(x, y) &&
12062 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12064 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12065 TEST_DrawTwinkleOnField(x, y);
12067 else if (element == EL_ACID)
12070 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12072 else if ((element == EL_EXIT_OPEN ||
12073 element == EL_EM_EXIT_OPEN ||
12074 element == EL_SP_EXIT_OPEN ||
12075 element == EL_STEEL_EXIT_OPEN ||
12076 element == EL_EM_STEEL_EXIT_OPEN ||
12077 element == EL_SP_TERMINAL ||
12078 element == EL_SP_TERMINAL_ACTIVE ||
12079 element == EL_EXTRA_TIME ||
12080 element == EL_SHIELD_NORMAL ||
12081 element == EL_SHIELD_DEADLY) &&
12082 IS_ANIMATED(graphic))
12083 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12084 else if (IS_MOVING(x, y))
12085 ContinueMoving(x, y);
12086 else if (IS_ACTIVE_BOMB(element))
12087 CheckDynamite(x, y);
12088 else if (element == EL_AMOEBA_GROWING)
12089 AmoebeWaechst(x, y);
12090 else if (element == EL_AMOEBA_SHRINKING)
12091 AmoebaDisappearing(x, y);
12093 #if !USE_NEW_AMOEBA_CODE
12094 else if (IS_AMOEBALIVE(element))
12095 AmoebeAbleger(x, y);
12098 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12100 else if (element == EL_EXIT_CLOSED)
12102 else if (element == EL_EM_EXIT_CLOSED)
12104 else if (element == EL_STEEL_EXIT_CLOSED)
12105 CheckExitSteel(x, y);
12106 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12107 CheckExitSteelEM(x, y);
12108 else if (element == EL_SP_EXIT_CLOSED)
12110 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12111 element == EL_EXPANDABLE_STEELWALL_GROWING)
12112 MauerWaechst(x, y);
12113 else if (element == EL_EXPANDABLE_WALL ||
12114 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12115 element == EL_EXPANDABLE_WALL_VERTICAL ||
12116 element == EL_EXPANDABLE_WALL_ANY ||
12117 element == EL_BD_EXPANDABLE_WALL)
12118 MauerAbleger(x, y);
12119 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12120 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12121 element == EL_EXPANDABLE_STEELWALL_ANY)
12122 MauerAblegerStahl(x, y);
12123 else if (element == EL_FLAMES)
12124 CheckForDragon(x, y);
12125 else if (element == EL_EXPLOSION)
12126 ; // drawing of correct explosion animation is handled separately
12127 else if (element == EL_ELEMENT_SNAPPING ||
12128 element == EL_DIAGONAL_SHRINKING ||
12129 element == EL_DIAGONAL_GROWING)
12131 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12133 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12135 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12136 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12138 if (IS_BELT_ACTIVE(element))
12139 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12141 if (game.magic_wall_active)
12143 int jx = local_player->jx, jy = local_player->jy;
12145 // play the element sound at the position nearest to the player
12146 if ((element == EL_MAGIC_WALL_FULL ||
12147 element == EL_MAGIC_WALL_ACTIVE ||
12148 element == EL_MAGIC_WALL_EMPTYING ||
12149 element == EL_BD_MAGIC_WALL_FULL ||
12150 element == EL_BD_MAGIC_WALL_ACTIVE ||
12151 element == EL_BD_MAGIC_WALL_EMPTYING ||
12152 element == EL_DC_MAGIC_WALL_FULL ||
12153 element == EL_DC_MAGIC_WALL_ACTIVE ||
12154 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12155 ABS(x - jx) + ABS(y - jy) <
12156 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12164 #if USE_NEW_AMOEBA_CODE
12165 // new experimental amoeba growth stuff
12166 if (!(FrameCounter % 8))
12168 static unsigned int random = 1684108901;
12170 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12172 x = RND(lev_fieldx);
12173 y = RND(lev_fieldy);
12174 element = Feld[x][y];
12176 if (!IS_PLAYER(x,y) &&
12177 (element == EL_EMPTY ||
12178 CAN_GROW_INTO(element) ||
12179 element == EL_QUICKSAND_EMPTY ||
12180 element == EL_QUICKSAND_FAST_EMPTY ||
12181 element == EL_ACID_SPLASH_LEFT ||
12182 element == EL_ACID_SPLASH_RIGHT))
12184 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12185 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12186 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12187 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12188 Feld[x][y] = EL_AMOEBA_DROP;
12191 random = random * 129 + 1;
12196 game.explosions_delayed = FALSE;
12198 SCAN_PLAYFIELD(x, y)
12200 element = Feld[x][y];
12202 if (ExplodeField[x][y])
12203 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12204 else if (element == EL_EXPLOSION)
12205 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12207 ExplodeField[x][y] = EX_TYPE_NONE;
12210 game.explosions_delayed = TRUE;
12212 if (game.magic_wall_active)
12214 if (!(game.magic_wall_time_left % 4))
12216 int element = Feld[magic_wall_x][magic_wall_y];
12218 if (element == EL_BD_MAGIC_WALL_FULL ||
12219 element == EL_BD_MAGIC_WALL_ACTIVE ||
12220 element == EL_BD_MAGIC_WALL_EMPTYING)
12221 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12222 else if (element == EL_DC_MAGIC_WALL_FULL ||
12223 element == EL_DC_MAGIC_WALL_ACTIVE ||
12224 element == EL_DC_MAGIC_WALL_EMPTYING)
12225 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12227 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12230 if (game.magic_wall_time_left > 0)
12232 game.magic_wall_time_left--;
12234 if (!game.magic_wall_time_left)
12236 SCAN_PLAYFIELD(x, y)
12238 element = Feld[x][y];
12240 if (element == EL_MAGIC_WALL_ACTIVE ||
12241 element == EL_MAGIC_WALL_FULL)
12243 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12244 TEST_DrawLevelField(x, y);
12246 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12247 element == EL_BD_MAGIC_WALL_FULL)
12249 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12250 TEST_DrawLevelField(x, y);
12252 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12253 element == EL_DC_MAGIC_WALL_FULL)
12255 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12256 TEST_DrawLevelField(x, y);
12260 game.magic_wall_active = FALSE;
12265 if (game.light_time_left > 0)
12267 game.light_time_left--;
12269 if (game.light_time_left == 0)
12270 RedrawAllLightSwitchesAndInvisibleElements();
12273 if (game.timegate_time_left > 0)
12275 game.timegate_time_left--;
12277 if (game.timegate_time_left == 0)
12278 CloseAllOpenTimegates();
12281 if (game.lenses_time_left > 0)
12283 game.lenses_time_left--;
12285 if (game.lenses_time_left == 0)
12286 RedrawAllInvisibleElementsForLenses();
12289 if (game.magnify_time_left > 0)
12291 game.magnify_time_left--;
12293 if (game.magnify_time_left == 0)
12294 RedrawAllInvisibleElementsForMagnifier();
12297 for (i = 0; i < MAX_PLAYERS; i++)
12299 struct PlayerInfo *player = &stored_player[i];
12301 if (SHIELD_ON(player))
12303 if (player->shield_deadly_time_left)
12304 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12305 else if (player->shield_normal_time_left)
12306 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12310 #if USE_DELAYED_GFX_REDRAW
12311 SCAN_PLAYFIELD(x, y)
12313 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12315 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12316 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12318 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12319 DrawLevelField(x, y);
12321 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12322 DrawLevelFieldCrumbled(x, y);
12324 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12325 DrawLevelFieldCrumbledNeighbours(x, y);
12327 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12328 DrawTwinkleOnField(x, y);
12331 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12336 PlayAllPlayersSound();
12338 for (i = 0; i < MAX_PLAYERS; i++)
12340 struct PlayerInfo *player = &stored_player[i];
12342 if (player->show_envelope != 0 && (!player->active ||
12343 player->MovPos == 0))
12345 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12347 player->show_envelope = 0;
12351 // use random number generator in every frame to make it less predictable
12352 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12355 mouse_action_last = mouse_action;
12358 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12360 int min_x = x, min_y = y, max_x = x, max_y = y;
12363 for (i = 0; i < MAX_PLAYERS; i++)
12365 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12367 if (!stored_player[i].active || &stored_player[i] == player)
12370 min_x = MIN(min_x, jx);
12371 min_y = MIN(min_y, jy);
12372 max_x = MAX(max_x, jx);
12373 max_y = MAX(max_y, jy);
12376 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12379 static boolean AllPlayersInVisibleScreen(void)
12383 for (i = 0; i < MAX_PLAYERS; i++)
12385 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12387 if (!stored_player[i].active)
12390 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12397 void ScrollLevel(int dx, int dy)
12399 int scroll_offset = 2 * TILEX_VAR;
12402 BlitBitmap(drawto_field, drawto_field,
12403 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12404 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12405 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12406 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12407 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12408 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12412 x = (dx == 1 ? BX1 : BX2);
12413 for (y = BY1; y <= BY2; y++)
12414 DrawScreenField(x, y);
12419 y = (dy == 1 ? BY1 : BY2);
12420 for (x = BX1; x <= BX2; x++)
12421 DrawScreenField(x, y);
12424 redraw_mask |= REDRAW_FIELD;
12427 static boolean canFallDown(struct PlayerInfo *player)
12429 int jx = player->jx, jy = player->jy;
12431 return (IN_LEV_FIELD(jx, jy + 1) &&
12432 (IS_FREE(jx, jy + 1) ||
12433 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12434 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12435 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12438 static boolean canPassField(int x, int y, int move_dir)
12440 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12441 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12442 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12443 int nextx = x + dx;
12444 int nexty = y + dy;
12445 int element = Feld[x][y];
12447 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12448 !CAN_MOVE(element) &&
12449 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12450 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12451 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12454 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12456 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12457 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12458 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12462 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12463 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12464 (IS_DIGGABLE(Feld[newx][newy]) ||
12465 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12466 canPassField(newx, newy, move_dir)));
12469 static void CheckGravityMovement(struct PlayerInfo *player)
12471 if (player->gravity && !player->programmed_action)
12473 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12474 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12475 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12476 int jx = player->jx, jy = player->jy;
12477 boolean player_is_moving_to_valid_field =
12478 (!player_is_snapping &&
12479 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12480 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12481 boolean player_can_fall_down = canFallDown(player);
12483 if (player_can_fall_down &&
12484 !player_is_moving_to_valid_field)
12485 player->programmed_action = MV_DOWN;
12489 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12491 return CheckGravityMovement(player);
12493 if (player->gravity && !player->programmed_action)
12495 int jx = player->jx, jy = player->jy;
12496 boolean field_under_player_is_free =
12497 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12498 boolean player_is_standing_on_valid_field =
12499 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12500 (IS_WALKABLE(Feld[jx][jy]) &&
12501 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12503 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12504 player->programmed_action = MV_DOWN;
12509 MovePlayerOneStep()
12510 -----------------------------------------------------------------------------
12511 dx, dy: direction (non-diagonal) to try to move the player to
12512 real_dx, real_dy: direction as read from input device (can be diagonal)
12515 boolean MovePlayerOneStep(struct PlayerInfo *player,
12516 int dx, int dy, int real_dx, int real_dy)
12518 int jx = player->jx, jy = player->jy;
12519 int new_jx = jx + dx, new_jy = jy + dy;
12521 boolean player_can_move = !player->cannot_move;
12523 if (!player->active || (!dx && !dy))
12524 return MP_NO_ACTION;
12526 player->MovDir = (dx < 0 ? MV_LEFT :
12527 dx > 0 ? MV_RIGHT :
12529 dy > 0 ? MV_DOWN : MV_NONE);
12531 if (!IN_LEV_FIELD(new_jx, new_jy))
12532 return MP_NO_ACTION;
12534 if (!player_can_move)
12536 if (player->MovPos == 0)
12538 player->is_moving = FALSE;
12539 player->is_digging = FALSE;
12540 player->is_collecting = FALSE;
12541 player->is_snapping = FALSE;
12542 player->is_pushing = FALSE;
12546 if (!network.enabled && game.centered_player_nr == -1 &&
12547 !AllPlayersInSight(player, new_jx, new_jy))
12548 return MP_NO_ACTION;
12550 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12551 if (can_move != MP_MOVING)
12554 // check if DigField() has caused relocation of the player
12555 if (player->jx != jx || player->jy != jy)
12556 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12558 StorePlayer[jx][jy] = 0;
12559 player->last_jx = jx;
12560 player->last_jy = jy;
12561 player->jx = new_jx;
12562 player->jy = new_jy;
12563 StorePlayer[new_jx][new_jy] = player->element_nr;
12565 if (player->move_delay_value_next != -1)
12567 player->move_delay_value = player->move_delay_value_next;
12568 player->move_delay_value_next = -1;
12572 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12574 player->step_counter++;
12576 PlayerVisit[jx][jy] = FrameCounter;
12578 player->is_moving = TRUE;
12581 // should better be called in MovePlayer(), but this breaks some tapes
12582 ScrollPlayer(player, SCROLL_INIT);
12588 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12590 int jx = player->jx, jy = player->jy;
12591 int old_jx = jx, old_jy = jy;
12592 int moved = MP_NO_ACTION;
12594 if (!player->active)
12599 if (player->MovPos == 0)
12601 player->is_moving = FALSE;
12602 player->is_digging = FALSE;
12603 player->is_collecting = FALSE;
12604 player->is_snapping = FALSE;
12605 player->is_pushing = FALSE;
12611 if (player->move_delay > 0)
12614 player->move_delay = -1; // set to "uninitialized" value
12616 // store if player is automatically moved to next field
12617 player->is_auto_moving = (player->programmed_action != MV_NONE);
12619 // remove the last programmed player action
12620 player->programmed_action = 0;
12622 if (player->MovPos)
12624 // should only happen if pre-1.2 tape recordings are played
12625 // this is only for backward compatibility
12627 int original_move_delay_value = player->move_delay_value;
12630 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12634 // scroll remaining steps with finest movement resolution
12635 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12637 while (player->MovPos)
12639 ScrollPlayer(player, SCROLL_GO_ON);
12640 ScrollScreen(NULL, SCROLL_GO_ON);
12642 AdvanceFrameAndPlayerCounters(player->index_nr);
12645 BackToFront_WithFrameDelay(0);
12648 player->move_delay_value = original_move_delay_value;
12651 player->is_active = FALSE;
12653 if (player->last_move_dir & MV_HORIZONTAL)
12655 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12656 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12660 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12661 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12664 if (!moved && !player->is_active)
12666 player->is_moving = FALSE;
12667 player->is_digging = FALSE;
12668 player->is_collecting = FALSE;
12669 player->is_snapping = FALSE;
12670 player->is_pushing = FALSE;
12676 if (moved & MP_MOVING && !ScreenMovPos &&
12677 (player->index_nr == game.centered_player_nr ||
12678 game.centered_player_nr == -1))
12680 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12682 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12684 // actual player has left the screen -- scroll in that direction
12685 if (jx != old_jx) // player has moved horizontally
12686 scroll_x += (jx - old_jx);
12687 else // player has moved vertically
12688 scroll_y += (jy - old_jy);
12692 int offset_raw = game.scroll_delay_value;
12694 if (jx != old_jx) // player has moved horizontally
12696 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12697 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12698 int new_scroll_x = jx - MIDPOSX + offset_x;
12700 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12701 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12702 scroll_x = new_scroll_x;
12704 // don't scroll over playfield boundaries
12705 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12707 // don't scroll more than one field at a time
12708 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12710 // don't scroll against the player's moving direction
12711 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12712 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12713 scroll_x = old_scroll_x;
12715 else // player has moved vertically
12717 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12718 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12719 int new_scroll_y = jy - MIDPOSY + offset_y;
12721 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12722 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12723 scroll_y = new_scroll_y;
12725 // don't scroll over playfield boundaries
12726 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12728 // don't scroll more than one field at a time
12729 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12731 // don't scroll against the player's moving direction
12732 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12733 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12734 scroll_y = old_scroll_y;
12738 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12740 if (!network.enabled && game.centered_player_nr == -1 &&
12741 !AllPlayersInVisibleScreen())
12743 scroll_x = old_scroll_x;
12744 scroll_y = old_scroll_y;
12748 ScrollScreen(player, SCROLL_INIT);
12749 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12754 player->StepFrame = 0;
12756 if (moved & MP_MOVING)
12758 if (old_jx != jx && old_jy == jy)
12759 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12760 else if (old_jx == jx && old_jy != jy)
12761 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12763 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12765 player->last_move_dir = player->MovDir;
12766 player->is_moving = TRUE;
12767 player->is_snapping = FALSE;
12768 player->is_switching = FALSE;
12769 player->is_dropping = FALSE;
12770 player->is_dropping_pressed = FALSE;
12771 player->drop_pressed_delay = 0;
12774 // should better be called here than above, but this breaks some tapes
12775 ScrollPlayer(player, SCROLL_INIT);
12780 CheckGravityMovementWhenNotMoving(player);
12782 player->is_moving = FALSE;
12784 /* at this point, the player is allowed to move, but cannot move right now
12785 (e.g. because of something blocking the way) -- ensure that the player
12786 is also allowed to move in the next frame (in old versions before 3.1.1,
12787 the player was forced to wait again for eight frames before next try) */
12789 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12790 player->move_delay = 0; // allow direct movement in the next frame
12793 if (player->move_delay == -1) // not yet initialized by DigField()
12794 player->move_delay = player->move_delay_value;
12796 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12798 TestIfPlayerTouchesBadThing(jx, jy);
12799 TestIfPlayerTouchesCustomElement(jx, jy);
12802 if (!player->active)
12803 RemovePlayer(player);
12808 void ScrollPlayer(struct PlayerInfo *player, int mode)
12810 int jx = player->jx, jy = player->jy;
12811 int last_jx = player->last_jx, last_jy = player->last_jy;
12812 int move_stepsize = TILEX / player->move_delay_value;
12814 if (!player->active)
12817 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12820 if (mode == SCROLL_INIT)
12822 player->actual_frame_counter = FrameCounter;
12823 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12825 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12826 Feld[last_jx][last_jy] == EL_EMPTY)
12828 int last_field_block_delay = 0; // start with no blocking at all
12829 int block_delay_adjustment = player->block_delay_adjustment;
12831 // if player blocks last field, add delay for exactly one move
12832 if (player->block_last_field)
12834 last_field_block_delay += player->move_delay_value;
12836 // when blocking enabled, prevent moving up despite gravity
12837 if (player->gravity && player->MovDir == MV_UP)
12838 block_delay_adjustment = -1;
12841 // add block delay adjustment (also possible when not blocking)
12842 last_field_block_delay += block_delay_adjustment;
12844 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12845 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12848 if (player->MovPos != 0) // player has not yet reached destination
12851 else if (!FrameReached(&player->actual_frame_counter, 1))
12854 if (player->MovPos != 0)
12856 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12857 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12859 // before DrawPlayer() to draw correct player graphic for this case
12860 if (player->MovPos == 0)
12861 CheckGravityMovement(player);
12864 if (player->MovPos == 0) // player reached destination field
12866 if (player->move_delay_reset_counter > 0)
12868 player->move_delay_reset_counter--;
12870 if (player->move_delay_reset_counter == 0)
12872 // continue with normal speed after quickly moving through gate
12873 HALVE_PLAYER_SPEED(player);
12875 // be able to make the next move without delay
12876 player->move_delay = 0;
12880 player->last_jx = jx;
12881 player->last_jy = jy;
12883 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12884 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12885 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12886 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12887 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12888 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12889 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12890 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12892 ExitPlayer(player);
12894 if (game.players_still_needed == 0 &&
12895 (game.friends_still_needed == 0 ||
12896 IS_SP_ELEMENT(Feld[jx][jy])))
12900 // this breaks one level: "machine", level 000
12902 int move_direction = player->MovDir;
12903 int enter_side = MV_DIR_OPPOSITE(move_direction);
12904 int leave_side = move_direction;
12905 int old_jx = last_jx;
12906 int old_jy = last_jy;
12907 int old_element = Feld[old_jx][old_jy];
12908 int new_element = Feld[jx][jy];
12910 if (IS_CUSTOM_ELEMENT(old_element))
12911 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12913 player->index_bit, leave_side);
12915 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12916 CE_PLAYER_LEAVES_X,
12917 player->index_bit, leave_side);
12919 if (IS_CUSTOM_ELEMENT(new_element))
12920 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12921 player->index_bit, enter_side);
12923 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12924 CE_PLAYER_ENTERS_X,
12925 player->index_bit, enter_side);
12927 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12928 CE_MOVE_OF_X, move_direction);
12931 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12933 TestIfPlayerTouchesBadThing(jx, jy);
12934 TestIfPlayerTouchesCustomElement(jx, jy);
12936 /* needed because pushed element has not yet reached its destination,
12937 so it would trigger a change event at its previous field location */
12938 if (!player->is_pushing)
12939 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12941 if (!player->active)
12942 RemovePlayer(player);
12945 if (!game.LevelSolved && level.use_step_counter)
12955 if (TimeLeft <= 10 && setup.time_limit)
12956 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12958 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12960 DisplayGameControlValues();
12962 if (!TimeLeft && setup.time_limit)
12963 for (i = 0; i < MAX_PLAYERS; i++)
12964 KillPlayer(&stored_player[i]);
12966 else if (game.no_time_limit && !game.all_players_gone)
12968 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12970 DisplayGameControlValues();
12974 if (tape.single_step && tape.recording && !tape.pausing &&
12975 !player->programmed_action)
12976 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12978 if (!player->programmed_action)
12979 CheckSaveEngineSnapshot(player);
12983 void ScrollScreen(struct PlayerInfo *player, int mode)
12985 static unsigned int screen_frame_counter = 0;
12987 if (mode == SCROLL_INIT)
12989 // set scrolling step size according to actual player's moving speed
12990 ScrollStepSize = TILEX / player->move_delay_value;
12992 screen_frame_counter = FrameCounter;
12993 ScreenMovDir = player->MovDir;
12994 ScreenMovPos = player->MovPos;
12995 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12998 else if (!FrameReached(&screen_frame_counter, 1))
13003 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13004 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13005 redraw_mask |= REDRAW_FIELD;
13008 ScreenMovDir = MV_NONE;
13011 void TestIfPlayerTouchesCustomElement(int x, int y)
13013 static int xy[4][2] =
13020 static int trigger_sides[4][2] =
13022 // center side border side
13023 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13024 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13025 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13026 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13028 static int touch_dir[4] =
13030 MV_LEFT | MV_RIGHT,
13035 int center_element = Feld[x][y]; // should always be non-moving!
13038 for (i = 0; i < NUM_DIRECTIONS; i++)
13040 int xx = x + xy[i][0];
13041 int yy = y + xy[i][1];
13042 int center_side = trigger_sides[i][0];
13043 int border_side = trigger_sides[i][1];
13044 int border_element;
13046 if (!IN_LEV_FIELD(xx, yy))
13049 if (IS_PLAYER(x, y)) // player found at center element
13051 struct PlayerInfo *player = PLAYERINFO(x, y);
13053 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13054 border_element = Feld[xx][yy]; // may be moving!
13055 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13056 border_element = Feld[xx][yy];
13057 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13058 border_element = MovingOrBlocked2Element(xx, yy);
13060 continue; // center and border element do not touch
13062 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13063 player->index_bit, border_side);
13064 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13065 CE_PLAYER_TOUCHES_X,
13066 player->index_bit, border_side);
13069 /* use player element that is initially defined in the level playfield,
13070 not the player element that corresponds to the runtime player number
13071 (example: a level that contains EL_PLAYER_3 as the only player would
13072 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13073 int player_element = PLAYERINFO(x, y)->initial_element;
13075 CheckElementChangeBySide(xx, yy, border_element, player_element,
13076 CE_TOUCHING_X, border_side);
13079 else if (IS_PLAYER(xx, yy)) // player found at border element
13081 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13083 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13085 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13086 continue; // center and border element do not touch
13089 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13090 player->index_bit, center_side);
13091 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13092 CE_PLAYER_TOUCHES_X,
13093 player->index_bit, center_side);
13096 /* use player element that is initially defined in the level playfield,
13097 not the player element that corresponds to the runtime player number
13098 (example: a level that contains EL_PLAYER_3 as the only player would
13099 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13100 int player_element = PLAYERINFO(xx, yy)->initial_element;
13102 CheckElementChangeBySide(x, y, center_element, player_element,
13103 CE_TOUCHING_X, center_side);
13111 void TestIfElementTouchesCustomElement(int x, int y)
13113 static int xy[4][2] =
13120 static int trigger_sides[4][2] =
13122 // center side border side
13123 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13124 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13125 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13126 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13128 static int touch_dir[4] =
13130 MV_LEFT | MV_RIGHT,
13135 boolean change_center_element = FALSE;
13136 int center_element = Feld[x][y]; // should always be non-moving!
13137 int border_element_old[NUM_DIRECTIONS];
13140 for (i = 0; i < NUM_DIRECTIONS; i++)
13142 int xx = x + xy[i][0];
13143 int yy = y + xy[i][1];
13144 int border_element;
13146 border_element_old[i] = -1;
13148 if (!IN_LEV_FIELD(xx, yy))
13151 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13152 border_element = Feld[xx][yy]; // may be moving!
13153 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13154 border_element = Feld[xx][yy];
13155 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13156 border_element = MovingOrBlocked2Element(xx, yy);
13158 continue; // center and border element do not touch
13160 border_element_old[i] = border_element;
13163 for (i = 0; i < NUM_DIRECTIONS; i++)
13165 int xx = x + xy[i][0];
13166 int yy = y + xy[i][1];
13167 int center_side = trigger_sides[i][0];
13168 int border_element = border_element_old[i];
13170 if (border_element == -1)
13173 // check for change of border element
13174 CheckElementChangeBySide(xx, yy, border_element, center_element,
13175 CE_TOUCHING_X, center_side);
13177 // (center element cannot be player, so we dont have to check this here)
13180 for (i = 0; i < NUM_DIRECTIONS; i++)
13182 int xx = x + xy[i][0];
13183 int yy = y + xy[i][1];
13184 int border_side = trigger_sides[i][1];
13185 int border_element = border_element_old[i];
13187 if (border_element == -1)
13190 // check for change of center element (but change it only once)
13191 if (!change_center_element)
13192 change_center_element =
13193 CheckElementChangeBySide(x, y, center_element, border_element,
13194 CE_TOUCHING_X, border_side);
13196 if (IS_PLAYER(xx, yy))
13198 /* use player element that is initially defined in the level playfield,
13199 not the player element that corresponds to the runtime player number
13200 (example: a level that contains EL_PLAYER_3 as the only player would
13201 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13202 int player_element = PLAYERINFO(xx, yy)->initial_element;
13204 CheckElementChangeBySide(x, y, center_element, player_element,
13205 CE_TOUCHING_X, border_side);
13210 void TestIfElementHitsCustomElement(int x, int y, int direction)
13212 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13213 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13214 int hitx = x + dx, hity = y + dy;
13215 int hitting_element = Feld[x][y];
13216 int touched_element;
13218 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13221 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13222 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13224 if (IN_LEV_FIELD(hitx, hity))
13226 int opposite_direction = MV_DIR_OPPOSITE(direction);
13227 int hitting_side = direction;
13228 int touched_side = opposite_direction;
13229 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13230 MovDir[hitx][hity] != direction ||
13231 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13237 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13238 CE_HITTING_X, touched_side);
13240 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13241 CE_HIT_BY_X, hitting_side);
13243 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13244 CE_HIT_BY_SOMETHING, opposite_direction);
13246 if (IS_PLAYER(hitx, hity))
13248 /* use player element that is initially defined in the level playfield,
13249 not the player element that corresponds to the runtime player number
13250 (example: a level that contains EL_PLAYER_3 as the only player would
13251 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13252 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13254 CheckElementChangeBySide(x, y, hitting_element, player_element,
13255 CE_HITTING_X, touched_side);
13260 // "hitting something" is also true when hitting the playfield border
13261 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13262 CE_HITTING_SOMETHING, direction);
13265 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13267 int i, kill_x = -1, kill_y = -1;
13269 int bad_element = -1;
13270 static int test_xy[4][2] =
13277 static int test_dir[4] =
13285 for (i = 0; i < NUM_DIRECTIONS; i++)
13287 int test_x, test_y, test_move_dir, test_element;
13289 test_x = good_x + test_xy[i][0];
13290 test_y = good_y + test_xy[i][1];
13292 if (!IN_LEV_FIELD(test_x, test_y))
13296 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13298 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13300 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13301 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13303 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13304 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13308 bad_element = test_element;
13314 if (kill_x != -1 || kill_y != -1)
13316 if (IS_PLAYER(good_x, good_y))
13318 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13320 if (player->shield_deadly_time_left > 0 &&
13321 !IS_INDESTRUCTIBLE(bad_element))
13322 Bang(kill_x, kill_y);
13323 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13324 KillPlayer(player);
13327 Bang(good_x, good_y);
13331 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13333 int i, kill_x = -1, kill_y = -1;
13334 int bad_element = Feld[bad_x][bad_y];
13335 static int test_xy[4][2] =
13342 static int touch_dir[4] =
13344 MV_LEFT | MV_RIGHT,
13349 static int test_dir[4] =
13357 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13360 for (i = 0; i < NUM_DIRECTIONS; i++)
13362 int test_x, test_y, test_move_dir, test_element;
13364 test_x = bad_x + test_xy[i][0];
13365 test_y = bad_y + test_xy[i][1];
13367 if (!IN_LEV_FIELD(test_x, test_y))
13371 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13373 test_element = Feld[test_x][test_y];
13375 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13376 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13378 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13379 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13381 // good thing is player or penguin that does not move away
13382 if (IS_PLAYER(test_x, test_y))
13384 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13386 if (bad_element == EL_ROBOT && player->is_moving)
13387 continue; // robot does not kill player if he is moving
13389 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13391 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13392 continue; // center and border element do not touch
13400 else if (test_element == EL_PENGUIN)
13410 if (kill_x != -1 || kill_y != -1)
13412 if (IS_PLAYER(kill_x, kill_y))
13414 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13416 if (player->shield_deadly_time_left > 0 &&
13417 !IS_INDESTRUCTIBLE(bad_element))
13418 Bang(bad_x, bad_y);
13419 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13420 KillPlayer(player);
13423 Bang(kill_x, kill_y);
13427 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13429 int bad_element = Feld[bad_x][bad_y];
13430 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13431 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13432 int test_x = bad_x + dx, test_y = bad_y + dy;
13433 int test_move_dir, test_element;
13434 int kill_x = -1, kill_y = -1;
13436 if (!IN_LEV_FIELD(test_x, test_y))
13440 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13442 test_element = Feld[test_x][test_y];
13444 if (test_move_dir != bad_move_dir)
13446 // good thing can be player or penguin that does not move away
13447 if (IS_PLAYER(test_x, test_y))
13449 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13451 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13452 player as being hit when he is moving towards the bad thing, because
13453 the "get hit by" condition would be lost after the player stops) */
13454 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13455 return; // player moves away from bad thing
13460 else if (test_element == EL_PENGUIN)
13467 if (kill_x != -1 || kill_y != -1)
13469 if (IS_PLAYER(kill_x, kill_y))
13471 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13473 if (player->shield_deadly_time_left > 0 &&
13474 !IS_INDESTRUCTIBLE(bad_element))
13475 Bang(bad_x, bad_y);
13476 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13477 KillPlayer(player);
13480 Bang(kill_x, kill_y);
13484 void TestIfPlayerTouchesBadThing(int x, int y)
13486 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13489 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13491 TestIfGoodThingHitsBadThing(x, y, move_dir);
13494 void TestIfBadThingTouchesPlayer(int x, int y)
13496 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13499 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13501 TestIfBadThingHitsGoodThing(x, y, move_dir);
13504 void TestIfFriendTouchesBadThing(int x, int y)
13506 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13509 void TestIfBadThingTouchesFriend(int x, int y)
13511 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13514 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13516 int i, kill_x = bad_x, kill_y = bad_y;
13517 static int xy[4][2] =
13525 for (i = 0; i < NUM_DIRECTIONS; i++)
13529 x = bad_x + xy[i][0];
13530 y = bad_y + xy[i][1];
13531 if (!IN_LEV_FIELD(x, y))
13534 element = Feld[x][y];
13535 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13536 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13544 if (kill_x != bad_x || kill_y != bad_y)
13545 Bang(bad_x, bad_y);
13548 void KillPlayer(struct PlayerInfo *player)
13550 int jx = player->jx, jy = player->jy;
13552 if (!player->active)
13556 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13557 player->killed, player->active, player->reanimated);
13560 /* the following code was introduced to prevent an infinite loop when calling
13562 -> CheckTriggeredElementChangeExt()
13563 -> ExecuteCustomElementAction()
13565 -> (infinitely repeating the above sequence of function calls)
13566 which occurs when killing the player while having a CE with the setting
13567 "kill player X when explosion of <player X>"; the solution using a new
13568 field "player->killed" was chosen for backwards compatibility, although
13569 clever use of the fields "player->active" etc. would probably also work */
13571 if (player->killed)
13575 player->killed = TRUE;
13577 // remove accessible field at the player's position
13578 Feld[jx][jy] = EL_EMPTY;
13580 // deactivate shield (else Bang()/Explode() would not work right)
13581 player->shield_normal_time_left = 0;
13582 player->shield_deadly_time_left = 0;
13585 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13586 player->killed, player->active, player->reanimated);
13592 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13593 player->killed, player->active, player->reanimated);
13596 if (player->reanimated) // killed player may have been reanimated
13597 player->killed = player->reanimated = FALSE;
13599 BuryPlayer(player);
13602 static void KillPlayerUnlessEnemyProtected(int x, int y)
13604 if (!PLAYER_ENEMY_PROTECTED(x, y))
13605 KillPlayer(PLAYERINFO(x, y));
13608 static void KillPlayerUnlessExplosionProtected(int x, int y)
13610 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13611 KillPlayer(PLAYERINFO(x, y));
13614 void BuryPlayer(struct PlayerInfo *player)
13616 int jx = player->jx, jy = player->jy;
13618 if (!player->active)
13621 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13622 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13624 RemovePlayer(player);
13626 player->buried = TRUE;
13628 if (game.all_players_gone)
13629 game.GameOver = TRUE;
13632 void RemovePlayer(struct PlayerInfo *player)
13634 int jx = player->jx, jy = player->jy;
13635 int i, found = FALSE;
13637 player->present = FALSE;
13638 player->active = FALSE;
13640 // required for some CE actions (even if the player is not active anymore)
13641 player->MovPos = 0;
13643 if (!ExplodeField[jx][jy])
13644 StorePlayer[jx][jy] = 0;
13646 if (player->is_moving)
13647 TEST_DrawLevelField(player->last_jx, player->last_jy);
13649 for (i = 0; i < MAX_PLAYERS; i++)
13650 if (stored_player[i].active)
13655 game.all_players_gone = TRUE;
13656 game.GameOver = TRUE;
13659 game.exit_x = game.robot_wheel_x = jx;
13660 game.exit_y = game.robot_wheel_y = jy;
13663 void ExitPlayer(struct PlayerInfo *player)
13665 DrawPlayer(player); // needed here only to cleanup last field
13666 RemovePlayer(player);
13668 if (game.players_still_needed > 0)
13669 game.players_still_needed--;
13672 static void setFieldForSnapping(int x, int y, int element, int direction)
13674 struct ElementInfo *ei = &element_info[element];
13675 int direction_bit = MV_DIR_TO_BIT(direction);
13676 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13677 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13678 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13680 Feld[x][y] = EL_ELEMENT_SNAPPING;
13681 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13683 ResetGfxAnimation(x, y);
13685 GfxElement[x][y] = element;
13686 GfxAction[x][y] = action;
13687 GfxDir[x][y] = direction;
13688 GfxFrame[x][y] = -1;
13692 =============================================================================
13693 checkDiagonalPushing()
13694 -----------------------------------------------------------------------------
13695 check if diagonal input device direction results in pushing of object
13696 (by checking if the alternative direction is walkable, diggable, ...)
13697 =============================================================================
13700 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13701 int x, int y, int real_dx, int real_dy)
13703 int jx, jy, dx, dy, xx, yy;
13705 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13708 // diagonal direction: check alternative direction
13713 xx = jx + (dx == 0 ? real_dx : 0);
13714 yy = jy + (dy == 0 ? real_dy : 0);
13716 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13720 =============================================================================
13722 -----------------------------------------------------------------------------
13723 x, y: field next to player (non-diagonal) to try to dig to
13724 real_dx, real_dy: direction as read from input device (can be diagonal)
13725 =============================================================================
13728 static int DigField(struct PlayerInfo *player,
13729 int oldx, int oldy, int x, int y,
13730 int real_dx, int real_dy, int mode)
13732 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13733 boolean player_was_pushing = player->is_pushing;
13734 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13735 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13736 int jx = oldx, jy = oldy;
13737 int dx = x - jx, dy = y - jy;
13738 int nextx = x + dx, nexty = y + dy;
13739 int move_direction = (dx == -1 ? MV_LEFT :
13740 dx == +1 ? MV_RIGHT :
13742 dy == +1 ? MV_DOWN : MV_NONE);
13743 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13744 int dig_side = MV_DIR_OPPOSITE(move_direction);
13745 int old_element = Feld[jx][jy];
13746 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13749 if (is_player) // function can also be called by EL_PENGUIN
13751 if (player->MovPos == 0)
13753 player->is_digging = FALSE;
13754 player->is_collecting = FALSE;
13757 if (player->MovPos == 0) // last pushing move finished
13758 player->is_pushing = FALSE;
13760 if (mode == DF_NO_PUSH) // player just stopped pushing
13762 player->is_switching = FALSE;
13763 player->push_delay = -1;
13765 return MP_NO_ACTION;
13769 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13770 old_element = Back[jx][jy];
13772 // in case of element dropped at player position, check background
13773 else if (Back[jx][jy] != EL_EMPTY &&
13774 game.engine_version >= VERSION_IDENT(2,2,0,0))
13775 old_element = Back[jx][jy];
13777 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13778 return MP_NO_ACTION; // field has no opening in this direction
13780 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13781 return MP_NO_ACTION; // field has no opening in this direction
13783 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13787 Feld[jx][jy] = player->artwork_element;
13788 InitMovingField(jx, jy, MV_DOWN);
13789 Store[jx][jy] = EL_ACID;
13790 ContinueMoving(jx, jy);
13791 BuryPlayer(player);
13793 return MP_DONT_RUN_INTO;
13796 if (player_can_move && DONT_RUN_INTO(element))
13798 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13800 return MP_DONT_RUN_INTO;
13803 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13804 return MP_NO_ACTION;
13806 collect_count = element_info[element].collect_count_initial;
13808 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13809 return MP_NO_ACTION;
13811 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13812 player_can_move = player_can_move_or_snap;
13814 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13815 game.engine_version >= VERSION_IDENT(2,2,0,0))
13817 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13818 player->index_bit, dig_side);
13819 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13820 player->index_bit, dig_side);
13822 if (element == EL_DC_LANDMINE)
13825 if (Feld[x][y] != element) // field changed by snapping
13828 return MP_NO_ACTION;
13831 if (player->gravity && is_player && !player->is_auto_moving &&
13832 canFallDown(player) && move_direction != MV_DOWN &&
13833 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13834 return MP_NO_ACTION; // player cannot walk here due to gravity
13836 if (player_can_move &&
13837 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13839 int sound_element = SND_ELEMENT(element);
13840 int sound_action = ACTION_WALKING;
13842 if (IS_RND_GATE(element))
13844 if (!player->key[RND_GATE_NR(element)])
13845 return MP_NO_ACTION;
13847 else if (IS_RND_GATE_GRAY(element))
13849 if (!player->key[RND_GATE_GRAY_NR(element)])
13850 return MP_NO_ACTION;
13852 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13854 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13855 return MP_NO_ACTION;
13857 else if (element == EL_EXIT_OPEN ||
13858 element == EL_EM_EXIT_OPEN ||
13859 element == EL_EM_EXIT_OPENING ||
13860 element == EL_STEEL_EXIT_OPEN ||
13861 element == EL_EM_STEEL_EXIT_OPEN ||
13862 element == EL_EM_STEEL_EXIT_OPENING ||
13863 element == EL_SP_EXIT_OPEN ||
13864 element == EL_SP_EXIT_OPENING)
13866 sound_action = ACTION_PASSING; // player is passing exit
13868 else if (element == EL_EMPTY)
13870 sound_action = ACTION_MOVING; // nothing to walk on
13873 // play sound from background or player, whatever is available
13874 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13875 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13877 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13879 else if (player_can_move &&
13880 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13882 if (!ACCESS_FROM(element, opposite_direction))
13883 return MP_NO_ACTION; // field not accessible from this direction
13885 if (CAN_MOVE(element)) // only fixed elements can be passed!
13886 return MP_NO_ACTION;
13888 if (IS_EM_GATE(element))
13890 if (!player->key[EM_GATE_NR(element)])
13891 return MP_NO_ACTION;
13893 else if (IS_EM_GATE_GRAY(element))
13895 if (!player->key[EM_GATE_GRAY_NR(element)])
13896 return MP_NO_ACTION;
13898 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13900 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13901 return MP_NO_ACTION;
13903 else if (IS_EMC_GATE(element))
13905 if (!player->key[EMC_GATE_NR(element)])
13906 return MP_NO_ACTION;
13908 else if (IS_EMC_GATE_GRAY(element))
13910 if (!player->key[EMC_GATE_GRAY_NR(element)])
13911 return MP_NO_ACTION;
13913 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13915 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13916 return MP_NO_ACTION;
13918 else if (element == EL_DC_GATE_WHITE ||
13919 element == EL_DC_GATE_WHITE_GRAY ||
13920 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13922 if (player->num_white_keys == 0)
13923 return MP_NO_ACTION;
13925 player->num_white_keys--;
13927 else if (IS_SP_PORT(element))
13929 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13930 element == EL_SP_GRAVITY_PORT_RIGHT ||
13931 element == EL_SP_GRAVITY_PORT_UP ||
13932 element == EL_SP_GRAVITY_PORT_DOWN)
13933 player->gravity = !player->gravity;
13934 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13935 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13936 element == EL_SP_GRAVITY_ON_PORT_UP ||
13937 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13938 player->gravity = TRUE;
13939 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13940 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13941 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13942 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13943 player->gravity = FALSE;
13946 // automatically move to the next field with double speed
13947 player->programmed_action = move_direction;
13949 if (player->move_delay_reset_counter == 0)
13951 player->move_delay_reset_counter = 2; // two double speed steps
13953 DOUBLE_PLAYER_SPEED(player);
13956 PlayLevelSoundAction(x, y, ACTION_PASSING);
13958 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13962 if (mode != DF_SNAP)
13964 GfxElement[x][y] = GFX_ELEMENT(element);
13965 player->is_digging = TRUE;
13968 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13970 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13971 player->index_bit, dig_side);
13973 if (mode == DF_SNAP)
13975 if (level.block_snap_field)
13976 setFieldForSnapping(x, y, element, move_direction);
13978 TestIfElementTouchesCustomElement(x, y); // for empty space
13980 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13981 player->index_bit, dig_side);
13984 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13988 if (is_player && mode != DF_SNAP)
13990 GfxElement[x][y] = element;
13991 player->is_collecting = TRUE;
13994 if (element == EL_SPEED_PILL)
13996 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13998 else if (element == EL_EXTRA_TIME && level.time > 0)
14000 TimeLeft += level.extra_time;
14002 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14004 DisplayGameControlValues();
14006 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14008 player->shield_normal_time_left += level.shield_normal_time;
14009 if (element == EL_SHIELD_DEADLY)
14010 player->shield_deadly_time_left += level.shield_deadly_time;
14012 else if (element == EL_DYNAMITE ||
14013 element == EL_EM_DYNAMITE ||
14014 element == EL_SP_DISK_RED)
14016 if (player->inventory_size < MAX_INVENTORY_SIZE)
14017 player->inventory_element[player->inventory_size++] = element;
14019 DrawGameDoorValues();
14021 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14023 player->dynabomb_count++;
14024 player->dynabombs_left++;
14026 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14028 player->dynabomb_size++;
14030 else if (element == EL_DYNABOMB_INCREASE_POWER)
14032 player->dynabomb_xl = TRUE;
14034 else if (IS_KEY(element))
14036 player->key[KEY_NR(element)] = TRUE;
14038 DrawGameDoorValues();
14040 else if (element == EL_DC_KEY_WHITE)
14042 player->num_white_keys++;
14044 // display white keys?
14045 // DrawGameDoorValues();
14047 else if (IS_ENVELOPE(element))
14049 player->show_envelope = element;
14051 else if (element == EL_EMC_LENSES)
14053 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14055 RedrawAllInvisibleElementsForLenses();
14057 else if (element == EL_EMC_MAGNIFIER)
14059 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14061 RedrawAllInvisibleElementsForMagnifier();
14063 else if (IS_DROPPABLE(element) ||
14064 IS_THROWABLE(element)) // can be collected and dropped
14068 if (collect_count == 0)
14069 player->inventory_infinite_element = element;
14071 for (i = 0; i < collect_count; i++)
14072 if (player->inventory_size < MAX_INVENTORY_SIZE)
14073 player->inventory_element[player->inventory_size++] = element;
14075 DrawGameDoorValues();
14077 else if (collect_count > 0)
14079 game.gems_still_needed -= collect_count;
14080 if (game.gems_still_needed < 0)
14081 game.gems_still_needed = 0;
14083 game.snapshot.collected_item = TRUE;
14085 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14087 DisplayGameControlValues();
14090 RaiseScoreElement(element);
14091 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14094 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14095 player->index_bit, dig_side);
14097 if (mode == DF_SNAP)
14099 if (level.block_snap_field)
14100 setFieldForSnapping(x, y, element, move_direction);
14102 TestIfElementTouchesCustomElement(x, y); // for empty space
14104 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14105 player->index_bit, dig_side);
14108 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14110 if (mode == DF_SNAP && element != EL_BD_ROCK)
14111 return MP_NO_ACTION;
14113 if (CAN_FALL(element) && dy)
14114 return MP_NO_ACTION;
14116 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14117 !(element == EL_SPRING && level.use_spring_bug))
14118 return MP_NO_ACTION;
14120 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14121 ((move_direction & MV_VERTICAL &&
14122 ((element_info[element].move_pattern & MV_LEFT &&
14123 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14124 (element_info[element].move_pattern & MV_RIGHT &&
14125 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14126 (move_direction & MV_HORIZONTAL &&
14127 ((element_info[element].move_pattern & MV_UP &&
14128 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14129 (element_info[element].move_pattern & MV_DOWN &&
14130 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14131 return MP_NO_ACTION;
14133 // do not push elements already moving away faster than player
14134 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14135 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14136 return MP_NO_ACTION;
14138 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14140 if (player->push_delay_value == -1 || !player_was_pushing)
14141 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14143 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14145 if (player->push_delay_value == -1)
14146 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14148 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14150 if (!player->is_pushing)
14151 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14154 player->is_pushing = TRUE;
14155 player->is_active = TRUE;
14157 if (!(IN_LEV_FIELD(nextx, nexty) &&
14158 (IS_FREE(nextx, nexty) ||
14159 (IS_SB_ELEMENT(element) &&
14160 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14161 (IS_CUSTOM_ELEMENT(element) &&
14162 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14163 return MP_NO_ACTION;
14165 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14166 return MP_NO_ACTION;
14168 if (player->push_delay == -1) // new pushing; restart delay
14169 player->push_delay = 0;
14171 if (player->push_delay < player->push_delay_value &&
14172 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14173 element != EL_SPRING && element != EL_BALLOON)
14175 // make sure that there is no move delay before next try to push
14176 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14177 player->move_delay = 0;
14179 return MP_NO_ACTION;
14182 if (IS_CUSTOM_ELEMENT(element) &&
14183 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14185 if (!DigFieldByCE(nextx, nexty, element))
14186 return MP_NO_ACTION;
14189 if (IS_SB_ELEMENT(element))
14191 boolean sokoban_task_solved = FALSE;
14193 if (element == EL_SOKOBAN_FIELD_FULL)
14195 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14197 IncrementSokobanFieldsNeeded();
14198 IncrementSokobanObjectsNeeded();
14201 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14203 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14205 DecrementSokobanFieldsNeeded();
14206 DecrementSokobanObjectsNeeded();
14208 // sokoban object was pushed from empty field to sokoban field
14209 if (Back[x][y] == EL_EMPTY)
14210 sokoban_task_solved = TRUE;
14213 Feld[x][y] = EL_SOKOBAN_OBJECT;
14215 if (Back[x][y] == Back[nextx][nexty])
14216 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14217 else if (Back[x][y] != 0)
14218 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14221 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14224 if (sokoban_task_solved &&
14225 game.sokoban_fields_still_needed == 0 &&
14226 game.sokoban_objects_still_needed == 0 &&
14227 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14229 game.players_still_needed = 0;
14233 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14237 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14239 InitMovingField(x, y, move_direction);
14240 GfxAction[x][y] = ACTION_PUSHING;
14242 if (mode == DF_SNAP)
14243 ContinueMoving(x, y);
14245 MovPos[x][y] = (dx != 0 ? dx : dy);
14247 Pushed[x][y] = TRUE;
14248 Pushed[nextx][nexty] = TRUE;
14250 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14251 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14253 player->push_delay_value = -1; // get new value later
14255 // check for element change _after_ element has been pushed
14256 if (game.use_change_when_pushing_bug)
14258 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14259 player->index_bit, dig_side);
14260 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14261 player->index_bit, dig_side);
14264 else if (IS_SWITCHABLE(element))
14266 if (PLAYER_SWITCHING(player, x, y))
14268 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14269 player->index_bit, dig_side);
14274 player->is_switching = TRUE;
14275 player->switch_x = x;
14276 player->switch_y = y;
14278 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14280 if (element == EL_ROBOT_WHEEL)
14282 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14284 game.robot_wheel_x = x;
14285 game.robot_wheel_y = y;
14286 game.robot_wheel_active = TRUE;
14288 TEST_DrawLevelField(x, y);
14290 else if (element == EL_SP_TERMINAL)
14294 SCAN_PLAYFIELD(xx, yy)
14296 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14300 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14302 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14304 ResetGfxAnimation(xx, yy);
14305 TEST_DrawLevelField(xx, yy);
14309 else if (IS_BELT_SWITCH(element))
14311 ToggleBeltSwitch(x, y);
14313 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14314 element == EL_SWITCHGATE_SWITCH_DOWN ||
14315 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14316 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14318 ToggleSwitchgateSwitch(x, y);
14320 else if (element == EL_LIGHT_SWITCH ||
14321 element == EL_LIGHT_SWITCH_ACTIVE)
14323 ToggleLightSwitch(x, y);
14325 else if (element == EL_TIMEGATE_SWITCH ||
14326 element == EL_DC_TIMEGATE_SWITCH)
14328 ActivateTimegateSwitch(x, y);
14330 else if (element == EL_BALLOON_SWITCH_LEFT ||
14331 element == EL_BALLOON_SWITCH_RIGHT ||
14332 element == EL_BALLOON_SWITCH_UP ||
14333 element == EL_BALLOON_SWITCH_DOWN ||
14334 element == EL_BALLOON_SWITCH_NONE ||
14335 element == EL_BALLOON_SWITCH_ANY)
14337 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14338 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14339 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14340 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14341 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14344 else if (element == EL_LAMP)
14346 Feld[x][y] = EL_LAMP_ACTIVE;
14347 game.lights_still_needed--;
14349 ResetGfxAnimation(x, y);
14350 TEST_DrawLevelField(x, y);
14352 else if (element == EL_TIME_ORB_FULL)
14354 Feld[x][y] = EL_TIME_ORB_EMPTY;
14356 if (level.time > 0 || level.use_time_orb_bug)
14358 TimeLeft += level.time_orb_time;
14359 game.no_time_limit = FALSE;
14361 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14363 DisplayGameControlValues();
14366 ResetGfxAnimation(x, y);
14367 TEST_DrawLevelField(x, y);
14369 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14370 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14374 game.ball_active = !game.ball_active;
14376 SCAN_PLAYFIELD(xx, yy)
14378 int e = Feld[xx][yy];
14380 if (game.ball_active)
14382 if (e == EL_EMC_MAGIC_BALL)
14383 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14384 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14385 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14389 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14390 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14391 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14392 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14397 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14398 player->index_bit, dig_side);
14400 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14401 player->index_bit, dig_side);
14403 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14404 player->index_bit, dig_side);
14410 if (!PLAYER_SWITCHING(player, x, y))
14412 player->is_switching = TRUE;
14413 player->switch_x = x;
14414 player->switch_y = y;
14416 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14417 player->index_bit, dig_side);
14418 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14419 player->index_bit, dig_side);
14421 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14422 player->index_bit, dig_side);
14423 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14424 player->index_bit, dig_side);
14427 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14428 player->index_bit, dig_side);
14429 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14430 player->index_bit, dig_side);
14432 return MP_NO_ACTION;
14435 player->push_delay = -1;
14437 if (is_player) // function can also be called by EL_PENGUIN
14439 if (Feld[x][y] != element) // really digged/collected something
14441 player->is_collecting = !player->is_digging;
14442 player->is_active = TRUE;
14449 static boolean DigFieldByCE(int x, int y, int digging_element)
14451 int element = Feld[x][y];
14453 if (!IS_FREE(x, y))
14455 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14456 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14459 // no element can dig solid indestructible elements
14460 if (IS_INDESTRUCTIBLE(element) &&
14461 !IS_DIGGABLE(element) &&
14462 !IS_COLLECTIBLE(element))
14465 if (AmoebaNr[x][y] &&
14466 (element == EL_AMOEBA_FULL ||
14467 element == EL_BD_AMOEBA ||
14468 element == EL_AMOEBA_GROWING))
14470 AmoebaCnt[AmoebaNr[x][y]]--;
14471 AmoebaCnt2[AmoebaNr[x][y]]--;
14474 if (IS_MOVING(x, y))
14475 RemoveMovingField(x, y);
14479 TEST_DrawLevelField(x, y);
14482 // if digged element was about to explode, prevent the explosion
14483 ExplodeField[x][y] = EX_TYPE_NONE;
14485 PlayLevelSoundAction(x, y, action);
14488 Store[x][y] = EL_EMPTY;
14490 // this makes it possible to leave the removed element again
14491 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14492 Store[x][y] = element;
14497 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14499 int jx = player->jx, jy = player->jy;
14500 int x = jx + dx, y = jy + dy;
14501 int snap_direction = (dx == -1 ? MV_LEFT :
14502 dx == +1 ? MV_RIGHT :
14504 dy == +1 ? MV_DOWN : MV_NONE);
14505 boolean can_continue_snapping = (level.continuous_snapping &&
14506 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14508 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14511 if (!player->active || !IN_LEV_FIELD(x, y))
14519 if (player->MovPos == 0)
14520 player->is_pushing = FALSE;
14522 player->is_snapping = FALSE;
14524 if (player->MovPos == 0)
14526 player->is_moving = FALSE;
14527 player->is_digging = FALSE;
14528 player->is_collecting = FALSE;
14534 // prevent snapping with already pressed snap key when not allowed
14535 if (player->is_snapping && !can_continue_snapping)
14538 player->MovDir = snap_direction;
14540 if (player->MovPos == 0)
14542 player->is_moving = FALSE;
14543 player->is_digging = FALSE;
14544 player->is_collecting = FALSE;
14547 player->is_dropping = FALSE;
14548 player->is_dropping_pressed = FALSE;
14549 player->drop_pressed_delay = 0;
14551 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14554 player->is_snapping = TRUE;
14555 player->is_active = TRUE;
14557 if (player->MovPos == 0)
14559 player->is_moving = FALSE;
14560 player->is_digging = FALSE;
14561 player->is_collecting = FALSE;
14564 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14565 TEST_DrawLevelField(player->last_jx, player->last_jy);
14567 TEST_DrawLevelField(x, y);
14572 static boolean DropElement(struct PlayerInfo *player)
14574 int old_element, new_element;
14575 int dropx = player->jx, dropy = player->jy;
14576 int drop_direction = player->MovDir;
14577 int drop_side = drop_direction;
14578 int drop_element = get_next_dropped_element(player);
14580 /* do not drop an element on top of another element; when holding drop key
14581 pressed without moving, dropped element must move away before the next
14582 element can be dropped (this is especially important if the next element
14583 is dynamite, which can be placed on background for historical reasons) */
14584 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14587 if (IS_THROWABLE(drop_element))
14589 dropx += GET_DX_FROM_DIR(drop_direction);
14590 dropy += GET_DY_FROM_DIR(drop_direction);
14592 if (!IN_LEV_FIELD(dropx, dropy))
14596 old_element = Feld[dropx][dropy]; // old element at dropping position
14597 new_element = drop_element; // default: no change when dropping
14599 // check if player is active, not moving and ready to drop
14600 if (!player->active || player->MovPos || player->drop_delay > 0)
14603 // check if player has anything that can be dropped
14604 if (new_element == EL_UNDEFINED)
14607 // only set if player has anything that can be dropped
14608 player->is_dropping_pressed = TRUE;
14610 // check if drop key was pressed long enough for EM style dynamite
14611 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14614 // check if anything can be dropped at the current position
14615 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14618 // collected custom elements can only be dropped on empty fields
14619 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14622 if (old_element != EL_EMPTY)
14623 Back[dropx][dropy] = old_element; // store old element on this field
14625 ResetGfxAnimation(dropx, dropy);
14626 ResetRandomAnimationValue(dropx, dropy);
14628 if (player->inventory_size > 0 ||
14629 player->inventory_infinite_element != EL_UNDEFINED)
14631 if (player->inventory_size > 0)
14633 player->inventory_size--;
14635 DrawGameDoorValues();
14637 if (new_element == EL_DYNAMITE)
14638 new_element = EL_DYNAMITE_ACTIVE;
14639 else if (new_element == EL_EM_DYNAMITE)
14640 new_element = EL_EM_DYNAMITE_ACTIVE;
14641 else if (new_element == EL_SP_DISK_RED)
14642 new_element = EL_SP_DISK_RED_ACTIVE;
14645 Feld[dropx][dropy] = new_element;
14647 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14648 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14649 el2img(Feld[dropx][dropy]), 0);
14651 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14653 // needed if previous element just changed to "empty" in the last frame
14654 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14656 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14657 player->index_bit, drop_side);
14658 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14660 player->index_bit, drop_side);
14662 TestIfElementTouchesCustomElement(dropx, dropy);
14664 else // player is dropping a dyna bomb
14666 player->dynabombs_left--;
14668 Feld[dropx][dropy] = new_element;
14670 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14671 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14672 el2img(Feld[dropx][dropy]), 0);
14674 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14677 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14678 InitField_WithBug1(dropx, dropy, FALSE);
14680 new_element = Feld[dropx][dropy]; // element might have changed
14682 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14683 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14685 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14686 MovDir[dropx][dropy] = drop_direction;
14688 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14690 // do not cause impact style collision by dropping elements that can fall
14691 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14694 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14695 player->is_dropping = TRUE;
14697 player->drop_pressed_delay = 0;
14698 player->is_dropping_pressed = FALSE;
14700 player->drop_x = dropx;
14701 player->drop_y = dropy;
14706 // ----------------------------------------------------------------------------
14707 // game sound playing functions
14708 // ----------------------------------------------------------------------------
14710 static int *loop_sound_frame = NULL;
14711 static int *loop_sound_volume = NULL;
14713 void InitPlayLevelSound(void)
14715 int num_sounds = getSoundListSize();
14717 checked_free(loop_sound_frame);
14718 checked_free(loop_sound_volume);
14720 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14721 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14724 static void PlayLevelSound(int x, int y, int nr)
14726 int sx = SCREENX(x), sy = SCREENY(y);
14727 int volume, stereo_position;
14728 int max_distance = 8;
14729 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14731 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14732 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14735 if (!IN_LEV_FIELD(x, y) ||
14736 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14737 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14740 volume = SOUND_MAX_VOLUME;
14742 if (!IN_SCR_FIELD(sx, sy))
14744 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14745 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14747 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14750 stereo_position = (SOUND_MAX_LEFT +
14751 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14752 (SCR_FIELDX + 2 * max_distance));
14754 if (IS_LOOP_SOUND(nr))
14756 /* This assures that quieter loop sounds do not overwrite louder ones,
14757 while restarting sound volume comparison with each new game frame. */
14759 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14762 loop_sound_volume[nr] = volume;
14763 loop_sound_frame[nr] = FrameCounter;
14766 PlaySoundExt(nr, volume, stereo_position, type);
14769 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14771 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14772 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14773 y < LEVELY(BY1) ? LEVELY(BY1) :
14774 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14778 static void PlayLevelSoundAction(int x, int y, int action)
14780 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14783 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14785 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14787 if (sound_effect != SND_UNDEFINED)
14788 PlayLevelSound(x, y, sound_effect);
14791 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14794 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14796 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14797 PlayLevelSound(x, y, sound_effect);
14800 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14802 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14804 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14805 PlayLevelSound(x, y, sound_effect);
14808 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14810 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14812 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14813 StopSound(sound_effect);
14816 static int getLevelMusicNr(void)
14818 if (levelset.music[level_nr] != MUS_UNDEFINED)
14819 return levelset.music[level_nr]; // from config file
14821 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14824 static void FadeLevelSounds(void)
14829 static void FadeLevelMusic(void)
14831 int music_nr = getLevelMusicNr();
14832 char *curr_music = getCurrentlyPlayingMusicFilename();
14833 char *next_music = getMusicInfoEntryFilename(music_nr);
14835 if (!strEqual(curr_music, next_music))
14839 void FadeLevelSoundsAndMusic(void)
14845 static void PlayLevelMusic(void)
14847 int music_nr = getLevelMusicNr();
14848 char *curr_music = getCurrentlyPlayingMusicFilename();
14849 char *next_music = getMusicInfoEntryFilename(music_nr);
14851 if (!strEqual(curr_music, next_music))
14852 PlayMusicLoop(music_nr);
14855 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14857 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14859 int x = xx - offset;
14860 int y = yy - offset;
14865 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14869 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14873 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14877 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14881 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14885 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14889 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14892 case SOUND_android_clone:
14893 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14896 case SOUND_android_move:
14897 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14901 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14905 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14909 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14912 case SOUND_eater_eat:
14913 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14917 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14920 case SOUND_collect:
14921 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14924 case SOUND_diamond:
14925 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14929 // !!! CHECK THIS !!!
14931 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14933 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14937 case SOUND_wonderfall:
14938 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14942 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14946 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14950 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14954 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14958 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14962 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14966 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14970 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14973 case SOUND_exit_open:
14974 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14977 case SOUND_exit_leave:
14978 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14981 case SOUND_dynamite:
14982 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14986 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14990 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14994 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14998 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15002 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15006 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15010 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15015 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15017 int element = map_element_SP_to_RND(element_sp);
15018 int action = map_action_SP_to_RND(action_sp);
15019 int offset = (setup.sp_show_border_elements ? 0 : 1);
15020 int x = xx - offset;
15021 int y = yy - offset;
15023 PlayLevelSoundElementAction(x, y, element, action);
15026 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15028 int element = map_element_MM_to_RND(element_mm);
15029 int action = map_action_MM_to_RND(action_mm);
15031 int x = xx - offset;
15032 int y = yy - offset;
15034 if (!IS_MM_ELEMENT(element))
15035 element = EL_MM_DEFAULT;
15037 PlayLevelSoundElementAction(x, y, element, action);
15040 void PlaySound_MM(int sound_mm)
15042 int sound = map_sound_MM_to_RND(sound_mm);
15044 if (sound == SND_UNDEFINED)
15050 void PlaySoundLoop_MM(int sound_mm)
15052 int sound = map_sound_MM_to_RND(sound_mm);
15054 if (sound == SND_UNDEFINED)
15057 PlaySoundLoop(sound);
15060 void StopSound_MM(int sound_mm)
15062 int sound = map_sound_MM_to_RND(sound_mm);
15064 if (sound == SND_UNDEFINED)
15070 void RaiseScore(int value)
15072 game.score += value;
15074 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15076 DisplayGameControlValues();
15079 void RaiseScoreElement(int element)
15084 case EL_BD_DIAMOND:
15085 case EL_EMERALD_YELLOW:
15086 case EL_EMERALD_RED:
15087 case EL_EMERALD_PURPLE:
15088 case EL_SP_INFOTRON:
15089 RaiseScore(level.score[SC_EMERALD]);
15092 RaiseScore(level.score[SC_DIAMOND]);
15095 RaiseScore(level.score[SC_CRYSTAL]);
15098 RaiseScore(level.score[SC_PEARL]);
15101 case EL_BD_BUTTERFLY:
15102 case EL_SP_ELECTRON:
15103 RaiseScore(level.score[SC_BUG]);
15106 case EL_BD_FIREFLY:
15107 case EL_SP_SNIKSNAK:
15108 RaiseScore(level.score[SC_SPACESHIP]);
15111 case EL_DARK_YAMYAM:
15112 RaiseScore(level.score[SC_YAMYAM]);
15115 RaiseScore(level.score[SC_ROBOT]);
15118 RaiseScore(level.score[SC_PACMAN]);
15121 RaiseScore(level.score[SC_NUT]);
15124 case EL_EM_DYNAMITE:
15125 case EL_SP_DISK_RED:
15126 case EL_DYNABOMB_INCREASE_NUMBER:
15127 case EL_DYNABOMB_INCREASE_SIZE:
15128 case EL_DYNABOMB_INCREASE_POWER:
15129 RaiseScore(level.score[SC_DYNAMITE]);
15131 case EL_SHIELD_NORMAL:
15132 case EL_SHIELD_DEADLY:
15133 RaiseScore(level.score[SC_SHIELD]);
15135 case EL_EXTRA_TIME:
15136 RaiseScore(level.extra_time_score);
15150 case EL_DC_KEY_WHITE:
15151 RaiseScore(level.score[SC_KEY]);
15154 RaiseScore(element_info[element].collect_score);
15159 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15161 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15163 // closing door required in case of envelope style request dialogs
15166 // prevent short reactivation of overlay buttons while closing door
15167 SetOverlayActive(FALSE);
15169 CloseDoor(DOOR_CLOSE_1);
15172 if (network.enabled)
15173 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15177 FadeSkipNextFadeIn();
15179 SetGameStatus(GAME_MODE_MAIN);
15184 else // continue playing the game
15186 if (tape.playing && tape.deactivate_display)
15187 TapeDeactivateDisplayOff(TRUE);
15189 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15191 if (tape.playing && tape.deactivate_display)
15192 TapeDeactivateDisplayOn();
15196 void RequestQuitGame(boolean ask_if_really_quit)
15198 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15199 boolean skip_request = game.all_players_gone || quick_quit;
15201 RequestQuitGameExt(skip_request, quick_quit,
15202 "Do you really want to quit the game?");
15205 void RequestRestartGame(char *message)
15207 game.restart_game_message = NULL;
15209 boolean has_started_game = hasStartedNetworkGame();
15210 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15212 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15214 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15218 SetGameStatus(GAME_MODE_MAIN);
15224 void CheckGameOver(void)
15226 static boolean last_game_over = FALSE;
15227 static int game_over_delay = 0;
15228 int game_over_delay_value = 50;
15229 boolean game_over = checkGameFailed();
15231 // do not handle game over if request dialog is already active
15232 if (game.request_active)
15235 // do not ask to play again if game was never actually played
15236 if (!game.GamePlayed)
15241 last_game_over = FALSE;
15242 game_over_delay = game_over_delay_value;
15247 if (game_over_delay > 0)
15254 if (last_game_over != game_over)
15255 game.restart_game_message = (hasStartedNetworkGame() ?
15256 "Game over! Play it again?" :
15259 last_game_over = game_over;
15262 boolean checkGameSolved(void)
15264 // set for all game engines if level was solved
15265 return game.LevelSolved_GameEnd;
15268 boolean checkGameFailed(void)
15270 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15271 return (game_em.game_over && !game_em.level_solved);
15272 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15273 return (game_sp.game_over && !game_sp.level_solved);
15274 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15275 return (game_mm.game_over && !game_mm.level_solved);
15276 else // GAME_ENGINE_TYPE_RND
15277 return (game.GameOver && !game.LevelSolved);
15280 boolean checkGameEnded(void)
15282 return (checkGameSolved() || checkGameFailed());
15286 // ----------------------------------------------------------------------------
15287 // random generator functions
15288 // ----------------------------------------------------------------------------
15290 unsigned int InitEngineRandom_RND(int seed)
15292 game.num_random_calls = 0;
15294 return InitEngineRandom(seed);
15297 unsigned int RND(int max)
15301 game.num_random_calls++;
15303 return GetEngineRandom(max);
15310 // ----------------------------------------------------------------------------
15311 // game engine snapshot handling functions
15312 // ----------------------------------------------------------------------------
15314 struct EngineSnapshotInfo
15316 // runtime values for custom element collect score
15317 int collect_score[NUM_CUSTOM_ELEMENTS];
15319 // runtime values for group element choice position
15320 int choice_pos[NUM_GROUP_ELEMENTS];
15322 // runtime values for belt position animations
15323 int belt_graphic[4][NUM_BELT_PARTS];
15324 int belt_anim_mode[4][NUM_BELT_PARTS];
15327 static struct EngineSnapshotInfo engine_snapshot_rnd;
15328 static char *snapshot_level_identifier = NULL;
15329 static int snapshot_level_nr = -1;
15331 static void SaveEngineSnapshotValues_RND(void)
15333 static int belt_base_active_element[4] =
15335 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15336 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15337 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15338 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15342 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15344 int element = EL_CUSTOM_START + i;
15346 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15349 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15351 int element = EL_GROUP_START + i;
15353 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15356 for (i = 0; i < 4; i++)
15358 for (j = 0; j < NUM_BELT_PARTS; j++)
15360 int element = belt_base_active_element[i] + j;
15361 int graphic = el2img(element);
15362 int anim_mode = graphic_info[graphic].anim_mode;
15364 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15365 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15370 static void LoadEngineSnapshotValues_RND(void)
15372 unsigned int num_random_calls = game.num_random_calls;
15375 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15377 int element = EL_CUSTOM_START + i;
15379 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15382 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15384 int element = EL_GROUP_START + i;
15386 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15389 for (i = 0; i < 4; i++)
15391 for (j = 0; j < NUM_BELT_PARTS; j++)
15393 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15394 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15396 graphic_info[graphic].anim_mode = anim_mode;
15400 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15402 InitRND(tape.random_seed);
15403 for (i = 0; i < num_random_calls; i++)
15407 if (game.num_random_calls != num_random_calls)
15409 Error(ERR_INFO, "number of random calls out of sync");
15410 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15411 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15412 Error(ERR_EXIT, "this should not happen -- please debug");
15416 void FreeEngineSnapshotSingle(void)
15418 FreeSnapshotSingle();
15420 setString(&snapshot_level_identifier, NULL);
15421 snapshot_level_nr = -1;
15424 void FreeEngineSnapshotList(void)
15426 FreeSnapshotList();
15429 static ListNode *SaveEngineSnapshotBuffers(void)
15431 ListNode *buffers = NULL;
15433 // copy some special values to a structure better suited for the snapshot
15435 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15436 SaveEngineSnapshotValues_RND();
15437 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15438 SaveEngineSnapshotValues_EM();
15439 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15440 SaveEngineSnapshotValues_SP(&buffers);
15441 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15442 SaveEngineSnapshotValues_MM(&buffers);
15444 // save values stored in special snapshot structure
15446 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15447 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15448 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15449 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15450 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15451 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15452 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15453 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15455 // save further RND engine values
15457 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15458 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15459 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15461 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15462 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15463 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15464 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15465 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15467 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15468 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15469 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15471 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15473 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15474 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15476 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15477 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15478 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15479 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15480 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15481 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15482 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15483 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15484 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15485 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15486 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15487 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15488 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15489 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15490 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15491 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15492 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15493 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15495 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15496 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15498 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15499 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15500 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15502 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15503 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15505 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15506 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15507 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15508 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15509 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15511 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15512 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15515 ListNode *node = engine_snapshot_list_rnd;
15518 while (node != NULL)
15520 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15525 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15531 void SaveEngineSnapshotSingle(void)
15533 ListNode *buffers = SaveEngineSnapshotBuffers();
15535 // finally save all snapshot buffers to single snapshot
15536 SaveSnapshotSingle(buffers);
15538 // save level identification information
15539 setString(&snapshot_level_identifier, leveldir_current->identifier);
15540 snapshot_level_nr = level_nr;
15543 boolean CheckSaveEngineSnapshotToList(void)
15545 boolean save_snapshot =
15546 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15547 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15548 game.snapshot.changed_action) ||
15549 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15550 game.snapshot.collected_item));
15552 game.snapshot.changed_action = FALSE;
15553 game.snapshot.collected_item = FALSE;
15554 game.snapshot.save_snapshot = save_snapshot;
15556 return save_snapshot;
15559 void SaveEngineSnapshotToList(void)
15561 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15565 ListNode *buffers = SaveEngineSnapshotBuffers();
15567 // finally save all snapshot buffers to snapshot list
15568 SaveSnapshotToList(buffers);
15571 void SaveEngineSnapshotToListInitial(void)
15573 FreeEngineSnapshotList();
15575 SaveEngineSnapshotToList();
15578 static void LoadEngineSnapshotValues(void)
15580 // restore special values from snapshot structure
15582 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15583 LoadEngineSnapshotValues_RND();
15584 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15585 LoadEngineSnapshotValues_EM();
15586 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15587 LoadEngineSnapshotValues_SP();
15588 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15589 LoadEngineSnapshotValues_MM();
15592 void LoadEngineSnapshotSingle(void)
15594 LoadSnapshotSingle();
15596 LoadEngineSnapshotValues();
15599 static void LoadEngineSnapshot_Undo(int steps)
15601 LoadSnapshotFromList_Older(steps);
15603 LoadEngineSnapshotValues();
15606 static void LoadEngineSnapshot_Redo(int steps)
15608 LoadSnapshotFromList_Newer(steps);
15610 LoadEngineSnapshotValues();
15613 boolean CheckEngineSnapshotSingle(void)
15615 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15616 snapshot_level_nr == level_nr);
15619 boolean CheckEngineSnapshotList(void)
15621 return CheckSnapshotList();
15625 // ---------- new game button stuff -------------------------------------------
15632 boolean *setup_value;
15633 boolean allowed_on_tape;
15634 boolean is_touch_button;
15636 } gamebutton_info[NUM_GAME_BUTTONS] =
15639 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15640 GAME_CTRL_ID_STOP, NULL,
15641 TRUE, FALSE, "stop game"
15644 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15645 GAME_CTRL_ID_PAUSE, NULL,
15646 TRUE, FALSE, "pause game"
15649 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15650 GAME_CTRL_ID_PLAY, NULL,
15651 TRUE, FALSE, "play game"
15654 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15655 GAME_CTRL_ID_UNDO, NULL,
15656 TRUE, FALSE, "undo step"
15659 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15660 GAME_CTRL_ID_REDO, NULL,
15661 TRUE, FALSE, "redo step"
15664 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15665 GAME_CTRL_ID_SAVE, NULL,
15666 TRUE, FALSE, "save game"
15669 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15670 GAME_CTRL_ID_PAUSE2, NULL,
15671 TRUE, FALSE, "pause game"
15674 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15675 GAME_CTRL_ID_LOAD, NULL,
15676 TRUE, FALSE, "load game"
15679 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15680 GAME_CTRL_ID_PANEL_STOP, NULL,
15681 FALSE, FALSE, "stop game"
15684 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15685 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15686 FALSE, FALSE, "pause game"
15689 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15690 GAME_CTRL_ID_PANEL_PLAY, NULL,
15691 FALSE, FALSE, "play game"
15694 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15695 GAME_CTRL_ID_TOUCH_STOP, NULL,
15696 FALSE, TRUE, "stop game"
15699 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15700 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15701 FALSE, TRUE, "pause game"
15704 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15705 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15706 TRUE, FALSE, "background music on/off"
15709 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15710 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15711 TRUE, FALSE, "sound loops on/off"
15714 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15715 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15716 TRUE, FALSE, "normal sounds on/off"
15719 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15720 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15721 FALSE, FALSE, "background music on/off"
15724 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15725 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15726 FALSE, FALSE, "sound loops on/off"
15729 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15730 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15731 FALSE, FALSE, "normal sounds on/off"
15735 void CreateGameButtons(void)
15739 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15741 int graphic = gamebutton_info[i].graphic;
15742 struct GraphicInfo *gfx = &graphic_info[graphic];
15743 struct XY *pos = gamebutton_info[i].pos;
15744 struct GadgetInfo *gi;
15747 unsigned int event_mask;
15748 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15749 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15750 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15751 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15752 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15753 int gd_x = gfx->src_x;
15754 int gd_y = gfx->src_y;
15755 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15756 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15757 int gd_xa = gfx->src_x + gfx->active_xoffset;
15758 int gd_ya = gfx->src_y + gfx->active_yoffset;
15759 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15760 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15761 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15762 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15765 if (gfx->bitmap == NULL)
15767 game_gadget[id] = NULL;
15772 if (id == GAME_CTRL_ID_STOP ||
15773 id == GAME_CTRL_ID_PANEL_STOP ||
15774 id == GAME_CTRL_ID_TOUCH_STOP ||
15775 id == GAME_CTRL_ID_PLAY ||
15776 id == GAME_CTRL_ID_PANEL_PLAY ||
15777 id == GAME_CTRL_ID_SAVE ||
15778 id == GAME_CTRL_ID_LOAD)
15780 button_type = GD_TYPE_NORMAL_BUTTON;
15782 event_mask = GD_EVENT_RELEASED;
15784 else if (id == GAME_CTRL_ID_UNDO ||
15785 id == GAME_CTRL_ID_REDO)
15787 button_type = GD_TYPE_NORMAL_BUTTON;
15789 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15793 button_type = GD_TYPE_CHECK_BUTTON;
15794 checked = (gamebutton_info[i].setup_value != NULL ?
15795 *gamebutton_info[i].setup_value : FALSE);
15796 event_mask = GD_EVENT_PRESSED;
15799 gi = CreateGadget(GDI_CUSTOM_ID, id,
15800 GDI_IMAGE_ID, graphic,
15801 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15804 GDI_WIDTH, gfx->width,
15805 GDI_HEIGHT, gfx->height,
15806 GDI_TYPE, button_type,
15807 GDI_STATE, GD_BUTTON_UNPRESSED,
15808 GDI_CHECKED, checked,
15809 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15810 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15811 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15812 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15813 GDI_DIRECT_DRAW, FALSE,
15814 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15815 GDI_EVENT_MASK, event_mask,
15816 GDI_CALLBACK_ACTION, HandleGameButtons,
15820 Error(ERR_EXIT, "cannot create gadget");
15822 game_gadget[id] = gi;
15826 void FreeGameButtons(void)
15830 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15831 FreeGadget(game_gadget[i]);
15834 static void UnmapGameButtonsAtSamePosition(int id)
15838 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15840 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15841 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15842 UnmapGadget(game_gadget[i]);
15845 static void UnmapGameButtonsAtSamePosition_All(void)
15847 if (setup.show_snapshot_buttons)
15849 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15850 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15851 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15855 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15856 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15857 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15859 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15860 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15861 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15865 static void MapGameButtonsAtSamePosition(int id)
15869 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15871 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15872 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15873 MapGadget(game_gadget[i]);
15875 UnmapGameButtonsAtSamePosition_All();
15878 void MapUndoRedoButtons(void)
15880 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15881 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15883 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15884 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15887 void UnmapUndoRedoButtons(void)
15889 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15890 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15892 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15893 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15896 void ModifyPauseButtons(void)
15900 GAME_CTRL_ID_PAUSE,
15901 GAME_CTRL_ID_PAUSE2,
15902 GAME_CTRL_ID_PANEL_PAUSE,
15903 GAME_CTRL_ID_TOUCH_PAUSE,
15908 for (i = 0; ids[i] > -1; i++)
15909 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15912 static void MapGameButtonsExt(boolean on_tape)
15916 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15917 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15918 i != GAME_CTRL_ID_UNDO &&
15919 i != GAME_CTRL_ID_REDO)
15920 MapGadget(game_gadget[i]);
15922 UnmapGameButtonsAtSamePosition_All();
15924 RedrawGameButtons();
15927 static void UnmapGameButtonsExt(boolean on_tape)
15931 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15932 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15933 UnmapGadget(game_gadget[i]);
15936 static void RedrawGameButtonsExt(boolean on_tape)
15940 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15941 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15942 RedrawGadget(game_gadget[i]);
15945 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15950 gi->checked = state;
15953 static void RedrawSoundButtonGadget(int id)
15955 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15956 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15957 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15958 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15959 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15960 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15963 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15964 RedrawGadget(game_gadget[id2]);
15967 void MapGameButtons(void)
15969 MapGameButtonsExt(FALSE);
15972 void UnmapGameButtons(void)
15974 UnmapGameButtonsExt(FALSE);
15977 void RedrawGameButtons(void)
15979 RedrawGameButtonsExt(FALSE);
15982 void MapGameButtonsOnTape(void)
15984 MapGameButtonsExt(TRUE);
15987 void UnmapGameButtonsOnTape(void)
15989 UnmapGameButtonsExt(TRUE);
15992 void RedrawGameButtonsOnTape(void)
15994 RedrawGameButtonsExt(TRUE);
15997 static void GameUndoRedoExt(void)
15999 ClearPlayerAction();
16001 tape.pausing = TRUE;
16004 UpdateAndDisplayGameControlValues();
16006 DrawCompleteVideoDisplay();
16007 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16008 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16009 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16014 static void GameUndo(int steps)
16016 if (!CheckEngineSnapshotList())
16019 LoadEngineSnapshot_Undo(steps);
16024 static void GameRedo(int steps)
16026 if (!CheckEngineSnapshotList())
16029 LoadEngineSnapshot_Redo(steps);
16034 static void HandleGameButtonsExt(int id, int button)
16036 static boolean game_undo_executed = FALSE;
16037 int steps = BUTTON_STEPSIZE(button);
16038 boolean handle_game_buttons =
16039 (game_status == GAME_MODE_PLAYING ||
16040 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16042 if (!handle_game_buttons)
16047 case GAME_CTRL_ID_STOP:
16048 case GAME_CTRL_ID_PANEL_STOP:
16049 case GAME_CTRL_ID_TOUCH_STOP:
16050 if (game_status == GAME_MODE_MAIN)
16056 RequestQuitGame(TRUE);
16060 case GAME_CTRL_ID_PAUSE:
16061 case GAME_CTRL_ID_PAUSE2:
16062 case GAME_CTRL_ID_PANEL_PAUSE:
16063 case GAME_CTRL_ID_TOUCH_PAUSE:
16064 if (network.enabled && game_status == GAME_MODE_PLAYING)
16067 SendToServer_ContinuePlaying();
16069 SendToServer_PausePlaying();
16072 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16074 game_undo_executed = FALSE;
16078 case GAME_CTRL_ID_PLAY:
16079 case GAME_CTRL_ID_PANEL_PLAY:
16080 if (game_status == GAME_MODE_MAIN)
16082 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16084 else if (tape.pausing)
16086 if (network.enabled)
16087 SendToServer_ContinuePlaying();
16089 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16093 case GAME_CTRL_ID_UNDO:
16094 // Important: When using "save snapshot when collecting an item" mode,
16095 // load last (current) snapshot for first "undo" after pressing "pause"
16096 // (else the last-but-one snapshot would be loaded, because the snapshot
16097 // pointer already points to the last snapshot when pressing "pause",
16098 // which is fine for "every step/move" mode, but not for "every collect")
16099 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16100 !game_undo_executed)
16103 game_undo_executed = TRUE;
16108 case GAME_CTRL_ID_REDO:
16112 case GAME_CTRL_ID_SAVE:
16116 case GAME_CTRL_ID_LOAD:
16120 case SOUND_CTRL_ID_MUSIC:
16121 case SOUND_CTRL_ID_PANEL_MUSIC:
16122 if (setup.sound_music)
16124 setup.sound_music = FALSE;
16128 else if (audio.music_available)
16130 setup.sound = setup.sound_music = TRUE;
16132 SetAudioMode(setup.sound);
16134 if (game_status == GAME_MODE_PLAYING)
16138 RedrawSoundButtonGadget(id);
16142 case SOUND_CTRL_ID_LOOPS:
16143 case SOUND_CTRL_ID_PANEL_LOOPS:
16144 if (setup.sound_loops)
16145 setup.sound_loops = FALSE;
16146 else if (audio.loops_available)
16148 setup.sound = setup.sound_loops = TRUE;
16150 SetAudioMode(setup.sound);
16153 RedrawSoundButtonGadget(id);
16157 case SOUND_CTRL_ID_SIMPLE:
16158 case SOUND_CTRL_ID_PANEL_SIMPLE:
16159 if (setup.sound_simple)
16160 setup.sound_simple = FALSE;
16161 else if (audio.sound_available)
16163 setup.sound = setup.sound_simple = TRUE;
16165 SetAudioMode(setup.sound);
16168 RedrawSoundButtonGadget(id);
16177 static void HandleGameButtons(struct GadgetInfo *gi)
16179 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16182 void HandleSoundButtonKeys(Key key)
16184 if (key == setup.shortcut.sound_simple)
16185 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16186 else if (key == setup.shortcut.sound_loops)
16187 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16188 else if (key == setup.shortcut.sound_music)
16189 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);