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 flags for handling game actions --------------------
3285 // set flags for game actions to default values
3286 game.use_key_actions = TRUE;
3287 game.use_mouse_actions = FALSE;
3289 // when using Mirror Magic game engine, handle mouse events only
3290 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3292 game.use_key_actions = FALSE;
3293 game.use_mouse_actions = TRUE;
3296 // check for custom elements with mouse click events
3297 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3299 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3301 int element = EL_CUSTOM_START + i;
3303 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3304 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3305 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3306 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3307 game.use_mouse_actions = TRUE;
3312 static int get_num_special_action(int element, int action_first,
3315 int num_special_action = 0;
3318 for (i = action_first; i <= action_last; i++)
3320 boolean found = FALSE;
3322 for (j = 0; j < NUM_DIRECTIONS; j++)
3323 if (el_act_dir2img(element, i, j) !=
3324 el_act_dir2img(element, ACTION_DEFAULT, j))
3328 num_special_action++;
3333 return num_special_action;
3337 // ============================================================================
3339 // ----------------------------------------------------------------------------
3340 // initialize and start new game
3341 // ============================================================================
3343 #if DEBUG_INIT_PLAYER
3344 static void DebugPrintPlayerStatus(char *message)
3351 printf("%s:\n", message);
3353 for (i = 0; i < MAX_PLAYERS; i++)
3355 struct PlayerInfo *player = &stored_player[i];
3357 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3361 player->connected_locally,
3362 player->connected_network,
3365 if (local_player == player)
3366 printf(" (local player)");
3375 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3376 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3377 int fade_mask = REDRAW_FIELD;
3379 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3380 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3381 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3382 int initial_move_dir = MV_DOWN;
3385 // required here to update video display before fading (FIX THIS)
3386 DrawMaskedBorder(REDRAW_DOOR_2);
3388 if (!game.restart_level)
3389 CloseDoor(DOOR_CLOSE_1);
3391 SetGameStatus(GAME_MODE_PLAYING);
3393 if (level_editor_test_game)
3394 FadeSkipNextFadeOut();
3396 FadeSetEnterScreen();
3399 fade_mask = REDRAW_ALL;
3401 FadeLevelSoundsAndMusic();
3403 ExpireSoundLoops(TRUE);
3407 if (level_editor_test_game)
3408 FadeSkipNextFadeIn();
3410 // needed if different viewport properties defined for playing
3411 ChangeViewportPropertiesIfNeeded();
3415 DrawCompleteVideoDisplay();
3417 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3420 InitGameControlValues();
3422 // initialize tape actions from game when recording tape
3425 tape.use_key_actions = game.use_key_actions;
3426 tape.use_mouse_actions = game.use_mouse_actions;
3429 // don't play tapes over network
3430 network_playing = (network.enabled && !tape.playing);
3432 for (i = 0; i < MAX_PLAYERS; i++)
3434 struct PlayerInfo *player = &stored_player[i];
3436 player->index_nr = i;
3437 player->index_bit = (1 << i);
3438 player->element_nr = EL_PLAYER_1 + i;
3440 player->present = FALSE;
3441 player->active = FALSE;
3442 player->mapped = FALSE;
3444 player->killed = FALSE;
3445 player->reanimated = FALSE;
3446 player->buried = FALSE;
3449 player->effective_action = 0;
3450 player->programmed_action = 0;
3451 player->snap_action = 0;
3453 player->mouse_action.lx = 0;
3454 player->mouse_action.ly = 0;
3455 player->mouse_action.button = 0;
3456 player->mouse_action.button_hint = 0;
3458 player->effective_mouse_action.lx = 0;
3459 player->effective_mouse_action.ly = 0;
3460 player->effective_mouse_action.button = 0;
3461 player->effective_mouse_action.button_hint = 0;
3463 for (j = 0; j < MAX_NUM_KEYS; j++)
3464 player->key[j] = FALSE;
3466 player->num_white_keys = 0;
3468 player->dynabomb_count = 0;
3469 player->dynabomb_size = 1;
3470 player->dynabombs_left = 0;
3471 player->dynabomb_xl = FALSE;
3473 player->MovDir = initial_move_dir;
3476 player->GfxDir = initial_move_dir;
3477 player->GfxAction = ACTION_DEFAULT;
3479 player->StepFrame = 0;
3481 player->initial_element = player->element_nr;
3482 player->artwork_element =
3483 (level.use_artwork_element[i] ? level.artwork_element[i] :
3484 player->element_nr);
3485 player->use_murphy = FALSE;
3487 player->block_last_field = FALSE; // initialized in InitPlayerField()
3488 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3490 player->gravity = level.initial_player_gravity[i];
3492 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3494 player->actual_frame_counter = 0;
3496 player->step_counter = 0;
3498 player->last_move_dir = initial_move_dir;
3500 player->is_active = FALSE;
3502 player->is_waiting = FALSE;
3503 player->is_moving = FALSE;
3504 player->is_auto_moving = FALSE;
3505 player->is_digging = FALSE;
3506 player->is_snapping = FALSE;
3507 player->is_collecting = FALSE;
3508 player->is_pushing = FALSE;
3509 player->is_switching = FALSE;
3510 player->is_dropping = FALSE;
3511 player->is_dropping_pressed = FALSE;
3513 player->is_bored = FALSE;
3514 player->is_sleeping = FALSE;
3516 player->was_waiting = TRUE;
3517 player->was_moving = FALSE;
3518 player->was_snapping = FALSE;
3519 player->was_dropping = FALSE;
3521 player->force_dropping = FALSE;
3523 player->frame_counter_bored = -1;
3524 player->frame_counter_sleeping = -1;
3526 player->anim_delay_counter = 0;
3527 player->post_delay_counter = 0;
3529 player->dir_waiting = initial_move_dir;
3530 player->action_waiting = ACTION_DEFAULT;
3531 player->last_action_waiting = ACTION_DEFAULT;
3532 player->special_action_bored = ACTION_DEFAULT;
3533 player->special_action_sleeping = ACTION_DEFAULT;
3535 player->switch_x = -1;
3536 player->switch_y = -1;
3538 player->drop_x = -1;
3539 player->drop_y = -1;
3541 player->show_envelope = 0;
3543 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3545 player->push_delay = -1; // initialized when pushing starts
3546 player->push_delay_value = game.initial_push_delay_value;
3548 player->drop_delay = 0;
3549 player->drop_pressed_delay = 0;
3551 player->last_jx = -1;
3552 player->last_jy = -1;
3556 player->shield_normal_time_left = 0;
3557 player->shield_deadly_time_left = 0;
3559 player->inventory_infinite_element = EL_UNDEFINED;
3560 player->inventory_size = 0;
3562 if (level.use_initial_inventory[i])
3564 for (j = 0; j < level.initial_inventory_size[i]; j++)
3566 int element = level.initial_inventory_content[i][j];
3567 int collect_count = element_info[element].collect_count_initial;
3570 if (!IS_CUSTOM_ELEMENT(element))
3573 if (collect_count == 0)
3574 player->inventory_infinite_element = element;
3576 for (k = 0; k < collect_count; k++)
3577 if (player->inventory_size < MAX_INVENTORY_SIZE)
3578 player->inventory_element[player->inventory_size++] = element;
3582 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3583 SnapField(player, 0, 0);
3585 map_player_action[i] = i;
3588 network_player_action_received = FALSE;
3590 // initial null action
3591 if (network_playing)
3592 SendToServer_MovePlayer(MV_NONE);
3597 TimeLeft = level.time;
3600 ScreenMovDir = MV_NONE;
3604 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3606 game.robot_wheel_x = -1;
3607 game.robot_wheel_y = -1;
3612 game.all_players_gone = FALSE;
3614 game.LevelSolved = FALSE;
3615 game.GameOver = FALSE;
3617 game.GamePlayed = !tape.playing;
3619 game.LevelSolved_GameWon = FALSE;
3620 game.LevelSolved_GameEnd = FALSE;
3621 game.LevelSolved_SaveTape = FALSE;
3622 game.LevelSolved_SaveScore = FALSE;
3624 game.LevelSolved_CountingTime = 0;
3625 game.LevelSolved_CountingScore = 0;
3626 game.LevelSolved_CountingHealth = 0;
3628 game.panel.active = TRUE;
3630 game.no_time_limit = (level.time == 0);
3632 game.yamyam_content_nr = 0;
3633 game.robot_wheel_active = FALSE;
3634 game.magic_wall_active = FALSE;
3635 game.magic_wall_time_left = 0;
3636 game.light_time_left = 0;
3637 game.timegate_time_left = 0;
3638 game.switchgate_pos = 0;
3639 game.wind_direction = level.wind_direction_initial;
3642 game.score_final = 0;
3644 game.health = MAX_HEALTH;
3645 game.health_final = MAX_HEALTH;
3647 game.gems_still_needed = level.gems_needed;
3648 game.sokoban_fields_still_needed = 0;
3649 game.sokoban_objects_still_needed = 0;
3650 game.lights_still_needed = 0;
3651 game.players_still_needed = 0;
3652 game.friends_still_needed = 0;
3654 game.lenses_time_left = 0;
3655 game.magnify_time_left = 0;
3657 game.ball_active = level.ball_active_initial;
3658 game.ball_content_nr = 0;
3660 game.explosions_delayed = TRUE;
3662 game.envelope_active = FALSE;
3664 for (i = 0; i < NUM_BELTS; i++)
3666 game.belt_dir[i] = MV_NONE;
3667 game.belt_dir_nr[i] = 3; // not moving, next moving left
3670 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3671 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3673 #if DEBUG_INIT_PLAYER
3674 DebugPrintPlayerStatus("Player status at level initialization");
3677 SCAN_PLAYFIELD(x, y)
3679 Feld[x][y] = Last[x][y] = level.field[x][y];
3680 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3681 ChangeDelay[x][y] = 0;
3682 ChangePage[x][y] = -1;
3683 CustomValue[x][y] = 0; // initialized in InitField()
3684 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3686 WasJustMoving[x][y] = 0;
3687 WasJustFalling[x][y] = 0;
3688 CheckCollision[x][y] = 0;
3689 CheckImpact[x][y] = 0;
3691 Pushed[x][y] = FALSE;
3693 ChangeCount[x][y] = 0;
3694 ChangeEvent[x][y] = -1;
3696 ExplodePhase[x][y] = 0;
3697 ExplodeDelay[x][y] = 0;
3698 ExplodeField[x][y] = EX_TYPE_NONE;
3700 RunnerVisit[x][y] = 0;
3701 PlayerVisit[x][y] = 0;
3704 GfxRandom[x][y] = INIT_GFX_RANDOM();
3705 GfxElement[x][y] = EL_UNDEFINED;
3706 GfxAction[x][y] = ACTION_DEFAULT;
3707 GfxDir[x][y] = MV_NONE;
3708 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3711 SCAN_PLAYFIELD(x, y)
3713 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3715 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3717 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3720 InitField(x, y, TRUE);
3722 ResetGfxAnimation(x, y);
3727 for (i = 0; i < MAX_PLAYERS; i++)
3729 struct PlayerInfo *player = &stored_player[i];
3731 // set number of special actions for bored and sleeping animation
3732 player->num_special_action_bored =
3733 get_num_special_action(player->artwork_element,
3734 ACTION_BORING_1, ACTION_BORING_LAST);
3735 player->num_special_action_sleeping =
3736 get_num_special_action(player->artwork_element,
3737 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3740 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3741 emulate_sb ? EMU_SOKOBAN :
3742 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3744 // initialize type of slippery elements
3745 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3747 if (!IS_CUSTOM_ELEMENT(i))
3749 // default: elements slip down either to the left or right randomly
3750 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3752 // SP style elements prefer to slip down on the left side
3753 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3754 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3756 // BD style elements prefer to slip down on the left side
3757 if (game.emulation == EMU_BOULDERDASH)
3758 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3762 // initialize explosion and ignition delay
3763 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3765 if (!IS_CUSTOM_ELEMENT(i))
3768 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3769 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3770 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3771 int last_phase = (num_phase + 1) * delay;
3772 int half_phase = (num_phase / 2) * delay;
3774 element_info[i].explosion_delay = last_phase - 1;
3775 element_info[i].ignition_delay = half_phase;
3777 if (i == EL_BLACK_ORB)
3778 element_info[i].ignition_delay = 1;
3782 // correct non-moving belts to start moving left
3783 for (i = 0; i < NUM_BELTS; i++)
3784 if (game.belt_dir[i] == MV_NONE)
3785 game.belt_dir_nr[i] = 3; // not moving, next moving left
3787 #if USE_NEW_PLAYER_ASSIGNMENTS
3788 // use preferred player also in local single-player mode
3789 if (!network.enabled && !game.team_mode)
3791 int new_index_nr = setup.network_player_nr;
3793 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3795 for (i = 0; i < MAX_PLAYERS; i++)
3796 stored_player[i].connected_locally = FALSE;
3798 stored_player[new_index_nr].connected_locally = TRUE;
3802 for (i = 0; i < MAX_PLAYERS; i++)
3804 stored_player[i].connected = FALSE;
3806 // in network game mode, the local player might not be the first player
3807 if (stored_player[i].connected_locally)
3808 local_player = &stored_player[i];
3811 if (!network.enabled)
3812 local_player->connected = TRUE;
3816 for (i = 0; i < MAX_PLAYERS; i++)
3817 stored_player[i].connected = tape.player_participates[i];
3819 else if (network.enabled)
3821 // add team mode players connected over the network (needed for correct
3822 // assignment of player figures from level to locally playing players)
3824 for (i = 0; i < MAX_PLAYERS; i++)
3825 if (stored_player[i].connected_network)
3826 stored_player[i].connected = TRUE;
3828 else if (game.team_mode)
3830 // try to guess locally connected team mode players (needed for correct
3831 // assignment of player figures from level to locally playing players)
3833 for (i = 0; i < MAX_PLAYERS; i++)
3834 if (setup.input[i].use_joystick ||
3835 setup.input[i].key.left != KSYM_UNDEFINED)
3836 stored_player[i].connected = TRUE;
3839 #if DEBUG_INIT_PLAYER
3840 DebugPrintPlayerStatus("Player status after level initialization");
3843 #if DEBUG_INIT_PLAYER
3845 printf("Reassigning players ...\n");
3848 // check if any connected player was not found in playfield
3849 for (i = 0; i < MAX_PLAYERS; i++)
3851 struct PlayerInfo *player = &stored_player[i];
3853 if (player->connected && !player->present)
3855 struct PlayerInfo *field_player = NULL;
3857 #if DEBUG_INIT_PLAYER
3859 printf("- looking for field player for player %d ...\n", i + 1);
3862 // assign first free player found that is present in the playfield
3864 // first try: look for unmapped playfield player that is not connected
3865 for (j = 0; j < MAX_PLAYERS; j++)
3866 if (field_player == NULL &&
3867 stored_player[j].present &&
3868 !stored_player[j].mapped &&
3869 !stored_player[j].connected)
3870 field_player = &stored_player[j];
3872 // second try: look for *any* unmapped playfield player
3873 for (j = 0; j < MAX_PLAYERS; j++)
3874 if (field_player == NULL &&
3875 stored_player[j].present &&
3876 !stored_player[j].mapped)
3877 field_player = &stored_player[j];
3879 if (field_player != NULL)
3881 int jx = field_player->jx, jy = field_player->jy;
3883 #if DEBUG_INIT_PLAYER
3885 printf("- found player %d\n", field_player->index_nr + 1);
3888 player->present = FALSE;
3889 player->active = FALSE;
3891 field_player->present = TRUE;
3892 field_player->active = TRUE;
3895 player->initial_element = field_player->initial_element;
3896 player->artwork_element = field_player->artwork_element;
3898 player->block_last_field = field_player->block_last_field;
3899 player->block_delay_adjustment = field_player->block_delay_adjustment;
3902 StorePlayer[jx][jy] = field_player->element_nr;
3904 field_player->jx = field_player->last_jx = jx;
3905 field_player->jy = field_player->last_jy = jy;
3907 if (local_player == player)
3908 local_player = field_player;
3910 map_player_action[field_player->index_nr] = i;
3912 field_player->mapped = TRUE;
3914 #if DEBUG_INIT_PLAYER
3916 printf("- map_player_action[%d] == %d\n",
3917 field_player->index_nr + 1, i + 1);
3922 if (player->connected && player->present)
3923 player->mapped = TRUE;
3926 #if DEBUG_INIT_PLAYER
3927 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
3932 // check if any connected player was not found in playfield
3933 for (i = 0; i < MAX_PLAYERS; i++)
3935 struct PlayerInfo *player = &stored_player[i];
3937 if (player->connected && !player->present)
3939 for (j = 0; j < MAX_PLAYERS; j++)
3941 struct PlayerInfo *field_player = &stored_player[j];
3942 int jx = field_player->jx, jy = field_player->jy;
3944 // assign first free player found that is present in the playfield
3945 if (field_player->present && !field_player->connected)
3947 player->present = TRUE;
3948 player->active = TRUE;
3950 field_player->present = FALSE;
3951 field_player->active = FALSE;
3953 player->initial_element = field_player->initial_element;
3954 player->artwork_element = field_player->artwork_element;
3956 player->block_last_field = field_player->block_last_field;
3957 player->block_delay_adjustment = field_player->block_delay_adjustment;
3959 StorePlayer[jx][jy] = player->element_nr;
3961 player->jx = player->last_jx = jx;
3962 player->jy = player->last_jy = jy;
3972 printf("::: local_player->present == %d\n", local_player->present);
3975 // set focus to local player for network games, else to all players
3976 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
3977 game.centered_player_nr_next = game.centered_player_nr;
3978 game.set_centered_player = FALSE;
3979 game.set_centered_player_wrap = FALSE;
3981 if (network_playing && tape.recording)
3983 // store client dependent player focus when recording network games
3984 tape.centered_player_nr_next = game.centered_player_nr_next;
3985 tape.set_centered_player = TRUE;
3990 // when playing a tape, eliminate all players who do not participate
3992 #if USE_NEW_PLAYER_ASSIGNMENTS
3994 if (!game.team_mode)
3996 for (i = 0; i < MAX_PLAYERS; i++)
3998 if (stored_player[i].active &&
3999 !tape.player_participates[map_player_action[i]])
4001 struct PlayerInfo *player = &stored_player[i];
4002 int jx = player->jx, jy = player->jy;
4004 #if DEBUG_INIT_PLAYER
4006 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
4009 player->active = FALSE;
4010 StorePlayer[jx][jy] = 0;
4011 Feld[jx][jy] = EL_EMPTY;
4018 for (i = 0; i < MAX_PLAYERS; i++)
4020 if (stored_player[i].active &&
4021 !tape.player_participates[i])
4023 struct PlayerInfo *player = &stored_player[i];
4024 int jx = player->jx, jy = player->jy;
4026 player->active = FALSE;
4027 StorePlayer[jx][jy] = 0;
4028 Feld[jx][jy] = EL_EMPTY;
4033 else if (!network.enabled && !game.team_mode) // && !tape.playing
4035 // when in single player mode, eliminate all but the local player
4037 for (i = 0; i < MAX_PLAYERS; i++)
4039 struct PlayerInfo *player = &stored_player[i];
4041 if (player->active && player != local_player)
4043 int jx = player->jx, jy = player->jy;
4045 player->active = FALSE;
4046 player->present = FALSE;
4048 StorePlayer[jx][jy] = 0;
4049 Feld[jx][jy] = EL_EMPTY;
4054 for (i = 0; i < MAX_PLAYERS; i++)
4055 if (stored_player[i].active)
4056 game.players_still_needed++;
4058 if (level.solved_by_one_player)
4059 game.players_still_needed = 1;
4061 // when recording the game, store which players take part in the game
4064 #if USE_NEW_PLAYER_ASSIGNMENTS
4065 for (i = 0; i < MAX_PLAYERS; i++)
4066 if (stored_player[i].connected)
4067 tape.player_participates[i] = TRUE;
4069 for (i = 0; i < MAX_PLAYERS; i++)
4070 if (stored_player[i].active)
4071 tape.player_participates[i] = TRUE;
4075 #if DEBUG_INIT_PLAYER
4076 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4079 if (BorderElement == EL_EMPTY)
4082 SBX_Right = lev_fieldx - SCR_FIELDX;
4084 SBY_Lower = lev_fieldy - SCR_FIELDY;
4089 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4091 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4094 if (full_lev_fieldx <= SCR_FIELDX)
4095 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4096 if (full_lev_fieldy <= SCR_FIELDY)
4097 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4099 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4101 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4104 // if local player not found, look for custom element that might create
4105 // the player (make some assumptions about the right custom element)
4106 if (!local_player->present)
4108 int start_x = 0, start_y = 0;
4109 int found_rating = 0;
4110 int found_element = EL_UNDEFINED;
4111 int player_nr = local_player->index_nr;
4113 SCAN_PLAYFIELD(x, y)
4115 int element = Feld[x][y];
4120 if (level.use_start_element[player_nr] &&
4121 level.start_element[player_nr] == element &&
4128 found_element = element;
4131 if (!IS_CUSTOM_ELEMENT(element))
4134 if (CAN_CHANGE(element))
4136 for (i = 0; i < element_info[element].num_change_pages; i++)
4138 // check for player created from custom element as single target
4139 content = element_info[element].change_page[i].target_element;
4140 is_player = ELEM_IS_PLAYER(content);
4142 if (is_player && (found_rating < 3 ||
4143 (found_rating == 3 && element < found_element)))
4149 found_element = element;
4154 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4156 // check for player created from custom element as explosion content
4157 content = element_info[element].content.e[xx][yy];
4158 is_player = ELEM_IS_PLAYER(content);
4160 if (is_player && (found_rating < 2 ||
4161 (found_rating == 2 && element < found_element)))
4163 start_x = x + xx - 1;
4164 start_y = y + yy - 1;
4167 found_element = element;
4170 if (!CAN_CHANGE(element))
4173 for (i = 0; i < element_info[element].num_change_pages; i++)
4175 // check for player created from custom element as extended target
4177 element_info[element].change_page[i].target_content.e[xx][yy];
4179 is_player = ELEM_IS_PLAYER(content);
4181 if (is_player && (found_rating < 1 ||
4182 (found_rating == 1 && element < found_element)))
4184 start_x = x + xx - 1;
4185 start_y = y + yy - 1;
4188 found_element = element;
4194 scroll_x = SCROLL_POSITION_X(start_x);
4195 scroll_y = SCROLL_POSITION_Y(start_y);
4199 scroll_x = SCROLL_POSITION_X(local_player->jx);
4200 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4203 // !!! FIX THIS (START) !!!
4204 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4206 InitGameEngine_EM();
4208 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4210 InitGameEngine_SP();
4212 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4214 InitGameEngine_MM();
4218 DrawLevel(REDRAW_FIELD);
4221 // after drawing the level, correct some elements
4222 if (game.timegate_time_left == 0)
4223 CloseAllOpenTimegates();
4226 // blit playfield from scroll buffer to normal back buffer for fading in
4227 BlitScreenToBitmap(backbuffer);
4228 // !!! FIX THIS (END) !!!
4230 DrawMaskedBorder(fade_mask);
4235 // full screen redraw is required at this point in the following cases:
4236 // - special editor door undrawn when game was started from level editor
4237 // - drawing area (playfield) was changed and has to be removed completely
4238 redraw_mask = REDRAW_ALL;
4242 if (!game.restart_level)
4244 // copy default game door content to main double buffer
4246 // !!! CHECK AGAIN !!!
4247 SetPanelBackground();
4248 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4249 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4252 SetPanelBackground();
4253 SetDrawBackgroundMask(REDRAW_DOOR_1);
4255 UpdateAndDisplayGameControlValues();
4257 if (!game.restart_level)
4263 CreateGameButtons();
4268 // copy actual game door content to door double buffer for OpenDoor()
4269 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4271 OpenDoor(DOOR_OPEN_ALL);
4273 KeyboardAutoRepeatOffUnlessAutoplay();
4275 #if DEBUG_INIT_PLAYER
4276 DebugPrintPlayerStatus("Player status (final)");
4285 if (!game.restart_level && !tape.playing)
4287 LevelStats_incPlayed(level_nr);
4289 SaveLevelSetup_SeriesInfo();
4292 game.restart_level = FALSE;
4293 game.restart_game_message = NULL;
4294 game.request_active = FALSE;
4296 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4297 InitGameActions_MM();
4299 SaveEngineSnapshotToListInitial();
4301 if (!game.restart_level)
4303 PlaySound(SND_GAME_STARTING);
4305 if (setup.sound_music)
4310 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4311 int actual_player_x, int actual_player_y)
4313 // this is used for non-R'n'D game engines to update certain engine values
4315 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4317 actual_player_x = correctLevelPosX_EM(actual_player_x);
4318 actual_player_y = correctLevelPosY_EM(actual_player_y);
4321 // needed to determine if sounds are played within the visible screen area
4322 scroll_x = actual_scroll_x;
4323 scroll_y = actual_scroll_y;
4325 // needed to get player position for "follow finger" playing input method
4326 local_player->jx = actual_player_x;
4327 local_player->jy = actual_player_y;
4330 void InitMovDir(int x, int y)
4332 int i, element = Feld[x][y];
4333 static int xy[4][2] =
4340 static int direction[3][4] =
4342 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4343 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4344 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4353 Feld[x][y] = EL_BUG;
4354 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4357 case EL_SPACESHIP_RIGHT:
4358 case EL_SPACESHIP_UP:
4359 case EL_SPACESHIP_LEFT:
4360 case EL_SPACESHIP_DOWN:
4361 Feld[x][y] = EL_SPACESHIP;
4362 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4365 case EL_BD_BUTTERFLY_RIGHT:
4366 case EL_BD_BUTTERFLY_UP:
4367 case EL_BD_BUTTERFLY_LEFT:
4368 case EL_BD_BUTTERFLY_DOWN:
4369 Feld[x][y] = EL_BD_BUTTERFLY;
4370 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4373 case EL_BD_FIREFLY_RIGHT:
4374 case EL_BD_FIREFLY_UP:
4375 case EL_BD_FIREFLY_LEFT:
4376 case EL_BD_FIREFLY_DOWN:
4377 Feld[x][y] = EL_BD_FIREFLY;
4378 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4381 case EL_PACMAN_RIGHT:
4383 case EL_PACMAN_LEFT:
4384 case EL_PACMAN_DOWN:
4385 Feld[x][y] = EL_PACMAN;
4386 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4389 case EL_YAMYAM_LEFT:
4390 case EL_YAMYAM_RIGHT:
4392 case EL_YAMYAM_DOWN:
4393 Feld[x][y] = EL_YAMYAM;
4394 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4397 case EL_SP_SNIKSNAK:
4398 MovDir[x][y] = MV_UP;
4401 case EL_SP_ELECTRON:
4402 MovDir[x][y] = MV_LEFT;
4409 Feld[x][y] = EL_MOLE;
4410 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4414 if (IS_CUSTOM_ELEMENT(element))
4416 struct ElementInfo *ei = &element_info[element];
4417 int move_direction_initial = ei->move_direction_initial;
4418 int move_pattern = ei->move_pattern;
4420 if (move_direction_initial == MV_START_PREVIOUS)
4422 if (MovDir[x][y] != MV_NONE)
4425 move_direction_initial = MV_START_AUTOMATIC;
4428 if (move_direction_initial == MV_START_RANDOM)
4429 MovDir[x][y] = 1 << RND(4);
4430 else if (move_direction_initial & MV_ANY_DIRECTION)
4431 MovDir[x][y] = move_direction_initial;
4432 else if (move_pattern == MV_ALL_DIRECTIONS ||
4433 move_pattern == MV_TURNING_LEFT ||
4434 move_pattern == MV_TURNING_RIGHT ||
4435 move_pattern == MV_TURNING_LEFT_RIGHT ||
4436 move_pattern == MV_TURNING_RIGHT_LEFT ||
4437 move_pattern == MV_TURNING_RANDOM)
4438 MovDir[x][y] = 1 << RND(4);
4439 else if (move_pattern == MV_HORIZONTAL)
4440 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4441 else if (move_pattern == MV_VERTICAL)
4442 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4443 else if (move_pattern & MV_ANY_DIRECTION)
4444 MovDir[x][y] = element_info[element].move_pattern;
4445 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4446 move_pattern == MV_ALONG_RIGHT_SIDE)
4448 // use random direction as default start direction
4449 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4450 MovDir[x][y] = 1 << RND(4);
4452 for (i = 0; i < NUM_DIRECTIONS; i++)
4454 int x1 = x + xy[i][0];
4455 int y1 = y + xy[i][1];
4457 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4459 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4460 MovDir[x][y] = direction[0][i];
4462 MovDir[x][y] = direction[1][i];
4471 MovDir[x][y] = 1 << RND(4);
4473 if (element != EL_BUG &&
4474 element != EL_SPACESHIP &&
4475 element != EL_BD_BUTTERFLY &&
4476 element != EL_BD_FIREFLY)
4479 for (i = 0; i < NUM_DIRECTIONS; i++)
4481 int x1 = x + xy[i][0];
4482 int y1 = y + xy[i][1];
4484 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4486 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4488 MovDir[x][y] = direction[0][i];
4491 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4492 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4494 MovDir[x][y] = direction[1][i];
4503 GfxDir[x][y] = MovDir[x][y];
4506 void InitAmoebaNr(int x, int y)
4509 int group_nr = AmoebeNachbarNr(x, y);
4513 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4515 if (AmoebaCnt[i] == 0)
4523 AmoebaNr[x][y] = group_nr;
4524 AmoebaCnt[group_nr]++;
4525 AmoebaCnt2[group_nr]++;
4528 static void LevelSolved(void)
4530 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4531 game.players_still_needed > 0)
4534 game.LevelSolved = TRUE;
4535 game.GameOver = TRUE;
4537 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4538 game_em.lev->score :
4539 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4542 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4543 MM_HEALTH(game_mm.laser_overload_value) :
4546 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4547 game.LevelSolved_CountingScore = game.score_final;
4548 game.LevelSolved_CountingHealth = game.health_final;
4553 static int time_count_steps;
4554 static int time, time_final;
4555 static int score, score_final;
4556 static int health, health_final;
4557 static int game_over_delay_1 = 0;
4558 static int game_over_delay_2 = 0;
4559 static int game_over_delay_3 = 0;
4560 int game_over_delay_value_1 = 50;
4561 int game_over_delay_value_2 = 25;
4562 int game_over_delay_value_3 = 50;
4564 if (!game.LevelSolved_GameWon)
4568 // do not start end game actions before the player stops moving (to exit)
4569 if (local_player->active && local_player->MovPos)
4572 game.LevelSolved_GameWon = TRUE;
4573 game.LevelSolved_SaveTape = tape.recording;
4574 game.LevelSolved_SaveScore = !tape.playing;
4578 LevelStats_incSolved(level_nr);
4580 SaveLevelSetup_SeriesInfo();
4583 if (tape.auto_play) // tape might already be stopped here
4584 tape.auto_play_level_solved = TRUE;
4588 game_over_delay_1 = 0;
4589 game_over_delay_2 = 0;
4590 game_over_delay_3 = game_over_delay_value_3;
4592 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4593 score = score_final = game.score_final;
4594 health = health_final = game.health_final;
4596 if (level.score[SC_TIME_BONUS] > 0)
4601 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4603 else if (game.no_time_limit && TimePlayed < 999)
4606 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4609 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4611 game_over_delay_1 = game_over_delay_value_1;
4613 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4616 score_final += health * level.score[SC_TIME_BONUS];
4618 game_over_delay_2 = game_over_delay_value_2;
4621 game.score_final = score_final;
4622 game.health_final = health_final;
4625 if (level_editor_test_game)
4628 score = score_final;
4630 game.LevelSolved_CountingTime = time;
4631 game.LevelSolved_CountingScore = score;
4633 game_panel_controls[GAME_PANEL_TIME].value = time;
4634 game_panel_controls[GAME_PANEL_SCORE].value = score;
4636 DisplayGameControlValues();
4639 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4641 // check if last player has left the level
4642 if (game.exit_x >= 0 &&
4645 int x = game.exit_x;
4646 int y = game.exit_y;
4647 int element = Feld[x][y];
4649 // close exit door after last player
4650 if ((game.all_players_gone &&
4651 (element == EL_EXIT_OPEN ||
4652 element == EL_SP_EXIT_OPEN ||
4653 element == EL_STEEL_EXIT_OPEN)) ||
4654 element == EL_EM_EXIT_OPEN ||
4655 element == EL_EM_STEEL_EXIT_OPEN)
4659 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4660 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4661 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4662 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4663 EL_EM_STEEL_EXIT_CLOSING);
4665 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4668 // player disappears
4669 DrawLevelField(x, y);
4672 for (i = 0; i < MAX_PLAYERS; i++)
4674 struct PlayerInfo *player = &stored_player[i];
4676 if (player->present)
4678 RemovePlayer(player);
4680 // player disappears
4681 DrawLevelField(player->jx, player->jy);
4686 PlaySound(SND_GAME_WINNING);
4689 if (game_over_delay_1 > 0)
4691 game_over_delay_1--;
4696 if (time != time_final)
4698 int time_to_go = ABS(time_final - time);
4699 int time_count_dir = (time < time_final ? +1 : -1);
4701 if (time_to_go < time_count_steps)
4702 time_count_steps = 1;
4704 time += time_count_steps * time_count_dir;
4705 score += time_count_steps * level.score[SC_TIME_BONUS];
4707 game.LevelSolved_CountingTime = time;
4708 game.LevelSolved_CountingScore = score;
4710 game_panel_controls[GAME_PANEL_TIME].value = time;
4711 game_panel_controls[GAME_PANEL_SCORE].value = score;
4713 DisplayGameControlValues();
4715 if (time == time_final)
4716 StopSound(SND_GAME_LEVELTIME_BONUS);
4717 else if (setup.sound_loops)
4718 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4720 PlaySound(SND_GAME_LEVELTIME_BONUS);
4725 if (game_over_delay_2 > 0)
4727 game_over_delay_2--;
4732 if (health != health_final)
4734 int health_count_dir = (health < health_final ? +1 : -1);
4736 health += health_count_dir;
4737 score += level.score[SC_TIME_BONUS];
4739 game.LevelSolved_CountingHealth = health;
4740 game.LevelSolved_CountingScore = score;
4742 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4743 game_panel_controls[GAME_PANEL_SCORE].value = score;
4745 DisplayGameControlValues();
4747 if (health == health_final)
4748 StopSound(SND_GAME_LEVELTIME_BONUS);
4749 else if (setup.sound_loops)
4750 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4752 PlaySound(SND_GAME_LEVELTIME_BONUS);
4757 game.panel.active = FALSE;
4759 if (game_over_delay_3 > 0)
4761 game_over_delay_3--;
4771 // used instead of "level_nr" (needed for network games)
4772 int last_level_nr = levelset.level_nr;
4775 game.LevelSolved_GameEnd = TRUE;
4777 if (game.LevelSolved_SaveTape)
4779 // make sure that request dialog to save tape does not open door again
4780 if (!global.use_envelope_request)
4781 CloseDoor(DOOR_CLOSE_1);
4783 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4786 // if no tape is to be saved, close both doors simultaneously
4787 CloseDoor(DOOR_CLOSE_ALL);
4789 if (level_editor_test_game)
4791 SetGameStatus(GAME_MODE_MAIN);
4798 if (!game.LevelSolved_SaveScore)
4800 SetGameStatus(GAME_MODE_MAIN);
4807 if (level_nr == leveldir_current->handicap_level)
4809 leveldir_current->handicap_level++;
4811 SaveLevelSetup_SeriesInfo();
4814 if (setup.increment_levels &&
4815 level_nr < leveldir_current->last_level &&
4818 level_nr++; // advance to next level
4819 TapeErase(); // start with empty tape
4821 if (setup.auto_play_next_level)
4823 LoadLevel(level_nr);
4825 SaveLevelSetup_SeriesInfo();
4829 hi_pos = NewHiScore(last_level_nr);
4831 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4833 SetGameStatus(GAME_MODE_SCORES);
4835 DrawHallOfFame(last_level_nr, hi_pos);
4837 else if (setup.auto_play_next_level && setup.increment_levels &&
4838 last_level_nr < leveldir_current->last_level &&
4841 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4845 SetGameStatus(GAME_MODE_MAIN);
4851 int NewHiScore(int level_nr)
4855 boolean one_score_entry_per_name = !program.many_scores_per_name;
4857 LoadScore(level_nr);
4859 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4860 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4863 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4865 if (game.score_final > highscore[k].Score)
4867 // player has made it to the hall of fame
4869 if (k < MAX_SCORE_ENTRIES - 1)
4871 int m = MAX_SCORE_ENTRIES - 1;
4873 if (one_score_entry_per_name)
4875 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4876 if (strEqual(setup.player_name, highscore[l].Name))
4879 if (m == k) // player's new highscore overwrites his old one
4883 for (l = m; l > k; l--)
4885 strcpy(highscore[l].Name, highscore[l - 1].Name);
4886 highscore[l].Score = highscore[l - 1].Score;
4892 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4893 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4894 highscore[k].Score = game.score_final;
4899 else if (one_score_entry_per_name &&
4900 !strncmp(setup.player_name, highscore[k].Name,
4901 MAX_PLAYER_NAME_LEN))
4902 break; // player already there with a higher score
4906 SaveScore(level_nr);
4911 static int getElementMoveStepsizeExt(int x, int y, int direction)
4913 int element = Feld[x][y];
4914 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4915 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4916 int horiz_move = (dx != 0);
4917 int sign = (horiz_move ? dx : dy);
4918 int step = sign * element_info[element].move_stepsize;
4920 // special values for move stepsize for spring and things on conveyor belt
4923 if (CAN_FALL(element) &&
4924 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
4925 step = sign * MOVE_STEPSIZE_NORMAL / 2;
4926 else if (element == EL_SPRING)
4927 step = sign * MOVE_STEPSIZE_NORMAL * 2;
4933 static int getElementMoveStepsize(int x, int y)
4935 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
4938 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
4940 if (player->GfxAction != action || player->GfxDir != dir)
4942 player->GfxAction = action;
4943 player->GfxDir = dir;
4945 player->StepFrame = 0;
4949 static void ResetGfxFrame(int x, int y)
4951 // profiling showed that "autotest" spends 10~20% of its time in this function
4952 if (DrawingDeactivatedField())
4955 int element = Feld[x][y];
4956 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
4958 if (graphic_info[graphic].anim_global_sync)
4959 GfxFrame[x][y] = FrameCounter;
4960 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
4961 GfxFrame[x][y] = CustomValue[x][y];
4962 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
4963 GfxFrame[x][y] = element_info[element].collect_score;
4964 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
4965 GfxFrame[x][y] = ChangeDelay[x][y];
4968 static void ResetGfxAnimation(int x, int y)
4970 GfxAction[x][y] = ACTION_DEFAULT;
4971 GfxDir[x][y] = MovDir[x][y];
4974 ResetGfxFrame(x, y);
4977 static void ResetRandomAnimationValue(int x, int y)
4979 GfxRandom[x][y] = INIT_GFX_RANDOM();
4982 static void InitMovingField(int x, int y, int direction)
4984 int element = Feld[x][y];
4985 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4986 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4989 boolean is_moving_before, is_moving_after;
4991 // check if element was/is moving or being moved before/after mode change
4992 is_moving_before = (WasJustMoving[x][y] != 0);
4993 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
4995 // reset animation only for moving elements which change direction of moving
4996 // or which just started or stopped moving
4997 // (else CEs with property "can move" / "not moving" are reset each frame)
4998 if (is_moving_before != is_moving_after ||
4999 direction != MovDir[x][y])
5000 ResetGfxAnimation(x, y);
5002 MovDir[x][y] = direction;
5003 GfxDir[x][y] = direction;
5005 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5006 direction == MV_DOWN && CAN_FALL(element) ?
5007 ACTION_FALLING : ACTION_MOVING);
5009 // this is needed for CEs with property "can move" / "not moving"
5011 if (is_moving_after)
5013 if (Feld[newx][newy] == EL_EMPTY)
5014 Feld[newx][newy] = EL_BLOCKED;
5016 MovDir[newx][newy] = MovDir[x][y];
5018 CustomValue[newx][newy] = CustomValue[x][y];
5020 GfxFrame[newx][newy] = GfxFrame[x][y];
5021 GfxRandom[newx][newy] = GfxRandom[x][y];
5022 GfxAction[newx][newy] = GfxAction[x][y];
5023 GfxDir[newx][newy] = GfxDir[x][y];
5027 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5029 int direction = MovDir[x][y];
5030 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5031 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5037 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5039 int oldx = x, oldy = y;
5040 int direction = MovDir[x][y];
5042 if (direction == MV_LEFT)
5044 else if (direction == MV_RIGHT)
5046 else if (direction == MV_UP)
5048 else if (direction == MV_DOWN)
5051 *comes_from_x = oldx;
5052 *comes_from_y = oldy;
5055 static int MovingOrBlocked2Element(int x, int y)
5057 int element = Feld[x][y];
5059 if (element == EL_BLOCKED)
5063 Blocked2Moving(x, y, &oldx, &oldy);
5064 return Feld[oldx][oldy];
5070 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5072 // like MovingOrBlocked2Element(), but if element is moving
5073 // and (x,y) is the field the moving element is just leaving,
5074 // return EL_BLOCKED instead of the element value
5075 int element = Feld[x][y];
5077 if (IS_MOVING(x, y))
5079 if (element == EL_BLOCKED)
5083 Blocked2Moving(x, y, &oldx, &oldy);
5084 return Feld[oldx][oldy];
5093 static void RemoveField(int x, int y)
5095 Feld[x][y] = EL_EMPTY;
5101 CustomValue[x][y] = 0;
5104 ChangeDelay[x][y] = 0;
5105 ChangePage[x][y] = -1;
5106 Pushed[x][y] = FALSE;
5108 GfxElement[x][y] = EL_UNDEFINED;
5109 GfxAction[x][y] = ACTION_DEFAULT;
5110 GfxDir[x][y] = MV_NONE;
5113 static void RemoveMovingField(int x, int y)
5115 int oldx = x, oldy = y, newx = x, newy = y;
5116 int element = Feld[x][y];
5117 int next_element = EL_UNDEFINED;
5119 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5122 if (IS_MOVING(x, y))
5124 Moving2Blocked(x, y, &newx, &newy);
5126 if (Feld[newx][newy] != EL_BLOCKED)
5128 // element is moving, but target field is not free (blocked), but
5129 // already occupied by something different (example: acid pool);
5130 // in this case, only remove the moving field, but not the target
5132 RemoveField(oldx, oldy);
5134 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5136 TEST_DrawLevelField(oldx, oldy);
5141 else if (element == EL_BLOCKED)
5143 Blocked2Moving(x, y, &oldx, &oldy);
5144 if (!IS_MOVING(oldx, oldy))
5148 if (element == EL_BLOCKED &&
5149 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5150 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5151 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5152 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5153 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5154 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5155 next_element = get_next_element(Feld[oldx][oldy]);
5157 RemoveField(oldx, oldy);
5158 RemoveField(newx, newy);
5160 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5162 if (next_element != EL_UNDEFINED)
5163 Feld[oldx][oldy] = next_element;
5165 TEST_DrawLevelField(oldx, oldy);
5166 TEST_DrawLevelField(newx, newy);
5169 void DrawDynamite(int x, int y)
5171 int sx = SCREENX(x), sy = SCREENY(y);
5172 int graphic = el2img(Feld[x][y]);
5175 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5178 if (IS_WALKABLE_INSIDE(Back[x][y]))
5182 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5183 else if (Store[x][y])
5184 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5186 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5188 if (Back[x][y] || Store[x][y])
5189 DrawGraphicThruMask(sx, sy, graphic, frame);
5191 DrawGraphic(sx, sy, graphic, frame);
5194 static void CheckDynamite(int x, int y)
5196 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5200 if (MovDelay[x][y] != 0)
5203 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5209 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5214 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5216 boolean num_checked_players = 0;
5219 for (i = 0; i < MAX_PLAYERS; i++)
5221 if (stored_player[i].active)
5223 int sx = stored_player[i].jx;
5224 int sy = stored_player[i].jy;
5226 if (num_checked_players == 0)
5233 *sx1 = MIN(*sx1, sx);
5234 *sy1 = MIN(*sy1, sy);
5235 *sx2 = MAX(*sx2, sx);
5236 *sy2 = MAX(*sy2, sy);
5239 num_checked_players++;
5244 static boolean checkIfAllPlayersFitToScreen_RND(void)
5246 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5248 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5250 return (sx2 - sx1 < SCR_FIELDX &&
5251 sy2 - sy1 < SCR_FIELDY);
5254 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5256 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5258 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5260 *sx = (sx1 + sx2) / 2;
5261 *sy = (sy1 + sy2) / 2;
5264 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5265 boolean center_screen, boolean quick_relocation)
5267 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5268 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5269 boolean no_delay = (tape.warp_forward);
5270 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5271 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5272 int new_scroll_x, new_scroll_y;
5274 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5276 // case 1: quick relocation inside visible screen (without scrolling)
5283 if (!level.shifted_relocation || center_screen)
5285 // relocation _with_ centering of screen
5287 new_scroll_x = SCROLL_POSITION_X(x);
5288 new_scroll_y = SCROLL_POSITION_Y(y);
5292 // relocation _without_ centering of screen
5294 int center_scroll_x = SCROLL_POSITION_X(old_x);
5295 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5296 int offset_x = x + (scroll_x - center_scroll_x);
5297 int offset_y = y + (scroll_y - center_scroll_y);
5299 // for new screen position, apply previous offset to center position
5300 new_scroll_x = SCROLL_POSITION_X(offset_x);
5301 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5304 if (quick_relocation)
5306 // case 2: quick relocation (redraw without visible scrolling)
5308 scroll_x = new_scroll_x;
5309 scroll_y = new_scroll_y;
5316 // case 3: visible relocation (with scrolling to new position)
5318 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5320 SetVideoFrameDelay(wait_delay_value);
5322 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5324 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5325 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5327 if (dx == 0 && dy == 0) // no scrolling needed at all
5333 // set values for horizontal/vertical screen scrolling (half tile size)
5334 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5335 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5336 int pos_x = dx * TILEX / 2;
5337 int pos_y = dy * TILEY / 2;
5338 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5339 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5341 ScrollLevel(dx, dy);
5344 // scroll in two steps of half tile size to make things smoother
5345 BlitScreenToBitmapExt_RND(window, fx, fy);
5347 // scroll second step to align at full tile size
5348 BlitScreenToBitmap(window);
5354 SetVideoFrameDelay(frame_delay_value_old);
5357 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5359 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5360 int player_nr = GET_PLAYER_NR(el_player);
5361 struct PlayerInfo *player = &stored_player[player_nr];
5362 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5363 boolean no_delay = (tape.warp_forward);
5364 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5365 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5366 int old_jx = player->jx;
5367 int old_jy = player->jy;
5368 int old_element = Feld[old_jx][old_jy];
5369 int element = Feld[jx][jy];
5370 boolean player_relocated = (old_jx != jx || old_jy != jy);
5372 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5373 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5374 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5375 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5376 int leave_side_horiz = move_dir_horiz;
5377 int leave_side_vert = move_dir_vert;
5378 int enter_side = enter_side_horiz | enter_side_vert;
5379 int leave_side = leave_side_horiz | leave_side_vert;
5381 if (player->buried) // do not reanimate dead player
5384 if (!player_relocated) // no need to relocate the player
5387 if (IS_PLAYER(jx, jy)) // player already placed at new position
5389 RemoveField(jx, jy); // temporarily remove newly placed player
5390 DrawLevelField(jx, jy);
5393 if (player->present)
5395 while (player->MovPos)
5397 ScrollPlayer(player, SCROLL_GO_ON);
5398 ScrollScreen(NULL, SCROLL_GO_ON);
5400 AdvanceFrameAndPlayerCounters(player->index_nr);
5404 BackToFront_WithFrameDelay(wait_delay_value);
5407 DrawPlayer(player); // needed here only to cleanup last field
5408 DrawLevelField(player->jx, player->jy); // remove player graphic
5410 player->is_moving = FALSE;
5413 if (IS_CUSTOM_ELEMENT(old_element))
5414 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5416 player->index_bit, leave_side);
5418 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5420 player->index_bit, leave_side);
5422 Feld[jx][jy] = el_player;
5423 InitPlayerField(jx, jy, el_player, TRUE);
5425 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5426 possible that the relocation target field did not contain a player element,
5427 but a walkable element, to which the new player was relocated -- in this
5428 case, restore that (already initialized!) element on the player field */
5429 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5431 Feld[jx][jy] = element; // restore previously existing element
5434 // only visually relocate centered player
5435 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5436 FALSE, level.instant_relocation);
5438 TestIfPlayerTouchesBadThing(jx, jy);
5439 TestIfPlayerTouchesCustomElement(jx, jy);
5441 if (IS_CUSTOM_ELEMENT(element))
5442 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5443 player->index_bit, enter_side);
5445 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5446 player->index_bit, enter_side);
5448 if (player->is_switching)
5450 /* ensure that relocation while still switching an element does not cause
5451 a new element to be treated as also switched directly after relocation
5452 (this is important for teleporter switches that teleport the player to
5453 a place where another teleporter switch is in the same direction, which
5454 would then incorrectly be treated as immediately switched before the
5455 direction key that caused the switch was released) */
5457 player->switch_x += jx - old_jx;
5458 player->switch_y += jy - old_jy;
5462 static void Explode(int ex, int ey, int phase, int mode)
5468 // !!! eliminate this variable !!!
5469 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5471 if (game.explosions_delayed)
5473 ExplodeField[ex][ey] = mode;
5477 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5479 int center_element = Feld[ex][ey];
5480 int artwork_element, explosion_element; // set these values later
5482 // remove things displayed in background while burning dynamite
5483 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5486 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5488 // put moving element to center field (and let it explode there)
5489 center_element = MovingOrBlocked2Element(ex, ey);
5490 RemoveMovingField(ex, ey);
5491 Feld[ex][ey] = center_element;
5494 // now "center_element" is finally determined -- set related values now
5495 artwork_element = center_element; // for custom player artwork
5496 explosion_element = center_element; // for custom player artwork
5498 if (IS_PLAYER(ex, ey))
5500 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5502 artwork_element = stored_player[player_nr].artwork_element;
5504 if (level.use_explosion_element[player_nr])
5506 explosion_element = level.explosion_element[player_nr];
5507 artwork_element = explosion_element;
5511 if (mode == EX_TYPE_NORMAL ||
5512 mode == EX_TYPE_CENTER ||
5513 mode == EX_TYPE_CROSS)
5514 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5516 last_phase = element_info[explosion_element].explosion_delay + 1;
5518 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5520 int xx = x - ex + 1;
5521 int yy = y - ey + 1;
5524 if (!IN_LEV_FIELD(x, y) ||
5525 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5526 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5529 element = Feld[x][y];
5531 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5533 element = MovingOrBlocked2Element(x, y);
5535 if (!IS_EXPLOSION_PROOF(element))
5536 RemoveMovingField(x, y);
5539 // indestructible elements can only explode in center (but not flames)
5540 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5541 mode == EX_TYPE_BORDER)) ||
5542 element == EL_FLAMES)
5545 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5546 behaviour, for example when touching a yamyam that explodes to rocks
5547 with active deadly shield, a rock is created under the player !!! */
5548 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5550 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5551 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5552 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5554 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5557 if (IS_ACTIVE_BOMB(element))
5559 // re-activate things under the bomb like gate or penguin
5560 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5567 // save walkable background elements while explosion on same tile
5568 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5569 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5570 Back[x][y] = element;
5572 // ignite explodable elements reached by other explosion
5573 if (element == EL_EXPLOSION)
5574 element = Store2[x][y];
5576 if (AmoebaNr[x][y] &&
5577 (element == EL_AMOEBA_FULL ||
5578 element == EL_BD_AMOEBA ||
5579 element == EL_AMOEBA_GROWING))
5581 AmoebaCnt[AmoebaNr[x][y]]--;
5582 AmoebaCnt2[AmoebaNr[x][y]]--;
5587 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5589 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5591 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5593 if (PLAYERINFO(ex, ey)->use_murphy)
5594 Store[x][y] = EL_EMPTY;
5597 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5598 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5599 else if (ELEM_IS_PLAYER(center_element))
5600 Store[x][y] = EL_EMPTY;
5601 else if (center_element == EL_YAMYAM)
5602 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5603 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5604 Store[x][y] = element_info[center_element].content.e[xx][yy];
5606 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5607 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5608 // otherwise) -- FIX THIS !!!
5609 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5610 Store[x][y] = element_info[element].content.e[1][1];
5612 else if (!CAN_EXPLODE(element))
5613 Store[x][y] = element_info[element].content.e[1][1];
5616 Store[x][y] = EL_EMPTY;
5618 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5619 center_element == EL_AMOEBA_TO_DIAMOND)
5620 Store2[x][y] = element;
5622 Feld[x][y] = EL_EXPLOSION;
5623 GfxElement[x][y] = artwork_element;
5625 ExplodePhase[x][y] = 1;
5626 ExplodeDelay[x][y] = last_phase;
5631 if (center_element == EL_YAMYAM)
5632 game.yamyam_content_nr =
5633 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5645 GfxFrame[x][y] = 0; // restart explosion animation
5647 last_phase = ExplodeDelay[x][y];
5649 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5651 // this can happen if the player leaves an explosion just in time
5652 if (GfxElement[x][y] == EL_UNDEFINED)
5653 GfxElement[x][y] = EL_EMPTY;
5655 border_element = Store2[x][y];
5656 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5657 border_element = StorePlayer[x][y];
5659 if (phase == element_info[border_element].ignition_delay ||
5660 phase == last_phase)
5662 boolean border_explosion = FALSE;
5664 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5665 !PLAYER_EXPLOSION_PROTECTED(x, y))
5667 KillPlayerUnlessExplosionProtected(x, y);
5668 border_explosion = TRUE;
5670 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5672 Feld[x][y] = Store2[x][y];
5675 border_explosion = TRUE;
5677 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5679 AmoebeUmwandeln(x, y);
5681 border_explosion = TRUE;
5684 // if an element just explodes due to another explosion (chain-reaction),
5685 // do not immediately end the new explosion when it was the last frame of
5686 // the explosion (as it would be done in the following "if"-statement!)
5687 if (border_explosion && phase == last_phase)
5691 if (phase == last_phase)
5695 element = Feld[x][y] = Store[x][y];
5696 Store[x][y] = Store2[x][y] = 0;
5697 GfxElement[x][y] = EL_UNDEFINED;
5699 // player can escape from explosions and might therefore be still alive
5700 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5701 element <= EL_PLAYER_IS_EXPLODING_4)
5703 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5704 int explosion_element = EL_PLAYER_1 + player_nr;
5705 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5706 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5708 if (level.use_explosion_element[player_nr])
5709 explosion_element = level.explosion_element[player_nr];
5711 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5712 element_info[explosion_element].content.e[xx][yy]);
5715 // restore probably existing indestructible background element
5716 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5717 element = Feld[x][y] = Back[x][y];
5720 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5721 GfxDir[x][y] = MV_NONE;
5722 ChangeDelay[x][y] = 0;
5723 ChangePage[x][y] = -1;
5725 CustomValue[x][y] = 0;
5727 InitField_WithBug2(x, y, FALSE);
5729 TEST_DrawLevelField(x, y);
5731 TestIfElementTouchesCustomElement(x, y);
5733 if (GFX_CRUMBLED(element))
5734 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5736 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5737 StorePlayer[x][y] = 0;
5739 if (ELEM_IS_PLAYER(element))
5740 RelocatePlayer(x, y, element);
5742 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5744 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5745 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5748 TEST_DrawLevelFieldCrumbled(x, y);
5750 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5752 DrawLevelElement(x, y, Back[x][y]);
5753 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5755 else if (IS_WALKABLE_UNDER(Back[x][y]))
5757 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5758 DrawLevelElementThruMask(x, y, Back[x][y]);
5760 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5761 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5765 static void DynaExplode(int ex, int ey)
5768 int dynabomb_element = Feld[ex][ey];
5769 int dynabomb_size = 1;
5770 boolean dynabomb_xl = FALSE;
5771 struct PlayerInfo *player;
5772 static int xy[4][2] =
5780 if (IS_ACTIVE_BOMB(dynabomb_element))
5782 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5783 dynabomb_size = player->dynabomb_size;
5784 dynabomb_xl = player->dynabomb_xl;
5785 player->dynabombs_left++;
5788 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5790 for (i = 0; i < NUM_DIRECTIONS; i++)
5792 for (j = 1; j <= dynabomb_size; j++)
5794 int x = ex + j * xy[i][0];
5795 int y = ey + j * xy[i][1];
5798 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5801 element = Feld[x][y];
5803 // do not restart explosions of fields with active bombs
5804 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5807 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5809 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5810 !IS_DIGGABLE(element) && !dynabomb_xl)
5816 void Bang(int x, int y)
5818 int element = MovingOrBlocked2Element(x, y);
5819 int explosion_type = EX_TYPE_NORMAL;
5821 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5823 struct PlayerInfo *player = PLAYERINFO(x, y);
5825 element = Feld[x][y] = player->initial_element;
5827 if (level.use_explosion_element[player->index_nr])
5829 int explosion_element = level.explosion_element[player->index_nr];
5831 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5832 explosion_type = EX_TYPE_CROSS;
5833 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5834 explosion_type = EX_TYPE_CENTER;
5842 case EL_BD_BUTTERFLY:
5845 case EL_DARK_YAMYAM:
5849 RaiseScoreElement(element);
5852 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5853 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5854 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5855 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5856 case EL_DYNABOMB_INCREASE_NUMBER:
5857 case EL_DYNABOMB_INCREASE_SIZE:
5858 case EL_DYNABOMB_INCREASE_POWER:
5859 explosion_type = EX_TYPE_DYNA;
5862 case EL_DC_LANDMINE:
5863 explosion_type = EX_TYPE_CENTER;
5868 case EL_LAMP_ACTIVE:
5869 case EL_AMOEBA_TO_DIAMOND:
5870 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5871 explosion_type = EX_TYPE_CENTER;
5875 if (element_info[element].explosion_type == EXPLODES_CROSS)
5876 explosion_type = EX_TYPE_CROSS;
5877 else if (element_info[element].explosion_type == EXPLODES_1X1)
5878 explosion_type = EX_TYPE_CENTER;
5882 if (explosion_type == EX_TYPE_DYNA)
5885 Explode(x, y, EX_PHASE_START, explosion_type);
5887 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5890 static void SplashAcid(int x, int y)
5892 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5893 (!IN_LEV_FIELD(x - 1, y - 2) ||
5894 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5895 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5897 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5898 (!IN_LEV_FIELD(x + 1, y - 2) ||
5899 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5900 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5902 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5905 static void InitBeltMovement(void)
5907 static int belt_base_element[4] =
5909 EL_CONVEYOR_BELT_1_LEFT,
5910 EL_CONVEYOR_BELT_2_LEFT,
5911 EL_CONVEYOR_BELT_3_LEFT,
5912 EL_CONVEYOR_BELT_4_LEFT
5914 static int belt_base_active_element[4] =
5916 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5917 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5918 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5919 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5924 // set frame order for belt animation graphic according to belt direction
5925 for (i = 0; i < NUM_BELTS; i++)
5929 for (j = 0; j < NUM_BELT_PARTS; j++)
5931 int element = belt_base_active_element[belt_nr] + j;
5932 int graphic_1 = el2img(element);
5933 int graphic_2 = el2panelimg(element);
5935 if (game.belt_dir[i] == MV_LEFT)
5937 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5938 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5942 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5943 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5948 SCAN_PLAYFIELD(x, y)
5950 int element = Feld[x][y];
5952 for (i = 0; i < NUM_BELTS; i++)
5954 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
5956 int e_belt_nr = getBeltNrFromBeltElement(element);
5959 if (e_belt_nr == belt_nr)
5961 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
5963 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
5970 static void ToggleBeltSwitch(int x, int y)
5972 static int belt_base_element[4] =
5974 EL_CONVEYOR_BELT_1_LEFT,
5975 EL_CONVEYOR_BELT_2_LEFT,
5976 EL_CONVEYOR_BELT_3_LEFT,
5977 EL_CONVEYOR_BELT_4_LEFT
5979 static int belt_base_active_element[4] =
5981 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5982 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5983 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5984 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5986 static int belt_base_switch_element[4] =
5988 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
5989 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
5990 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
5991 EL_CONVEYOR_BELT_4_SWITCH_LEFT
5993 static int belt_move_dir[4] =
6001 int element = Feld[x][y];
6002 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6003 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6004 int belt_dir = belt_move_dir[belt_dir_nr];
6007 if (!IS_BELT_SWITCH(element))
6010 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6011 game.belt_dir[belt_nr] = belt_dir;
6013 if (belt_dir_nr == 3)
6016 // set frame order for belt animation graphic according to belt direction
6017 for (i = 0; i < NUM_BELT_PARTS; i++)
6019 int element = belt_base_active_element[belt_nr] + i;
6020 int graphic_1 = el2img(element);
6021 int graphic_2 = el2panelimg(element);
6023 if (belt_dir == MV_LEFT)
6025 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6026 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6030 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6031 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6035 SCAN_PLAYFIELD(xx, yy)
6037 int element = Feld[xx][yy];
6039 if (IS_BELT_SWITCH(element))
6041 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6043 if (e_belt_nr == belt_nr)
6045 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6046 TEST_DrawLevelField(xx, yy);
6049 else if (IS_BELT(element) && belt_dir != MV_NONE)
6051 int e_belt_nr = getBeltNrFromBeltElement(element);
6053 if (e_belt_nr == belt_nr)
6055 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
6057 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6058 TEST_DrawLevelField(xx, yy);
6061 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6063 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6065 if (e_belt_nr == belt_nr)
6067 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6069 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6070 TEST_DrawLevelField(xx, yy);
6076 static void ToggleSwitchgateSwitch(int x, int y)
6080 game.switchgate_pos = !game.switchgate_pos;
6082 SCAN_PLAYFIELD(xx, yy)
6084 int element = Feld[xx][yy];
6086 if (element == EL_SWITCHGATE_SWITCH_UP)
6088 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6089 TEST_DrawLevelField(xx, yy);
6091 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6093 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6094 TEST_DrawLevelField(xx, yy);
6096 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6098 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6099 TEST_DrawLevelField(xx, yy);
6101 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6103 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6104 TEST_DrawLevelField(xx, yy);
6106 else if (element == EL_SWITCHGATE_OPEN ||
6107 element == EL_SWITCHGATE_OPENING)
6109 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6111 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6113 else if (element == EL_SWITCHGATE_CLOSED ||
6114 element == EL_SWITCHGATE_CLOSING)
6116 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6118 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6123 static int getInvisibleActiveFromInvisibleElement(int element)
6125 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6126 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6127 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6131 static int getInvisibleFromInvisibleActiveElement(int element)
6133 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6134 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6135 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6139 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6143 SCAN_PLAYFIELD(x, y)
6145 int element = Feld[x][y];
6147 if (element == EL_LIGHT_SWITCH &&
6148 game.light_time_left > 0)
6150 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6151 TEST_DrawLevelField(x, y);
6153 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6154 game.light_time_left == 0)
6156 Feld[x][y] = EL_LIGHT_SWITCH;
6157 TEST_DrawLevelField(x, y);
6159 else if (element == EL_EMC_DRIPPER &&
6160 game.light_time_left > 0)
6162 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6163 TEST_DrawLevelField(x, y);
6165 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6166 game.light_time_left == 0)
6168 Feld[x][y] = EL_EMC_DRIPPER;
6169 TEST_DrawLevelField(x, y);
6171 else if (element == EL_INVISIBLE_STEELWALL ||
6172 element == EL_INVISIBLE_WALL ||
6173 element == EL_INVISIBLE_SAND)
6175 if (game.light_time_left > 0)
6176 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6178 TEST_DrawLevelField(x, y);
6180 // uncrumble neighbour fields, if needed
6181 if (element == EL_INVISIBLE_SAND)
6182 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6184 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6185 element == EL_INVISIBLE_WALL_ACTIVE ||
6186 element == EL_INVISIBLE_SAND_ACTIVE)
6188 if (game.light_time_left == 0)
6189 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6191 TEST_DrawLevelField(x, y);
6193 // re-crumble neighbour fields, if needed
6194 if (element == EL_INVISIBLE_SAND)
6195 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6200 static void RedrawAllInvisibleElementsForLenses(void)
6204 SCAN_PLAYFIELD(x, y)
6206 int element = Feld[x][y];
6208 if (element == EL_EMC_DRIPPER &&
6209 game.lenses_time_left > 0)
6211 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6212 TEST_DrawLevelField(x, y);
6214 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6215 game.lenses_time_left == 0)
6217 Feld[x][y] = EL_EMC_DRIPPER;
6218 TEST_DrawLevelField(x, y);
6220 else if (element == EL_INVISIBLE_STEELWALL ||
6221 element == EL_INVISIBLE_WALL ||
6222 element == EL_INVISIBLE_SAND)
6224 if (game.lenses_time_left > 0)
6225 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6227 TEST_DrawLevelField(x, y);
6229 // uncrumble neighbour fields, if needed
6230 if (element == EL_INVISIBLE_SAND)
6231 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6233 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6234 element == EL_INVISIBLE_WALL_ACTIVE ||
6235 element == EL_INVISIBLE_SAND_ACTIVE)
6237 if (game.lenses_time_left == 0)
6238 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6240 TEST_DrawLevelField(x, y);
6242 // re-crumble neighbour fields, if needed
6243 if (element == EL_INVISIBLE_SAND)
6244 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6249 static void RedrawAllInvisibleElementsForMagnifier(void)
6253 SCAN_PLAYFIELD(x, y)
6255 int element = Feld[x][y];
6257 if (element == EL_EMC_FAKE_GRASS &&
6258 game.magnify_time_left > 0)
6260 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6261 TEST_DrawLevelField(x, y);
6263 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6264 game.magnify_time_left == 0)
6266 Feld[x][y] = EL_EMC_FAKE_GRASS;
6267 TEST_DrawLevelField(x, y);
6269 else if (IS_GATE_GRAY(element) &&
6270 game.magnify_time_left > 0)
6272 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6273 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6274 IS_EM_GATE_GRAY(element) ?
6275 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6276 IS_EMC_GATE_GRAY(element) ?
6277 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6278 IS_DC_GATE_GRAY(element) ?
6279 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6281 TEST_DrawLevelField(x, y);
6283 else if (IS_GATE_GRAY_ACTIVE(element) &&
6284 game.magnify_time_left == 0)
6286 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6287 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6288 IS_EM_GATE_GRAY_ACTIVE(element) ?
6289 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6290 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6291 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6292 IS_DC_GATE_GRAY_ACTIVE(element) ?
6293 EL_DC_GATE_WHITE_GRAY :
6295 TEST_DrawLevelField(x, y);
6300 static void ToggleLightSwitch(int x, int y)
6302 int element = Feld[x][y];
6304 game.light_time_left =
6305 (element == EL_LIGHT_SWITCH ?
6306 level.time_light * FRAMES_PER_SECOND : 0);
6308 RedrawAllLightSwitchesAndInvisibleElements();
6311 static void ActivateTimegateSwitch(int x, int y)
6315 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6317 SCAN_PLAYFIELD(xx, yy)
6319 int element = Feld[xx][yy];
6321 if (element == EL_TIMEGATE_CLOSED ||
6322 element == EL_TIMEGATE_CLOSING)
6324 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6325 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6329 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6331 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6332 TEST_DrawLevelField(xx, yy);
6338 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6339 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6342 static void Impact(int x, int y)
6344 boolean last_line = (y == lev_fieldy - 1);
6345 boolean object_hit = FALSE;
6346 boolean impact = (last_line || object_hit);
6347 int element = Feld[x][y];
6348 int smashed = EL_STEELWALL;
6350 if (!last_line) // check if element below was hit
6352 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6355 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6356 MovDir[x][y + 1] != MV_DOWN ||
6357 MovPos[x][y + 1] <= TILEY / 2));
6359 // do not smash moving elements that left the smashed field in time
6360 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6361 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6364 #if USE_QUICKSAND_IMPACT_BUGFIX
6365 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6367 RemoveMovingField(x, y + 1);
6368 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6369 Feld[x][y + 2] = EL_ROCK;
6370 TEST_DrawLevelField(x, y + 2);
6375 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6377 RemoveMovingField(x, y + 1);
6378 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6379 Feld[x][y + 2] = EL_ROCK;
6380 TEST_DrawLevelField(x, y + 2);
6387 smashed = MovingOrBlocked2Element(x, y + 1);
6389 impact = (last_line || object_hit);
6392 if (!last_line && smashed == EL_ACID) // element falls into acid
6394 SplashAcid(x, y + 1);
6398 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6399 // only reset graphic animation if graphic really changes after impact
6401 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6403 ResetGfxAnimation(x, y);
6404 TEST_DrawLevelField(x, y);
6407 if (impact && CAN_EXPLODE_IMPACT(element))
6412 else if (impact && element == EL_PEARL &&
6413 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6415 ResetGfxAnimation(x, y);
6417 Feld[x][y] = EL_PEARL_BREAKING;
6418 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6421 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6423 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6428 if (impact && element == EL_AMOEBA_DROP)
6430 if (object_hit && IS_PLAYER(x, y + 1))
6431 KillPlayerUnlessEnemyProtected(x, y + 1);
6432 else if (object_hit && smashed == EL_PENGUIN)
6436 Feld[x][y] = EL_AMOEBA_GROWING;
6437 Store[x][y] = EL_AMOEBA_WET;
6439 ResetRandomAnimationValue(x, y);
6444 if (object_hit) // check which object was hit
6446 if ((CAN_PASS_MAGIC_WALL(element) &&
6447 (smashed == EL_MAGIC_WALL ||
6448 smashed == EL_BD_MAGIC_WALL)) ||
6449 (CAN_PASS_DC_MAGIC_WALL(element) &&
6450 smashed == EL_DC_MAGIC_WALL))
6453 int activated_magic_wall =
6454 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6455 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6456 EL_DC_MAGIC_WALL_ACTIVE);
6458 // activate magic wall / mill
6459 SCAN_PLAYFIELD(xx, yy)
6461 if (Feld[xx][yy] == smashed)
6462 Feld[xx][yy] = activated_magic_wall;
6465 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6466 game.magic_wall_active = TRUE;
6468 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6469 SND_MAGIC_WALL_ACTIVATING :
6470 smashed == EL_BD_MAGIC_WALL ?
6471 SND_BD_MAGIC_WALL_ACTIVATING :
6472 SND_DC_MAGIC_WALL_ACTIVATING));
6475 if (IS_PLAYER(x, y + 1))
6477 if (CAN_SMASH_PLAYER(element))
6479 KillPlayerUnlessEnemyProtected(x, y + 1);
6483 else if (smashed == EL_PENGUIN)
6485 if (CAN_SMASH_PLAYER(element))
6491 else if (element == EL_BD_DIAMOND)
6493 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6499 else if (((element == EL_SP_INFOTRON ||
6500 element == EL_SP_ZONK) &&
6501 (smashed == EL_SP_SNIKSNAK ||
6502 smashed == EL_SP_ELECTRON ||
6503 smashed == EL_SP_DISK_ORANGE)) ||
6504 (element == EL_SP_INFOTRON &&
6505 smashed == EL_SP_DISK_YELLOW))
6510 else if (CAN_SMASH_EVERYTHING(element))
6512 if (IS_CLASSIC_ENEMY(smashed) ||
6513 CAN_EXPLODE_SMASHED(smashed))
6518 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6520 if (smashed == EL_LAMP ||
6521 smashed == EL_LAMP_ACTIVE)
6526 else if (smashed == EL_NUT)
6528 Feld[x][y + 1] = EL_NUT_BREAKING;
6529 PlayLevelSound(x, y, SND_NUT_BREAKING);
6530 RaiseScoreElement(EL_NUT);
6533 else if (smashed == EL_PEARL)
6535 ResetGfxAnimation(x, y);
6537 Feld[x][y + 1] = EL_PEARL_BREAKING;
6538 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6541 else if (smashed == EL_DIAMOND)
6543 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6544 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6547 else if (IS_BELT_SWITCH(smashed))
6549 ToggleBeltSwitch(x, y + 1);
6551 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6552 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6553 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6554 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6556 ToggleSwitchgateSwitch(x, y + 1);
6558 else if (smashed == EL_LIGHT_SWITCH ||
6559 smashed == EL_LIGHT_SWITCH_ACTIVE)
6561 ToggleLightSwitch(x, y + 1);
6565 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6567 CheckElementChangeBySide(x, y + 1, smashed, element,
6568 CE_SWITCHED, CH_SIDE_TOP);
6569 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6575 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6580 // play sound of magic wall / mill
6582 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6583 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6584 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6586 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6587 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6588 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6589 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6590 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6591 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6596 // play sound of object that hits the ground
6597 if (last_line || object_hit)
6598 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6601 static void TurnRoundExt(int x, int y)
6613 { 0, 0 }, { 0, 0 }, { 0, 0 },
6618 int left, right, back;
6622 { MV_DOWN, MV_UP, MV_RIGHT },
6623 { MV_UP, MV_DOWN, MV_LEFT },
6625 { MV_LEFT, MV_RIGHT, MV_DOWN },
6629 { MV_RIGHT, MV_LEFT, MV_UP }
6632 int element = Feld[x][y];
6633 int move_pattern = element_info[element].move_pattern;
6635 int old_move_dir = MovDir[x][y];
6636 int left_dir = turn[old_move_dir].left;
6637 int right_dir = turn[old_move_dir].right;
6638 int back_dir = turn[old_move_dir].back;
6640 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6641 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6642 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6643 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6645 int left_x = x + left_dx, left_y = y + left_dy;
6646 int right_x = x + right_dx, right_y = y + right_dy;
6647 int move_x = x + move_dx, move_y = y + move_dy;
6651 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6653 TestIfBadThingTouchesOtherBadThing(x, y);
6655 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6656 MovDir[x][y] = right_dir;
6657 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6658 MovDir[x][y] = left_dir;
6660 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6662 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6665 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6667 TestIfBadThingTouchesOtherBadThing(x, y);
6669 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6670 MovDir[x][y] = left_dir;
6671 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6672 MovDir[x][y] = right_dir;
6674 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6676 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6679 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6681 TestIfBadThingTouchesOtherBadThing(x, y);
6683 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6684 MovDir[x][y] = left_dir;
6685 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6686 MovDir[x][y] = right_dir;
6688 if (MovDir[x][y] != old_move_dir)
6691 else if (element == EL_YAMYAM)
6693 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6694 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6696 if (can_turn_left && can_turn_right)
6697 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6698 else if (can_turn_left)
6699 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6700 else if (can_turn_right)
6701 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6703 MovDir[x][y] = back_dir;
6705 MovDelay[x][y] = 16 + 16 * RND(3);
6707 else if (element == EL_DARK_YAMYAM)
6709 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6711 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6714 if (can_turn_left && can_turn_right)
6715 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6716 else if (can_turn_left)
6717 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6718 else if (can_turn_right)
6719 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6721 MovDir[x][y] = back_dir;
6723 MovDelay[x][y] = 16 + 16 * RND(3);
6725 else if (element == EL_PACMAN)
6727 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6728 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6730 if (can_turn_left && can_turn_right)
6731 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6732 else if (can_turn_left)
6733 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6734 else if (can_turn_right)
6735 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6737 MovDir[x][y] = back_dir;
6739 MovDelay[x][y] = 6 + RND(40);
6741 else if (element == EL_PIG)
6743 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6744 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6745 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6746 boolean should_turn_left, should_turn_right, should_move_on;
6748 int rnd = RND(rnd_value);
6750 should_turn_left = (can_turn_left &&
6752 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6753 y + back_dy + left_dy)));
6754 should_turn_right = (can_turn_right &&
6756 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6757 y + back_dy + right_dy)));
6758 should_move_on = (can_move_on &&
6761 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6762 y + move_dy + left_dy) ||
6763 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6764 y + move_dy + right_dy)));
6766 if (should_turn_left || should_turn_right || should_move_on)
6768 if (should_turn_left && should_turn_right && should_move_on)
6769 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6770 rnd < 2 * rnd_value / 3 ? right_dir :
6772 else if (should_turn_left && should_turn_right)
6773 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6774 else if (should_turn_left && should_move_on)
6775 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6776 else if (should_turn_right && should_move_on)
6777 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6778 else if (should_turn_left)
6779 MovDir[x][y] = left_dir;
6780 else if (should_turn_right)
6781 MovDir[x][y] = right_dir;
6782 else if (should_move_on)
6783 MovDir[x][y] = old_move_dir;
6785 else if (can_move_on && rnd > rnd_value / 8)
6786 MovDir[x][y] = old_move_dir;
6787 else if (can_turn_left && can_turn_right)
6788 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6789 else if (can_turn_left && rnd > rnd_value / 8)
6790 MovDir[x][y] = left_dir;
6791 else if (can_turn_right && rnd > rnd_value/8)
6792 MovDir[x][y] = right_dir;
6794 MovDir[x][y] = back_dir;
6796 xx = x + move_xy[MovDir[x][y]].dx;
6797 yy = y + move_xy[MovDir[x][y]].dy;
6799 if (!IN_LEV_FIELD(xx, yy) ||
6800 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6801 MovDir[x][y] = old_move_dir;
6805 else if (element == EL_DRAGON)
6807 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6808 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6809 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6811 int rnd = RND(rnd_value);
6813 if (can_move_on && rnd > rnd_value / 8)
6814 MovDir[x][y] = old_move_dir;
6815 else if (can_turn_left && can_turn_right)
6816 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6817 else if (can_turn_left && rnd > rnd_value / 8)
6818 MovDir[x][y] = left_dir;
6819 else if (can_turn_right && rnd > rnd_value / 8)
6820 MovDir[x][y] = right_dir;
6822 MovDir[x][y] = back_dir;
6824 xx = x + move_xy[MovDir[x][y]].dx;
6825 yy = y + move_xy[MovDir[x][y]].dy;
6827 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6828 MovDir[x][y] = old_move_dir;
6832 else if (element == EL_MOLE)
6834 boolean can_move_on =
6835 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6836 IS_AMOEBOID(Feld[move_x][move_y]) ||
6837 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6840 boolean can_turn_left =
6841 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6842 IS_AMOEBOID(Feld[left_x][left_y])));
6844 boolean can_turn_right =
6845 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6846 IS_AMOEBOID(Feld[right_x][right_y])));
6848 if (can_turn_left && can_turn_right)
6849 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6850 else if (can_turn_left)
6851 MovDir[x][y] = left_dir;
6853 MovDir[x][y] = right_dir;
6856 if (MovDir[x][y] != old_move_dir)
6859 else if (element == EL_BALLOON)
6861 MovDir[x][y] = game.wind_direction;
6864 else if (element == EL_SPRING)
6866 if (MovDir[x][y] & MV_HORIZONTAL)
6868 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6869 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6871 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6872 ResetGfxAnimation(move_x, move_y);
6873 TEST_DrawLevelField(move_x, move_y);
6875 MovDir[x][y] = back_dir;
6877 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6878 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6879 MovDir[x][y] = MV_NONE;
6884 else if (element == EL_ROBOT ||
6885 element == EL_SATELLITE ||
6886 element == EL_PENGUIN ||
6887 element == EL_EMC_ANDROID)
6889 int attr_x = -1, attr_y = -1;
6891 if (game.all_players_gone)
6893 attr_x = game.exit_x;
6894 attr_y = game.exit_y;
6900 for (i = 0; i < MAX_PLAYERS; i++)
6902 struct PlayerInfo *player = &stored_player[i];
6903 int jx = player->jx, jy = player->jy;
6905 if (!player->active)
6909 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6917 if (element == EL_ROBOT &&
6918 game.robot_wheel_x >= 0 &&
6919 game.robot_wheel_y >= 0 &&
6920 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
6921 game.engine_version < VERSION_IDENT(3,1,0,0)))
6923 attr_x = game.robot_wheel_x;
6924 attr_y = game.robot_wheel_y;
6927 if (element == EL_PENGUIN)
6930 static int xy[4][2] =
6938 for (i = 0; i < NUM_DIRECTIONS; i++)
6940 int ex = x + xy[i][0];
6941 int ey = y + xy[i][1];
6943 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
6944 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
6945 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
6946 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
6955 MovDir[x][y] = MV_NONE;
6957 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
6958 else if (attr_x > x)
6959 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
6961 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
6962 else if (attr_y > y)
6963 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
6965 if (element == EL_ROBOT)
6969 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6970 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
6971 Moving2Blocked(x, y, &newx, &newy);
6973 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
6974 MovDelay[x][y] = 8 + 8 * !RND(3);
6976 MovDelay[x][y] = 16;
6978 else if (element == EL_PENGUIN)
6984 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6986 boolean first_horiz = RND(2);
6987 int new_move_dir = MovDir[x][y];
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))
6997 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6998 Moving2Blocked(x, y, &newx, &newy);
7000 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7003 MovDir[x][y] = old_move_dir;
7007 else if (element == EL_SATELLITE)
7013 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7015 boolean first_horiz = RND(2);
7016 int new_move_dir = MovDir[x][y];
7019 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7020 Moving2Blocked(x, y, &newx, &newy);
7022 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7026 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7027 Moving2Blocked(x, y, &newx, &newy);
7029 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7032 MovDir[x][y] = old_move_dir;
7036 else if (element == EL_EMC_ANDROID)
7038 static int check_pos[16] =
7040 -1, // 0 => (invalid)
7043 -1, // 3 => (invalid)
7045 0, // 5 => MV_LEFT | MV_UP
7046 2, // 6 => MV_RIGHT | MV_UP
7047 -1, // 7 => (invalid)
7049 6, // 9 => MV_LEFT | MV_DOWN
7050 4, // 10 => MV_RIGHT | MV_DOWN
7051 -1, // 11 => (invalid)
7052 -1, // 12 => (invalid)
7053 -1, // 13 => (invalid)
7054 -1, // 14 => (invalid)
7055 -1, // 15 => (invalid)
7063 { -1, -1, MV_LEFT | MV_UP },
7065 { +1, -1, MV_RIGHT | MV_UP },
7066 { +1, 0, MV_RIGHT },
7067 { +1, +1, MV_RIGHT | MV_DOWN },
7069 { -1, +1, MV_LEFT | MV_DOWN },
7072 int start_pos, check_order;
7073 boolean can_clone = FALSE;
7076 // check if there is any free field around current position
7077 for (i = 0; i < 8; i++)
7079 int newx = x + check_xy[i].dx;
7080 int newy = y + check_xy[i].dy;
7082 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7090 if (can_clone) // randomly find an element to clone
7094 start_pos = check_pos[RND(8)];
7095 check_order = (RND(2) ? -1 : +1);
7097 for (i = 0; i < 8; i++)
7099 int pos_raw = start_pos + i * check_order;
7100 int pos = (pos_raw + 8) % 8;
7101 int newx = x + check_xy[pos].dx;
7102 int newy = y + check_xy[pos].dy;
7104 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7106 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7107 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7109 Store[x][y] = Feld[newx][newy];
7118 if (can_clone) // randomly find a direction to move
7122 start_pos = check_pos[RND(8)];
7123 check_order = (RND(2) ? -1 : +1);
7125 for (i = 0; i < 8; i++)
7127 int pos_raw = start_pos + i * check_order;
7128 int pos = (pos_raw + 8) % 8;
7129 int newx = x + check_xy[pos].dx;
7130 int newy = y + check_xy[pos].dy;
7131 int new_move_dir = check_xy[pos].dir;
7133 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7135 MovDir[x][y] = new_move_dir;
7136 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7145 if (can_clone) // cloning and moving successful
7148 // cannot clone -- try to move towards player
7150 start_pos = check_pos[MovDir[x][y] & 0x0f];
7151 check_order = (RND(2) ? -1 : +1);
7153 for (i = 0; i < 3; i++)
7155 // first check start_pos, then previous/next or (next/previous) pos
7156 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7157 int pos = (pos_raw + 8) % 8;
7158 int newx = x + check_xy[pos].dx;
7159 int newy = y + check_xy[pos].dy;
7160 int new_move_dir = check_xy[pos].dir;
7162 if (IS_PLAYER(newx, newy))
7165 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7167 MovDir[x][y] = new_move_dir;
7168 MovDelay[x][y] = level.android_move_time * 8 + 1;
7175 else if (move_pattern == MV_TURNING_LEFT ||
7176 move_pattern == MV_TURNING_RIGHT ||
7177 move_pattern == MV_TURNING_LEFT_RIGHT ||
7178 move_pattern == MV_TURNING_RIGHT_LEFT ||
7179 move_pattern == MV_TURNING_RANDOM ||
7180 move_pattern == MV_ALL_DIRECTIONS)
7182 boolean can_turn_left =
7183 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7184 boolean can_turn_right =
7185 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7187 if (element_info[element].move_stepsize == 0) // "not moving"
7190 if (move_pattern == MV_TURNING_LEFT)
7191 MovDir[x][y] = left_dir;
7192 else if (move_pattern == MV_TURNING_RIGHT)
7193 MovDir[x][y] = right_dir;
7194 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7195 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7196 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7197 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7198 else if (move_pattern == MV_TURNING_RANDOM)
7199 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7200 can_turn_right && !can_turn_left ? right_dir :
7201 RND(2) ? left_dir : right_dir);
7202 else if (can_turn_left && can_turn_right)
7203 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7204 else if (can_turn_left)
7205 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7206 else if (can_turn_right)
7207 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7209 MovDir[x][y] = back_dir;
7211 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7213 else if (move_pattern == MV_HORIZONTAL ||
7214 move_pattern == MV_VERTICAL)
7216 if (move_pattern & old_move_dir)
7217 MovDir[x][y] = back_dir;
7218 else if (move_pattern == MV_HORIZONTAL)
7219 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7220 else if (move_pattern == MV_VERTICAL)
7221 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7223 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7225 else if (move_pattern & MV_ANY_DIRECTION)
7227 MovDir[x][y] = move_pattern;
7228 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7230 else if (move_pattern & MV_WIND_DIRECTION)
7232 MovDir[x][y] = game.wind_direction;
7233 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7235 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7237 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7238 MovDir[x][y] = left_dir;
7239 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7240 MovDir[x][y] = right_dir;
7242 if (MovDir[x][y] != old_move_dir)
7243 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7245 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7247 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7248 MovDir[x][y] = right_dir;
7249 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7250 MovDir[x][y] = left_dir;
7252 if (MovDir[x][y] != old_move_dir)
7253 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7255 else if (move_pattern == MV_TOWARDS_PLAYER ||
7256 move_pattern == MV_AWAY_FROM_PLAYER)
7258 int attr_x = -1, attr_y = -1;
7260 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7262 if (game.all_players_gone)
7264 attr_x = game.exit_x;
7265 attr_y = game.exit_y;
7271 for (i = 0; i < MAX_PLAYERS; i++)
7273 struct PlayerInfo *player = &stored_player[i];
7274 int jx = player->jx, jy = player->jy;
7276 if (!player->active)
7280 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7288 MovDir[x][y] = MV_NONE;
7290 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7291 else if (attr_x > x)
7292 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7294 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7295 else if (attr_y > y)
7296 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7298 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7300 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7302 boolean first_horiz = RND(2);
7303 int new_move_dir = MovDir[x][y];
7305 if (element_info[element].move_stepsize == 0) // "not moving"
7307 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7308 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
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))
7321 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7322 Moving2Blocked(x, y, &newx, &newy);
7324 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7327 MovDir[x][y] = old_move_dir;
7330 else if (move_pattern == MV_WHEN_PUSHED ||
7331 move_pattern == MV_WHEN_DROPPED)
7333 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7334 MovDir[x][y] = MV_NONE;
7338 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7340 static int test_xy[7][2] =
7350 static int test_dir[7] =
7360 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7361 int move_preference = -1000000; // start with very low preference
7362 int new_move_dir = MV_NONE;
7363 int start_test = RND(4);
7366 for (i = 0; i < NUM_DIRECTIONS; i++)
7368 int move_dir = test_dir[start_test + i];
7369 int move_dir_preference;
7371 xx = x + test_xy[start_test + i][0];
7372 yy = y + test_xy[start_test + i][1];
7374 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7375 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7377 new_move_dir = move_dir;
7382 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7385 move_dir_preference = -1 * RunnerVisit[xx][yy];
7386 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7387 move_dir_preference = PlayerVisit[xx][yy];
7389 if (move_dir_preference > move_preference)
7391 // prefer field that has not been visited for the longest time
7392 move_preference = move_dir_preference;
7393 new_move_dir = move_dir;
7395 else if (move_dir_preference == move_preference &&
7396 move_dir == old_move_dir)
7398 // prefer last direction when all directions are preferred equally
7399 move_preference = move_dir_preference;
7400 new_move_dir = move_dir;
7404 MovDir[x][y] = new_move_dir;
7405 if (old_move_dir != new_move_dir)
7406 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7410 static void TurnRound(int x, int y)
7412 int direction = MovDir[x][y];
7416 GfxDir[x][y] = MovDir[x][y];
7418 if (direction != MovDir[x][y])
7422 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7424 ResetGfxFrame(x, y);
7427 static boolean JustBeingPushed(int x, int y)
7431 for (i = 0; i < MAX_PLAYERS; i++)
7433 struct PlayerInfo *player = &stored_player[i];
7435 if (player->active && player->is_pushing && player->MovPos)
7437 int next_jx = player->jx + (player->jx - player->last_jx);
7438 int next_jy = player->jy + (player->jy - player->last_jy);
7440 if (x == next_jx && y == next_jy)
7448 static void StartMoving(int x, int y)
7450 boolean started_moving = FALSE; // some elements can fall _and_ move
7451 int element = Feld[x][y];
7456 if (MovDelay[x][y] == 0)
7457 GfxAction[x][y] = ACTION_DEFAULT;
7459 if (CAN_FALL(element) && y < lev_fieldy - 1)
7461 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7462 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7463 if (JustBeingPushed(x, y))
7466 if (element == EL_QUICKSAND_FULL)
7468 if (IS_FREE(x, y + 1))
7470 InitMovingField(x, y, MV_DOWN);
7471 started_moving = TRUE;
7473 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7474 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7475 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7476 Store[x][y] = EL_ROCK;
7478 Store[x][y] = EL_ROCK;
7481 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7483 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7485 if (!MovDelay[x][y])
7487 MovDelay[x][y] = TILEY + 1;
7489 ResetGfxAnimation(x, y);
7490 ResetGfxAnimation(x, y + 1);
7495 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7496 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7503 Feld[x][y] = EL_QUICKSAND_EMPTY;
7504 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7505 Store[x][y + 1] = Store[x][y];
7508 PlayLevelSoundAction(x, y, ACTION_FILLING);
7510 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7512 if (!MovDelay[x][y])
7514 MovDelay[x][y] = TILEY + 1;
7516 ResetGfxAnimation(x, y);
7517 ResetGfxAnimation(x, y + 1);
7522 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7523 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7530 Feld[x][y] = EL_QUICKSAND_EMPTY;
7531 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7532 Store[x][y + 1] = Store[x][y];
7535 PlayLevelSoundAction(x, y, ACTION_FILLING);
7538 else if (element == EL_QUICKSAND_FAST_FULL)
7540 if (IS_FREE(x, y + 1))
7542 InitMovingField(x, y, MV_DOWN);
7543 started_moving = TRUE;
7545 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7546 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7547 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7548 Store[x][y] = EL_ROCK;
7550 Store[x][y] = EL_ROCK;
7553 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7555 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7557 if (!MovDelay[x][y])
7559 MovDelay[x][y] = TILEY + 1;
7561 ResetGfxAnimation(x, y);
7562 ResetGfxAnimation(x, y + 1);
7567 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7568 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7575 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7576 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7577 Store[x][y + 1] = Store[x][y];
7580 PlayLevelSoundAction(x, y, ACTION_FILLING);
7582 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7584 if (!MovDelay[x][y])
7586 MovDelay[x][y] = TILEY + 1;
7588 ResetGfxAnimation(x, y);
7589 ResetGfxAnimation(x, y + 1);
7594 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7595 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7602 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7603 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7604 Store[x][y + 1] = Store[x][y];
7607 PlayLevelSoundAction(x, y, ACTION_FILLING);
7610 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7611 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7613 InitMovingField(x, y, MV_DOWN);
7614 started_moving = TRUE;
7616 Feld[x][y] = EL_QUICKSAND_FILLING;
7617 Store[x][y] = element;
7619 PlayLevelSoundAction(x, y, ACTION_FILLING);
7621 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7622 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7624 InitMovingField(x, y, MV_DOWN);
7625 started_moving = TRUE;
7627 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7628 Store[x][y] = element;
7630 PlayLevelSoundAction(x, y, ACTION_FILLING);
7632 else if (element == EL_MAGIC_WALL_FULL)
7634 if (IS_FREE(x, y + 1))
7636 InitMovingField(x, y, MV_DOWN);
7637 started_moving = TRUE;
7639 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7640 Store[x][y] = EL_CHANGED(Store[x][y]);
7642 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7644 if (!MovDelay[x][y])
7645 MovDelay[x][y] = TILEY / 4 + 1;
7654 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7655 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7656 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7660 else if (element == EL_BD_MAGIC_WALL_FULL)
7662 if (IS_FREE(x, y + 1))
7664 InitMovingField(x, y, MV_DOWN);
7665 started_moving = TRUE;
7667 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7668 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7670 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7672 if (!MovDelay[x][y])
7673 MovDelay[x][y] = TILEY / 4 + 1;
7682 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7683 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7684 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7688 else if (element == EL_DC_MAGIC_WALL_FULL)
7690 if (IS_FREE(x, y + 1))
7692 InitMovingField(x, y, MV_DOWN);
7693 started_moving = TRUE;
7695 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7696 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7698 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7700 if (!MovDelay[x][y])
7701 MovDelay[x][y] = TILEY / 4 + 1;
7710 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7711 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7712 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7716 else if ((CAN_PASS_MAGIC_WALL(element) &&
7717 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7718 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7719 (CAN_PASS_DC_MAGIC_WALL(element) &&
7720 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7723 InitMovingField(x, y, MV_DOWN);
7724 started_moving = TRUE;
7727 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7728 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7729 EL_DC_MAGIC_WALL_FILLING);
7730 Store[x][y] = element;
7732 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7734 SplashAcid(x, y + 1);
7736 InitMovingField(x, y, MV_DOWN);
7737 started_moving = TRUE;
7739 Store[x][y] = EL_ACID;
7742 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7743 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7744 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7745 CAN_FALL(element) && WasJustFalling[x][y] &&
7746 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7748 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7749 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7750 (Feld[x][y + 1] == EL_BLOCKED)))
7752 /* this is needed for a special case not covered by calling "Impact()"
7753 from "ContinueMoving()": if an element moves to a tile directly below
7754 another element which was just falling on that tile (which was empty
7755 in the previous frame), the falling element above would just stop
7756 instead of smashing the element below (in previous version, the above
7757 element was just checked for "moving" instead of "falling", resulting
7758 in incorrect smashes caused by horizontal movement of the above
7759 element; also, the case of the player being the element to smash was
7760 simply not covered here... :-/ ) */
7762 CheckCollision[x][y] = 0;
7763 CheckImpact[x][y] = 0;
7767 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7769 if (MovDir[x][y] == MV_NONE)
7771 InitMovingField(x, y, MV_DOWN);
7772 started_moving = TRUE;
7775 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7777 if (WasJustFalling[x][y]) // prevent animation from being restarted
7778 MovDir[x][y] = MV_DOWN;
7780 InitMovingField(x, y, MV_DOWN);
7781 started_moving = TRUE;
7783 else if (element == EL_AMOEBA_DROP)
7785 Feld[x][y] = EL_AMOEBA_GROWING;
7786 Store[x][y] = EL_AMOEBA_WET;
7788 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7789 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7790 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7791 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7793 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7794 (IS_FREE(x - 1, y + 1) ||
7795 Feld[x - 1][y + 1] == EL_ACID));
7796 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7797 (IS_FREE(x + 1, y + 1) ||
7798 Feld[x + 1][y + 1] == EL_ACID));
7799 boolean can_fall_any = (can_fall_left || can_fall_right);
7800 boolean can_fall_both = (can_fall_left && can_fall_right);
7801 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7803 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7805 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7806 can_fall_right = FALSE;
7807 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7808 can_fall_left = FALSE;
7809 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7810 can_fall_right = FALSE;
7811 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7812 can_fall_left = FALSE;
7814 can_fall_any = (can_fall_left || can_fall_right);
7815 can_fall_both = FALSE;
7820 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7821 can_fall_right = FALSE; // slip down on left side
7823 can_fall_left = !(can_fall_right = RND(2));
7825 can_fall_both = FALSE;
7830 // if not determined otherwise, prefer left side for slipping down
7831 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7832 started_moving = TRUE;
7835 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7837 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7838 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7839 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7840 int belt_dir = game.belt_dir[belt_nr];
7842 if ((belt_dir == MV_LEFT && left_is_free) ||
7843 (belt_dir == MV_RIGHT && right_is_free))
7845 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7847 InitMovingField(x, y, belt_dir);
7848 started_moving = TRUE;
7850 Pushed[x][y] = TRUE;
7851 Pushed[nextx][y] = TRUE;
7853 GfxAction[x][y] = ACTION_DEFAULT;
7857 MovDir[x][y] = 0; // if element was moving, stop it
7862 // not "else if" because of elements that can fall and move (EL_SPRING)
7863 if (CAN_MOVE(element) && !started_moving)
7865 int move_pattern = element_info[element].move_pattern;
7868 Moving2Blocked(x, y, &newx, &newy);
7870 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7873 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7874 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7876 WasJustMoving[x][y] = 0;
7877 CheckCollision[x][y] = 0;
7879 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7881 if (Feld[x][y] != element) // element has changed
7885 if (!MovDelay[x][y]) // start new movement phase
7887 // all objects that can change their move direction after each step
7888 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7890 if (element != EL_YAMYAM &&
7891 element != EL_DARK_YAMYAM &&
7892 element != EL_PACMAN &&
7893 !(move_pattern & MV_ANY_DIRECTION) &&
7894 move_pattern != MV_TURNING_LEFT &&
7895 move_pattern != MV_TURNING_RIGHT &&
7896 move_pattern != MV_TURNING_LEFT_RIGHT &&
7897 move_pattern != MV_TURNING_RIGHT_LEFT &&
7898 move_pattern != MV_TURNING_RANDOM)
7902 if (MovDelay[x][y] && (element == EL_BUG ||
7903 element == EL_SPACESHIP ||
7904 element == EL_SP_SNIKSNAK ||
7905 element == EL_SP_ELECTRON ||
7906 element == EL_MOLE))
7907 TEST_DrawLevelField(x, y);
7911 if (MovDelay[x][y]) // wait some time before next movement
7915 if (element == EL_ROBOT ||
7916 element == EL_YAMYAM ||
7917 element == EL_DARK_YAMYAM)
7919 DrawLevelElementAnimationIfNeeded(x, y, element);
7920 PlayLevelSoundAction(x, y, ACTION_WAITING);
7922 else if (element == EL_SP_ELECTRON)
7923 DrawLevelElementAnimationIfNeeded(x, y, element);
7924 else if (element == EL_DRAGON)
7927 int dir = MovDir[x][y];
7928 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
7929 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
7930 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
7931 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
7932 dir == MV_UP ? IMG_FLAMES_1_UP :
7933 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
7934 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
7936 GfxAction[x][y] = ACTION_ATTACKING;
7938 if (IS_PLAYER(x, y))
7939 DrawPlayerField(x, y);
7941 TEST_DrawLevelField(x, y);
7943 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
7945 for (i = 1; i <= 3; i++)
7947 int xx = x + i * dx;
7948 int yy = y + i * dy;
7949 int sx = SCREENX(xx);
7950 int sy = SCREENY(yy);
7951 int flame_graphic = graphic + (i - 1);
7953 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
7958 int flamed = MovingOrBlocked2Element(xx, yy);
7960 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
7963 RemoveMovingField(xx, yy);
7965 ChangeDelay[xx][yy] = 0;
7967 Feld[xx][yy] = EL_FLAMES;
7969 if (IN_SCR_FIELD(sx, sy))
7971 TEST_DrawLevelFieldCrumbled(xx, yy);
7972 DrawGraphic(sx, sy, flame_graphic, frame);
7977 if (Feld[xx][yy] == EL_FLAMES)
7978 Feld[xx][yy] = EL_EMPTY;
7979 TEST_DrawLevelField(xx, yy);
7984 if (MovDelay[x][y]) // element still has to wait some time
7986 PlayLevelSoundAction(x, y, ACTION_WAITING);
7992 // now make next step
7994 Moving2Blocked(x, y, &newx, &newy); // get next screen position
7996 if (DONT_COLLIDE_WITH(element) &&
7997 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
7998 !PLAYER_ENEMY_PROTECTED(newx, newy))
8000 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8005 else if (CAN_MOVE_INTO_ACID(element) &&
8006 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
8007 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8008 (MovDir[x][y] == MV_DOWN ||
8009 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8011 SplashAcid(newx, newy);
8012 Store[x][y] = EL_ACID;
8014 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8016 if (Feld[newx][newy] == EL_EXIT_OPEN ||
8017 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
8018 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
8019 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8022 TEST_DrawLevelField(x, y);
8024 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8025 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8026 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8028 game.friends_still_needed--;
8029 if (!game.friends_still_needed &&
8031 game.all_players_gone)
8036 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
8038 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8039 TEST_DrawLevelField(newx, newy);
8041 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8043 else if (!IS_FREE(newx, newy))
8045 GfxAction[x][y] = ACTION_WAITING;
8047 if (IS_PLAYER(x, y))
8048 DrawPlayerField(x, y);
8050 TEST_DrawLevelField(x, y);
8055 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8057 if (IS_FOOD_PIG(Feld[newx][newy]))
8059 if (IS_MOVING(newx, newy))
8060 RemoveMovingField(newx, newy);
8063 Feld[newx][newy] = EL_EMPTY;
8064 TEST_DrawLevelField(newx, newy);
8067 PlayLevelSound(x, y, SND_PIG_DIGGING);
8069 else if (!IS_FREE(newx, newy))
8071 if (IS_PLAYER(x, y))
8072 DrawPlayerField(x, y);
8074 TEST_DrawLevelField(x, y);
8079 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8081 if (Store[x][y] != EL_EMPTY)
8083 boolean can_clone = FALSE;
8086 // check if element to clone is still there
8087 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8089 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8097 // cannot clone or target field not free anymore -- do not clone
8098 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8099 Store[x][y] = EL_EMPTY;
8102 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8104 if (IS_MV_DIAGONAL(MovDir[x][y]))
8106 int diagonal_move_dir = MovDir[x][y];
8107 int stored = Store[x][y];
8108 int change_delay = 8;
8111 // android is moving diagonally
8113 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8115 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8116 GfxElement[x][y] = EL_EMC_ANDROID;
8117 GfxAction[x][y] = ACTION_SHRINKING;
8118 GfxDir[x][y] = diagonal_move_dir;
8119 ChangeDelay[x][y] = change_delay;
8121 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8124 DrawLevelGraphicAnimation(x, y, graphic);
8125 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8127 if (Feld[newx][newy] == EL_ACID)
8129 SplashAcid(newx, newy);
8134 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8136 Store[newx][newy] = EL_EMC_ANDROID;
8137 GfxElement[newx][newy] = EL_EMC_ANDROID;
8138 GfxAction[newx][newy] = ACTION_GROWING;
8139 GfxDir[newx][newy] = diagonal_move_dir;
8140 ChangeDelay[newx][newy] = change_delay;
8142 graphic = el_act_dir2img(GfxElement[newx][newy],
8143 GfxAction[newx][newy], GfxDir[newx][newy]);
8145 DrawLevelGraphicAnimation(newx, newy, graphic);
8146 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8152 Feld[newx][newy] = EL_EMPTY;
8153 TEST_DrawLevelField(newx, newy);
8155 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8158 else if (!IS_FREE(newx, newy))
8163 else if (IS_CUSTOM_ELEMENT(element) &&
8164 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8166 if (!DigFieldByCE(newx, newy, element))
8169 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8171 RunnerVisit[x][y] = FrameCounter;
8172 PlayerVisit[x][y] /= 8; // expire player visit path
8175 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8177 if (!IS_FREE(newx, newy))
8179 if (IS_PLAYER(x, y))
8180 DrawPlayerField(x, y);
8182 TEST_DrawLevelField(x, y);
8188 boolean wanna_flame = !RND(10);
8189 int dx = newx - x, dy = newy - y;
8190 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8191 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8192 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8193 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8194 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8195 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8198 IS_CLASSIC_ENEMY(element1) ||
8199 IS_CLASSIC_ENEMY(element2)) &&
8200 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8201 element1 != EL_FLAMES && element2 != EL_FLAMES)
8203 ResetGfxAnimation(x, y);
8204 GfxAction[x][y] = ACTION_ATTACKING;
8206 if (IS_PLAYER(x, y))
8207 DrawPlayerField(x, y);
8209 TEST_DrawLevelField(x, y);
8211 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8213 MovDelay[x][y] = 50;
8215 Feld[newx][newy] = EL_FLAMES;
8216 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8217 Feld[newx1][newy1] = EL_FLAMES;
8218 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8219 Feld[newx2][newy2] = EL_FLAMES;
8225 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8226 Feld[newx][newy] == EL_DIAMOND)
8228 if (IS_MOVING(newx, newy))
8229 RemoveMovingField(newx, newy);
8232 Feld[newx][newy] = EL_EMPTY;
8233 TEST_DrawLevelField(newx, newy);
8236 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8238 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8239 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8241 if (AmoebaNr[newx][newy])
8243 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8244 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8245 Feld[newx][newy] == EL_BD_AMOEBA)
8246 AmoebaCnt[AmoebaNr[newx][newy]]--;
8249 if (IS_MOVING(newx, newy))
8251 RemoveMovingField(newx, newy);
8255 Feld[newx][newy] = EL_EMPTY;
8256 TEST_DrawLevelField(newx, newy);
8259 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8261 else if ((element == EL_PACMAN || element == EL_MOLE)
8262 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8264 if (AmoebaNr[newx][newy])
8266 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8267 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8268 Feld[newx][newy] == EL_BD_AMOEBA)
8269 AmoebaCnt[AmoebaNr[newx][newy]]--;
8272 if (element == EL_MOLE)
8274 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8275 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8277 ResetGfxAnimation(x, y);
8278 GfxAction[x][y] = ACTION_DIGGING;
8279 TEST_DrawLevelField(x, y);
8281 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8283 return; // wait for shrinking amoeba
8285 else // element == EL_PACMAN
8287 Feld[newx][newy] = EL_EMPTY;
8288 TEST_DrawLevelField(newx, newy);
8289 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8292 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8293 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8294 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8296 // wait for shrinking amoeba to completely disappear
8299 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8301 // object was running against a wall
8305 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8306 DrawLevelElementAnimation(x, y, element);
8308 if (DONT_TOUCH(element))
8309 TestIfBadThingTouchesPlayer(x, y);
8314 InitMovingField(x, y, MovDir[x][y]);
8316 PlayLevelSoundAction(x, y, ACTION_MOVING);
8320 ContinueMoving(x, y);
8323 void ContinueMoving(int x, int y)
8325 int element = Feld[x][y];
8326 struct ElementInfo *ei = &element_info[element];
8327 int direction = MovDir[x][y];
8328 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8329 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8330 int newx = x + dx, newy = y + dy;
8331 int stored = Store[x][y];
8332 int stored_new = Store[newx][newy];
8333 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8334 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8335 boolean last_line = (newy == lev_fieldy - 1);
8337 MovPos[x][y] += getElementMoveStepsize(x, y);
8339 if (pushed_by_player) // special case: moving object pushed by player
8340 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8342 if (ABS(MovPos[x][y]) < TILEX)
8344 TEST_DrawLevelField(x, y);
8346 return; // element is still moving
8349 // element reached destination field
8351 Feld[x][y] = EL_EMPTY;
8352 Feld[newx][newy] = element;
8353 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8355 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8357 element = Feld[newx][newy] = EL_ACID;
8359 else if (element == EL_MOLE)
8361 Feld[x][y] = EL_SAND;
8363 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8365 else if (element == EL_QUICKSAND_FILLING)
8367 element = Feld[newx][newy] = get_next_element(element);
8368 Store[newx][newy] = Store[x][y];
8370 else if (element == EL_QUICKSAND_EMPTYING)
8372 Feld[x][y] = get_next_element(element);
8373 element = Feld[newx][newy] = Store[x][y];
8375 else if (element == EL_QUICKSAND_FAST_FILLING)
8377 element = Feld[newx][newy] = get_next_element(element);
8378 Store[newx][newy] = Store[x][y];
8380 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8382 Feld[x][y] = get_next_element(element);
8383 element = Feld[newx][newy] = Store[x][y];
8385 else if (element == EL_MAGIC_WALL_FILLING)
8387 element = Feld[newx][newy] = get_next_element(element);
8388 if (!game.magic_wall_active)
8389 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8390 Store[newx][newy] = Store[x][y];
8392 else if (element == EL_MAGIC_WALL_EMPTYING)
8394 Feld[x][y] = get_next_element(element);
8395 if (!game.magic_wall_active)
8396 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8397 element = Feld[newx][newy] = Store[x][y];
8399 InitField(newx, newy, FALSE);
8401 else if (element == EL_BD_MAGIC_WALL_FILLING)
8403 element = Feld[newx][newy] = get_next_element(element);
8404 if (!game.magic_wall_active)
8405 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8406 Store[newx][newy] = Store[x][y];
8408 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8410 Feld[x][y] = get_next_element(element);
8411 if (!game.magic_wall_active)
8412 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8413 element = Feld[newx][newy] = Store[x][y];
8415 InitField(newx, newy, FALSE);
8417 else if (element == EL_DC_MAGIC_WALL_FILLING)
8419 element = Feld[newx][newy] = get_next_element(element);
8420 if (!game.magic_wall_active)
8421 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8422 Store[newx][newy] = Store[x][y];
8424 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8426 Feld[x][y] = get_next_element(element);
8427 if (!game.magic_wall_active)
8428 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8429 element = Feld[newx][newy] = Store[x][y];
8431 InitField(newx, newy, FALSE);
8433 else if (element == EL_AMOEBA_DROPPING)
8435 Feld[x][y] = get_next_element(element);
8436 element = Feld[newx][newy] = Store[x][y];
8438 else if (element == EL_SOKOBAN_OBJECT)
8441 Feld[x][y] = Back[x][y];
8443 if (Back[newx][newy])
8444 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8446 Back[x][y] = Back[newx][newy] = 0;
8449 Store[x][y] = EL_EMPTY;
8454 MovDelay[newx][newy] = 0;
8456 if (CAN_CHANGE_OR_HAS_ACTION(element))
8458 // copy element change control values to new field
8459 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8460 ChangePage[newx][newy] = ChangePage[x][y];
8461 ChangeCount[newx][newy] = ChangeCount[x][y];
8462 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8465 CustomValue[newx][newy] = CustomValue[x][y];
8467 ChangeDelay[x][y] = 0;
8468 ChangePage[x][y] = -1;
8469 ChangeCount[x][y] = 0;
8470 ChangeEvent[x][y] = -1;
8472 CustomValue[x][y] = 0;
8474 // copy animation control values to new field
8475 GfxFrame[newx][newy] = GfxFrame[x][y];
8476 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8477 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8478 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8480 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8482 // some elements can leave other elements behind after moving
8483 if (ei->move_leave_element != EL_EMPTY &&
8484 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8485 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8487 int move_leave_element = ei->move_leave_element;
8489 // this makes it possible to leave the removed element again
8490 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8491 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8493 Feld[x][y] = move_leave_element;
8495 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8496 MovDir[x][y] = direction;
8498 InitField(x, y, FALSE);
8500 if (GFX_CRUMBLED(Feld[x][y]))
8501 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8503 if (ELEM_IS_PLAYER(move_leave_element))
8504 RelocatePlayer(x, y, move_leave_element);
8507 // do this after checking for left-behind element
8508 ResetGfxAnimation(x, y); // reset animation values for old field
8510 if (!CAN_MOVE(element) ||
8511 (CAN_FALL(element) && direction == MV_DOWN &&
8512 (element == EL_SPRING ||
8513 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8514 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8515 GfxDir[x][y] = MovDir[newx][newy] = 0;
8517 TEST_DrawLevelField(x, y);
8518 TEST_DrawLevelField(newx, newy);
8520 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8522 // prevent pushed element from moving on in pushed direction
8523 if (pushed_by_player && CAN_MOVE(element) &&
8524 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8525 !(element_info[element].move_pattern & direction))
8526 TurnRound(newx, newy);
8528 // prevent elements on conveyor belt from moving on in last direction
8529 if (pushed_by_conveyor && CAN_FALL(element) &&
8530 direction & MV_HORIZONTAL)
8531 MovDir[newx][newy] = 0;
8533 if (!pushed_by_player)
8535 int nextx = newx + dx, nexty = newy + dy;
8536 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8538 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8540 if (CAN_FALL(element) && direction == MV_DOWN)
8541 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8543 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8544 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8546 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8547 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8550 if (DONT_TOUCH(element)) // object may be nasty to player or others
8552 TestIfBadThingTouchesPlayer(newx, newy);
8553 TestIfBadThingTouchesFriend(newx, newy);
8555 if (!IS_CUSTOM_ELEMENT(element))
8556 TestIfBadThingTouchesOtherBadThing(newx, newy);
8558 else if (element == EL_PENGUIN)
8559 TestIfFriendTouchesBadThing(newx, newy);
8561 if (DONT_GET_HIT_BY(element))
8563 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8566 // give the player one last chance (one more frame) to move away
8567 if (CAN_FALL(element) && direction == MV_DOWN &&
8568 (last_line || (!IS_FREE(x, newy + 1) &&
8569 (!IS_PLAYER(x, newy + 1) ||
8570 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8573 if (pushed_by_player && !game.use_change_when_pushing_bug)
8575 int push_side = MV_DIR_OPPOSITE(direction);
8576 struct PlayerInfo *player = PLAYERINFO(x, y);
8578 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8579 player->index_bit, push_side);
8580 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8581 player->index_bit, push_side);
8584 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8585 MovDelay[newx][newy] = 1;
8587 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8589 TestIfElementTouchesCustomElement(x, y); // empty or new element
8590 TestIfElementHitsCustomElement(newx, newy, direction);
8591 TestIfPlayerTouchesCustomElement(newx, newy);
8592 TestIfElementTouchesCustomElement(newx, newy);
8594 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8595 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8596 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8597 MV_DIR_OPPOSITE(direction));
8600 int AmoebeNachbarNr(int ax, int ay)
8603 int element = Feld[ax][ay];
8605 static int xy[4][2] =
8613 for (i = 0; i < NUM_DIRECTIONS; i++)
8615 int x = ax + xy[i][0];
8616 int y = ay + xy[i][1];
8618 if (!IN_LEV_FIELD(x, y))
8621 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8622 group_nr = AmoebaNr[x][y];
8628 static void AmoebenVereinigen(int ax, int ay)
8630 int i, x, y, xx, yy;
8631 int new_group_nr = AmoebaNr[ax][ay];
8632 static int xy[4][2] =
8640 if (new_group_nr == 0)
8643 for (i = 0; i < NUM_DIRECTIONS; i++)
8648 if (!IN_LEV_FIELD(x, y))
8651 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8652 Feld[x][y] == EL_BD_AMOEBA ||
8653 Feld[x][y] == EL_AMOEBA_DEAD) &&
8654 AmoebaNr[x][y] != new_group_nr)
8656 int old_group_nr = AmoebaNr[x][y];
8658 if (old_group_nr == 0)
8661 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8662 AmoebaCnt[old_group_nr] = 0;
8663 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8664 AmoebaCnt2[old_group_nr] = 0;
8666 SCAN_PLAYFIELD(xx, yy)
8668 if (AmoebaNr[xx][yy] == old_group_nr)
8669 AmoebaNr[xx][yy] = new_group_nr;
8675 void AmoebeUmwandeln(int ax, int ay)
8679 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8681 int group_nr = AmoebaNr[ax][ay];
8686 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8687 printf("AmoebeUmwandeln(): This should never happen!\n");
8692 SCAN_PLAYFIELD(x, y)
8694 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8697 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8701 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8702 SND_AMOEBA_TURNING_TO_GEM :
8703 SND_AMOEBA_TURNING_TO_ROCK));
8708 static int xy[4][2] =
8716 for (i = 0; i < NUM_DIRECTIONS; i++)
8721 if (!IN_LEV_FIELD(x, y))
8724 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8726 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8727 SND_AMOEBA_TURNING_TO_GEM :
8728 SND_AMOEBA_TURNING_TO_ROCK));
8735 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8738 int group_nr = AmoebaNr[ax][ay];
8739 boolean done = FALSE;
8744 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8745 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8750 SCAN_PLAYFIELD(x, y)
8752 if (AmoebaNr[x][y] == group_nr &&
8753 (Feld[x][y] == EL_AMOEBA_DEAD ||
8754 Feld[x][y] == EL_BD_AMOEBA ||
8755 Feld[x][y] == EL_AMOEBA_GROWING))
8758 Feld[x][y] = new_element;
8759 InitField(x, y, FALSE);
8760 TEST_DrawLevelField(x, y);
8766 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8767 SND_BD_AMOEBA_TURNING_TO_ROCK :
8768 SND_BD_AMOEBA_TURNING_TO_GEM));
8771 static void AmoebeWaechst(int x, int y)
8773 static unsigned int sound_delay = 0;
8774 static unsigned int sound_delay_value = 0;
8776 if (!MovDelay[x][y]) // start new growing cycle
8780 if (DelayReached(&sound_delay, sound_delay_value))
8782 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8783 sound_delay_value = 30;
8787 if (MovDelay[x][y]) // wait some time before growing bigger
8790 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8792 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8793 6 - MovDelay[x][y]);
8795 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8798 if (!MovDelay[x][y])
8800 Feld[x][y] = Store[x][y];
8802 TEST_DrawLevelField(x, y);
8807 static void AmoebaDisappearing(int x, int y)
8809 static unsigned int sound_delay = 0;
8810 static unsigned int sound_delay_value = 0;
8812 if (!MovDelay[x][y]) // start new shrinking cycle
8816 if (DelayReached(&sound_delay, sound_delay_value))
8817 sound_delay_value = 30;
8820 if (MovDelay[x][y]) // wait some time before shrinking
8823 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8825 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8826 6 - MovDelay[x][y]);
8828 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8831 if (!MovDelay[x][y])
8833 Feld[x][y] = EL_EMPTY;
8834 TEST_DrawLevelField(x, y);
8836 // don't let mole enter this field in this cycle;
8837 // (give priority to objects falling to this field from above)
8843 static void AmoebeAbleger(int ax, int ay)
8846 int element = Feld[ax][ay];
8847 int graphic = el2img(element);
8848 int newax = ax, neway = ay;
8849 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8850 static int xy[4][2] =
8858 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8860 Feld[ax][ay] = EL_AMOEBA_DEAD;
8861 TEST_DrawLevelField(ax, ay);
8865 if (IS_ANIMATED(graphic))
8866 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8868 if (!MovDelay[ax][ay]) // start making new amoeba field
8869 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8871 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8874 if (MovDelay[ax][ay])
8878 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8881 int x = ax + xy[start][0];
8882 int y = ay + xy[start][1];
8884 if (!IN_LEV_FIELD(x, y))
8887 if (IS_FREE(x, y) ||
8888 CAN_GROW_INTO(Feld[x][y]) ||
8889 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8890 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8896 if (newax == ax && neway == ay)
8899 else // normal or "filled" (BD style) amoeba
8902 boolean waiting_for_player = FALSE;
8904 for (i = 0; i < NUM_DIRECTIONS; i++)
8906 int j = (start + i) % 4;
8907 int x = ax + xy[j][0];
8908 int y = ay + xy[j][1];
8910 if (!IN_LEV_FIELD(x, y))
8913 if (IS_FREE(x, y) ||
8914 CAN_GROW_INTO(Feld[x][y]) ||
8915 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8916 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8922 else if (IS_PLAYER(x, y))
8923 waiting_for_player = TRUE;
8926 if (newax == ax && neway == ay) // amoeba cannot grow
8928 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
8930 Feld[ax][ay] = EL_AMOEBA_DEAD;
8931 TEST_DrawLevelField(ax, ay);
8932 AmoebaCnt[AmoebaNr[ax][ay]]--;
8934 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
8936 if (element == EL_AMOEBA_FULL)
8937 AmoebeUmwandeln(ax, ay);
8938 else if (element == EL_BD_AMOEBA)
8939 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
8944 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
8946 // amoeba gets larger by growing in some direction
8948 int new_group_nr = AmoebaNr[ax][ay];
8951 if (new_group_nr == 0)
8953 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
8954 printf("AmoebeAbleger(): This should never happen!\n");
8959 AmoebaNr[newax][neway] = new_group_nr;
8960 AmoebaCnt[new_group_nr]++;
8961 AmoebaCnt2[new_group_nr]++;
8963 // if amoeba touches other amoeba(s) after growing, unify them
8964 AmoebenVereinigen(newax, neway);
8966 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
8968 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
8974 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
8975 (neway == lev_fieldy - 1 && newax != ax))
8977 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
8978 Store[newax][neway] = element;
8980 else if (neway == ay || element == EL_EMC_DRIPPER)
8982 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
8984 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
8988 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
8989 Feld[ax][ay] = EL_AMOEBA_DROPPING;
8990 Store[ax][ay] = EL_AMOEBA_DROP;
8991 ContinueMoving(ax, ay);
8995 TEST_DrawLevelField(newax, neway);
8998 static void Life(int ax, int ay)
9002 int element = Feld[ax][ay];
9003 int graphic = el2img(element);
9004 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9006 boolean changed = FALSE;
9008 if (IS_ANIMATED(graphic))
9009 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9014 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9015 MovDelay[ax][ay] = life_time;
9017 if (MovDelay[ax][ay]) // wait some time before next cycle
9020 if (MovDelay[ax][ay])
9024 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9026 int xx = ax+x1, yy = ay+y1;
9027 int old_element = Feld[xx][yy];
9028 int num_neighbours = 0;
9030 if (!IN_LEV_FIELD(xx, yy))
9033 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9035 int x = xx+x2, y = yy+y2;
9037 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9040 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9041 boolean is_neighbour = FALSE;
9043 if (level.use_life_bugs)
9045 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9046 (IS_FREE(x, y) && Stop[x][y]));
9049 (Last[x][y] == element || is_player_cell);
9055 boolean is_free = FALSE;
9057 if (level.use_life_bugs)
9058 is_free = (IS_FREE(xx, yy));
9060 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9062 if (xx == ax && yy == ay) // field in the middle
9064 if (num_neighbours < life_parameter[0] ||
9065 num_neighbours > life_parameter[1])
9067 Feld[xx][yy] = EL_EMPTY;
9068 if (Feld[xx][yy] != old_element)
9069 TEST_DrawLevelField(xx, yy);
9070 Stop[xx][yy] = TRUE;
9074 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9075 { // free border field
9076 if (num_neighbours >= life_parameter[2] &&
9077 num_neighbours <= life_parameter[3])
9079 Feld[xx][yy] = element;
9080 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9081 if (Feld[xx][yy] != old_element)
9082 TEST_DrawLevelField(xx, yy);
9083 Stop[xx][yy] = TRUE;
9090 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9091 SND_GAME_OF_LIFE_GROWING);
9094 static void InitRobotWheel(int x, int y)
9096 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9099 static void RunRobotWheel(int x, int y)
9101 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9104 static void StopRobotWheel(int x, int y)
9106 if (game.robot_wheel_x == x &&
9107 game.robot_wheel_y == y)
9109 game.robot_wheel_x = -1;
9110 game.robot_wheel_y = -1;
9111 game.robot_wheel_active = FALSE;
9115 static void InitTimegateWheel(int x, int y)
9117 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9120 static void RunTimegateWheel(int x, int y)
9122 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9125 static void InitMagicBallDelay(int x, int y)
9127 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9130 static void ActivateMagicBall(int bx, int by)
9134 if (level.ball_random)
9136 int pos_border = RND(8); // select one of the eight border elements
9137 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9138 int xx = pos_content % 3;
9139 int yy = pos_content / 3;
9144 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9145 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9149 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9151 int xx = x - bx + 1;
9152 int yy = y - by + 1;
9154 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9155 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9159 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9162 static void CheckExit(int x, int y)
9164 if (game.gems_still_needed > 0 ||
9165 game.sokoban_fields_still_needed > 0 ||
9166 game.sokoban_objects_still_needed > 0 ||
9167 game.lights_still_needed > 0)
9169 int element = Feld[x][y];
9170 int graphic = el2img(element);
9172 if (IS_ANIMATED(graphic))
9173 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9178 // do not re-open exit door closed after last player
9179 if (game.all_players_gone)
9182 Feld[x][y] = EL_EXIT_OPENING;
9184 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9187 static void CheckExitEM(int x, int y)
9189 if (game.gems_still_needed > 0 ||
9190 game.sokoban_fields_still_needed > 0 ||
9191 game.sokoban_objects_still_needed > 0 ||
9192 game.lights_still_needed > 0)
9194 int element = Feld[x][y];
9195 int graphic = el2img(element);
9197 if (IS_ANIMATED(graphic))
9198 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9203 // do not re-open exit door closed after last player
9204 if (game.all_players_gone)
9207 Feld[x][y] = EL_EM_EXIT_OPENING;
9209 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9212 static void CheckExitSteel(int x, int y)
9214 if (game.gems_still_needed > 0 ||
9215 game.sokoban_fields_still_needed > 0 ||
9216 game.sokoban_objects_still_needed > 0 ||
9217 game.lights_still_needed > 0)
9219 int element = Feld[x][y];
9220 int graphic = el2img(element);
9222 if (IS_ANIMATED(graphic))
9223 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9228 // do not re-open exit door closed after last player
9229 if (game.all_players_gone)
9232 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9234 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9237 static void CheckExitSteelEM(int x, int y)
9239 if (game.gems_still_needed > 0 ||
9240 game.sokoban_fields_still_needed > 0 ||
9241 game.sokoban_objects_still_needed > 0 ||
9242 game.lights_still_needed > 0)
9244 int element = Feld[x][y];
9245 int graphic = el2img(element);
9247 if (IS_ANIMATED(graphic))
9248 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9253 // do not re-open exit door closed after last player
9254 if (game.all_players_gone)
9257 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9259 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9262 static void CheckExitSP(int x, int y)
9264 if (game.gems_still_needed > 0)
9266 int element = Feld[x][y];
9267 int graphic = el2img(element);
9269 if (IS_ANIMATED(graphic))
9270 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9275 // do not re-open exit door closed after last player
9276 if (game.all_players_gone)
9279 Feld[x][y] = EL_SP_EXIT_OPENING;
9281 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9284 static void CloseAllOpenTimegates(void)
9288 SCAN_PLAYFIELD(x, y)
9290 int element = Feld[x][y];
9292 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9294 Feld[x][y] = EL_TIMEGATE_CLOSING;
9296 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9301 static void DrawTwinkleOnField(int x, int y)
9303 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9306 if (Feld[x][y] == EL_BD_DIAMOND)
9309 if (MovDelay[x][y] == 0) // next animation frame
9310 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9312 if (MovDelay[x][y] != 0) // wait some time before next frame
9316 DrawLevelElementAnimation(x, y, Feld[x][y]);
9318 if (MovDelay[x][y] != 0)
9320 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9321 10 - MovDelay[x][y]);
9323 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9328 static void MauerWaechst(int x, int y)
9332 if (!MovDelay[x][y]) // next animation frame
9333 MovDelay[x][y] = 3 * delay;
9335 if (MovDelay[x][y]) // wait some time before next frame
9339 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9341 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9342 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9344 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9347 if (!MovDelay[x][y])
9349 if (MovDir[x][y] == MV_LEFT)
9351 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9352 TEST_DrawLevelField(x - 1, y);
9354 else if (MovDir[x][y] == MV_RIGHT)
9356 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9357 TEST_DrawLevelField(x + 1, y);
9359 else if (MovDir[x][y] == MV_UP)
9361 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9362 TEST_DrawLevelField(x, y - 1);
9366 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9367 TEST_DrawLevelField(x, y + 1);
9370 Feld[x][y] = Store[x][y];
9372 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9373 TEST_DrawLevelField(x, y);
9378 static void MauerAbleger(int ax, int ay)
9380 int element = Feld[ax][ay];
9381 int graphic = el2img(element);
9382 boolean oben_frei = FALSE, unten_frei = FALSE;
9383 boolean links_frei = FALSE, rechts_frei = FALSE;
9384 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9385 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9386 boolean new_wall = FALSE;
9388 if (IS_ANIMATED(graphic))
9389 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9391 if (!MovDelay[ax][ay]) // start building new wall
9392 MovDelay[ax][ay] = 6;
9394 if (MovDelay[ax][ay]) // wait some time before building new wall
9397 if (MovDelay[ax][ay])
9401 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9403 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9405 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9407 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9410 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9411 element == EL_EXPANDABLE_WALL_ANY)
9415 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9416 Store[ax][ay-1] = element;
9417 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9418 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9419 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9420 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9425 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9426 Store[ax][ay+1] = element;
9427 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9428 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9429 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9430 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9435 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9436 element == EL_EXPANDABLE_WALL_ANY ||
9437 element == EL_EXPANDABLE_WALL ||
9438 element == EL_BD_EXPANDABLE_WALL)
9442 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9443 Store[ax-1][ay] = element;
9444 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9445 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9446 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9447 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9453 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9454 Store[ax+1][ay] = element;
9455 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9456 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9457 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9458 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9463 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9464 TEST_DrawLevelField(ax, ay);
9466 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9468 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9469 unten_massiv = TRUE;
9470 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9471 links_massiv = TRUE;
9472 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9473 rechts_massiv = TRUE;
9475 if (((oben_massiv && unten_massiv) ||
9476 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9477 element == EL_EXPANDABLE_WALL) &&
9478 ((links_massiv && rechts_massiv) ||
9479 element == EL_EXPANDABLE_WALL_VERTICAL))
9480 Feld[ax][ay] = EL_WALL;
9483 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9486 static void MauerAblegerStahl(int ax, int ay)
9488 int element = Feld[ax][ay];
9489 int graphic = el2img(element);
9490 boolean oben_frei = FALSE, unten_frei = FALSE;
9491 boolean links_frei = FALSE, rechts_frei = FALSE;
9492 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9493 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9494 boolean new_wall = FALSE;
9496 if (IS_ANIMATED(graphic))
9497 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9499 if (!MovDelay[ax][ay]) // start building new wall
9500 MovDelay[ax][ay] = 6;
9502 if (MovDelay[ax][ay]) // wait some time before building new wall
9505 if (MovDelay[ax][ay])
9509 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9511 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9513 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9515 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9518 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9519 element == EL_EXPANDABLE_STEELWALL_ANY)
9523 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9524 Store[ax][ay-1] = element;
9525 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9526 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9527 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9528 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9533 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9534 Store[ax][ay+1] = element;
9535 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9536 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9537 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9538 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9543 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9544 element == EL_EXPANDABLE_STEELWALL_ANY)
9548 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9549 Store[ax-1][ay] = element;
9550 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9551 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9552 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9553 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9559 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9560 Store[ax+1][ay] = element;
9561 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9562 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9563 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9564 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9569 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9571 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9572 unten_massiv = TRUE;
9573 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9574 links_massiv = TRUE;
9575 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9576 rechts_massiv = TRUE;
9578 if (((oben_massiv && unten_massiv) ||
9579 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9580 ((links_massiv && rechts_massiv) ||
9581 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9582 Feld[ax][ay] = EL_STEELWALL;
9585 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9588 static void CheckForDragon(int x, int y)
9591 boolean dragon_found = FALSE;
9592 static int xy[4][2] =
9600 for (i = 0; i < NUM_DIRECTIONS; i++)
9602 for (j = 0; j < 4; j++)
9604 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9606 if (IN_LEV_FIELD(xx, yy) &&
9607 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9609 if (Feld[xx][yy] == EL_DRAGON)
9610 dragon_found = TRUE;
9619 for (i = 0; i < NUM_DIRECTIONS; i++)
9621 for (j = 0; j < 3; j++)
9623 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9625 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9627 Feld[xx][yy] = EL_EMPTY;
9628 TEST_DrawLevelField(xx, yy);
9637 static void InitBuggyBase(int x, int y)
9639 int element = Feld[x][y];
9640 int activating_delay = FRAMES_PER_SECOND / 4;
9643 (element == EL_SP_BUGGY_BASE ?
9644 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9645 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9647 element == EL_SP_BUGGY_BASE_ACTIVE ?
9648 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9651 static void WarnBuggyBase(int x, int y)
9654 static int xy[4][2] =
9662 for (i = 0; i < NUM_DIRECTIONS; i++)
9664 int xx = x + xy[i][0];
9665 int yy = y + xy[i][1];
9667 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9669 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9676 static void InitTrap(int x, int y)
9678 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9681 static void ActivateTrap(int x, int y)
9683 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9686 static void ChangeActiveTrap(int x, int y)
9688 int graphic = IMG_TRAP_ACTIVE;
9690 // if new animation frame was drawn, correct crumbled sand border
9691 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9692 TEST_DrawLevelFieldCrumbled(x, y);
9695 static int getSpecialActionElement(int element, int number, int base_element)
9697 return (element != EL_EMPTY ? element :
9698 number != -1 ? base_element + number - 1 :
9702 static int getModifiedActionNumber(int value_old, int operator, int operand,
9703 int value_min, int value_max)
9705 int value_new = (operator == CA_MODE_SET ? operand :
9706 operator == CA_MODE_ADD ? value_old + operand :
9707 operator == CA_MODE_SUBTRACT ? value_old - operand :
9708 operator == CA_MODE_MULTIPLY ? value_old * operand :
9709 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9710 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9713 return (value_new < value_min ? value_min :
9714 value_new > value_max ? value_max :
9718 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9720 struct ElementInfo *ei = &element_info[element];
9721 struct ElementChangeInfo *change = &ei->change_page[page];
9722 int target_element = change->target_element;
9723 int action_type = change->action_type;
9724 int action_mode = change->action_mode;
9725 int action_arg = change->action_arg;
9726 int action_element = change->action_element;
9729 if (!change->has_action)
9732 // ---------- determine action paramater values -----------------------------
9734 int level_time_value =
9735 (level.time > 0 ? TimeLeft :
9738 int action_arg_element_raw =
9739 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9740 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9741 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9742 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9743 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9744 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9745 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9747 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9749 int action_arg_direction =
9750 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9751 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9752 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9753 change->actual_trigger_side :
9754 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9755 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9758 int action_arg_number_min =
9759 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9762 int action_arg_number_max =
9763 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9764 action_type == CA_SET_LEVEL_GEMS ? 999 :
9765 action_type == CA_SET_LEVEL_TIME ? 9999 :
9766 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9767 action_type == CA_SET_CE_VALUE ? 9999 :
9768 action_type == CA_SET_CE_SCORE ? 9999 :
9771 int action_arg_number_reset =
9772 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9773 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9774 action_type == CA_SET_LEVEL_TIME ? level.time :
9775 action_type == CA_SET_LEVEL_SCORE ? 0 :
9776 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9777 action_type == CA_SET_CE_SCORE ? 0 :
9780 int action_arg_number =
9781 (action_arg <= CA_ARG_MAX ? action_arg :
9782 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9783 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9784 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9785 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9786 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9787 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9788 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9789 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9790 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9791 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9792 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9793 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9794 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9795 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9796 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9797 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9798 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9799 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9800 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9801 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9802 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9805 int action_arg_number_old =
9806 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9807 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9808 action_type == CA_SET_LEVEL_SCORE ? game.score :
9809 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9810 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9813 int action_arg_number_new =
9814 getModifiedActionNumber(action_arg_number_old,
9815 action_mode, action_arg_number,
9816 action_arg_number_min, action_arg_number_max);
9818 int trigger_player_bits =
9819 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9820 change->actual_trigger_player_bits : change->trigger_player);
9822 int action_arg_player_bits =
9823 (action_arg >= CA_ARG_PLAYER_1 &&
9824 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9825 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9826 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9829 // ---------- execute action -----------------------------------------------
9831 switch (action_type)
9838 // ---------- level actions ----------------------------------------------
9840 case CA_RESTART_LEVEL:
9842 game.restart_level = TRUE;
9847 case CA_SHOW_ENVELOPE:
9849 int element = getSpecialActionElement(action_arg_element,
9850 action_arg_number, EL_ENVELOPE_1);
9852 if (IS_ENVELOPE(element))
9853 local_player->show_envelope = element;
9858 case CA_SET_LEVEL_TIME:
9860 if (level.time > 0) // only modify limited time value
9862 TimeLeft = action_arg_number_new;
9864 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9866 DisplayGameControlValues();
9868 if (!TimeLeft && setup.time_limit)
9869 for (i = 0; i < MAX_PLAYERS; i++)
9870 KillPlayer(&stored_player[i]);
9876 case CA_SET_LEVEL_SCORE:
9878 game.score = action_arg_number_new;
9880 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9882 DisplayGameControlValues();
9887 case CA_SET_LEVEL_GEMS:
9889 game.gems_still_needed = action_arg_number_new;
9891 game.snapshot.collected_item = TRUE;
9893 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9895 DisplayGameControlValues();
9900 case CA_SET_LEVEL_WIND:
9902 game.wind_direction = action_arg_direction;
9907 case CA_SET_LEVEL_RANDOM_SEED:
9909 // ensure that setting a new random seed while playing is predictable
9910 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9915 // ---------- player actions ---------------------------------------------
9917 case CA_MOVE_PLAYER:
9918 case CA_MOVE_PLAYER_NEW:
9920 // automatically move to the next field in specified direction
9921 for (i = 0; i < MAX_PLAYERS; i++)
9922 if (trigger_player_bits & (1 << i))
9923 if (action_type == CA_MOVE_PLAYER ||
9924 stored_player[i].MovPos == 0)
9925 stored_player[i].programmed_action = action_arg_direction;
9930 case CA_EXIT_PLAYER:
9932 for (i = 0; i < MAX_PLAYERS; i++)
9933 if (action_arg_player_bits & (1 << i))
9934 ExitPlayer(&stored_player[i]);
9936 if (game.players_still_needed == 0)
9942 case CA_KILL_PLAYER:
9944 for (i = 0; i < MAX_PLAYERS; i++)
9945 if (action_arg_player_bits & (1 << i))
9946 KillPlayer(&stored_player[i]);
9951 case CA_SET_PLAYER_KEYS:
9953 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
9954 int element = getSpecialActionElement(action_arg_element,
9955 action_arg_number, EL_KEY_1);
9957 if (IS_KEY(element))
9959 for (i = 0; i < MAX_PLAYERS; i++)
9961 if (trigger_player_bits & (1 << i))
9963 stored_player[i].key[KEY_NR(element)] = key_state;
9965 DrawGameDoorValues();
9973 case CA_SET_PLAYER_SPEED:
9975 for (i = 0; i < MAX_PLAYERS; i++)
9977 if (trigger_player_bits & (1 << i))
9979 int move_stepsize = TILEX / stored_player[i].move_delay_value;
9981 if (action_arg == CA_ARG_SPEED_FASTER &&
9982 stored_player[i].cannot_move)
9984 action_arg_number = STEPSIZE_VERY_SLOW;
9986 else if (action_arg == CA_ARG_SPEED_SLOWER ||
9987 action_arg == CA_ARG_SPEED_FASTER)
9989 action_arg_number = 2;
9990 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
9993 else if (action_arg == CA_ARG_NUMBER_RESET)
9995 action_arg_number = level.initial_player_stepsize[i];
9999 getModifiedActionNumber(move_stepsize,
10002 action_arg_number_min,
10003 action_arg_number_max);
10005 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10012 case CA_SET_PLAYER_SHIELD:
10014 for (i = 0; i < MAX_PLAYERS; i++)
10016 if (trigger_player_bits & (1 << i))
10018 if (action_arg == CA_ARG_SHIELD_OFF)
10020 stored_player[i].shield_normal_time_left = 0;
10021 stored_player[i].shield_deadly_time_left = 0;
10023 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10025 stored_player[i].shield_normal_time_left = 999999;
10027 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10029 stored_player[i].shield_normal_time_left = 999999;
10030 stored_player[i].shield_deadly_time_left = 999999;
10038 case CA_SET_PLAYER_GRAVITY:
10040 for (i = 0; i < MAX_PLAYERS; i++)
10042 if (trigger_player_bits & (1 << i))
10044 stored_player[i].gravity =
10045 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10046 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10047 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10048 stored_player[i].gravity);
10055 case CA_SET_PLAYER_ARTWORK:
10057 for (i = 0; i < MAX_PLAYERS; i++)
10059 if (trigger_player_bits & (1 << i))
10061 int artwork_element = action_arg_element;
10063 if (action_arg == CA_ARG_ELEMENT_RESET)
10065 (level.use_artwork_element[i] ? level.artwork_element[i] :
10066 stored_player[i].element_nr);
10068 if (stored_player[i].artwork_element != artwork_element)
10069 stored_player[i].Frame = 0;
10071 stored_player[i].artwork_element = artwork_element;
10073 SetPlayerWaiting(&stored_player[i], FALSE);
10075 // set number of special actions for bored and sleeping animation
10076 stored_player[i].num_special_action_bored =
10077 get_num_special_action(artwork_element,
10078 ACTION_BORING_1, ACTION_BORING_LAST);
10079 stored_player[i].num_special_action_sleeping =
10080 get_num_special_action(artwork_element,
10081 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10088 case CA_SET_PLAYER_INVENTORY:
10090 for (i = 0; i < MAX_PLAYERS; i++)
10092 struct PlayerInfo *player = &stored_player[i];
10095 if (trigger_player_bits & (1 << i))
10097 int inventory_element = action_arg_element;
10099 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10100 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10101 action_arg == CA_ARG_ELEMENT_ACTION)
10103 int element = inventory_element;
10104 int collect_count = element_info[element].collect_count_initial;
10106 if (!IS_CUSTOM_ELEMENT(element))
10109 if (collect_count == 0)
10110 player->inventory_infinite_element = element;
10112 for (k = 0; k < collect_count; k++)
10113 if (player->inventory_size < MAX_INVENTORY_SIZE)
10114 player->inventory_element[player->inventory_size++] =
10117 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10118 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10119 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10121 if (player->inventory_infinite_element != EL_UNDEFINED &&
10122 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10123 action_arg_element_raw))
10124 player->inventory_infinite_element = EL_UNDEFINED;
10126 for (k = 0, j = 0; j < player->inventory_size; j++)
10128 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10129 action_arg_element_raw))
10130 player->inventory_element[k++] = player->inventory_element[j];
10133 player->inventory_size = k;
10135 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10137 if (player->inventory_size > 0)
10139 for (j = 0; j < player->inventory_size - 1; j++)
10140 player->inventory_element[j] = player->inventory_element[j + 1];
10142 player->inventory_size--;
10145 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10147 if (player->inventory_size > 0)
10148 player->inventory_size--;
10150 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10152 player->inventory_infinite_element = EL_UNDEFINED;
10153 player->inventory_size = 0;
10155 else if (action_arg == CA_ARG_INVENTORY_RESET)
10157 player->inventory_infinite_element = EL_UNDEFINED;
10158 player->inventory_size = 0;
10160 if (level.use_initial_inventory[i])
10162 for (j = 0; j < level.initial_inventory_size[i]; j++)
10164 int element = level.initial_inventory_content[i][j];
10165 int collect_count = element_info[element].collect_count_initial;
10167 if (!IS_CUSTOM_ELEMENT(element))
10170 if (collect_count == 0)
10171 player->inventory_infinite_element = element;
10173 for (k = 0; k < collect_count; k++)
10174 if (player->inventory_size < MAX_INVENTORY_SIZE)
10175 player->inventory_element[player->inventory_size++] =
10186 // ---------- CE actions -------------------------------------------------
10188 case CA_SET_CE_VALUE:
10190 int last_ce_value = CustomValue[x][y];
10192 CustomValue[x][y] = action_arg_number_new;
10194 if (CustomValue[x][y] != last_ce_value)
10196 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10197 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10199 if (CustomValue[x][y] == 0)
10201 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10202 ChangeCount[x][y] = 0; // allow at least one more change
10204 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10205 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10212 case CA_SET_CE_SCORE:
10214 int last_ce_score = ei->collect_score;
10216 ei->collect_score = action_arg_number_new;
10218 if (ei->collect_score != last_ce_score)
10220 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10221 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10223 if (ei->collect_score == 0)
10227 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10228 ChangeCount[x][y] = 0; // allow at least one more change
10230 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10231 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10234 This is a very special case that seems to be a mixture between
10235 CheckElementChange() and CheckTriggeredElementChange(): while
10236 the first one only affects single elements that are triggered
10237 directly, the second one affects multiple elements in the playfield
10238 that are triggered indirectly by another element. This is a third
10239 case: Changing the CE score always affects multiple identical CEs,
10240 so every affected CE must be checked, not only the single CE for
10241 which the CE score was changed in the first place (as every instance
10242 of that CE shares the same CE score, and therefore also can change)!
10244 SCAN_PLAYFIELD(xx, yy)
10246 if (Feld[xx][yy] == element)
10247 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10248 CE_SCORE_GETS_ZERO);
10256 case CA_SET_CE_ARTWORK:
10258 int artwork_element = action_arg_element;
10259 boolean reset_frame = FALSE;
10262 if (action_arg == CA_ARG_ELEMENT_RESET)
10263 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10266 if (ei->gfx_element != artwork_element)
10267 reset_frame = TRUE;
10269 ei->gfx_element = artwork_element;
10271 SCAN_PLAYFIELD(xx, yy)
10273 if (Feld[xx][yy] == element)
10277 ResetGfxAnimation(xx, yy);
10278 ResetRandomAnimationValue(xx, yy);
10281 TEST_DrawLevelField(xx, yy);
10288 // ---------- engine actions ---------------------------------------------
10290 case CA_SET_ENGINE_SCAN_MODE:
10292 InitPlayfieldScanMode(action_arg);
10302 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10304 int old_element = Feld[x][y];
10305 int new_element = GetElementFromGroupElement(element);
10306 int previous_move_direction = MovDir[x][y];
10307 int last_ce_value = CustomValue[x][y];
10308 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10309 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10310 boolean add_player_onto_element = (new_element_is_player &&
10311 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10312 IS_WALKABLE(old_element));
10314 if (!add_player_onto_element)
10316 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10317 RemoveMovingField(x, y);
10321 Feld[x][y] = new_element;
10323 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10324 MovDir[x][y] = previous_move_direction;
10326 if (element_info[new_element].use_last_ce_value)
10327 CustomValue[x][y] = last_ce_value;
10329 InitField_WithBug1(x, y, FALSE);
10331 new_element = Feld[x][y]; // element may have changed
10333 ResetGfxAnimation(x, y);
10334 ResetRandomAnimationValue(x, y);
10336 TEST_DrawLevelField(x, y);
10338 if (GFX_CRUMBLED(new_element))
10339 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10342 // check if element under the player changes from accessible to unaccessible
10343 // (needed for special case of dropping element which then changes)
10344 // (must be checked after creating new element for walkable group elements)
10345 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10346 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10353 // "ChangeCount" not set yet to allow "entered by player" change one time
10354 if (new_element_is_player)
10355 RelocatePlayer(x, y, new_element);
10358 ChangeCount[x][y]++; // count number of changes in the same frame
10360 TestIfBadThingTouchesPlayer(x, y);
10361 TestIfPlayerTouchesCustomElement(x, y);
10362 TestIfElementTouchesCustomElement(x, y);
10365 static void CreateField(int x, int y, int element)
10367 CreateFieldExt(x, y, element, FALSE);
10370 static void CreateElementFromChange(int x, int y, int element)
10372 element = GET_VALID_RUNTIME_ELEMENT(element);
10374 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10376 int old_element = Feld[x][y];
10378 // prevent changed element from moving in same engine frame
10379 // unless both old and new element can either fall or move
10380 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10381 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10385 CreateFieldExt(x, y, element, TRUE);
10388 static boolean ChangeElement(int x, int y, int element, int page)
10390 struct ElementInfo *ei = &element_info[element];
10391 struct ElementChangeInfo *change = &ei->change_page[page];
10392 int ce_value = CustomValue[x][y];
10393 int ce_score = ei->collect_score;
10394 int target_element;
10395 int old_element = Feld[x][y];
10397 // always use default change event to prevent running into a loop
10398 if (ChangeEvent[x][y] == -1)
10399 ChangeEvent[x][y] = CE_DELAY;
10401 if (ChangeEvent[x][y] == CE_DELAY)
10403 // reset actual trigger element, trigger player and action element
10404 change->actual_trigger_element = EL_EMPTY;
10405 change->actual_trigger_player = EL_EMPTY;
10406 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10407 change->actual_trigger_side = CH_SIDE_NONE;
10408 change->actual_trigger_ce_value = 0;
10409 change->actual_trigger_ce_score = 0;
10412 // do not change elements more than a specified maximum number of changes
10413 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10416 ChangeCount[x][y]++; // count number of changes in the same frame
10418 if (change->explode)
10425 if (change->use_target_content)
10427 boolean complete_replace = TRUE;
10428 boolean can_replace[3][3];
10431 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10434 boolean is_walkable;
10435 boolean is_diggable;
10436 boolean is_collectible;
10437 boolean is_removable;
10438 boolean is_destructible;
10439 int ex = x + xx - 1;
10440 int ey = y + yy - 1;
10441 int content_element = change->target_content.e[xx][yy];
10444 can_replace[xx][yy] = TRUE;
10446 if (ex == x && ey == y) // do not check changing element itself
10449 if (content_element == EL_EMPTY_SPACE)
10451 can_replace[xx][yy] = FALSE; // do not replace border with space
10456 if (!IN_LEV_FIELD(ex, ey))
10458 can_replace[xx][yy] = FALSE;
10459 complete_replace = FALSE;
10466 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10467 e = MovingOrBlocked2Element(ex, ey);
10469 is_empty = (IS_FREE(ex, ey) ||
10470 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10472 is_walkable = (is_empty || IS_WALKABLE(e));
10473 is_diggable = (is_empty || IS_DIGGABLE(e));
10474 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10475 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10476 is_removable = (is_diggable || is_collectible);
10478 can_replace[xx][yy] =
10479 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10480 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10481 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10482 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10483 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10484 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10485 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10487 if (!can_replace[xx][yy])
10488 complete_replace = FALSE;
10491 if (!change->only_if_complete || complete_replace)
10493 boolean something_has_changed = FALSE;
10495 if (change->only_if_complete && change->use_random_replace &&
10496 RND(100) < change->random_percentage)
10499 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10501 int ex = x + xx - 1;
10502 int ey = y + yy - 1;
10503 int content_element;
10505 if (can_replace[xx][yy] && (!change->use_random_replace ||
10506 RND(100) < change->random_percentage))
10508 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10509 RemoveMovingField(ex, ey);
10511 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10513 content_element = change->target_content.e[xx][yy];
10514 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10515 ce_value, ce_score);
10517 CreateElementFromChange(ex, ey, target_element);
10519 something_has_changed = TRUE;
10521 // for symmetry reasons, freeze newly created border elements
10522 if (ex != x || ey != y)
10523 Stop[ex][ey] = TRUE; // no more moving in this frame
10527 if (something_has_changed)
10529 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10530 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10536 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10537 ce_value, ce_score);
10539 if (element == EL_DIAGONAL_GROWING ||
10540 element == EL_DIAGONAL_SHRINKING)
10542 target_element = Store[x][y];
10544 Store[x][y] = EL_EMPTY;
10547 CreateElementFromChange(x, y, target_element);
10549 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10550 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10553 // this uses direct change before indirect change
10554 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10559 static void HandleElementChange(int x, int y, int page)
10561 int element = MovingOrBlocked2Element(x, y);
10562 struct ElementInfo *ei = &element_info[element];
10563 struct ElementChangeInfo *change = &ei->change_page[page];
10564 boolean handle_action_before_change = FALSE;
10567 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10568 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10571 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10572 x, y, element, element_info[element].token_name);
10573 printf("HandleElementChange(): This should never happen!\n");
10578 // this can happen with classic bombs on walkable, changing elements
10579 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10584 if (ChangeDelay[x][y] == 0) // initialize element change
10586 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10588 if (change->can_change)
10590 // !!! not clear why graphic animation should be reset at all here !!!
10591 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10592 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10595 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10597 When using an animation frame delay of 1 (this only happens with
10598 "sp_zonk.moving.left/right" in the classic graphics), the default
10599 (non-moving) animation shows wrong animation frames (while the
10600 moving animation, like "sp_zonk.moving.left/right", is correct,
10601 so this graphical bug never shows up with the classic graphics).
10602 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10603 be drawn instead of the correct frames 0,1,2,3. This is caused by
10604 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10605 an element change: First when the change delay ("ChangeDelay[][]")
10606 counter has reached zero after decrementing, then a second time in
10607 the next frame (after "GfxFrame[][]" was already incremented) when
10608 "ChangeDelay[][]" is reset to the initial delay value again.
10610 This causes frame 0 to be drawn twice, while the last frame won't
10611 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10613 As some animations may already be cleverly designed around this bug
10614 (at least the "Snake Bite" snake tail animation does this), it cannot
10615 simply be fixed here without breaking such existing animations.
10616 Unfortunately, it cannot easily be detected if a graphics set was
10617 designed "before" or "after" the bug was fixed. As a workaround,
10618 a new graphics set option "game.graphics_engine_version" was added
10619 to be able to specify the game's major release version for which the
10620 graphics set was designed, which can then be used to decide if the
10621 bugfix should be used (version 4 and above) or not (version 3 or
10622 below, or if no version was specified at all, as with old sets).
10624 (The wrong/fixed animation frames can be tested with the test level set
10625 "test_gfxframe" and level "000", which contains a specially prepared
10626 custom element at level position (x/y) == (11/9) which uses the zonk
10627 animation mentioned above. Using "game.graphics_engine_version: 4"
10628 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10629 This can also be seen from the debug output for this test element.)
10632 // when a custom element is about to change (for example by change delay),
10633 // do not reset graphic animation when the custom element is moving
10634 if (game.graphics_engine_version < 4 &&
10637 ResetGfxAnimation(x, y);
10638 ResetRandomAnimationValue(x, y);
10641 if (change->pre_change_function)
10642 change->pre_change_function(x, y);
10646 ChangeDelay[x][y]--;
10648 if (ChangeDelay[x][y] != 0) // continue element change
10650 if (change->can_change)
10652 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10654 if (IS_ANIMATED(graphic))
10655 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10657 if (change->change_function)
10658 change->change_function(x, y);
10661 else // finish element change
10663 if (ChangePage[x][y] != -1) // remember page from delayed change
10665 page = ChangePage[x][y];
10666 ChangePage[x][y] = -1;
10668 change = &ei->change_page[page];
10671 if (IS_MOVING(x, y)) // never change a running system ;-)
10673 ChangeDelay[x][y] = 1; // try change after next move step
10674 ChangePage[x][y] = page; // remember page to use for change
10679 // special case: set new level random seed before changing element
10680 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10681 handle_action_before_change = TRUE;
10683 if (change->has_action && handle_action_before_change)
10684 ExecuteCustomElementAction(x, y, element, page);
10686 if (change->can_change)
10688 if (ChangeElement(x, y, element, page))
10690 if (change->post_change_function)
10691 change->post_change_function(x, y);
10695 if (change->has_action && !handle_action_before_change)
10696 ExecuteCustomElementAction(x, y, element, page);
10700 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10701 int trigger_element,
10703 int trigger_player,
10707 boolean change_done_any = FALSE;
10708 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10711 if (!(trigger_events[trigger_element][trigger_event]))
10714 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10716 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10718 int element = EL_CUSTOM_START + i;
10719 boolean change_done = FALSE;
10722 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10723 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10726 for (p = 0; p < element_info[element].num_change_pages; p++)
10728 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10730 if (change->can_change_or_has_action &&
10731 change->has_event[trigger_event] &&
10732 change->trigger_side & trigger_side &&
10733 change->trigger_player & trigger_player &&
10734 change->trigger_page & trigger_page_bits &&
10735 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10737 change->actual_trigger_element = trigger_element;
10738 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10739 change->actual_trigger_player_bits = trigger_player;
10740 change->actual_trigger_side = trigger_side;
10741 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10742 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10744 if ((change->can_change && !change_done) || change->has_action)
10748 SCAN_PLAYFIELD(x, y)
10750 if (Feld[x][y] == element)
10752 if (change->can_change && !change_done)
10754 // if element already changed in this frame, not only prevent
10755 // another element change (checked in ChangeElement()), but
10756 // also prevent additional element actions for this element
10758 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10759 !level.use_action_after_change_bug)
10762 ChangeDelay[x][y] = 1;
10763 ChangeEvent[x][y] = trigger_event;
10765 HandleElementChange(x, y, p);
10767 else if (change->has_action)
10769 // if element already changed in this frame, not only prevent
10770 // another element change (checked in ChangeElement()), but
10771 // also prevent additional element actions for this element
10773 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10774 !level.use_action_after_change_bug)
10777 ExecuteCustomElementAction(x, y, element, p);
10778 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10783 if (change->can_change)
10785 change_done = TRUE;
10786 change_done_any = TRUE;
10793 RECURSION_LOOP_DETECTION_END();
10795 return change_done_any;
10798 static boolean CheckElementChangeExt(int x, int y,
10800 int trigger_element,
10802 int trigger_player,
10805 boolean change_done = FALSE;
10808 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10809 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10812 if (Feld[x][y] == EL_BLOCKED)
10814 Blocked2Moving(x, y, &x, &y);
10815 element = Feld[x][y];
10818 // check if element has already changed or is about to change after moving
10819 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10820 Feld[x][y] != element) ||
10822 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10823 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10824 ChangePage[x][y] != -1)))
10827 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10829 for (p = 0; p < element_info[element].num_change_pages; p++)
10831 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10833 /* check trigger element for all events where the element that is checked
10834 for changing interacts with a directly adjacent element -- this is
10835 different to element changes that affect other elements to change on the
10836 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10837 boolean check_trigger_element =
10838 (trigger_event == CE_TOUCHING_X ||
10839 trigger_event == CE_HITTING_X ||
10840 trigger_event == CE_HIT_BY_X ||
10841 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10843 if (change->can_change_or_has_action &&
10844 change->has_event[trigger_event] &&
10845 change->trigger_side & trigger_side &&
10846 change->trigger_player & trigger_player &&
10847 (!check_trigger_element ||
10848 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10850 change->actual_trigger_element = trigger_element;
10851 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10852 change->actual_trigger_player_bits = trigger_player;
10853 change->actual_trigger_side = trigger_side;
10854 change->actual_trigger_ce_value = CustomValue[x][y];
10855 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10857 // special case: trigger element not at (x,y) position for some events
10858 if (check_trigger_element)
10870 { 0, 0 }, { 0, 0 }, { 0, 0 },
10874 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10875 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10877 change->actual_trigger_ce_value = CustomValue[xx][yy];
10878 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10881 if (change->can_change && !change_done)
10883 ChangeDelay[x][y] = 1;
10884 ChangeEvent[x][y] = trigger_event;
10886 HandleElementChange(x, y, p);
10888 change_done = TRUE;
10890 else if (change->has_action)
10892 ExecuteCustomElementAction(x, y, element, p);
10893 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10898 RECURSION_LOOP_DETECTION_END();
10900 return change_done;
10903 static void PlayPlayerSound(struct PlayerInfo *player)
10905 int jx = player->jx, jy = player->jy;
10906 int sound_element = player->artwork_element;
10907 int last_action = player->last_action_waiting;
10908 int action = player->action_waiting;
10910 if (player->is_waiting)
10912 if (action != last_action)
10913 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10915 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10919 if (action != last_action)
10920 StopSound(element_info[sound_element].sound[last_action]);
10922 if (last_action == ACTION_SLEEPING)
10923 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
10927 static void PlayAllPlayersSound(void)
10931 for (i = 0; i < MAX_PLAYERS; i++)
10932 if (stored_player[i].active)
10933 PlayPlayerSound(&stored_player[i]);
10936 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
10938 boolean last_waiting = player->is_waiting;
10939 int move_dir = player->MovDir;
10941 player->dir_waiting = move_dir;
10942 player->last_action_waiting = player->action_waiting;
10946 if (!last_waiting) // not waiting -> waiting
10948 player->is_waiting = TRUE;
10950 player->frame_counter_bored =
10952 game.player_boring_delay_fixed +
10953 GetSimpleRandom(game.player_boring_delay_random);
10954 player->frame_counter_sleeping =
10956 game.player_sleeping_delay_fixed +
10957 GetSimpleRandom(game.player_sleeping_delay_random);
10959 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
10962 if (game.player_sleeping_delay_fixed +
10963 game.player_sleeping_delay_random > 0 &&
10964 player->anim_delay_counter == 0 &&
10965 player->post_delay_counter == 0 &&
10966 FrameCounter >= player->frame_counter_sleeping)
10967 player->is_sleeping = TRUE;
10968 else if (game.player_boring_delay_fixed +
10969 game.player_boring_delay_random > 0 &&
10970 FrameCounter >= player->frame_counter_bored)
10971 player->is_bored = TRUE;
10973 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
10974 player->is_bored ? ACTION_BORING :
10977 if (player->is_sleeping && player->use_murphy)
10979 // special case for sleeping Murphy when leaning against non-free tile
10981 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
10982 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
10983 !IS_MOVING(player->jx - 1, player->jy)))
10984 move_dir = MV_LEFT;
10985 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
10986 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
10987 !IS_MOVING(player->jx + 1, player->jy)))
10988 move_dir = MV_RIGHT;
10990 player->is_sleeping = FALSE;
10992 player->dir_waiting = move_dir;
10995 if (player->is_sleeping)
10997 if (player->num_special_action_sleeping > 0)
10999 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11001 int last_special_action = player->special_action_sleeping;
11002 int num_special_action = player->num_special_action_sleeping;
11003 int special_action =
11004 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11005 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11006 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11007 last_special_action + 1 : ACTION_SLEEPING);
11008 int special_graphic =
11009 el_act_dir2img(player->artwork_element, special_action, move_dir);
11011 player->anim_delay_counter =
11012 graphic_info[special_graphic].anim_delay_fixed +
11013 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11014 player->post_delay_counter =
11015 graphic_info[special_graphic].post_delay_fixed +
11016 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11018 player->special_action_sleeping = special_action;
11021 if (player->anim_delay_counter > 0)
11023 player->action_waiting = player->special_action_sleeping;
11024 player->anim_delay_counter--;
11026 else if (player->post_delay_counter > 0)
11028 player->post_delay_counter--;
11032 else if (player->is_bored)
11034 if (player->num_special_action_bored > 0)
11036 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11038 int special_action =
11039 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11040 int special_graphic =
11041 el_act_dir2img(player->artwork_element, special_action, move_dir);
11043 player->anim_delay_counter =
11044 graphic_info[special_graphic].anim_delay_fixed +
11045 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11046 player->post_delay_counter =
11047 graphic_info[special_graphic].post_delay_fixed +
11048 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11050 player->special_action_bored = special_action;
11053 if (player->anim_delay_counter > 0)
11055 player->action_waiting = player->special_action_bored;
11056 player->anim_delay_counter--;
11058 else if (player->post_delay_counter > 0)
11060 player->post_delay_counter--;
11065 else if (last_waiting) // waiting -> not waiting
11067 player->is_waiting = FALSE;
11068 player->is_bored = FALSE;
11069 player->is_sleeping = FALSE;
11071 player->frame_counter_bored = -1;
11072 player->frame_counter_sleeping = -1;
11074 player->anim_delay_counter = 0;
11075 player->post_delay_counter = 0;
11077 player->dir_waiting = player->MovDir;
11078 player->action_waiting = ACTION_DEFAULT;
11080 player->special_action_bored = ACTION_DEFAULT;
11081 player->special_action_sleeping = ACTION_DEFAULT;
11085 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11087 if ((!player->is_moving && player->was_moving) ||
11088 (player->MovPos == 0 && player->was_moving) ||
11089 (player->is_snapping && !player->was_snapping) ||
11090 (player->is_dropping && !player->was_dropping))
11092 if (!CheckSaveEngineSnapshotToList())
11095 player->was_moving = FALSE;
11096 player->was_snapping = TRUE;
11097 player->was_dropping = TRUE;
11101 if (player->is_moving)
11102 player->was_moving = TRUE;
11104 if (!player->is_snapping)
11105 player->was_snapping = FALSE;
11107 if (!player->is_dropping)
11108 player->was_dropping = FALSE;
11112 static void CheckSingleStepMode(struct PlayerInfo *player)
11114 if (tape.single_step && tape.recording && !tape.pausing)
11116 /* as it is called "single step mode", just return to pause mode when the
11117 player stopped moving after one tile (or never starts moving at all) */
11118 if (!player->is_moving &&
11119 !player->is_pushing &&
11120 !player->is_dropping_pressed)
11121 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11124 CheckSaveEngineSnapshot(player);
11127 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11129 int left = player_action & JOY_LEFT;
11130 int right = player_action & JOY_RIGHT;
11131 int up = player_action & JOY_UP;
11132 int down = player_action & JOY_DOWN;
11133 int button1 = player_action & JOY_BUTTON_1;
11134 int button2 = player_action & JOY_BUTTON_2;
11135 int dx = (left ? -1 : right ? 1 : 0);
11136 int dy = (up ? -1 : down ? 1 : 0);
11138 if (!player->active || tape.pausing)
11144 SnapField(player, dx, dy);
11148 DropElement(player);
11150 MovePlayer(player, dx, dy);
11153 CheckSingleStepMode(player);
11155 SetPlayerWaiting(player, FALSE);
11157 return player_action;
11161 // no actions for this player (no input at player's configured device)
11163 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11164 SnapField(player, 0, 0);
11165 CheckGravityMovementWhenNotMoving(player);
11167 if (player->MovPos == 0)
11168 SetPlayerWaiting(player, TRUE);
11170 if (player->MovPos == 0) // needed for tape.playing
11171 player->is_moving = FALSE;
11173 player->is_dropping = FALSE;
11174 player->is_dropping_pressed = FALSE;
11175 player->drop_pressed_delay = 0;
11177 CheckSingleStepMode(player);
11183 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11186 if (!tape.use_mouse_actions)
11189 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11190 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11191 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11194 static void SetTapeActionFromMouseAction(byte *tape_action,
11195 struct MouseActionInfo *mouse_action)
11197 if (!tape.use_mouse_actions)
11200 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11201 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11202 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11205 static void CheckLevelSolved(void)
11207 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11209 if (game_em.level_solved &&
11210 !game_em.game_over) // game won
11214 game_em.game_over = TRUE;
11216 game.all_players_gone = TRUE;
11219 if (game_em.game_over) // game lost
11220 game.all_players_gone = TRUE;
11222 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11224 if (game_sp.level_solved &&
11225 !game_sp.game_over) // game won
11229 game_sp.game_over = TRUE;
11231 game.all_players_gone = TRUE;
11234 if (game_sp.game_over) // game lost
11235 game.all_players_gone = TRUE;
11237 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11239 if (game_mm.level_solved &&
11240 !game_mm.game_over) // game won
11244 game_mm.game_over = TRUE;
11246 game.all_players_gone = TRUE;
11249 if (game_mm.game_over) // game lost
11250 game.all_players_gone = TRUE;
11254 static void CheckLevelTime(void)
11258 if (TimeFrames >= FRAMES_PER_SECOND)
11263 for (i = 0; i < MAX_PLAYERS; i++)
11265 struct PlayerInfo *player = &stored_player[i];
11267 if (SHIELD_ON(player))
11269 player->shield_normal_time_left--;
11271 if (player->shield_deadly_time_left > 0)
11272 player->shield_deadly_time_left--;
11276 if (!game.LevelSolved && !level.use_step_counter)
11284 if (TimeLeft <= 10 && setup.time_limit)
11285 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11287 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11288 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11290 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11292 if (!TimeLeft && setup.time_limit)
11294 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11295 game_em.lev->killed_out_of_time = TRUE;
11297 for (i = 0; i < MAX_PLAYERS; i++)
11298 KillPlayer(&stored_player[i]);
11301 else if (game.no_time_limit && !game.all_players_gone)
11303 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11306 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11309 if (tape.recording || tape.playing)
11310 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11313 if (tape.recording || tape.playing)
11314 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11316 UpdateAndDisplayGameControlValues();
11319 void AdvanceFrameAndPlayerCounters(int player_nr)
11323 // advance frame counters (global frame counter and time frame counter)
11327 // advance player counters (counters for move delay, move animation etc.)
11328 for (i = 0; i < MAX_PLAYERS; i++)
11330 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11331 int move_delay_value = stored_player[i].move_delay_value;
11332 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11334 if (!advance_player_counters) // not all players may be affected
11337 if (move_frames == 0) // less than one move per game frame
11339 int stepsize = TILEX / move_delay_value;
11340 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11341 int count = (stored_player[i].is_moving ?
11342 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11344 if (count % delay == 0)
11348 stored_player[i].Frame += move_frames;
11350 if (stored_player[i].MovPos != 0)
11351 stored_player[i].StepFrame += move_frames;
11353 if (stored_player[i].move_delay > 0)
11354 stored_player[i].move_delay--;
11356 // due to bugs in previous versions, counter must count up, not down
11357 if (stored_player[i].push_delay != -1)
11358 stored_player[i].push_delay++;
11360 if (stored_player[i].drop_delay > 0)
11361 stored_player[i].drop_delay--;
11363 if (stored_player[i].is_dropping_pressed)
11364 stored_player[i].drop_pressed_delay++;
11368 void StartGameActions(boolean init_network_game, boolean record_tape,
11371 unsigned int new_random_seed = InitRND(random_seed);
11374 TapeStartRecording(new_random_seed);
11376 if (init_network_game)
11378 SendToServer_LevelFile();
11379 SendToServer_StartPlaying();
11387 static void GameActionsExt(void)
11390 static unsigned int game_frame_delay = 0;
11392 unsigned int game_frame_delay_value;
11393 byte *recorded_player_action;
11394 byte summarized_player_action = 0;
11395 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11398 // detect endless loops, caused by custom element programming
11399 if (recursion_loop_detected && recursion_loop_depth == 0)
11401 char *message = getStringCat3("Internal Error! Element ",
11402 EL_NAME(recursion_loop_element),
11403 " caused endless loop! Quit the game?");
11405 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11406 EL_NAME(recursion_loop_element));
11408 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11410 recursion_loop_detected = FALSE; // if game should be continued
11417 if (game.restart_level)
11418 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11420 CheckLevelSolved();
11422 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11425 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11428 if (game_status != GAME_MODE_PLAYING) // status might have changed
11431 game_frame_delay_value =
11432 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11434 if (tape.playing && tape.warp_forward && !tape.pausing)
11435 game_frame_delay_value = 0;
11437 SetVideoFrameDelay(game_frame_delay_value);
11439 // (de)activate virtual buttons depending on current game status
11440 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11442 if (game.all_players_gone) // if no players there to be controlled anymore
11443 SetOverlayActive(FALSE);
11444 else if (!tape.playing) // if game continues after tape stopped playing
11445 SetOverlayActive(TRUE);
11450 // ---------- main game synchronization point ----------
11452 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11454 printf("::: skip == %d\n", skip);
11457 // ---------- main game synchronization point ----------
11459 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11463 if (network_playing && !network_player_action_received)
11465 // try to get network player actions in time
11467 // last chance to get network player actions without main loop delay
11468 HandleNetworking();
11470 // game was quit by network peer
11471 if (game_status != GAME_MODE_PLAYING)
11474 // check if network player actions still missing and game still running
11475 if (!network_player_action_received && !checkGameEnded())
11476 return; // failed to get network player actions in time
11478 // do not yet reset "network_player_action_received" (for tape.pausing)
11484 // at this point we know that we really continue executing the game
11486 network_player_action_received = FALSE;
11488 // when playing tape, read previously recorded player input from tape data
11489 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11491 local_player->effective_mouse_action = local_player->mouse_action;
11493 if (recorded_player_action != NULL)
11494 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11495 recorded_player_action);
11497 // TapePlayAction() may return NULL when toggling to "pause before death"
11501 if (tape.set_centered_player)
11503 game.centered_player_nr_next = tape.centered_player_nr_next;
11504 game.set_centered_player = TRUE;
11507 for (i = 0; i < MAX_PLAYERS; i++)
11509 summarized_player_action |= stored_player[i].action;
11511 if (!network_playing && (game.team_mode || tape.playing))
11512 stored_player[i].effective_action = stored_player[i].action;
11515 if (network_playing && !checkGameEnded())
11516 SendToServer_MovePlayer(summarized_player_action);
11518 // summarize all actions at local players mapped input device position
11519 // (this allows using different input devices in single player mode)
11520 if (!network.enabled && !game.team_mode)
11521 stored_player[map_player_action[local_player->index_nr]].effective_action =
11522 summarized_player_action;
11524 // summarize all actions at centered player in local team mode
11525 if (tape.recording &&
11526 setup.team_mode && !network.enabled &&
11527 setup.input_on_focus &&
11528 game.centered_player_nr != -1)
11530 for (i = 0; i < MAX_PLAYERS; i++)
11531 stored_player[map_player_action[i]].effective_action =
11532 (i == game.centered_player_nr ? summarized_player_action : 0);
11535 if (recorded_player_action != NULL)
11536 for (i = 0; i < MAX_PLAYERS; i++)
11537 stored_player[i].effective_action = recorded_player_action[i];
11539 for (i = 0; i < MAX_PLAYERS; i++)
11541 tape_action[i] = stored_player[i].effective_action;
11543 /* (this may happen in the RND game engine if a player was not present on
11544 the playfield on level start, but appeared later from a custom element */
11545 if (setup.team_mode &&
11548 !tape.player_participates[i])
11549 tape.player_participates[i] = TRUE;
11552 SetTapeActionFromMouseAction(tape_action,
11553 &local_player->effective_mouse_action);
11555 // only record actions from input devices, but not programmed actions
11556 if (tape.recording)
11557 TapeRecordAction(tape_action);
11559 // remember if game was played (especially after tape stopped playing)
11560 if (!tape.playing && summarized_player_action)
11561 game.GamePlayed = TRUE;
11563 #if USE_NEW_PLAYER_ASSIGNMENTS
11564 // !!! also map player actions in single player mode !!!
11565 // if (game.team_mode)
11568 byte mapped_action[MAX_PLAYERS];
11570 #if DEBUG_PLAYER_ACTIONS
11572 for (i = 0; i < MAX_PLAYERS; i++)
11573 printf(" %d, ", stored_player[i].effective_action);
11576 for (i = 0; i < MAX_PLAYERS; i++)
11577 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11579 for (i = 0; i < MAX_PLAYERS; i++)
11580 stored_player[i].effective_action = mapped_action[i];
11582 #if DEBUG_PLAYER_ACTIONS
11584 for (i = 0; i < MAX_PLAYERS; i++)
11585 printf(" %d, ", stored_player[i].effective_action);
11589 #if DEBUG_PLAYER_ACTIONS
11593 for (i = 0; i < MAX_PLAYERS; i++)
11594 printf(" %d, ", stored_player[i].effective_action);
11600 for (i = 0; i < MAX_PLAYERS; i++)
11602 // allow engine snapshot in case of changed movement attempt
11603 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11604 (stored_player[i].effective_action & KEY_MOTION))
11605 game.snapshot.changed_action = TRUE;
11607 // allow engine snapshot in case of snapping/dropping attempt
11608 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11609 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11610 game.snapshot.changed_action = TRUE;
11612 game.snapshot.last_action[i] = stored_player[i].effective_action;
11615 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11617 GameActions_EM_Main();
11619 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11621 GameActions_SP_Main();
11623 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11625 GameActions_MM_Main();
11629 GameActions_RND_Main();
11632 BlitScreenToBitmap(backbuffer);
11634 CheckLevelSolved();
11637 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11639 if (global.show_frames_per_second)
11641 static unsigned int fps_counter = 0;
11642 static int fps_frames = 0;
11643 unsigned int fps_delay_ms = Counter() - fps_counter;
11647 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11649 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11652 fps_counter = Counter();
11654 // always draw FPS to screen after FPS value was updated
11655 redraw_mask |= REDRAW_FPS;
11658 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11659 if (GetDrawDeactivationMask() == REDRAW_NONE)
11660 redraw_mask |= REDRAW_FPS;
11664 static void GameActions_CheckSaveEngineSnapshot(void)
11666 if (!game.snapshot.save_snapshot)
11669 // clear flag for saving snapshot _before_ saving snapshot
11670 game.snapshot.save_snapshot = FALSE;
11672 SaveEngineSnapshotToList();
11675 void GameActions(void)
11679 GameActions_CheckSaveEngineSnapshot();
11682 void GameActions_EM_Main(void)
11684 byte effective_action[MAX_PLAYERS];
11685 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11688 for (i = 0; i < MAX_PLAYERS; i++)
11689 effective_action[i] = stored_player[i].effective_action;
11691 GameActions_EM(effective_action, warp_mode);
11694 void GameActions_SP_Main(void)
11696 byte effective_action[MAX_PLAYERS];
11697 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11700 for (i = 0; i < MAX_PLAYERS; i++)
11701 effective_action[i] = stored_player[i].effective_action;
11703 GameActions_SP(effective_action, warp_mode);
11705 for (i = 0; i < MAX_PLAYERS; i++)
11707 if (stored_player[i].force_dropping)
11708 stored_player[i].action |= KEY_BUTTON_DROP;
11710 stored_player[i].force_dropping = FALSE;
11714 void GameActions_MM_Main(void)
11716 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11718 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11721 void GameActions_RND_Main(void)
11726 void GameActions_RND(void)
11728 static struct MouseActionInfo mouse_action_last = { 0 };
11729 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11730 int magic_wall_x = 0, magic_wall_y = 0;
11731 int i, x, y, element, graphic, last_gfx_frame;
11733 InitPlayfieldScanModeVars();
11735 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11737 SCAN_PLAYFIELD(x, y)
11739 ChangeCount[x][y] = 0;
11740 ChangeEvent[x][y] = -1;
11744 if (game.set_centered_player)
11746 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11748 // switching to "all players" only possible if all players fit to screen
11749 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11751 game.centered_player_nr_next = game.centered_player_nr;
11752 game.set_centered_player = FALSE;
11755 // do not switch focus to non-existing (or non-active) player
11756 if (game.centered_player_nr_next >= 0 &&
11757 !stored_player[game.centered_player_nr_next].active)
11759 game.centered_player_nr_next = game.centered_player_nr;
11760 game.set_centered_player = FALSE;
11764 if (game.set_centered_player &&
11765 ScreenMovPos == 0) // screen currently aligned at tile position
11769 if (game.centered_player_nr_next == -1)
11771 setScreenCenteredToAllPlayers(&sx, &sy);
11775 sx = stored_player[game.centered_player_nr_next].jx;
11776 sy = stored_player[game.centered_player_nr_next].jy;
11779 game.centered_player_nr = game.centered_player_nr_next;
11780 game.set_centered_player = FALSE;
11782 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11783 DrawGameDoorValues();
11786 for (i = 0; i < MAX_PLAYERS; i++)
11788 int actual_player_action = stored_player[i].effective_action;
11791 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11792 - rnd_equinox_tetrachloride 048
11793 - rnd_equinox_tetrachloride_ii 096
11794 - rnd_emanuel_schmieg 002
11795 - doctor_sloan_ww 001, 020
11797 if (stored_player[i].MovPos == 0)
11798 CheckGravityMovement(&stored_player[i]);
11801 // overwrite programmed action with tape action
11802 if (stored_player[i].programmed_action)
11803 actual_player_action = stored_player[i].programmed_action;
11805 PlayerActions(&stored_player[i], actual_player_action);
11807 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11810 ScrollScreen(NULL, SCROLL_GO_ON);
11812 /* for backwards compatibility, the following code emulates a fixed bug that
11813 occured when pushing elements (causing elements that just made their last
11814 pushing step to already (if possible) make their first falling step in the
11815 same game frame, which is bad); this code is also needed to use the famous
11816 "spring push bug" which is used in older levels and might be wanted to be
11817 used also in newer levels, but in this case the buggy pushing code is only
11818 affecting the "spring" element and no other elements */
11820 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11822 for (i = 0; i < MAX_PLAYERS; i++)
11824 struct PlayerInfo *player = &stored_player[i];
11825 int x = player->jx;
11826 int y = player->jy;
11828 if (player->active && player->is_pushing && player->is_moving &&
11830 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11831 Feld[x][y] == EL_SPRING))
11833 ContinueMoving(x, y);
11835 // continue moving after pushing (this is actually a bug)
11836 if (!IS_MOVING(x, y))
11837 Stop[x][y] = FALSE;
11842 SCAN_PLAYFIELD(x, y)
11844 Last[x][y] = Feld[x][y];
11846 ChangeCount[x][y] = 0;
11847 ChangeEvent[x][y] = -1;
11849 // this must be handled before main playfield loop
11850 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11853 if (MovDelay[x][y] <= 0)
11857 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11860 if (MovDelay[x][y] <= 0)
11863 TEST_DrawLevelField(x, y);
11865 TestIfElementTouchesCustomElement(x, y); // for empty space
11870 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11872 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11873 printf("GameActions(): This should never happen!\n");
11875 ChangePage[x][y] = -1;
11879 Stop[x][y] = FALSE;
11880 if (WasJustMoving[x][y] > 0)
11881 WasJustMoving[x][y]--;
11882 if (WasJustFalling[x][y] > 0)
11883 WasJustFalling[x][y]--;
11884 if (CheckCollision[x][y] > 0)
11885 CheckCollision[x][y]--;
11886 if (CheckImpact[x][y] > 0)
11887 CheckImpact[x][y]--;
11891 /* reset finished pushing action (not done in ContinueMoving() to allow
11892 continuous pushing animation for elements with zero push delay) */
11893 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11895 ResetGfxAnimation(x, y);
11896 TEST_DrawLevelField(x, y);
11900 if (IS_BLOCKED(x, y))
11904 Blocked2Moving(x, y, &oldx, &oldy);
11905 if (!IS_MOVING(oldx, oldy))
11907 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11908 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11909 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11910 printf("GameActions(): This should never happen!\n");
11916 if (mouse_action.button)
11918 int new_button = (mouse_action.button && mouse_action_last.button == 0);
11920 x = mouse_action.lx;
11921 y = mouse_action.ly;
11922 element = Feld[x][y];
11926 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
11927 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
11930 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
11931 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
11934 SCAN_PLAYFIELD(x, y)
11936 element = Feld[x][y];
11937 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11938 last_gfx_frame = GfxFrame[x][y];
11940 ResetGfxFrame(x, y);
11942 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
11943 DrawLevelGraphicAnimation(x, y, graphic);
11945 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
11946 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
11947 ResetRandomAnimationValue(x, y);
11949 SetRandomAnimationValue(x, y);
11951 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
11953 if (IS_INACTIVE(element))
11955 if (IS_ANIMATED(graphic))
11956 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11961 // this may take place after moving, so 'element' may have changed
11962 if (IS_CHANGING(x, y) &&
11963 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
11965 int page = element_info[element].event_page_nr[CE_DELAY];
11967 HandleElementChange(x, y, page);
11969 element = Feld[x][y];
11970 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11973 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
11977 element = Feld[x][y];
11978 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11980 if (IS_ANIMATED(graphic) &&
11981 !IS_MOVING(x, y) &&
11983 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11985 if (IS_GEM(element) || element == EL_SP_INFOTRON)
11986 TEST_DrawTwinkleOnField(x, y);
11988 else if (element == EL_ACID)
11991 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11993 else if ((element == EL_EXIT_OPEN ||
11994 element == EL_EM_EXIT_OPEN ||
11995 element == EL_SP_EXIT_OPEN ||
11996 element == EL_STEEL_EXIT_OPEN ||
11997 element == EL_EM_STEEL_EXIT_OPEN ||
11998 element == EL_SP_TERMINAL ||
11999 element == EL_SP_TERMINAL_ACTIVE ||
12000 element == EL_EXTRA_TIME ||
12001 element == EL_SHIELD_NORMAL ||
12002 element == EL_SHIELD_DEADLY) &&
12003 IS_ANIMATED(graphic))
12004 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12005 else if (IS_MOVING(x, y))
12006 ContinueMoving(x, y);
12007 else if (IS_ACTIVE_BOMB(element))
12008 CheckDynamite(x, y);
12009 else if (element == EL_AMOEBA_GROWING)
12010 AmoebeWaechst(x, y);
12011 else if (element == EL_AMOEBA_SHRINKING)
12012 AmoebaDisappearing(x, y);
12014 #if !USE_NEW_AMOEBA_CODE
12015 else if (IS_AMOEBALIVE(element))
12016 AmoebeAbleger(x, y);
12019 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12021 else if (element == EL_EXIT_CLOSED)
12023 else if (element == EL_EM_EXIT_CLOSED)
12025 else if (element == EL_STEEL_EXIT_CLOSED)
12026 CheckExitSteel(x, y);
12027 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12028 CheckExitSteelEM(x, y);
12029 else if (element == EL_SP_EXIT_CLOSED)
12031 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12032 element == EL_EXPANDABLE_STEELWALL_GROWING)
12033 MauerWaechst(x, y);
12034 else if (element == EL_EXPANDABLE_WALL ||
12035 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12036 element == EL_EXPANDABLE_WALL_VERTICAL ||
12037 element == EL_EXPANDABLE_WALL_ANY ||
12038 element == EL_BD_EXPANDABLE_WALL)
12039 MauerAbleger(x, y);
12040 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12041 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12042 element == EL_EXPANDABLE_STEELWALL_ANY)
12043 MauerAblegerStahl(x, y);
12044 else if (element == EL_FLAMES)
12045 CheckForDragon(x, y);
12046 else if (element == EL_EXPLOSION)
12047 ; // drawing of correct explosion animation is handled separately
12048 else if (element == EL_ELEMENT_SNAPPING ||
12049 element == EL_DIAGONAL_SHRINKING ||
12050 element == EL_DIAGONAL_GROWING)
12052 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12054 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12056 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12057 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12059 if (IS_BELT_ACTIVE(element))
12060 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12062 if (game.magic_wall_active)
12064 int jx = local_player->jx, jy = local_player->jy;
12066 // play the element sound at the position nearest to the player
12067 if ((element == EL_MAGIC_WALL_FULL ||
12068 element == EL_MAGIC_WALL_ACTIVE ||
12069 element == EL_MAGIC_WALL_EMPTYING ||
12070 element == EL_BD_MAGIC_WALL_FULL ||
12071 element == EL_BD_MAGIC_WALL_ACTIVE ||
12072 element == EL_BD_MAGIC_WALL_EMPTYING ||
12073 element == EL_DC_MAGIC_WALL_FULL ||
12074 element == EL_DC_MAGIC_WALL_ACTIVE ||
12075 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12076 ABS(x - jx) + ABS(y - jy) <
12077 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12085 #if USE_NEW_AMOEBA_CODE
12086 // new experimental amoeba growth stuff
12087 if (!(FrameCounter % 8))
12089 static unsigned int random = 1684108901;
12091 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12093 x = RND(lev_fieldx);
12094 y = RND(lev_fieldy);
12095 element = Feld[x][y];
12097 if (!IS_PLAYER(x,y) &&
12098 (element == EL_EMPTY ||
12099 CAN_GROW_INTO(element) ||
12100 element == EL_QUICKSAND_EMPTY ||
12101 element == EL_QUICKSAND_FAST_EMPTY ||
12102 element == EL_ACID_SPLASH_LEFT ||
12103 element == EL_ACID_SPLASH_RIGHT))
12105 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12106 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12107 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12108 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12109 Feld[x][y] = EL_AMOEBA_DROP;
12112 random = random * 129 + 1;
12117 game.explosions_delayed = FALSE;
12119 SCAN_PLAYFIELD(x, y)
12121 element = Feld[x][y];
12123 if (ExplodeField[x][y])
12124 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12125 else if (element == EL_EXPLOSION)
12126 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12128 ExplodeField[x][y] = EX_TYPE_NONE;
12131 game.explosions_delayed = TRUE;
12133 if (game.magic_wall_active)
12135 if (!(game.magic_wall_time_left % 4))
12137 int element = Feld[magic_wall_x][magic_wall_y];
12139 if (element == EL_BD_MAGIC_WALL_FULL ||
12140 element == EL_BD_MAGIC_WALL_ACTIVE ||
12141 element == EL_BD_MAGIC_WALL_EMPTYING)
12142 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12143 else if (element == EL_DC_MAGIC_WALL_FULL ||
12144 element == EL_DC_MAGIC_WALL_ACTIVE ||
12145 element == EL_DC_MAGIC_WALL_EMPTYING)
12146 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12148 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12151 if (game.magic_wall_time_left > 0)
12153 game.magic_wall_time_left--;
12155 if (!game.magic_wall_time_left)
12157 SCAN_PLAYFIELD(x, y)
12159 element = Feld[x][y];
12161 if (element == EL_MAGIC_WALL_ACTIVE ||
12162 element == EL_MAGIC_WALL_FULL)
12164 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12165 TEST_DrawLevelField(x, y);
12167 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12168 element == EL_BD_MAGIC_WALL_FULL)
12170 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12171 TEST_DrawLevelField(x, y);
12173 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12174 element == EL_DC_MAGIC_WALL_FULL)
12176 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12177 TEST_DrawLevelField(x, y);
12181 game.magic_wall_active = FALSE;
12186 if (game.light_time_left > 0)
12188 game.light_time_left--;
12190 if (game.light_time_left == 0)
12191 RedrawAllLightSwitchesAndInvisibleElements();
12194 if (game.timegate_time_left > 0)
12196 game.timegate_time_left--;
12198 if (game.timegate_time_left == 0)
12199 CloseAllOpenTimegates();
12202 if (game.lenses_time_left > 0)
12204 game.lenses_time_left--;
12206 if (game.lenses_time_left == 0)
12207 RedrawAllInvisibleElementsForLenses();
12210 if (game.magnify_time_left > 0)
12212 game.magnify_time_left--;
12214 if (game.magnify_time_left == 0)
12215 RedrawAllInvisibleElementsForMagnifier();
12218 for (i = 0; i < MAX_PLAYERS; i++)
12220 struct PlayerInfo *player = &stored_player[i];
12222 if (SHIELD_ON(player))
12224 if (player->shield_deadly_time_left)
12225 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12226 else if (player->shield_normal_time_left)
12227 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12231 #if USE_DELAYED_GFX_REDRAW
12232 SCAN_PLAYFIELD(x, y)
12234 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12236 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12237 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12239 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12240 DrawLevelField(x, y);
12242 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12243 DrawLevelFieldCrumbled(x, y);
12245 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12246 DrawLevelFieldCrumbledNeighbours(x, y);
12248 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12249 DrawTwinkleOnField(x, y);
12252 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12257 PlayAllPlayersSound();
12259 for (i = 0; i < MAX_PLAYERS; i++)
12261 struct PlayerInfo *player = &stored_player[i];
12263 if (player->show_envelope != 0 && (!player->active ||
12264 player->MovPos == 0))
12266 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12268 player->show_envelope = 0;
12272 // use random number generator in every frame to make it less predictable
12273 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12276 mouse_action_last = mouse_action;
12279 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12281 int min_x = x, min_y = y, max_x = x, max_y = y;
12284 for (i = 0; i < MAX_PLAYERS; i++)
12286 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12288 if (!stored_player[i].active || &stored_player[i] == player)
12291 min_x = MIN(min_x, jx);
12292 min_y = MIN(min_y, jy);
12293 max_x = MAX(max_x, jx);
12294 max_y = MAX(max_y, jy);
12297 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12300 static boolean AllPlayersInVisibleScreen(void)
12304 for (i = 0; i < MAX_PLAYERS; i++)
12306 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12308 if (!stored_player[i].active)
12311 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12318 void ScrollLevel(int dx, int dy)
12320 int scroll_offset = 2 * TILEX_VAR;
12323 BlitBitmap(drawto_field, drawto_field,
12324 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12325 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12326 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12327 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12328 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12329 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12333 x = (dx == 1 ? BX1 : BX2);
12334 for (y = BY1; y <= BY2; y++)
12335 DrawScreenField(x, y);
12340 y = (dy == 1 ? BY1 : BY2);
12341 for (x = BX1; x <= BX2; x++)
12342 DrawScreenField(x, y);
12345 redraw_mask |= REDRAW_FIELD;
12348 static boolean canFallDown(struct PlayerInfo *player)
12350 int jx = player->jx, jy = player->jy;
12352 return (IN_LEV_FIELD(jx, jy + 1) &&
12353 (IS_FREE(jx, jy + 1) ||
12354 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12355 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12356 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12359 static boolean canPassField(int x, int y, int move_dir)
12361 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12362 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12363 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12364 int nextx = x + dx;
12365 int nexty = y + dy;
12366 int element = Feld[x][y];
12368 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12369 !CAN_MOVE(element) &&
12370 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12371 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12372 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12375 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12377 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12378 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12379 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12383 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12384 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12385 (IS_DIGGABLE(Feld[newx][newy]) ||
12386 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12387 canPassField(newx, newy, move_dir)));
12390 static void CheckGravityMovement(struct PlayerInfo *player)
12392 if (player->gravity && !player->programmed_action)
12394 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12395 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12396 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12397 int jx = player->jx, jy = player->jy;
12398 boolean player_is_moving_to_valid_field =
12399 (!player_is_snapping &&
12400 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12401 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12402 boolean player_can_fall_down = canFallDown(player);
12404 if (player_can_fall_down &&
12405 !player_is_moving_to_valid_field)
12406 player->programmed_action = MV_DOWN;
12410 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12412 return CheckGravityMovement(player);
12414 if (player->gravity && !player->programmed_action)
12416 int jx = player->jx, jy = player->jy;
12417 boolean field_under_player_is_free =
12418 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12419 boolean player_is_standing_on_valid_field =
12420 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12421 (IS_WALKABLE(Feld[jx][jy]) &&
12422 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12424 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12425 player->programmed_action = MV_DOWN;
12430 MovePlayerOneStep()
12431 -----------------------------------------------------------------------------
12432 dx, dy: direction (non-diagonal) to try to move the player to
12433 real_dx, real_dy: direction as read from input device (can be diagonal)
12436 boolean MovePlayerOneStep(struct PlayerInfo *player,
12437 int dx, int dy, int real_dx, int real_dy)
12439 int jx = player->jx, jy = player->jy;
12440 int new_jx = jx + dx, new_jy = jy + dy;
12442 boolean player_can_move = !player->cannot_move;
12444 if (!player->active || (!dx && !dy))
12445 return MP_NO_ACTION;
12447 player->MovDir = (dx < 0 ? MV_LEFT :
12448 dx > 0 ? MV_RIGHT :
12450 dy > 0 ? MV_DOWN : MV_NONE);
12452 if (!IN_LEV_FIELD(new_jx, new_jy))
12453 return MP_NO_ACTION;
12455 if (!player_can_move)
12457 if (player->MovPos == 0)
12459 player->is_moving = FALSE;
12460 player->is_digging = FALSE;
12461 player->is_collecting = FALSE;
12462 player->is_snapping = FALSE;
12463 player->is_pushing = FALSE;
12467 if (!network.enabled && game.centered_player_nr == -1 &&
12468 !AllPlayersInSight(player, new_jx, new_jy))
12469 return MP_NO_ACTION;
12471 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12472 if (can_move != MP_MOVING)
12475 // check if DigField() has caused relocation of the player
12476 if (player->jx != jx || player->jy != jy)
12477 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12479 StorePlayer[jx][jy] = 0;
12480 player->last_jx = jx;
12481 player->last_jy = jy;
12482 player->jx = new_jx;
12483 player->jy = new_jy;
12484 StorePlayer[new_jx][new_jy] = player->element_nr;
12486 if (player->move_delay_value_next != -1)
12488 player->move_delay_value = player->move_delay_value_next;
12489 player->move_delay_value_next = -1;
12493 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12495 player->step_counter++;
12497 PlayerVisit[jx][jy] = FrameCounter;
12499 player->is_moving = TRUE;
12502 // should better be called in MovePlayer(), but this breaks some tapes
12503 ScrollPlayer(player, SCROLL_INIT);
12509 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12511 int jx = player->jx, jy = player->jy;
12512 int old_jx = jx, old_jy = jy;
12513 int moved = MP_NO_ACTION;
12515 if (!player->active)
12520 if (player->MovPos == 0)
12522 player->is_moving = FALSE;
12523 player->is_digging = FALSE;
12524 player->is_collecting = FALSE;
12525 player->is_snapping = FALSE;
12526 player->is_pushing = FALSE;
12532 if (player->move_delay > 0)
12535 player->move_delay = -1; // set to "uninitialized" value
12537 // store if player is automatically moved to next field
12538 player->is_auto_moving = (player->programmed_action != MV_NONE);
12540 // remove the last programmed player action
12541 player->programmed_action = 0;
12543 if (player->MovPos)
12545 // should only happen if pre-1.2 tape recordings are played
12546 // this is only for backward compatibility
12548 int original_move_delay_value = player->move_delay_value;
12551 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12555 // scroll remaining steps with finest movement resolution
12556 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12558 while (player->MovPos)
12560 ScrollPlayer(player, SCROLL_GO_ON);
12561 ScrollScreen(NULL, SCROLL_GO_ON);
12563 AdvanceFrameAndPlayerCounters(player->index_nr);
12566 BackToFront_WithFrameDelay(0);
12569 player->move_delay_value = original_move_delay_value;
12572 player->is_active = FALSE;
12574 if (player->last_move_dir & MV_HORIZONTAL)
12576 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12577 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12581 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12582 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12585 if (!moved && !player->is_active)
12587 player->is_moving = FALSE;
12588 player->is_digging = FALSE;
12589 player->is_collecting = FALSE;
12590 player->is_snapping = FALSE;
12591 player->is_pushing = FALSE;
12597 if (moved & MP_MOVING && !ScreenMovPos &&
12598 (player->index_nr == game.centered_player_nr ||
12599 game.centered_player_nr == -1))
12601 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12603 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12605 // actual player has left the screen -- scroll in that direction
12606 if (jx != old_jx) // player has moved horizontally
12607 scroll_x += (jx - old_jx);
12608 else // player has moved vertically
12609 scroll_y += (jy - old_jy);
12613 int offset_raw = game.scroll_delay_value;
12615 if (jx != old_jx) // player has moved horizontally
12617 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12618 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12619 int new_scroll_x = jx - MIDPOSX + offset_x;
12621 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12622 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12623 scroll_x = new_scroll_x;
12625 // don't scroll over playfield boundaries
12626 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12628 // don't scroll more than one field at a time
12629 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12631 // don't scroll against the player's moving direction
12632 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12633 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12634 scroll_x = old_scroll_x;
12636 else // player has moved vertically
12638 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12639 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12640 int new_scroll_y = jy - MIDPOSY + offset_y;
12642 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12643 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12644 scroll_y = new_scroll_y;
12646 // don't scroll over playfield boundaries
12647 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12649 // don't scroll more than one field at a time
12650 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12652 // don't scroll against the player's moving direction
12653 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12654 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12655 scroll_y = old_scroll_y;
12659 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12661 if (!network.enabled && game.centered_player_nr == -1 &&
12662 !AllPlayersInVisibleScreen())
12664 scroll_x = old_scroll_x;
12665 scroll_y = old_scroll_y;
12669 ScrollScreen(player, SCROLL_INIT);
12670 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12675 player->StepFrame = 0;
12677 if (moved & MP_MOVING)
12679 if (old_jx != jx && old_jy == jy)
12680 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12681 else if (old_jx == jx && old_jy != jy)
12682 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12684 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12686 player->last_move_dir = player->MovDir;
12687 player->is_moving = TRUE;
12688 player->is_snapping = FALSE;
12689 player->is_switching = FALSE;
12690 player->is_dropping = FALSE;
12691 player->is_dropping_pressed = FALSE;
12692 player->drop_pressed_delay = 0;
12695 // should better be called here than above, but this breaks some tapes
12696 ScrollPlayer(player, SCROLL_INIT);
12701 CheckGravityMovementWhenNotMoving(player);
12703 player->is_moving = FALSE;
12705 /* at this point, the player is allowed to move, but cannot move right now
12706 (e.g. because of something blocking the way) -- ensure that the player
12707 is also allowed to move in the next frame (in old versions before 3.1.1,
12708 the player was forced to wait again for eight frames before next try) */
12710 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12711 player->move_delay = 0; // allow direct movement in the next frame
12714 if (player->move_delay == -1) // not yet initialized by DigField()
12715 player->move_delay = player->move_delay_value;
12717 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12719 TestIfPlayerTouchesBadThing(jx, jy);
12720 TestIfPlayerTouchesCustomElement(jx, jy);
12723 if (!player->active)
12724 RemovePlayer(player);
12729 void ScrollPlayer(struct PlayerInfo *player, int mode)
12731 int jx = player->jx, jy = player->jy;
12732 int last_jx = player->last_jx, last_jy = player->last_jy;
12733 int move_stepsize = TILEX / player->move_delay_value;
12735 if (!player->active)
12738 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12741 if (mode == SCROLL_INIT)
12743 player->actual_frame_counter = FrameCounter;
12744 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12746 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12747 Feld[last_jx][last_jy] == EL_EMPTY)
12749 int last_field_block_delay = 0; // start with no blocking at all
12750 int block_delay_adjustment = player->block_delay_adjustment;
12752 // if player blocks last field, add delay for exactly one move
12753 if (player->block_last_field)
12755 last_field_block_delay += player->move_delay_value;
12757 // when blocking enabled, prevent moving up despite gravity
12758 if (player->gravity && player->MovDir == MV_UP)
12759 block_delay_adjustment = -1;
12762 // add block delay adjustment (also possible when not blocking)
12763 last_field_block_delay += block_delay_adjustment;
12765 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12766 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12769 if (player->MovPos != 0) // player has not yet reached destination
12772 else if (!FrameReached(&player->actual_frame_counter, 1))
12775 if (player->MovPos != 0)
12777 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12778 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12780 // before DrawPlayer() to draw correct player graphic for this case
12781 if (player->MovPos == 0)
12782 CheckGravityMovement(player);
12785 if (player->MovPos == 0) // player reached destination field
12787 if (player->move_delay_reset_counter > 0)
12789 player->move_delay_reset_counter--;
12791 if (player->move_delay_reset_counter == 0)
12793 // continue with normal speed after quickly moving through gate
12794 HALVE_PLAYER_SPEED(player);
12796 // be able to make the next move without delay
12797 player->move_delay = 0;
12801 player->last_jx = jx;
12802 player->last_jy = jy;
12804 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12805 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12806 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12807 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12808 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12809 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12810 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12811 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12813 ExitPlayer(player);
12815 if (game.players_still_needed == 0 &&
12816 (game.friends_still_needed == 0 ||
12817 IS_SP_ELEMENT(Feld[jx][jy])))
12821 // this breaks one level: "machine", level 000
12823 int move_direction = player->MovDir;
12824 int enter_side = MV_DIR_OPPOSITE(move_direction);
12825 int leave_side = move_direction;
12826 int old_jx = last_jx;
12827 int old_jy = last_jy;
12828 int old_element = Feld[old_jx][old_jy];
12829 int new_element = Feld[jx][jy];
12831 if (IS_CUSTOM_ELEMENT(old_element))
12832 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12834 player->index_bit, leave_side);
12836 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12837 CE_PLAYER_LEAVES_X,
12838 player->index_bit, leave_side);
12840 if (IS_CUSTOM_ELEMENT(new_element))
12841 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12842 player->index_bit, enter_side);
12844 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12845 CE_PLAYER_ENTERS_X,
12846 player->index_bit, enter_side);
12848 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12849 CE_MOVE_OF_X, move_direction);
12852 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12854 TestIfPlayerTouchesBadThing(jx, jy);
12855 TestIfPlayerTouchesCustomElement(jx, jy);
12857 /* needed because pushed element has not yet reached its destination,
12858 so it would trigger a change event at its previous field location */
12859 if (!player->is_pushing)
12860 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12862 if (!player->active)
12863 RemovePlayer(player);
12866 if (!game.LevelSolved && level.use_step_counter)
12876 if (TimeLeft <= 10 && setup.time_limit)
12877 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12879 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12881 DisplayGameControlValues();
12883 if (!TimeLeft && setup.time_limit)
12884 for (i = 0; i < MAX_PLAYERS; i++)
12885 KillPlayer(&stored_player[i]);
12887 else if (game.no_time_limit && !game.all_players_gone)
12889 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12891 DisplayGameControlValues();
12895 if (tape.single_step && tape.recording && !tape.pausing &&
12896 !player->programmed_action)
12897 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12899 if (!player->programmed_action)
12900 CheckSaveEngineSnapshot(player);
12904 void ScrollScreen(struct PlayerInfo *player, int mode)
12906 static unsigned int screen_frame_counter = 0;
12908 if (mode == SCROLL_INIT)
12910 // set scrolling step size according to actual player's moving speed
12911 ScrollStepSize = TILEX / player->move_delay_value;
12913 screen_frame_counter = FrameCounter;
12914 ScreenMovDir = player->MovDir;
12915 ScreenMovPos = player->MovPos;
12916 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12919 else if (!FrameReached(&screen_frame_counter, 1))
12924 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
12925 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12926 redraw_mask |= REDRAW_FIELD;
12929 ScreenMovDir = MV_NONE;
12932 void TestIfPlayerTouchesCustomElement(int x, int y)
12934 static int xy[4][2] =
12941 static int trigger_sides[4][2] =
12943 // center side border side
12944 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12945 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12946 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12947 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12949 static int touch_dir[4] =
12951 MV_LEFT | MV_RIGHT,
12956 int center_element = Feld[x][y]; // should always be non-moving!
12959 for (i = 0; i < NUM_DIRECTIONS; i++)
12961 int xx = x + xy[i][0];
12962 int yy = y + xy[i][1];
12963 int center_side = trigger_sides[i][0];
12964 int border_side = trigger_sides[i][1];
12965 int border_element;
12967 if (!IN_LEV_FIELD(xx, yy))
12970 if (IS_PLAYER(x, y)) // player found at center element
12972 struct PlayerInfo *player = PLAYERINFO(x, y);
12974 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12975 border_element = Feld[xx][yy]; // may be moving!
12976 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12977 border_element = Feld[xx][yy];
12978 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
12979 border_element = MovingOrBlocked2Element(xx, yy);
12981 continue; // center and border element do not touch
12983 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
12984 player->index_bit, border_side);
12985 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
12986 CE_PLAYER_TOUCHES_X,
12987 player->index_bit, border_side);
12990 /* use player element that is initially defined in the level playfield,
12991 not the player element that corresponds to the runtime player number
12992 (example: a level that contains EL_PLAYER_3 as the only player would
12993 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12994 int player_element = PLAYERINFO(x, y)->initial_element;
12996 CheckElementChangeBySide(xx, yy, border_element, player_element,
12997 CE_TOUCHING_X, border_side);
13000 else if (IS_PLAYER(xx, yy)) // player found at border element
13002 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13004 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13006 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13007 continue; // center and border element do not touch
13010 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13011 player->index_bit, center_side);
13012 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13013 CE_PLAYER_TOUCHES_X,
13014 player->index_bit, center_side);
13017 /* use player element that is initially defined in the level playfield,
13018 not the player element that corresponds to the runtime player number
13019 (example: a level that contains EL_PLAYER_3 as the only player would
13020 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13021 int player_element = PLAYERINFO(xx, yy)->initial_element;
13023 CheckElementChangeBySide(x, y, center_element, player_element,
13024 CE_TOUCHING_X, center_side);
13032 void TestIfElementTouchesCustomElement(int x, int y)
13034 static int xy[4][2] =
13041 static int trigger_sides[4][2] =
13043 // center side border side
13044 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13045 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13046 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13047 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13049 static int touch_dir[4] =
13051 MV_LEFT | MV_RIGHT,
13056 boolean change_center_element = FALSE;
13057 int center_element = Feld[x][y]; // should always be non-moving!
13058 int border_element_old[NUM_DIRECTIONS];
13061 for (i = 0; i < NUM_DIRECTIONS; i++)
13063 int xx = x + xy[i][0];
13064 int yy = y + xy[i][1];
13065 int border_element;
13067 border_element_old[i] = -1;
13069 if (!IN_LEV_FIELD(xx, yy))
13072 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13073 border_element = Feld[xx][yy]; // may be moving!
13074 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13075 border_element = Feld[xx][yy];
13076 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13077 border_element = MovingOrBlocked2Element(xx, yy);
13079 continue; // center and border element do not touch
13081 border_element_old[i] = border_element;
13084 for (i = 0; i < NUM_DIRECTIONS; i++)
13086 int xx = x + xy[i][0];
13087 int yy = y + xy[i][1];
13088 int center_side = trigger_sides[i][0];
13089 int border_element = border_element_old[i];
13091 if (border_element == -1)
13094 // check for change of border element
13095 CheckElementChangeBySide(xx, yy, border_element, center_element,
13096 CE_TOUCHING_X, center_side);
13098 // (center element cannot be player, so we dont have to check this here)
13101 for (i = 0; i < NUM_DIRECTIONS; i++)
13103 int xx = x + xy[i][0];
13104 int yy = y + xy[i][1];
13105 int border_side = trigger_sides[i][1];
13106 int border_element = border_element_old[i];
13108 if (border_element == -1)
13111 // check for change of center element (but change it only once)
13112 if (!change_center_element)
13113 change_center_element =
13114 CheckElementChangeBySide(x, y, center_element, border_element,
13115 CE_TOUCHING_X, border_side);
13117 if (IS_PLAYER(xx, yy))
13119 /* use player element that is initially defined in the level playfield,
13120 not the player element that corresponds to the runtime player number
13121 (example: a level that contains EL_PLAYER_3 as the only player would
13122 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13123 int player_element = PLAYERINFO(xx, yy)->initial_element;
13125 CheckElementChangeBySide(x, y, center_element, player_element,
13126 CE_TOUCHING_X, border_side);
13131 void TestIfElementHitsCustomElement(int x, int y, int direction)
13133 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13134 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13135 int hitx = x + dx, hity = y + dy;
13136 int hitting_element = Feld[x][y];
13137 int touched_element;
13139 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13142 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13143 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13145 if (IN_LEV_FIELD(hitx, hity))
13147 int opposite_direction = MV_DIR_OPPOSITE(direction);
13148 int hitting_side = direction;
13149 int touched_side = opposite_direction;
13150 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13151 MovDir[hitx][hity] != direction ||
13152 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13158 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13159 CE_HITTING_X, touched_side);
13161 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13162 CE_HIT_BY_X, hitting_side);
13164 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13165 CE_HIT_BY_SOMETHING, opposite_direction);
13167 if (IS_PLAYER(hitx, hity))
13169 /* use player element that is initially defined in the level playfield,
13170 not the player element that corresponds to the runtime player number
13171 (example: a level that contains EL_PLAYER_3 as the only player would
13172 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13173 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13175 CheckElementChangeBySide(x, y, hitting_element, player_element,
13176 CE_HITTING_X, touched_side);
13181 // "hitting something" is also true when hitting the playfield border
13182 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13183 CE_HITTING_SOMETHING, direction);
13186 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13188 int i, kill_x = -1, kill_y = -1;
13190 int bad_element = -1;
13191 static int test_xy[4][2] =
13198 static int test_dir[4] =
13206 for (i = 0; i < NUM_DIRECTIONS; i++)
13208 int test_x, test_y, test_move_dir, test_element;
13210 test_x = good_x + test_xy[i][0];
13211 test_y = good_y + test_xy[i][1];
13213 if (!IN_LEV_FIELD(test_x, test_y))
13217 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13219 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13221 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13222 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13224 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13225 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13229 bad_element = test_element;
13235 if (kill_x != -1 || kill_y != -1)
13237 if (IS_PLAYER(good_x, good_y))
13239 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13241 if (player->shield_deadly_time_left > 0 &&
13242 !IS_INDESTRUCTIBLE(bad_element))
13243 Bang(kill_x, kill_y);
13244 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13245 KillPlayer(player);
13248 Bang(good_x, good_y);
13252 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13254 int i, kill_x = -1, kill_y = -1;
13255 int bad_element = Feld[bad_x][bad_y];
13256 static int test_xy[4][2] =
13263 static int touch_dir[4] =
13265 MV_LEFT | MV_RIGHT,
13270 static int test_dir[4] =
13278 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13281 for (i = 0; i < NUM_DIRECTIONS; i++)
13283 int test_x, test_y, test_move_dir, test_element;
13285 test_x = bad_x + test_xy[i][0];
13286 test_y = bad_y + test_xy[i][1];
13288 if (!IN_LEV_FIELD(test_x, test_y))
13292 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13294 test_element = Feld[test_x][test_y];
13296 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13297 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13299 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13300 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13302 // good thing is player or penguin that does not move away
13303 if (IS_PLAYER(test_x, test_y))
13305 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13307 if (bad_element == EL_ROBOT && player->is_moving)
13308 continue; // robot does not kill player if he is moving
13310 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13312 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13313 continue; // center and border element do not touch
13321 else if (test_element == EL_PENGUIN)
13331 if (kill_x != -1 || kill_y != -1)
13333 if (IS_PLAYER(kill_x, kill_y))
13335 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13337 if (player->shield_deadly_time_left > 0 &&
13338 !IS_INDESTRUCTIBLE(bad_element))
13339 Bang(bad_x, bad_y);
13340 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13341 KillPlayer(player);
13344 Bang(kill_x, kill_y);
13348 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13350 int bad_element = Feld[bad_x][bad_y];
13351 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13352 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13353 int test_x = bad_x + dx, test_y = bad_y + dy;
13354 int test_move_dir, test_element;
13355 int kill_x = -1, kill_y = -1;
13357 if (!IN_LEV_FIELD(test_x, test_y))
13361 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13363 test_element = Feld[test_x][test_y];
13365 if (test_move_dir != bad_move_dir)
13367 // good thing can be player or penguin that does not move away
13368 if (IS_PLAYER(test_x, test_y))
13370 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13372 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13373 player as being hit when he is moving towards the bad thing, because
13374 the "get hit by" condition would be lost after the player stops) */
13375 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13376 return; // player moves away from bad thing
13381 else if (test_element == EL_PENGUIN)
13388 if (kill_x != -1 || kill_y != -1)
13390 if (IS_PLAYER(kill_x, kill_y))
13392 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13394 if (player->shield_deadly_time_left > 0 &&
13395 !IS_INDESTRUCTIBLE(bad_element))
13396 Bang(bad_x, bad_y);
13397 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13398 KillPlayer(player);
13401 Bang(kill_x, kill_y);
13405 void TestIfPlayerTouchesBadThing(int x, int y)
13407 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13410 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13412 TestIfGoodThingHitsBadThing(x, y, move_dir);
13415 void TestIfBadThingTouchesPlayer(int x, int y)
13417 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13420 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13422 TestIfBadThingHitsGoodThing(x, y, move_dir);
13425 void TestIfFriendTouchesBadThing(int x, int y)
13427 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13430 void TestIfBadThingTouchesFriend(int x, int y)
13432 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13435 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13437 int i, kill_x = bad_x, kill_y = bad_y;
13438 static int xy[4][2] =
13446 for (i = 0; i < NUM_DIRECTIONS; i++)
13450 x = bad_x + xy[i][0];
13451 y = bad_y + xy[i][1];
13452 if (!IN_LEV_FIELD(x, y))
13455 element = Feld[x][y];
13456 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13457 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13465 if (kill_x != bad_x || kill_y != bad_y)
13466 Bang(bad_x, bad_y);
13469 void KillPlayer(struct PlayerInfo *player)
13471 int jx = player->jx, jy = player->jy;
13473 if (!player->active)
13477 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13478 player->killed, player->active, player->reanimated);
13481 /* the following code was introduced to prevent an infinite loop when calling
13483 -> CheckTriggeredElementChangeExt()
13484 -> ExecuteCustomElementAction()
13486 -> (infinitely repeating the above sequence of function calls)
13487 which occurs when killing the player while having a CE with the setting
13488 "kill player X when explosion of <player X>"; the solution using a new
13489 field "player->killed" was chosen for backwards compatibility, although
13490 clever use of the fields "player->active" etc. would probably also work */
13492 if (player->killed)
13496 player->killed = TRUE;
13498 // remove accessible field at the player's position
13499 Feld[jx][jy] = EL_EMPTY;
13501 // deactivate shield (else Bang()/Explode() would not work right)
13502 player->shield_normal_time_left = 0;
13503 player->shield_deadly_time_left = 0;
13506 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13507 player->killed, player->active, player->reanimated);
13513 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13514 player->killed, player->active, player->reanimated);
13517 if (player->reanimated) // killed player may have been reanimated
13518 player->killed = player->reanimated = FALSE;
13520 BuryPlayer(player);
13523 static void KillPlayerUnlessEnemyProtected(int x, int y)
13525 if (!PLAYER_ENEMY_PROTECTED(x, y))
13526 KillPlayer(PLAYERINFO(x, y));
13529 static void KillPlayerUnlessExplosionProtected(int x, int y)
13531 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13532 KillPlayer(PLAYERINFO(x, y));
13535 void BuryPlayer(struct PlayerInfo *player)
13537 int jx = player->jx, jy = player->jy;
13539 if (!player->active)
13542 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13543 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13545 RemovePlayer(player);
13547 player->buried = TRUE;
13549 if (game.all_players_gone)
13550 game.GameOver = TRUE;
13553 void RemovePlayer(struct PlayerInfo *player)
13555 int jx = player->jx, jy = player->jy;
13556 int i, found = FALSE;
13558 player->present = FALSE;
13559 player->active = FALSE;
13561 // required for some CE actions (even if the player is not active anymore)
13562 player->MovPos = 0;
13564 if (!ExplodeField[jx][jy])
13565 StorePlayer[jx][jy] = 0;
13567 if (player->is_moving)
13568 TEST_DrawLevelField(player->last_jx, player->last_jy);
13570 for (i = 0; i < MAX_PLAYERS; i++)
13571 if (stored_player[i].active)
13576 game.all_players_gone = TRUE;
13577 game.GameOver = TRUE;
13580 game.exit_x = game.robot_wheel_x = jx;
13581 game.exit_y = game.robot_wheel_y = jy;
13584 void ExitPlayer(struct PlayerInfo *player)
13586 DrawPlayer(player); // needed here only to cleanup last field
13587 RemovePlayer(player);
13589 if (game.players_still_needed > 0)
13590 game.players_still_needed--;
13593 static void setFieldForSnapping(int x, int y, int element, int direction)
13595 struct ElementInfo *ei = &element_info[element];
13596 int direction_bit = MV_DIR_TO_BIT(direction);
13597 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13598 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13599 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13601 Feld[x][y] = EL_ELEMENT_SNAPPING;
13602 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13604 ResetGfxAnimation(x, y);
13606 GfxElement[x][y] = element;
13607 GfxAction[x][y] = action;
13608 GfxDir[x][y] = direction;
13609 GfxFrame[x][y] = -1;
13613 =============================================================================
13614 checkDiagonalPushing()
13615 -----------------------------------------------------------------------------
13616 check if diagonal input device direction results in pushing of object
13617 (by checking if the alternative direction is walkable, diggable, ...)
13618 =============================================================================
13621 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13622 int x, int y, int real_dx, int real_dy)
13624 int jx, jy, dx, dy, xx, yy;
13626 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13629 // diagonal direction: check alternative direction
13634 xx = jx + (dx == 0 ? real_dx : 0);
13635 yy = jy + (dy == 0 ? real_dy : 0);
13637 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13641 =============================================================================
13643 -----------------------------------------------------------------------------
13644 x, y: field next to player (non-diagonal) to try to dig to
13645 real_dx, real_dy: direction as read from input device (can be diagonal)
13646 =============================================================================
13649 static int DigField(struct PlayerInfo *player,
13650 int oldx, int oldy, int x, int y,
13651 int real_dx, int real_dy, int mode)
13653 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13654 boolean player_was_pushing = player->is_pushing;
13655 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13656 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13657 int jx = oldx, jy = oldy;
13658 int dx = x - jx, dy = y - jy;
13659 int nextx = x + dx, nexty = y + dy;
13660 int move_direction = (dx == -1 ? MV_LEFT :
13661 dx == +1 ? MV_RIGHT :
13663 dy == +1 ? MV_DOWN : MV_NONE);
13664 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13665 int dig_side = MV_DIR_OPPOSITE(move_direction);
13666 int old_element = Feld[jx][jy];
13667 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13670 if (is_player) // function can also be called by EL_PENGUIN
13672 if (player->MovPos == 0)
13674 player->is_digging = FALSE;
13675 player->is_collecting = FALSE;
13678 if (player->MovPos == 0) // last pushing move finished
13679 player->is_pushing = FALSE;
13681 if (mode == DF_NO_PUSH) // player just stopped pushing
13683 player->is_switching = FALSE;
13684 player->push_delay = -1;
13686 return MP_NO_ACTION;
13690 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13691 old_element = Back[jx][jy];
13693 // in case of element dropped at player position, check background
13694 else if (Back[jx][jy] != EL_EMPTY &&
13695 game.engine_version >= VERSION_IDENT(2,2,0,0))
13696 old_element = Back[jx][jy];
13698 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13699 return MP_NO_ACTION; // field has no opening in this direction
13701 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13702 return MP_NO_ACTION; // field has no opening in this direction
13704 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13708 Feld[jx][jy] = player->artwork_element;
13709 InitMovingField(jx, jy, MV_DOWN);
13710 Store[jx][jy] = EL_ACID;
13711 ContinueMoving(jx, jy);
13712 BuryPlayer(player);
13714 return MP_DONT_RUN_INTO;
13717 if (player_can_move && DONT_RUN_INTO(element))
13719 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13721 return MP_DONT_RUN_INTO;
13724 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13725 return MP_NO_ACTION;
13727 collect_count = element_info[element].collect_count_initial;
13729 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13730 return MP_NO_ACTION;
13732 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13733 player_can_move = player_can_move_or_snap;
13735 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13736 game.engine_version >= VERSION_IDENT(2,2,0,0))
13738 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13739 player->index_bit, dig_side);
13740 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13741 player->index_bit, dig_side);
13743 if (element == EL_DC_LANDMINE)
13746 if (Feld[x][y] != element) // field changed by snapping
13749 return MP_NO_ACTION;
13752 if (player->gravity && is_player && !player->is_auto_moving &&
13753 canFallDown(player) && move_direction != MV_DOWN &&
13754 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13755 return MP_NO_ACTION; // player cannot walk here due to gravity
13757 if (player_can_move &&
13758 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13760 int sound_element = SND_ELEMENT(element);
13761 int sound_action = ACTION_WALKING;
13763 if (IS_RND_GATE(element))
13765 if (!player->key[RND_GATE_NR(element)])
13766 return MP_NO_ACTION;
13768 else if (IS_RND_GATE_GRAY(element))
13770 if (!player->key[RND_GATE_GRAY_NR(element)])
13771 return MP_NO_ACTION;
13773 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13775 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13776 return MP_NO_ACTION;
13778 else if (element == EL_EXIT_OPEN ||
13779 element == EL_EM_EXIT_OPEN ||
13780 element == EL_EM_EXIT_OPENING ||
13781 element == EL_STEEL_EXIT_OPEN ||
13782 element == EL_EM_STEEL_EXIT_OPEN ||
13783 element == EL_EM_STEEL_EXIT_OPENING ||
13784 element == EL_SP_EXIT_OPEN ||
13785 element == EL_SP_EXIT_OPENING)
13787 sound_action = ACTION_PASSING; // player is passing exit
13789 else if (element == EL_EMPTY)
13791 sound_action = ACTION_MOVING; // nothing to walk on
13794 // play sound from background or player, whatever is available
13795 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13796 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13798 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13800 else if (player_can_move &&
13801 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13803 if (!ACCESS_FROM(element, opposite_direction))
13804 return MP_NO_ACTION; // field not accessible from this direction
13806 if (CAN_MOVE(element)) // only fixed elements can be passed!
13807 return MP_NO_ACTION;
13809 if (IS_EM_GATE(element))
13811 if (!player->key[EM_GATE_NR(element)])
13812 return MP_NO_ACTION;
13814 else if (IS_EM_GATE_GRAY(element))
13816 if (!player->key[EM_GATE_GRAY_NR(element)])
13817 return MP_NO_ACTION;
13819 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13821 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13822 return MP_NO_ACTION;
13824 else if (IS_EMC_GATE(element))
13826 if (!player->key[EMC_GATE_NR(element)])
13827 return MP_NO_ACTION;
13829 else if (IS_EMC_GATE_GRAY(element))
13831 if (!player->key[EMC_GATE_GRAY_NR(element)])
13832 return MP_NO_ACTION;
13834 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13836 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13837 return MP_NO_ACTION;
13839 else if (element == EL_DC_GATE_WHITE ||
13840 element == EL_DC_GATE_WHITE_GRAY ||
13841 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13843 if (player->num_white_keys == 0)
13844 return MP_NO_ACTION;
13846 player->num_white_keys--;
13848 else if (IS_SP_PORT(element))
13850 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13851 element == EL_SP_GRAVITY_PORT_RIGHT ||
13852 element == EL_SP_GRAVITY_PORT_UP ||
13853 element == EL_SP_GRAVITY_PORT_DOWN)
13854 player->gravity = !player->gravity;
13855 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13856 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13857 element == EL_SP_GRAVITY_ON_PORT_UP ||
13858 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13859 player->gravity = TRUE;
13860 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13861 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13862 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13863 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13864 player->gravity = FALSE;
13867 // automatically move to the next field with double speed
13868 player->programmed_action = move_direction;
13870 if (player->move_delay_reset_counter == 0)
13872 player->move_delay_reset_counter = 2; // two double speed steps
13874 DOUBLE_PLAYER_SPEED(player);
13877 PlayLevelSoundAction(x, y, ACTION_PASSING);
13879 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13883 if (mode != DF_SNAP)
13885 GfxElement[x][y] = GFX_ELEMENT(element);
13886 player->is_digging = TRUE;
13889 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13891 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13892 player->index_bit, dig_side);
13894 if (mode == DF_SNAP)
13896 if (level.block_snap_field)
13897 setFieldForSnapping(x, y, element, move_direction);
13899 TestIfElementTouchesCustomElement(x, y); // for empty space
13901 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13902 player->index_bit, dig_side);
13905 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13909 if (is_player && mode != DF_SNAP)
13911 GfxElement[x][y] = element;
13912 player->is_collecting = TRUE;
13915 if (element == EL_SPEED_PILL)
13917 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13919 else if (element == EL_EXTRA_TIME && level.time > 0)
13921 TimeLeft += level.extra_time;
13923 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13925 DisplayGameControlValues();
13927 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
13929 player->shield_normal_time_left += level.shield_normal_time;
13930 if (element == EL_SHIELD_DEADLY)
13931 player->shield_deadly_time_left += level.shield_deadly_time;
13933 else if (element == EL_DYNAMITE ||
13934 element == EL_EM_DYNAMITE ||
13935 element == EL_SP_DISK_RED)
13937 if (player->inventory_size < MAX_INVENTORY_SIZE)
13938 player->inventory_element[player->inventory_size++] = element;
13940 DrawGameDoorValues();
13942 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
13944 player->dynabomb_count++;
13945 player->dynabombs_left++;
13947 else if (element == EL_DYNABOMB_INCREASE_SIZE)
13949 player->dynabomb_size++;
13951 else if (element == EL_DYNABOMB_INCREASE_POWER)
13953 player->dynabomb_xl = TRUE;
13955 else if (IS_KEY(element))
13957 player->key[KEY_NR(element)] = TRUE;
13959 DrawGameDoorValues();
13961 else if (element == EL_DC_KEY_WHITE)
13963 player->num_white_keys++;
13965 // display white keys?
13966 // DrawGameDoorValues();
13968 else if (IS_ENVELOPE(element))
13970 player->show_envelope = element;
13972 else if (element == EL_EMC_LENSES)
13974 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
13976 RedrawAllInvisibleElementsForLenses();
13978 else if (element == EL_EMC_MAGNIFIER)
13980 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
13982 RedrawAllInvisibleElementsForMagnifier();
13984 else if (IS_DROPPABLE(element) ||
13985 IS_THROWABLE(element)) // can be collected and dropped
13989 if (collect_count == 0)
13990 player->inventory_infinite_element = element;
13992 for (i = 0; i < collect_count; i++)
13993 if (player->inventory_size < MAX_INVENTORY_SIZE)
13994 player->inventory_element[player->inventory_size++] = element;
13996 DrawGameDoorValues();
13998 else if (collect_count > 0)
14000 game.gems_still_needed -= collect_count;
14001 if (game.gems_still_needed < 0)
14002 game.gems_still_needed = 0;
14004 game.snapshot.collected_item = TRUE;
14006 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14008 DisplayGameControlValues();
14011 RaiseScoreElement(element);
14012 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14015 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14016 player->index_bit, dig_side);
14018 if (mode == DF_SNAP)
14020 if (level.block_snap_field)
14021 setFieldForSnapping(x, y, element, move_direction);
14023 TestIfElementTouchesCustomElement(x, y); // for empty space
14025 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14026 player->index_bit, dig_side);
14029 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14031 if (mode == DF_SNAP && element != EL_BD_ROCK)
14032 return MP_NO_ACTION;
14034 if (CAN_FALL(element) && dy)
14035 return MP_NO_ACTION;
14037 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14038 !(element == EL_SPRING && level.use_spring_bug))
14039 return MP_NO_ACTION;
14041 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14042 ((move_direction & MV_VERTICAL &&
14043 ((element_info[element].move_pattern & MV_LEFT &&
14044 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14045 (element_info[element].move_pattern & MV_RIGHT &&
14046 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14047 (move_direction & MV_HORIZONTAL &&
14048 ((element_info[element].move_pattern & MV_UP &&
14049 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14050 (element_info[element].move_pattern & MV_DOWN &&
14051 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14052 return MP_NO_ACTION;
14054 // do not push elements already moving away faster than player
14055 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14056 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14057 return MP_NO_ACTION;
14059 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14061 if (player->push_delay_value == -1 || !player_was_pushing)
14062 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14064 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14066 if (player->push_delay_value == -1)
14067 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14069 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14071 if (!player->is_pushing)
14072 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14075 player->is_pushing = TRUE;
14076 player->is_active = TRUE;
14078 if (!(IN_LEV_FIELD(nextx, nexty) &&
14079 (IS_FREE(nextx, nexty) ||
14080 (IS_SB_ELEMENT(element) &&
14081 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14082 (IS_CUSTOM_ELEMENT(element) &&
14083 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14084 return MP_NO_ACTION;
14086 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14087 return MP_NO_ACTION;
14089 if (player->push_delay == -1) // new pushing; restart delay
14090 player->push_delay = 0;
14092 if (player->push_delay < player->push_delay_value &&
14093 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14094 element != EL_SPRING && element != EL_BALLOON)
14096 // make sure that there is no move delay before next try to push
14097 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14098 player->move_delay = 0;
14100 return MP_NO_ACTION;
14103 if (IS_CUSTOM_ELEMENT(element) &&
14104 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14106 if (!DigFieldByCE(nextx, nexty, element))
14107 return MP_NO_ACTION;
14110 if (IS_SB_ELEMENT(element))
14112 boolean sokoban_task_solved = FALSE;
14114 if (element == EL_SOKOBAN_FIELD_FULL)
14116 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14118 IncrementSokobanFieldsNeeded();
14119 IncrementSokobanObjectsNeeded();
14122 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14124 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14126 DecrementSokobanFieldsNeeded();
14127 DecrementSokobanObjectsNeeded();
14129 // sokoban object was pushed from empty field to sokoban field
14130 if (Back[x][y] == EL_EMPTY)
14131 sokoban_task_solved = TRUE;
14134 Feld[x][y] = EL_SOKOBAN_OBJECT;
14136 if (Back[x][y] == Back[nextx][nexty])
14137 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14138 else if (Back[x][y] != 0)
14139 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14142 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14145 if (sokoban_task_solved &&
14146 game.sokoban_fields_still_needed == 0 &&
14147 game.sokoban_objects_still_needed == 0 &&
14148 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14150 game.players_still_needed = 0;
14154 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14158 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14160 InitMovingField(x, y, move_direction);
14161 GfxAction[x][y] = ACTION_PUSHING;
14163 if (mode == DF_SNAP)
14164 ContinueMoving(x, y);
14166 MovPos[x][y] = (dx != 0 ? dx : dy);
14168 Pushed[x][y] = TRUE;
14169 Pushed[nextx][nexty] = TRUE;
14171 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14172 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14174 player->push_delay_value = -1; // get new value later
14176 // check for element change _after_ element has been pushed
14177 if (game.use_change_when_pushing_bug)
14179 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14180 player->index_bit, dig_side);
14181 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14182 player->index_bit, dig_side);
14185 else if (IS_SWITCHABLE(element))
14187 if (PLAYER_SWITCHING(player, x, y))
14189 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14190 player->index_bit, dig_side);
14195 player->is_switching = TRUE;
14196 player->switch_x = x;
14197 player->switch_y = y;
14199 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14201 if (element == EL_ROBOT_WHEEL)
14203 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14205 game.robot_wheel_x = x;
14206 game.robot_wheel_y = y;
14207 game.robot_wheel_active = TRUE;
14209 TEST_DrawLevelField(x, y);
14211 else if (element == EL_SP_TERMINAL)
14215 SCAN_PLAYFIELD(xx, yy)
14217 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14221 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14223 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14225 ResetGfxAnimation(xx, yy);
14226 TEST_DrawLevelField(xx, yy);
14230 else if (IS_BELT_SWITCH(element))
14232 ToggleBeltSwitch(x, y);
14234 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14235 element == EL_SWITCHGATE_SWITCH_DOWN ||
14236 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14237 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14239 ToggleSwitchgateSwitch(x, y);
14241 else if (element == EL_LIGHT_SWITCH ||
14242 element == EL_LIGHT_SWITCH_ACTIVE)
14244 ToggleLightSwitch(x, y);
14246 else if (element == EL_TIMEGATE_SWITCH ||
14247 element == EL_DC_TIMEGATE_SWITCH)
14249 ActivateTimegateSwitch(x, y);
14251 else if (element == EL_BALLOON_SWITCH_LEFT ||
14252 element == EL_BALLOON_SWITCH_RIGHT ||
14253 element == EL_BALLOON_SWITCH_UP ||
14254 element == EL_BALLOON_SWITCH_DOWN ||
14255 element == EL_BALLOON_SWITCH_NONE ||
14256 element == EL_BALLOON_SWITCH_ANY)
14258 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14259 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14260 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14261 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14262 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14265 else if (element == EL_LAMP)
14267 Feld[x][y] = EL_LAMP_ACTIVE;
14268 game.lights_still_needed--;
14270 ResetGfxAnimation(x, y);
14271 TEST_DrawLevelField(x, y);
14273 else if (element == EL_TIME_ORB_FULL)
14275 Feld[x][y] = EL_TIME_ORB_EMPTY;
14277 if (level.time > 0 || level.use_time_orb_bug)
14279 TimeLeft += level.time_orb_time;
14280 game.no_time_limit = FALSE;
14282 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14284 DisplayGameControlValues();
14287 ResetGfxAnimation(x, y);
14288 TEST_DrawLevelField(x, y);
14290 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14291 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14295 game.ball_active = !game.ball_active;
14297 SCAN_PLAYFIELD(xx, yy)
14299 int e = Feld[xx][yy];
14301 if (game.ball_active)
14303 if (e == EL_EMC_MAGIC_BALL)
14304 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14305 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14306 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14310 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14311 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14312 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14313 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14318 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14319 player->index_bit, dig_side);
14321 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14322 player->index_bit, dig_side);
14324 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14325 player->index_bit, dig_side);
14331 if (!PLAYER_SWITCHING(player, x, y))
14333 player->is_switching = TRUE;
14334 player->switch_x = x;
14335 player->switch_y = y;
14337 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14338 player->index_bit, dig_side);
14339 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14340 player->index_bit, dig_side);
14342 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14343 player->index_bit, dig_side);
14344 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14345 player->index_bit, dig_side);
14348 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14349 player->index_bit, dig_side);
14350 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14351 player->index_bit, dig_side);
14353 return MP_NO_ACTION;
14356 player->push_delay = -1;
14358 if (is_player) // function can also be called by EL_PENGUIN
14360 if (Feld[x][y] != element) // really digged/collected something
14362 player->is_collecting = !player->is_digging;
14363 player->is_active = TRUE;
14370 static boolean DigFieldByCE(int x, int y, int digging_element)
14372 int element = Feld[x][y];
14374 if (!IS_FREE(x, y))
14376 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14377 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14380 // no element can dig solid indestructible elements
14381 if (IS_INDESTRUCTIBLE(element) &&
14382 !IS_DIGGABLE(element) &&
14383 !IS_COLLECTIBLE(element))
14386 if (AmoebaNr[x][y] &&
14387 (element == EL_AMOEBA_FULL ||
14388 element == EL_BD_AMOEBA ||
14389 element == EL_AMOEBA_GROWING))
14391 AmoebaCnt[AmoebaNr[x][y]]--;
14392 AmoebaCnt2[AmoebaNr[x][y]]--;
14395 if (IS_MOVING(x, y))
14396 RemoveMovingField(x, y);
14400 TEST_DrawLevelField(x, y);
14403 // if digged element was about to explode, prevent the explosion
14404 ExplodeField[x][y] = EX_TYPE_NONE;
14406 PlayLevelSoundAction(x, y, action);
14409 Store[x][y] = EL_EMPTY;
14411 // this makes it possible to leave the removed element again
14412 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14413 Store[x][y] = element;
14418 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14420 int jx = player->jx, jy = player->jy;
14421 int x = jx + dx, y = jy + dy;
14422 int snap_direction = (dx == -1 ? MV_LEFT :
14423 dx == +1 ? MV_RIGHT :
14425 dy == +1 ? MV_DOWN : MV_NONE);
14426 boolean can_continue_snapping = (level.continuous_snapping &&
14427 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14429 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14432 if (!player->active || !IN_LEV_FIELD(x, y))
14440 if (player->MovPos == 0)
14441 player->is_pushing = FALSE;
14443 player->is_snapping = FALSE;
14445 if (player->MovPos == 0)
14447 player->is_moving = FALSE;
14448 player->is_digging = FALSE;
14449 player->is_collecting = FALSE;
14455 // prevent snapping with already pressed snap key when not allowed
14456 if (player->is_snapping && !can_continue_snapping)
14459 player->MovDir = snap_direction;
14461 if (player->MovPos == 0)
14463 player->is_moving = FALSE;
14464 player->is_digging = FALSE;
14465 player->is_collecting = FALSE;
14468 player->is_dropping = FALSE;
14469 player->is_dropping_pressed = FALSE;
14470 player->drop_pressed_delay = 0;
14472 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14475 player->is_snapping = TRUE;
14476 player->is_active = TRUE;
14478 if (player->MovPos == 0)
14480 player->is_moving = FALSE;
14481 player->is_digging = FALSE;
14482 player->is_collecting = FALSE;
14485 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14486 TEST_DrawLevelField(player->last_jx, player->last_jy);
14488 TEST_DrawLevelField(x, y);
14493 static boolean DropElement(struct PlayerInfo *player)
14495 int old_element, new_element;
14496 int dropx = player->jx, dropy = player->jy;
14497 int drop_direction = player->MovDir;
14498 int drop_side = drop_direction;
14499 int drop_element = get_next_dropped_element(player);
14501 /* do not drop an element on top of another element; when holding drop key
14502 pressed without moving, dropped element must move away before the next
14503 element can be dropped (this is especially important if the next element
14504 is dynamite, which can be placed on background for historical reasons) */
14505 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14508 if (IS_THROWABLE(drop_element))
14510 dropx += GET_DX_FROM_DIR(drop_direction);
14511 dropy += GET_DY_FROM_DIR(drop_direction);
14513 if (!IN_LEV_FIELD(dropx, dropy))
14517 old_element = Feld[dropx][dropy]; // old element at dropping position
14518 new_element = drop_element; // default: no change when dropping
14520 // check if player is active, not moving and ready to drop
14521 if (!player->active || player->MovPos || player->drop_delay > 0)
14524 // check if player has anything that can be dropped
14525 if (new_element == EL_UNDEFINED)
14528 // only set if player has anything that can be dropped
14529 player->is_dropping_pressed = TRUE;
14531 // check if drop key was pressed long enough for EM style dynamite
14532 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14535 // check if anything can be dropped at the current position
14536 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14539 // collected custom elements can only be dropped on empty fields
14540 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14543 if (old_element != EL_EMPTY)
14544 Back[dropx][dropy] = old_element; // store old element on this field
14546 ResetGfxAnimation(dropx, dropy);
14547 ResetRandomAnimationValue(dropx, dropy);
14549 if (player->inventory_size > 0 ||
14550 player->inventory_infinite_element != EL_UNDEFINED)
14552 if (player->inventory_size > 0)
14554 player->inventory_size--;
14556 DrawGameDoorValues();
14558 if (new_element == EL_DYNAMITE)
14559 new_element = EL_DYNAMITE_ACTIVE;
14560 else if (new_element == EL_EM_DYNAMITE)
14561 new_element = EL_EM_DYNAMITE_ACTIVE;
14562 else if (new_element == EL_SP_DISK_RED)
14563 new_element = EL_SP_DISK_RED_ACTIVE;
14566 Feld[dropx][dropy] = new_element;
14568 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14569 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14570 el2img(Feld[dropx][dropy]), 0);
14572 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14574 // needed if previous element just changed to "empty" in the last frame
14575 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14577 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14578 player->index_bit, drop_side);
14579 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14581 player->index_bit, drop_side);
14583 TestIfElementTouchesCustomElement(dropx, dropy);
14585 else // player is dropping a dyna bomb
14587 player->dynabombs_left--;
14589 Feld[dropx][dropy] = new_element;
14591 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14592 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14593 el2img(Feld[dropx][dropy]), 0);
14595 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14598 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14599 InitField_WithBug1(dropx, dropy, FALSE);
14601 new_element = Feld[dropx][dropy]; // element might have changed
14603 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14604 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14606 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14607 MovDir[dropx][dropy] = drop_direction;
14609 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14611 // do not cause impact style collision by dropping elements that can fall
14612 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14615 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14616 player->is_dropping = TRUE;
14618 player->drop_pressed_delay = 0;
14619 player->is_dropping_pressed = FALSE;
14621 player->drop_x = dropx;
14622 player->drop_y = dropy;
14627 // ----------------------------------------------------------------------------
14628 // game sound playing functions
14629 // ----------------------------------------------------------------------------
14631 static int *loop_sound_frame = NULL;
14632 static int *loop_sound_volume = NULL;
14634 void InitPlayLevelSound(void)
14636 int num_sounds = getSoundListSize();
14638 checked_free(loop_sound_frame);
14639 checked_free(loop_sound_volume);
14641 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14642 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14645 static void PlayLevelSound(int x, int y, int nr)
14647 int sx = SCREENX(x), sy = SCREENY(y);
14648 int volume, stereo_position;
14649 int max_distance = 8;
14650 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14652 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14653 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14656 if (!IN_LEV_FIELD(x, y) ||
14657 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14658 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14661 volume = SOUND_MAX_VOLUME;
14663 if (!IN_SCR_FIELD(sx, sy))
14665 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14666 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14668 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14671 stereo_position = (SOUND_MAX_LEFT +
14672 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14673 (SCR_FIELDX + 2 * max_distance));
14675 if (IS_LOOP_SOUND(nr))
14677 /* This assures that quieter loop sounds do not overwrite louder ones,
14678 while restarting sound volume comparison with each new game frame. */
14680 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14683 loop_sound_volume[nr] = volume;
14684 loop_sound_frame[nr] = FrameCounter;
14687 PlaySoundExt(nr, volume, stereo_position, type);
14690 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14692 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14693 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14694 y < LEVELY(BY1) ? LEVELY(BY1) :
14695 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14699 static void PlayLevelSoundAction(int x, int y, int action)
14701 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14704 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14706 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14708 if (sound_effect != SND_UNDEFINED)
14709 PlayLevelSound(x, y, sound_effect);
14712 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14715 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14717 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14718 PlayLevelSound(x, y, sound_effect);
14721 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14723 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14725 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14726 PlayLevelSound(x, y, sound_effect);
14729 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14731 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14733 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14734 StopSound(sound_effect);
14737 static int getLevelMusicNr(void)
14739 if (levelset.music[level_nr] != MUS_UNDEFINED)
14740 return levelset.music[level_nr]; // from config file
14742 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14745 static void FadeLevelSounds(void)
14750 static void FadeLevelMusic(void)
14752 int music_nr = getLevelMusicNr();
14753 char *curr_music = getCurrentlyPlayingMusicFilename();
14754 char *next_music = getMusicInfoEntryFilename(music_nr);
14756 if (!strEqual(curr_music, next_music))
14760 void FadeLevelSoundsAndMusic(void)
14766 static void PlayLevelMusic(void)
14768 int music_nr = getLevelMusicNr();
14769 char *curr_music = getCurrentlyPlayingMusicFilename();
14770 char *next_music = getMusicInfoEntryFilename(music_nr);
14772 if (!strEqual(curr_music, next_music))
14773 PlayMusicLoop(music_nr);
14776 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14778 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14779 int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
14780 int x = xx - 1 - offset;
14781 int y = yy - 1 - offset;
14786 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14790 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14794 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14798 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14802 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14806 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14810 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14813 case SOUND_android_clone:
14814 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14817 case SOUND_android_move:
14818 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14822 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14826 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14830 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14833 case SOUND_eater_eat:
14834 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14838 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14841 case SOUND_collect:
14842 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14845 case SOUND_diamond:
14846 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14850 // !!! CHECK THIS !!!
14852 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14854 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14858 case SOUND_wonderfall:
14859 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14863 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14867 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14871 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14875 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14879 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14883 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14887 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14891 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14894 case SOUND_exit_open:
14895 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14898 case SOUND_exit_leave:
14899 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14902 case SOUND_dynamite:
14903 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14907 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14911 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14915 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14919 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14923 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
14927 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
14931 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
14936 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
14938 int element = map_element_SP_to_RND(element_sp);
14939 int action = map_action_SP_to_RND(action_sp);
14940 int offset = (setup.sp_show_border_elements ? 0 : 1);
14941 int x = xx - offset;
14942 int y = yy - offset;
14944 PlayLevelSoundElementAction(x, y, element, action);
14947 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
14949 int element = map_element_MM_to_RND(element_mm);
14950 int action = map_action_MM_to_RND(action_mm);
14952 int x = xx - offset;
14953 int y = yy - offset;
14955 if (!IS_MM_ELEMENT(element))
14956 element = EL_MM_DEFAULT;
14958 PlayLevelSoundElementAction(x, y, element, action);
14961 void PlaySound_MM(int sound_mm)
14963 int sound = map_sound_MM_to_RND(sound_mm);
14965 if (sound == SND_UNDEFINED)
14971 void PlaySoundLoop_MM(int sound_mm)
14973 int sound = map_sound_MM_to_RND(sound_mm);
14975 if (sound == SND_UNDEFINED)
14978 PlaySoundLoop(sound);
14981 void StopSound_MM(int sound_mm)
14983 int sound = map_sound_MM_to_RND(sound_mm);
14985 if (sound == SND_UNDEFINED)
14991 void RaiseScore(int value)
14993 game.score += value;
14995 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
14997 DisplayGameControlValues();
15000 void RaiseScoreElement(int element)
15005 case EL_BD_DIAMOND:
15006 case EL_EMERALD_YELLOW:
15007 case EL_EMERALD_RED:
15008 case EL_EMERALD_PURPLE:
15009 case EL_SP_INFOTRON:
15010 RaiseScore(level.score[SC_EMERALD]);
15013 RaiseScore(level.score[SC_DIAMOND]);
15016 RaiseScore(level.score[SC_CRYSTAL]);
15019 RaiseScore(level.score[SC_PEARL]);
15022 case EL_BD_BUTTERFLY:
15023 case EL_SP_ELECTRON:
15024 RaiseScore(level.score[SC_BUG]);
15027 case EL_BD_FIREFLY:
15028 case EL_SP_SNIKSNAK:
15029 RaiseScore(level.score[SC_SPACESHIP]);
15032 case EL_DARK_YAMYAM:
15033 RaiseScore(level.score[SC_YAMYAM]);
15036 RaiseScore(level.score[SC_ROBOT]);
15039 RaiseScore(level.score[SC_PACMAN]);
15042 RaiseScore(level.score[SC_NUT]);
15045 case EL_EM_DYNAMITE:
15046 case EL_SP_DISK_RED:
15047 case EL_DYNABOMB_INCREASE_NUMBER:
15048 case EL_DYNABOMB_INCREASE_SIZE:
15049 case EL_DYNABOMB_INCREASE_POWER:
15050 RaiseScore(level.score[SC_DYNAMITE]);
15052 case EL_SHIELD_NORMAL:
15053 case EL_SHIELD_DEADLY:
15054 RaiseScore(level.score[SC_SHIELD]);
15056 case EL_EXTRA_TIME:
15057 RaiseScore(level.extra_time_score);
15071 case EL_DC_KEY_WHITE:
15072 RaiseScore(level.score[SC_KEY]);
15075 RaiseScore(element_info[element].collect_score);
15080 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15082 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15084 // closing door required in case of envelope style request dialogs
15087 // prevent short reactivation of overlay buttons while closing door
15088 SetOverlayActive(FALSE);
15090 CloseDoor(DOOR_CLOSE_1);
15093 if (network.enabled)
15094 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15098 FadeSkipNextFadeIn();
15100 SetGameStatus(GAME_MODE_MAIN);
15105 else // continue playing the game
15107 if (tape.playing && tape.deactivate_display)
15108 TapeDeactivateDisplayOff(TRUE);
15110 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15112 if (tape.playing && tape.deactivate_display)
15113 TapeDeactivateDisplayOn();
15117 void RequestQuitGame(boolean ask_if_really_quit)
15119 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15120 boolean skip_request = game.all_players_gone || quick_quit;
15122 RequestQuitGameExt(skip_request, quick_quit,
15123 "Do you really want to quit the game?");
15126 void RequestRestartGame(char *message)
15128 game.restart_game_message = NULL;
15130 boolean has_started_game = hasStartedNetworkGame();
15131 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15133 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15135 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15139 SetGameStatus(GAME_MODE_MAIN);
15145 void CheckGameOver(void)
15147 static boolean last_game_over = FALSE;
15148 static int game_over_delay = 0;
15149 int game_over_delay_value = 50;
15150 boolean game_over = checkGameFailed();
15152 // do not handle game over if request dialog is already active
15153 if (game.request_active)
15156 // do not ask to play again if game was never actually played
15157 if (!game.GamePlayed)
15162 last_game_over = FALSE;
15163 game_over_delay = game_over_delay_value;
15168 if (game_over_delay > 0)
15175 if (last_game_over != game_over)
15176 game.restart_game_message = (hasStartedNetworkGame() ?
15177 "Game over! Play it again?" :
15180 last_game_over = game_over;
15183 boolean checkGameSolved(void)
15185 // set for all game engines if level was solved
15186 return game.LevelSolved_GameEnd;
15189 boolean checkGameFailed(void)
15191 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15192 return (game_em.game_over && !game_em.level_solved);
15193 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15194 return (game_sp.game_over && !game_sp.level_solved);
15195 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15196 return (game_mm.game_over && !game_mm.level_solved);
15197 else // GAME_ENGINE_TYPE_RND
15198 return (game.GameOver && !game.LevelSolved);
15201 boolean checkGameEnded(void)
15203 return (checkGameSolved() || checkGameFailed());
15207 // ----------------------------------------------------------------------------
15208 // random generator functions
15209 // ----------------------------------------------------------------------------
15211 unsigned int InitEngineRandom_RND(int seed)
15213 game.num_random_calls = 0;
15215 return InitEngineRandom(seed);
15218 unsigned int RND(int max)
15222 game.num_random_calls++;
15224 return GetEngineRandom(max);
15231 // ----------------------------------------------------------------------------
15232 // game engine snapshot handling functions
15233 // ----------------------------------------------------------------------------
15235 struct EngineSnapshotInfo
15237 // runtime values for custom element collect score
15238 int collect_score[NUM_CUSTOM_ELEMENTS];
15240 // runtime values for group element choice position
15241 int choice_pos[NUM_GROUP_ELEMENTS];
15243 // runtime values for belt position animations
15244 int belt_graphic[4][NUM_BELT_PARTS];
15245 int belt_anim_mode[4][NUM_BELT_PARTS];
15248 static struct EngineSnapshotInfo engine_snapshot_rnd;
15249 static char *snapshot_level_identifier = NULL;
15250 static int snapshot_level_nr = -1;
15252 static void SaveEngineSnapshotValues_RND(void)
15254 static int belt_base_active_element[4] =
15256 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15257 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15258 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15259 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15263 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15265 int element = EL_CUSTOM_START + i;
15267 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15270 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15272 int element = EL_GROUP_START + i;
15274 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15277 for (i = 0; i < 4; i++)
15279 for (j = 0; j < NUM_BELT_PARTS; j++)
15281 int element = belt_base_active_element[i] + j;
15282 int graphic = el2img(element);
15283 int anim_mode = graphic_info[graphic].anim_mode;
15285 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15286 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15291 static void LoadEngineSnapshotValues_RND(void)
15293 unsigned int num_random_calls = game.num_random_calls;
15296 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15298 int element = EL_CUSTOM_START + i;
15300 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15303 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15305 int element = EL_GROUP_START + i;
15307 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15310 for (i = 0; i < 4; i++)
15312 for (j = 0; j < NUM_BELT_PARTS; j++)
15314 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15315 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15317 graphic_info[graphic].anim_mode = anim_mode;
15321 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15323 InitRND(tape.random_seed);
15324 for (i = 0; i < num_random_calls; i++)
15328 if (game.num_random_calls != num_random_calls)
15330 Error(ERR_INFO, "number of random calls out of sync");
15331 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15332 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15333 Error(ERR_EXIT, "this should not happen -- please debug");
15337 void FreeEngineSnapshotSingle(void)
15339 FreeSnapshotSingle();
15341 setString(&snapshot_level_identifier, NULL);
15342 snapshot_level_nr = -1;
15345 void FreeEngineSnapshotList(void)
15347 FreeSnapshotList();
15350 static ListNode *SaveEngineSnapshotBuffers(void)
15352 ListNode *buffers = NULL;
15354 // copy some special values to a structure better suited for the snapshot
15356 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15357 SaveEngineSnapshotValues_RND();
15358 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15359 SaveEngineSnapshotValues_EM();
15360 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15361 SaveEngineSnapshotValues_SP(&buffers);
15362 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15363 SaveEngineSnapshotValues_MM(&buffers);
15365 // save values stored in special snapshot structure
15367 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15368 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15369 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15370 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15371 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15372 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15373 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15374 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15376 // save further RND engine values
15378 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15379 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15380 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15382 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15383 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15384 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15385 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15386 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15388 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15389 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15390 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15392 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15394 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15395 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15397 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15398 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15399 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15400 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15401 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15402 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15403 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15404 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15405 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15406 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15407 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15408 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15409 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15410 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15411 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15412 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15413 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15414 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15416 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15417 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15419 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15420 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15421 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15423 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15424 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15426 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15427 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15428 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15429 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15430 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15432 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15433 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15436 ListNode *node = engine_snapshot_list_rnd;
15439 while (node != NULL)
15441 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15446 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15452 void SaveEngineSnapshotSingle(void)
15454 ListNode *buffers = SaveEngineSnapshotBuffers();
15456 // finally save all snapshot buffers to single snapshot
15457 SaveSnapshotSingle(buffers);
15459 // save level identification information
15460 setString(&snapshot_level_identifier, leveldir_current->identifier);
15461 snapshot_level_nr = level_nr;
15464 boolean CheckSaveEngineSnapshotToList(void)
15466 boolean save_snapshot =
15467 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15468 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15469 game.snapshot.changed_action) ||
15470 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15471 game.snapshot.collected_item));
15473 game.snapshot.changed_action = FALSE;
15474 game.snapshot.collected_item = FALSE;
15475 game.snapshot.save_snapshot = save_snapshot;
15477 return save_snapshot;
15480 void SaveEngineSnapshotToList(void)
15482 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15486 ListNode *buffers = SaveEngineSnapshotBuffers();
15488 // finally save all snapshot buffers to snapshot list
15489 SaveSnapshotToList(buffers);
15492 void SaveEngineSnapshotToListInitial(void)
15494 FreeEngineSnapshotList();
15496 SaveEngineSnapshotToList();
15499 static void LoadEngineSnapshotValues(void)
15501 // restore special values from snapshot structure
15503 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15504 LoadEngineSnapshotValues_RND();
15505 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15506 LoadEngineSnapshotValues_EM();
15507 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15508 LoadEngineSnapshotValues_SP();
15509 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15510 LoadEngineSnapshotValues_MM();
15513 void LoadEngineSnapshotSingle(void)
15515 LoadSnapshotSingle();
15517 LoadEngineSnapshotValues();
15520 static void LoadEngineSnapshot_Undo(int steps)
15522 LoadSnapshotFromList_Older(steps);
15524 LoadEngineSnapshotValues();
15527 static void LoadEngineSnapshot_Redo(int steps)
15529 LoadSnapshotFromList_Newer(steps);
15531 LoadEngineSnapshotValues();
15534 boolean CheckEngineSnapshotSingle(void)
15536 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15537 snapshot_level_nr == level_nr);
15540 boolean CheckEngineSnapshotList(void)
15542 return CheckSnapshotList();
15546 // ---------- new game button stuff -------------------------------------------
15553 boolean *setup_value;
15554 boolean allowed_on_tape;
15555 boolean is_touch_button;
15557 } gamebutton_info[NUM_GAME_BUTTONS] =
15560 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15561 GAME_CTRL_ID_STOP, NULL,
15562 TRUE, FALSE, "stop game"
15565 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15566 GAME_CTRL_ID_PAUSE, NULL,
15567 TRUE, FALSE, "pause game"
15570 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15571 GAME_CTRL_ID_PLAY, NULL,
15572 TRUE, FALSE, "play game"
15575 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15576 GAME_CTRL_ID_UNDO, NULL,
15577 TRUE, FALSE, "undo step"
15580 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15581 GAME_CTRL_ID_REDO, NULL,
15582 TRUE, FALSE, "redo step"
15585 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15586 GAME_CTRL_ID_SAVE, NULL,
15587 TRUE, FALSE, "save game"
15590 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15591 GAME_CTRL_ID_PAUSE2, NULL,
15592 TRUE, FALSE, "pause game"
15595 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15596 GAME_CTRL_ID_LOAD, NULL,
15597 TRUE, FALSE, "load game"
15600 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15601 GAME_CTRL_ID_PANEL_STOP, NULL,
15602 FALSE, FALSE, "stop game"
15605 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15606 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15607 FALSE, FALSE, "pause game"
15610 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15611 GAME_CTRL_ID_PANEL_PLAY, NULL,
15612 FALSE, FALSE, "play game"
15615 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15616 GAME_CTRL_ID_TOUCH_STOP, NULL,
15617 FALSE, TRUE, "stop game"
15620 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15621 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15622 FALSE, TRUE, "pause game"
15625 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15626 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15627 TRUE, FALSE, "background music on/off"
15630 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15631 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15632 TRUE, FALSE, "sound loops on/off"
15635 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15636 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15637 TRUE, FALSE, "normal sounds on/off"
15640 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15641 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15642 FALSE, FALSE, "background music on/off"
15645 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15646 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15647 FALSE, FALSE, "sound loops on/off"
15650 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15651 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15652 FALSE, FALSE, "normal sounds on/off"
15656 void CreateGameButtons(void)
15660 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15662 int graphic = gamebutton_info[i].graphic;
15663 struct GraphicInfo *gfx = &graphic_info[graphic];
15664 struct XY *pos = gamebutton_info[i].pos;
15665 struct GadgetInfo *gi;
15668 unsigned int event_mask;
15669 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15670 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15671 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15672 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15673 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15674 int gd_x = gfx->src_x;
15675 int gd_y = gfx->src_y;
15676 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15677 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15678 int gd_xa = gfx->src_x + gfx->active_xoffset;
15679 int gd_ya = gfx->src_y + gfx->active_yoffset;
15680 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15681 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15682 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15683 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15686 if (gfx->bitmap == NULL)
15688 game_gadget[id] = NULL;
15693 if (id == GAME_CTRL_ID_STOP ||
15694 id == GAME_CTRL_ID_PANEL_STOP ||
15695 id == GAME_CTRL_ID_TOUCH_STOP ||
15696 id == GAME_CTRL_ID_PLAY ||
15697 id == GAME_CTRL_ID_PANEL_PLAY ||
15698 id == GAME_CTRL_ID_SAVE ||
15699 id == GAME_CTRL_ID_LOAD)
15701 button_type = GD_TYPE_NORMAL_BUTTON;
15703 event_mask = GD_EVENT_RELEASED;
15705 else if (id == GAME_CTRL_ID_UNDO ||
15706 id == GAME_CTRL_ID_REDO)
15708 button_type = GD_TYPE_NORMAL_BUTTON;
15710 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15714 button_type = GD_TYPE_CHECK_BUTTON;
15715 checked = (gamebutton_info[i].setup_value != NULL ?
15716 *gamebutton_info[i].setup_value : FALSE);
15717 event_mask = GD_EVENT_PRESSED;
15720 gi = CreateGadget(GDI_CUSTOM_ID, id,
15721 GDI_IMAGE_ID, graphic,
15722 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15725 GDI_WIDTH, gfx->width,
15726 GDI_HEIGHT, gfx->height,
15727 GDI_TYPE, button_type,
15728 GDI_STATE, GD_BUTTON_UNPRESSED,
15729 GDI_CHECKED, checked,
15730 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15731 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15732 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15733 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15734 GDI_DIRECT_DRAW, FALSE,
15735 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15736 GDI_EVENT_MASK, event_mask,
15737 GDI_CALLBACK_ACTION, HandleGameButtons,
15741 Error(ERR_EXIT, "cannot create gadget");
15743 game_gadget[id] = gi;
15747 void FreeGameButtons(void)
15751 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15752 FreeGadget(game_gadget[i]);
15755 static void UnmapGameButtonsAtSamePosition(int id)
15759 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15761 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15762 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15763 UnmapGadget(game_gadget[i]);
15766 static void UnmapGameButtonsAtSamePosition_All(void)
15768 if (setup.show_snapshot_buttons)
15770 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15771 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15772 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15776 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15777 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15778 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15780 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15781 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15782 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15786 static void MapGameButtonsAtSamePosition(int id)
15790 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15792 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15793 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15794 MapGadget(game_gadget[i]);
15796 UnmapGameButtonsAtSamePosition_All();
15799 void MapUndoRedoButtons(void)
15801 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15802 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15804 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15805 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15808 void UnmapUndoRedoButtons(void)
15810 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15811 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15813 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15814 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15817 void ModifyPauseButtons(void)
15821 GAME_CTRL_ID_PAUSE,
15822 GAME_CTRL_ID_PAUSE2,
15823 GAME_CTRL_ID_PANEL_PAUSE,
15824 GAME_CTRL_ID_TOUCH_PAUSE,
15829 for (i = 0; ids[i] > -1; i++)
15830 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15833 static void MapGameButtonsExt(boolean on_tape)
15837 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15838 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15839 i != GAME_CTRL_ID_UNDO &&
15840 i != GAME_CTRL_ID_REDO)
15841 MapGadget(game_gadget[i]);
15843 UnmapGameButtonsAtSamePosition_All();
15845 RedrawGameButtons();
15848 static void UnmapGameButtonsExt(boolean on_tape)
15852 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15853 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15854 UnmapGadget(game_gadget[i]);
15857 static void RedrawGameButtonsExt(boolean on_tape)
15861 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15862 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15863 RedrawGadget(game_gadget[i]);
15866 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15871 gi->checked = state;
15874 static void RedrawSoundButtonGadget(int id)
15876 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15877 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15878 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15879 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15880 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15881 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15884 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15885 RedrawGadget(game_gadget[id2]);
15888 void MapGameButtons(void)
15890 MapGameButtonsExt(FALSE);
15893 void UnmapGameButtons(void)
15895 UnmapGameButtonsExt(FALSE);
15898 void RedrawGameButtons(void)
15900 RedrawGameButtonsExt(FALSE);
15903 void MapGameButtonsOnTape(void)
15905 MapGameButtonsExt(TRUE);
15908 void UnmapGameButtonsOnTape(void)
15910 UnmapGameButtonsExt(TRUE);
15913 void RedrawGameButtonsOnTape(void)
15915 RedrawGameButtonsExt(TRUE);
15918 static void GameUndoRedoExt(void)
15920 ClearPlayerAction();
15922 tape.pausing = TRUE;
15925 UpdateAndDisplayGameControlValues();
15927 DrawCompleteVideoDisplay();
15928 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
15929 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
15930 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
15935 static void GameUndo(int steps)
15937 if (!CheckEngineSnapshotList())
15940 LoadEngineSnapshot_Undo(steps);
15945 static void GameRedo(int steps)
15947 if (!CheckEngineSnapshotList())
15950 LoadEngineSnapshot_Redo(steps);
15955 static void HandleGameButtonsExt(int id, int button)
15957 static boolean game_undo_executed = FALSE;
15958 int steps = BUTTON_STEPSIZE(button);
15959 boolean handle_game_buttons =
15960 (game_status == GAME_MODE_PLAYING ||
15961 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
15963 if (!handle_game_buttons)
15968 case GAME_CTRL_ID_STOP:
15969 case GAME_CTRL_ID_PANEL_STOP:
15970 case GAME_CTRL_ID_TOUCH_STOP:
15971 if (game_status == GAME_MODE_MAIN)
15977 RequestQuitGame(TRUE);
15981 case GAME_CTRL_ID_PAUSE:
15982 case GAME_CTRL_ID_PAUSE2:
15983 case GAME_CTRL_ID_PANEL_PAUSE:
15984 case GAME_CTRL_ID_TOUCH_PAUSE:
15985 if (network.enabled && game_status == GAME_MODE_PLAYING)
15988 SendToServer_ContinuePlaying();
15990 SendToServer_PausePlaying();
15993 TapeTogglePause(TAPE_TOGGLE_MANUAL);
15995 game_undo_executed = FALSE;
15999 case GAME_CTRL_ID_PLAY:
16000 case GAME_CTRL_ID_PANEL_PLAY:
16001 if (game_status == GAME_MODE_MAIN)
16003 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16005 else if (tape.pausing)
16007 if (network.enabled)
16008 SendToServer_ContinuePlaying();
16010 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16014 case GAME_CTRL_ID_UNDO:
16015 // Important: When using "save snapshot when collecting an item" mode,
16016 // load last (current) snapshot for first "undo" after pressing "pause"
16017 // (else the last-but-one snapshot would be loaded, because the snapshot
16018 // pointer already points to the last snapshot when pressing "pause",
16019 // which is fine for "every step/move" mode, but not for "every collect")
16020 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16021 !game_undo_executed)
16024 game_undo_executed = TRUE;
16029 case GAME_CTRL_ID_REDO:
16033 case GAME_CTRL_ID_SAVE:
16037 case GAME_CTRL_ID_LOAD:
16041 case SOUND_CTRL_ID_MUSIC:
16042 case SOUND_CTRL_ID_PANEL_MUSIC:
16043 if (setup.sound_music)
16045 setup.sound_music = FALSE;
16049 else if (audio.music_available)
16051 setup.sound = setup.sound_music = TRUE;
16053 SetAudioMode(setup.sound);
16055 if (game_status == GAME_MODE_PLAYING)
16059 RedrawSoundButtonGadget(id);
16063 case SOUND_CTRL_ID_LOOPS:
16064 case SOUND_CTRL_ID_PANEL_LOOPS:
16065 if (setup.sound_loops)
16066 setup.sound_loops = FALSE;
16067 else if (audio.loops_available)
16069 setup.sound = setup.sound_loops = TRUE;
16071 SetAudioMode(setup.sound);
16074 RedrawSoundButtonGadget(id);
16078 case SOUND_CTRL_ID_SIMPLE:
16079 case SOUND_CTRL_ID_PANEL_SIMPLE:
16080 if (setup.sound_simple)
16081 setup.sound_simple = FALSE;
16082 else if (audio.sound_available)
16084 setup.sound = setup.sound_simple = TRUE;
16086 SetAudioMode(setup.sound);
16089 RedrawSoundButtonGadget(id);
16098 static void HandleGameButtons(struct GadgetInfo *gi)
16100 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16103 void HandleSoundButtonKeys(Key key)
16105 if (key == setup.shortcut.sound_simple)
16106 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16107 else if (key == setup.shortcut.sound_loops)
16108 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16109 else if (key == setup.shortcut.sound_music)
16110 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);