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;
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);
2843 // --------------------------------------------------------------------------
2844 // set flags for bugs and changes according to active game engine version
2845 // --------------------------------------------------------------------------
2848 Summary of bugfix/change:
2849 Fixed handling for custom elements that change when pushed by the player.
2851 Fixed/changed in version:
2855 Before 3.1.0, custom elements that "change when pushing" changed directly
2856 after the player started pushing them (until then handled in "DigField()").
2857 Since 3.1.0, these custom elements are not changed until the "pushing"
2858 move of the element is finished (now handled in "ContinueMoving()").
2860 Affected levels/tapes:
2861 The first condition is generally needed for all levels/tapes before version
2862 3.1.0, which might use the old behaviour before it was changed; known tapes
2863 that are affected are some tapes from the level set "Walpurgis Gardens" by
2865 The second condition is an exception from the above case and is needed for
2866 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2867 above (including some development versions of 3.1.0), but before it was
2868 known that this change would break tapes like the above and was fixed in
2869 3.1.1, so that the changed behaviour was active although the engine version
2870 while recording maybe was before 3.1.0. There is at least one tape that is
2871 affected by this exception, which is the tape for the one-level set "Bug
2872 Machine" by Juergen Bonhagen.
2875 game.use_change_when_pushing_bug =
2876 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2878 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2879 tape.game_version < VERSION_IDENT(3,1,1,0)));
2882 Summary of bugfix/change:
2883 Fixed handling for blocking the field the player leaves when moving.
2885 Fixed/changed in version:
2889 Before 3.1.1, when "block last field when moving" was enabled, the field
2890 the player is leaving when moving was blocked for the time of the move,
2891 and was directly unblocked afterwards. This resulted in the last field
2892 being blocked for exactly one less than the number of frames of one player
2893 move. Additionally, even when blocking was disabled, the last field was
2894 blocked for exactly one frame.
2895 Since 3.1.1, due to changes in player movement handling, the last field
2896 is not blocked at all when blocking is disabled. When blocking is enabled,
2897 the last field is blocked for exactly the number of frames of one player
2898 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2899 last field is blocked for exactly one more than the number of frames of
2902 Affected levels/tapes:
2903 (!!! yet to be determined -- probably many !!!)
2906 game.use_block_last_field_bug =
2907 (game.engine_version < VERSION_IDENT(3,1,1,0));
2909 /* various special flags and settings for native Emerald Mine game engine */
2911 game_em.use_single_button =
2912 (game.engine_version > VERSION_IDENT(4,0,0,2));
2914 game_em.use_snap_key_bug =
2915 (game.engine_version < VERSION_IDENT(4,0,1,0));
2917 game_em.use_old_explosions =
2918 (game.engine_version < VERSION_IDENT(4,1,4,2));
2920 // --------------------------------------------------------------------------
2922 // set maximal allowed number of custom element changes per game frame
2923 game.max_num_changes_per_frame = 1;
2925 // default scan direction: scan playfield from top/left to bottom/right
2926 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
2928 // dynamically adjust element properties according to game engine version
2929 InitElementPropertiesEngine(game.engine_version);
2932 printf("level %d: level version == %06d\n", level_nr, level.game_version);
2933 printf(" tape version == %06d [%s] [file: %06d]\n",
2934 tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
2936 printf(" => game.engine_version == %06d\n", game.engine_version);
2939 // ---------- initialize player's initial move delay ------------------------
2941 // dynamically adjust player properties according to level information
2942 for (i = 0; i < MAX_PLAYERS; i++)
2943 game.initial_move_delay_value[i] =
2944 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
2946 // dynamically adjust player properties according to game engine version
2947 for (i = 0; i < MAX_PLAYERS; i++)
2948 game.initial_move_delay[i] =
2949 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
2950 game.initial_move_delay_value[i] : 0);
2952 // ---------- initialize player's initial push delay ------------------------
2954 // dynamically adjust player properties according to game engine version
2955 game.initial_push_delay_value =
2956 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
2958 // ---------- initialize changing elements ----------------------------------
2960 // initialize changing elements information
2961 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2963 struct ElementInfo *ei = &element_info[i];
2965 // this pointer might have been changed in the level editor
2966 ei->change = &ei->change_page[0];
2968 if (!IS_CUSTOM_ELEMENT(i))
2970 ei->change->target_element = EL_EMPTY_SPACE;
2971 ei->change->delay_fixed = 0;
2972 ei->change->delay_random = 0;
2973 ei->change->delay_frames = 1;
2976 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
2978 ei->has_change_event[j] = FALSE;
2980 ei->event_page_nr[j] = 0;
2981 ei->event_page[j] = &ei->change_page[0];
2985 // add changing elements from pre-defined list
2986 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
2988 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
2989 struct ElementInfo *ei = &element_info[ch_delay->element];
2991 ei->change->target_element = ch_delay->target_element;
2992 ei->change->delay_fixed = ch_delay->change_delay;
2994 ei->change->pre_change_function = ch_delay->pre_change_function;
2995 ei->change->change_function = ch_delay->change_function;
2996 ei->change->post_change_function = ch_delay->post_change_function;
2998 ei->change->can_change = TRUE;
2999 ei->change->can_change_or_has_action = TRUE;
3001 ei->has_change_event[CE_DELAY] = TRUE;
3003 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3004 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3007 // ---------- initialize internal run-time variables ------------------------
3009 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3011 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3013 for (j = 0; j < ei->num_change_pages; j++)
3015 ei->change_page[j].can_change_or_has_action =
3016 (ei->change_page[j].can_change |
3017 ei->change_page[j].has_action);
3021 // add change events from custom element configuration
3022 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3024 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3026 for (j = 0; j < ei->num_change_pages; j++)
3028 if (!ei->change_page[j].can_change_or_has_action)
3031 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3033 // only add event page for the first page found with this event
3034 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3036 ei->has_change_event[k] = TRUE;
3038 ei->event_page_nr[k] = j;
3039 ei->event_page[k] = &ei->change_page[j];
3045 // ---------- initialize reference elements in change conditions ------------
3047 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3049 int element = EL_CUSTOM_START + i;
3050 struct ElementInfo *ei = &element_info[element];
3052 for (j = 0; j < ei->num_change_pages; j++)
3054 int trigger_element = ei->change_page[j].initial_trigger_element;
3056 if (trigger_element >= EL_PREV_CE_8 &&
3057 trigger_element <= EL_NEXT_CE_8)
3058 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3060 ei->change_page[j].trigger_element = trigger_element;
3064 // ---------- initialize run-time trigger player and element ----------------
3066 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3068 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3070 for (j = 0; j < ei->num_change_pages; j++)
3072 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3073 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3074 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3075 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3076 ei->change_page[j].actual_trigger_ce_value = 0;
3077 ei->change_page[j].actual_trigger_ce_score = 0;
3081 // ---------- initialize trigger events -------------------------------------
3083 // initialize trigger events information
3084 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3085 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3086 trigger_events[i][j] = FALSE;
3088 // add trigger events from element change event properties
3089 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3091 struct ElementInfo *ei = &element_info[i];
3093 for (j = 0; j < ei->num_change_pages; j++)
3095 if (!ei->change_page[j].can_change_or_has_action)
3098 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3100 int trigger_element = ei->change_page[j].trigger_element;
3102 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3104 if (ei->change_page[j].has_event[k])
3106 if (IS_GROUP_ELEMENT(trigger_element))
3108 struct ElementGroupInfo *group =
3109 element_info[trigger_element].group;
3111 for (l = 0; l < group->num_elements_resolved; l++)
3112 trigger_events[group->element_resolved[l]][k] = TRUE;
3114 else if (trigger_element == EL_ANY_ELEMENT)
3115 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3116 trigger_events[l][k] = TRUE;
3118 trigger_events[trigger_element][k] = TRUE;
3125 // ---------- initialize push delay -----------------------------------------
3127 // initialize push delay values to default
3128 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3130 if (!IS_CUSTOM_ELEMENT(i))
3132 // set default push delay values (corrected since version 3.0.7-1)
3133 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3135 element_info[i].push_delay_fixed = 2;
3136 element_info[i].push_delay_random = 8;
3140 element_info[i].push_delay_fixed = 8;
3141 element_info[i].push_delay_random = 8;
3146 // set push delay value for certain elements from pre-defined list
3147 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3149 int e = push_delay_list[i].element;
3151 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3152 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3155 // set push delay value for Supaplex elements for newer engine versions
3156 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3158 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3160 if (IS_SP_ELEMENT(i))
3162 // set SP push delay to just enough to push under a falling zonk
3163 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3165 element_info[i].push_delay_fixed = delay;
3166 element_info[i].push_delay_random = 0;
3171 // ---------- initialize move stepsize --------------------------------------
3173 // initialize move stepsize values to default
3174 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3175 if (!IS_CUSTOM_ELEMENT(i))
3176 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3178 // set move stepsize value for certain elements from pre-defined list
3179 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3181 int e = move_stepsize_list[i].element;
3183 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3186 // ---------- initialize collect score --------------------------------------
3188 // initialize collect score values for custom elements from initial value
3189 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3190 if (IS_CUSTOM_ELEMENT(i))
3191 element_info[i].collect_score = element_info[i].collect_score_initial;
3193 // ---------- initialize collect count --------------------------------------
3195 // initialize collect count values for non-custom elements
3196 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3197 if (!IS_CUSTOM_ELEMENT(i))
3198 element_info[i].collect_count_initial = 0;
3200 // add collect count values for all elements from pre-defined list
3201 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3202 element_info[collect_count_list[i].element].collect_count_initial =
3203 collect_count_list[i].count;
3205 // ---------- initialize access direction -----------------------------------
3207 // initialize access direction values to default (access from every side)
3208 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3209 if (!IS_CUSTOM_ELEMENT(i))
3210 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3212 // set access direction value for certain elements from pre-defined list
3213 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3214 element_info[access_direction_list[i].element].access_direction =
3215 access_direction_list[i].direction;
3217 // ---------- initialize explosion content ----------------------------------
3218 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3220 if (IS_CUSTOM_ELEMENT(i))
3223 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3225 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3227 element_info[i].content.e[x][y] =
3228 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3229 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3230 i == EL_PLAYER_3 ? EL_EMERALD :
3231 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3232 i == EL_MOLE ? EL_EMERALD_RED :
3233 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3234 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3235 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3236 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3237 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3238 i == EL_WALL_EMERALD ? EL_EMERALD :
3239 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3240 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3241 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3242 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3243 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3244 i == EL_WALL_PEARL ? EL_PEARL :
3245 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3250 // ---------- initialize recursion detection --------------------------------
3251 recursion_loop_depth = 0;
3252 recursion_loop_detected = FALSE;
3253 recursion_loop_element = EL_UNDEFINED;
3255 // ---------- initialize graphics engine ------------------------------------
3256 game.scroll_delay_value =
3257 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3258 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3259 !setup.forced_scroll_delay ? 0 :
3260 setup.scroll_delay ? setup.scroll_delay_value : 0);
3261 game.scroll_delay_value =
3262 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3264 // ---------- initialize game engine snapshots ------------------------------
3265 for (i = 0; i < MAX_PLAYERS; i++)
3266 game.snapshot.last_action[i] = 0;
3267 game.snapshot.changed_action = FALSE;
3268 game.snapshot.collected_item = FALSE;
3269 game.snapshot.mode =
3270 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3271 SNAPSHOT_MODE_EVERY_STEP :
3272 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3273 SNAPSHOT_MODE_EVERY_MOVE :
3274 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3275 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3276 game.snapshot.save_snapshot = FALSE;
3278 // ---------- initialize level time for Supaplex engine ---------------------
3279 // Supaplex levels with time limit currently unsupported -- should be added
3280 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3283 // ---------- initialize mask for handling game action events ---------------
3285 // set game action events mask to default value
3286 game.event_mask = GAME_EVENTS_DEFAULT;
3288 // when using Mirror Magic game engine, handle mouse events only
3289 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3290 game.event_mask = GAME_EVENTS_MOUSE;
3292 // check for custom elements with mouse click events
3293 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3295 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3297 int element = EL_CUSTOM_START + i;
3299 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3300 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3301 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3302 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3303 game.event_mask = GAME_EVENTS_MOUSE;
3308 static int get_num_special_action(int element, int action_first,
3311 int num_special_action = 0;
3314 for (i = action_first; i <= action_last; i++)
3316 boolean found = FALSE;
3318 for (j = 0; j < NUM_DIRECTIONS; j++)
3319 if (el_act_dir2img(element, i, j) !=
3320 el_act_dir2img(element, ACTION_DEFAULT, j))
3324 num_special_action++;
3329 return num_special_action;
3333 // ============================================================================
3335 // ----------------------------------------------------------------------------
3336 // initialize and start new game
3337 // ============================================================================
3339 #if DEBUG_INIT_PLAYER
3340 static void DebugPrintPlayerStatus(char *message)
3347 printf("%s:\n", message);
3349 for (i = 0; i < MAX_PLAYERS; i++)
3351 struct PlayerInfo *player = &stored_player[i];
3353 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3357 player->connected_locally,
3358 player->connected_network,
3361 if (local_player == player)
3362 printf(" (local player)");
3371 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3372 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3373 int fade_mask = REDRAW_FIELD;
3375 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3376 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3377 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3378 int initial_move_dir = MV_DOWN;
3381 // required here to update video display before fading (FIX THIS)
3382 DrawMaskedBorder(REDRAW_DOOR_2);
3384 if (!game.restart_level)
3385 CloseDoor(DOOR_CLOSE_1);
3387 SetGameStatus(GAME_MODE_PLAYING);
3389 if (level_editor_test_game)
3390 FadeSkipNextFadeOut();
3392 FadeSetEnterScreen();
3395 fade_mask = REDRAW_ALL;
3397 FadeLevelSoundsAndMusic();
3399 ExpireSoundLoops(TRUE);
3403 if (level_editor_test_game)
3404 FadeSkipNextFadeIn();
3406 // needed if different viewport properties defined for playing
3407 ChangeViewportPropertiesIfNeeded();
3411 DrawCompleteVideoDisplay();
3413 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3416 InitGameControlValues();
3418 // initialize tape action events from game when recording tape
3420 tape.event_mask = game.event_mask;
3422 // don't play tapes over network
3423 network_playing = (network.enabled && !tape.playing);
3425 for (i = 0; i < MAX_PLAYERS; i++)
3427 struct PlayerInfo *player = &stored_player[i];
3429 player->index_nr = i;
3430 player->index_bit = (1 << i);
3431 player->element_nr = EL_PLAYER_1 + i;
3433 player->present = FALSE;
3434 player->active = FALSE;
3435 player->mapped = FALSE;
3437 player->killed = FALSE;
3438 player->reanimated = FALSE;
3439 player->buried = FALSE;
3442 player->effective_action = 0;
3443 player->programmed_action = 0;
3444 player->snap_action = 0;
3446 player->mouse_action.lx = 0;
3447 player->mouse_action.ly = 0;
3448 player->mouse_action.button = 0;
3449 player->mouse_action.button_hint = 0;
3451 player->effective_mouse_action.lx = 0;
3452 player->effective_mouse_action.ly = 0;
3453 player->effective_mouse_action.button = 0;
3454 player->effective_mouse_action.button_hint = 0;
3456 for (j = 0; j < MAX_NUM_KEYS; j++)
3457 player->key[j] = FALSE;
3459 player->num_white_keys = 0;
3461 player->dynabomb_count = 0;
3462 player->dynabomb_size = 1;
3463 player->dynabombs_left = 0;
3464 player->dynabomb_xl = FALSE;
3466 player->MovDir = initial_move_dir;
3469 player->GfxDir = initial_move_dir;
3470 player->GfxAction = ACTION_DEFAULT;
3472 player->StepFrame = 0;
3474 player->initial_element = player->element_nr;
3475 player->artwork_element =
3476 (level.use_artwork_element[i] ? level.artwork_element[i] :
3477 player->element_nr);
3478 player->use_murphy = FALSE;
3480 player->block_last_field = FALSE; // initialized in InitPlayerField()
3481 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3483 player->gravity = level.initial_player_gravity[i];
3485 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3487 player->actual_frame_counter = 0;
3489 player->step_counter = 0;
3491 player->last_move_dir = initial_move_dir;
3493 player->is_active = FALSE;
3495 player->is_waiting = FALSE;
3496 player->is_moving = FALSE;
3497 player->is_auto_moving = FALSE;
3498 player->is_digging = FALSE;
3499 player->is_snapping = FALSE;
3500 player->is_collecting = FALSE;
3501 player->is_pushing = FALSE;
3502 player->is_switching = FALSE;
3503 player->is_dropping = FALSE;
3504 player->is_dropping_pressed = FALSE;
3506 player->is_bored = FALSE;
3507 player->is_sleeping = FALSE;
3509 player->was_waiting = TRUE;
3510 player->was_moving = FALSE;
3511 player->was_snapping = FALSE;
3512 player->was_dropping = FALSE;
3514 player->force_dropping = FALSE;
3516 player->frame_counter_bored = -1;
3517 player->frame_counter_sleeping = -1;
3519 player->anim_delay_counter = 0;
3520 player->post_delay_counter = 0;
3522 player->dir_waiting = initial_move_dir;
3523 player->action_waiting = ACTION_DEFAULT;
3524 player->last_action_waiting = ACTION_DEFAULT;
3525 player->special_action_bored = ACTION_DEFAULT;
3526 player->special_action_sleeping = ACTION_DEFAULT;
3528 player->switch_x = -1;
3529 player->switch_y = -1;
3531 player->drop_x = -1;
3532 player->drop_y = -1;
3534 player->show_envelope = 0;
3536 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3538 player->push_delay = -1; // initialized when pushing starts
3539 player->push_delay_value = game.initial_push_delay_value;
3541 player->drop_delay = 0;
3542 player->drop_pressed_delay = 0;
3544 player->last_jx = -1;
3545 player->last_jy = -1;
3549 player->shield_normal_time_left = 0;
3550 player->shield_deadly_time_left = 0;
3552 player->inventory_infinite_element = EL_UNDEFINED;
3553 player->inventory_size = 0;
3555 if (level.use_initial_inventory[i])
3557 for (j = 0; j < level.initial_inventory_size[i]; j++)
3559 int element = level.initial_inventory_content[i][j];
3560 int collect_count = element_info[element].collect_count_initial;
3563 if (!IS_CUSTOM_ELEMENT(element))
3566 if (collect_count == 0)
3567 player->inventory_infinite_element = element;
3569 for (k = 0; k < collect_count; k++)
3570 if (player->inventory_size < MAX_INVENTORY_SIZE)
3571 player->inventory_element[player->inventory_size++] = element;
3575 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3576 SnapField(player, 0, 0);
3578 map_player_action[i] = i;
3581 network_player_action_received = FALSE;
3583 // initial null action
3584 if (network_playing)
3585 SendToServer_MovePlayer(MV_NONE);
3590 TimeLeft = level.time;
3593 ScreenMovDir = MV_NONE;
3597 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3599 game.robot_wheel_x = -1;
3600 game.robot_wheel_y = -1;
3605 game.all_players_gone = FALSE;
3607 game.LevelSolved = FALSE;
3608 game.GameOver = FALSE;
3610 game.GamePlayed = !tape.playing;
3612 game.LevelSolved_GameWon = FALSE;
3613 game.LevelSolved_GameEnd = FALSE;
3614 game.LevelSolved_SaveTape = FALSE;
3615 game.LevelSolved_SaveScore = FALSE;
3617 game.LevelSolved_CountingTime = 0;
3618 game.LevelSolved_CountingScore = 0;
3619 game.LevelSolved_CountingHealth = 0;
3621 game.panel.active = TRUE;
3623 game.no_time_limit = (level.time == 0);
3625 game.yamyam_content_nr = 0;
3626 game.robot_wheel_active = FALSE;
3627 game.magic_wall_active = FALSE;
3628 game.magic_wall_time_left = 0;
3629 game.light_time_left = 0;
3630 game.timegate_time_left = 0;
3631 game.switchgate_pos = 0;
3632 game.wind_direction = level.wind_direction_initial;
3635 game.score_final = 0;
3637 game.health = MAX_HEALTH;
3638 game.health_final = MAX_HEALTH;
3640 game.gems_still_needed = level.gems_needed;
3641 game.sokoban_fields_still_needed = 0;
3642 game.sokoban_objects_still_needed = 0;
3643 game.lights_still_needed = 0;
3644 game.players_still_needed = 0;
3645 game.friends_still_needed = 0;
3647 game.lenses_time_left = 0;
3648 game.magnify_time_left = 0;
3650 game.ball_active = level.ball_active_initial;
3651 game.ball_content_nr = 0;
3653 game.explosions_delayed = TRUE;
3655 game.envelope_active = FALSE;
3657 for (i = 0; i < NUM_BELTS; i++)
3659 game.belt_dir[i] = MV_NONE;
3660 game.belt_dir_nr[i] = 3; // not moving, next moving left
3663 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3664 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3666 #if DEBUG_INIT_PLAYER
3667 DebugPrintPlayerStatus("Player status at level initialization");
3670 SCAN_PLAYFIELD(x, y)
3672 Feld[x][y] = Last[x][y] = level.field[x][y];
3673 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3674 ChangeDelay[x][y] = 0;
3675 ChangePage[x][y] = -1;
3676 CustomValue[x][y] = 0; // initialized in InitField()
3677 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3679 WasJustMoving[x][y] = 0;
3680 WasJustFalling[x][y] = 0;
3681 CheckCollision[x][y] = 0;
3682 CheckImpact[x][y] = 0;
3684 Pushed[x][y] = FALSE;
3686 ChangeCount[x][y] = 0;
3687 ChangeEvent[x][y] = -1;
3689 ExplodePhase[x][y] = 0;
3690 ExplodeDelay[x][y] = 0;
3691 ExplodeField[x][y] = EX_TYPE_NONE;
3693 RunnerVisit[x][y] = 0;
3694 PlayerVisit[x][y] = 0;
3697 GfxRandom[x][y] = INIT_GFX_RANDOM();
3698 GfxElement[x][y] = EL_UNDEFINED;
3699 GfxAction[x][y] = ACTION_DEFAULT;
3700 GfxDir[x][y] = MV_NONE;
3701 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3704 SCAN_PLAYFIELD(x, y)
3706 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3708 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3710 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3713 InitField(x, y, TRUE);
3715 ResetGfxAnimation(x, y);
3720 for (i = 0; i < MAX_PLAYERS; i++)
3722 struct PlayerInfo *player = &stored_player[i];
3724 // set number of special actions for bored and sleeping animation
3725 player->num_special_action_bored =
3726 get_num_special_action(player->artwork_element,
3727 ACTION_BORING_1, ACTION_BORING_LAST);
3728 player->num_special_action_sleeping =
3729 get_num_special_action(player->artwork_element,
3730 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3733 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3734 emulate_sb ? EMU_SOKOBAN :
3735 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3737 // initialize type of slippery elements
3738 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3740 if (!IS_CUSTOM_ELEMENT(i))
3742 // default: elements slip down either to the left or right randomly
3743 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3745 // SP style elements prefer to slip down on the left side
3746 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3747 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3749 // BD style elements prefer to slip down on the left side
3750 if (game.emulation == EMU_BOULDERDASH)
3751 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3755 // initialize explosion and ignition delay
3756 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3758 if (!IS_CUSTOM_ELEMENT(i))
3761 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3762 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3763 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3764 int last_phase = (num_phase + 1) * delay;
3765 int half_phase = (num_phase / 2) * delay;
3767 element_info[i].explosion_delay = last_phase - 1;
3768 element_info[i].ignition_delay = half_phase;
3770 if (i == EL_BLACK_ORB)
3771 element_info[i].ignition_delay = 1;
3775 // correct non-moving belts to start moving left
3776 for (i = 0; i < NUM_BELTS; i++)
3777 if (game.belt_dir[i] == MV_NONE)
3778 game.belt_dir_nr[i] = 3; // not moving, next moving left
3780 #if USE_NEW_PLAYER_ASSIGNMENTS
3781 // use preferred player also in local single-player mode
3782 if (!network.enabled && !game.team_mode)
3784 int new_index_nr = setup.network_player_nr;
3786 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3788 for (i = 0; i < MAX_PLAYERS; i++)
3789 stored_player[i].connected_locally = FALSE;
3791 stored_player[new_index_nr].connected_locally = TRUE;
3795 for (i = 0; i < MAX_PLAYERS; i++)
3797 stored_player[i].connected = FALSE;
3799 // in network game mode, the local player might not be the first player
3800 if (stored_player[i].connected_locally)
3801 local_player = &stored_player[i];
3804 if (!network.enabled)
3805 local_player->connected = TRUE;
3809 for (i = 0; i < MAX_PLAYERS; i++)
3810 stored_player[i].connected = tape.player_participates[i];
3812 else if (network.enabled)
3814 // add team mode players connected over the network (needed for correct
3815 // assignment of player figures from level to locally playing players)
3817 for (i = 0; i < MAX_PLAYERS; i++)
3818 if (stored_player[i].connected_network)
3819 stored_player[i].connected = TRUE;
3821 else if (game.team_mode)
3823 // try to guess locally connected team mode players (needed for correct
3824 // assignment of player figures from level to locally playing players)
3826 for (i = 0; i < MAX_PLAYERS; i++)
3827 if (setup.input[i].use_joystick ||
3828 setup.input[i].key.left != KSYM_UNDEFINED)
3829 stored_player[i].connected = TRUE;
3832 #if DEBUG_INIT_PLAYER
3833 DebugPrintPlayerStatus("Player status after level initialization");
3836 #if DEBUG_INIT_PLAYER
3838 printf("Reassigning players ...\n");
3841 // check if any connected player was not found in playfield
3842 for (i = 0; i < MAX_PLAYERS; i++)
3844 struct PlayerInfo *player = &stored_player[i];
3846 if (player->connected && !player->present)
3848 struct PlayerInfo *field_player = NULL;
3850 #if DEBUG_INIT_PLAYER
3852 printf("- looking for field player for player %d ...\n", i + 1);
3855 // assign first free player found that is present in the playfield
3857 // first try: look for unmapped playfield player that is not connected
3858 for (j = 0; j < MAX_PLAYERS; j++)
3859 if (field_player == NULL &&
3860 stored_player[j].present &&
3861 !stored_player[j].mapped &&
3862 !stored_player[j].connected)
3863 field_player = &stored_player[j];
3865 // second try: look for *any* unmapped playfield player
3866 for (j = 0; j < MAX_PLAYERS; j++)
3867 if (field_player == NULL &&
3868 stored_player[j].present &&
3869 !stored_player[j].mapped)
3870 field_player = &stored_player[j];
3872 if (field_player != NULL)
3874 int jx = field_player->jx, jy = field_player->jy;
3876 #if DEBUG_INIT_PLAYER
3878 printf("- found player %d\n", field_player->index_nr + 1);
3881 player->present = FALSE;
3882 player->active = FALSE;
3884 field_player->present = TRUE;
3885 field_player->active = TRUE;
3888 player->initial_element = field_player->initial_element;
3889 player->artwork_element = field_player->artwork_element;
3891 player->block_last_field = field_player->block_last_field;
3892 player->block_delay_adjustment = field_player->block_delay_adjustment;
3895 StorePlayer[jx][jy] = field_player->element_nr;
3897 field_player->jx = field_player->last_jx = jx;
3898 field_player->jy = field_player->last_jy = jy;
3900 if (local_player == player)
3901 local_player = field_player;
3903 map_player_action[field_player->index_nr] = i;
3905 field_player->mapped = TRUE;
3907 #if DEBUG_INIT_PLAYER
3909 printf("- map_player_action[%d] == %d\n",
3910 field_player->index_nr + 1, i + 1);
3915 if (player->connected && player->present)
3916 player->mapped = TRUE;
3919 #if DEBUG_INIT_PLAYER
3920 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
3925 // check if any connected player was not found in playfield
3926 for (i = 0; i < MAX_PLAYERS; i++)
3928 struct PlayerInfo *player = &stored_player[i];
3930 if (player->connected && !player->present)
3932 for (j = 0; j < MAX_PLAYERS; j++)
3934 struct PlayerInfo *field_player = &stored_player[j];
3935 int jx = field_player->jx, jy = field_player->jy;
3937 // assign first free player found that is present in the playfield
3938 if (field_player->present && !field_player->connected)
3940 player->present = TRUE;
3941 player->active = TRUE;
3943 field_player->present = FALSE;
3944 field_player->active = FALSE;
3946 player->initial_element = field_player->initial_element;
3947 player->artwork_element = field_player->artwork_element;
3949 player->block_last_field = field_player->block_last_field;
3950 player->block_delay_adjustment = field_player->block_delay_adjustment;
3952 StorePlayer[jx][jy] = player->element_nr;
3954 player->jx = player->last_jx = jx;
3955 player->jy = player->last_jy = jy;
3965 printf("::: local_player->present == %d\n", local_player->present);
3968 // set focus to local player for network games, else to all players
3969 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
3970 game.centered_player_nr_next = game.centered_player_nr;
3971 game.set_centered_player = FALSE;
3972 game.set_centered_player_wrap = FALSE;
3974 if (network_playing && tape.recording)
3976 // store client dependent player focus when recording network games
3977 tape.centered_player_nr_next = game.centered_player_nr_next;
3978 tape.set_centered_player = TRUE;
3983 // when playing a tape, eliminate all players who do not participate
3985 #if USE_NEW_PLAYER_ASSIGNMENTS
3987 if (!game.team_mode)
3989 for (i = 0; i < MAX_PLAYERS; i++)
3991 if (stored_player[i].active &&
3992 !tape.player_participates[map_player_action[i]])
3994 struct PlayerInfo *player = &stored_player[i];
3995 int jx = player->jx, jy = player->jy;
3997 #if DEBUG_INIT_PLAYER
3999 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
4002 player->active = FALSE;
4003 StorePlayer[jx][jy] = 0;
4004 Feld[jx][jy] = EL_EMPTY;
4011 for (i = 0; i < MAX_PLAYERS; i++)
4013 if (stored_player[i].active &&
4014 !tape.player_participates[i])
4016 struct PlayerInfo *player = &stored_player[i];
4017 int jx = player->jx, jy = player->jy;
4019 player->active = FALSE;
4020 StorePlayer[jx][jy] = 0;
4021 Feld[jx][jy] = EL_EMPTY;
4026 else if (!network.enabled && !game.team_mode) // && !tape.playing
4028 // when in single player mode, eliminate all but the local player
4030 for (i = 0; i < MAX_PLAYERS; i++)
4032 struct PlayerInfo *player = &stored_player[i];
4034 if (player->active && player != local_player)
4036 int jx = player->jx, jy = player->jy;
4038 player->active = FALSE;
4039 player->present = FALSE;
4041 StorePlayer[jx][jy] = 0;
4042 Feld[jx][jy] = EL_EMPTY;
4047 for (i = 0; i < MAX_PLAYERS; i++)
4048 if (stored_player[i].active)
4049 game.players_still_needed++;
4051 if (level.solved_by_one_player)
4052 game.players_still_needed = 1;
4054 // when recording the game, store which players take part in the game
4057 #if USE_NEW_PLAYER_ASSIGNMENTS
4058 for (i = 0; i < MAX_PLAYERS; i++)
4059 if (stored_player[i].connected)
4060 tape.player_participates[i] = TRUE;
4062 for (i = 0; i < MAX_PLAYERS; i++)
4063 if (stored_player[i].active)
4064 tape.player_participates[i] = TRUE;
4068 #if DEBUG_INIT_PLAYER
4069 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4072 if (BorderElement == EL_EMPTY)
4075 SBX_Right = lev_fieldx - SCR_FIELDX;
4077 SBY_Lower = lev_fieldy - SCR_FIELDY;
4082 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4084 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4087 if (full_lev_fieldx <= SCR_FIELDX)
4088 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4089 if (full_lev_fieldy <= SCR_FIELDY)
4090 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4092 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4094 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4097 // if local player not found, look for custom element that might create
4098 // the player (make some assumptions about the right custom element)
4099 if (!local_player->present)
4101 int start_x = 0, start_y = 0;
4102 int found_rating = 0;
4103 int found_element = EL_UNDEFINED;
4104 int player_nr = local_player->index_nr;
4106 SCAN_PLAYFIELD(x, y)
4108 int element = Feld[x][y];
4113 if (level.use_start_element[player_nr] &&
4114 level.start_element[player_nr] == element &&
4121 found_element = element;
4124 if (!IS_CUSTOM_ELEMENT(element))
4127 if (CAN_CHANGE(element))
4129 for (i = 0; i < element_info[element].num_change_pages; i++)
4131 // check for player created from custom element as single target
4132 content = element_info[element].change_page[i].target_element;
4133 is_player = ELEM_IS_PLAYER(content);
4135 if (is_player && (found_rating < 3 ||
4136 (found_rating == 3 && element < found_element)))
4142 found_element = element;
4147 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4149 // check for player created from custom element as explosion content
4150 content = element_info[element].content.e[xx][yy];
4151 is_player = ELEM_IS_PLAYER(content);
4153 if (is_player && (found_rating < 2 ||
4154 (found_rating == 2 && element < found_element)))
4156 start_x = x + xx - 1;
4157 start_y = y + yy - 1;
4160 found_element = element;
4163 if (!CAN_CHANGE(element))
4166 for (i = 0; i < element_info[element].num_change_pages; i++)
4168 // check for player created from custom element as extended target
4170 element_info[element].change_page[i].target_content.e[xx][yy];
4172 is_player = ELEM_IS_PLAYER(content);
4174 if (is_player && (found_rating < 1 ||
4175 (found_rating == 1 && element < found_element)))
4177 start_x = x + xx - 1;
4178 start_y = y + yy - 1;
4181 found_element = element;
4187 scroll_x = SCROLL_POSITION_X(start_x);
4188 scroll_y = SCROLL_POSITION_Y(start_y);
4192 scroll_x = SCROLL_POSITION_X(local_player->jx);
4193 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4196 // !!! FIX THIS (START) !!!
4197 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4199 InitGameEngine_EM();
4201 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4203 InitGameEngine_SP();
4205 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4207 InitGameEngine_MM();
4211 DrawLevel(REDRAW_FIELD);
4214 // after drawing the level, correct some elements
4215 if (game.timegate_time_left == 0)
4216 CloseAllOpenTimegates();
4219 // blit playfield from scroll buffer to normal back buffer for fading in
4220 BlitScreenToBitmap(backbuffer);
4221 // !!! FIX THIS (END) !!!
4223 DrawMaskedBorder(fade_mask);
4228 // full screen redraw is required at this point in the following cases:
4229 // - special editor door undrawn when game was started from level editor
4230 // - drawing area (playfield) was changed and has to be removed completely
4231 redraw_mask = REDRAW_ALL;
4235 if (!game.restart_level)
4237 // copy default game door content to main double buffer
4239 // !!! CHECK AGAIN !!!
4240 SetPanelBackground();
4241 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4242 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4245 SetPanelBackground();
4246 SetDrawBackgroundMask(REDRAW_DOOR_1);
4248 UpdateAndDisplayGameControlValues();
4250 if (!game.restart_level)
4256 CreateGameButtons();
4261 // copy actual game door content to door double buffer for OpenDoor()
4262 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4264 OpenDoor(DOOR_OPEN_ALL);
4266 KeyboardAutoRepeatOffUnlessAutoplay();
4268 #if DEBUG_INIT_PLAYER
4269 DebugPrintPlayerStatus("Player status (final)");
4278 if (!game.restart_level && !tape.playing)
4280 LevelStats_incPlayed(level_nr);
4282 SaveLevelSetup_SeriesInfo();
4285 game.restart_level = FALSE;
4286 game.restart_game_message = NULL;
4287 game.request_active = FALSE;
4289 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4290 InitGameActions_MM();
4292 SaveEngineSnapshotToListInitial();
4294 if (!game.restart_level)
4296 PlaySound(SND_GAME_STARTING);
4298 if (setup.sound_music)
4303 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4304 int actual_player_x, int actual_player_y)
4306 // this is used for non-R'n'D game engines to update certain engine values
4308 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4310 actual_player_x = correctLevelPosX_EM(actual_player_x);
4311 actual_player_y = correctLevelPosY_EM(actual_player_y);
4314 // needed to determine if sounds are played within the visible screen area
4315 scroll_x = actual_scroll_x;
4316 scroll_y = actual_scroll_y;
4318 // needed to get player position for "follow finger" playing input method
4319 local_player->jx = actual_player_x;
4320 local_player->jy = actual_player_y;
4323 void InitMovDir(int x, int y)
4325 int i, element = Feld[x][y];
4326 static int xy[4][2] =
4333 static int direction[3][4] =
4335 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4336 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4337 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4346 Feld[x][y] = EL_BUG;
4347 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4350 case EL_SPACESHIP_RIGHT:
4351 case EL_SPACESHIP_UP:
4352 case EL_SPACESHIP_LEFT:
4353 case EL_SPACESHIP_DOWN:
4354 Feld[x][y] = EL_SPACESHIP;
4355 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4358 case EL_BD_BUTTERFLY_RIGHT:
4359 case EL_BD_BUTTERFLY_UP:
4360 case EL_BD_BUTTERFLY_LEFT:
4361 case EL_BD_BUTTERFLY_DOWN:
4362 Feld[x][y] = EL_BD_BUTTERFLY;
4363 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4366 case EL_BD_FIREFLY_RIGHT:
4367 case EL_BD_FIREFLY_UP:
4368 case EL_BD_FIREFLY_LEFT:
4369 case EL_BD_FIREFLY_DOWN:
4370 Feld[x][y] = EL_BD_FIREFLY;
4371 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4374 case EL_PACMAN_RIGHT:
4376 case EL_PACMAN_LEFT:
4377 case EL_PACMAN_DOWN:
4378 Feld[x][y] = EL_PACMAN;
4379 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4382 case EL_YAMYAM_LEFT:
4383 case EL_YAMYAM_RIGHT:
4385 case EL_YAMYAM_DOWN:
4386 Feld[x][y] = EL_YAMYAM;
4387 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4390 case EL_SP_SNIKSNAK:
4391 MovDir[x][y] = MV_UP;
4394 case EL_SP_ELECTRON:
4395 MovDir[x][y] = MV_LEFT;
4402 Feld[x][y] = EL_MOLE;
4403 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4407 if (IS_CUSTOM_ELEMENT(element))
4409 struct ElementInfo *ei = &element_info[element];
4410 int move_direction_initial = ei->move_direction_initial;
4411 int move_pattern = ei->move_pattern;
4413 if (move_direction_initial == MV_START_PREVIOUS)
4415 if (MovDir[x][y] != MV_NONE)
4418 move_direction_initial = MV_START_AUTOMATIC;
4421 if (move_direction_initial == MV_START_RANDOM)
4422 MovDir[x][y] = 1 << RND(4);
4423 else if (move_direction_initial & MV_ANY_DIRECTION)
4424 MovDir[x][y] = move_direction_initial;
4425 else if (move_pattern == MV_ALL_DIRECTIONS ||
4426 move_pattern == MV_TURNING_LEFT ||
4427 move_pattern == MV_TURNING_RIGHT ||
4428 move_pattern == MV_TURNING_LEFT_RIGHT ||
4429 move_pattern == MV_TURNING_RIGHT_LEFT ||
4430 move_pattern == MV_TURNING_RANDOM)
4431 MovDir[x][y] = 1 << RND(4);
4432 else if (move_pattern == MV_HORIZONTAL)
4433 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4434 else if (move_pattern == MV_VERTICAL)
4435 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4436 else if (move_pattern & MV_ANY_DIRECTION)
4437 MovDir[x][y] = element_info[element].move_pattern;
4438 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4439 move_pattern == MV_ALONG_RIGHT_SIDE)
4441 // use random direction as default start direction
4442 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4443 MovDir[x][y] = 1 << RND(4);
4445 for (i = 0; i < NUM_DIRECTIONS; i++)
4447 int x1 = x + xy[i][0];
4448 int y1 = y + xy[i][1];
4450 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4452 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4453 MovDir[x][y] = direction[0][i];
4455 MovDir[x][y] = direction[1][i];
4464 MovDir[x][y] = 1 << RND(4);
4466 if (element != EL_BUG &&
4467 element != EL_SPACESHIP &&
4468 element != EL_BD_BUTTERFLY &&
4469 element != EL_BD_FIREFLY)
4472 for (i = 0; i < NUM_DIRECTIONS; i++)
4474 int x1 = x + xy[i][0];
4475 int y1 = y + xy[i][1];
4477 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4479 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4481 MovDir[x][y] = direction[0][i];
4484 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4485 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4487 MovDir[x][y] = direction[1][i];
4496 GfxDir[x][y] = MovDir[x][y];
4499 void InitAmoebaNr(int x, int y)
4502 int group_nr = AmoebeNachbarNr(x, y);
4506 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4508 if (AmoebaCnt[i] == 0)
4516 AmoebaNr[x][y] = group_nr;
4517 AmoebaCnt[group_nr]++;
4518 AmoebaCnt2[group_nr]++;
4521 static void LevelSolved(void)
4523 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4524 game.players_still_needed > 0)
4527 game.LevelSolved = TRUE;
4528 game.GameOver = TRUE;
4530 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4531 game_em.lev->score :
4532 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4535 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4536 MM_HEALTH(game_mm.laser_overload_value) :
4539 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4540 game.LevelSolved_CountingScore = game.score_final;
4541 game.LevelSolved_CountingHealth = game.health_final;
4546 static int time_count_steps;
4547 static int time, time_final;
4548 static int score, score_final;
4549 static int health, health_final;
4550 static int game_over_delay_1 = 0;
4551 static int game_over_delay_2 = 0;
4552 static int game_over_delay_3 = 0;
4553 int game_over_delay_value_1 = 50;
4554 int game_over_delay_value_2 = 25;
4555 int game_over_delay_value_3 = 50;
4557 if (!game.LevelSolved_GameWon)
4561 // do not start end game actions before the player stops moving (to exit)
4562 if (local_player->active && local_player->MovPos)
4565 game.LevelSolved_GameWon = TRUE;
4566 game.LevelSolved_SaveTape = tape.recording;
4567 game.LevelSolved_SaveScore = !tape.playing;
4571 LevelStats_incSolved(level_nr);
4573 SaveLevelSetup_SeriesInfo();
4576 if (tape.auto_play) // tape might already be stopped here
4577 tape.auto_play_level_solved = TRUE;
4581 game_over_delay_1 = 0;
4582 game_over_delay_2 = 0;
4583 game_over_delay_3 = game_over_delay_value_3;
4585 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4586 score = score_final = game.score_final;
4587 health = health_final = game.health_final;
4589 if (level.score[SC_TIME_BONUS] > 0)
4594 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4596 else if (game.no_time_limit && TimePlayed < 999)
4599 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4602 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4604 game_over_delay_1 = game_over_delay_value_1;
4606 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4609 score_final += health * level.score[SC_TIME_BONUS];
4611 game_over_delay_2 = game_over_delay_value_2;
4614 game.score_final = score_final;
4615 game.health_final = health_final;
4618 if (level_editor_test_game)
4621 score = score_final;
4623 game.LevelSolved_CountingTime = time;
4624 game.LevelSolved_CountingScore = score;
4626 game_panel_controls[GAME_PANEL_TIME].value = time;
4627 game_panel_controls[GAME_PANEL_SCORE].value = score;
4629 DisplayGameControlValues();
4632 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4634 // check if last player has left the level
4635 if (game.exit_x >= 0 &&
4638 int x = game.exit_x;
4639 int y = game.exit_y;
4640 int element = Feld[x][y];
4642 // close exit door after last player
4643 if ((game.all_players_gone &&
4644 (element == EL_EXIT_OPEN ||
4645 element == EL_SP_EXIT_OPEN ||
4646 element == EL_STEEL_EXIT_OPEN)) ||
4647 element == EL_EM_EXIT_OPEN ||
4648 element == EL_EM_STEEL_EXIT_OPEN)
4652 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4653 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4654 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4655 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4656 EL_EM_STEEL_EXIT_CLOSING);
4658 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4661 // player disappears
4662 DrawLevelField(x, y);
4665 for (i = 0; i < MAX_PLAYERS; i++)
4667 struct PlayerInfo *player = &stored_player[i];
4669 if (player->present)
4671 RemovePlayer(player);
4673 // player disappears
4674 DrawLevelField(player->jx, player->jy);
4679 PlaySound(SND_GAME_WINNING);
4682 if (game_over_delay_1 > 0)
4684 game_over_delay_1--;
4689 if (time != time_final)
4691 int time_to_go = ABS(time_final - time);
4692 int time_count_dir = (time < time_final ? +1 : -1);
4694 if (time_to_go < time_count_steps)
4695 time_count_steps = 1;
4697 time += time_count_steps * time_count_dir;
4698 score += time_count_steps * level.score[SC_TIME_BONUS];
4700 game.LevelSolved_CountingTime = time;
4701 game.LevelSolved_CountingScore = score;
4703 game_panel_controls[GAME_PANEL_TIME].value = time;
4704 game_panel_controls[GAME_PANEL_SCORE].value = score;
4706 DisplayGameControlValues();
4708 if (time == time_final)
4709 StopSound(SND_GAME_LEVELTIME_BONUS);
4710 else if (setup.sound_loops)
4711 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4713 PlaySound(SND_GAME_LEVELTIME_BONUS);
4718 if (game_over_delay_2 > 0)
4720 game_over_delay_2--;
4725 if (health != health_final)
4727 int health_count_dir = (health < health_final ? +1 : -1);
4729 health += health_count_dir;
4730 score += level.score[SC_TIME_BONUS];
4732 game.LevelSolved_CountingHealth = health;
4733 game.LevelSolved_CountingScore = score;
4735 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4736 game_panel_controls[GAME_PANEL_SCORE].value = score;
4738 DisplayGameControlValues();
4740 if (health == health_final)
4741 StopSound(SND_GAME_LEVELTIME_BONUS);
4742 else if (setup.sound_loops)
4743 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4745 PlaySound(SND_GAME_LEVELTIME_BONUS);
4750 game.panel.active = FALSE;
4752 if (game_over_delay_3 > 0)
4754 game_over_delay_3--;
4764 // used instead of "level_nr" (needed for network games)
4765 int last_level_nr = levelset.level_nr;
4768 game.LevelSolved_GameEnd = TRUE;
4770 if (game.LevelSolved_SaveTape)
4772 // make sure that request dialog to save tape does not open door again
4773 if (!global.use_envelope_request)
4774 CloseDoor(DOOR_CLOSE_1);
4776 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4779 // if no tape is to be saved, close both doors simultaneously
4780 CloseDoor(DOOR_CLOSE_ALL);
4782 if (level_editor_test_game)
4784 SetGameStatus(GAME_MODE_MAIN);
4791 if (!game.LevelSolved_SaveScore)
4793 SetGameStatus(GAME_MODE_MAIN);
4800 if (level_nr == leveldir_current->handicap_level)
4802 leveldir_current->handicap_level++;
4804 SaveLevelSetup_SeriesInfo();
4807 if (setup.increment_levels &&
4808 level_nr < leveldir_current->last_level &&
4811 level_nr++; // advance to next level
4812 TapeErase(); // start with empty tape
4814 if (setup.auto_play_next_level)
4816 LoadLevel(level_nr);
4818 SaveLevelSetup_SeriesInfo();
4822 hi_pos = NewHiScore(last_level_nr);
4824 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4826 SetGameStatus(GAME_MODE_SCORES);
4828 DrawHallOfFame(last_level_nr, hi_pos);
4830 else if (setup.auto_play_next_level && setup.increment_levels &&
4831 last_level_nr < leveldir_current->last_level &&
4834 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4838 SetGameStatus(GAME_MODE_MAIN);
4844 int NewHiScore(int level_nr)
4848 boolean one_score_entry_per_name = !program.many_scores_per_name;
4850 LoadScore(level_nr);
4852 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4853 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4856 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4858 if (game.score_final > highscore[k].Score)
4860 // player has made it to the hall of fame
4862 if (k < MAX_SCORE_ENTRIES - 1)
4864 int m = MAX_SCORE_ENTRIES - 1;
4866 if (one_score_entry_per_name)
4868 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4869 if (strEqual(setup.player_name, highscore[l].Name))
4872 if (m == k) // player's new highscore overwrites his old one
4876 for (l = m; l > k; l--)
4878 strcpy(highscore[l].Name, highscore[l - 1].Name);
4879 highscore[l].Score = highscore[l - 1].Score;
4885 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4886 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4887 highscore[k].Score = game.score_final;
4892 else if (one_score_entry_per_name &&
4893 !strncmp(setup.player_name, highscore[k].Name,
4894 MAX_PLAYER_NAME_LEN))
4895 break; // player already there with a higher score
4899 SaveScore(level_nr);
4904 static int getElementMoveStepsizeExt(int x, int y, int direction)
4906 int element = Feld[x][y];
4907 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4908 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4909 int horiz_move = (dx != 0);
4910 int sign = (horiz_move ? dx : dy);
4911 int step = sign * element_info[element].move_stepsize;
4913 // special values for move stepsize for spring and things on conveyor belt
4916 if (CAN_FALL(element) &&
4917 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
4918 step = sign * MOVE_STEPSIZE_NORMAL / 2;
4919 else if (element == EL_SPRING)
4920 step = sign * MOVE_STEPSIZE_NORMAL * 2;
4926 static int getElementMoveStepsize(int x, int y)
4928 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
4931 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
4933 if (player->GfxAction != action || player->GfxDir != dir)
4935 player->GfxAction = action;
4936 player->GfxDir = dir;
4938 player->StepFrame = 0;
4942 static void ResetGfxFrame(int x, int y)
4944 // profiling showed that "autotest" spends 10~20% of its time in this function
4945 if (DrawingDeactivatedField())
4948 int element = Feld[x][y];
4949 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
4951 if (graphic_info[graphic].anim_global_sync)
4952 GfxFrame[x][y] = FrameCounter;
4953 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
4954 GfxFrame[x][y] = CustomValue[x][y];
4955 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
4956 GfxFrame[x][y] = element_info[element].collect_score;
4957 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
4958 GfxFrame[x][y] = ChangeDelay[x][y];
4961 static void ResetGfxAnimation(int x, int y)
4963 GfxAction[x][y] = ACTION_DEFAULT;
4964 GfxDir[x][y] = MovDir[x][y];
4967 ResetGfxFrame(x, y);
4970 static void ResetRandomAnimationValue(int x, int y)
4972 GfxRandom[x][y] = INIT_GFX_RANDOM();
4975 static void InitMovingField(int x, int y, int direction)
4977 int element = Feld[x][y];
4978 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4979 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4982 boolean is_moving_before, is_moving_after;
4984 // check if element was/is moving or being moved before/after mode change
4985 is_moving_before = (WasJustMoving[x][y] != 0);
4986 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
4988 // reset animation only for moving elements which change direction of moving
4989 // or which just started or stopped moving
4990 // (else CEs with property "can move" / "not moving" are reset each frame)
4991 if (is_moving_before != is_moving_after ||
4992 direction != MovDir[x][y])
4993 ResetGfxAnimation(x, y);
4995 MovDir[x][y] = direction;
4996 GfxDir[x][y] = direction;
4998 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
4999 direction == MV_DOWN && CAN_FALL(element) ?
5000 ACTION_FALLING : ACTION_MOVING);
5002 // this is needed for CEs with property "can move" / "not moving"
5004 if (is_moving_after)
5006 if (Feld[newx][newy] == EL_EMPTY)
5007 Feld[newx][newy] = EL_BLOCKED;
5009 MovDir[newx][newy] = MovDir[x][y];
5011 CustomValue[newx][newy] = CustomValue[x][y];
5013 GfxFrame[newx][newy] = GfxFrame[x][y];
5014 GfxRandom[newx][newy] = GfxRandom[x][y];
5015 GfxAction[newx][newy] = GfxAction[x][y];
5016 GfxDir[newx][newy] = GfxDir[x][y];
5020 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5022 int direction = MovDir[x][y];
5023 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5024 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5030 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5032 int oldx = x, oldy = y;
5033 int direction = MovDir[x][y];
5035 if (direction == MV_LEFT)
5037 else if (direction == MV_RIGHT)
5039 else if (direction == MV_UP)
5041 else if (direction == MV_DOWN)
5044 *comes_from_x = oldx;
5045 *comes_from_y = oldy;
5048 static int MovingOrBlocked2Element(int x, int y)
5050 int element = Feld[x][y];
5052 if (element == EL_BLOCKED)
5056 Blocked2Moving(x, y, &oldx, &oldy);
5057 return Feld[oldx][oldy];
5063 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5065 // like MovingOrBlocked2Element(), but if element is moving
5066 // and (x,y) is the field the moving element is just leaving,
5067 // return EL_BLOCKED instead of the element value
5068 int element = Feld[x][y];
5070 if (IS_MOVING(x, y))
5072 if (element == EL_BLOCKED)
5076 Blocked2Moving(x, y, &oldx, &oldy);
5077 return Feld[oldx][oldy];
5086 static void RemoveField(int x, int y)
5088 Feld[x][y] = EL_EMPTY;
5094 CustomValue[x][y] = 0;
5097 ChangeDelay[x][y] = 0;
5098 ChangePage[x][y] = -1;
5099 Pushed[x][y] = FALSE;
5101 GfxElement[x][y] = EL_UNDEFINED;
5102 GfxAction[x][y] = ACTION_DEFAULT;
5103 GfxDir[x][y] = MV_NONE;
5106 static void RemoveMovingField(int x, int y)
5108 int oldx = x, oldy = y, newx = x, newy = y;
5109 int element = Feld[x][y];
5110 int next_element = EL_UNDEFINED;
5112 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5115 if (IS_MOVING(x, y))
5117 Moving2Blocked(x, y, &newx, &newy);
5119 if (Feld[newx][newy] != EL_BLOCKED)
5121 // element is moving, but target field is not free (blocked), but
5122 // already occupied by something different (example: acid pool);
5123 // in this case, only remove the moving field, but not the target
5125 RemoveField(oldx, oldy);
5127 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5129 TEST_DrawLevelField(oldx, oldy);
5134 else if (element == EL_BLOCKED)
5136 Blocked2Moving(x, y, &oldx, &oldy);
5137 if (!IS_MOVING(oldx, oldy))
5141 if (element == EL_BLOCKED &&
5142 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5143 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5144 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5145 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5146 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5147 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5148 next_element = get_next_element(Feld[oldx][oldy]);
5150 RemoveField(oldx, oldy);
5151 RemoveField(newx, newy);
5153 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5155 if (next_element != EL_UNDEFINED)
5156 Feld[oldx][oldy] = next_element;
5158 TEST_DrawLevelField(oldx, oldy);
5159 TEST_DrawLevelField(newx, newy);
5162 void DrawDynamite(int x, int y)
5164 int sx = SCREENX(x), sy = SCREENY(y);
5165 int graphic = el2img(Feld[x][y]);
5168 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5171 if (IS_WALKABLE_INSIDE(Back[x][y]))
5175 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5176 else if (Store[x][y])
5177 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5179 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5181 if (Back[x][y] || Store[x][y])
5182 DrawGraphicThruMask(sx, sy, graphic, frame);
5184 DrawGraphic(sx, sy, graphic, frame);
5187 static void CheckDynamite(int x, int y)
5189 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5193 if (MovDelay[x][y] != 0)
5196 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5202 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5207 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5209 boolean num_checked_players = 0;
5212 for (i = 0; i < MAX_PLAYERS; i++)
5214 if (stored_player[i].active)
5216 int sx = stored_player[i].jx;
5217 int sy = stored_player[i].jy;
5219 if (num_checked_players == 0)
5226 *sx1 = MIN(*sx1, sx);
5227 *sy1 = MIN(*sy1, sy);
5228 *sx2 = MAX(*sx2, sx);
5229 *sy2 = MAX(*sy2, sy);
5232 num_checked_players++;
5237 static boolean checkIfAllPlayersFitToScreen_RND(void)
5239 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5241 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5243 return (sx2 - sx1 < SCR_FIELDX &&
5244 sy2 - sy1 < SCR_FIELDY);
5247 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5249 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5251 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5253 *sx = (sx1 + sx2) / 2;
5254 *sy = (sy1 + sy2) / 2;
5257 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5258 boolean center_screen, boolean quick_relocation)
5260 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5261 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5262 boolean no_delay = (tape.warp_forward);
5263 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5264 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5265 int new_scroll_x, new_scroll_y;
5267 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5269 // case 1: quick relocation inside visible screen (without scrolling)
5276 if (!level.shifted_relocation || center_screen)
5278 // relocation _with_ centering of screen
5280 new_scroll_x = SCROLL_POSITION_X(x);
5281 new_scroll_y = SCROLL_POSITION_Y(y);
5285 // relocation _without_ centering of screen
5287 int center_scroll_x = SCROLL_POSITION_X(old_x);
5288 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5289 int offset_x = x + (scroll_x - center_scroll_x);
5290 int offset_y = y + (scroll_y - center_scroll_y);
5292 // for new screen position, apply previous offset to center position
5293 new_scroll_x = SCROLL_POSITION_X(offset_x);
5294 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5297 if (quick_relocation)
5299 // case 2: quick relocation (redraw without visible scrolling)
5301 scroll_x = new_scroll_x;
5302 scroll_y = new_scroll_y;
5309 // case 3: visible relocation (with scrolling to new position)
5311 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5313 SetVideoFrameDelay(wait_delay_value);
5315 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5317 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5318 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5320 if (dx == 0 && dy == 0) // no scrolling needed at all
5326 // set values for horizontal/vertical screen scrolling (half tile size)
5327 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5328 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5329 int pos_x = dx * TILEX / 2;
5330 int pos_y = dy * TILEY / 2;
5331 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5332 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5334 ScrollLevel(dx, dy);
5337 // scroll in two steps of half tile size to make things smoother
5338 BlitScreenToBitmapExt_RND(window, fx, fy);
5340 // scroll second step to align at full tile size
5341 BlitScreenToBitmap(window);
5347 SetVideoFrameDelay(frame_delay_value_old);
5350 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5352 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5353 int player_nr = GET_PLAYER_NR(el_player);
5354 struct PlayerInfo *player = &stored_player[player_nr];
5355 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5356 boolean no_delay = (tape.warp_forward);
5357 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5358 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5359 int old_jx = player->jx;
5360 int old_jy = player->jy;
5361 int old_element = Feld[old_jx][old_jy];
5362 int element = Feld[jx][jy];
5363 boolean player_relocated = (old_jx != jx || old_jy != jy);
5365 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5366 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5367 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5368 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5369 int leave_side_horiz = move_dir_horiz;
5370 int leave_side_vert = move_dir_vert;
5371 int enter_side = enter_side_horiz | enter_side_vert;
5372 int leave_side = leave_side_horiz | leave_side_vert;
5374 if (player->buried) // do not reanimate dead player
5377 if (!player_relocated) // no need to relocate the player
5380 if (IS_PLAYER(jx, jy)) // player already placed at new position
5382 RemoveField(jx, jy); // temporarily remove newly placed player
5383 DrawLevelField(jx, jy);
5386 if (player->present)
5388 while (player->MovPos)
5390 ScrollPlayer(player, SCROLL_GO_ON);
5391 ScrollScreen(NULL, SCROLL_GO_ON);
5393 AdvanceFrameAndPlayerCounters(player->index_nr);
5397 BackToFront_WithFrameDelay(wait_delay_value);
5400 DrawPlayer(player); // needed here only to cleanup last field
5401 DrawLevelField(player->jx, player->jy); // remove player graphic
5403 player->is_moving = FALSE;
5406 if (IS_CUSTOM_ELEMENT(old_element))
5407 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5409 player->index_bit, leave_side);
5411 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5413 player->index_bit, leave_side);
5415 Feld[jx][jy] = el_player;
5416 InitPlayerField(jx, jy, el_player, TRUE);
5418 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5419 possible that the relocation target field did not contain a player element,
5420 but a walkable element, to which the new player was relocated -- in this
5421 case, restore that (already initialized!) element on the player field */
5422 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5424 Feld[jx][jy] = element; // restore previously existing element
5427 // only visually relocate centered player
5428 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5429 FALSE, level.instant_relocation);
5431 TestIfPlayerTouchesBadThing(jx, jy);
5432 TestIfPlayerTouchesCustomElement(jx, jy);
5434 if (IS_CUSTOM_ELEMENT(element))
5435 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5436 player->index_bit, enter_side);
5438 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5439 player->index_bit, enter_side);
5441 if (player->is_switching)
5443 /* ensure that relocation while still switching an element does not cause
5444 a new element to be treated as also switched directly after relocation
5445 (this is important for teleporter switches that teleport the player to
5446 a place where another teleporter switch is in the same direction, which
5447 would then incorrectly be treated as immediately switched before the
5448 direction key that caused the switch was released) */
5450 player->switch_x += jx - old_jx;
5451 player->switch_y += jy - old_jy;
5455 static void Explode(int ex, int ey, int phase, int mode)
5461 // !!! eliminate this variable !!!
5462 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5464 if (game.explosions_delayed)
5466 ExplodeField[ex][ey] = mode;
5470 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5472 int center_element = Feld[ex][ey];
5473 int artwork_element, explosion_element; // set these values later
5475 // remove things displayed in background while burning dynamite
5476 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5479 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5481 // put moving element to center field (and let it explode there)
5482 center_element = MovingOrBlocked2Element(ex, ey);
5483 RemoveMovingField(ex, ey);
5484 Feld[ex][ey] = center_element;
5487 // now "center_element" is finally determined -- set related values now
5488 artwork_element = center_element; // for custom player artwork
5489 explosion_element = center_element; // for custom player artwork
5491 if (IS_PLAYER(ex, ey))
5493 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5495 artwork_element = stored_player[player_nr].artwork_element;
5497 if (level.use_explosion_element[player_nr])
5499 explosion_element = level.explosion_element[player_nr];
5500 artwork_element = explosion_element;
5504 if (mode == EX_TYPE_NORMAL ||
5505 mode == EX_TYPE_CENTER ||
5506 mode == EX_TYPE_CROSS)
5507 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5509 last_phase = element_info[explosion_element].explosion_delay + 1;
5511 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5513 int xx = x - ex + 1;
5514 int yy = y - ey + 1;
5517 if (!IN_LEV_FIELD(x, y) ||
5518 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5519 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5522 element = Feld[x][y];
5524 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5526 element = MovingOrBlocked2Element(x, y);
5528 if (!IS_EXPLOSION_PROOF(element))
5529 RemoveMovingField(x, y);
5532 // indestructible elements can only explode in center (but not flames)
5533 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5534 mode == EX_TYPE_BORDER)) ||
5535 element == EL_FLAMES)
5538 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5539 behaviour, for example when touching a yamyam that explodes to rocks
5540 with active deadly shield, a rock is created under the player !!! */
5541 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5543 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5544 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5545 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5547 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5550 if (IS_ACTIVE_BOMB(element))
5552 // re-activate things under the bomb like gate or penguin
5553 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5560 // save walkable background elements while explosion on same tile
5561 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5562 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5563 Back[x][y] = element;
5565 // ignite explodable elements reached by other explosion
5566 if (element == EL_EXPLOSION)
5567 element = Store2[x][y];
5569 if (AmoebaNr[x][y] &&
5570 (element == EL_AMOEBA_FULL ||
5571 element == EL_BD_AMOEBA ||
5572 element == EL_AMOEBA_GROWING))
5574 AmoebaCnt[AmoebaNr[x][y]]--;
5575 AmoebaCnt2[AmoebaNr[x][y]]--;
5580 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5582 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5584 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5586 if (PLAYERINFO(ex, ey)->use_murphy)
5587 Store[x][y] = EL_EMPTY;
5590 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5591 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5592 else if (ELEM_IS_PLAYER(center_element))
5593 Store[x][y] = EL_EMPTY;
5594 else if (center_element == EL_YAMYAM)
5595 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5596 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5597 Store[x][y] = element_info[center_element].content.e[xx][yy];
5599 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5600 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5601 // otherwise) -- FIX THIS !!!
5602 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5603 Store[x][y] = element_info[element].content.e[1][1];
5605 else if (!CAN_EXPLODE(element))
5606 Store[x][y] = element_info[element].content.e[1][1];
5609 Store[x][y] = EL_EMPTY;
5611 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5612 center_element == EL_AMOEBA_TO_DIAMOND)
5613 Store2[x][y] = element;
5615 Feld[x][y] = EL_EXPLOSION;
5616 GfxElement[x][y] = artwork_element;
5618 ExplodePhase[x][y] = 1;
5619 ExplodeDelay[x][y] = last_phase;
5624 if (center_element == EL_YAMYAM)
5625 game.yamyam_content_nr =
5626 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5638 GfxFrame[x][y] = 0; // restart explosion animation
5640 last_phase = ExplodeDelay[x][y];
5642 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5644 // this can happen if the player leaves an explosion just in time
5645 if (GfxElement[x][y] == EL_UNDEFINED)
5646 GfxElement[x][y] = EL_EMPTY;
5648 border_element = Store2[x][y];
5649 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5650 border_element = StorePlayer[x][y];
5652 if (phase == element_info[border_element].ignition_delay ||
5653 phase == last_phase)
5655 boolean border_explosion = FALSE;
5657 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5658 !PLAYER_EXPLOSION_PROTECTED(x, y))
5660 KillPlayerUnlessExplosionProtected(x, y);
5661 border_explosion = TRUE;
5663 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5665 Feld[x][y] = Store2[x][y];
5668 border_explosion = TRUE;
5670 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5672 AmoebeUmwandeln(x, y);
5674 border_explosion = TRUE;
5677 // if an element just explodes due to another explosion (chain-reaction),
5678 // do not immediately end the new explosion when it was the last frame of
5679 // the explosion (as it would be done in the following "if"-statement!)
5680 if (border_explosion && phase == last_phase)
5684 if (phase == last_phase)
5688 element = Feld[x][y] = Store[x][y];
5689 Store[x][y] = Store2[x][y] = 0;
5690 GfxElement[x][y] = EL_UNDEFINED;
5692 // player can escape from explosions and might therefore be still alive
5693 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5694 element <= EL_PLAYER_IS_EXPLODING_4)
5696 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5697 int explosion_element = EL_PLAYER_1 + player_nr;
5698 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5699 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5701 if (level.use_explosion_element[player_nr])
5702 explosion_element = level.explosion_element[player_nr];
5704 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5705 element_info[explosion_element].content.e[xx][yy]);
5708 // restore probably existing indestructible background element
5709 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5710 element = Feld[x][y] = Back[x][y];
5713 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5714 GfxDir[x][y] = MV_NONE;
5715 ChangeDelay[x][y] = 0;
5716 ChangePage[x][y] = -1;
5718 CustomValue[x][y] = 0;
5720 InitField_WithBug2(x, y, FALSE);
5722 TEST_DrawLevelField(x, y);
5724 TestIfElementTouchesCustomElement(x, y);
5726 if (GFX_CRUMBLED(element))
5727 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5729 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5730 StorePlayer[x][y] = 0;
5732 if (ELEM_IS_PLAYER(element))
5733 RelocatePlayer(x, y, element);
5735 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5737 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5738 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5741 TEST_DrawLevelFieldCrumbled(x, y);
5743 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5745 DrawLevelElement(x, y, Back[x][y]);
5746 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5748 else if (IS_WALKABLE_UNDER(Back[x][y]))
5750 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5751 DrawLevelElementThruMask(x, y, Back[x][y]);
5753 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5754 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5758 static void DynaExplode(int ex, int ey)
5761 int dynabomb_element = Feld[ex][ey];
5762 int dynabomb_size = 1;
5763 boolean dynabomb_xl = FALSE;
5764 struct PlayerInfo *player;
5765 static int xy[4][2] =
5773 if (IS_ACTIVE_BOMB(dynabomb_element))
5775 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5776 dynabomb_size = player->dynabomb_size;
5777 dynabomb_xl = player->dynabomb_xl;
5778 player->dynabombs_left++;
5781 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5783 for (i = 0; i < NUM_DIRECTIONS; i++)
5785 for (j = 1; j <= dynabomb_size; j++)
5787 int x = ex + j * xy[i][0];
5788 int y = ey + j * xy[i][1];
5791 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5794 element = Feld[x][y];
5796 // do not restart explosions of fields with active bombs
5797 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5800 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5802 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5803 !IS_DIGGABLE(element) && !dynabomb_xl)
5809 void Bang(int x, int y)
5811 int element = MovingOrBlocked2Element(x, y);
5812 int explosion_type = EX_TYPE_NORMAL;
5814 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5816 struct PlayerInfo *player = PLAYERINFO(x, y);
5818 element = Feld[x][y] = player->initial_element;
5820 if (level.use_explosion_element[player->index_nr])
5822 int explosion_element = level.explosion_element[player->index_nr];
5824 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5825 explosion_type = EX_TYPE_CROSS;
5826 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5827 explosion_type = EX_TYPE_CENTER;
5835 case EL_BD_BUTTERFLY:
5838 case EL_DARK_YAMYAM:
5842 RaiseScoreElement(element);
5845 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5846 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5847 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5848 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5849 case EL_DYNABOMB_INCREASE_NUMBER:
5850 case EL_DYNABOMB_INCREASE_SIZE:
5851 case EL_DYNABOMB_INCREASE_POWER:
5852 explosion_type = EX_TYPE_DYNA;
5855 case EL_DC_LANDMINE:
5856 explosion_type = EX_TYPE_CENTER;
5861 case EL_LAMP_ACTIVE:
5862 case EL_AMOEBA_TO_DIAMOND:
5863 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5864 explosion_type = EX_TYPE_CENTER;
5868 if (element_info[element].explosion_type == EXPLODES_CROSS)
5869 explosion_type = EX_TYPE_CROSS;
5870 else if (element_info[element].explosion_type == EXPLODES_1X1)
5871 explosion_type = EX_TYPE_CENTER;
5875 if (explosion_type == EX_TYPE_DYNA)
5878 Explode(x, y, EX_PHASE_START, explosion_type);
5880 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5883 static void SplashAcid(int x, int y)
5885 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5886 (!IN_LEV_FIELD(x - 1, y - 2) ||
5887 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5888 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5890 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5891 (!IN_LEV_FIELD(x + 1, y - 2) ||
5892 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5893 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5895 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5898 static void InitBeltMovement(void)
5900 static int belt_base_element[4] =
5902 EL_CONVEYOR_BELT_1_LEFT,
5903 EL_CONVEYOR_BELT_2_LEFT,
5904 EL_CONVEYOR_BELT_3_LEFT,
5905 EL_CONVEYOR_BELT_4_LEFT
5907 static int belt_base_active_element[4] =
5909 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5910 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5911 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5912 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5917 // set frame order for belt animation graphic according to belt direction
5918 for (i = 0; i < NUM_BELTS; i++)
5922 for (j = 0; j < NUM_BELT_PARTS; j++)
5924 int element = belt_base_active_element[belt_nr] + j;
5925 int graphic_1 = el2img(element);
5926 int graphic_2 = el2panelimg(element);
5928 if (game.belt_dir[i] == MV_LEFT)
5930 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5931 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5935 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5936 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5941 SCAN_PLAYFIELD(x, y)
5943 int element = Feld[x][y];
5945 for (i = 0; i < NUM_BELTS; i++)
5947 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
5949 int e_belt_nr = getBeltNrFromBeltElement(element);
5952 if (e_belt_nr == belt_nr)
5954 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
5956 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
5963 static void ToggleBeltSwitch(int x, int y)
5965 static int belt_base_element[4] =
5967 EL_CONVEYOR_BELT_1_LEFT,
5968 EL_CONVEYOR_BELT_2_LEFT,
5969 EL_CONVEYOR_BELT_3_LEFT,
5970 EL_CONVEYOR_BELT_4_LEFT
5972 static int belt_base_active_element[4] =
5974 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5975 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5976 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5977 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5979 static int belt_base_switch_element[4] =
5981 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
5982 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
5983 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
5984 EL_CONVEYOR_BELT_4_SWITCH_LEFT
5986 static int belt_move_dir[4] =
5994 int element = Feld[x][y];
5995 int belt_nr = getBeltNrFromBeltSwitchElement(element);
5996 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
5997 int belt_dir = belt_move_dir[belt_dir_nr];
6000 if (!IS_BELT_SWITCH(element))
6003 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6004 game.belt_dir[belt_nr] = belt_dir;
6006 if (belt_dir_nr == 3)
6009 // set frame order for belt animation graphic according to belt direction
6010 for (i = 0; i < NUM_BELT_PARTS; i++)
6012 int element = belt_base_active_element[belt_nr] + i;
6013 int graphic_1 = el2img(element);
6014 int graphic_2 = el2panelimg(element);
6016 if (belt_dir == MV_LEFT)
6018 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6019 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6023 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6024 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6028 SCAN_PLAYFIELD(xx, yy)
6030 int element = Feld[xx][yy];
6032 if (IS_BELT_SWITCH(element))
6034 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6036 if (e_belt_nr == belt_nr)
6038 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6039 TEST_DrawLevelField(xx, yy);
6042 else if (IS_BELT(element) && belt_dir != MV_NONE)
6044 int e_belt_nr = getBeltNrFromBeltElement(element);
6046 if (e_belt_nr == belt_nr)
6048 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
6050 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6051 TEST_DrawLevelField(xx, yy);
6054 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6056 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6058 if (e_belt_nr == belt_nr)
6060 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6062 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6063 TEST_DrawLevelField(xx, yy);
6069 static void ToggleSwitchgateSwitch(int x, int y)
6073 game.switchgate_pos = !game.switchgate_pos;
6075 SCAN_PLAYFIELD(xx, yy)
6077 int element = Feld[xx][yy];
6079 if (element == EL_SWITCHGATE_SWITCH_UP)
6081 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6082 TEST_DrawLevelField(xx, yy);
6084 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6086 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6087 TEST_DrawLevelField(xx, yy);
6089 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6091 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6092 TEST_DrawLevelField(xx, yy);
6094 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6096 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6097 TEST_DrawLevelField(xx, yy);
6099 else if (element == EL_SWITCHGATE_OPEN ||
6100 element == EL_SWITCHGATE_OPENING)
6102 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6104 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6106 else if (element == EL_SWITCHGATE_CLOSED ||
6107 element == EL_SWITCHGATE_CLOSING)
6109 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6111 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6116 static int getInvisibleActiveFromInvisibleElement(int element)
6118 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6119 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6120 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6124 static int getInvisibleFromInvisibleActiveElement(int element)
6126 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6127 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6128 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6132 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6136 SCAN_PLAYFIELD(x, y)
6138 int element = Feld[x][y];
6140 if (element == EL_LIGHT_SWITCH &&
6141 game.light_time_left > 0)
6143 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6144 TEST_DrawLevelField(x, y);
6146 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6147 game.light_time_left == 0)
6149 Feld[x][y] = EL_LIGHT_SWITCH;
6150 TEST_DrawLevelField(x, y);
6152 else if (element == EL_EMC_DRIPPER &&
6153 game.light_time_left > 0)
6155 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6156 TEST_DrawLevelField(x, y);
6158 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6159 game.light_time_left == 0)
6161 Feld[x][y] = EL_EMC_DRIPPER;
6162 TEST_DrawLevelField(x, y);
6164 else if (element == EL_INVISIBLE_STEELWALL ||
6165 element == EL_INVISIBLE_WALL ||
6166 element == EL_INVISIBLE_SAND)
6168 if (game.light_time_left > 0)
6169 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6171 TEST_DrawLevelField(x, y);
6173 // uncrumble neighbour fields, if needed
6174 if (element == EL_INVISIBLE_SAND)
6175 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6177 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6178 element == EL_INVISIBLE_WALL_ACTIVE ||
6179 element == EL_INVISIBLE_SAND_ACTIVE)
6181 if (game.light_time_left == 0)
6182 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6184 TEST_DrawLevelField(x, y);
6186 // re-crumble neighbour fields, if needed
6187 if (element == EL_INVISIBLE_SAND)
6188 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6193 static void RedrawAllInvisibleElementsForLenses(void)
6197 SCAN_PLAYFIELD(x, y)
6199 int element = Feld[x][y];
6201 if (element == EL_EMC_DRIPPER &&
6202 game.lenses_time_left > 0)
6204 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6205 TEST_DrawLevelField(x, y);
6207 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6208 game.lenses_time_left == 0)
6210 Feld[x][y] = EL_EMC_DRIPPER;
6211 TEST_DrawLevelField(x, y);
6213 else if (element == EL_INVISIBLE_STEELWALL ||
6214 element == EL_INVISIBLE_WALL ||
6215 element == EL_INVISIBLE_SAND)
6217 if (game.lenses_time_left > 0)
6218 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6220 TEST_DrawLevelField(x, y);
6222 // uncrumble neighbour fields, if needed
6223 if (element == EL_INVISIBLE_SAND)
6224 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6226 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6227 element == EL_INVISIBLE_WALL_ACTIVE ||
6228 element == EL_INVISIBLE_SAND_ACTIVE)
6230 if (game.lenses_time_left == 0)
6231 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6233 TEST_DrawLevelField(x, y);
6235 // re-crumble neighbour fields, if needed
6236 if (element == EL_INVISIBLE_SAND)
6237 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6242 static void RedrawAllInvisibleElementsForMagnifier(void)
6246 SCAN_PLAYFIELD(x, y)
6248 int element = Feld[x][y];
6250 if (element == EL_EMC_FAKE_GRASS &&
6251 game.magnify_time_left > 0)
6253 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6254 TEST_DrawLevelField(x, y);
6256 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6257 game.magnify_time_left == 0)
6259 Feld[x][y] = EL_EMC_FAKE_GRASS;
6260 TEST_DrawLevelField(x, y);
6262 else if (IS_GATE_GRAY(element) &&
6263 game.magnify_time_left > 0)
6265 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6266 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6267 IS_EM_GATE_GRAY(element) ?
6268 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6269 IS_EMC_GATE_GRAY(element) ?
6270 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6271 IS_DC_GATE_GRAY(element) ?
6272 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6274 TEST_DrawLevelField(x, y);
6276 else if (IS_GATE_GRAY_ACTIVE(element) &&
6277 game.magnify_time_left == 0)
6279 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6280 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6281 IS_EM_GATE_GRAY_ACTIVE(element) ?
6282 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6283 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6284 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6285 IS_DC_GATE_GRAY_ACTIVE(element) ?
6286 EL_DC_GATE_WHITE_GRAY :
6288 TEST_DrawLevelField(x, y);
6293 static void ToggleLightSwitch(int x, int y)
6295 int element = Feld[x][y];
6297 game.light_time_left =
6298 (element == EL_LIGHT_SWITCH ?
6299 level.time_light * FRAMES_PER_SECOND : 0);
6301 RedrawAllLightSwitchesAndInvisibleElements();
6304 static void ActivateTimegateSwitch(int x, int y)
6308 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6310 SCAN_PLAYFIELD(xx, yy)
6312 int element = Feld[xx][yy];
6314 if (element == EL_TIMEGATE_CLOSED ||
6315 element == EL_TIMEGATE_CLOSING)
6317 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6318 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6322 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6324 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6325 TEST_DrawLevelField(xx, yy);
6331 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6332 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6335 static void Impact(int x, int y)
6337 boolean last_line = (y == lev_fieldy - 1);
6338 boolean object_hit = FALSE;
6339 boolean impact = (last_line || object_hit);
6340 int element = Feld[x][y];
6341 int smashed = EL_STEELWALL;
6343 if (!last_line) // check if element below was hit
6345 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6348 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6349 MovDir[x][y + 1] != MV_DOWN ||
6350 MovPos[x][y + 1] <= TILEY / 2));
6352 // do not smash moving elements that left the smashed field in time
6353 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6354 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6357 #if USE_QUICKSAND_IMPACT_BUGFIX
6358 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6360 RemoveMovingField(x, y + 1);
6361 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6362 Feld[x][y + 2] = EL_ROCK;
6363 TEST_DrawLevelField(x, y + 2);
6368 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6370 RemoveMovingField(x, y + 1);
6371 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6372 Feld[x][y + 2] = EL_ROCK;
6373 TEST_DrawLevelField(x, y + 2);
6380 smashed = MovingOrBlocked2Element(x, y + 1);
6382 impact = (last_line || object_hit);
6385 if (!last_line && smashed == EL_ACID) // element falls into acid
6387 SplashAcid(x, y + 1);
6391 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6392 // only reset graphic animation if graphic really changes after impact
6394 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6396 ResetGfxAnimation(x, y);
6397 TEST_DrawLevelField(x, y);
6400 if (impact && CAN_EXPLODE_IMPACT(element))
6405 else if (impact && element == EL_PEARL &&
6406 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6408 ResetGfxAnimation(x, y);
6410 Feld[x][y] = EL_PEARL_BREAKING;
6411 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6414 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6416 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6421 if (impact && element == EL_AMOEBA_DROP)
6423 if (object_hit && IS_PLAYER(x, y + 1))
6424 KillPlayerUnlessEnemyProtected(x, y + 1);
6425 else if (object_hit && smashed == EL_PENGUIN)
6429 Feld[x][y] = EL_AMOEBA_GROWING;
6430 Store[x][y] = EL_AMOEBA_WET;
6432 ResetRandomAnimationValue(x, y);
6437 if (object_hit) // check which object was hit
6439 if ((CAN_PASS_MAGIC_WALL(element) &&
6440 (smashed == EL_MAGIC_WALL ||
6441 smashed == EL_BD_MAGIC_WALL)) ||
6442 (CAN_PASS_DC_MAGIC_WALL(element) &&
6443 smashed == EL_DC_MAGIC_WALL))
6446 int activated_magic_wall =
6447 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6448 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6449 EL_DC_MAGIC_WALL_ACTIVE);
6451 // activate magic wall / mill
6452 SCAN_PLAYFIELD(xx, yy)
6454 if (Feld[xx][yy] == smashed)
6455 Feld[xx][yy] = activated_magic_wall;
6458 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6459 game.magic_wall_active = TRUE;
6461 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6462 SND_MAGIC_WALL_ACTIVATING :
6463 smashed == EL_BD_MAGIC_WALL ?
6464 SND_BD_MAGIC_WALL_ACTIVATING :
6465 SND_DC_MAGIC_WALL_ACTIVATING));
6468 if (IS_PLAYER(x, y + 1))
6470 if (CAN_SMASH_PLAYER(element))
6472 KillPlayerUnlessEnemyProtected(x, y + 1);
6476 else if (smashed == EL_PENGUIN)
6478 if (CAN_SMASH_PLAYER(element))
6484 else if (element == EL_BD_DIAMOND)
6486 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6492 else if (((element == EL_SP_INFOTRON ||
6493 element == EL_SP_ZONK) &&
6494 (smashed == EL_SP_SNIKSNAK ||
6495 smashed == EL_SP_ELECTRON ||
6496 smashed == EL_SP_DISK_ORANGE)) ||
6497 (element == EL_SP_INFOTRON &&
6498 smashed == EL_SP_DISK_YELLOW))
6503 else if (CAN_SMASH_EVERYTHING(element))
6505 if (IS_CLASSIC_ENEMY(smashed) ||
6506 CAN_EXPLODE_SMASHED(smashed))
6511 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6513 if (smashed == EL_LAMP ||
6514 smashed == EL_LAMP_ACTIVE)
6519 else if (smashed == EL_NUT)
6521 Feld[x][y + 1] = EL_NUT_BREAKING;
6522 PlayLevelSound(x, y, SND_NUT_BREAKING);
6523 RaiseScoreElement(EL_NUT);
6526 else if (smashed == EL_PEARL)
6528 ResetGfxAnimation(x, y);
6530 Feld[x][y + 1] = EL_PEARL_BREAKING;
6531 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6534 else if (smashed == EL_DIAMOND)
6536 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6537 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6540 else if (IS_BELT_SWITCH(smashed))
6542 ToggleBeltSwitch(x, y + 1);
6544 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6545 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6546 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6547 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6549 ToggleSwitchgateSwitch(x, y + 1);
6551 else if (smashed == EL_LIGHT_SWITCH ||
6552 smashed == EL_LIGHT_SWITCH_ACTIVE)
6554 ToggleLightSwitch(x, y + 1);
6558 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6560 CheckElementChangeBySide(x, y + 1, smashed, element,
6561 CE_SWITCHED, CH_SIDE_TOP);
6562 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6568 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6573 // play sound of magic wall / mill
6575 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6576 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6577 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6579 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6580 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6581 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6582 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6583 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6584 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6589 // play sound of object that hits the ground
6590 if (last_line || object_hit)
6591 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6594 static void TurnRoundExt(int x, int y)
6606 { 0, 0 }, { 0, 0 }, { 0, 0 },
6611 int left, right, back;
6615 { MV_DOWN, MV_UP, MV_RIGHT },
6616 { MV_UP, MV_DOWN, MV_LEFT },
6618 { MV_LEFT, MV_RIGHT, MV_DOWN },
6622 { MV_RIGHT, MV_LEFT, MV_UP }
6625 int element = Feld[x][y];
6626 int move_pattern = element_info[element].move_pattern;
6628 int old_move_dir = MovDir[x][y];
6629 int left_dir = turn[old_move_dir].left;
6630 int right_dir = turn[old_move_dir].right;
6631 int back_dir = turn[old_move_dir].back;
6633 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6634 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6635 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6636 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6638 int left_x = x + left_dx, left_y = y + left_dy;
6639 int right_x = x + right_dx, right_y = y + right_dy;
6640 int move_x = x + move_dx, move_y = y + move_dy;
6644 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6646 TestIfBadThingTouchesOtherBadThing(x, y);
6648 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6649 MovDir[x][y] = right_dir;
6650 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6651 MovDir[x][y] = left_dir;
6653 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6655 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6658 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6660 TestIfBadThingTouchesOtherBadThing(x, y);
6662 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6663 MovDir[x][y] = left_dir;
6664 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6665 MovDir[x][y] = right_dir;
6667 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6669 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6672 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6674 TestIfBadThingTouchesOtherBadThing(x, y);
6676 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6677 MovDir[x][y] = left_dir;
6678 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6679 MovDir[x][y] = right_dir;
6681 if (MovDir[x][y] != old_move_dir)
6684 else if (element == EL_YAMYAM)
6686 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6687 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6689 if (can_turn_left && can_turn_right)
6690 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6691 else if (can_turn_left)
6692 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6693 else if (can_turn_right)
6694 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6696 MovDir[x][y] = back_dir;
6698 MovDelay[x][y] = 16 + 16 * RND(3);
6700 else if (element == EL_DARK_YAMYAM)
6702 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6704 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6707 if (can_turn_left && can_turn_right)
6708 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6709 else if (can_turn_left)
6710 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6711 else if (can_turn_right)
6712 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6714 MovDir[x][y] = back_dir;
6716 MovDelay[x][y] = 16 + 16 * RND(3);
6718 else if (element == EL_PACMAN)
6720 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6721 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6723 if (can_turn_left && can_turn_right)
6724 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6725 else if (can_turn_left)
6726 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6727 else if (can_turn_right)
6728 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6730 MovDir[x][y] = back_dir;
6732 MovDelay[x][y] = 6 + RND(40);
6734 else if (element == EL_PIG)
6736 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6737 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6738 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6739 boolean should_turn_left, should_turn_right, should_move_on;
6741 int rnd = RND(rnd_value);
6743 should_turn_left = (can_turn_left &&
6745 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6746 y + back_dy + left_dy)));
6747 should_turn_right = (can_turn_right &&
6749 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6750 y + back_dy + right_dy)));
6751 should_move_on = (can_move_on &&
6754 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6755 y + move_dy + left_dy) ||
6756 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6757 y + move_dy + right_dy)));
6759 if (should_turn_left || should_turn_right || should_move_on)
6761 if (should_turn_left && should_turn_right && should_move_on)
6762 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6763 rnd < 2 * rnd_value / 3 ? right_dir :
6765 else if (should_turn_left && should_turn_right)
6766 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6767 else if (should_turn_left && should_move_on)
6768 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6769 else if (should_turn_right && should_move_on)
6770 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6771 else if (should_turn_left)
6772 MovDir[x][y] = left_dir;
6773 else if (should_turn_right)
6774 MovDir[x][y] = right_dir;
6775 else if (should_move_on)
6776 MovDir[x][y] = old_move_dir;
6778 else if (can_move_on && rnd > rnd_value / 8)
6779 MovDir[x][y] = old_move_dir;
6780 else if (can_turn_left && can_turn_right)
6781 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6782 else if (can_turn_left && rnd > rnd_value / 8)
6783 MovDir[x][y] = left_dir;
6784 else if (can_turn_right && rnd > rnd_value/8)
6785 MovDir[x][y] = right_dir;
6787 MovDir[x][y] = back_dir;
6789 xx = x + move_xy[MovDir[x][y]].dx;
6790 yy = y + move_xy[MovDir[x][y]].dy;
6792 if (!IN_LEV_FIELD(xx, yy) ||
6793 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6794 MovDir[x][y] = old_move_dir;
6798 else if (element == EL_DRAGON)
6800 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6801 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6802 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6804 int rnd = RND(rnd_value);
6806 if (can_move_on && rnd > rnd_value / 8)
6807 MovDir[x][y] = old_move_dir;
6808 else if (can_turn_left && can_turn_right)
6809 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6810 else if (can_turn_left && rnd > rnd_value / 8)
6811 MovDir[x][y] = left_dir;
6812 else if (can_turn_right && rnd > rnd_value / 8)
6813 MovDir[x][y] = right_dir;
6815 MovDir[x][y] = back_dir;
6817 xx = x + move_xy[MovDir[x][y]].dx;
6818 yy = y + move_xy[MovDir[x][y]].dy;
6820 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6821 MovDir[x][y] = old_move_dir;
6825 else if (element == EL_MOLE)
6827 boolean can_move_on =
6828 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6829 IS_AMOEBOID(Feld[move_x][move_y]) ||
6830 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6833 boolean can_turn_left =
6834 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6835 IS_AMOEBOID(Feld[left_x][left_y])));
6837 boolean can_turn_right =
6838 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6839 IS_AMOEBOID(Feld[right_x][right_y])));
6841 if (can_turn_left && can_turn_right)
6842 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6843 else if (can_turn_left)
6844 MovDir[x][y] = left_dir;
6846 MovDir[x][y] = right_dir;
6849 if (MovDir[x][y] != old_move_dir)
6852 else if (element == EL_BALLOON)
6854 MovDir[x][y] = game.wind_direction;
6857 else if (element == EL_SPRING)
6859 if (MovDir[x][y] & MV_HORIZONTAL)
6861 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6862 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6864 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6865 ResetGfxAnimation(move_x, move_y);
6866 TEST_DrawLevelField(move_x, move_y);
6868 MovDir[x][y] = back_dir;
6870 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6871 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6872 MovDir[x][y] = MV_NONE;
6877 else if (element == EL_ROBOT ||
6878 element == EL_SATELLITE ||
6879 element == EL_PENGUIN ||
6880 element == EL_EMC_ANDROID)
6882 int attr_x = -1, attr_y = -1;
6884 if (game.all_players_gone)
6886 attr_x = game.exit_x;
6887 attr_y = game.exit_y;
6893 for (i = 0; i < MAX_PLAYERS; i++)
6895 struct PlayerInfo *player = &stored_player[i];
6896 int jx = player->jx, jy = player->jy;
6898 if (!player->active)
6902 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6910 if (element == EL_ROBOT &&
6911 game.robot_wheel_x >= 0 &&
6912 game.robot_wheel_y >= 0 &&
6913 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
6914 game.engine_version < VERSION_IDENT(3,1,0,0)))
6916 attr_x = game.robot_wheel_x;
6917 attr_y = game.robot_wheel_y;
6920 if (element == EL_PENGUIN)
6923 static int xy[4][2] =
6931 for (i = 0; i < NUM_DIRECTIONS; i++)
6933 int ex = x + xy[i][0];
6934 int ey = y + xy[i][1];
6936 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
6937 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
6938 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
6939 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
6948 MovDir[x][y] = MV_NONE;
6950 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
6951 else if (attr_x > x)
6952 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
6954 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
6955 else if (attr_y > y)
6956 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
6958 if (element == EL_ROBOT)
6962 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6963 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
6964 Moving2Blocked(x, y, &newx, &newy);
6966 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
6967 MovDelay[x][y] = 8 + 8 * !RND(3);
6969 MovDelay[x][y] = 16;
6971 else if (element == EL_PENGUIN)
6977 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6979 boolean first_horiz = RND(2);
6980 int new_move_dir = MovDir[x][y];
6983 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6984 Moving2Blocked(x, y, &newx, &newy);
6986 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6990 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6991 Moving2Blocked(x, y, &newx, &newy);
6993 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6996 MovDir[x][y] = old_move_dir;
7000 else if (element == EL_SATELLITE)
7006 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7008 boolean first_horiz = RND(2);
7009 int new_move_dir = MovDir[x][y];
7012 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7013 Moving2Blocked(x, y, &newx, &newy);
7015 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7019 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7020 Moving2Blocked(x, y, &newx, &newy);
7022 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7025 MovDir[x][y] = old_move_dir;
7029 else if (element == EL_EMC_ANDROID)
7031 static int check_pos[16] =
7033 -1, // 0 => (invalid)
7036 -1, // 3 => (invalid)
7038 0, // 5 => MV_LEFT | MV_UP
7039 2, // 6 => MV_RIGHT | MV_UP
7040 -1, // 7 => (invalid)
7042 6, // 9 => MV_LEFT | MV_DOWN
7043 4, // 10 => MV_RIGHT | MV_DOWN
7044 -1, // 11 => (invalid)
7045 -1, // 12 => (invalid)
7046 -1, // 13 => (invalid)
7047 -1, // 14 => (invalid)
7048 -1, // 15 => (invalid)
7056 { -1, -1, MV_LEFT | MV_UP },
7058 { +1, -1, MV_RIGHT | MV_UP },
7059 { +1, 0, MV_RIGHT },
7060 { +1, +1, MV_RIGHT | MV_DOWN },
7062 { -1, +1, MV_LEFT | MV_DOWN },
7065 int start_pos, check_order;
7066 boolean can_clone = FALSE;
7069 // check if there is any free field around current position
7070 for (i = 0; i < 8; i++)
7072 int newx = x + check_xy[i].dx;
7073 int newy = y + check_xy[i].dy;
7075 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7083 if (can_clone) // randomly find an element to clone
7087 start_pos = check_pos[RND(8)];
7088 check_order = (RND(2) ? -1 : +1);
7090 for (i = 0; i < 8; i++)
7092 int pos_raw = start_pos + i * check_order;
7093 int pos = (pos_raw + 8) % 8;
7094 int newx = x + check_xy[pos].dx;
7095 int newy = y + check_xy[pos].dy;
7097 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7099 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7100 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7102 Store[x][y] = Feld[newx][newy];
7111 if (can_clone) // randomly find a direction to move
7115 start_pos = check_pos[RND(8)];
7116 check_order = (RND(2) ? -1 : +1);
7118 for (i = 0; i < 8; i++)
7120 int pos_raw = start_pos + i * check_order;
7121 int pos = (pos_raw + 8) % 8;
7122 int newx = x + check_xy[pos].dx;
7123 int newy = y + check_xy[pos].dy;
7124 int new_move_dir = check_xy[pos].dir;
7126 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7128 MovDir[x][y] = new_move_dir;
7129 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7138 if (can_clone) // cloning and moving successful
7141 // cannot clone -- try to move towards player
7143 start_pos = check_pos[MovDir[x][y] & 0x0f];
7144 check_order = (RND(2) ? -1 : +1);
7146 for (i = 0; i < 3; i++)
7148 // first check start_pos, then previous/next or (next/previous) pos
7149 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7150 int pos = (pos_raw + 8) % 8;
7151 int newx = x + check_xy[pos].dx;
7152 int newy = y + check_xy[pos].dy;
7153 int new_move_dir = check_xy[pos].dir;
7155 if (IS_PLAYER(newx, newy))
7158 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7160 MovDir[x][y] = new_move_dir;
7161 MovDelay[x][y] = level.android_move_time * 8 + 1;
7168 else if (move_pattern == MV_TURNING_LEFT ||
7169 move_pattern == MV_TURNING_RIGHT ||
7170 move_pattern == MV_TURNING_LEFT_RIGHT ||
7171 move_pattern == MV_TURNING_RIGHT_LEFT ||
7172 move_pattern == MV_TURNING_RANDOM ||
7173 move_pattern == MV_ALL_DIRECTIONS)
7175 boolean can_turn_left =
7176 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7177 boolean can_turn_right =
7178 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7180 if (element_info[element].move_stepsize == 0) // "not moving"
7183 if (move_pattern == MV_TURNING_LEFT)
7184 MovDir[x][y] = left_dir;
7185 else if (move_pattern == MV_TURNING_RIGHT)
7186 MovDir[x][y] = right_dir;
7187 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7188 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7189 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7190 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7191 else if (move_pattern == MV_TURNING_RANDOM)
7192 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7193 can_turn_right && !can_turn_left ? right_dir :
7194 RND(2) ? left_dir : right_dir);
7195 else if (can_turn_left && can_turn_right)
7196 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7197 else if (can_turn_left)
7198 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7199 else if (can_turn_right)
7200 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7202 MovDir[x][y] = back_dir;
7204 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7206 else if (move_pattern == MV_HORIZONTAL ||
7207 move_pattern == MV_VERTICAL)
7209 if (move_pattern & old_move_dir)
7210 MovDir[x][y] = back_dir;
7211 else if (move_pattern == MV_HORIZONTAL)
7212 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7213 else if (move_pattern == MV_VERTICAL)
7214 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7216 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7218 else if (move_pattern & MV_ANY_DIRECTION)
7220 MovDir[x][y] = move_pattern;
7221 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7223 else if (move_pattern & MV_WIND_DIRECTION)
7225 MovDir[x][y] = game.wind_direction;
7226 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7228 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7230 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7231 MovDir[x][y] = left_dir;
7232 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7233 MovDir[x][y] = right_dir;
7235 if (MovDir[x][y] != old_move_dir)
7236 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7238 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7240 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7241 MovDir[x][y] = right_dir;
7242 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7243 MovDir[x][y] = left_dir;
7245 if (MovDir[x][y] != old_move_dir)
7246 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7248 else if (move_pattern == MV_TOWARDS_PLAYER ||
7249 move_pattern == MV_AWAY_FROM_PLAYER)
7251 int attr_x = -1, attr_y = -1;
7253 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7255 if (game.all_players_gone)
7257 attr_x = game.exit_x;
7258 attr_y = game.exit_y;
7264 for (i = 0; i < MAX_PLAYERS; i++)
7266 struct PlayerInfo *player = &stored_player[i];
7267 int jx = player->jx, jy = player->jy;
7269 if (!player->active)
7273 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7281 MovDir[x][y] = MV_NONE;
7283 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7284 else if (attr_x > x)
7285 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7287 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7288 else if (attr_y > y)
7289 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7291 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7293 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7295 boolean first_horiz = RND(2);
7296 int new_move_dir = MovDir[x][y];
7298 if (element_info[element].move_stepsize == 0) // "not moving"
7300 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7301 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7307 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7308 Moving2Blocked(x, y, &newx, &newy);
7310 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7314 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7315 Moving2Blocked(x, y, &newx, &newy);
7317 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7320 MovDir[x][y] = old_move_dir;
7323 else if (move_pattern == MV_WHEN_PUSHED ||
7324 move_pattern == MV_WHEN_DROPPED)
7326 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7327 MovDir[x][y] = MV_NONE;
7331 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7333 static int test_xy[7][2] =
7343 static int test_dir[7] =
7353 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7354 int move_preference = -1000000; // start with very low preference
7355 int new_move_dir = MV_NONE;
7356 int start_test = RND(4);
7359 for (i = 0; i < NUM_DIRECTIONS; i++)
7361 int move_dir = test_dir[start_test + i];
7362 int move_dir_preference;
7364 xx = x + test_xy[start_test + i][0];
7365 yy = y + test_xy[start_test + i][1];
7367 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7368 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7370 new_move_dir = move_dir;
7375 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7378 move_dir_preference = -1 * RunnerVisit[xx][yy];
7379 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7380 move_dir_preference = PlayerVisit[xx][yy];
7382 if (move_dir_preference > move_preference)
7384 // prefer field that has not been visited for the longest time
7385 move_preference = move_dir_preference;
7386 new_move_dir = move_dir;
7388 else if (move_dir_preference == move_preference &&
7389 move_dir == old_move_dir)
7391 // prefer last direction when all directions are preferred equally
7392 move_preference = move_dir_preference;
7393 new_move_dir = move_dir;
7397 MovDir[x][y] = new_move_dir;
7398 if (old_move_dir != new_move_dir)
7399 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7403 static void TurnRound(int x, int y)
7405 int direction = MovDir[x][y];
7409 GfxDir[x][y] = MovDir[x][y];
7411 if (direction != MovDir[x][y])
7415 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7417 ResetGfxFrame(x, y);
7420 static boolean JustBeingPushed(int x, int y)
7424 for (i = 0; i < MAX_PLAYERS; i++)
7426 struct PlayerInfo *player = &stored_player[i];
7428 if (player->active && player->is_pushing && player->MovPos)
7430 int next_jx = player->jx + (player->jx - player->last_jx);
7431 int next_jy = player->jy + (player->jy - player->last_jy);
7433 if (x == next_jx && y == next_jy)
7441 static void StartMoving(int x, int y)
7443 boolean started_moving = FALSE; // some elements can fall _and_ move
7444 int element = Feld[x][y];
7449 if (MovDelay[x][y] == 0)
7450 GfxAction[x][y] = ACTION_DEFAULT;
7452 if (CAN_FALL(element) && y < lev_fieldy - 1)
7454 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7455 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7456 if (JustBeingPushed(x, y))
7459 if (element == EL_QUICKSAND_FULL)
7461 if (IS_FREE(x, y + 1))
7463 InitMovingField(x, y, MV_DOWN);
7464 started_moving = TRUE;
7466 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7467 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7468 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7469 Store[x][y] = EL_ROCK;
7471 Store[x][y] = EL_ROCK;
7474 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7476 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7478 if (!MovDelay[x][y])
7480 MovDelay[x][y] = TILEY + 1;
7482 ResetGfxAnimation(x, y);
7483 ResetGfxAnimation(x, y + 1);
7488 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7489 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7496 Feld[x][y] = EL_QUICKSAND_EMPTY;
7497 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7498 Store[x][y + 1] = Store[x][y];
7501 PlayLevelSoundAction(x, y, ACTION_FILLING);
7503 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7505 if (!MovDelay[x][y])
7507 MovDelay[x][y] = TILEY + 1;
7509 ResetGfxAnimation(x, y);
7510 ResetGfxAnimation(x, y + 1);
7515 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7516 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7523 Feld[x][y] = EL_QUICKSAND_EMPTY;
7524 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7525 Store[x][y + 1] = Store[x][y];
7528 PlayLevelSoundAction(x, y, ACTION_FILLING);
7531 else if (element == EL_QUICKSAND_FAST_FULL)
7533 if (IS_FREE(x, y + 1))
7535 InitMovingField(x, y, MV_DOWN);
7536 started_moving = TRUE;
7538 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7539 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7540 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7541 Store[x][y] = EL_ROCK;
7543 Store[x][y] = EL_ROCK;
7546 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7548 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7550 if (!MovDelay[x][y])
7552 MovDelay[x][y] = TILEY + 1;
7554 ResetGfxAnimation(x, y);
7555 ResetGfxAnimation(x, y + 1);
7560 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7561 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7568 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7569 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7570 Store[x][y + 1] = Store[x][y];
7573 PlayLevelSoundAction(x, y, ACTION_FILLING);
7575 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7577 if (!MovDelay[x][y])
7579 MovDelay[x][y] = TILEY + 1;
7581 ResetGfxAnimation(x, y);
7582 ResetGfxAnimation(x, y + 1);
7587 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7588 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7595 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7596 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7597 Store[x][y + 1] = Store[x][y];
7600 PlayLevelSoundAction(x, y, ACTION_FILLING);
7603 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7604 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7606 InitMovingField(x, y, MV_DOWN);
7607 started_moving = TRUE;
7609 Feld[x][y] = EL_QUICKSAND_FILLING;
7610 Store[x][y] = element;
7612 PlayLevelSoundAction(x, y, ACTION_FILLING);
7614 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7615 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7617 InitMovingField(x, y, MV_DOWN);
7618 started_moving = TRUE;
7620 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7621 Store[x][y] = element;
7623 PlayLevelSoundAction(x, y, ACTION_FILLING);
7625 else if (element == EL_MAGIC_WALL_FULL)
7627 if (IS_FREE(x, y + 1))
7629 InitMovingField(x, y, MV_DOWN);
7630 started_moving = TRUE;
7632 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7633 Store[x][y] = EL_CHANGED(Store[x][y]);
7635 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7637 if (!MovDelay[x][y])
7638 MovDelay[x][y] = TILEY / 4 + 1;
7647 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7648 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7649 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7653 else if (element == EL_BD_MAGIC_WALL_FULL)
7655 if (IS_FREE(x, y + 1))
7657 InitMovingField(x, y, MV_DOWN);
7658 started_moving = TRUE;
7660 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7661 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7663 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7665 if (!MovDelay[x][y])
7666 MovDelay[x][y] = TILEY / 4 + 1;
7675 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7676 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7677 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7681 else if (element == EL_DC_MAGIC_WALL_FULL)
7683 if (IS_FREE(x, y + 1))
7685 InitMovingField(x, y, MV_DOWN);
7686 started_moving = TRUE;
7688 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7689 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7691 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7693 if (!MovDelay[x][y])
7694 MovDelay[x][y] = TILEY / 4 + 1;
7703 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7704 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7705 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7709 else if ((CAN_PASS_MAGIC_WALL(element) &&
7710 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7711 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7712 (CAN_PASS_DC_MAGIC_WALL(element) &&
7713 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7716 InitMovingField(x, y, MV_DOWN);
7717 started_moving = TRUE;
7720 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7721 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7722 EL_DC_MAGIC_WALL_FILLING);
7723 Store[x][y] = element;
7725 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7727 SplashAcid(x, y + 1);
7729 InitMovingField(x, y, MV_DOWN);
7730 started_moving = TRUE;
7732 Store[x][y] = EL_ACID;
7735 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7736 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7737 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7738 CAN_FALL(element) && WasJustFalling[x][y] &&
7739 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7741 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7742 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7743 (Feld[x][y + 1] == EL_BLOCKED)))
7745 /* this is needed for a special case not covered by calling "Impact()"
7746 from "ContinueMoving()": if an element moves to a tile directly below
7747 another element which was just falling on that tile (which was empty
7748 in the previous frame), the falling element above would just stop
7749 instead of smashing the element below (in previous version, the above
7750 element was just checked for "moving" instead of "falling", resulting
7751 in incorrect smashes caused by horizontal movement of the above
7752 element; also, the case of the player being the element to smash was
7753 simply not covered here... :-/ ) */
7755 CheckCollision[x][y] = 0;
7756 CheckImpact[x][y] = 0;
7760 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7762 if (MovDir[x][y] == MV_NONE)
7764 InitMovingField(x, y, MV_DOWN);
7765 started_moving = TRUE;
7768 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7770 if (WasJustFalling[x][y]) // prevent animation from being restarted
7771 MovDir[x][y] = MV_DOWN;
7773 InitMovingField(x, y, MV_DOWN);
7774 started_moving = TRUE;
7776 else if (element == EL_AMOEBA_DROP)
7778 Feld[x][y] = EL_AMOEBA_GROWING;
7779 Store[x][y] = EL_AMOEBA_WET;
7781 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7782 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7783 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7784 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7786 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7787 (IS_FREE(x - 1, y + 1) ||
7788 Feld[x - 1][y + 1] == EL_ACID));
7789 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7790 (IS_FREE(x + 1, y + 1) ||
7791 Feld[x + 1][y + 1] == EL_ACID));
7792 boolean can_fall_any = (can_fall_left || can_fall_right);
7793 boolean can_fall_both = (can_fall_left && can_fall_right);
7794 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7796 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7798 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7799 can_fall_right = FALSE;
7800 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7801 can_fall_left = FALSE;
7802 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7803 can_fall_right = FALSE;
7804 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7805 can_fall_left = FALSE;
7807 can_fall_any = (can_fall_left || can_fall_right);
7808 can_fall_both = FALSE;
7813 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7814 can_fall_right = FALSE; // slip down on left side
7816 can_fall_left = !(can_fall_right = RND(2));
7818 can_fall_both = FALSE;
7823 // if not determined otherwise, prefer left side for slipping down
7824 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7825 started_moving = TRUE;
7828 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7830 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7831 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7832 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7833 int belt_dir = game.belt_dir[belt_nr];
7835 if ((belt_dir == MV_LEFT && left_is_free) ||
7836 (belt_dir == MV_RIGHT && right_is_free))
7838 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7840 InitMovingField(x, y, belt_dir);
7841 started_moving = TRUE;
7843 Pushed[x][y] = TRUE;
7844 Pushed[nextx][y] = TRUE;
7846 GfxAction[x][y] = ACTION_DEFAULT;
7850 MovDir[x][y] = 0; // if element was moving, stop it
7855 // not "else if" because of elements that can fall and move (EL_SPRING)
7856 if (CAN_MOVE(element) && !started_moving)
7858 int move_pattern = element_info[element].move_pattern;
7861 Moving2Blocked(x, y, &newx, &newy);
7863 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7866 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7867 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7869 WasJustMoving[x][y] = 0;
7870 CheckCollision[x][y] = 0;
7872 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7874 if (Feld[x][y] != element) // element has changed
7878 if (!MovDelay[x][y]) // start new movement phase
7880 // all objects that can change their move direction after each step
7881 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7883 if (element != EL_YAMYAM &&
7884 element != EL_DARK_YAMYAM &&
7885 element != EL_PACMAN &&
7886 !(move_pattern & MV_ANY_DIRECTION) &&
7887 move_pattern != MV_TURNING_LEFT &&
7888 move_pattern != MV_TURNING_RIGHT &&
7889 move_pattern != MV_TURNING_LEFT_RIGHT &&
7890 move_pattern != MV_TURNING_RIGHT_LEFT &&
7891 move_pattern != MV_TURNING_RANDOM)
7895 if (MovDelay[x][y] && (element == EL_BUG ||
7896 element == EL_SPACESHIP ||
7897 element == EL_SP_SNIKSNAK ||
7898 element == EL_SP_ELECTRON ||
7899 element == EL_MOLE))
7900 TEST_DrawLevelField(x, y);
7904 if (MovDelay[x][y]) // wait some time before next movement
7908 if (element == EL_ROBOT ||
7909 element == EL_YAMYAM ||
7910 element == EL_DARK_YAMYAM)
7912 DrawLevelElementAnimationIfNeeded(x, y, element);
7913 PlayLevelSoundAction(x, y, ACTION_WAITING);
7915 else if (element == EL_SP_ELECTRON)
7916 DrawLevelElementAnimationIfNeeded(x, y, element);
7917 else if (element == EL_DRAGON)
7920 int dir = MovDir[x][y];
7921 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
7922 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
7923 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
7924 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
7925 dir == MV_UP ? IMG_FLAMES_1_UP :
7926 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
7927 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
7929 GfxAction[x][y] = ACTION_ATTACKING;
7931 if (IS_PLAYER(x, y))
7932 DrawPlayerField(x, y);
7934 TEST_DrawLevelField(x, y);
7936 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
7938 for (i = 1; i <= 3; i++)
7940 int xx = x + i * dx;
7941 int yy = y + i * dy;
7942 int sx = SCREENX(xx);
7943 int sy = SCREENY(yy);
7944 int flame_graphic = graphic + (i - 1);
7946 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
7951 int flamed = MovingOrBlocked2Element(xx, yy);
7953 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
7956 RemoveMovingField(xx, yy);
7958 ChangeDelay[xx][yy] = 0;
7960 Feld[xx][yy] = EL_FLAMES;
7962 if (IN_SCR_FIELD(sx, sy))
7964 TEST_DrawLevelFieldCrumbled(xx, yy);
7965 DrawGraphic(sx, sy, flame_graphic, frame);
7970 if (Feld[xx][yy] == EL_FLAMES)
7971 Feld[xx][yy] = EL_EMPTY;
7972 TEST_DrawLevelField(xx, yy);
7977 if (MovDelay[x][y]) // element still has to wait some time
7979 PlayLevelSoundAction(x, y, ACTION_WAITING);
7985 // now make next step
7987 Moving2Blocked(x, y, &newx, &newy); // get next screen position
7989 if (DONT_COLLIDE_WITH(element) &&
7990 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
7991 !PLAYER_ENEMY_PROTECTED(newx, newy))
7993 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
7998 else if (CAN_MOVE_INTO_ACID(element) &&
7999 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
8000 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8001 (MovDir[x][y] == MV_DOWN ||
8002 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8004 SplashAcid(newx, newy);
8005 Store[x][y] = EL_ACID;
8007 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8009 if (Feld[newx][newy] == EL_EXIT_OPEN ||
8010 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
8011 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
8012 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8015 TEST_DrawLevelField(x, y);
8017 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8018 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8019 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8021 game.friends_still_needed--;
8022 if (!game.friends_still_needed &&
8024 game.all_players_gone)
8029 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
8031 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8032 TEST_DrawLevelField(newx, newy);
8034 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8036 else if (!IS_FREE(newx, newy))
8038 GfxAction[x][y] = ACTION_WAITING;
8040 if (IS_PLAYER(x, y))
8041 DrawPlayerField(x, y);
8043 TEST_DrawLevelField(x, y);
8048 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8050 if (IS_FOOD_PIG(Feld[newx][newy]))
8052 if (IS_MOVING(newx, newy))
8053 RemoveMovingField(newx, newy);
8056 Feld[newx][newy] = EL_EMPTY;
8057 TEST_DrawLevelField(newx, newy);
8060 PlayLevelSound(x, y, SND_PIG_DIGGING);
8062 else if (!IS_FREE(newx, newy))
8064 if (IS_PLAYER(x, y))
8065 DrawPlayerField(x, y);
8067 TEST_DrawLevelField(x, y);
8072 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8074 if (Store[x][y] != EL_EMPTY)
8076 boolean can_clone = FALSE;
8079 // check if element to clone is still there
8080 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8082 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8090 // cannot clone or target field not free anymore -- do not clone
8091 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8092 Store[x][y] = EL_EMPTY;
8095 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8097 if (IS_MV_DIAGONAL(MovDir[x][y]))
8099 int diagonal_move_dir = MovDir[x][y];
8100 int stored = Store[x][y];
8101 int change_delay = 8;
8104 // android is moving diagonally
8106 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8108 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8109 GfxElement[x][y] = EL_EMC_ANDROID;
8110 GfxAction[x][y] = ACTION_SHRINKING;
8111 GfxDir[x][y] = diagonal_move_dir;
8112 ChangeDelay[x][y] = change_delay;
8114 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8117 DrawLevelGraphicAnimation(x, y, graphic);
8118 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8120 if (Feld[newx][newy] == EL_ACID)
8122 SplashAcid(newx, newy);
8127 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8129 Store[newx][newy] = EL_EMC_ANDROID;
8130 GfxElement[newx][newy] = EL_EMC_ANDROID;
8131 GfxAction[newx][newy] = ACTION_GROWING;
8132 GfxDir[newx][newy] = diagonal_move_dir;
8133 ChangeDelay[newx][newy] = change_delay;
8135 graphic = el_act_dir2img(GfxElement[newx][newy],
8136 GfxAction[newx][newy], GfxDir[newx][newy]);
8138 DrawLevelGraphicAnimation(newx, newy, graphic);
8139 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8145 Feld[newx][newy] = EL_EMPTY;
8146 TEST_DrawLevelField(newx, newy);
8148 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8151 else if (!IS_FREE(newx, newy))
8156 else if (IS_CUSTOM_ELEMENT(element) &&
8157 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8159 if (!DigFieldByCE(newx, newy, element))
8162 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8164 RunnerVisit[x][y] = FrameCounter;
8165 PlayerVisit[x][y] /= 8; // expire player visit path
8168 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8170 if (!IS_FREE(newx, newy))
8172 if (IS_PLAYER(x, y))
8173 DrawPlayerField(x, y);
8175 TEST_DrawLevelField(x, y);
8181 boolean wanna_flame = !RND(10);
8182 int dx = newx - x, dy = newy - y;
8183 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8184 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8185 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8186 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8187 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8188 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8191 IS_CLASSIC_ENEMY(element1) ||
8192 IS_CLASSIC_ENEMY(element2)) &&
8193 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8194 element1 != EL_FLAMES && element2 != EL_FLAMES)
8196 ResetGfxAnimation(x, y);
8197 GfxAction[x][y] = ACTION_ATTACKING;
8199 if (IS_PLAYER(x, y))
8200 DrawPlayerField(x, y);
8202 TEST_DrawLevelField(x, y);
8204 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8206 MovDelay[x][y] = 50;
8208 Feld[newx][newy] = EL_FLAMES;
8209 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8210 Feld[newx1][newy1] = EL_FLAMES;
8211 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8212 Feld[newx2][newy2] = EL_FLAMES;
8218 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8219 Feld[newx][newy] == EL_DIAMOND)
8221 if (IS_MOVING(newx, newy))
8222 RemoveMovingField(newx, newy);
8225 Feld[newx][newy] = EL_EMPTY;
8226 TEST_DrawLevelField(newx, newy);
8229 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8231 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8232 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8234 if (AmoebaNr[newx][newy])
8236 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8237 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8238 Feld[newx][newy] == EL_BD_AMOEBA)
8239 AmoebaCnt[AmoebaNr[newx][newy]]--;
8242 if (IS_MOVING(newx, newy))
8244 RemoveMovingField(newx, newy);
8248 Feld[newx][newy] = EL_EMPTY;
8249 TEST_DrawLevelField(newx, newy);
8252 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8254 else if ((element == EL_PACMAN || element == EL_MOLE)
8255 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8257 if (AmoebaNr[newx][newy])
8259 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8260 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8261 Feld[newx][newy] == EL_BD_AMOEBA)
8262 AmoebaCnt[AmoebaNr[newx][newy]]--;
8265 if (element == EL_MOLE)
8267 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8268 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8270 ResetGfxAnimation(x, y);
8271 GfxAction[x][y] = ACTION_DIGGING;
8272 TEST_DrawLevelField(x, y);
8274 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8276 return; // wait for shrinking amoeba
8278 else // element == EL_PACMAN
8280 Feld[newx][newy] = EL_EMPTY;
8281 TEST_DrawLevelField(newx, newy);
8282 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8285 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8286 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8287 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8289 // wait for shrinking amoeba to completely disappear
8292 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8294 // object was running against a wall
8298 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8299 DrawLevelElementAnimation(x, y, element);
8301 if (DONT_TOUCH(element))
8302 TestIfBadThingTouchesPlayer(x, y);
8307 InitMovingField(x, y, MovDir[x][y]);
8309 PlayLevelSoundAction(x, y, ACTION_MOVING);
8313 ContinueMoving(x, y);
8316 void ContinueMoving(int x, int y)
8318 int element = Feld[x][y];
8319 struct ElementInfo *ei = &element_info[element];
8320 int direction = MovDir[x][y];
8321 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8322 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8323 int newx = x + dx, newy = y + dy;
8324 int stored = Store[x][y];
8325 int stored_new = Store[newx][newy];
8326 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8327 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8328 boolean last_line = (newy == lev_fieldy - 1);
8330 MovPos[x][y] += getElementMoveStepsize(x, y);
8332 if (pushed_by_player) // special case: moving object pushed by player
8333 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8335 if (ABS(MovPos[x][y]) < TILEX)
8337 TEST_DrawLevelField(x, y);
8339 return; // element is still moving
8342 // element reached destination field
8344 Feld[x][y] = EL_EMPTY;
8345 Feld[newx][newy] = element;
8346 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8348 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8350 element = Feld[newx][newy] = EL_ACID;
8352 else if (element == EL_MOLE)
8354 Feld[x][y] = EL_SAND;
8356 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8358 else if (element == EL_QUICKSAND_FILLING)
8360 element = Feld[newx][newy] = get_next_element(element);
8361 Store[newx][newy] = Store[x][y];
8363 else if (element == EL_QUICKSAND_EMPTYING)
8365 Feld[x][y] = get_next_element(element);
8366 element = Feld[newx][newy] = Store[x][y];
8368 else if (element == EL_QUICKSAND_FAST_FILLING)
8370 element = Feld[newx][newy] = get_next_element(element);
8371 Store[newx][newy] = Store[x][y];
8373 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8375 Feld[x][y] = get_next_element(element);
8376 element = Feld[newx][newy] = Store[x][y];
8378 else if (element == EL_MAGIC_WALL_FILLING)
8380 element = Feld[newx][newy] = get_next_element(element);
8381 if (!game.magic_wall_active)
8382 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8383 Store[newx][newy] = Store[x][y];
8385 else if (element == EL_MAGIC_WALL_EMPTYING)
8387 Feld[x][y] = get_next_element(element);
8388 if (!game.magic_wall_active)
8389 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8390 element = Feld[newx][newy] = Store[x][y];
8392 InitField(newx, newy, FALSE);
8394 else if (element == EL_BD_MAGIC_WALL_FILLING)
8396 element = Feld[newx][newy] = get_next_element(element);
8397 if (!game.magic_wall_active)
8398 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8399 Store[newx][newy] = Store[x][y];
8401 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8403 Feld[x][y] = get_next_element(element);
8404 if (!game.magic_wall_active)
8405 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8406 element = Feld[newx][newy] = Store[x][y];
8408 InitField(newx, newy, FALSE);
8410 else if (element == EL_DC_MAGIC_WALL_FILLING)
8412 element = Feld[newx][newy] = get_next_element(element);
8413 if (!game.magic_wall_active)
8414 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8415 Store[newx][newy] = Store[x][y];
8417 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8419 Feld[x][y] = get_next_element(element);
8420 if (!game.magic_wall_active)
8421 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8422 element = Feld[newx][newy] = Store[x][y];
8424 InitField(newx, newy, FALSE);
8426 else if (element == EL_AMOEBA_DROPPING)
8428 Feld[x][y] = get_next_element(element);
8429 element = Feld[newx][newy] = Store[x][y];
8431 else if (element == EL_SOKOBAN_OBJECT)
8434 Feld[x][y] = Back[x][y];
8436 if (Back[newx][newy])
8437 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8439 Back[x][y] = Back[newx][newy] = 0;
8442 Store[x][y] = EL_EMPTY;
8447 MovDelay[newx][newy] = 0;
8449 if (CAN_CHANGE_OR_HAS_ACTION(element))
8451 // copy element change control values to new field
8452 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8453 ChangePage[newx][newy] = ChangePage[x][y];
8454 ChangeCount[newx][newy] = ChangeCount[x][y];
8455 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8458 CustomValue[newx][newy] = CustomValue[x][y];
8460 ChangeDelay[x][y] = 0;
8461 ChangePage[x][y] = -1;
8462 ChangeCount[x][y] = 0;
8463 ChangeEvent[x][y] = -1;
8465 CustomValue[x][y] = 0;
8467 // copy animation control values to new field
8468 GfxFrame[newx][newy] = GfxFrame[x][y];
8469 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8470 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8471 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8473 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8475 // some elements can leave other elements behind after moving
8476 if (ei->move_leave_element != EL_EMPTY &&
8477 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8478 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8480 int move_leave_element = ei->move_leave_element;
8482 // this makes it possible to leave the removed element again
8483 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8484 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8486 Feld[x][y] = move_leave_element;
8488 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8489 MovDir[x][y] = direction;
8491 InitField(x, y, FALSE);
8493 if (GFX_CRUMBLED(Feld[x][y]))
8494 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8496 if (ELEM_IS_PLAYER(move_leave_element))
8497 RelocatePlayer(x, y, move_leave_element);
8500 // do this after checking for left-behind element
8501 ResetGfxAnimation(x, y); // reset animation values for old field
8503 if (!CAN_MOVE(element) ||
8504 (CAN_FALL(element) && direction == MV_DOWN &&
8505 (element == EL_SPRING ||
8506 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8507 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8508 GfxDir[x][y] = MovDir[newx][newy] = 0;
8510 TEST_DrawLevelField(x, y);
8511 TEST_DrawLevelField(newx, newy);
8513 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8515 // prevent pushed element from moving on in pushed direction
8516 if (pushed_by_player && CAN_MOVE(element) &&
8517 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8518 !(element_info[element].move_pattern & direction))
8519 TurnRound(newx, newy);
8521 // prevent elements on conveyor belt from moving on in last direction
8522 if (pushed_by_conveyor && CAN_FALL(element) &&
8523 direction & MV_HORIZONTAL)
8524 MovDir[newx][newy] = 0;
8526 if (!pushed_by_player)
8528 int nextx = newx + dx, nexty = newy + dy;
8529 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8531 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8533 if (CAN_FALL(element) && direction == MV_DOWN)
8534 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8536 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8537 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8539 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8540 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8543 if (DONT_TOUCH(element)) // object may be nasty to player or others
8545 TestIfBadThingTouchesPlayer(newx, newy);
8546 TestIfBadThingTouchesFriend(newx, newy);
8548 if (!IS_CUSTOM_ELEMENT(element))
8549 TestIfBadThingTouchesOtherBadThing(newx, newy);
8551 else if (element == EL_PENGUIN)
8552 TestIfFriendTouchesBadThing(newx, newy);
8554 if (DONT_GET_HIT_BY(element))
8556 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8559 // give the player one last chance (one more frame) to move away
8560 if (CAN_FALL(element) && direction == MV_DOWN &&
8561 (last_line || (!IS_FREE(x, newy + 1) &&
8562 (!IS_PLAYER(x, newy + 1) ||
8563 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8566 if (pushed_by_player && !game.use_change_when_pushing_bug)
8568 int push_side = MV_DIR_OPPOSITE(direction);
8569 struct PlayerInfo *player = PLAYERINFO(x, y);
8571 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8572 player->index_bit, push_side);
8573 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8574 player->index_bit, push_side);
8577 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8578 MovDelay[newx][newy] = 1;
8580 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8582 TestIfElementTouchesCustomElement(x, y); // empty or new element
8583 TestIfElementHitsCustomElement(newx, newy, direction);
8584 TestIfPlayerTouchesCustomElement(newx, newy);
8585 TestIfElementTouchesCustomElement(newx, newy);
8587 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8588 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8589 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8590 MV_DIR_OPPOSITE(direction));
8593 int AmoebeNachbarNr(int ax, int ay)
8596 int element = Feld[ax][ay];
8598 static int xy[4][2] =
8606 for (i = 0; i < NUM_DIRECTIONS; i++)
8608 int x = ax + xy[i][0];
8609 int y = ay + xy[i][1];
8611 if (!IN_LEV_FIELD(x, y))
8614 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8615 group_nr = AmoebaNr[x][y];
8621 static void AmoebenVereinigen(int ax, int ay)
8623 int i, x, y, xx, yy;
8624 int new_group_nr = AmoebaNr[ax][ay];
8625 static int xy[4][2] =
8633 if (new_group_nr == 0)
8636 for (i = 0; i < NUM_DIRECTIONS; i++)
8641 if (!IN_LEV_FIELD(x, y))
8644 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8645 Feld[x][y] == EL_BD_AMOEBA ||
8646 Feld[x][y] == EL_AMOEBA_DEAD) &&
8647 AmoebaNr[x][y] != new_group_nr)
8649 int old_group_nr = AmoebaNr[x][y];
8651 if (old_group_nr == 0)
8654 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8655 AmoebaCnt[old_group_nr] = 0;
8656 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8657 AmoebaCnt2[old_group_nr] = 0;
8659 SCAN_PLAYFIELD(xx, yy)
8661 if (AmoebaNr[xx][yy] == old_group_nr)
8662 AmoebaNr[xx][yy] = new_group_nr;
8668 void AmoebeUmwandeln(int ax, int ay)
8672 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8674 int group_nr = AmoebaNr[ax][ay];
8679 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8680 printf("AmoebeUmwandeln(): This should never happen!\n");
8685 SCAN_PLAYFIELD(x, y)
8687 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8690 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8694 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8695 SND_AMOEBA_TURNING_TO_GEM :
8696 SND_AMOEBA_TURNING_TO_ROCK));
8701 static int xy[4][2] =
8709 for (i = 0; i < NUM_DIRECTIONS; i++)
8714 if (!IN_LEV_FIELD(x, y))
8717 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8719 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8720 SND_AMOEBA_TURNING_TO_GEM :
8721 SND_AMOEBA_TURNING_TO_ROCK));
8728 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8731 int group_nr = AmoebaNr[ax][ay];
8732 boolean done = FALSE;
8737 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8738 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8743 SCAN_PLAYFIELD(x, y)
8745 if (AmoebaNr[x][y] == group_nr &&
8746 (Feld[x][y] == EL_AMOEBA_DEAD ||
8747 Feld[x][y] == EL_BD_AMOEBA ||
8748 Feld[x][y] == EL_AMOEBA_GROWING))
8751 Feld[x][y] = new_element;
8752 InitField(x, y, FALSE);
8753 TEST_DrawLevelField(x, y);
8759 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8760 SND_BD_AMOEBA_TURNING_TO_ROCK :
8761 SND_BD_AMOEBA_TURNING_TO_GEM));
8764 static void AmoebeWaechst(int x, int y)
8766 static unsigned int sound_delay = 0;
8767 static unsigned int sound_delay_value = 0;
8769 if (!MovDelay[x][y]) // start new growing cycle
8773 if (DelayReached(&sound_delay, sound_delay_value))
8775 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8776 sound_delay_value = 30;
8780 if (MovDelay[x][y]) // wait some time before growing bigger
8783 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8785 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8786 6 - MovDelay[x][y]);
8788 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8791 if (!MovDelay[x][y])
8793 Feld[x][y] = Store[x][y];
8795 TEST_DrawLevelField(x, y);
8800 static void AmoebaDisappearing(int x, int y)
8802 static unsigned int sound_delay = 0;
8803 static unsigned int sound_delay_value = 0;
8805 if (!MovDelay[x][y]) // start new shrinking cycle
8809 if (DelayReached(&sound_delay, sound_delay_value))
8810 sound_delay_value = 30;
8813 if (MovDelay[x][y]) // wait some time before shrinking
8816 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8818 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8819 6 - MovDelay[x][y]);
8821 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8824 if (!MovDelay[x][y])
8826 Feld[x][y] = EL_EMPTY;
8827 TEST_DrawLevelField(x, y);
8829 // don't let mole enter this field in this cycle;
8830 // (give priority to objects falling to this field from above)
8836 static void AmoebeAbleger(int ax, int ay)
8839 int element = Feld[ax][ay];
8840 int graphic = el2img(element);
8841 int newax = ax, neway = ay;
8842 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8843 static int xy[4][2] =
8851 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8853 Feld[ax][ay] = EL_AMOEBA_DEAD;
8854 TEST_DrawLevelField(ax, ay);
8858 if (IS_ANIMATED(graphic))
8859 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8861 if (!MovDelay[ax][ay]) // start making new amoeba field
8862 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8864 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8867 if (MovDelay[ax][ay])
8871 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8874 int x = ax + xy[start][0];
8875 int y = ay + xy[start][1];
8877 if (!IN_LEV_FIELD(x, y))
8880 if (IS_FREE(x, y) ||
8881 CAN_GROW_INTO(Feld[x][y]) ||
8882 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8883 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8889 if (newax == ax && neway == ay)
8892 else // normal or "filled" (BD style) amoeba
8895 boolean waiting_for_player = FALSE;
8897 for (i = 0; i < NUM_DIRECTIONS; i++)
8899 int j = (start + i) % 4;
8900 int x = ax + xy[j][0];
8901 int y = ay + xy[j][1];
8903 if (!IN_LEV_FIELD(x, y))
8906 if (IS_FREE(x, y) ||
8907 CAN_GROW_INTO(Feld[x][y]) ||
8908 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8909 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8915 else if (IS_PLAYER(x, y))
8916 waiting_for_player = TRUE;
8919 if (newax == ax && neway == ay) // amoeba cannot grow
8921 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
8923 Feld[ax][ay] = EL_AMOEBA_DEAD;
8924 TEST_DrawLevelField(ax, ay);
8925 AmoebaCnt[AmoebaNr[ax][ay]]--;
8927 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
8929 if (element == EL_AMOEBA_FULL)
8930 AmoebeUmwandeln(ax, ay);
8931 else if (element == EL_BD_AMOEBA)
8932 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
8937 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
8939 // amoeba gets larger by growing in some direction
8941 int new_group_nr = AmoebaNr[ax][ay];
8944 if (new_group_nr == 0)
8946 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
8947 printf("AmoebeAbleger(): This should never happen!\n");
8952 AmoebaNr[newax][neway] = new_group_nr;
8953 AmoebaCnt[new_group_nr]++;
8954 AmoebaCnt2[new_group_nr]++;
8956 // if amoeba touches other amoeba(s) after growing, unify them
8957 AmoebenVereinigen(newax, neway);
8959 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
8961 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
8967 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
8968 (neway == lev_fieldy - 1 && newax != ax))
8970 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
8971 Store[newax][neway] = element;
8973 else if (neway == ay || element == EL_EMC_DRIPPER)
8975 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
8977 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
8981 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
8982 Feld[ax][ay] = EL_AMOEBA_DROPPING;
8983 Store[ax][ay] = EL_AMOEBA_DROP;
8984 ContinueMoving(ax, ay);
8988 TEST_DrawLevelField(newax, neway);
8991 static void Life(int ax, int ay)
8995 int element = Feld[ax][ay];
8996 int graphic = el2img(element);
8997 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
8999 boolean changed = FALSE;
9001 if (IS_ANIMATED(graphic))
9002 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9007 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9008 MovDelay[ax][ay] = life_time;
9010 if (MovDelay[ax][ay]) // wait some time before next cycle
9013 if (MovDelay[ax][ay])
9017 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9019 int xx = ax+x1, yy = ay+y1;
9020 int old_element = Feld[xx][yy];
9021 int num_neighbours = 0;
9023 if (!IN_LEV_FIELD(xx, yy))
9026 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9028 int x = xx+x2, y = yy+y2;
9030 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9033 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9034 boolean is_neighbour = FALSE;
9036 if (level.use_life_bugs)
9038 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9039 (IS_FREE(x, y) && Stop[x][y]));
9042 (Last[x][y] == element || is_player_cell);
9048 boolean is_free = FALSE;
9050 if (level.use_life_bugs)
9051 is_free = (IS_FREE(xx, yy));
9053 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9055 if (xx == ax && yy == ay) // field in the middle
9057 if (num_neighbours < life_parameter[0] ||
9058 num_neighbours > life_parameter[1])
9060 Feld[xx][yy] = EL_EMPTY;
9061 if (Feld[xx][yy] != old_element)
9062 TEST_DrawLevelField(xx, yy);
9063 Stop[xx][yy] = TRUE;
9067 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9068 { // free border field
9069 if (num_neighbours >= life_parameter[2] &&
9070 num_neighbours <= life_parameter[3])
9072 Feld[xx][yy] = element;
9073 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9074 if (Feld[xx][yy] != old_element)
9075 TEST_DrawLevelField(xx, yy);
9076 Stop[xx][yy] = TRUE;
9083 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9084 SND_GAME_OF_LIFE_GROWING);
9087 static void InitRobotWheel(int x, int y)
9089 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9092 static void RunRobotWheel(int x, int y)
9094 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9097 static void StopRobotWheel(int x, int y)
9099 if (game.robot_wheel_x == x &&
9100 game.robot_wheel_y == y)
9102 game.robot_wheel_x = -1;
9103 game.robot_wheel_y = -1;
9104 game.robot_wheel_active = FALSE;
9108 static void InitTimegateWheel(int x, int y)
9110 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9113 static void RunTimegateWheel(int x, int y)
9115 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9118 static void InitMagicBallDelay(int x, int y)
9120 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9123 static void ActivateMagicBall(int bx, int by)
9127 if (level.ball_random)
9129 int pos_border = RND(8); // select one of the eight border elements
9130 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9131 int xx = pos_content % 3;
9132 int yy = pos_content / 3;
9137 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9138 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9142 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9144 int xx = x - bx + 1;
9145 int yy = y - by + 1;
9147 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9148 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9152 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9155 static void CheckExit(int x, int y)
9157 if (game.gems_still_needed > 0 ||
9158 game.sokoban_fields_still_needed > 0 ||
9159 game.sokoban_objects_still_needed > 0 ||
9160 game.lights_still_needed > 0)
9162 int element = Feld[x][y];
9163 int graphic = el2img(element);
9165 if (IS_ANIMATED(graphic))
9166 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9171 // do not re-open exit door closed after last player
9172 if (game.all_players_gone)
9175 Feld[x][y] = EL_EXIT_OPENING;
9177 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9180 static void CheckExitEM(int x, int y)
9182 if (game.gems_still_needed > 0 ||
9183 game.sokoban_fields_still_needed > 0 ||
9184 game.sokoban_objects_still_needed > 0 ||
9185 game.lights_still_needed > 0)
9187 int element = Feld[x][y];
9188 int graphic = el2img(element);
9190 if (IS_ANIMATED(graphic))
9191 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9196 // do not re-open exit door closed after last player
9197 if (game.all_players_gone)
9200 Feld[x][y] = EL_EM_EXIT_OPENING;
9202 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9205 static void CheckExitSteel(int x, int y)
9207 if (game.gems_still_needed > 0 ||
9208 game.sokoban_fields_still_needed > 0 ||
9209 game.sokoban_objects_still_needed > 0 ||
9210 game.lights_still_needed > 0)
9212 int element = Feld[x][y];
9213 int graphic = el2img(element);
9215 if (IS_ANIMATED(graphic))
9216 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9221 // do not re-open exit door closed after last player
9222 if (game.all_players_gone)
9225 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9227 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9230 static void CheckExitSteelEM(int x, int y)
9232 if (game.gems_still_needed > 0 ||
9233 game.sokoban_fields_still_needed > 0 ||
9234 game.sokoban_objects_still_needed > 0 ||
9235 game.lights_still_needed > 0)
9237 int element = Feld[x][y];
9238 int graphic = el2img(element);
9240 if (IS_ANIMATED(graphic))
9241 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9246 // do not re-open exit door closed after last player
9247 if (game.all_players_gone)
9250 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9252 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9255 static void CheckExitSP(int x, int y)
9257 if (game.gems_still_needed > 0)
9259 int element = Feld[x][y];
9260 int graphic = el2img(element);
9262 if (IS_ANIMATED(graphic))
9263 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9268 // do not re-open exit door closed after last player
9269 if (game.all_players_gone)
9272 Feld[x][y] = EL_SP_EXIT_OPENING;
9274 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9277 static void CloseAllOpenTimegates(void)
9281 SCAN_PLAYFIELD(x, y)
9283 int element = Feld[x][y];
9285 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9287 Feld[x][y] = EL_TIMEGATE_CLOSING;
9289 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9294 static void DrawTwinkleOnField(int x, int y)
9296 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9299 if (Feld[x][y] == EL_BD_DIAMOND)
9302 if (MovDelay[x][y] == 0) // next animation frame
9303 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9305 if (MovDelay[x][y] != 0) // wait some time before next frame
9309 DrawLevelElementAnimation(x, y, Feld[x][y]);
9311 if (MovDelay[x][y] != 0)
9313 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9314 10 - MovDelay[x][y]);
9316 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9321 static void MauerWaechst(int x, int y)
9325 if (!MovDelay[x][y]) // next animation frame
9326 MovDelay[x][y] = 3 * delay;
9328 if (MovDelay[x][y]) // wait some time before next frame
9332 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9334 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9335 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9337 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9340 if (!MovDelay[x][y])
9342 if (MovDir[x][y] == MV_LEFT)
9344 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9345 TEST_DrawLevelField(x - 1, y);
9347 else if (MovDir[x][y] == MV_RIGHT)
9349 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9350 TEST_DrawLevelField(x + 1, y);
9352 else if (MovDir[x][y] == MV_UP)
9354 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9355 TEST_DrawLevelField(x, y - 1);
9359 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9360 TEST_DrawLevelField(x, y + 1);
9363 Feld[x][y] = Store[x][y];
9365 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9366 TEST_DrawLevelField(x, y);
9371 static void MauerAbleger(int ax, int ay)
9373 int element = Feld[ax][ay];
9374 int graphic = el2img(element);
9375 boolean oben_frei = FALSE, unten_frei = FALSE;
9376 boolean links_frei = FALSE, rechts_frei = FALSE;
9377 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9378 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9379 boolean new_wall = FALSE;
9381 if (IS_ANIMATED(graphic))
9382 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9384 if (!MovDelay[ax][ay]) // start building new wall
9385 MovDelay[ax][ay] = 6;
9387 if (MovDelay[ax][ay]) // wait some time before building new wall
9390 if (MovDelay[ax][ay])
9394 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9396 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9398 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9400 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9403 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9404 element == EL_EXPANDABLE_WALL_ANY)
9408 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9409 Store[ax][ay-1] = element;
9410 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9411 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9412 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9413 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9418 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9419 Store[ax][ay+1] = element;
9420 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9421 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9422 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9423 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9428 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9429 element == EL_EXPANDABLE_WALL_ANY ||
9430 element == EL_EXPANDABLE_WALL ||
9431 element == EL_BD_EXPANDABLE_WALL)
9435 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9436 Store[ax-1][ay] = element;
9437 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9438 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9439 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9440 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9446 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9447 Store[ax+1][ay] = element;
9448 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9449 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9450 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9451 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9456 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9457 TEST_DrawLevelField(ax, ay);
9459 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9461 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9462 unten_massiv = TRUE;
9463 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9464 links_massiv = TRUE;
9465 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9466 rechts_massiv = TRUE;
9468 if (((oben_massiv && unten_massiv) ||
9469 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9470 element == EL_EXPANDABLE_WALL) &&
9471 ((links_massiv && rechts_massiv) ||
9472 element == EL_EXPANDABLE_WALL_VERTICAL))
9473 Feld[ax][ay] = EL_WALL;
9476 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9479 static void MauerAblegerStahl(int ax, int ay)
9481 int element = Feld[ax][ay];
9482 int graphic = el2img(element);
9483 boolean oben_frei = FALSE, unten_frei = FALSE;
9484 boolean links_frei = FALSE, rechts_frei = FALSE;
9485 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9486 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9487 boolean new_wall = FALSE;
9489 if (IS_ANIMATED(graphic))
9490 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9492 if (!MovDelay[ax][ay]) // start building new wall
9493 MovDelay[ax][ay] = 6;
9495 if (MovDelay[ax][ay]) // wait some time before building new wall
9498 if (MovDelay[ax][ay])
9502 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9504 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9506 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9508 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9511 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9512 element == EL_EXPANDABLE_STEELWALL_ANY)
9516 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9517 Store[ax][ay-1] = element;
9518 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9519 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9520 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9521 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9526 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9527 Store[ax][ay+1] = element;
9528 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9529 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9530 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9531 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9536 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9537 element == EL_EXPANDABLE_STEELWALL_ANY)
9541 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9542 Store[ax-1][ay] = element;
9543 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9544 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9545 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9546 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9552 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9553 Store[ax+1][ay] = element;
9554 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9555 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9556 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9557 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9562 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9564 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9565 unten_massiv = TRUE;
9566 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9567 links_massiv = TRUE;
9568 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9569 rechts_massiv = TRUE;
9571 if (((oben_massiv && unten_massiv) ||
9572 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9573 ((links_massiv && rechts_massiv) ||
9574 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9575 Feld[ax][ay] = EL_STEELWALL;
9578 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9581 static void CheckForDragon(int x, int y)
9584 boolean dragon_found = FALSE;
9585 static int xy[4][2] =
9593 for (i = 0; i < NUM_DIRECTIONS; i++)
9595 for (j = 0; j < 4; j++)
9597 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9599 if (IN_LEV_FIELD(xx, yy) &&
9600 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9602 if (Feld[xx][yy] == EL_DRAGON)
9603 dragon_found = TRUE;
9612 for (i = 0; i < NUM_DIRECTIONS; i++)
9614 for (j = 0; j < 3; j++)
9616 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9618 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9620 Feld[xx][yy] = EL_EMPTY;
9621 TEST_DrawLevelField(xx, yy);
9630 static void InitBuggyBase(int x, int y)
9632 int element = Feld[x][y];
9633 int activating_delay = FRAMES_PER_SECOND / 4;
9636 (element == EL_SP_BUGGY_BASE ?
9637 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9638 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9640 element == EL_SP_BUGGY_BASE_ACTIVE ?
9641 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9644 static void WarnBuggyBase(int x, int y)
9647 static int xy[4][2] =
9655 for (i = 0; i < NUM_DIRECTIONS; i++)
9657 int xx = x + xy[i][0];
9658 int yy = y + xy[i][1];
9660 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9662 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9669 static void InitTrap(int x, int y)
9671 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9674 static void ActivateTrap(int x, int y)
9676 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9679 static void ChangeActiveTrap(int x, int y)
9681 int graphic = IMG_TRAP_ACTIVE;
9683 // if new animation frame was drawn, correct crumbled sand border
9684 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9685 TEST_DrawLevelFieldCrumbled(x, y);
9688 static int getSpecialActionElement(int element, int number, int base_element)
9690 return (element != EL_EMPTY ? element :
9691 number != -1 ? base_element + number - 1 :
9695 static int getModifiedActionNumber(int value_old, int operator, int operand,
9696 int value_min, int value_max)
9698 int value_new = (operator == CA_MODE_SET ? operand :
9699 operator == CA_MODE_ADD ? value_old + operand :
9700 operator == CA_MODE_SUBTRACT ? value_old - operand :
9701 operator == CA_MODE_MULTIPLY ? value_old * operand :
9702 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9703 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9706 return (value_new < value_min ? value_min :
9707 value_new > value_max ? value_max :
9711 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9713 struct ElementInfo *ei = &element_info[element];
9714 struct ElementChangeInfo *change = &ei->change_page[page];
9715 int target_element = change->target_element;
9716 int action_type = change->action_type;
9717 int action_mode = change->action_mode;
9718 int action_arg = change->action_arg;
9719 int action_element = change->action_element;
9722 if (!change->has_action)
9725 // ---------- determine action paramater values -----------------------------
9727 int level_time_value =
9728 (level.time > 0 ? TimeLeft :
9731 int action_arg_element_raw =
9732 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9733 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9734 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9735 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9736 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9737 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9738 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9740 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9742 int action_arg_direction =
9743 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9744 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9745 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9746 change->actual_trigger_side :
9747 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9748 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9751 int action_arg_number_min =
9752 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9755 int action_arg_number_max =
9756 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9757 action_type == CA_SET_LEVEL_GEMS ? 999 :
9758 action_type == CA_SET_LEVEL_TIME ? 9999 :
9759 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9760 action_type == CA_SET_CE_VALUE ? 9999 :
9761 action_type == CA_SET_CE_SCORE ? 9999 :
9764 int action_arg_number_reset =
9765 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9766 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9767 action_type == CA_SET_LEVEL_TIME ? level.time :
9768 action_type == CA_SET_LEVEL_SCORE ? 0 :
9769 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9770 action_type == CA_SET_CE_SCORE ? 0 :
9773 int action_arg_number =
9774 (action_arg <= CA_ARG_MAX ? action_arg :
9775 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9776 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9777 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9778 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9779 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9780 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9781 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9782 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9783 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9784 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9785 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9786 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9787 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9788 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9789 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9790 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9791 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9792 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9793 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9794 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9795 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9798 int action_arg_number_old =
9799 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9800 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9801 action_type == CA_SET_LEVEL_SCORE ? game.score :
9802 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9803 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9806 int action_arg_number_new =
9807 getModifiedActionNumber(action_arg_number_old,
9808 action_mode, action_arg_number,
9809 action_arg_number_min, action_arg_number_max);
9811 int trigger_player_bits =
9812 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9813 change->actual_trigger_player_bits : change->trigger_player);
9815 int action_arg_player_bits =
9816 (action_arg >= CA_ARG_PLAYER_1 &&
9817 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9818 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9819 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9822 // ---------- execute action -----------------------------------------------
9824 switch (action_type)
9831 // ---------- level actions ----------------------------------------------
9833 case CA_RESTART_LEVEL:
9835 game.restart_level = TRUE;
9840 case CA_SHOW_ENVELOPE:
9842 int element = getSpecialActionElement(action_arg_element,
9843 action_arg_number, EL_ENVELOPE_1);
9845 if (IS_ENVELOPE(element))
9846 local_player->show_envelope = element;
9851 case CA_SET_LEVEL_TIME:
9853 if (level.time > 0) // only modify limited time value
9855 TimeLeft = action_arg_number_new;
9857 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9859 DisplayGameControlValues();
9861 if (!TimeLeft && setup.time_limit)
9862 for (i = 0; i < MAX_PLAYERS; i++)
9863 KillPlayer(&stored_player[i]);
9869 case CA_SET_LEVEL_SCORE:
9871 game.score = action_arg_number_new;
9873 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9875 DisplayGameControlValues();
9880 case CA_SET_LEVEL_GEMS:
9882 game.gems_still_needed = action_arg_number_new;
9884 game.snapshot.collected_item = TRUE;
9886 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9888 DisplayGameControlValues();
9893 case CA_SET_LEVEL_WIND:
9895 game.wind_direction = action_arg_direction;
9900 case CA_SET_LEVEL_RANDOM_SEED:
9902 // ensure that setting a new random seed while playing is predictable
9903 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9908 // ---------- player actions ---------------------------------------------
9910 case CA_MOVE_PLAYER:
9911 case CA_MOVE_PLAYER_NEW:
9913 // automatically move to the next field in specified direction
9914 for (i = 0; i < MAX_PLAYERS; i++)
9915 if (trigger_player_bits & (1 << i))
9916 if (action_type == CA_MOVE_PLAYER ||
9917 stored_player[i].MovPos == 0)
9918 stored_player[i].programmed_action = action_arg_direction;
9923 case CA_EXIT_PLAYER:
9925 for (i = 0; i < MAX_PLAYERS; i++)
9926 if (action_arg_player_bits & (1 << i))
9927 ExitPlayer(&stored_player[i]);
9929 if (game.players_still_needed == 0)
9935 case CA_KILL_PLAYER:
9937 for (i = 0; i < MAX_PLAYERS; i++)
9938 if (action_arg_player_bits & (1 << i))
9939 KillPlayer(&stored_player[i]);
9944 case CA_SET_PLAYER_KEYS:
9946 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
9947 int element = getSpecialActionElement(action_arg_element,
9948 action_arg_number, EL_KEY_1);
9950 if (IS_KEY(element))
9952 for (i = 0; i < MAX_PLAYERS; i++)
9954 if (trigger_player_bits & (1 << i))
9956 stored_player[i].key[KEY_NR(element)] = key_state;
9958 DrawGameDoorValues();
9966 case CA_SET_PLAYER_SPEED:
9968 for (i = 0; i < MAX_PLAYERS; i++)
9970 if (trigger_player_bits & (1 << i))
9972 int move_stepsize = TILEX / stored_player[i].move_delay_value;
9974 if (action_arg == CA_ARG_SPEED_FASTER &&
9975 stored_player[i].cannot_move)
9977 action_arg_number = STEPSIZE_VERY_SLOW;
9979 else if (action_arg == CA_ARG_SPEED_SLOWER ||
9980 action_arg == CA_ARG_SPEED_FASTER)
9982 action_arg_number = 2;
9983 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
9986 else if (action_arg == CA_ARG_NUMBER_RESET)
9988 action_arg_number = level.initial_player_stepsize[i];
9992 getModifiedActionNumber(move_stepsize,
9995 action_arg_number_min,
9996 action_arg_number_max);
9998 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10005 case CA_SET_PLAYER_SHIELD:
10007 for (i = 0; i < MAX_PLAYERS; i++)
10009 if (trigger_player_bits & (1 << i))
10011 if (action_arg == CA_ARG_SHIELD_OFF)
10013 stored_player[i].shield_normal_time_left = 0;
10014 stored_player[i].shield_deadly_time_left = 0;
10016 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10018 stored_player[i].shield_normal_time_left = 999999;
10020 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10022 stored_player[i].shield_normal_time_left = 999999;
10023 stored_player[i].shield_deadly_time_left = 999999;
10031 case CA_SET_PLAYER_GRAVITY:
10033 for (i = 0; i < MAX_PLAYERS; i++)
10035 if (trigger_player_bits & (1 << i))
10037 stored_player[i].gravity =
10038 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10039 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10040 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10041 stored_player[i].gravity);
10048 case CA_SET_PLAYER_ARTWORK:
10050 for (i = 0; i < MAX_PLAYERS; i++)
10052 if (trigger_player_bits & (1 << i))
10054 int artwork_element = action_arg_element;
10056 if (action_arg == CA_ARG_ELEMENT_RESET)
10058 (level.use_artwork_element[i] ? level.artwork_element[i] :
10059 stored_player[i].element_nr);
10061 if (stored_player[i].artwork_element != artwork_element)
10062 stored_player[i].Frame = 0;
10064 stored_player[i].artwork_element = artwork_element;
10066 SetPlayerWaiting(&stored_player[i], FALSE);
10068 // set number of special actions for bored and sleeping animation
10069 stored_player[i].num_special_action_bored =
10070 get_num_special_action(artwork_element,
10071 ACTION_BORING_1, ACTION_BORING_LAST);
10072 stored_player[i].num_special_action_sleeping =
10073 get_num_special_action(artwork_element,
10074 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10081 case CA_SET_PLAYER_INVENTORY:
10083 for (i = 0; i < MAX_PLAYERS; i++)
10085 struct PlayerInfo *player = &stored_player[i];
10088 if (trigger_player_bits & (1 << i))
10090 int inventory_element = action_arg_element;
10092 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10093 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10094 action_arg == CA_ARG_ELEMENT_ACTION)
10096 int element = inventory_element;
10097 int collect_count = element_info[element].collect_count_initial;
10099 if (!IS_CUSTOM_ELEMENT(element))
10102 if (collect_count == 0)
10103 player->inventory_infinite_element = element;
10105 for (k = 0; k < collect_count; k++)
10106 if (player->inventory_size < MAX_INVENTORY_SIZE)
10107 player->inventory_element[player->inventory_size++] =
10110 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10111 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10112 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10114 if (player->inventory_infinite_element != EL_UNDEFINED &&
10115 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10116 action_arg_element_raw))
10117 player->inventory_infinite_element = EL_UNDEFINED;
10119 for (k = 0, j = 0; j < player->inventory_size; j++)
10121 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10122 action_arg_element_raw))
10123 player->inventory_element[k++] = player->inventory_element[j];
10126 player->inventory_size = k;
10128 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10130 if (player->inventory_size > 0)
10132 for (j = 0; j < player->inventory_size - 1; j++)
10133 player->inventory_element[j] = player->inventory_element[j + 1];
10135 player->inventory_size--;
10138 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10140 if (player->inventory_size > 0)
10141 player->inventory_size--;
10143 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10145 player->inventory_infinite_element = EL_UNDEFINED;
10146 player->inventory_size = 0;
10148 else if (action_arg == CA_ARG_INVENTORY_RESET)
10150 player->inventory_infinite_element = EL_UNDEFINED;
10151 player->inventory_size = 0;
10153 if (level.use_initial_inventory[i])
10155 for (j = 0; j < level.initial_inventory_size[i]; j++)
10157 int element = level.initial_inventory_content[i][j];
10158 int collect_count = element_info[element].collect_count_initial;
10160 if (!IS_CUSTOM_ELEMENT(element))
10163 if (collect_count == 0)
10164 player->inventory_infinite_element = element;
10166 for (k = 0; k < collect_count; k++)
10167 if (player->inventory_size < MAX_INVENTORY_SIZE)
10168 player->inventory_element[player->inventory_size++] =
10179 // ---------- CE actions -------------------------------------------------
10181 case CA_SET_CE_VALUE:
10183 int last_ce_value = CustomValue[x][y];
10185 CustomValue[x][y] = action_arg_number_new;
10187 if (CustomValue[x][y] != last_ce_value)
10189 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10190 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10192 if (CustomValue[x][y] == 0)
10194 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10195 ChangeCount[x][y] = 0; // allow at least one more change
10197 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10198 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10205 case CA_SET_CE_SCORE:
10207 int last_ce_score = ei->collect_score;
10209 ei->collect_score = action_arg_number_new;
10211 if (ei->collect_score != last_ce_score)
10213 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10214 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10216 if (ei->collect_score == 0)
10220 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10221 ChangeCount[x][y] = 0; // allow at least one more change
10223 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10224 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10227 This is a very special case that seems to be a mixture between
10228 CheckElementChange() and CheckTriggeredElementChange(): while
10229 the first one only affects single elements that are triggered
10230 directly, the second one affects multiple elements in the playfield
10231 that are triggered indirectly by another element. This is a third
10232 case: Changing the CE score always affects multiple identical CEs,
10233 so every affected CE must be checked, not only the single CE for
10234 which the CE score was changed in the first place (as every instance
10235 of that CE shares the same CE score, and therefore also can change)!
10237 SCAN_PLAYFIELD(xx, yy)
10239 if (Feld[xx][yy] == element)
10240 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10241 CE_SCORE_GETS_ZERO);
10249 case CA_SET_CE_ARTWORK:
10251 int artwork_element = action_arg_element;
10252 boolean reset_frame = FALSE;
10255 if (action_arg == CA_ARG_ELEMENT_RESET)
10256 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10259 if (ei->gfx_element != artwork_element)
10260 reset_frame = TRUE;
10262 ei->gfx_element = artwork_element;
10264 SCAN_PLAYFIELD(xx, yy)
10266 if (Feld[xx][yy] == element)
10270 ResetGfxAnimation(xx, yy);
10271 ResetRandomAnimationValue(xx, yy);
10274 TEST_DrawLevelField(xx, yy);
10281 // ---------- engine actions ---------------------------------------------
10283 case CA_SET_ENGINE_SCAN_MODE:
10285 InitPlayfieldScanMode(action_arg);
10295 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10297 int old_element = Feld[x][y];
10298 int new_element = GetElementFromGroupElement(element);
10299 int previous_move_direction = MovDir[x][y];
10300 int last_ce_value = CustomValue[x][y];
10301 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10302 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10303 boolean add_player_onto_element = (new_element_is_player &&
10304 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10305 IS_WALKABLE(old_element));
10307 if (!add_player_onto_element)
10309 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10310 RemoveMovingField(x, y);
10314 Feld[x][y] = new_element;
10316 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10317 MovDir[x][y] = previous_move_direction;
10319 if (element_info[new_element].use_last_ce_value)
10320 CustomValue[x][y] = last_ce_value;
10322 InitField_WithBug1(x, y, FALSE);
10324 new_element = Feld[x][y]; // element may have changed
10326 ResetGfxAnimation(x, y);
10327 ResetRandomAnimationValue(x, y);
10329 TEST_DrawLevelField(x, y);
10331 if (GFX_CRUMBLED(new_element))
10332 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10335 // check if element under the player changes from accessible to unaccessible
10336 // (needed for special case of dropping element which then changes)
10337 // (must be checked after creating new element for walkable group elements)
10338 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10339 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10346 // "ChangeCount" not set yet to allow "entered by player" change one time
10347 if (new_element_is_player)
10348 RelocatePlayer(x, y, new_element);
10351 ChangeCount[x][y]++; // count number of changes in the same frame
10353 TestIfBadThingTouchesPlayer(x, y);
10354 TestIfPlayerTouchesCustomElement(x, y);
10355 TestIfElementTouchesCustomElement(x, y);
10358 static void CreateField(int x, int y, int element)
10360 CreateFieldExt(x, y, element, FALSE);
10363 static void CreateElementFromChange(int x, int y, int element)
10365 element = GET_VALID_RUNTIME_ELEMENT(element);
10367 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10369 int old_element = Feld[x][y];
10371 // prevent changed element from moving in same engine frame
10372 // unless both old and new element can either fall or move
10373 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10374 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10378 CreateFieldExt(x, y, element, TRUE);
10381 static boolean ChangeElement(int x, int y, int element, int page)
10383 struct ElementInfo *ei = &element_info[element];
10384 struct ElementChangeInfo *change = &ei->change_page[page];
10385 int ce_value = CustomValue[x][y];
10386 int ce_score = ei->collect_score;
10387 int target_element;
10388 int old_element = Feld[x][y];
10390 // always use default change event to prevent running into a loop
10391 if (ChangeEvent[x][y] == -1)
10392 ChangeEvent[x][y] = CE_DELAY;
10394 if (ChangeEvent[x][y] == CE_DELAY)
10396 // reset actual trigger element, trigger player and action element
10397 change->actual_trigger_element = EL_EMPTY;
10398 change->actual_trigger_player = EL_EMPTY;
10399 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10400 change->actual_trigger_side = CH_SIDE_NONE;
10401 change->actual_trigger_ce_value = 0;
10402 change->actual_trigger_ce_score = 0;
10405 // do not change elements more than a specified maximum number of changes
10406 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10409 ChangeCount[x][y]++; // count number of changes in the same frame
10411 if (change->explode)
10418 if (change->use_target_content)
10420 boolean complete_replace = TRUE;
10421 boolean can_replace[3][3];
10424 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10427 boolean is_walkable;
10428 boolean is_diggable;
10429 boolean is_collectible;
10430 boolean is_removable;
10431 boolean is_destructible;
10432 int ex = x + xx - 1;
10433 int ey = y + yy - 1;
10434 int content_element = change->target_content.e[xx][yy];
10437 can_replace[xx][yy] = TRUE;
10439 if (ex == x && ey == y) // do not check changing element itself
10442 if (content_element == EL_EMPTY_SPACE)
10444 can_replace[xx][yy] = FALSE; // do not replace border with space
10449 if (!IN_LEV_FIELD(ex, ey))
10451 can_replace[xx][yy] = FALSE;
10452 complete_replace = FALSE;
10459 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10460 e = MovingOrBlocked2Element(ex, ey);
10462 is_empty = (IS_FREE(ex, ey) ||
10463 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10465 is_walkable = (is_empty || IS_WALKABLE(e));
10466 is_diggable = (is_empty || IS_DIGGABLE(e));
10467 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10468 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10469 is_removable = (is_diggable || is_collectible);
10471 can_replace[xx][yy] =
10472 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10473 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10474 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10475 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10476 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10477 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10478 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10480 if (!can_replace[xx][yy])
10481 complete_replace = FALSE;
10484 if (!change->only_if_complete || complete_replace)
10486 boolean something_has_changed = FALSE;
10488 if (change->only_if_complete && change->use_random_replace &&
10489 RND(100) < change->random_percentage)
10492 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10494 int ex = x + xx - 1;
10495 int ey = y + yy - 1;
10496 int content_element;
10498 if (can_replace[xx][yy] && (!change->use_random_replace ||
10499 RND(100) < change->random_percentage))
10501 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10502 RemoveMovingField(ex, ey);
10504 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10506 content_element = change->target_content.e[xx][yy];
10507 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10508 ce_value, ce_score);
10510 CreateElementFromChange(ex, ey, target_element);
10512 something_has_changed = TRUE;
10514 // for symmetry reasons, freeze newly created border elements
10515 if (ex != x || ey != y)
10516 Stop[ex][ey] = TRUE; // no more moving in this frame
10520 if (something_has_changed)
10522 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10523 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10529 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10530 ce_value, ce_score);
10532 if (element == EL_DIAGONAL_GROWING ||
10533 element == EL_DIAGONAL_SHRINKING)
10535 target_element = Store[x][y];
10537 Store[x][y] = EL_EMPTY;
10540 CreateElementFromChange(x, y, target_element);
10542 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10543 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10546 // this uses direct change before indirect change
10547 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10552 static void HandleElementChange(int x, int y, int page)
10554 int element = MovingOrBlocked2Element(x, y);
10555 struct ElementInfo *ei = &element_info[element];
10556 struct ElementChangeInfo *change = &ei->change_page[page];
10557 boolean handle_action_before_change = FALSE;
10560 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10561 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10564 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10565 x, y, element, element_info[element].token_name);
10566 printf("HandleElementChange(): This should never happen!\n");
10571 // this can happen with classic bombs on walkable, changing elements
10572 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10577 if (ChangeDelay[x][y] == 0) // initialize element change
10579 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10581 if (change->can_change)
10583 // !!! not clear why graphic animation should be reset at all here !!!
10584 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10585 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10588 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10590 When using an animation frame delay of 1 (this only happens with
10591 "sp_zonk.moving.left/right" in the classic graphics), the default
10592 (non-moving) animation shows wrong animation frames (while the
10593 moving animation, like "sp_zonk.moving.left/right", is correct,
10594 so this graphical bug never shows up with the classic graphics).
10595 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10596 be drawn instead of the correct frames 0,1,2,3. This is caused by
10597 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10598 an element change: First when the change delay ("ChangeDelay[][]")
10599 counter has reached zero after decrementing, then a second time in
10600 the next frame (after "GfxFrame[][]" was already incremented) when
10601 "ChangeDelay[][]" is reset to the initial delay value again.
10603 This causes frame 0 to be drawn twice, while the last frame won't
10604 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10606 As some animations may already be cleverly designed around this bug
10607 (at least the "Snake Bite" snake tail animation does this), it cannot
10608 simply be fixed here without breaking such existing animations.
10609 Unfortunately, it cannot easily be detected if a graphics set was
10610 designed "before" or "after" the bug was fixed. As a workaround,
10611 a new graphics set option "game.graphics_engine_version" was added
10612 to be able to specify the game's major release version for which the
10613 graphics set was designed, which can then be used to decide if the
10614 bugfix should be used (version 4 and above) or not (version 3 or
10615 below, or if no version was specified at all, as with old sets).
10617 (The wrong/fixed animation frames can be tested with the test level set
10618 "test_gfxframe" and level "000", which contains a specially prepared
10619 custom element at level position (x/y) == (11/9) which uses the zonk
10620 animation mentioned above. Using "game.graphics_engine_version: 4"
10621 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10622 This can also be seen from the debug output for this test element.)
10625 // when a custom element is about to change (for example by change delay),
10626 // do not reset graphic animation when the custom element is moving
10627 if (game.graphics_engine_version < 4 &&
10630 ResetGfxAnimation(x, y);
10631 ResetRandomAnimationValue(x, y);
10634 if (change->pre_change_function)
10635 change->pre_change_function(x, y);
10639 ChangeDelay[x][y]--;
10641 if (ChangeDelay[x][y] != 0) // continue element change
10643 if (change->can_change)
10645 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10647 if (IS_ANIMATED(graphic))
10648 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10650 if (change->change_function)
10651 change->change_function(x, y);
10654 else // finish element change
10656 if (ChangePage[x][y] != -1) // remember page from delayed change
10658 page = ChangePage[x][y];
10659 ChangePage[x][y] = -1;
10661 change = &ei->change_page[page];
10664 if (IS_MOVING(x, y)) // never change a running system ;-)
10666 ChangeDelay[x][y] = 1; // try change after next move step
10667 ChangePage[x][y] = page; // remember page to use for change
10672 // special case: set new level random seed before changing element
10673 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10674 handle_action_before_change = TRUE;
10676 if (change->has_action && handle_action_before_change)
10677 ExecuteCustomElementAction(x, y, element, page);
10679 if (change->can_change)
10681 if (ChangeElement(x, y, element, page))
10683 if (change->post_change_function)
10684 change->post_change_function(x, y);
10688 if (change->has_action && !handle_action_before_change)
10689 ExecuteCustomElementAction(x, y, element, page);
10693 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10694 int trigger_element,
10696 int trigger_player,
10700 boolean change_done_any = FALSE;
10701 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10704 if (!(trigger_events[trigger_element][trigger_event]))
10707 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10709 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10711 int element = EL_CUSTOM_START + i;
10712 boolean change_done = FALSE;
10715 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10716 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10719 for (p = 0; p < element_info[element].num_change_pages; p++)
10721 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10723 if (change->can_change_or_has_action &&
10724 change->has_event[trigger_event] &&
10725 change->trigger_side & trigger_side &&
10726 change->trigger_player & trigger_player &&
10727 change->trigger_page & trigger_page_bits &&
10728 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10730 change->actual_trigger_element = trigger_element;
10731 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10732 change->actual_trigger_player_bits = trigger_player;
10733 change->actual_trigger_side = trigger_side;
10734 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10735 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10737 if ((change->can_change && !change_done) || change->has_action)
10741 SCAN_PLAYFIELD(x, y)
10743 if (Feld[x][y] == element)
10745 if (change->can_change && !change_done)
10747 // if element already changed in this frame, not only prevent
10748 // another element change (checked in ChangeElement()), but
10749 // also prevent additional element actions for this element
10751 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10752 !level.use_action_after_change_bug)
10755 ChangeDelay[x][y] = 1;
10756 ChangeEvent[x][y] = trigger_event;
10758 HandleElementChange(x, y, p);
10760 else if (change->has_action)
10762 // if element already changed in this frame, not only prevent
10763 // another element change (checked in ChangeElement()), but
10764 // also prevent additional element actions for this element
10766 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10767 !level.use_action_after_change_bug)
10770 ExecuteCustomElementAction(x, y, element, p);
10771 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10776 if (change->can_change)
10778 change_done = TRUE;
10779 change_done_any = TRUE;
10786 RECURSION_LOOP_DETECTION_END();
10788 return change_done_any;
10791 static boolean CheckElementChangeExt(int x, int y,
10793 int trigger_element,
10795 int trigger_player,
10798 boolean change_done = FALSE;
10801 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10802 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10805 if (Feld[x][y] == EL_BLOCKED)
10807 Blocked2Moving(x, y, &x, &y);
10808 element = Feld[x][y];
10811 // check if element has already changed or is about to change after moving
10812 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10813 Feld[x][y] != element) ||
10815 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10816 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10817 ChangePage[x][y] != -1)))
10820 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10822 for (p = 0; p < element_info[element].num_change_pages; p++)
10824 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10826 /* check trigger element for all events where the element that is checked
10827 for changing interacts with a directly adjacent element -- this is
10828 different to element changes that affect other elements to change on the
10829 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10830 boolean check_trigger_element =
10831 (trigger_event == CE_TOUCHING_X ||
10832 trigger_event == CE_HITTING_X ||
10833 trigger_event == CE_HIT_BY_X ||
10834 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10836 if (change->can_change_or_has_action &&
10837 change->has_event[trigger_event] &&
10838 change->trigger_side & trigger_side &&
10839 change->trigger_player & trigger_player &&
10840 (!check_trigger_element ||
10841 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10843 change->actual_trigger_element = trigger_element;
10844 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10845 change->actual_trigger_player_bits = trigger_player;
10846 change->actual_trigger_side = trigger_side;
10847 change->actual_trigger_ce_value = CustomValue[x][y];
10848 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10850 // special case: trigger element not at (x,y) position for some events
10851 if (check_trigger_element)
10863 { 0, 0 }, { 0, 0 }, { 0, 0 },
10867 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10868 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10870 change->actual_trigger_ce_value = CustomValue[xx][yy];
10871 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10874 if (change->can_change && !change_done)
10876 ChangeDelay[x][y] = 1;
10877 ChangeEvent[x][y] = trigger_event;
10879 HandleElementChange(x, y, p);
10881 change_done = TRUE;
10883 else if (change->has_action)
10885 ExecuteCustomElementAction(x, y, element, p);
10886 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10891 RECURSION_LOOP_DETECTION_END();
10893 return change_done;
10896 static void PlayPlayerSound(struct PlayerInfo *player)
10898 int jx = player->jx, jy = player->jy;
10899 int sound_element = player->artwork_element;
10900 int last_action = player->last_action_waiting;
10901 int action = player->action_waiting;
10903 if (player->is_waiting)
10905 if (action != last_action)
10906 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10908 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10912 if (action != last_action)
10913 StopSound(element_info[sound_element].sound[last_action]);
10915 if (last_action == ACTION_SLEEPING)
10916 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
10920 static void PlayAllPlayersSound(void)
10924 for (i = 0; i < MAX_PLAYERS; i++)
10925 if (stored_player[i].active)
10926 PlayPlayerSound(&stored_player[i]);
10929 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
10931 boolean last_waiting = player->is_waiting;
10932 int move_dir = player->MovDir;
10934 player->dir_waiting = move_dir;
10935 player->last_action_waiting = player->action_waiting;
10939 if (!last_waiting) // not waiting -> waiting
10941 player->is_waiting = TRUE;
10943 player->frame_counter_bored =
10945 game.player_boring_delay_fixed +
10946 GetSimpleRandom(game.player_boring_delay_random);
10947 player->frame_counter_sleeping =
10949 game.player_sleeping_delay_fixed +
10950 GetSimpleRandom(game.player_sleeping_delay_random);
10952 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
10955 if (game.player_sleeping_delay_fixed +
10956 game.player_sleeping_delay_random > 0 &&
10957 player->anim_delay_counter == 0 &&
10958 player->post_delay_counter == 0 &&
10959 FrameCounter >= player->frame_counter_sleeping)
10960 player->is_sleeping = TRUE;
10961 else if (game.player_boring_delay_fixed +
10962 game.player_boring_delay_random > 0 &&
10963 FrameCounter >= player->frame_counter_bored)
10964 player->is_bored = TRUE;
10966 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
10967 player->is_bored ? ACTION_BORING :
10970 if (player->is_sleeping && player->use_murphy)
10972 // special case for sleeping Murphy when leaning against non-free tile
10974 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
10975 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
10976 !IS_MOVING(player->jx - 1, player->jy)))
10977 move_dir = MV_LEFT;
10978 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
10979 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
10980 !IS_MOVING(player->jx + 1, player->jy)))
10981 move_dir = MV_RIGHT;
10983 player->is_sleeping = FALSE;
10985 player->dir_waiting = move_dir;
10988 if (player->is_sleeping)
10990 if (player->num_special_action_sleeping > 0)
10992 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10994 int last_special_action = player->special_action_sleeping;
10995 int num_special_action = player->num_special_action_sleeping;
10996 int special_action =
10997 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
10998 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
10999 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11000 last_special_action + 1 : ACTION_SLEEPING);
11001 int special_graphic =
11002 el_act_dir2img(player->artwork_element, special_action, move_dir);
11004 player->anim_delay_counter =
11005 graphic_info[special_graphic].anim_delay_fixed +
11006 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11007 player->post_delay_counter =
11008 graphic_info[special_graphic].post_delay_fixed +
11009 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11011 player->special_action_sleeping = special_action;
11014 if (player->anim_delay_counter > 0)
11016 player->action_waiting = player->special_action_sleeping;
11017 player->anim_delay_counter--;
11019 else if (player->post_delay_counter > 0)
11021 player->post_delay_counter--;
11025 else if (player->is_bored)
11027 if (player->num_special_action_bored > 0)
11029 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11031 int special_action =
11032 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11033 int special_graphic =
11034 el_act_dir2img(player->artwork_element, special_action, move_dir);
11036 player->anim_delay_counter =
11037 graphic_info[special_graphic].anim_delay_fixed +
11038 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11039 player->post_delay_counter =
11040 graphic_info[special_graphic].post_delay_fixed +
11041 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11043 player->special_action_bored = special_action;
11046 if (player->anim_delay_counter > 0)
11048 player->action_waiting = player->special_action_bored;
11049 player->anim_delay_counter--;
11051 else if (player->post_delay_counter > 0)
11053 player->post_delay_counter--;
11058 else if (last_waiting) // waiting -> not waiting
11060 player->is_waiting = FALSE;
11061 player->is_bored = FALSE;
11062 player->is_sleeping = FALSE;
11064 player->frame_counter_bored = -1;
11065 player->frame_counter_sleeping = -1;
11067 player->anim_delay_counter = 0;
11068 player->post_delay_counter = 0;
11070 player->dir_waiting = player->MovDir;
11071 player->action_waiting = ACTION_DEFAULT;
11073 player->special_action_bored = ACTION_DEFAULT;
11074 player->special_action_sleeping = ACTION_DEFAULT;
11078 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11080 if ((!player->is_moving && player->was_moving) ||
11081 (player->MovPos == 0 && player->was_moving) ||
11082 (player->is_snapping && !player->was_snapping) ||
11083 (player->is_dropping && !player->was_dropping))
11085 if (!CheckSaveEngineSnapshotToList())
11088 player->was_moving = FALSE;
11089 player->was_snapping = TRUE;
11090 player->was_dropping = TRUE;
11094 if (player->is_moving)
11095 player->was_moving = TRUE;
11097 if (!player->is_snapping)
11098 player->was_snapping = FALSE;
11100 if (!player->is_dropping)
11101 player->was_dropping = FALSE;
11105 static void CheckSingleStepMode(struct PlayerInfo *player)
11107 if (tape.single_step && tape.recording && !tape.pausing)
11109 /* as it is called "single step mode", just return to pause mode when the
11110 player stopped moving after one tile (or never starts moving at all) */
11111 if (!player->is_moving &&
11112 !player->is_pushing &&
11113 !player->is_dropping_pressed)
11114 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11117 CheckSaveEngineSnapshot(player);
11120 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11122 int left = player_action & JOY_LEFT;
11123 int right = player_action & JOY_RIGHT;
11124 int up = player_action & JOY_UP;
11125 int down = player_action & JOY_DOWN;
11126 int button1 = player_action & JOY_BUTTON_1;
11127 int button2 = player_action & JOY_BUTTON_2;
11128 int dx = (left ? -1 : right ? 1 : 0);
11129 int dy = (up ? -1 : down ? 1 : 0);
11131 if (!player->active || tape.pausing)
11137 SnapField(player, dx, dy);
11141 DropElement(player);
11143 MovePlayer(player, dx, dy);
11146 CheckSingleStepMode(player);
11148 SetPlayerWaiting(player, FALSE);
11150 return player_action;
11154 // no actions for this player (no input at player's configured device)
11156 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11157 SnapField(player, 0, 0);
11158 CheckGravityMovementWhenNotMoving(player);
11160 if (player->MovPos == 0)
11161 SetPlayerWaiting(player, TRUE);
11163 if (player->MovPos == 0) // needed for tape.playing
11164 player->is_moving = FALSE;
11166 player->is_dropping = FALSE;
11167 player->is_dropping_pressed = FALSE;
11168 player->drop_pressed_delay = 0;
11170 CheckSingleStepMode(player);
11176 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11179 if (tape.event_mask != GAME_EVENTS_MOUSE)
11182 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11183 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11184 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11187 static void SetTapeActionFromMouseAction(byte *tape_action,
11188 struct MouseActionInfo *mouse_action)
11190 if (tape.event_mask != GAME_EVENTS_MOUSE)
11193 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11194 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11195 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11198 static void CheckLevelSolved(void)
11200 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11202 if (game_em.level_solved &&
11203 !game_em.game_over) // game won
11207 game_em.game_over = TRUE;
11209 game.all_players_gone = TRUE;
11212 if (game_em.game_over) // game lost
11213 game.all_players_gone = TRUE;
11215 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11217 if (game_sp.level_solved &&
11218 !game_sp.game_over) // game won
11222 game_sp.game_over = TRUE;
11224 game.all_players_gone = TRUE;
11227 if (game_sp.game_over) // game lost
11228 game.all_players_gone = TRUE;
11230 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11232 if (game_mm.level_solved &&
11233 !game_mm.game_over) // game won
11237 game_mm.game_over = TRUE;
11239 game.all_players_gone = TRUE;
11242 if (game_mm.game_over) // game lost
11243 game.all_players_gone = TRUE;
11247 static void CheckLevelTime(void)
11251 if (TimeFrames >= FRAMES_PER_SECOND)
11256 for (i = 0; i < MAX_PLAYERS; i++)
11258 struct PlayerInfo *player = &stored_player[i];
11260 if (SHIELD_ON(player))
11262 player->shield_normal_time_left--;
11264 if (player->shield_deadly_time_left > 0)
11265 player->shield_deadly_time_left--;
11269 if (!game.LevelSolved && !level.use_step_counter)
11277 if (TimeLeft <= 10 && setup.time_limit)
11278 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11280 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11281 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11283 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11285 if (!TimeLeft && setup.time_limit)
11287 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11288 game_em.lev->killed_out_of_time = TRUE;
11290 for (i = 0; i < MAX_PLAYERS; i++)
11291 KillPlayer(&stored_player[i]);
11294 else if (game.no_time_limit && !game.all_players_gone)
11296 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11299 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11302 if (tape.recording || tape.playing)
11303 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11306 if (tape.recording || tape.playing)
11307 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11309 UpdateAndDisplayGameControlValues();
11312 void AdvanceFrameAndPlayerCounters(int player_nr)
11316 // advance frame counters (global frame counter and time frame counter)
11320 // advance player counters (counters for move delay, move animation etc.)
11321 for (i = 0; i < MAX_PLAYERS; i++)
11323 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11324 int move_delay_value = stored_player[i].move_delay_value;
11325 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11327 if (!advance_player_counters) // not all players may be affected
11330 if (move_frames == 0) // less than one move per game frame
11332 int stepsize = TILEX / move_delay_value;
11333 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11334 int count = (stored_player[i].is_moving ?
11335 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11337 if (count % delay == 0)
11341 stored_player[i].Frame += move_frames;
11343 if (stored_player[i].MovPos != 0)
11344 stored_player[i].StepFrame += move_frames;
11346 if (stored_player[i].move_delay > 0)
11347 stored_player[i].move_delay--;
11349 // due to bugs in previous versions, counter must count up, not down
11350 if (stored_player[i].push_delay != -1)
11351 stored_player[i].push_delay++;
11353 if (stored_player[i].drop_delay > 0)
11354 stored_player[i].drop_delay--;
11356 if (stored_player[i].is_dropping_pressed)
11357 stored_player[i].drop_pressed_delay++;
11361 void StartGameActions(boolean init_network_game, boolean record_tape,
11364 unsigned int new_random_seed = InitRND(random_seed);
11367 TapeStartRecording(new_random_seed);
11369 if (init_network_game)
11371 SendToServer_LevelFile();
11372 SendToServer_StartPlaying();
11380 static void GameActionsExt(void)
11383 static unsigned int game_frame_delay = 0;
11385 unsigned int game_frame_delay_value;
11386 byte *recorded_player_action;
11387 byte summarized_player_action = 0;
11388 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11391 // detect endless loops, caused by custom element programming
11392 if (recursion_loop_detected && recursion_loop_depth == 0)
11394 char *message = getStringCat3("Internal Error! Element ",
11395 EL_NAME(recursion_loop_element),
11396 " caused endless loop! Quit the game?");
11398 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11399 EL_NAME(recursion_loop_element));
11401 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11403 recursion_loop_detected = FALSE; // if game should be continued
11410 if (game.restart_level)
11411 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11413 CheckLevelSolved();
11415 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11418 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11421 if (game_status != GAME_MODE_PLAYING) // status might have changed
11424 game_frame_delay_value =
11425 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11427 if (tape.playing && tape.warp_forward && !tape.pausing)
11428 game_frame_delay_value = 0;
11430 SetVideoFrameDelay(game_frame_delay_value);
11432 // (de)activate virtual buttons depending on current game status
11433 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11435 if (game.all_players_gone) // if no players there to be controlled anymore
11436 SetOverlayActive(FALSE);
11437 else if (!tape.playing) // if game continues after tape stopped playing
11438 SetOverlayActive(TRUE);
11443 // ---------- main game synchronization point ----------
11445 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11447 printf("::: skip == %d\n", skip);
11450 // ---------- main game synchronization point ----------
11452 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11456 if (network_playing && !network_player_action_received)
11458 // try to get network player actions in time
11460 // last chance to get network player actions without main loop delay
11461 HandleNetworking();
11463 // game was quit by network peer
11464 if (game_status != GAME_MODE_PLAYING)
11467 // check if network player actions still missing and game still running
11468 if (!network_player_action_received && !checkGameEnded())
11469 return; // failed to get network player actions in time
11471 // do not yet reset "network_player_action_received" (for tape.pausing)
11477 // at this point we know that we really continue executing the game
11479 network_player_action_received = FALSE;
11481 // when playing tape, read previously recorded player input from tape data
11482 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11484 local_player->effective_mouse_action = local_player->mouse_action;
11486 if (recorded_player_action != NULL)
11487 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11488 recorded_player_action);
11490 // TapePlayAction() may return NULL when toggling to "pause before death"
11494 if (tape.set_centered_player)
11496 game.centered_player_nr_next = tape.centered_player_nr_next;
11497 game.set_centered_player = TRUE;
11500 for (i = 0; i < MAX_PLAYERS; i++)
11502 summarized_player_action |= stored_player[i].action;
11504 if (!network_playing && (game.team_mode || tape.playing))
11505 stored_player[i].effective_action = stored_player[i].action;
11508 if (network_playing && !checkGameEnded())
11509 SendToServer_MovePlayer(summarized_player_action);
11511 // summarize all actions at local players mapped input device position
11512 // (this allows using different input devices in single player mode)
11513 if (!network.enabled && !game.team_mode)
11514 stored_player[map_player_action[local_player->index_nr]].effective_action =
11515 summarized_player_action;
11517 // summarize all actions at centered player in local team mode
11518 if (tape.recording &&
11519 setup.team_mode && !network.enabled &&
11520 setup.input_on_focus &&
11521 game.centered_player_nr != -1)
11523 for (i = 0; i < MAX_PLAYERS; i++)
11524 stored_player[map_player_action[i]].effective_action =
11525 (i == game.centered_player_nr ? summarized_player_action : 0);
11528 if (recorded_player_action != NULL)
11529 for (i = 0; i < MAX_PLAYERS; i++)
11530 stored_player[i].effective_action = recorded_player_action[i];
11532 for (i = 0; i < MAX_PLAYERS; i++)
11534 tape_action[i] = stored_player[i].effective_action;
11536 /* (this may happen in the RND game engine if a player was not present on
11537 the playfield on level start, but appeared later from a custom element */
11538 if (setup.team_mode &&
11541 !tape.player_participates[i])
11542 tape.player_participates[i] = TRUE;
11545 SetTapeActionFromMouseAction(tape_action,
11546 &local_player->effective_mouse_action);
11548 // only record actions from input devices, but not programmed actions
11549 if (tape.recording)
11550 TapeRecordAction(tape_action);
11552 // remember if game was played (especially after tape stopped playing)
11553 if (!tape.playing && summarized_player_action)
11554 game.GamePlayed = TRUE;
11556 #if USE_NEW_PLAYER_ASSIGNMENTS
11557 // !!! also map player actions in single player mode !!!
11558 // if (game.team_mode)
11561 byte mapped_action[MAX_PLAYERS];
11563 #if DEBUG_PLAYER_ACTIONS
11565 for (i = 0; i < MAX_PLAYERS; i++)
11566 printf(" %d, ", stored_player[i].effective_action);
11569 for (i = 0; i < MAX_PLAYERS; i++)
11570 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11572 for (i = 0; i < MAX_PLAYERS; i++)
11573 stored_player[i].effective_action = mapped_action[i];
11575 #if DEBUG_PLAYER_ACTIONS
11577 for (i = 0; i < MAX_PLAYERS; i++)
11578 printf(" %d, ", stored_player[i].effective_action);
11582 #if DEBUG_PLAYER_ACTIONS
11586 for (i = 0; i < MAX_PLAYERS; i++)
11587 printf(" %d, ", stored_player[i].effective_action);
11593 for (i = 0; i < MAX_PLAYERS; i++)
11595 // allow engine snapshot in case of changed movement attempt
11596 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11597 (stored_player[i].effective_action & KEY_MOTION))
11598 game.snapshot.changed_action = TRUE;
11600 // allow engine snapshot in case of snapping/dropping attempt
11601 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11602 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11603 game.snapshot.changed_action = TRUE;
11605 game.snapshot.last_action[i] = stored_player[i].effective_action;
11608 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11610 GameActions_EM_Main();
11612 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11614 GameActions_SP_Main();
11616 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11618 GameActions_MM_Main();
11622 GameActions_RND_Main();
11625 BlitScreenToBitmap(backbuffer);
11627 CheckLevelSolved();
11630 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11632 if (global.show_frames_per_second)
11634 static unsigned int fps_counter = 0;
11635 static int fps_frames = 0;
11636 unsigned int fps_delay_ms = Counter() - fps_counter;
11640 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11642 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11645 fps_counter = Counter();
11647 // always draw FPS to screen after FPS value was updated
11648 redraw_mask |= REDRAW_FPS;
11651 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11652 if (GetDrawDeactivationMask() == REDRAW_NONE)
11653 redraw_mask |= REDRAW_FPS;
11657 static void GameActions_CheckSaveEngineSnapshot(void)
11659 if (!game.snapshot.save_snapshot)
11662 // clear flag for saving snapshot _before_ saving snapshot
11663 game.snapshot.save_snapshot = FALSE;
11665 SaveEngineSnapshotToList();
11668 void GameActions(void)
11672 GameActions_CheckSaveEngineSnapshot();
11675 void GameActions_EM_Main(void)
11677 byte effective_action[MAX_PLAYERS];
11678 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11681 for (i = 0; i < MAX_PLAYERS; i++)
11682 effective_action[i] = stored_player[i].effective_action;
11684 GameActions_EM(effective_action, warp_mode);
11687 void GameActions_SP_Main(void)
11689 byte effective_action[MAX_PLAYERS];
11690 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11693 for (i = 0; i < MAX_PLAYERS; i++)
11694 effective_action[i] = stored_player[i].effective_action;
11696 GameActions_SP(effective_action, warp_mode);
11698 for (i = 0; i < MAX_PLAYERS; i++)
11700 if (stored_player[i].force_dropping)
11701 stored_player[i].action |= KEY_BUTTON_DROP;
11703 stored_player[i].force_dropping = FALSE;
11707 void GameActions_MM_Main(void)
11709 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11711 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11714 void GameActions_RND_Main(void)
11719 void GameActions_RND(void)
11721 static struct MouseActionInfo mouse_action_last = { 0 };
11722 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11723 int magic_wall_x = 0, magic_wall_y = 0;
11724 int i, x, y, element, graphic, last_gfx_frame;
11726 InitPlayfieldScanModeVars();
11728 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11730 SCAN_PLAYFIELD(x, y)
11732 ChangeCount[x][y] = 0;
11733 ChangeEvent[x][y] = -1;
11737 if (game.set_centered_player)
11739 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11741 // switching to "all players" only possible if all players fit to screen
11742 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11744 game.centered_player_nr_next = game.centered_player_nr;
11745 game.set_centered_player = FALSE;
11748 // do not switch focus to non-existing (or non-active) player
11749 if (game.centered_player_nr_next >= 0 &&
11750 !stored_player[game.centered_player_nr_next].active)
11752 game.centered_player_nr_next = game.centered_player_nr;
11753 game.set_centered_player = FALSE;
11757 if (game.set_centered_player &&
11758 ScreenMovPos == 0) // screen currently aligned at tile position
11762 if (game.centered_player_nr_next == -1)
11764 setScreenCenteredToAllPlayers(&sx, &sy);
11768 sx = stored_player[game.centered_player_nr_next].jx;
11769 sy = stored_player[game.centered_player_nr_next].jy;
11772 game.centered_player_nr = game.centered_player_nr_next;
11773 game.set_centered_player = FALSE;
11775 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11776 DrawGameDoorValues();
11779 for (i = 0; i < MAX_PLAYERS; i++)
11781 int actual_player_action = stored_player[i].effective_action;
11784 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11785 - rnd_equinox_tetrachloride 048
11786 - rnd_equinox_tetrachloride_ii 096
11787 - rnd_emanuel_schmieg 002
11788 - doctor_sloan_ww 001, 020
11790 if (stored_player[i].MovPos == 0)
11791 CheckGravityMovement(&stored_player[i]);
11794 // overwrite programmed action with tape action
11795 if (stored_player[i].programmed_action)
11796 actual_player_action = stored_player[i].programmed_action;
11798 PlayerActions(&stored_player[i], actual_player_action);
11800 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11803 ScrollScreen(NULL, SCROLL_GO_ON);
11805 /* for backwards compatibility, the following code emulates a fixed bug that
11806 occured when pushing elements (causing elements that just made their last
11807 pushing step to already (if possible) make their first falling step in the
11808 same game frame, which is bad); this code is also needed to use the famous
11809 "spring push bug" which is used in older levels and might be wanted to be
11810 used also in newer levels, but in this case the buggy pushing code is only
11811 affecting the "spring" element and no other elements */
11813 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11815 for (i = 0; i < MAX_PLAYERS; i++)
11817 struct PlayerInfo *player = &stored_player[i];
11818 int x = player->jx;
11819 int y = player->jy;
11821 if (player->active && player->is_pushing && player->is_moving &&
11823 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11824 Feld[x][y] == EL_SPRING))
11826 ContinueMoving(x, y);
11828 // continue moving after pushing (this is actually a bug)
11829 if (!IS_MOVING(x, y))
11830 Stop[x][y] = FALSE;
11835 SCAN_PLAYFIELD(x, y)
11837 Last[x][y] = Feld[x][y];
11839 ChangeCount[x][y] = 0;
11840 ChangeEvent[x][y] = -1;
11842 // this must be handled before main playfield loop
11843 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11846 if (MovDelay[x][y] <= 0)
11850 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11853 if (MovDelay[x][y] <= 0)
11856 TEST_DrawLevelField(x, y);
11858 TestIfElementTouchesCustomElement(x, y); // for empty space
11863 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11865 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11866 printf("GameActions(): This should never happen!\n");
11868 ChangePage[x][y] = -1;
11872 Stop[x][y] = FALSE;
11873 if (WasJustMoving[x][y] > 0)
11874 WasJustMoving[x][y]--;
11875 if (WasJustFalling[x][y] > 0)
11876 WasJustFalling[x][y]--;
11877 if (CheckCollision[x][y] > 0)
11878 CheckCollision[x][y]--;
11879 if (CheckImpact[x][y] > 0)
11880 CheckImpact[x][y]--;
11884 /* reset finished pushing action (not done in ContinueMoving() to allow
11885 continuous pushing animation for elements with zero push delay) */
11886 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11888 ResetGfxAnimation(x, y);
11889 TEST_DrawLevelField(x, y);
11893 if (IS_BLOCKED(x, y))
11897 Blocked2Moving(x, y, &oldx, &oldy);
11898 if (!IS_MOVING(oldx, oldy))
11900 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11901 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11902 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11903 printf("GameActions(): This should never happen!\n");
11909 if (mouse_action.button)
11911 int new_button = (mouse_action.button && mouse_action_last.button == 0);
11913 x = local_player->mouse_action.lx;
11914 y = local_player->mouse_action.ly;
11915 element = Feld[x][y];
11919 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
11920 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
11923 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
11924 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
11927 SCAN_PLAYFIELD(x, y)
11929 element = Feld[x][y];
11930 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11931 last_gfx_frame = GfxFrame[x][y];
11933 ResetGfxFrame(x, y);
11935 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
11936 DrawLevelGraphicAnimation(x, y, graphic);
11938 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
11939 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
11940 ResetRandomAnimationValue(x, y);
11942 SetRandomAnimationValue(x, y);
11944 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
11946 if (IS_INACTIVE(element))
11948 if (IS_ANIMATED(graphic))
11949 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11954 // this may take place after moving, so 'element' may have changed
11955 if (IS_CHANGING(x, y) &&
11956 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
11958 int page = element_info[element].event_page_nr[CE_DELAY];
11960 HandleElementChange(x, y, page);
11962 element = Feld[x][y];
11963 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11966 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
11970 element = Feld[x][y];
11971 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11973 if (IS_ANIMATED(graphic) &&
11974 !IS_MOVING(x, y) &&
11976 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11978 if (IS_GEM(element) || element == EL_SP_INFOTRON)
11979 TEST_DrawTwinkleOnField(x, y);
11981 else if (element == EL_ACID)
11984 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11986 else if ((element == EL_EXIT_OPEN ||
11987 element == EL_EM_EXIT_OPEN ||
11988 element == EL_SP_EXIT_OPEN ||
11989 element == EL_STEEL_EXIT_OPEN ||
11990 element == EL_EM_STEEL_EXIT_OPEN ||
11991 element == EL_SP_TERMINAL ||
11992 element == EL_SP_TERMINAL_ACTIVE ||
11993 element == EL_EXTRA_TIME ||
11994 element == EL_SHIELD_NORMAL ||
11995 element == EL_SHIELD_DEADLY) &&
11996 IS_ANIMATED(graphic))
11997 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11998 else if (IS_MOVING(x, y))
11999 ContinueMoving(x, y);
12000 else if (IS_ACTIVE_BOMB(element))
12001 CheckDynamite(x, y);
12002 else if (element == EL_AMOEBA_GROWING)
12003 AmoebeWaechst(x, y);
12004 else if (element == EL_AMOEBA_SHRINKING)
12005 AmoebaDisappearing(x, y);
12007 #if !USE_NEW_AMOEBA_CODE
12008 else if (IS_AMOEBALIVE(element))
12009 AmoebeAbleger(x, y);
12012 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12014 else if (element == EL_EXIT_CLOSED)
12016 else if (element == EL_EM_EXIT_CLOSED)
12018 else if (element == EL_STEEL_EXIT_CLOSED)
12019 CheckExitSteel(x, y);
12020 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12021 CheckExitSteelEM(x, y);
12022 else if (element == EL_SP_EXIT_CLOSED)
12024 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12025 element == EL_EXPANDABLE_STEELWALL_GROWING)
12026 MauerWaechst(x, y);
12027 else if (element == EL_EXPANDABLE_WALL ||
12028 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12029 element == EL_EXPANDABLE_WALL_VERTICAL ||
12030 element == EL_EXPANDABLE_WALL_ANY ||
12031 element == EL_BD_EXPANDABLE_WALL)
12032 MauerAbleger(x, y);
12033 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12034 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12035 element == EL_EXPANDABLE_STEELWALL_ANY)
12036 MauerAblegerStahl(x, y);
12037 else if (element == EL_FLAMES)
12038 CheckForDragon(x, y);
12039 else if (element == EL_EXPLOSION)
12040 ; // drawing of correct explosion animation is handled separately
12041 else if (element == EL_ELEMENT_SNAPPING ||
12042 element == EL_DIAGONAL_SHRINKING ||
12043 element == EL_DIAGONAL_GROWING)
12045 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12047 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12049 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12050 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12052 if (IS_BELT_ACTIVE(element))
12053 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12055 if (game.magic_wall_active)
12057 int jx = local_player->jx, jy = local_player->jy;
12059 // play the element sound at the position nearest to the player
12060 if ((element == EL_MAGIC_WALL_FULL ||
12061 element == EL_MAGIC_WALL_ACTIVE ||
12062 element == EL_MAGIC_WALL_EMPTYING ||
12063 element == EL_BD_MAGIC_WALL_FULL ||
12064 element == EL_BD_MAGIC_WALL_ACTIVE ||
12065 element == EL_BD_MAGIC_WALL_EMPTYING ||
12066 element == EL_DC_MAGIC_WALL_FULL ||
12067 element == EL_DC_MAGIC_WALL_ACTIVE ||
12068 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12069 ABS(x - jx) + ABS(y - jy) <
12070 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12078 #if USE_NEW_AMOEBA_CODE
12079 // new experimental amoeba growth stuff
12080 if (!(FrameCounter % 8))
12082 static unsigned int random = 1684108901;
12084 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12086 x = RND(lev_fieldx);
12087 y = RND(lev_fieldy);
12088 element = Feld[x][y];
12090 if (!IS_PLAYER(x,y) &&
12091 (element == EL_EMPTY ||
12092 CAN_GROW_INTO(element) ||
12093 element == EL_QUICKSAND_EMPTY ||
12094 element == EL_QUICKSAND_FAST_EMPTY ||
12095 element == EL_ACID_SPLASH_LEFT ||
12096 element == EL_ACID_SPLASH_RIGHT))
12098 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12099 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12100 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12101 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12102 Feld[x][y] = EL_AMOEBA_DROP;
12105 random = random * 129 + 1;
12110 game.explosions_delayed = FALSE;
12112 SCAN_PLAYFIELD(x, y)
12114 element = Feld[x][y];
12116 if (ExplodeField[x][y])
12117 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12118 else if (element == EL_EXPLOSION)
12119 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12121 ExplodeField[x][y] = EX_TYPE_NONE;
12124 game.explosions_delayed = TRUE;
12126 if (game.magic_wall_active)
12128 if (!(game.magic_wall_time_left % 4))
12130 int element = Feld[magic_wall_x][magic_wall_y];
12132 if (element == EL_BD_MAGIC_WALL_FULL ||
12133 element == EL_BD_MAGIC_WALL_ACTIVE ||
12134 element == EL_BD_MAGIC_WALL_EMPTYING)
12135 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12136 else if (element == EL_DC_MAGIC_WALL_FULL ||
12137 element == EL_DC_MAGIC_WALL_ACTIVE ||
12138 element == EL_DC_MAGIC_WALL_EMPTYING)
12139 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12141 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12144 if (game.magic_wall_time_left > 0)
12146 game.magic_wall_time_left--;
12148 if (!game.magic_wall_time_left)
12150 SCAN_PLAYFIELD(x, y)
12152 element = Feld[x][y];
12154 if (element == EL_MAGIC_WALL_ACTIVE ||
12155 element == EL_MAGIC_WALL_FULL)
12157 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12158 TEST_DrawLevelField(x, y);
12160 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12161 element == EL_BD_MAGIC_WALL_FULL)
12163 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12164 TEST_DrawLevelField(x, y);
12166 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12167 element == EL_DC_MAGIC_WALL_FULL)
12169 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12170 TEST_DrawLevelField(x, y);
12174 game.magic_wall_active = FALSE;
12179 if (game.light_time_left > 0)
12181 game.light_time_left--;
12183 if (game.light_time_left == 0)
12184 RedrawAllLightSwitchesAndInvisibleElements();
12187 if (game.timegate_time_left > 0)
12189 game.timegate_time_left--;
12191 if (game.timegate_time_left == 0)
12192 CloseAllOpenTimegates();
12195 if (game.lenses_time_left > 0)
12197 game.lenses_time_left--;
12199 if (game.lenses_time_left == 0)
12200 RedrawAllInvisibleElementsForLenses();
12203 if (game.magnify_time_left > 0)
12205 game.magnify_time_left--;
12207 if (game.magnify_time_left == 0)
12208 RedrawAllInvisibleElementsForMagnifier();
12211 for (i = 0; i < MAX_PLAYERS; i++)
12213 struct PlayerInfo *player = &stored_player[i];
12215 if (SHIELD_ON(player))
12217 if (player->shield_deadly_time_left)
12218 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12219 else if (player->shield_normal_time_left)
12220 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12224 #if USE_DELAYED_GFX_REDRAW
12225 SCAN_PLAYFIELD(x, y)
12227 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12229 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12230 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12232 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12233 DrawLevelField(x, y);
12235 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12236 DrawLevelFieldCrumbled(x, y);
12238 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12239 DrawLevelFieldCrumbledNeighbours(x, y);
12241 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12242 DrawTwinkleOnField(x, y);
12245 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12250 PlayAllPlayersSound();
12252 for (i = 0; i < MAX_PLAYERS; i++)
12254 struct PlayerInfo *player = &stored_player[i];
12256 if (player->show_envelope != 0 && (!player->active ||
12257 player->MovPos == 0))
12259 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12261 player->show_envelope = 0;
12265 // use random number generator in every frame to make it less predictable
12266 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12269 mouse_action_last = mouse_action;
12272 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12274 int min_x = x, min_y = y, max_x = x, max_y = y;
12277 for (i = 0; i < MAX_PLAYERS; i++)
12279 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12281 if (!stored_player[i].active || &stored_player[i] == player)
12284 min_x = MIN(min_x, jx);
12285 min_y = MIN(min_y, jy);
12286 max_x = MAX(max_x, jx);
12287 max_y = MAX(max_y, jy);
12290 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12293 static boolean AllPlayersInVisibleScreen(void)
12297 for (i = 0; i < MAX_PLAYERS; i++)
12299 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12301 if (!stored_player[i].active)
12304 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12311 void ScrollLevel(int dx, int dy)
12313 int scroll_offset = 2 * TILEX_VAR;
12316 BlitBitmap(drawto_field, drawto_field,
12317 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12318 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12319 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12320 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12321 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12322 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12326 x = (dx == 1 ? BX1 : BX2);
12327 for (y = BY1; y <= BY2; y++)
12328 DrawScreenField(x, y);
12333 y = (dy == 1 ? BY1 : BY2);
12334 for (x = BX1; x <= BX2; x++)
12335 DrawScreenField(x, y);
12338 redraw_mask |= REDRAW_FIELD;
12341 static boolean canFallDown(struct PlayerInfo *player)
12343 int jx = player->jx, jy = player->jy;
12345 return (IN_LEV_FIELD(jx, jy + 1) &&
12346 (IS_FREE(jx, jy + 1) ||
12347 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12348 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12349 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12352 static boolean canPassField(int x, int y, int move_dir)
12354 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12355 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12356 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12357 int nextx = x + dx;
12358 int nexty = y + dy;
12359 int element = Feld[x][y];
12361 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12362 !CAN_MOVE(element) &&
12363 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12364 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12365 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12368 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12370 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12371 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12372 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12376 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12377 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12378 (IS_DIGGABLE(Feld[newx][newy]) ||
12379 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12380 canPassField(newx, newy, move_dir)));
12383 static void CheckGravityMovement(struct PlayerInfo *player)
12385 if (player->gravity && !player->programmed_action)
12387 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12388 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12389 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12390 int jx = player->jx, jy = player->jy;
12391 boolean player_is_moving_to_valid_field =
12392 (!player_is_snapping &&
12393 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12394 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12395 boolean player_can_fall_down = canFallDown(player);
12397 if (player_can_fall_down &&
12398 !player_is_moving_to_valid_field)
12399 player->programmed_action = MV_DOWN;
12403 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12405 return CheckGravityMovement(player);
12407 if (player->gravity && !player->programmed_action)
12409 int jx = player->jx, jy = player->jy;
12410 boolean field_under_player_is_free =
12411 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12412 boolean player_is_standing_on_valid_field =
12413 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12414 (IS_WALKABLE(Feld[jx][jy]) &&
12415 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12417 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12418 player->programmed_action = MV_DOWN;
12423 MovePlayerOneStep()
12424 -----------------------------------------------------------------------------
12425 dx, dy: direction (non-diagonal) to try to move the player to
12426 real_dx, real_dy: direction as read from input device (can be diagonal)
12429 boolean MovePlayerOneStep(struct PlayerInfo *player,
12430 int dx, int dy, int real_dx, int real_dy)
12432 int jx = player->jx, jy = player->jy;
12433 int new_jx = jx + dx, new_jy = jy + dy;
12435 boolean player_can_move = !player->cannot_move;
12437 if (!player->active || (!dx && !dy))
12438 return MP_NO_ACTION;
12440 player->MovDir = (dx < 0 ? MV_LEFT :
12441 dx > 0 ? MV_RIGHT :
12443 dy > 0 ? MV_DOWN : MV_NONE);
12445 if (!IN_LEV_FIELD(new_jx, new_jy))
12446 return MP_NO_ACTION;
12448 if (!player_can_move)
12450 if (player->MovPos == 0)
12452 player->is_moving = FALSE;
12453 player->is_digging = FALSE;
12454 player->is_collecting = FALSE;
12455 player->is_snapping = FALSE;
12456 player->is_pushing = FALSE;
12460 if (!network.enabled && game.centered_player_nr == -1 &&
12461 !AllPlayersInSight(player, new_jx, new_jy))
12462 return MP_NO_ACTION;
12464 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12465 if (can_move != MP_MOVING)
12468 // check if DigField() has caused relocation of the player
12469 if (player->jx != jx || player->jy != jy)
12470 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12472 StorePlayer[jx][jy] = 0;
12473 player->last_jx = jx;
12474 player->last_jy = jy;
12475 player->jx = new_jx;
12476 player->jy = new_jy;
12477 StorePlayer[new_jx][new_jy] = player->element_nr;
12479 if (player->move_delay_value_next != -1)
12481 player->move_delay_value = player->move_delay_value_next;
12482 player->move_delay_value_next = -1;
12486 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12488 player->step_counter++;
12490 PlayerVisit[jx][jy] = FrameCounter;
12492 player->is_moving = TRUE;
12495 // should better be called in MovePlayer(), but this breaks some tapes
12496 ScrollPlayer(player, SCROLL_INIT);
12502 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12504 int jx = player->jx, jy = player->jy;
12505 int old_jx = jx, old_jy = jy;
12506 int moved = MP_NO_ACTION;
12508 if (!player->active)
12513 if (player->MovPos == 0)
12515 player->is_moving = FALSE;
12516 player->is_digging = FALSE;
12517 player->is_collecting = FALSE;
12518 player->is_snapping = FALSE;
12519 player->is_pushing = FALSE;
12525 if (player->move_delay > 0)
12528 player->move_delay = -1; // set to "uninitialized" value
12530 // store if player is automatically moved to next field
12531 player->is_auto_moving = (player->programmed_action != MV_NONE);
12533 // remove the last programmed player action
12534 player->programmed_action = 0;
12536 if (player->MovPos)
12538 // should only happen if pre-1.2 tape recordings are played
12539 // this is only for backward compatibility
12541 int original_move_delay_value = player->move_delay_value;
12544 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12548 // scroll remaining steps with finest movement resolution
12549 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12551 while (player->MovPos)
12553 ScrollPlayer(player, SCROLL_GO_ON);
12554 ScrollScreen(NULL, SCROLL_GO_ON);
12556 AdvanceFrameAndPlayerCounters(player->index_nr);
12559 BackToFront_WithFrameDelay(0);
12562 player->move_delay_value = original_move_delay_value;
12565 player->is_active = FALSE;
12567 if (player->last_move_dir & MV_HORIZONTAL)
12569 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12570 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12574 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12575 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12578 if (!moved && !player->is_active)
12580 player->is_moving = FALSE;
12581 player->is_digging = FALSE;
12582 player->is_collecting = FALSE;
12583 player->is_snapping = FALSE;
12584 player->is_pushing = FALSE;
12590 if (moved & MP_MOVING && !ScreenMovPos &&
12591 (player->index_nr == game.centered_player_nr ||
12592 game.centered_player_nr == -1))
12594 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12596 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12598 // actual player has left the screen -- scroll in that direction
12599 if (jx != old_jx) // player has moved horizontally
12600 scroll_x += (jx - old_jx);
12601 else // player has moved vertically
12602 scroll_y += (jy - old_jy);
12606 int offset_raw = game.scroll_delay_value;
12608 if (jx != old_jx) // player has moved horizontally
12610 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12611 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12612 int new_scroll_x = jx - MIDPOSX + offset_x;
12614 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12615 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12616 scroll_x = new_scroll_x;
12618 // don't scroll over playfield boundaries
12619 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12621 // don't scroll more than one field at a time
12622 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12624 // don't scroll against the player's moving direction
12625 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12626 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12627 scroll_x = old_scroll_x;
12629 else // player has moved vertically
12631 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12632 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12633 int new_scroll_y = jy - MIDPOSY + offset_y;
12635 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12636 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12637 scroll_y = new_scroll_y;
12639 // don't scroll over playfield boundaries
12640 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12642 // don't scroll more than one field at a time
12643 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12645 // don't scroll against the player's moving direction
12646 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12647 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12648 scroll_y = old_scroll_y;
12652 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12654 if (!network.enabled && game.centered_player_nr == -1 &&
12655 !AllPlayersInVisibleScreen())
12657 scroll_x = old_scroll_x;
12658 scroll_y = old_scroll_y;
12662 ScrollScreen(player, SCROLL_INIT);
12663 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12668 player->StepFrame = 0;
12670 if (moved & MP_MOVING)
12672 if (old_jx != jx && old_jy == jy)
12673 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12674 else if (old_jx == jx && old_jy != jy)
12675 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12677 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12679 player->last_move_dir = player->MovDir;
12680 player->is_moving = TRUE;
12681 player->is_snapping = FALSE;
12682 player->is_switching = FALSE;
12683 player->is_dropping = FALSE;
12684 player->is_dropping_pressed = FALSE;
12685 player->drop_pressed_delay = 0;
12688 // should better be called here than above, but this breaks some tapes
12689 ScrollPlayer(player, SCROLL_INIT);
12694 CheckGravityMovementWhenNotMoving(player);
12696 player->is_moving = FALSE;
12698 /* at this point, the player is allowed to move, but cannot move right now
12699 (e.g. because of something blocking the way) -- ensure that the player
12700 is also allowed to move in the next frame (in old versions before 3.1.1,
12701 the player was forced to wait again for eight frames before next try) */
12703 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12704 player->move_delay = 0; // allow direct movement in the next frame
12707 if (player->move_delay == -1) // not yet initialized by DigField()
12708 player->move_delay = player->move_delay_value;
12710 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12712 TestIfPlayerTouchesBadThing(jx, jy);
12713 TestIfPlayerTouchesCustomElement(jx, jy);
12716 if (!player->active)
12717 RemovePlayer(player);
12722 void ScrollPlayer(struct PlayerInfo *player, int mode)
12724 int jx = player->jx, jy = player->jy;
12725 int last_jx = player->last_jx, last_jy = player->last_jy;
12726 int move_stepsize = TILEX / player->move_delay_value;
12728 if (!player->active)
12731 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12734 if (mode == SCROLL_INIT)
12736 player->actual_frame_counter = FrameCounter;
12737 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12739 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12740 Feld[last_jx][last_jy] == EL_EMPTY)
12742 int last_field_block_delay = 0; // start with no blocking at all
12743 int block_delay_adjustment = player->block_delay_adjustment;
12745 // if player blocks last field, add delay for exactly one move
12746 if (player->block_last_field)
12748 last_field_block_delay += player->move_delay_value;
12750 // when blocking enabled, prevent moving up despite gravity
12751 if (player->gravity && player->MovDir == MV_UP)
12752 block_delay_adjustment = -1;
12755 // add block delay adjustment (also possible when not blocking)
12756 last_field_block_delay += block_delay_adjustment;
12758 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12759 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12762 if (player->MovPos != 0) // player has not yet reached destination
12765 else if (!FrameReached(&player->actual_frame_counter, 1))
12768 if (player->MovPos != 0)
12770 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12771 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12773 // before DrawPlayer() to draw correct player graphic for this case
12774 if (player->MovPos == 0)
12775 CheckGravityMovement(player);
12778 if (player->MovPos == 0) // player reached destination field
12780 if (player->move_delay_reset_counter > 0)
12782 player->move_delay_reset_counter--;
12784 if (player->move_delay_reset_counter == 0)
12786 // continue with normal speed after quickly moving through gate
12787 HALVE_PLAYER_SPEED(player);
12789 // be able to make the next move without delay
12790 player->move_delay = 0;
12794 player->last_jx = jx;
12795 player->last_jy = jy;
12797 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12798 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12799 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12800 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12801 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12802 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12803 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12804 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12806 ExitPlayer(player);
12808 if (game.players_still_needed == 0 &&
12809 (game.friends_still_needed == 0 ||
12810 IS_SP_ELEMENT(Feld[jx][jy])))
12814 // this breaks one level: "machine", level 000
12816 int move_direction = player->MovDir;
12817 int enter_side = MV_DIR_OPPOSITE(move_direction);
12818 int leave_side = move_direction;
12819 int old_jx = last_jx;
12820 int old_jy = last_jy;
12821 int old_element = Feld[old_jx][old_jy];
12822 int new_element = Feld[jx][jy];
12824 if (IS_CUSTOM_ELEMENT(old_element))
12825 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12827 player->index_bit, leave_side);
12829 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12830 CE_PLAYER_LEAVES_X,
12831 player->index_bit, leave_side);
12833 if (IS_CUSTOM_ELEMENT(new_element))
12834 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12835 player->index_bit, enter_side);
12837 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12838 CE_PLAYER_ENTERS_X,
12839 player->index_bit, enter_side);
12841 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12842 CE_MOVE_OF_X, move_direction);
12845 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12847 TestIfPlayerTouchesBadThing(jx, jy);
12848 TestIfPlayerTouchesCustomElement(jx, jy);
12850 /* needed because pushed element has not yet reached its destination,
12851 so it would trigger a change event at its previous field location */
12852 if (!player->is_pushing)
12853 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12855 if (!player->active)
12856 RemovePlayer(player);
12859 if (!game.LevelSolved && level.use_step_counter)
12869 if (TimeLeft <= 10 && setup.time_limit)
12870 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12872 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12874 DisplayGameControlValues();
12876 if (!TimeLeft && setup.time_limit)
12877 for (i = 0; i < MAX_PLAYERS; i++)
12878 KillPlayer(&stored_player[i]);
12880 else if (game.no_time_limit && !game.all_players_gone)
12882 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12884 DisplayGameControlValues();
12888 if (tape.single_step && tape.recording && !tape.pausing &&
12889 !player->programmed_action)
12890 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12892 if (!player->programmed_action)
12893 CheckSaveEngineSnapshot(player);
12897 void ScrollScreen(struct PlayerInfo *player, int mode)
12899 static unsigned int screen_frame_counter = 0;
12901 if (mode == SCROLL_INIT)
12903 // set scrolling step size according to actual player's moving speed
12904 ScrollStepSize = TILEX / player->move_delay_value;
12906 screen_frame_counter = FrameCounter;
12907 ScreenMovDir = player->MovDir;
12908 ScreenMovPos = player->MovPos;
12909 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12912 else if (!FrameReached(&screen_frame_counter, 1))
12917 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
12918 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12919 redraw_mask |= REDRAW_FIELD;
12922 ScreenMovDir = MV_NONE;
12925 void TestIfPlayerTouchesCustomElement(int x, int y)
12927 static int xy[4][2] =
12934 static int trigger_sides[4][2] =
12936 // center side border side
12937 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12938 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12939 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12940 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12942 static int touch_dir[4] =
12944 MV_LEFT | MV_RIGHT,
12949 int center_element = Feld[x][y]; // should always be non-moving!
12952 for (i = 0; i < NUM_DIRECTIONS; i++)
12954 int xx = x + xy[i][0];
12955 int yy = y + xy[i][1];
12956 int center_side = trigger_sides[i][0];
12957 int border_side = trigger_sides[i][1];
12958 int border_element;
12960 if (!IN_LEV_FIELD(xx, yy))
12963 if (IS_PLAYER(x, y)) // player found at center element
12965 struct PlayerInfo *player = PLAYERINFO(x, y);
12967 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12968 border_element = Feld[xx][yy]; // may be moving!
12969 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12970 border_element = Feld[xx][yy];
12971 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
12972 border_element = MovingOrBlocked2Element(xx, yy);
12974 continue; // center and border element do not touch
12976 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
12977 player->index_bit, border_side);
12978 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
12979 CE_PLAYER_TOUCHES_X,
12980 player->index_bit, border_side);
12983 /* use player element that is initially defined in the level playfield,
12984 not the player element that corresponds to the runtime player number
12985 (example: a level that contains EL_PLAYER_3 as the only player would
12986 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12987 int player_element = PLAYERINFO(x, y)->initial_element;
12989 CheckElementChangeBySide(xx, yy, border_element, player_element,
12990 CE_TOUCHING_X, border_side);
12993 else if (IS_PLAYER(xx, yy)) // player found at border element
12995 struct PlayerInfo *player = PLAYERINFO(xx, yy);
12997 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12999 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13000 continue; // center and border element do not touch
13003 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13004 player->index_bit, center_side);
13005 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13006 CE_PLAYER_TOUCHES_X,
13007 player->index_bit, center_side);
13010 /* use player element that is initially defined in the level playfield,
13011 not the player element that corresponds to the runtime player number
13012 (example: a level that contains EL_PLAYER_3 as the only player would
13013 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13014 int player_element = PLAYERINFO(xx, yy)->initial_element;
13016 CheckElementChangeBySide(x, y, center_element, player_element,
13017 CE_TOUCHING_X, center_side);
13025 void TestIfElementTouchesCustomElement(int x, int y)
13027 static int xy[4][2] =
13034 static int trigger_sides[4][2] =
13036 // center side border side
13037 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13038 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13039 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13040 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13042 static int touch_dir[4] =
13044 MV_LEFT | MV_RIGHT,
13049 boolean change_center_element = FALSE;
13050 int center_element = Feld[x][y]; // should always be non-moving!
13051 int border_element_old[NUM_DIRECTIONS];
13054 for (i = 0; i < NUM_DIRECTIONS; i++)
13056 int xx = x + xy[i][0];
13057 int yy = y + xy[i][1];
13058 int border_element;
13060 border_element_old[i] = -1;
13062 if (!IN_LEV_FIELD(xx, yy))
13065 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13066 border_element = Feld[xx][yy]; // may be moving!
13067 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13068 border_element = Feld[xx][yy];
13069 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13070 border_element = MovingOrBlocked2Element(xx, yy);
13072 continue; // center and border element do not touch
13074 border_element_old[i] = border_element;
13077 for (i = 0; i < NUM_DIRECTIONS; i++)
13079 int xx = x + xy[i][0];
13080 int yy = y + xy[i][1];
13081 int center_side = trigger_sides[i][0];
13082 int border_element = border_element_old[i];
13084 if (border_element == -1)
13087 // check for change of border element
13088 CheckElementChangeBySide(xx, yy, border_element, center_element,
13089 CE_TOUCHING_X, center_side);
13091 // (center element cannot be player, so we dont have to check this here)
13094 for (i = 0; i < NUM_DIRECTIONS; i++)
13096 int xx = x + xy[i][0];
13097 int yy = y + xy[i][1];
13098 int border_side = trigger_sides[i][1];
13099 int border_element = border_element_old[i];
13101 if (border_element == -1)
13104 // check for change of center element (but change it only once)
13105 if (!change_center_element)
13106 change_center_element =
13107 CheckElementChangeBySide(x, y, center_element, border_element,
13108 CE_TOUCHING_X, border_side);
13110 if (IS_PLAYER(xx, yy))
13112 /* use player element that is initially defined in the level playfield,
13113 not the player element that corresponds to the runtime player number
13114 (example: a level that contains EL_PLAYER_3 as the only player would
13115 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13116 int player_element = PLAYERINFO(xx, yy)->initial_element;
13118 CheckElementChangeBySide(x, y, center_element, player_element,
13119 CE_TOUCHING_X, border_side);
13124 void TestIfElementHitsCustomElement(int x, int y, int direction)
13126 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13127 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13128 int hitx = x + dx, hity = y + dy;
13129 int hitting_element = Feld[x][y];
13130 int touched_element;
13132 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13135 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13136 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13138 if (IN_LEV_FIELD(hitx, hity))
13140 int opposite_direction = MV_DIR_OPPOSITE(direction);
13141 int hitting_side = direction;
13142 int touched_side = opposite_direction;
13143 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13144 MovDir[hitx][hity] != direction ||
13145 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13151 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13152 CE_HITTING_X, touched_side);
13154 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13155 CE_HIT_BY_X, hitting_side);
13157 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13158 CE_HIT_BY_SOMETHING, opposite_direction);
13160 if (IS_PLAYER(hitx, hity))
13162 /* use player element that is initially defined in the level playfield,
13163 not the player element that corresponds to the runtime player number
13164 (example: a level that contains EL_PLAYER_3 as the only player would
13165 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13166 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13168 CheckElementChangeBySide(x, y, hitting_element, player_element,
13169 CE_HITTING_X, touched_side);
13174 // "hitting something" is also true when hitting the playfield border
13175 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13176 CE_HITTING_SOMETHING, direction);
13179 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13181 int i, kill_x = -1, kill_y = -1;
13183 int bad_element = -1;
13184 static int test_xy[4][2] =
13191 static int test_dir[4] =
13199 for (i = 0; i < NUM_DIRECTIONS; i++)
13201 int test_x, test_y, test_move_dir, test_element;
13203 test_x = good_x + test_xy[i][0];
13204 test_y = good_y + test_xy[i][1];
13206 if (!IN_LEV_FIELD(test_x, test_y))
13210 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13212 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13214 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13215 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13217 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13218 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13222 bad_element = test_element;
13228 if (kill_x != -1 || kill_y != -1)
13230 if (IS_PLAYER(good_x, good_y))
13232 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13234 if (player->shield_deadly_time_left > 0 &&
13235 !IS_INDESTRUCTIBLE(bad_element))
13236 Bang(kill_x, kill_y);
13237 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13238 KillPlayer(player);
13241 Bang(good_x, good_y);
13245 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13247 int i, kill_x = -1, kill_y = -1;
13248 int bad_element = Feld[bad_x][bad_y];
13249 static int test_xy[4][2] =
13256 static int touch_dir[4] =
13258 MV_LEFT | MV_RIGHT,
13263 static int test_dir[4] =
13271 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13274 for (i = 0; i < NUM_DIRECTIONS; i++)
13276 int test_x, test_y, test_move_dir, test_element;
13278 test_x = bad_x + test_xy[i][0];
13279 test_y = bad_y + test_xy[i][1];
13281 if (!IN_LEV_FIELD(test_x, test_y))
13285 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13287 test_element = Feld[test_x][test_y];
13289 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13290 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13292 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13293 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13295 // good thing is player or penguin that does not move away
13296 if (IS_PLAYER(test_x, test_y))
13298 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13300 if (bad_element == EL_ROBOT && player->is_moving)
13301 continue; // robot does not kill player if he is moving
13303 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13305 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13306 continue; // center and border element do not touch
13314 else if (test_element == EL_PENGUIN)
13324 if (kill_x != -1 || kill_y != -1)
13326 if (IS_PLAYER(kill_x, kill_y))
13328 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13330 if (player->shield_deadly_time_left > 0 &&
13331 !IS_INDESTRUCTIBLE(bad_element))
13332 Bang(bad_x, bad_y);
13333 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13334 KillPlayer(player);
13337 Bang(kill_x, kill_y);
13341 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13343 int bad_element = Feld[bad_x][bad_y];
13344 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13345 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13346 int test_x = bad_x + dx, test_y = bad_y + dy;
13347 int test_move_dir, test_element;
13348 int kill_x = -1, kill_y = -1;
13350 if (!IN_LEV_FIELD(test_x, test_y))
13354 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13356 test_element = Feld[test_x][test_y];
13358 if (test_move_dir != bad_move_dir)
13360 // good thing can be player or penguin that does not move away
13361 if (IS_PLAYER(test_x, test_y))
13363 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13365 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13366 player as being hit when he is moving towards the bad thing, because
13367 the "get hit by" condition would be lost after the player stops) */
13368 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13369 return; // player moves away from bad thing
13374 else if (test_element == EL_PENGUIN)
13381 if (kill_x != -1 || kill_y != -1)
13383 if (IS_PLAYER(kill_x, kill_y))
13385 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13387 if (player->shield_deadly_time_left > 0 &&
13388 !IS_INDESTRUCTIBLE(bad_element))
13389 Bang(bad_x, bad_y);
13390 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13391 KillPlayer(player);
13394 Bang(kill_x, kill_y);
13398 void TestIfPlayerTouchesBadThing(int x, int y)
13400 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13403 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13405 TestIfGoodThingHitsBadThing(x, y, move_dir);
13408 void TestIfBadThingTouchesPlayer(int x, int y)
13410 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13413 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13415 TestIfBadThingHitsGoodThing(x, y, move_dir);
13418 void TestIfFriendTouchesBadThing(int x, int y)
13420 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13423 void TestIfBadThingTouchesFriend(int x, int y)
13425 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13428 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13430 int i, kill_x = bad_x, kill_y = bad_y;
13431 static int xy[4][2] =
13439 for (i = 0; i < NUM_DIRECTIONS; i++)
13443 x = bad_x + xy[i][0];
13444 y = bad_y + xy[i][1];
13445 if (!IN_LEV_FIELD(x, y))
13448 element = Feld[x][y];
13449 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13450 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13458 if (kill_x != bad_x || kill_y != bad_y)
13459 Bang(bad_x, bad_y);
13462 void KillPlayer(struct PlayerInfo *player)
13464 int jx = player->jx, jy = player->jy;
13466 if (!player->active)
13470 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13471 player->killed, player->active, player->reanimated);
13474 /* the following code was introduced to prevent an infinite loop when calling
13476 -> CheckTriggeredElementChangeExt()
13477 -> ExecuteCustomElementAction()
13479 -> (infinitely repeating the above sequence of function calls)
13480 which occurs when killing the player while having a CE with the setting
13481 "kill player X when explosion of <player X>"; the solution using a new
13482 field "player->killed" was chosen for backwards compatibility, although
13483 clever use of the fields "player->active" etc. would probably also work */
13485 if (player->killed)
13489 player->killed = TRUE;
13491 // remove accessible field at the player's position
13492 Feld[jx][jy] = EL_EMPTY;
13494 // deactivate shield (else Bang()/Explode() would not work right)
13495 player->shield_normal_time_left = 0;
13496 player->shield_deadly_time_left = 0;
13499 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13500 player->killed, player->active, player->reanimated);
13506 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13507 player->killed, player->active, player->reanimated);
13510 if (player->reanimated) // killed player may have been reanimated
13511 player->killed = player->reanimated = FALSE;
13513 BuryPlayer(player);
13516 static void KillPlayerUnlessEnemyProtected(int x, int y)
13518 if (!PLAYER_ENEMY_PROTECTED(x, y))
13519 KillPlayer(PLAYERINFO(x, y));
13522 static void KillPlayerUnlessExplosionProtected(int x, int y)
13524 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13525 KillPlayer(PLAYERINFO(x, y));
13528 void BuryPlayer(struct PlayerInfo *player)
13530 int jx = player->jx, jy = player->jy;
13532 if (!player->active)
13535 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13536 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13538 RemovePlayer(player);
13540 player->buried = TRUE;
13542 if (game.all_players_gone)
13543 game.GameOver = TRUE;
13546 void RemovePlayer(struct PlayerInfo *player)
13548 int jx = player->jx, jy = player->jy;
13549 int i, found = FALSE;
13551 player->present = FALSE;
13552 player->active = FALSE;
13554 // required for some CE actions (even if the player is not active anymore)
13555 player->MovPos = 0;
13557 if (!ExplodeField[jx][jy])
13558 StorePlayer[jx][jy] = 0;
13560 if (player->is_moving)
13561 TEST_DrawLevelField(player->last_jx, player->last_jy);
13563 for (i = 0; i < MAX_PLAYERS; i++)
13564 if (stored_player[i].active)
13569 game.all_players_gone = TRUE;
13570 game.GameOver = TRUE;
13573 game.exit_x = game.robot_wheel_x = jx;
13574 game.exit_y = game.robot_wheel_y = jy;
13577 void ExitPlayer(struct PlayerInfo *player)
13579 DrawPlayer(player); // needed here only to cleanup last field
13580 RemovePlayer(player);
13582 if (game.players_still_needed > 0)
13583 game.players_still_needed--;
13586 static void setFieldForSnapping(int x, int y, int element, int direction)
13588 struct ElementInfo *ei = &element_info[element];
13589 int direction_bit = MV_DIR_TO_BIT(direction);
13590 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13591 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13592 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13594 Feld[x][y] = EL_ELEMENT_SNAPPING;
13595 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13597 ResetGfxAnimation(x, y);
13599 GfxElement[x][y] = element;
13600 GfxAction[x][y] = action;
13601 GfxDir[x][y] = direction;
13602 GfxFrame[x][y] = -1;
13606 =============================================================================
13607 checkDiagonalPushing()
13608 -----------------------------------------------------------------------------
13609 check if diagonal input device direction results in pushing of object
13610 (by checking if the alternative direction is walkable, diggable, ...)
13611 =============================================================================
13614 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13615 int x, int y, int real_dx, int real_dy)
13617 int jx, jy, dx, dy, xx, yy;
13619 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13622 // diagonal direction: check alternative direction
13627 xx = jx + (dx == 0 ? real_dx : 0);
13628 yy = jy + (dy == 0 ? real_dy : 0);
13630 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13634 =============================================================================
13636 -----------------------------------------------------------------------------
13637 x, y: field next to player (non-diagonal) to try to dig to
13638 real_dx, real_dy: direction as read from input device (can be diagonal)
13639 =============================================================================
13642 static int DigField(struct PlayerInfo *player,
13643 int oldx, int oldy, int x, int y,
13644 int real_dx, int real_dy, int mode)
13646 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13647 boolean player_was_pushing = player->is_pushing;
13648 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13649 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13650 int jx = oldx, jy = oldy;
13651 int dx = x - jx, dy = y - jy;
13652 int nextx = x + dx, nexty = y + dy;
13653 int move_direction = (dx == -1 ? MV_LEFT :
13654 dx == +1 ? MV_RIGHT :
13656 dy == +1 ? MV_DOWN : MV_NONE);
13657 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13658 int dig_side = MV_DIR_OPPOSITE(move_direction);
13659 int old_element = Feld[jx][jy];
13660 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13663 if (is_player) // function can also be called by EL_PENGUIN
13665 if (player->MovPos == 0)
13667 player->is_digging = FALSE;
13668 player->is_collecting = FALSE;
13671 if (player->MovPos == 0) // last pushing move finished
13672 player->is_pushing = FALSE;
13674 if (mode == DF_NO_PUSH) // player just stopped pushing
13676 player->is_switching = FALSE;
13677 player->push_delay = -1;
13679 return MP_NO_ACTION;
13683 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13684 old_element = Back[jx][jy];
13686 // in case of element dropped at player position, check background
13687 else if (Back[jx][jy] != EL_EMPTY &&
13688 game.engine_version >= VERSION_IDENT(2,2,0,0))
13689 old_element = Back[jx][jy];
13691 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13692 return MP_NO_ACTION; // field has no opening in this direction
13694 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13695 return MP_NO_ACTION; // field has no opening in this direction
13697 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13701 Feld[jx][jy] = player->artwork_element;
13702 InitMovingField(jx, jy, MV_DOWN);
13703 Store[jx][jy] = EL_ACID;
13704 ContinueMoving(jx, jy);
13705 BuryPlayer(player);
13707 return MP_DONT_RUN_INTO;
13710 if (player_can_move && DONT_RUN_INTO(element))
13712 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13714 return MP_DONT_RUN_INTO;
13717 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13718 return MP_NO_ACTION;
13720 collect_count = element_info[element].collect_count_initial;
13722 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13723 return MP_NO_ACTION;
13725 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13726 player_can_move = player_can_move_or_snap;
13728 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13729 game.engine_version >= VERSION_IDENT(2,2,0,0))
13731 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13732 player->index_bit, dig_side);
13733 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13734 player->index_bit, dig_side);
13736 if (element == EL_DC_LANDMINE)
13739 if (Feld[x][y] != element) // field changed by snapping
13742 return MP_NO_ACTION;
13745 if (player->gravity && is_player && !player->is_auto_moving &&
13746 canFallDown(player) && move_direction != MV_DOWN &&
13747 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13748 return MP_NO_ACTION; // player cannot walk here due to gravity
13750 if (player_can_move &&
13751 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13753 int sound_element = SND_ELEMENT(element);
13754 int sound_action = ACTION_WALKING;
13756 if (IS_RND_GATE(element))
13758 if (!player->key[RND_GATE_NR(element)])
13759 return MP_NO_ACTION;
13761 else if (IS_RND_GATE_GRAY(element))
13763 if (!player->key[RND_GATE_GRAY_NR(element)])
13764 return MP_NO_ACTION;
13766 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13768 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13769 return MP_NO_ACTION;
13771 else if (element == EL_EXIT_OPEN ||
13772 element == EL_EM_EXIT_OPEN ||
13773 element == EL_EM_EXIT_OPENING ||
13774 element == EL_STEEL_EXIT_OPEN ||
13775 element == EL_EM_STEEL_EXIT_OPEN ||
13776 element == EL_EM_STEEL_EXIT_OPENING ||
13777 element == EL_SP_EXIT_OPEN ||
13778 element == EL_SP_EXIT_OPENING)
13780 sound_action = ACTION_PASSING; // player is passing exit
13782 else if (element == EL_EMPTY)
13784 sound_action = ACTION_MOVING; // nothing to walk on
13787 // play sound from background or player, whatever is available
13788 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13789 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13791 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13793 else if (player_can_move &&
13794 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13796 if (!ACCESS_FROM(element, opposite_direction))
13797 return MP_NO_ACTION; // field not accessible from this direction
13799 if (CAN_MOVE(element)) // only fixed elements can be passed!
13800 return MP_NO_ACTION;
13802 if (IS_EM_GATE(element))
13804 if (!player->key[EM_GATE_NR(element)])
13805 return MP_NO_ACTION;
13807 else if (IS_EM_GATE_GRAY(element))
13809 if (!player->key[EM_GATE_GRAY_NR(element)])
13810 return MP_NO_ACTION;
13812 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13814 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13815 return MP_NO_ACTION;
13817 else if (IS_EMC_GATE(element))
13819 if (!player->key[EMC_GATE_NR(element)])
13820 return MP_NO_ACTION;
13822 else if (IS_EMC_GATE_GRAY(element))
13824 if (!player->key[EMC_GATE_GRAY_NR(element)])
13825 return MP_NO_ACTION;
13827 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13829 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13830 return MP_NO_ACTION;
13832 else if (element == EL_DC_GATE_WHITE ||
13833 element == EL_DC_GATE_WHITE_GRAY ||
13834 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13836 if (player->num_white_keys == 0)
13837 return MP_NO_ACTION;
13839 player->num_white_keys--;
13841 else if (IS_SP_PORT(element))
13843 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13844 element == EL_SP_GRAVITY_PORT_RIGHT ||
13845 element == EL_SP_GRAVITY_PORT_UP ||
13846 element == EL_SP_GRAVITY_PORT_DOWN)
13847 player->gravity = !player->gravity;
13848 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13849 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13850 element == EL_SP_GRAVITY_ON_PORT_UP ||
13851 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13852 player->gravity = TRUE;
13853 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13854 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13855 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13856 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13857 player->gravity = FALSE;
13860 // automatically move to the next field with double speed
13861 player->programmed_action = move_direction;
13863 if (player->move_delay_reset_counter == 0)
13865 player->move_delay_reset_counter = 2; // two double speed steps
13867 DOUBLE_PLAYER_SPEED(player);
13870 PlayLevelSoundAction(x, y, ACTION_PASSING);
13872 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13876 if (mode != DF_SNAP)
13878 GfxElement[x][y] = GFX_ELEMENT(element);
13879 player->is_digging = TRUE;
13882 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13884 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13885 player->index_bit, dig_side);
13887 if (mode == DF_SNAP)
13889 if (level.block_snap_field)
13890 setFieldForSnapping(x, y, element, move_direction);
13892 TestIfElementTouchesCustomElement(x, y); // for empty space
13894 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13895 player->index_bit, dig_side);
13898 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13902 if (is_player && mode != DF_SNAP)
13904 GfxElement[x][y] = element;
13905 player->is_collecting = TRUE;
13908 if (element == EL_SPEED_PILL)
13910 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13912 else if (element == EL_EXTRA_TIME && level.time > 0)
13914 TimeLeft += level.extra_time;
13916 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13918 DisplayGameControlValues();
13920 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
13922 player->shield_normal_time_left += level.shield_normal_time;
13923 if (element == EL_SHIELD_DEADLY)
13924 player->shield_deadly_time_left += level.shield_deadly_time;
13926 else if (element == EL_DYNAMITE ||
13927 element == EL_EM_DYNAMITE ||
13928 element == EL_SP_DISK_RED)
13930 if (player->inventory_size < MAX_INVENTORY_SIZE)
13931 player->inventory_element[player->inventory_size++] = element;
13933 DrawGameDoorValues();
13935 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
13937 player->dynabomb_count++;
13938 player->dynabombs_left++;
13940 else if (element == EL_DYNABOMB_INCREASE_SIZE)
13942 player->dynabomb_size++;
13944 else if (element == EL_DYNABOMB_INCREASE_POWER)
13946 player->dynabomb_xl = TRUE;
13948 else if (IS_KEY(element))
13950 player->key[KEY_NR(element)] = TRUE;
13952 DrawGameDoorValues();
13954 else if (element == EL_DC_KEY_WHITE)
13956 player->num_white_keys++;
13958 // display white keys?
13959 // DrawGameDoorValues();
13961 else if (IS_ENVELOPE(element))
13963 player->show_envelope = element;
13965 else if (element == EL_EMC_LENSES)
13967 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
13969 RedrawAllInvisibleElementsForLenses();
13971 else if (element == EL_EMC_MAGNIFIER)
13973 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
13975 RedrawAllInvisibleElementsForMagnifier();
13977 else if (IS_DROPPABLE(element) ||
13978 IS_THROWABLE(element)) // can be collected and dropped
13982 if (collect_count == 0)
13983 player->inventory_infinite_element = element;
13985 for (i = 0; i < collect_count; i++)
13986 if (player->inventory_size < MAX_INVENTORY_SIZE)
13987 player->inventory_element[player->inventory_size++] = element;
13989 DrawGameDoorValues();
13991 else if (collect_count > 0)
13993 game.gems_still_needed -= collect_count;
13994 if (game.gems_still_needed < 0)
13995 game.gems_still_needed = 0;
13997 game.snapshot.collected_item = TRUE;
13999 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14001 DisplayGameControlValues();
14004 RaiseScoreElement(element);
14005 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14008 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14009 player->index_bit, dig_side);
14011 if (mode == DF_SNAP)
14013 if (level.block_snap_field)
14014 setFieldForSnapping(x, y, element, move_direction);
14016 TestIfElementTouchesCustomElement(x, y); // for empty space
14018 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14019 player->index_bit, dig_side);
14022 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14024 if (mode == DF_SNAP && element != EL_BD_ROCK)
14025 return MP_NO_ACTION;
14027 if (CAN_FALL(element) && dy)
14028 return MP_NO_ACTION;
14030 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14031 !(element == EL_SPRING && level.use_spring_bug))
14032 return MP_NO_ACTION;
14034 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14035 ((move_direction & MV_VERTICAL &&
14036 ((element_info[element].move_pattern & MV_LEFT &&
14037 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14038 (element_info[element].move_pattern & MV_RIGHT &&
14039 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14040 (move_direction & MV_HORIZONTAL &&
14041 ((element_info[element].move_pattern & MV_UP &&
14042 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14043 (element_info[element].move_pattern & MV_DOWN &&
14044 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14045 return MP_NO_ACTION;
14047 // do not push elements already moving away faster than player
14048 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14049 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14050 return MP_NO_ACTION;
14052 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14054 if (player->push_delay_value == -1 || !player_was_pushing)
14055 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14057 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14059 if (player->push_delay_value == -1)
14060 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14062 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14064 if (!player->is_pushing)
14065 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14068 player->is_pushing = TRUE;
14069 player->is_active = TRUE;
14071 if (!(IN_LEV_FIELD(nextx, nexty) &&
14072 (IS_FREE(nextx, nexty) ||
14073 (IS_SB_ELEMENT(element) &&
14074 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14075 (IS_CUSTOM_ELEMENT(element) &&
14076 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14077 return MP_NO_ACTION;
14079 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14080 return MP_NO_ACTION;
14082 if (player->push_delay == -1) // new pushing; restart delay
14083 player->push_delay = 0;
14085 if (player->push_delay < player->push_delay_value &&
14086 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14087 element != EL_SPRING && element != EL_BALLOON)
14089 // make sure that there is no move delay before next try to push
14090 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14091 player->move_delay = 0;
14093 return MP_NO_ACTION;
14096 if (IS_CUSTOM_ELEMENT(element) &&
14097 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14099 if (!DigFieldByCE(nextx, nexty, element))
14100 return MP_NO_ACTION;
14103 if (IS_SB_ELEMENT(element))
14105 boolean sokoban_task_solved = FALSE;
14107 if (element == EL_SOKOBAN_FIELD_FULL)
14109 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14111 IncrementSokobanFieldsNeeded();
14112 IncrementSokobanObjectsNeeded();
14115 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14117 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14119 DecrementSokobanFieldsNeeded();
14120 DecrementSokobanObjectsNeeded();
14122 // sokoban object was pushed from empty field to sokoban field
14123 if (Back[x][y] == EL_EMPTY)
14124 sokoban_task_solved = TRUE;
14127 Feld[x][y] = EL_SOKOBAN_OBJECT;
14129 if (Back[x][y] == Back[nextx][nexty])
14130 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14131 else if (Back[x][y] != 0)
14132 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14135 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14138 if (sokoban_task_solved &&
14139 game.sokoban_fields_still_needed == 0 &&
14140 game.sokoban_objects_still_needed == 0 &&
14141 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14143 game.players_still_needed = 0;
14147 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14151 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14153 InitMovingField(x, y, move_direction);
14154 GfxAction[x][y] = ACTION_PUSHING;
14156 if (mode == DF_SNAP)
14157 ContinueMoving(x, y);
14159 MovPos[x][y] = (dx != 0 ? dx : dy);
14161 Pushed[x][y] = TRUE;
14162 Pushed[nextx][nexty] = TRUE;
14164 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14165 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14167 player->push_delay_value = -1; // get new value later
14169 // check for element change _after_ element has been pushed
14170 if (game.use_change_when_pushing_bug)
14172 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14173 player->index_bit, dig_side);
14174 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14175 player->index_bit, dig_side);
14178 else if (IS_SWITCHABLE(element))
14180 if (PLAYER_SWITCHING(player, x, y))
14182 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14183 player->index_bit, dig_side);
14188 player->is_switching = TRUE;
14189 player->switch_x = x;
14190 player->switch_y = y;
14192 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14194 if (element == EL_ROBOT_WHEEL)
14196 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14198 game.robot_wheel_x = x;
14199 game.robot_wheel_y = y;
14200 game.robot_wheel_active = TRUE;
14202 TEST_DrawLevelField(x, y);
14204 else if (element == EL_SP_TERMINAL)
14208 SCAN_PLAYFIELD(xx, yy)
14210 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14214 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14216 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14218 ResetGfxAnimation(xx, yy);
14219 TEST_DrawLevelField(xx, yy);
14223 else if (IS_BELT_SWITCH(element))
14225 ToggleBeltSwitch(x, y);
14227 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14228 element == EL_SWITCHGATE_SWITCH_DOWN ||
14229 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14230 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14232 ToggleSwitchgateSwitch(x, y);
14234 else if (element == EL_LIGHT_SWITCH ||
14235 element == EL_LIGHT_SWITCH_ACTIVE)
14237 ToggleLightSwitch(x, y);
14239 else if (element == EL_TIMEGATE_SWITCH ||
14240 element == EL_DC_TIMEGATE_SWITCH)
14242 ActivateTimegateSwitch(x, y);
14244 else if (element == EL_BALLOON_SWITCH_LEFT ||
14245 element == EL_BALLOON_SWITCH_RIGHT ||
14246 element == EL_BALLOON_SWITCH_UP ||
14247 element == EL_BALLOON_SWITCH_DOWN ||
14248 element == EL_BALLOON_SWITCH_NONE ||
14249 element == EL_BALLOON_SWITCH_ANY)
14251 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14252 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14253 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14254 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14255 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14258 else if (element == EL_LAMP)
14260 Feld[x][y] = EL_LAMP_ACTIVE;
14261 game.lights_still_needed--;
14263 ResetGfxAnimation(x, y);
14264 TEST_DrawLevelField(x, y);
14266 else if (element == EL_TIME_ORB_FULL)
14268 Feld[x][y] = EL_TIME_ORB_EMPTY;
14270 if (level.time > 0 || level.use_time_orb_bug)
14272 TimeLeft += level.time_orb_time;
14273 game.no_time_limit = FALSE;
14275 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14277 DisplayGameControlValues();
14280 ResetGfxAnimation(x, y);
14281 TEST_DrawLevelField(x, y);
14283 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14284 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14288 game.ball_active = !game.ball_active;
14290 SCAN_PLAYFIELD(xx, yy)
14292 int e = Feld[xx][yy];
14294 if (game.ball_active)
14296 if (e == EL_EMC_MAGIC_BALL)
14297 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14298 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14299 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14303 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14304 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14305 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14306 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14311 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14312 player->index_bit, dig_side);
14314 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14315 player->index_bit, dig_side);
14317 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14318 player->index_bit, dig_side);
14324 if (!PLAYER_SWITCHING(player, x, y))
14326 player->is_switching = TRUE;
14327 player->switch_x = x;
14328 player->switch_y = y;
14330 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14331 player->index_bit, dig_side);
14332 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14333 player->index_bit, dig_side);
14335 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14336 player->index_bit, dig_side);
14337 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14338 player->index_bit, dig_side);
14341 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14342 player->index_bit, dig_side);
14343 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14344 player->index_bit, dig_side);
14346 return MP_NO_ACTION;
14349 player->push_delay = -1;
14351 if (is_player) // function can also be called by EL_PENGUIN
14353 if (Feld[x][y] != element) // really digged/collected something
14355 player->is_collecting = !player->is_digging;
14356 player->is_active = TRUE;
14363 static boolean DigFieldByCE(int x, int y, int digging_element)
14365 int element = Feld[x][y];
14367 if (!IS_FREE(x, y))
14369 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14370 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14373 // no element can dig solid indestructible elements
14374 if (IS_INDESTRUCTIBLE(element) &&
14375 !IS_DIGGABLE(element) &&
14376 !IS_COLLECTIBLE(element))
14379 if (AmoebaNr[x][y] &&
14380 (element == EL_AMOEBA_FULL ||
14381 element == EL_BD_AMOEBA ||
14382 element == EL_AMOEBA_GROWING))
14384 AmoebaCnt[AmoebaNr[x][y]]--;
14385 AmoebaCnt2[AmoebaNr[x][y]]--;
14388 if (IS_MOVING(x, y))
14389 RemoveMovingField(x, y);
14393 TEST_DrawLevelField(x, y);
14396 // if digged element was about to explode, prevent the explosion
14397 ExplodeField[x][y] = EX_TYPE_NONE;
14399 PlayLevelSoundAction(x, y, action);
14402 Store[x][y] = EL_EMPTY;
14404 // this makes it possible to leave the removed element again
14405 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14406 Store[x][y] = element;
14411 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14413 int jx = player->jx, jy = player->jy;
14414 int x = jx + dx, y = jy + dy;
14415 int snap_direction = (dx == -1 ? MV_LEFT :
14416 dx == +1 ? MV_RIGHT :
14418 dy == +1 ? MV_DOWN : MV_NONE);
14419 boolean can_continue_snapping = (level.continuous_snapping &&
14420 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14422 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14425 if (!player->active || !IN_LEV_FIELD(x, y))
14433 if (player->MovPos == 0)
14434 player->is_pushing = FALSE;
14436 player->is_snapping = FALSE;
14438 if (player->MovPos == 0)
14440 player->is_moving = FALSE;
14441 player->is_digging = FALSE;
14442 player->is_collecting = FALSE;
14448 // prevent snapping with already pressed snap key when not allowed
14449 if (player->is_snapping && !can_continue_snapping)
14452 player->MovDir = snap_direction;
14454 if (player->MovPos == 0)
14456 player->is_moving = FALSE;
14457 player->is_digging = FALSE;
14458 player->is_collecting = FALSE;
14461 player->is_dropping = FALSE;
14462 player->is_dropping_pressed = FALSE;
14463 player->drop_pressed_delay = 0;
14465 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14468 player->is_snapping = TRUE;
14469 player->is_active = TRUE;
14471 if (player->MovPos == 0)
14473 player->is_moving = FALSE;
14474 player->is_digging = FALSE;
14475 player->is_collecting = FALSE;
14478 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14479 TEST_DrawLevelField(player->last_jx, player->last_jy);
14481 TEST_DrawLevelField(x, y);
14486 static boolean DropElement(struct PlayerInfo *player)
14488 int old_element, new_element;
14489 int dropx = player->jx, dropy = player->jy;
14490 int drop_direction = player->MovDir;
14491 int drop_side = drop_direction;
14492 int drop_element = get_next_dropped_element(player);
14494 /* do not drop an element on top of another element; when holding drop key
14495 pressed without moving, dropped element must move away before the next
14496 element can be dropped (this is especially important if the next element
14497 is dynamite, which can be placed on background for historical reasons) */
14498 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14501 if (IS_THROWABLE(drop_element))
14503 dropx += GET_DX_FROM_DIR(drop_direction);
14504 dropy += GET_DY_FROM_DIR(drop_direction);
14506 if (!IN_LEV_FIELD(dropx, dropy))
14510 old_element = Feld[dropx][dropy]; // old element at dropping position
14511 new_element = drop_element; // default: no change when dropping
14513 // check if player is active, not moving and ready to drop
14514 if (!player->active || player->MovPos || player->drop_delay > 0)
14517 // check if player has anything that can be dropped
14518 if (new_element == EL_UNDEFINED)
14521 // only set if player has anything that can be dropped
14522 player->is_dropping_pressed = TRUE;
14524 // check if drop key was pressed long enough for EM style dynamite
14525 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14528 // check if anything can be dropped at the current position
14529 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14532 // collected custom elements can only be dropped on empty fields
14533 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14536 if (old_element != EL_EMPTY)
14537 Back[dropx][dropy] = old_element; // store old element on this field
14539 ResetGfxAnimation(dropx, dropy);
14540 ResetRandomAnimationValue(dropx, dropy);
14542 if (player->inventory_size > 0 ||
14543 player->inventory_infinite_element != EL_UNDEFINED)
14545 if (player->inventory_size > 0)
14547 player->inventory_size--;
14549 DrawGameDoorValues();
14551 if (new_element == EL_DYNAMITE)
14552 new_element = EL_DYNAMITE_ACTIVE;
14553 else if (new_element == EL_EM_DYNAMITE)
14554 new_element = EL_EM_DYNAMITE_ACTIVE;
14555 else if (new_element == EL_SP_DISK_RED)
14556 new_element = EL_SP_DISK_RED_ACTIVE;
14559 Feld[dropx][dropy] = new_element;
14561 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14562 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14563 el2img(Feld[dropx][dropy]), 0);
14565 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14567 // needed if previous element just changed to "empty" in the last frame
14568 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14570 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14571 player->index_bit, drop_side);
14572 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14574 player->index_bit, drop_side);
14576 TestIfElementTouchesCustomElement(dropx, dropy);
14578 else // player is dropping a dyna bomb
14580 player->dynabombs_left--;
14582 Feld[dropx][dropy] = new_element;
14584 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14585 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14586 el2img(Feld[dropx][dropy]), 0);
14588 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14591 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14592 InitField_WithBug1(dropx, dropy, FALSE);
14594 new_element = Feld[dropx][dropy]; // element might have changed
14596 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14597 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14599 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14600 MovDir[dropx][dropy] = drop_direction;
14602 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14604 // do not cause impact style collision by dropping elements that can fall
14605 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14608 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14609 player->is_dropping = TRUE;
14611 player->drop_pressed_delay = 0;
14612 player->is_dropping_pressed = FALSE;
14614 player->drop_x = dropx;
14615 player->drop_y = dropy;
14620 // ----------------------------------------------------------------------------
14621 // game sound playing functions
14622 // ----------------------------------------------------------------------------
14624 static int *loop_sound_frame = NULL;
14625 static int *loop_sound_volume = NULL;
14627 void InitPlayLevelSound(void)
14629 int num_sounds = getSoundListSize();
14631 checked_free(loop_sound_frame);
14632 checked_free(loop_sound_volume);
14634 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14635 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14638 static void PlayLevelSound(int x, int y, int nr)
14640 int sx = SCREENX(x), sy = SCREENY(y);
14641 int volume, stereo_position;
14642 int max_distance = 8;
14643 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14645 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14646 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14649 if (!IN_LEV_FIELD(x, y) ||
14650 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14651 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14654 volume = SOUND_MAX_VOLUME;
14656 if (!IN_SCR_FIELD(sx, sy))
14658 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14659 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14661 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14664 stereo_position = (SOUND_MAX_LEFT +
14665 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14666 (SCR_FIELDX + 2 * max_distance));
14668 if (IS_LOOP_SOUND(nr))
14670 /* This assures that quieter loop sounds do not overwrite louder ones,
14671 while restarting sound volume comparison with each new game frame. */
14673 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14676 loop_sound_volume[nr] = volume;
14677 loop_sound_frame[nr] = FrameCounter;
14680 PlaySoundExt(nr, volume, stereo_position, type);
14683 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14685 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14686 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14687 y < LEVELY(BY1) ? LEVELY(BY1) :
14688 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14692 static void PlayLevelSoundAction(int x, int y, int action)
14694 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14697 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14699 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14701 if (sound_effect != SND_UNDEFINED)
14702 PlayLevelSound(x, y, sound_effect);
14705 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14708 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14710 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14711 PlayLevelSound(x, y, sound_effect);
14714 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14716 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14718 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14719 PlayLevelSound(x, y, sound_effect);
14722 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14724 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14726 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14727 StopSound(sound_effect);
14730 static int getLevelMusicNr(void)
14732 if (levelset.music[level_nr] != MUS_UNDEFINED)
14733 return levelset.music[level_nr]; // from config file
14735 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14738 static void FadeLevelSounds(void)
14743 static void FadeLevelMusic(void)
14745 int music_nr = getLevelMusicNr();
14746 char *curr_music = getCurrentlyPlayingMusicFilename();
14747 char *next_music = getMusicInfoEntryFilename(music_nr);
14749 if (!strEqual(curr_music, next_music))
14753 void FadeLevelSoundsAndMusic(void)
14759 static void PlayLevelMusic(void)
14761 int music_nr = getLevelMusicNr();
14762 char *curr_music = getCurrentlyPlayingMusicFilename();
14763 char *next_music = getMusicInfoEntryFilename(music_nr);
14765 if (!strEqual(curr_music, next_music))
14766 PlayMusicLoop(music_nr);
14769 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14771 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14772 int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
14773 int x = xx - 1 - offset;
14774 int y = yy - 1 - offset;
14779 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14783 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14787 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14791 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14795 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14799 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14803 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14806 case SOUND_android_clone:
14807 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14810 case SOUND_android_move:
14811 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14815 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14819 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14823 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14826 case SOUND_eater_eat:
14827 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14831 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14834 case SOUND_collect:
14835 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14838 case SOUND_diamond:
14839 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14843 // !!! CHECK THIS !!!
14845 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14847 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14851 case SOUND_wonderfall:
14852 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14856 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14860 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14864 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14868 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14872 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14876 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14880 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14884 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14887 case SOUND_exit_open:
14888 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14891 case SOUND_exit_leave:
14892 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14895 case SOUND_dynamite:
14896 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14900 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14904 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14908 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14912 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14916 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
14920 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
14924 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
14929 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
14931 int element = map_element_SP_to_RND(element_sp);
14932 int action = map_action_SP_to_RND(action_sp);
14933 int offset = (setup.sp_show_border_elements ? 0 : 1);
14934 int x = xx - offset;
14935 int y = yy - offset;
14937 PlayLevelSoundElementAction(x, y, element, action);
14940 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
14942 int element = map_element_MM_to_RND(element_mm);
14943 int action = map_action_MM_to_RND(action_mm);
14945 int x = xx - offset;
14946 int y = yy - offset;
14948 if (!IS_MM_ELEMENT(element))
14949 element = EL_MM_DEFAULT;
14951 PlayLevelSoundElementAction(x, y, element, action);
14954 void PlaySound_MM(int sound_mm)
14956 int sound = map_sound_MM_to_RND(sound_mm);
14958 if (sound == SND_UNDEFINED)
14964 void PlaySoundLoop_MM(int sound_mm)
14966 int sound = map_sound_MM_to_RND(sound_mm);
14968 if (sound == SND_UNDEFINED)
14971 PlaySoundLoop(sound);
14974 void StopSound_MM(int sound_mm)
14976 int sound = map_sound_MM_to_RND(sound_mm);
14978 if (sound == SND_UNDEFINED)
14984 void RaiseScore(int value)
14986 game.score += value;
14988 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
14990 DisplayGameControlValues();
14993 void RaiseScoreElement(int element)
14998 case EL_BD_DIAMOND:
14999 case EL_EMERALD_YELLOW:
15000 case EL_EMERALD_RED:
15001 case EL_EMERALD_PURPLE:
15002 case EL_SP_INFOTRON:
15003 RaiseScore(level.score[SC_EMERALD]);
15006 RaiseScore(level.score[SC_DIAMOND]);
15009 RaiseScore(level.score[SC_CRYSTAL]);
15012 RaiseScore(level.score[SC_PEARL]);
15015 case EL_BD_BUTTERFLY:
15016 case EL_SP_ELECTRON:
15017 RaiseScore(level.score[SC_BUG]);
15020 case EL_BD_FIREFLY:
15021 case EL_SP_SNIKSNAK:
15022 RaiseScore(level.score[SC_SPACESHIP]);
15025 case EL_DARK_YAMYAM:
15026 RaiseScore(level.score[SC_YAMYAM]);
15029 RaiseScore(level.score[SC_ROBOT]);
15032 RaiseScore(level.score[SC_PACMAN]);
15035 RaiseScore(level.score[SC_NUT]);
15038 case EL_EM_DYNAMITE:
15039 case EL_SP_DISK_RED:
15040 case EL_DYNABOMB_INCREASE_NUMBER:
15041 case EL_DYNABOMB_INCREASE_SIZE:
15042 case EL_DYNABOMB_INCREASE_POWER:
15043 RaiseScore(level.score[SC_DYNAMITE]);
15045 case EL_SHIELD_NORMAL:
15046 case EL_SHIELD_DEADLY:
15047 RaiseScore(level.score[SC_SHIELD]);
15049 case EL_EXTRA_TIME:
15050 RaiseScore(level.extra_time_score);
15064 case EL_DC_KEY_WHITE:
15065 RaiseScore(level.score[SC_KEY]);
15068 RaiseScore(element_info[element].collect_score);
15073 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15075 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15077 // closing door required in case of envelope style request dialogs
15080 // prevent short reactivation of overlay buttons while closing door
15081 SetOverlayActive(FALSE);
15083 CloseDoor(DOOR_CLOSE_1);
15086 if (network.enabled)
15087 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15091 FadeSkipNextFadeIn();
15093 SetGameStatus(GAME_MODE_MAIN);
15098 else // continue playing the game
15100 if (tape.playing && tape.deactivate_display)
15101 TapeDeactivateDisplayOff(TRUE);
15103 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15105 if (tape.playing && tape.deactivate_display)
15106 TapeDeactivateDisplayOn();
15110 void RequestQuitGame(boolean ask_if_really_quit)
15112 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15113 boolean skip_request = game.all_players_gone || quick_quit;
15115 RequestQuitGameExt(skip_request, quick_quit,
15116 "Do you really want to quit the game?");
15119 void RequestRestartGame(char *message)
15121 game.restart_game_message = NULL;
15123 boolean has_started_game = hasStartedNetworkGame();
15124 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15126 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15128 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15132 SetGameStatus(GAME_MODE_MAIN);
15138 void CheckGameOver(void)
15140 static boolean last_game_over = FALSE;
15141 static int game_over_delay = 0;
15142 int game_over_delay_value = 50;
15143 boolean game_over = checkGameFailed();
15145 // do not handle game over if request dialog is already active
15146 if (game.request_active)
15149 // do not ask to play again if game was never actually played
15150 if (!game.GamePlayed)
15155 last_game_over = FALSE;
15156 game_over_delay = game_over_delay_value;
15161 if (game_over_delay > 0)
15168 if (last_game_over != game_over)
15169 game.restart_game_message = (hasStartedNetworkGame() ?
15170 "Game over! Play it again?" :
15173 last_game_over = game_over;
15176 boolean checkGameSolved(void)
15178 // set for all game engines if level was solved
15179 return game.LevelSolved_GameEnd;
15182 boolean checkGameFailed(void)
15184 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15185 return (game_em.game_over && !game_em.level_solved);
15186 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15187 return (game_sp.game_over && !game_sp.level_solved);
15188 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15189 return (game_mm.game_over && !game_mm.level_solved);
15190 else // GAME_ENGINE_TYPE_RND
15191 return (game.GameOver && !game.LevelSolved);
15194 boolean checkGameEnded(void)
15196 return (checkGameSolved() || checkGameFailed());
15200 // ----------------------------------------------------------------------------
15201 // random generator functions
15202 // ----------------------------------------------------------------------------
15204 unsigned int InitEngineRandom_RND(int seed)
15206 game.num_random_calls = 0;
15208 return InitEngineRandom(seed);
15211 unsigned int RND(int max)
15215 game.num_random_calls++;
15217 return GetEngineRandom(max);
15224 // ----------------------------------------------------------------------------
15225 // game engine snapshot handling functions
15226 // ----------------------------------------------------------------------------
15228 struct EngineSnapshotInfo
15230 // runtime values for custom element collect score
15231 int collect_score[NUM_CUSTOM_ELEMENTS];
15233 // runtime values for group element choice position
15234 int choice_pos[NUM_GROUP_ELEMENTS];
15236 // runtime values for belt position animations
15237 int belt_graphic[4][NUM_BELT_PARTS];
15238 int belt_anim_mode[4][NUM_BELT_PARTS];
15241 static struct EngineSnapshotInfo engine_snapshot_rnd;
15242 static char *snapshot_level_identifier = NULL;
15243 static int snapshot_level_nr = -1;
15245 static void SaveEngineSnapshotValues_RND(void)
15247 static int belt_base_active_element[4] =
15249 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15250 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15251 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15252 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15256 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15258 int element = EL_CUSTOM_START + i;
15260 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15263 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15265 int element = EL_GROUP_START + i;
15267 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15270 for (i = 0; i < 4; i++)
15272 for (j = 0; j < NUM_BELT_PARTS; j++)
15274 int element = belt_base_active_element[i] + j;
15275 int graphic = el2img(element);
15276 int anim_mode = graphic_info[graphic].anim_mode;
15278 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15279 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15284 static void LoadEngineSnapshotValues_RND(void)
15286 unsigned int num_random_calls = game.num_random_calls;
15289 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15291 int element = EL_CUSTOM_START + i;
15293 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15296 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15298 int element = EL_GROUP_START + i;
15300 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15303 for (i = 0; i < 4; i++)
15305 for (j = 0; j < NUM_BELT_PARTS; j++)
15307 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15308 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15310 graphic_info[graphic].anim_mode = anim_mode;
15314 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15316 InitRND(tape.random_seed);
15317 for (i = 0; i < num_random_calls; i++)
15321 if (game.num_random_calls != num_random_calls)
15323 Error(ERR_INFO, "number of random calls out of sync");
15324 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15325 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15326 Error(ERR_EXIT, "this should not happen -- please debug");
15330 void FreeEngineSnapshotSingle(void)
15332 FreeSnapshotSingle();
15334 setString(&snapshot_level_identifier, NULL);
15335 snapshot_level_nr = -1;
15338 void FreeEngineSnapshotList(void)
15340 FreeSnapshotList();
15343 static ListNode *SaveEngineSnapshotBuffers(void)
15345 ListNode *buffers = NULL;
15347 // copy some special values to a structure better suited for the snapshot
15349 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15350 SaveEngineSnapshotValues_RND();
15351 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15352 SaveEngineSnapshotValues_EM();
15353 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15354 SaveEngineSnapshotValues_SP(&buffers);
15355 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15356 SaveEngineSnapshotValues_MM(&buffers);
15358 // save values stored in special snapshot structure
15360 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15361 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15362 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15363 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15364 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15365 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15366 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15367 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15369 // save further RND engine values
15371 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15372 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15373 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15375 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15376 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15377 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15378 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15379 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15381 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15382 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15383 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15385 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15387 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15388 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15390 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15391 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15392 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15393 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15394 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15395 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15396 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15397 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15398 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15399 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15400 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15401 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15402 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15403 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15404 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15405 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15406 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15407 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15409 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15410 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15412 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15413 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15414 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15416 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15417 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15419 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15420 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15421 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15422 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15423 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15425 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15426 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15429 ListNode *node = engine_snapshot_list_rnd;
15432 while (node != NULL)
15434 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15439 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15445 void SaveEngineSnapshotSingle(void)
15447 ListNode *buffers = SaveEngineSnapshotBuffers();
15449 // finally save all snapshot buffers to single snapshot
15450 SaveSnapshotSingle(buffers);
15452 // save level identification information
15453 setString(&snapshot_level_identifier, leveldir_current->identifier);
15454 snapshot_level_nr = level_nr;
15457 boolean CheckSaveEngineSnapshotToList(void)
15459 boolean save_snapshot =
15460 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15461 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15462 game.snapshot.changed_action) ||
15463 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15464 game.snapshot.collected_item));
15466 game.snapshot.changed_action = FALSE;
15467 game.snapshot.collected_item = FALSE;
15468 game.snapshot.save_snapshot = save_snapshot;
15470 return save_snapshot;
15473 void SaveEngineSnapshotToList(void)
15475 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15479 ListNode *buffers = SaveEngineSnapshotBuffers();
15481 // finally save all snapshot buffers to snapshot list
15482 SaveSnapshotToList(buffers);
15485 void SaveEngineSnapshotToListInitial(void)
15487 FreeEngineSnapshotList();
15489 SaveEngineSnapshotToList();
15492 static void LoadEngineSnapshotValues(void)
15494 // restore special values from snapshot structure
15496 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15497 LoadEngineSnapshotValues_RND();
15498 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15499 LoadEngineSnapshotValues_EM();
15500 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15501 LoadEngineSnapshotValues_SP();
15502 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15503 LoadEngineSnapshotValues_MM();
15506 void LoadEngineSnapshotSingle(void)
15508 LoadSnapshotSingle();
15510 LoadEngineSnapshotValues();
15513 static void LoadEngineSnapshot_Undo(int steps)
15515 LoadSnapshotFromList_Older(steps);
15517 LoadEngineSnapshotValues();
15520 static void LoadEngineSnapshot_Redo(int steps)
15522 LoadSnapshotFromList_Newer(steps);
15524 LoadEngineSnapshotValues();
15527 boolean CheckEngineSnapshotSingle(void)
15529 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15530 snapshot_level_nr == level_nr);
15533 boolean CheckEngineSnapshotList(void)
15535 return CheckSnapshotList();
15539 // ---------- new game button stuff -------------------------------------------
15546 boolean *setup_value;
15547 boolean allowed_on_tape;
15548 boolean is_touch_button;
15550 } gamebutton_info[NUM_GAME_BUTTONS] =
15553 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15554 GAME_CTRL_ID_STOP, NULL,
15555 TRUE, FALSE, "stop game"
15558 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15559 GAME_CTRL_ID_PAUSE, NULL,
15560 TRUE, FALSE, "pause game"
15563 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15564 GAME_CTRL_ID_PLAY, NULL,
15565 TRUE, FALSE, "play game"
15568 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15569 GAME_CTRL_ID_UNDO, NULL,
15570 TRUE, FALSE, "undo step"
15573 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15574 GAME_CTRL_ID_REDO, NULL,
15575 TRUE, FALSE, "redo step"
15578 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15579 GAME_CTRL_ID_SAVE, NULL,
15580 TRUE, FALSE, "save game"
15583 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15584 GAME_CTRL_ID_PAUSE2, NULL,
15585 TRUE, FALSE, "pause game"
15588 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15589 GAME_CTRL_ID_LOAD, NULL,
15590 TRUE, FALSE, "load game"
15593 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15594 GAME_CTRL_ID_PANEL_STOP, NULL,
15595 FALSE, FALSE, "stop game"
15598 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15599 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15600 FALSE, FALSE, "pause game"
15603 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15604 GAME_CTRL_ID_PANEL_PLAY, NULL,
15605 FALSE, FALSE, "play game"
15608 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15609 GAME_CTRL_ID_TOUCH_STOP, NULL,
15610 FALSE, TRUE, "stop game"
15613 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15614 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15615 FALSE, TRUE, "pause game"
15618 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15619 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15620 TRUE, FALSE, "background music on/off"
15623 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15624 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15625 TRUE, FALSE, "sound loops on/off"
15628 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15629 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15630 TRUE, FALSE, "normal sounds on/off"
15633 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15634 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15635 FALSE, FALSE, "background music on/off"
15638 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15639 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15640 FALSE, FALSE, "sound loops on/off"
15643 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15644 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15645 FALSE, FALSE, "normal sounds on/off"
15649 void CreateGameButtons(void)
15653 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15655 int graphic = gamebutton_info[i].graphic;
15656 struct GraphicInfo *gfx = &graphic_info[graphic];
15657 struct XY *pos = gamebutton_info[i].pos;
15658 struct GadgetInfo *gi;
15661 unsigned int event_mask;
15662 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15663 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15664 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15665 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15666 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15667 int gd_x = gfx->src_x;
15668 int gd_y = gfx->src_y;
15669 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15670 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15671 int gd_xa = gfx->src_x + gfx->active_xoffset;
15672 int gd_ya = gfx->src_y + gfx->active_yoffset;
15673 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15674 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15675 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15676 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15679 if (gfx->bitmap == NULL)
15681 game_gadget[id] = NULL;
15686 if (id == GAME_CTRL_ID_STOP ||
15687 id == GAME_CTRL_ID_PANEL_STOP ||
15688 id == GAME_CTRL_ID_TOUCH_STOP ||
15689 id == GAME_CTRL_ID_PLAY ||
15690 id == GAME_CTRL_ID_PANEL_PLAY ||
15691 id == GAME_CTRL_ID_SAVE ||
15692 id == GAME_CTRL_ID_LOAD)
15694 button_type = GD_TYPE_NORMAL_BUTTON;
15696 event_mask = GD_EVENT_RELEASED;
15698 else if (id == GAME_CTRL_ID_UNDO ||
15699 id == GAME_CTRL_ID_REDO)
15701 button_type = GD_TYPE_NORMAL_BUTTON;
15703 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15707 button_type = GD_TYPE_CHECK_BUTTON;
15708 checked = (gamebutton_info[i].setup_value != NULL ?
15709 *gamebutton_info[i].setup_value : FALSE);
15710 event_mask = GD_EVENT_PRESSED;
15713 gi = CreateGadget(GDI_CUSTOM_ID, id,
15714 GDI_IMAGE_ID, graphic,
15715 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15718 GDI_WIDTH, gfx->width,
15719 GDI_HEIGHT, gfx->height,
15720 GDI_TYPE, button_type,
15721 GDI_STATE, GD_BUTTON_UNPRESSED,
15722 GDI_CHECKED, checked,
15723 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15724 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15725 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15726 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15727 GDI_DIRECT_DRAW, FALSE,
15728 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15729 GDI_EVENT_MASK, event_mask,
15730 GDI_CALLBACK_ACTION, HandleGameButtons,
15734 Error(ERR_EXIT, "cannot create gadget");
15736 game_gadget[id] = gi;
15740 void FreeGameButtons(void)
15744 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15745 FreeGadget(game_gadget[i]);
15748 static void UnmapGameButtonsAtSamePosition(int id)
15752 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15754 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15755 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15756 UnmapGadget(game_gadget[i]);
15759 static void UnmapGameButtonsAtSamePosition_All(void)
15761 if (setup.show_snapshot_buttons)
15763 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15764 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15765 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15769 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15770 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15771 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15773 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15774 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15775 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15779 static void MapGameButtonsAtSamePosition(int id)
15783 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15785 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15786 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15787 MapGadget(game_gadget[i]);
15789 UnmapGameButtonsAtSamePosition_All();
15792 void MapUndoRedoButtons(void)
15794 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15795 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15797 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15798 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15801 void UnmapUndoRedoButtons(void)
15803 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15804 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15806 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15807 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15810 void ModifyPauseButtons(void)
15814 GAME_CTRL_ID_PAUSE,
15815 GAME_CTRL_ID_PAUSE2,
15816 GAME_CTRL_ID_PANEL_PAUSE,
15817 GAME_CTRL_ID_TOUCH_PAUSE,
15822 for (i = 0; ids[i] > -1; i++)
15823 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15826 static void MapGameButtonsExt(boolean on_tape)
15830 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15831 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15832 i != GAME_CTRL_ID_UNDO &&
15833 i != GAME_CTRL_ID_REDO)
15834 MapGadget(game_gadget[i]);
15836 UnmapGameButtonsAtSamePosition_All();
15838 RedrawGameButtons();
15841 static void UnmapGameButtonsExt(boolean on_tape)
15845 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15846 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15847 UnmapGadget(game_gadget[i]);
15850 static void RedrawGameButtonsExt(boolean on_tape)
15854 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15855 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15856 RedrawGadget(game_gadget[i]);
15859 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15864 gi->checked = state;
15867 static void RedrawSoundButtonGadget(int id)
15869 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15870 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15871 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15872 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15873 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15874 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15877 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15878 RedrawGadget(game_gadget[id2]);
15881 void MapGameButtons(void)
15883 MapGameButtonsExt(FALSE);
15886 void UnmapGameButtons(void)
15888 UnmapGameButtonsExt(FALSE);
15891 void RedrawGameButtons(void)
15893 RedrawGameButtonsExt(FALSE);
15896 void MapGameButtonsOnTape(void)
15898 MapGameButtonsExt(TRUE);
15901 void UnmapGameButtonsOnTape(void)
15903 UnmapGameButtonsExt(TRUE);
15906 void RedrawGameButtonsOnTape(void)
15908 RedrawGameButtonsExt(TRUE);
15911 static void GameUndoRedoExt(void)
15913 ClearPlayerAction();
15915 tape.pausing = TRUE;
15918 UpdateAndDisplayGameControlValues();
15920 DrawCompleteVideoDisplay();
15921 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
15922 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
15923 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
15928 static void GameUndo(int steps)
15930 if (!CheckEngineSnapshotList())
15933 LoadEngineSnapshot_Undo(steps);
15938 static void GameRedo(int steps)
15940 if (!CheckEngineSnapshotList())
15943 LoadEngineSnapshot_Redo(steps);
15948 static void HandleGameButtonsExt(int id, int button)
15950 static boolean game_undo_executed = FALSE;
15951 int steps = BUTTON_STEPSIZE(button);
15952 boolean handle_game_buttons =
15953 (game_status == GAME_MODE_PLAYING ||
15954 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
15956 if (!handle_game_buttons)
15961 case GAME_CTRL_ID_STOP:
15962 case GAME_CTRL_ID_PANEL_STOP:
15963 case GAME_CTRL_ID_TOUCH_STOP:
15964 if (game_status == GAME_MODE_MAIN)
15970 RequestQuitGame(TRUE);
15974 case GAME_CTRL_ID_PAUSE:
15975 case GAME_CTRL_ID_PAUSE2:
15976 case GAME_CTRL_ID_PANEL_PAUSE:
15977 case GAME_CTRL_ID_TOUCH_PAUSE:
15978 if (network.enabled && game_status == GAME_MODE_PLAYING)
15981 SendToServer_ContinuePlaying();
15983 SendToServer_PausePlaying();
15986 TapeTogglePause(TAPE_TOGGLE_MANUAL);
15988 game_undo_executed = FALSE;
15992 case GAME_CTRL_ID_PLAY:
15993 case GAME_CTRL_ID_PANEL_PLAY:
15994 if (game_status == GAME_MODE_MAIN)
15996 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15998 else if (tape.pausing)
16000 if (network.enabled)
16001 SendToServer_ContinuePlaying();
16003 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16007 case GAME_CTRL_ID_UNDO:
16008 // Important: When using "save snapshot when collecting an item" mode,
16009 // load last (current) snapshot for first "undo" after pressing "pause"
16010 // (else the last-but-one snapshot would be loaded, because the snapshot
16011 // pointer already points to the last snapshot when pressing "pause",
16012 // which is fine for "every step/move" mode, but not for "every collect")
16013 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16014 !game_undo_executed)
16017 game_undo_executed = TRUE;
16022 case GAME_CTRL_ID_REDO:
16026 case GAME_CTRL_ID_SAVE:
16030 case GAME_CTRL_ID_LOAD:
16034 case SOUND_CTRL_ID_MUSIC:
16035 case SOUND_CTRL_ID_PANEL_MUSIC:
16036 if (setup.sound_music)
16038 setup.sound_music = FALSE;
16042 else if (audio.music_available)
16044 setup.sound = setup.sound_music = TRUE;
16046 SetAudioMode(setup.sound);
16048 if (game_status == GAME_MODE_PLAYING)
16052 RedrawSoundButtonGadget(id);
16056 case SOUND_CTRL_ID_LOOPS:
16057 case SOUND_CTRL_ID_PANEL_LOOPS:
16058 if (setup.sound_loops)
16059 setup.sound_loops = FALSE;
16060 else if (audio.loops_available)
16062 setup.sound = setup.sound_loops = TRUE;
16064 SetAudioMode(setup.sound);
16067 RedrawSoundButtonGadget(id);
16071 case SOUND_CTRL_ID_SIMPLE:
16072 case SOUND_CTRL_ID_PANEL_SIMPLE:
16073 if (setup.sound_simple)
16074 setup.sound_simple = FALSE;
16075 else if (audio.sound_available)
16077 setup.sound = setup.sound_simple = TRUE;
16079 SetAudioMode(setup.sound);
16082 RedrawSoundButtonGadget(id);
16091 static void HandleGameButtons(struct GadgetInfo *gi)
16093 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16096 void HandleSoundButtonKeys(Key key)
16098 if (key == setup.shortcut.sound_simple)
16099 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16100 else if (key == setup.shortcut.sound_loops)
16101 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16102 else if (key == setup.shortcut.sound_music)
16103 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);