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
29 /* EXPERIMENTAL STUFF */
30 #define USE_NEW_AMOEBA_CODE FALSE
32 /* EXPERIMENTAL STUFF */
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)
64 /* for MovePlayer() */
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
70 /* for ScrollPlayer() */
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() (local_player->LevelSolved_PanelOff)
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 SOUND_CTRL_ID_MUSIC 11
1020 #define SOUND_CTRL_ID_LOOPS 12
1021 #define SOUND_CTRL_ID_SIMPLE 13
1022 #define SOUND_CTRL_ID_PANEL_MUSIC 14
1023 #define SOUND_CTRL_ID_PANEL_LOOPS 15
1024 #define SOUND_CTRL_ID_PANEL_SIMPLE 16
1026 #define NUM_GAME_BUTTONS 17
1029 /* forward declaration for internal use */
1031 static void CreateField(int, int, int);
1033 static void ResetGfxAnimation(int, int);
1035 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1036 static void AdvanceFrameAndPlayerCounters(int);
1038 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1039 static boolean MovePlayer(struct PlayerInfo *, int, int);
1040 static void ScrollPlayer(struct PlayerInfo *, int);
1041 static void ScrollScreen(struct PlayerInfo *, int);
1043 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1044 static boolean DigFieldByCE(int, int, int);
1045 static boolean SnapField(struct PlayerInfo *, int, int);
1046 static boolean DropElement(struct PlayerInfo *);
1048 static void InitBeltMovement(void);
1049 static void CloseAllOpenTimegates(void);
1050 static void CheckGravityMovement(struct PlayerInfo *);
1051 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1052 static void KillPlayerUnlessEnemyProtected(int, int);
1053 static void KillPlayerUnlessExplosionProtected(int, int);
1055 static void TestIfPlayerTouchesCustomElement(int, int);
1056 static void TestIfElementTouchesCustomElement(int, int);
1057 static void TestIfElementHitsCustomElement(int, int, int);
1059 static void HandleElementChange(int, int, int);
1060 static void ExecuteCustomElementAction(int, int, int, int);
1061 static boolean ChangeElement(int, int, int, int);
1063 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1064 #define CheckTriggeredElementChange(x, y, e, ev) \
1065 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1066 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1067 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1068 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1069 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1070 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1071 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1073 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1074 #define CheckElementChange(x, y, e, te, ev) \
1075 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1076 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1077 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1078 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1079 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1081 static void PlayLevelSound(int, int, int);
1082 static void PlayLevelSoundNearest(int, int, int);
1083 static void PlayLevelSoundAction(int, int, int);
1084 static void PlayLevelSoundElementAction(int, int, int, int);
1085 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1086 static void PlayLevelSoundActionIfLoop(int, int, int);
1087 static void StopLevelSoundActionIfLoop(int, int, int);
1088 static void PlayLevelMusic();
1089 static void FadeLevelSoundsAndMusic();
1091 static void HandleGameButtons(struct GadgetInfo *);
1093 int AmoebeNachbarNr(int, int);
1094 void AmoebeUmwandeln(int, int);
1095 void ContinueMoving(int, int);
1096 void Bang(int, int);
1097 void InitMovDir(int, int);
1098 void InitAmoebaNr(int, int);
1099 int NewHiScore(int);
1101 void TestIfGoodThingHitsBadThing(int, int, int);
1102 void TestIfBadThingHitsGoodThing(int, int, int);
1103 void TestIfPlayerTouchesBadThing(int, int);
1104 void TestIfPlayerRunsIntoBadThing(int, int, int);
1105 void TestIfBadThingTouchesPlayer(int, int);
1106 void TestIfBadThingRunsIntoPlayer(int, int, int);
1107 void TestIfFriendTouchesBadThing(int, int);
1108 void TestIfBadThingTouchesFriend(int, int);
1109 void TestIfBadThingTouchesOtherBadThing(int, int);
1110 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1112 void KillPlayer(struct PlayerInfo *);
1113 void BuryPlayer(struct PlayerInfo *);
1114 void RemovePlayer(struct PlayerInfo *);
1116 static int getInvisibleActiveFromInvisibleElement(int);
1117 static int getInvisibleFromInvisibleActiveElement(int);
1119 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1121 /* for detection of endless loops, caused by custom element programming */
1122 /* (using maximal playfield width x 10 is just a rough approximation) */
1123 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1125 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1127 if (recursion_loop_detected) \
1130 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1132 recursion_loop_detected = TRUE; \
1133 recursion_loop_element = (e); \
1136 recursion_loop_depth++; \
1139 #define RECURSION_LOOP_DETECTION_END() \
1141 recursion_loop_depth--; \
1144 static int recursion_loop_depth;
1145 static boolean recursion_loop_detected;
1146 static boolean recursion_loop_element;
1148 static int map_player_action[MAX_PLAYERS];
1151 /* ------------------------------------------------------------------------- */
1152 /* definition of elements that automatically change to other elements after */
1153 /* a specified time, eventually calling a function when changing */
1154 /* ------------------------------------------------------------------------- */
1156 /* forward declaration for changer functions */
1157 static void InitBuggyBase(int, int);
1158 static void WarnBuggyBase(int, int);
1160 static void InitTrap(int, int);
1161 static void ActivateTrap(int, int);
1162 static void ChangeActiveTrap(int, int);
1164 static void InitRobotWheel(int, int);
1165 static void RunRobotWheel(int, int);
1166 static void StopRobotWheel(int, int);
1168 static void InitTimegateWheel(int, int);
1169 static void RunTimegateWheel(int, int);
1171 static void InitMagicBallDelay(int, int);
1172 static void ActivateMagicBall(int, int);
1174 struct ChangingElementInfo
1179 void (*pre_change_function)(int x, int y);
1180 void (*change_function)(int x, int y);
1181 void (*post_change_function)(int x, int y);
1184 static struct ChangingElementInfo change_delay_list[] =
1219 EL_STEEL_EXIT_OPENING,
1227 EL_STEEL_EXIT_CLOSING,
1228 EL_STEEL_EXIT_CLOSED,
1251 EL_EM_STEEL_EXIT_OPENING,
1252 EL_EM_STEEL_EXIT_OPEN,
1259 EL_EM_STEEL_EXIT_CLOSING,
1283 EL_SWITCHGATE_OPENING,
1291 EL_SWITCHGATE_CLOSING,
1292 EL_SWITCHGATE_CLOSED,
1299 EL_TIMEGATE_OPENING,
1307 EL_TIMEGATE_CLOSING,
1316 EL_ACID_SPLASH_LEFT,
1324 EL_ACID_SPLASH_RIGHT,
1333 EL_SP_BUGGY_BASE_ACTIVATING,
1340 EL_SP_BUGGY_BASE_ACTIVATING,
1341 EL_SP_BUGGY_BASE_ACTIVE,
1348 EL_SP_BUGGY_BASE_ACTIVE,
1372 EL_ROBOT_WHEEL_ACTIVE,
1380 EL_TIMEGATE_SWITCH_ACTIVE,
1388 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1389 EL_DC_TIMEGATE_SWITCH,
1396 EL_EMC_MAGIC_BALL_ACTIVE,
1397 EL_EMC_MAGIC_BALL_ACTIVE,
1404 EL_EMC_SPRING_BUMPER_ACTIVE,
1405 EL_EMC_SPRING_BUMPER,
1412 EL_DIAGONAL_SHRINKING,
1420 EL_DIAGONAL_GROWING,
1441 int push_delay_fixed, push_delay_random;
1445 { EL_SPRING, 0, 0 },
1446 { EL_BALLOON, 0, 0 },
1448 { EL_SOKOBAN_OBJECT, 2, 0 },
1449 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1450 { EL_SATELLITE, 2, 0 },
1451 { EL_SP_DISK_YELLOW, 2, 0 },
1453 { EL_UNDEFINED, 0, 0 },
1461 move_stepsize_list[] =
1463 { EL_AMOEBA_DROP, 2 },
1464 { EL_AMOEBA_DROPPING, 2 },
1465 { EL_QUICKSAND_FILLING, 1 },
1466 { EL_QUICKSAND_EMPTYING, 1 },
1467 { EL_QUICKSAND_FAST_FILLING, 2 },
1468 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1469 { EL_MAGIC_WALL_FILLING, 2 },
1470 { EL_MAGIC_WALL_EMPTYING, 2 },
1471 { EL_BD_MAGIC_WALL_FILLING, 2 },
1472 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1473 { EL_DC_MAGIC_WALL_FILLING, 2 },
1474 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1476 { EL_UNDEFINED, 0 },
1484 collect_count_list[] =
1487 { EL_BD_DIAMOND, 1 },
1488 { EL_EMERALD_YELLOW, 1 },
1489 { EL_EMERALD_RED, 1 },
1490 { EL_EMERALD_PURPLE, 1 },
1492 { EL_SP_INFOTRON, 1 },
1496 { EL_UNDEFINED, 0 },
1504 access_direction_list[] =
1506 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1507 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1508 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1509 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1510 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1511 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1512 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1513 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1514 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1515 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1516 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1518 { EL_SP_PORT_LEFT, MV_RIGHT },
1519 { EL_SP_PORT_RIGHT, MV_LEFT },
1520 { EL_SP_PORT_UP, MV_DOWN },
1521 { EL_SP_PORT_DOWN, MV_UP },
1522 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1523 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1524 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1525 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1526 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1527 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1528 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1529 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1530 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1531 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1532 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1533 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1534 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1535 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1536 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1538 { EL_UNDEFINED, MV_NONE }
1541 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1543 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1544 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1545 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Feld[x][y]) || \
1546 IS_JUST_CHANGING(x, y))
1548 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1550 /* static variables for playfield scan mode (scanning forward or backward) */
1551 static int playfield_scan_start_x = 0;
1552 static int playfield_scan_start_y = 0;
1553 static int playfield_scan_delta_x = 1;
1554 static int playfield_scan_delta_y = 1;
1556 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1557 (y) >= 0 && (y) <= lev_fieldy - 1; \
1558 (y) += playfield_scan_delta_y) \
1559 for ((x) = playfield_scan_start_x; \
1560 (x) >= 0 && (x) <= lev_fieldx - 1; \
1561 (x) += playfield_scan_delta_x)
1564 void DEBUG_SetMaximumDynamite()
1568 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1569 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1570 local_player->inventory_element[local_player->inventory_size++] =
1575 static void InitPlayfieldScanModeVars()
1577 if (game.use_reverse_scan_direction)
1579 playfield_scan_start_x = lev_fieldx - 1;
1580 playfield_scan_start_y = lev_fieldy - 1;
1582 playfield_scan_delta_x = -1;
1583 playfield_scan_delta_y = -1;
1587 playfield_scan_start_x = 0;
1588 playfield_scan_start_y = 0;
1590 playfield_scan_delta_x = 1;
1591 playfield_scan_delta_y = 1;
1595 static void InitPlayfieldScanMode(int mode)
1597 game.use_reverse_scan_direction =
1598 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1600 InitPlayfieldScanModeVars();
1603 static int get_move_delay_from_stepsize(int move_stepsize)
1606 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1608 /* make sure that stepsize value is always a power of 2 */
1609 move_stepsize = (1 << log_2(move_stepsize));
1611 return TILEX / move_stepsize;
1614 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1617 int player_nr = player->index_nr;
1618 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1619 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1621 /* do no immediately change move delay -- the player might just be moving */
1622 player->move_delay_value_next = move_delay;
1624 /* information if player can move must be set separately */
1625 player->cannot_move = cannot_move;
1629 player->move_delay = game.initial_move_delay[player_nr];
1630 player->move_delay_value = game.initial_move_delay_value[player_nr];
1632 player->move_delay_value_next = -1;
1634 player->move_delay_reset_counter = 0;
1638 void GetPlayerConfig()
1640 GameFrameDelay = setup.game_frame_delay;
1642 if (!audio.sound_available)
1643 setup.sound_simple = FALSE;
1645 if (!audio.loops_available)
1646 setup.sound_loops = FALSE;
1648 if (!audio.music_available)
1649 setup.sound_music = FALSE;
1651 if (!video.fullscreen_available)
1652 setup.fullscreen = FALSE;
1654 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1656 SetAudioMode(setup.sound);
1659 int GetElementFromGroupElement(int element)
1661 if (IS_GROUP_ELEMENT(element))
1663 struct ElementGroupInfo *group = element_info[element].group;
1664 int last_anim_random_frame = gfx.anim_random_frame;
1667 if (group->choice_mode == ANIM_RANDOM)
1668 gfx.anim_random_frame = RND(group->num_elements_resolved);
1670 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1671 group->choice_mode, 0,
1674 if (group->choice_mode == ANIM_RANDOM)
1675 gfx.anim_random_frame = last_anim_random_frame;
1677 group->choice_pos++;
1679 element = group->element_resolved[element_pos];
1685 static void InitPlayerField(int x, int y, int element, boolean init_game)
1687 if (element == EL_SP_MURPHY)
1691 if (stored_player[0].present)
1693 Feld[x][y] = EL_SP_MURPHY_CLONE;
1699 stored_player[0].initial_element = element;
1700 stored_player[0].use_murphy = TRUE;
1702 if (!level.use_artwork_element[0])
1703 stored_player[0].artwork_element = EL_SP_MURPHY;
1706 Feld[x][y] = EL_PLAYER_1;
1712 struct PlayerInfo *player = &stored_player[Feld[x][y] - EL_PLAYER_1];
1713 int jx = player->jx, jy = player->jy;
1715 player->present = TRUE;
1717 player->block_last_field = (element == EL_SP_MURPHY ?
1718 level.sp_block_last_field :
1719 level.block_last_field);
1721 /* ---------- initialize player's last field block delay --------------- */
1723 /* always start with reliable default value (no adjustment needed) */
1724 player->block_delay_adjustment = 0;
1726 /* special case 1: in Supaplex, Murphy blocks last field one more frame */
1727 if (player->block_last_field && element == EL_SP_MURPHY)
1728 player->block_delay_adjustment = 1;
1730 /* special case 2: in game engines before 3.1.1, blocking was different */
1731 if (game.use_block_last_field_bug)
1732 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1734 if (!network.enabled || player->connected_network)
1736 player->active = TRUE;
1738 /* remove potentially duplicate players */
1739 if (StorePlayer[jx][jy] == Feld[x][y])
1740 StorePlayer[jx][jy] = 0;
1742 StorePlayer[x][y] = Feld[x][y];
1744 #if DEBUG_INIT_PLAYER
1747 printf("- player element %d activated", player->element_nr);
1748 printf(" (local player is %d and currently %s)\n",
1749 local_player->element_nr,
1750 local_player->active ? "active" : "not active");
1755 Feld[x][y] = EL_EMPTY;
1757 player->jx = player->last_jx = x;
1758 player->jy = player->last_jy = y;
1763 int player_nr = GET_PLAYER_NR(element);
1764 struct PlayerInfo *player = &stored_player[player_nr];
1766 if (player->active && player->killed)
1767 player->reanimated = TRUE; /* if player was just killed, reanimate him */
1771 static void InitField(int x, int y, boolean init_game)
1773 int element = Feld[x][y];
1782 InitPlayerField(x, y, element, init_game);
1785 case EL_SOKOBAN_FIELD_PLAYER:
1786 element = Feld[x][y] = EL_PLAYER_1;
1787 InitField(x, y, init_game);
1789 element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1790 InitField(x, y, init_game);
1793 case EL_SOKOBAN_FIELD_EMPTY:
1794 local_player->sokobanfields_still_needed++;
1798 if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID)
1799 Feld[x][y] = EL_ACID_POOL_TOPLEFT;
1800 else if (x > 0 && Feld[x-1][y] == EL_ACID)
1801 Feld[x][y] = EL_ACID_POOL_TOPRIGHT;
1802 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPLEFT)
1803 Feld[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1804 else if (y > 0 && Feld[x][y-1] == EL_ACID)
1805 Feld[x][y] = EL_ACID_POOL_BOTTOM;
1806 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1807 Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1816 case EL_SPACESHIP_RIGHT:
1817 case EL_SPACESHIP_UP:
1818 case EL_SPACESHIP_LEFT:
1819 case EL_SPACESHIP_DOWN:
1820 case EL_BD_BUTTERFLY:
1821 case EL_BD_BUTTERFLY_RIGHT:
1822 case EL_BD_BUTTERFLY_UP:
1823 case EL_BD_BUTTERFLY_LEFT:
1824 case EL_BD_BUTTERFLY_DOWN:
1826 case EL_BD_FIREFLY_RIGHT:
1827 case EL_BD_FIREFLY_UP:
1828 case EL_BD_FIREFLY_LEFT:
1829 case EL_BD_FIREFLY_DOWN:
1830 case EL_PACMAN_RIGHT:
1832 case EL_PACMAN_LEFT:
1833 case EL_PACMAN_DOWN:
1835 case EL_YAMYAM_LEFT:
1836 case EL_YAMYAM_RIGHT:
1838 case EL_YAMYAM_DOWN:
1839 case EL_DARK_YAMYAM:
1842 case EL_SP_SNIKSNAK:
1843 case EL_SP_ELECTRON:
1852 case EL_AMOEBA_FULL:
1857 case EL_AMOEBA_DROP:
1858 if (y == lev_fieldy - 1)
1860 Feld[x][y] = EL_AMOEBA_GROWING;
1861 Store[x][y] = EL_AMOEBA_WET;
1865 case EL_DYNAMITE_ACTIVE:
1866 case EL_SP_DISK_RED_ACTIVE:
1867 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1868 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1869 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1870 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1871 MovDelay[x][y] = 96;
1874 case EL_EM_DYNAMITE_ACTIVE:
1875 MovDelay[x][y] = 32;
1879 local_player->lights_still_needed++;
1883 local_player->friends_still_needed++;
1888 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1891 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1892 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1893 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1894 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1895 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1896 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1897 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1898 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1899 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1900 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1901 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1902 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1905 int belt_nr = getBeltNrFromBeltSwitchElement(Feld[x][y]);
1906 int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]);
1907 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[x][y]);
1909 if (game.belt_dir_nr[belt_nr] == 3) /* initial value */
1911 game.belt_dir[belt_nr] = belt_dir;
1912 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1914 else /* more than one switch -- set it like the first switch */
1916 Feld[x][y] = Feld[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1921 case EL_LIGHT_SWITCH_ACTIVE:
1923 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1926 case EL_INVISIBLE_STEELWALL:
1927 case EL_INVISIBLE_WALL:
1928 case EL_INVISIBLE_SAND:
1929 if (game.light_time_left > 0 ||
1930 game.lenses_time_left > 0)
1931 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
1934 case EL_EMC_MAGIC_BALL:
1935 if (game.ball_state)
1936 Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1939 case EL_EMC_MAGIC_BALL_SWITCH:
1940 if (game.ball_state)
1941 Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1944 case EL_TRIGGER_PLAYER:
1945 case EL_TRIGGER_ELEMENT:
1946 case EL_TRIGGER_CE_VALUE:
1947 case EL_TRIGGER_CE_SCORE:
1949 case EL_ANY_ELEMENT:
1950 case EL_CURRENT_CE_VALUE:
1951 case EL_CURRENT_CE_SCORE:
1968 /* reference elements should not be used on the playfield */
1969 Feld[x][y] = EL_EMPTY;
1973 if (IS_CUSTOM_ELEMENT(element))
1975 if (CAN_MOVE(element))
1978 if (!element_info[element].use_last_ce_value || init_game)
1979 CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
1981 else if (IS_GROUP_ELEMENT(element))
1983 Feld[x][y] = GetElementFromGroupElement(element);
1985 InitField(x, y, init_game);
1992 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
1995 inline static void InitField_WithBug1(int x, int y, boolean init_game)
1997 InitField(x, y, init_game);
1999 /* not needed to call InitMovDir() -- already done by InitField()! */
2000 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2001 CAN_MOVE(Feld[x][y]))
2005 inline static void InitField_WithBug2(int x, int y, boolean init_game)
2007 int old_element = Feld[x][y];
2009 InitField(x, y, init_game);
2011 /* not needed to call InitMovDir() -- already done by InitField()! */
2012 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2013 CAN_MOVE(old_element) &&
2014 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2017 /* this case is in fact a combination of not less than three bugs:
2018 first, it calls InitMovDir() for elements that can move, although this is
2019 already done by InitField(); then, it checks the element that was at this
2020 field _before_ the call to InitField() (which can change it); lastly, it
2021 was not called for "mole with direction" elements, which were treated as
2022 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2026 static int get_key_element_from_nr(int key_nr)
2028 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2029 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2030 EL_EM_KEY_1 : EL_KEY_1);
2032 return key_base_element + key_nr;
2035 static int get_next_dropped_element(struct PlayerInfo *player)
2037 return (player->inventory_size > 0 ?
2038 player->inventory_element[player->inventory_size - 1] :
2039 player->inventory_infinite_element != EL_UNDEFINED ?
2040 player->inventory_infinite_element :
2041 player->dynabombs_left > 0 ?
2042 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2046 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2048 /* pos >= 0: get element from bottom of the stack;
2049 pos < 0: get element from top of the stack */
2053 int min_inventory_size = -pos;
2054 int inventory_pos = player->inventory_size - min_inventory_size;
2055 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2057 return (player->inventory_size >= min_inventory_size ?
2058 player->inventory_element[inventory_pos] :
2059 player->inventory_infinite_element != EL_UNDEFINED ?
2060 player->inventory_infinite_element :
2061 player->dynabombs_left >= min_dynabombs_left ?
2062 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2067 int min_dynabombs_left = pos + 1;
2068 int min_inventory_size = pos + 1 - player->dynabombs_left;
2069 int inventory_pos = pos - player->dynabombs_left;
2071 return (player->inventory_infinite_element != EL_UNDEFINED ?
2072 player->inventory_infinite_element :
2073 player->dynabombs_left >= min_dynabombs_left ?
2074 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2075 player->inventory_size >= min_inventory_size ?
2076 player->inventory_element[inventory_pos] :
2081 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2083 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2084 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2087 if (gpo1->sort_priority != gpo2->sort_priority)
2088 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2090 compare_result = gpo1->nr - gpo2->nr;
2092 return compare_result;
2095 int getPlayerInventorySize(int player_nr)
2097 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2098 return level.native_em_level->ply[player_nr]->dynamite;
2099 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2100 return level.native_sp_level->game_sp->red_disk_count;
2102 return stored_player[player_nr].inventory_size;
2105 void InitGameControlValues()
2109 for (i = 0; game_panel_controls[i].nr != -1; i++)
2111 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2112 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2113 struct TextPosInfo *pos = gpc->pos;
2115 int type = gpc->type;
2119 Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
2120 Error(ERR_EXIT, "this should not happen -- please debug");
2123 /* force update of game controls after initialization */
2124 gpc->value = gpc->last_value = -1;
2125 gpc->frame = gpc->last_frame = -1;
2126 gpc->gfx_frame = -1;
2128 /* determine panel value width for later calculation of alignment */
2129 if (type == TYPE_INTEGER || type == TYPE_STRING)
2131 pos->width = pos->size * getFontWidth(pos->font);
2132 pos->height = getFontHeight(pos->font);
2134 else if (type == TYPE_ELEMENT)
2136 pos->width = pos->size;
2137 pos->height = pos->size;
2140 /* fill structure for game panel draw order */
2142 gpo->sort_priority = pos->sort_priority;
2145 /* sort game panel controls according to sort_priority and control number */
2146 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2147 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2150 void UpdatePlayfieldElementCount()
2152 boolean use_element_count = FALSE;
2155 /* first check if it is needed at all to calculate playfield element count */
2156 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2157 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2158 use_element_count = TRUE;
2160 if (!use_element_count)
2163 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2164 element_info[i].element_count = 0;
2166 SCAN_PLAYFIELD(x, y)
2168 element_info[Feld[x][y]].element_count++;
2171 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2172 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2173 if (IS_IN_GROUP(j, i))
2174 element_info[EL_GROUP_START + i].element_count +=
2175 element_info[j].element_count;
2178 void UpdateGameControlValues()
2181 int time = (local_player->LevelSolved ?
2182 local_player->LevelSolved_CountingTime :
2183 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2184 level.native_em_level->lev->time :
2185 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2186 level.native_sp_level->game_sp->time_played :
2187 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2188 game_mm.energy_left :
2189 game.no_time_limit ? TimePlayed : TimeLeft);
2190 int score = (local_player->LevelSolved ?
2191 local_player->LevelSolved_CountingScore :
2192 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2193 level.native_em_level->lev->score :
2194 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2195 level.native_sp_level->game_sp->score :
2196 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2198 local_player->score);
2199 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2200 level.native_em_level->lev->required :
2201 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2202 level.native_sp_level->game_sp->infotrons_still_needed :
2203 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2204 game_mm.kettles_still_needed :
2205 local_player->gems_still_needed);
2206 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2207 level.native_em_level->lev->required > 0 :
2208 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2209 level.native_sp_level->game_sp->infotrons_still_needed > 0 :
2210 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2211 game_mm.kettles_still_needed > 0 ||
2212 game_mm.lights_still_needed > 0 :
2213 local_player->gems_still_needed > 0 ||
2214 local_player->sokobanfields_still_needed > 0 ||
2215 local_player->lights_still_needed > 0);
2216 int health = (local_player->LevelSolved ?
2217 local_player->LevelSolved_CountingHealth :
2218 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2219 MM_HEALTH(game_mm.laser_overload_value) :
2220 local_player->health);
2222 UpdatePlayfieldElementCount();
2224 /* update game panel control values */
2226 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = level_nr;
2227 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2229 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2230 for (i = 0; i < MAX_NUM_KEYS; i++)
2231 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2232 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2233 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2235 if (game.centered_player_nr == -1)
2237 for (i = 0; i < MAX_PLAYERS; i++)
2239 /* only one player in Supaplex game engine */
2240 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2243 for (k = 0; k < MAX_NUM_KEYS; k++)
2245 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2247 if (level.native_em_level->ply[i]->keys & (1 << k))
2248 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2249 get_key_element_from_nr(k);
2251 else if (stored_player[i].key[k])
2252 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2253 get_key_element_from_nr(k);
2256 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2257 getPlayerInventorySize(i);
2259 if (stored_player[i].num_white_keys > 0)
2260 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2263 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2264 stored_player[i].num_white_keys;
2269 int player_nr = game.centered_player_nr;
2271 for (k = 0; k < MAX_NUM_KEYS; k++)
2273 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2275 if (level.native_em_level->ply[player_nr]->keys & (1 << k))
2276 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2277 get_key_element_from_nr(k);
2279 else if (stored_player[player_nr].key[k])
2280 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2281 get_key_element_from_nr(k);
2284 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2285 getPlayerInventorySize(player_nr);
2287 if (stored_player[player_nr].num_white_keys > 0)
2288 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2290 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2291 stored_player[player_nr].num_white_keys;
2294 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2296 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2297 get_inventory_element_from_pos(local_player, i);
2298 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2299 get_inventory_element_from_pos(local_player, -i - 1);
2302 game_panel_controls[GAME_PANEL_SCORE].value = score;
2303 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2305 game_panel_controls[GAME_PANEL_TIME].value = time;
2307 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2308 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2309 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2311 if (level.time == 0)
2312 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2314 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2316 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2317 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2319 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2321 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2322 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2324 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2325 local_player->shield_normal_time_left;
2326 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2327 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2329 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2330 local_player->shield_deadly_time_left;
2332 game_panel_controls[GAME_PANEL_EXIT].value =
2333 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2335 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2336 (game.ball_state ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2337 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2338 (game.ball_state ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2339 EL_EMC_MAGIC_BALL_SWITCH);
2341 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2342 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2343 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2344 game.light_time_left;
2346 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2347 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2348 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2349 game.timegate_time_left;
2351 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2352 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2354 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2355 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2356 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2357 game.lenses_time_left;
2359 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2360 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2361 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2362 game.magnify_time_left;
2364 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2365 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2366 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2367 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2368 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2369 EL_BALLOON_SWITCH_NONE);
2371 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2372 local_player->dynabomb_count;
2373 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2374 local_player->dynabomb_size;
2375 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2376 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2378 game_panel_controls[GAME_PANEL_PENGUINS].value =
2379 local_player->friends_still_needed;
2381 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2382 local_player->sokobanfields_still_needed;
2383 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2384 local_player->sokobanfields_still_needed;
2386 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2387 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2389 for (i = 0; i < NUM_BELTS; i++)
2391 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2392 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2393 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2394 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2395 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2398 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2399 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2400 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2401 game.magic_wall_time_left;
2403 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2404 local_player->gravity;
2406 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2407 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2409 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2410 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2411 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2412 game.panel.element[i].id : EL_UNDEFINED);
2414 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2415 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2416 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2417 element_info[game.panel.element_count[i].id].element_count : 0);
2419 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2420 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2421 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2422 element_info[game.panel.ce_score[i].id].collect_score : 0);
2424 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2425 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2426 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2427 element_info[game.panel.ce_score_element[i].id].collect_score :
2430 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2431 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2432 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2434 /* update game panel control frames */
2436 for (i = 0; game_panel_controls[i].nr != -1; i++)
2438 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2440 if (gpc->type == TYPE_ELEMENT)
2442 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2444 int last_anim_random_frame = gfx.anim_random_frame;
2445 int element = gpc->value;
2446 int graphic = el2panelimg(element);
2448 if (gpc->value != gpc->last_value)
2451 gpc->gfx_random = INIT_GFX_RANDOM();
2457 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2458 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2459 gpc->gfx_random = INIT_GFX_RANDOM();
2462 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2463 gfx.anim_random_frame = gpc->gfx_random;
2465 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2466 gpc->gfx_frame = element_info[element].collect_score;
2468 gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
2471 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2472 gfx.anim_random_frame = last_anim_random_frame;
2475 else if (gpc->type == TYPE_GRAPHIC)
2477 if (gpc->graphic != IMG_UNDEFINED)
2479 int last_anim_random_frame = gfx.anim_random_frame;
2480 int graphic = gpc->graphic;
2482 if (gpc->value != gpc->last_value)
2485 gpc->gfx_random = INIT_GFX_RANDOM();
2491 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2492 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2493 gpc->gfx_random = INIT_GFX_RANDOM();
2496 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2497 gfx.anim_random_frame = gpc->gfx_random;
2499 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2501 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2502 gfx.anim_random_frame = last_anim_random_frame;
2508 void DisplayGameControlValues()
2510 boolean redraw_panel = FALSE;
2513 for (i = 0; game_panel_controls[i].nr != -1; i++)
2515 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2517 if (PANEL_DEACTIVATED(gpc->pos))
2520 if (gpc->value == gpc->last_value &&
2521 gpc->frame == gpc->last_frame)
2524 redraw_panel = TRUE;
2530 /* copy default game door content to main double buffer */
2532 /* !!! CHECK AGAIN !!! */
2533 SetPanelBackground();
2534 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2535 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2537 /* redraw game control buttons */
2538 RedrawGameButtons();
2540 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2542 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2544 int nr = game_panel_order[i].nr;
2545 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2546 struct TextPosInfo *pos = gpc->pos;
2547 int type = gpc->type;
2548 int value = gpc->value;
2549 int frame = gpc->frame;
2550 int size = pos->size;
2551 int font = pos->font;
2552 boolean draw_masked = pos->draw_masked;
2553 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2555 if (PANEL_DEACTIVATED(pos))
2558 gpc->last_value = value;
2559 gpc->last_frame = frame;
2561 if (type == TYPE_INTEGER)
2563 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2564 nr == GAME_PANEL_TIME)
2566 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2568 if (use_dynamic_size) /* use dynamic number of digits */
2570 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2571 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2572 int size2 = size1 + 1;
2573 int font1 = pos->font;
2574 int font2 = pos->font_alt;
2576 size = (value < value_change ? size1 : size2);
2577 font = (value < value_change ? font1 : font2);
2581 /* correct text size if "digits" is zero or less */
2583 size = strlen(int2str(value, size));
2585 /* dynamically correct text alignment */
2586 pos->width = size * getFontWidth(font);
2588 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2589 int2str(value, size), font, mask_mode);
2591 else if (type == TYPE_ELEMENT)
2593 int element, graphic;
2597 int dst_x = PANEL_XPOS(pos);
2598 int dst_y = PANEL_YPOS(pos);
2600 if (value != EL_UNDEFINED && value != EL_EMPTY)
2603 graphic = el2panelimg(value);
2605 // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
2607 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2610 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2613 width = graphic_info[graphic].width * size / TILESIZE;
2614 height = graphic_info[graphic].height * size / TILESIZE;
2617 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2620 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2624 else if (type == TYPE_GRAPHIC)
2626 int graphic = gpc->graphic;
2627 int graphic_active = gpc->graphic_active;
2631 int dst_x = PANEL_XPOS(pos);
2632 int dst_y = PANEL_YPOS(pos);
2633 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2634 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2636 if (graphic != IMG_UNDEFINED && !skip)
2638 if (pos->style == STYLE_REVERSE)
2639 value = 100 - value;
2641 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2643 if (pos->direction & MV_HORIZONTAL)
2645 width = graphic_info[graphic_active].width * value / 100;
2646 height = graphic_info[graphic_active].height;
2648 if (pos->direction == MV_LEFT)
2650 src_x += graphic_info[graphic_active].width - width;
2651 dst_x += graphic_info[graphic_active].width - width;
2656 width = graphic_info[graphic_active].width;
2657 height = graphic_info[graphic_active].height * value / 100;
2659 if (pos->direction == MV_UP)
2661 src_y += graphic_info[graphic_active].height - height;
2662 dst_y += graphic_info[graphic_active].height - height;
2667 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2670 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2673 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2675 if (pos->direction & MV_HORIZONTAL)
2677 if (pos->direction == MV_RIGHT)
2684 dst_x = PANEL_XPOS(pos);
2687 width = graphic_info[graphic].width - width;
2691 if (pos->direction == MV_DOWN)
2698 dst_y = PANEL_YPOS(pos);
2701 height = graphic_info[graphic].height - height;
2705 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2708 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2712 else if (type == TYPE_STRING)
2714 boolean active = (value != 0);
2715 char *state_normal = "off";
2716 char *state_active = "on";
2717 char *state = (active ? state_active : state_normal);
2718 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2719 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2720 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2721 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2723 if (nr == GAME_PANEL_GRAVITY_STATE)
2725 int font1 = pos->font; /* (used for normal state) */
2726 int font2 = pos->font_alt; /* (used for active state) */
2728 font = (active ? font2 : font1);
2737 /* don't truncate output if "chars" is zero or less */
2740 /* dynamically correct text alignment */
2741 pos->width = size * getFontWidth(font);
2744 s_cut = getStringCopyN(s, size);
2746 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2747 s_cut, font, mask_mode);
2753 redraw_mask |= REDRAW_DOOR_1;
2756 SetGameStatus(GAME_MODE_PLAYING);
2759 void UpdateAndDisplayGameControlValues()
2761 if (tape.deactivate_display)
2764 UpdateGameControlValues();
2765 DisplayGameControlValues();
2768 void UpdateGameDoorValues()
2770 UpdateGameControlValues();
2773 void DrawGameDoorValues()
2775 DisplayGameControlValues();
2780 =============================================================================
2782 -----------------------------------------------------------------------------
2783 initialize game engine due to level / tape version number
2784 =============================================================================
2787 static void InitGameEngine()
2789 int i, j, k, l, x, y;
2791 /* set game engine from tape file when re-playing, else from level file */
2792 game.engine_version = (tape.playing ? tape.engine_version :
2793 level.game_version);
2795 /* set single or multi-player game mode (needed for re-playing tapes) */
2796 game.team_mode = setup.team_mode;
2800 int num_players = 0;
2802 for (i = 0; i < MAX_PLAYERS; i++)
2803 if (tape.player_participates[i])
2806 /* multi-player tapes contain input data for more than one player */
2807 game.team_mode = (num_players > 1);
2810 /* ---------------------------------------------------------------------- */
2811 /* set flags for bugs and changes according to active game engine version */
2812 /* ---------------------------------------------------------------------- */
2815 Summary of bugfix/change:
2816 Fixed handling for custom elements that change when pushed by the player.
2818 Fixed/changed in version:
2822 Before 3.1.0, custom elements that "change when pushing" changed directly
2823 after the player started pushing them (until then handled in "DigField()").
2824 Since 3.1.0, these custom elements are not changed until the "pushing"
2825 move of the element is finished (now handled in "ContinueMoving()").
2827 Affected levels/tapes:
2828 The first condition is generally needed for all levels/tapes before version
2829 3.1.0, which might use the old behaviour before it was changed; known tapes
2830 that are affected are some tapes from the level set "Walpurgis Gardens" by
2832 The second condition is an exception from the above case and is needed for
2833 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2834 above (including some development versions of 3.1.0), but before it was
2835 known that this change would break tapes like the above and was fixed in
2836 3.1.1, so that the changed behaviour was active although the engine version
2837 while recording maybe was before 3.1.0. There is at least one tape that is
2838 affected by this exception, which is the tape for the one-level set "Bug
2839 Machine" by Juergen Bonhagen.
2842 game.use_change_when_pushing_bug =
2843 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2845 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2846 tape.game_version < VERSION_IDENT(3,1,1,0)));
2849 Summary of bugfix/change:
2850 Fixed handling for blocking the field the player leaves when moving.
2852 Fixed/changed in version:
2856 Before 3.1.1, when "block last field when moving" was enabled, the field
2857 the player is leaving when moving was blocked for the time of the move,
2858 and was directly unblocked afterwards. This resulted in the last field
2859 being blocked for exactly one less than the number of frames of one player
2860 move. Additionally, even when blocking was disabled, the last field was
2861 blocked for exactly one frame.
2862 Since 3.1.1, due to changes in player movement handling, the last field
2863 is not blocked at all when blocking is disabled. When blocking is enabled,
2864 the last field is blocked for exactly the number of frames of one player
2865 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2866 last field is blocked for exactly one more than the number of frames of
2869 Affected levels/tapes:
2870 (!!! yet to be determined -- probably many !!!)
2873 game.use_block_last_field_bug =
2874 (game.engine_version < VERSION_IDENT(3,1,1,0));
2876 game_em.use_single_button =
2877 (game.engine_version > VERSION_IDENT(4,0,0,2));
2879 game_em.use_snap_key_bug =
2880 (game.engine_version < VERSION_IDENT(4,0,1,0));
2882 /* ---------------------------------------------------------------------- */
2884 /* set maximal allowed number of custom element changes per game frame */
2885 game.max_num_changes_per_frame = 1;
2887 /* default scan direction: scan playfield from top/left to bottom/right */
2888 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
2890 /* dynamically adjust element properties according to game engine version */
2891 InitElementPropertiesEngine(game.engine_version);
2894 printf("level %d: level version == %06d\n", level_nr, level.game_version);
2895 printf(" tape version == %06d [%s] [file: %06d]\n",
2896 tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
2898 printf(" => game.engine_version == %06d\n", game.engine_version);
2901 /* ---------- initialize player's initial move delay --------------------- */
2903 /* dynamically adjust player properties according to level information */
2904 for (i = 0; i < MAX_PLAYERS; i++)
2905 game.initial_move_delay_value[i] =
2906 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
2908 /* dynamically adjust player properties according to game engine version */
2909 for (i = 0; i < MAX_PLAYERS; i++)
2910 game.initial_move_delay[i] =
2911 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
2912 game.initial_move_delay_value[i] : 0);
2914 /* ---------- initialize player's initial push delay --------------------- */
2916 /* dynamically adjust player properties according to game engine version */
2917 game.initial_push_delay_value =
2918 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
2920 /* ---------- initialize changing elements ------------------------------- */
2922 /* initialize changing elements information */
2923 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2925 struct ElementInfo *ei = &element_info[i];
2927 /* this pointer might have been changed in the level editor */
2928 ei->change = &ei->change_page[0];
2930 if (!IS_CUSTOM_ELEMENT(i))
2932 ei->change->target_element = EL_EMPTY_SPACE;
2933 ei->change->delay_fixed = 0;
2934 ei->change->delay_random = 0;
2935 ei->change->delay_frames = 1;
2938 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
2940 ei->has_change_event[j] = FALSE;
2942 ei->event_page_nr[j] = 0;
2943 ei->event_page[j] = &ei->change_page[0];
2947 /* add changing elements from pre-defined list */
2948 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
2950 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
2951 struct ElementInfo *ei = &element_info[ch_delay->element];
2953 ei->change->target_element = ch_delay->target_element;
2954 ei->change->delay_fixed = ch_delay->change_delay;
2956 ei->change->pre_change_function = ch_delay->pre_change_function;
2957 ei->change->change_function = ch_delay->change_function;
2958 ei->change->post_change_function = ch_delay->post_change_function;
2960 ei->change->can_change = TRUE;
2961 ei->change->can_change_or_has_action = TRUE;
2963 ei->has_change_event[CE_DELAY] = TRUE;
2965 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
2966 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
2969 /* ---------- initialize internal run-time variables --------------------- */
2971 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
2973 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
2975 for (j = 0; j < ei->num_change_pages; j++)
2977 ei->change_page[j].can_change_or_has_action =
2978 (ei->change_page[j].can_change |
2979 ei->change_page[j].has_action);
2983 /* add change events from custom element configuration */
2984 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
2986 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
2988 for (j = 0; j < ei->num_change_pages; j++)
2990 if (!ei->change_page[j].can_change_or_has_action)
2993 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
2995 /* only add event page for the first page found with this event */
2996 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
2998 ei->has_change_event[k] = TRUE;
3000 ei->event_page_nr[k] = j;
3001 ei->event_page[k] = &ei->change_page[j];
3007 /* ---------- initialize reference elements in change conditions --------- */
3009 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3011 int element = EL_CUSTOM_START + i;
3012 struct ElementInfo *ei = &element_info[element];
3014 for (j = 0; j < ei->num_change_pages; j++)
3016 int trigger_element = ei->change_page[j].initial_trigger_element;
3018 if (trigger_element >= EL_PREV_CE_8 &&
3019 trigger_element <= EL_NEXT_CE_8)
3020 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3022 ei->change_page[j].trigger_element = trigger_element;
3026 /* ---------- initialize run-time trigger player and element ------------- */
3028 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3030 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3032 for (j = 0; j < ei->num_change_pages; j++)
3034 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3035 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3036 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3037 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3038 ei->change_page[j].actual_trigger_ce_value = 0;
3039 ei->change_page[j].actual_trigger_ce_score = 0;
3043 /* ---------- initialize trigger events ---------------------------------- */
3045 /* initialize trigger events information */
3046 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3047 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3048 trigger_events[i][j] = FALSE;
3050 /* add trigger events from element change event properties */
3051 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3053 struct ElementInfo *ei = &element_info[i];
3055 for (j = 0; j < ei->num_change_pages; j++)
3057 if (!ei->change_page[j].can_change_or_has_action)
3060 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3062 int trigger_element = ei->change_page[j].trigger_element;
3064 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3066 if (ei->change_page[j].has_event[k])
3068 if (IS_GROUP_ELEMENT(trigger_element))
3070 struct ElementGroupInfo *group =
3071 element_info[trigger_element].group;
3073 for (l = 0; l < group->num_elements_resolved; l++)
3074 trigger_events[group->element_resolved[l]][k] = TRUE;
3076 else if (trigger_element == EL_ANY_ELEMENT)
3077 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3078 trigger_events[l][k] = TRUE;
3080 trigger_events[trigger_element][k] = TRUE;
3087 /* ---------- initialize push delay -------------------------------------- */
3089 /* initialize push delay values to default */
3090 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3092 if (!IS_CUSTOM_ELEMENT(i))
3094 /* set default push delay values (corrected since version 3.0.7-1) */
3095 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3097 element_info[i].push_delay_fixed = 2;
3098 element_info[i].push_delay_random = 8;
3102 element_info[i].push_delay_fixed = 8;
3103 element_info[i].push_delay_random = 8;
3108 /* set push delay value for certain elements from pre-defined list */
3109 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3111 int e = push_delay_list[i].element;
3113 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3114 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3117 /* set push delay value for Supaplex elements for newer engine versions */
3118 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3120 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3122 if (IS_SP_ELEMENT(i))
3124 /* set SP push delay to just enough to push under a falling zonk */
3125 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3127 element_info[i].push_delay_fixed = delay;
3128 element_info[i].push_delay_random = 0;
3133 /* ---------- initialize move stepsize ----------------------------------- */
3135 /* initialize move stepsize values to default */
3136 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3137 if (!IS_CUSTOM_ELEMENT(i))
3138 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3140 /* set move stepsize value for certain elements from pre-defined list */
3141 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3143 int e = move_stepsize_list[i].element;
3145 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3148 /* ---------- initialize collect score ----------------------------------- */
3150 /* initialize collect score values for custom elements from initial value */
3151 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3152 if (IS_CUSTOM_ELEMENT(i))
3153 element_info[i].collect_score = element_info[i].collect_score_initial;
3155 /* ---------- initialize collect count ----------------------------------- */
3157 /* initialize collect count values for non-custom elements */
3158 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3159 if (!IS_CUSTOM_ELEMENT(i))
3160 element_info[i].collect_count_initial = 0;
3162 /* add collect count values for all elements from pre-defined list */
3163 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3164 element_info[collect_count_list[i].element].collect_count_initial =
3165 collect_count_list[i].count;
3167 /* ---------- initialize access direction -------------------------------- */
3169 /* initialize access direction values to default (access from every side) */
3170 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3171 if (!IS_CUSTOM_ELEMENT(i))
3172 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3174 /* set access direction value for certain elements from pre-defined list */
3175 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3176 element_info[access_direction_list[i].element].access_direction =
3177 access_direction_list[i].direction;
3179 /* ---------- initialize explosion content ------------------------------- */
3180 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3182 if (IS_CUSTOM_ELEMENT(i))
3185 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3187 /* (content for EL_YAMYAM set at run-time with game.yamyam_content_nr) */
3189 element_info[i].content.e[x][y] =
3190 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3191 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3192 i == EL_PLAYER_3 ? EL_EMERALD :
3193 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3194 i == EL_MOLE ? EL_EMERALD_RED :
3195 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3196 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3197 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3198 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3199 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3200 i == EL_WALL_EMERALD ? EL_EMERALD :
3201 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3202 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3203 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3204 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3205 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3206 i == EL_WALL_PEARL ? EL_PEARL :
3207 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3212 /* ---------- initialize recursion detection ------------------------------ */
3213 recursion_loop_depth = 0;
3214 recursion_loop_detected = FALSE;
3215 recursion_loop_element = EL_UNDEFINED;
3217 /* ---------- initialize graphics engine ---------------------------------- */
3218 game.scroll_delay_value =
3219 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3220 setup.scroll_delay ? setup.scroll_delay_value : 0);
3221 game.scroll_delay_value =
3222 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3224 /* ---------- initialize game engine snapshots ---------------------------- */
3225 for (i = 0; i < MAX_PLAYERS; i++)
3226 game.snapshot.last_action[i] = 0;
3227 game.snapshot.changed_action = FALSE;
3228 game.snapshot.collected_item = FALSE;
3229 game.snapshot.mode =
3230 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3231 SNAPSHOT_MODE_EVERY_STEP :
3232 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3233 SNAPSHOT_MODE_EVERY_MOVE :
3234 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3235 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3236 game.snapshot.save_snapshot = FALSE;
3238 /* ---------- initialize level time for Supaplex engine ------------------- */
3239 /* Supaplex levels with time limit currently unsupported -- should be added */
3240 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3244 int get_num_special_action(int element, int action_first, int action_last)
3246 int num_special_action = 0;
3249 for (i = action_first; i <= action_last; i++)
3251 boolean found = FALSE;
3253 for (j = 0; j < NUM_DIRECTIONS; j++)
3254 if (el_act_dir2img(element, i, j) !=
3255 el_act_dir2img(element, ACTION_DEFAULT, j))
3259 num_special_action++;
3264 return num_special_action;
3269 =============================================================================
3271 -----------------------------------------------------------------------------
3272 initialize and start new game
3273 =============================================================================
3276 #if DEBUG_INIT_PLAYER
3277 static void DebugPrintPlayerStatus(char *message)
3284 printf("%s:\n", message);
3286 for (i = 0; i < MAX_PLAYERS; i++)
3288 struct PlayerInfo *player = &stored_player[i];
3290 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3294 player->connected_locally,
3295 player->connected_network,
3298 if (local_player == player)
3299 printf(" (local player)");
3308 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3309 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3310 int fade_mask = REDRAW_FIELD;
3312 boolean emulate_bd = TRUE; /* unless non-BOULDERDASH elements found */
3313 boolean emulate_sb = TRUE; /* unless non-SOKOBAN elements found */
3314 boolean emulate_sp = TRUE; /* unless non-SUPAPLEX elements found */
3315 int initial_move_dir = MV_DOWN;
3318 // required here to update video display before fading (FIX THIS)
3319 DrawMaskedBorder(REDRAW_DOOR_2);
3321 if (!game.restart_level)
3322 CloseDoor(DOOR_CLOSE_1);
3324 SetGameStatus(GAME_MODE_PLAYING);
3326 if (level_editor_test_game)
3327 FadeSkipNextFadeIn();
3329 FadeSetEnterScreen();
3331 if (CheckIfGlobalBorderOrPlayfieldViewportHasChanged())
3332 fade_mask = REDRAW_ALL;
3334 FadeLevelSoundsAndMusic();
3336 ExpireSoundLoops(TRUE);
3338 if (!level_editor_test_game)
3341 /* needed if different viewport properties defined for playing */
3342 ChangeViewportPropertiesIfNeeded();
3346 DrawCompleteVideoDisplay();
3348 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3351 InitGameControlValues();
3353 /* don't play tapes over network */
3354 network_playing = (network.enabled && !tape.playing);
3356 for (i = 0; i < MAX_PLAYERS; i++)
3358 struct PlayerInfo *player = &stored_player[i];
3360 player->index_nr = i;
3361 player->index_bit = (1 << i);
3362 player->element_nr = EL_PLAYER_1 + i;
3364 player->present = FALSE;
3365 player->active = FALSE;
3366 player->mapped = FALSE;
3368 player->killed = FALSE;
3369 player->reanimated = FALSE;
3372 player->effective_action = 0;
3373 player->programmed_action = 0;
3375 player->mouse_action.lx = 0;
3376 player->mouse_action.ly = 0;
3377 player->mouse_action.button = 0;
3378 player->mouse_action.button_hint = 0;
3380 player->effective_mouse_action.lx = 0;
3381 player->effective_mouse_action.ly = 0;
3382 player->effective_mouse_action.button = 0;
3383 player->effective_mouse_action.button_hint = 0;
3386 player->score_final = 0;
3388 player->health = MAX_HEALTH;
3389 player->health_final = MAX_HEALTH;
3391 player->gems_still_needed = level.gems_needed;
3392 player->sokobanfields_still_needed = 0;
3393 player->lights_still_needed = 0;
3394 player->friends_still_needed = 0;
3396 for (j = 0; j < MAX_NUM_KEYS; j++)
3397 player->key[j] = FALSE;
3399 player->num_white_keys = 0;
3401 player->dynabomb_count = 0;
3402 player->dynabomb_size = 1;
3403 player->dynabombs_left = 0;
3404 player->dynabomb_xl = FALSE;
3406 player->MovDir = initial_move_dir;
3409 player->GfxDir = initial_move_dir;
3410 player->GfxAction = ACTION_DEFAULT;
3412 player->StepFrame = 0;
3414 player->initial_element = player->element_nr;
3415 player->artwork_element =
3416 (level.use_artwork_element[i] ? level.artwork_element[i] :
3417 player->element_nr);
3418 player->use_murphy = FALSE;
3420 player->block_last_field = FALSE; /* initialized in InitPlayerField() */
3421 player->block_delay_adjustment = 0; /* initialized in InitPlayerField() */
3423 player->gravity = level.initial_player_gravity[i];
3425 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3427 player->actual_frame_counter = 0;
3429 player->step_counter = 0;
3431 player->last_move_dir = initial_move_dir;
3433 player->is_active = FALSE;
3435 player->is_waiting = FALSE;
3436 player->is_moving = FALSE;
3437 player->is_auto_moving = FALSE;
3438 player->is_digging = FALSE;
3439 player->is_snapping = FALSE;
3440 player->is_collecting = FALSE;
3441 player->is_pushing = FALSE;
3442 player->is_switching = FALSE;
3443 player->is_dropping = FALSE;
3444 player->is_dropping_pressed = FALSE;
3446 player->is_bored = FALSE;
3447 player->is_sleeping = FALSE;
3449 player->was_waiting = TRUE;
3450 player->was_moving = FALSE;
3451 player->was_snapping = FALSE;
3452 player->was_dropping = FALSE;
3454 player->force_dropping = FALSE;
3456 player->frame_counter_bored = -1;
3457 player->frame_counter_sleeping = -1;
3459 player->anim_delay_counter = 0;
3460 player->post_delay_counter = 0;
3462 player->dir_waiting = initial_move_dir;
3463 player->action_waiting = ACTION_DEFAULT;
3464 player->last_action_waiting = ACTION_DEFAULT;
3465 player->special_action_bored = ACTION_DEFAULT;
3466 player->special_action_sleeping = ACTION_DEFAULT;
3468 player->switch_x = -1;
3469 player->switch_y = -1;
3471 player->drop_x = -1;
3472 player->drop_y = -1;
3474 player->show_envelope = 0;
3476 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3478 player->push_delay = -1; /* initialized when pushing starts */
3479 player->push_delay_value = game.initial_push_delay_value;
3481 player->drop_delay = 0;
3482 player->drop_pressed_delay = 0;
3484 player->last_jx = -1;
3485 player->last_jy = -1;
3489 player->shield_normal_time_left = 0;
3490 player->shield_deadly_time_left = 0;
3492 player->inventory_infinite_element = EL_UNDEFINED;
3493 player->inventory_size = 0;
3495 if (level.use_initial_inventory[i])
3497 for (j = 0; j < level.initial_inventory_size[i]; j++)
3499 int element = level.initial_inventory_content[i][j];
3500 int collect_count = element_info[element].collect_count_initial;
3503 if (!IS_CUSTOM_ELEMENT(element))
3506 if (collect_count == 0)
3507 player->inventory_infinite_element = element;
3509 for (k = 0; k < collect_count; k++)
3510 if (player->inventory_size < MAX_INVENTORY_SIZE)
3511 player->inventory_element[player->inventory_size++] = element;
3515 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3516 SnapField(player, 0, 0);
3518 player->LevelSolved = FALSE;
3519 player->GameOver = FALSE;
3521 player->LevelSolved_GameWon = FALSE;
3522 player->LevelSolved_GameEnd = FALSE;
3523 player->LevelSolved_PanelOff = FALSE;
3524 player->LevelSolved_SaveTape = FALSE;
3525 player->LevelSolved_SaveScore = FALSE;
3527 player->LevelSolved_CountingTime = 0;
3528 player->LevelSolved_CountingScore = 0;
3529 player->LevelSolved_CountingHealth = 0;
3531 map_player_action[i] = i;
3534 network_player_action_received = FALSE;
3536 /* initial null action */
3537 if (network_playing)
3538 SendToServer_MovePlayer(MV_NONE);
3546 TimeLeft = level.time;
3549 ScreenMovDir = MV_NONE;
3553 ScrollStepSize = 0; /* will be correctly initialized by ScrollScreen() */
3555 AllPlayersGone = FALSE;
3557 game.no_time_limit = (level.time == 0);
3559 game.yamyam_content_nr = 0;
3560 game.robot_wheel_active = FALSE;
3561 game.magic_wall_active = FALSE;
3562 game.magic_wall_time_left = 0;
3563 game.light_time_left = 0;
3564 game.timegate_time_left = 0;
3565 game.switchgate_pos = 0;
3566 game.wind_direction = level.wind_direction_initial;
3568 game.lenses_time_left = 0;
3569 game.magnify_time_left = 0;
3571 game.ball_state = level.ball_state_initial;
3572 game.ball_content_nr = 0;
3574 game.envelope_active = FALSE;
3576 for (i = 0; i < NUM_BELTS; i++)
3578 game.belt_dir[i] = MV_NONE;
3579 game.belt_dir_nr[i] = 3; /* not moving, next moving left */
3582 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3583 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3585 #if DEBUG_INIT_PLAYER
3586 DebugPrintPlayerStatus("Player status at level initialization");
3589 SCAN_PLAYFIELD(x, y)
3591 Feld[x][y] = level.field[x][y];
3592 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3593 ChangeDelay[x][y] = 0;
3594 ChangePage[x][y] = -1;
3595 CustomValue[x][y] = 0; /* initialized in InitField() */
3596 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3598 WasJustMoving[x][y] = 0;
3599 WasJustFalling[x][y] = 0;
3600 CheckCollision[x][y] = 0;
3601 CheckImpact[x][y] = 0;
3603 Pushed[x][y] = FALSE;
3605 ChangeCount[x][y] = 0;
3606 ChangeEvent[x][y] = -1;
3608 ExplodePhase[x][y] = 0;
3609 ExplodeDelay[x][y] = 0;
3610 ExplodeField[x][y] = EX_TYPE_NONE;
3612 RunnerVisit[x][y] = 0;
3613 PlayerVisit[x][y] = 0;
3616 GfxRandom[x][y] = INIT_GFX_RANDOM();
3617 GfxElement[x][y] = EL_UNDEFINED;
3618 GfxAction[x][y] = ACTION_DEFAULT;
3619 GfxDir[x][y] = MV_NONE;
3620 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3623 SCAN_PLAYFIELD(x, y)
3625 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3627 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3629 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3632 InitField(x, y, TRUE);
3634 ResetGfxAnimation(x, y);
3639 for (i = 0; i < MAX_PLAYERS; i++)
3641 struct PlayerInfo *player = &stored_player[i];
3643 /* set number of special actions for bored and sleeping animation */
3644 player->num_special_action_bored =
3645 get_num_special_action(player->artwork_element,
3646 ACTION_BORING_1, ACTION_BORING_LAST);
3647 player->num_special_action_sleeping =
3648 get_num_special_action(player->artwork_element,
3649 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3652 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3653 emulate_sb ? EMU_SOKOBAN :
3654 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3656 /* initialize type of slippery elements */
3657 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3659 if (!IS_CUSTOM_ELEMENT(i))
3661 /* default: elements slip down either to the left or right randomly */
3662 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3664 /* SP style elements prefer to slip down on the left side */
3665 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3666 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3668 /* BD style elements prefer to slip down on the left side */
3669 if (game.emulation == EMU_BOULDERDASH)
3670 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3674 /* initialize explosion and ignition delay */
3675 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3677 if (!IS_CUSTOM_ELEMENT(i))
3680 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3681 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3682 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3683 int last_phase = (num_phase + 1) * delay;
3684 int half_phase = (num_phase / 2) * delay;
3686 element_info[i].explosion_delay = last_phase - 1;
3687 element_info[i].ignition_delay = half_phase;
3689 if (i == EL_BLACK_ORB)
3690 element_info[i].ignition_delay = 1;
3694 /* correct non-moving belts to start moving left */
3695 for (i = 0; i < NUM_BELTS; i++)
3696 if (game.belt_dir[i] == MV_NONE)
3697 game.belt_dir_nr[i] = 3; /* not moving, next moving left */
3699 #if USE_NEW_PLAYER_ASSIGNMENTS
3700 for (i = 0; i < MAX_PLAYERS; i++)
3702 stored_player[i].connected = FALSE;
3704 /* in network game mode, the local player might not be the first player */
3705 if (stored_player[i].connected_locally)
3706 local_player = &stored_player[i];
3709 if (!network.enabled)
3710 local_player->connected = TRUE;
3714 for (i = 0; i < MAX_PLAYERS; i++)
3715 stored_player[i].connected = tape.player_participates[i];
3717 else if (network.enabled)
3719 /* add team mode players connected over the network (needed for correct
3720 assignment of player figures from level to locally playing players) */
3722 for (i = 0; i < MAX_PLAYERS; i++)
3723 if (stored_player[i].connected_network)
3724 stored_player[i].connected = TRUE;
3726 else if (game.team_mode)
3728 /* try to guess locally connected team mode players (needed for correct
3729 assignment of player figures from level to locally playing players) */
3731 for (i = 0; i < MAX_PLAYERS; i++)
3732 if (setup.input[i].use_joystick ||
3733 setup.input[i].key.left != KSYM_UNDEFINED)
3734 stored_player[i].connected = TRUE;
3737 #if DEBUG_INIT_PLAYER
3738 DebugPrintPlayerStatus("Player status after level initialization");
3741 #if DEBUG_INIT_PLAYER
3743 printf("Reassigning players ...\n");
3746 /* check if any connected player was not found in playfield */
3747 for (i = 0; i < MAX_PLAYERS; i++)
3749 struct PlayerInfo *player = &stored_player[i];
3751 if (player->connected && !player->present)
3753 struct PlayerInfo *field_player = NULL;
3755 #if DEBUG_INIT_PLAYER
3757 printf("- looking for field player for player %d ...\n", i + 1);
3760 /* assign first free player found that is present in the playfield */
3762 /* first try: look for unmapped playfield player that is not connected */
3763 for (j = 0; j < MAX_PLAYERS; j++)
3764 if (field_player == NULL &&
3765 stored_player[j].present &&
3766 !stored_player[j].mapped &&
3767 !stored_player[j].connected)
3768 field_player = &stored_player[j];
3770 /* second try: look for *any* unmapped playfield player */
3771 for (j = 0; j < MAX_PLAYERS; j++)
3772 if (field_player == NULL &&
3773 stored_player[j].present &&
3774 !stored_player[j].mapped)
3775 field_player = &stored_player[j];
3777 if (field_player != NULL)
3779 int jx = field_player->jx, jy = field_player->jy;
3781 #if DEBUG_INIT_PLAYER
3783 printf("- found player %d\n", field_player->index_nr + 1);
3786 player->present = FALSE;
3787 player->active = FALSE;
3789 field_player->present = TRUE;
3790 field_player->active = TRUE;
3793 player->initial_element = field_player->initial_element;
3794 player->artwork_element = field_player->artwork_element;
3796 player->block_last_field = field_player->block_last_field;
3797 player->block_delay_adjustment = field_player->block_delay_adjustment;
3800 StorePlayer[jx][jy] = field_player->element_nr;
3802 field_player->jx = field_player->last_jx = jx;
3803 field_player->jy = field_player->last_jy = jy;
3805 if (local_player == player)
3806 local_player = field_player;
3808 map_player_action[field_player->index_nr] = i;
3810 field_player->mapped = TRUE;
3812 #if DEBUG_INIT_PLAYER
3814 printf("- map_player_action[%d] == %d\n",
3815 field_player->index_nr + 1, i + 1);
3820 if (player->connected && player->present)
3821 player->mapped = TRUE;
3824 #if DEBUG_INIT_PLAYER
3825 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
3830 /* check if any connected player was not found in playfield */
3831 for (i = 0; i < MAX_PLAYERS; i++)
3833 struct PlayerInfo *player = &stored_player[i];
3835 if (player->connected && !player->present)
3837 for (j = 0; j < MAX_PLAYERS; j++)
3839 struct PlayerInfo *field_player = &stored_player[j];
3840 int jx = field_player->jx, jy = field_player->jy;
3842 /* assign first free player found that is present in the playfield */
3843 if (field_player->present && !field_player->connected)
3845 player->present = TRUE;
3846 player->active = TRUE;
3848 field_player->present = FALSE;
3849 field_player->active = FALSE;
3851 player->initial_element = field_player->initial_element;
3852 player->artwork_element = field_player->artwork_element;
3854 player->block_last_field = field_player->block_last_field;
3855 player->block_delay_adjustment = field_player->block_delay_adjustment;
3857 StorePlayer[jx][jy] = player->element_nr;
3859 player->jx = player->last_jx = jx;
3860 player->jy = player->last_jy = jy;
3870 printf("::: local_player->present == %d\n", local_player->present);
3873 /* set focus to local player for network games, else to all players */
3874 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
3875 game.centered_player_nr_next = game.centered_player_nr;
3876 game.set_centered_player = FALSE;
3878 if (network_playing && tape.recording)
3880 /* store client dependent player focus when recording network games */
3881 tape.centered_player_nr_next = game.centered_player_nr_next;
3882 tape.set_centered_player = TRUE;
3887 /* when playing a tape, eliminate all players who do not participate */
3889 #if USE_NEW_PLAYER_ASSIGNMENTS
3891 if (!game.team_mode)
3893 for (i = 0; i < MAX_PLAYERS; i++)
3895 if (stored_player[i].active &&
3896 !tape.player_participates[map_player_action[i]])
3898 struct PlayerInfo *player = &stored_player[i];
3899 int jx = player->jx, jy = player->jy;
3901 #if DEBUG_INIT_PLAYER
3903 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
3906 player->active = FALSE;
3907 StorePlayer[jx][jy] = 0;
3908 Feld[jx][jy] = EL_EMPTY;
3915 for (i = 0; i < MAX_PLAYERS; i++)
3917 if (stored_player[i].active &&
3918 !tape.player_participates[i])
3920 struct PlayerInfo *player = &stored_player[i];
3921 int jx = player->jx, jy = player->jy;
3923 player->active = FALSE;
3924 StorePlayer[jx][jy] = 0;
3925 Feld[jx][jy] = EL_EMPTY;
3930 else if (!network.enabled && !game.team_mode) /* && !tape.playing */
3932 /* when in single player mode, eliminate all but the local player */
3934 for (i = 0; i < MAX_PLAYERS; i++)
3936 struct PlayerInfo *player = &stored_player[i];
3938 if (player->active && player != local_player)
3940 int jx = player->jx, jy = player->jy;
3942 player->active = FALSE;
3943 player->present = FALSE;
3945 StorePlayer[jx][jy] = 0;
3946 Feld[jx][jy] = EL_EMPTY;
3951 /* when recording the game, store which players take part in the game */
3954 #if USE_NEW_PLAYER_ASSIGNMENTS
3955 for (i = 0; i < MAX_PLAYERS; i++)
3956 if (stored_player[i].connected)
3957 tape.player_participates[i] = TRUE;
3959 for (i = 0; i < MAX_PLAYERS; i++)
3960 if (stored_player[i].active)
3961 tape.player_participates[i] = TRUE;
3965 #if DEBUG_INIT_PLAYER
3966 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
3969 if (BorderElement == EL_EMPTY)
3972 SBX_Right = lev_fieldx - SCR_FIELDX;
3974 SBY_Lower = lev_fieldy - SCR_FIELDY;
3979 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
3981 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
3984 if (full_lev_fieldx <= SCR_FIELDX)
3985 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
3986 if (full_lev_fieldy <= SCR_FIELDY)
3987 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
3989 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
3991 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
3994 /* if local player not found, look for custom element that might create
3995 the player (make some assumptions about the right custom element) */
3996 if (!local_player->present)
3998 int start_x = 0, start_y = 0;
3999 int found_rating = 0;
4000 int found_element = EL_UNDEFINED;
4001 int player_nr = local_player->index_nr;
4003 SCAN_PLAYFIELD(x, y)
4005 int element = Feld[x][y];
4010 if (level.use_start_element[player_nr] &&
4011 level.start_element[player_nr] == element &&
4018 found_element = element;
4021 if (!IS_CUSTOM_ELEMENT(element))
4024 if (CAN_CHANGE(element))
4026 for (i = 0; i < element_info[element].num_change_pages; i++)
4028 /* check for player created from custom element as single target */
4029 content = element_info[element].change_page[i].target_element;
4030 is_player = ELEM_IS_PLAYER(content);
4032 if (is_player && (found_rating < 3 ||
4033 (found_rating == 3 && element < found_element)))
4039 found_element = element;
4044 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4046 /* check for player created from custom element as explosion content */
4047 content = element_info[element].content.e[xx][yy];
4048 is_player = ELEM_IS_PLAYER(content);
4050 if (is_player && (found_rating < 2 ||
4051 (found_rating == 2 && element < found_element)))
4053 start_x = x + xx - 1;
4054 start_y = y + yy - 1;
4057 found_element = element;
4060 if (!CAN_CHANGE(element))
4063 for (i = 0; i < element_info[element].num_change_pages; i++)
4065 /* check for player created from custom element as extended target */
4067 element_info[element].change_page[i].target_content.e[xx][yy];
4069 is_player = ELEM_IS_PLAYER(content);
4071 if (is_player && (found_rating < 1 ||
4072 (found_rating == 1 && element < found_element)))
4074 start_x = x + xx - 1;
4075 start_y = y + yy - 1;
4078 found_element = element;
4084 scroll_x = SCROLL_POSITION_X(start_x);
4085 scroll_y = SCROLL_POSITION_Y(start_y);
4089 scroll_x = SCROLL_POSITION_X(local_player->jx);
4090 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4093 /* !!! FIX THIS (START) !!! */
4094 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4096 InitGameEngine_EM();
4098 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4100 InitGameEngine_SP();
4102 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4104 InitGameEngine_MM();
4108 DrawLevel(REDRAW_FIELD);
4111 /* after drawing the level, correct some elements */
4112 if (game.timegate_time_left == 0)
4113 CloseAllOpenTimegates();
4116 /* blit playfield from scroll buffer to normal back buffer for fading in */
4117 BlitScreenToBitmap(backbuffer);
4118 /* !!! FIX THIS (END) !!! */
4120 DrawMaskedBorder(fade_mask);
4125 // full screen redraw is required at this point in the following cases:
4126 // - special editor door undrawn when game was started from level editor
4127 // - drawing area (playfield) was changed and has to be removed completely
4128 redraw_mask = REDRAW_ALL;
4132 if (!game.restart_level)
4134 /* copy default game door content to main double buffer */
4136 /* !!! CHECK AGAIN !!! */
4137 SetPanelBackground();
4138 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4139 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4142 SetPanelBackground();
4143 SetDrawBackgroundMask(REDRAW_DOOR_1);
4145 UpdateAndDisplayGameControlValues();
4147 if (!game.restart_level)
4153 CreateGameButtons();
4158 /* copy actual game door content to door double buffer for OpenDoor() */
4159 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4161 OpenDoor(DOOR_OPEN_ALL);
4163 KeyboardAutoRepeatOffUnlessAutoplay();
4165 #if DEBUG_INIT_PLAYER
4166 DebugPrintPlayerStatus("Player status (final)");
4175 if (!game.restart_level && !tape.playing)
4177 LevelStats_incPlayed(level_nr);
4179 SaveLevelSetup_SeriesInfo();
4182 game.restart_level = FALSE;
4183 game.restart_game_message = NULL;
4185 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4186 InitGameActions_MM();
4188 SaveEngineSnapshotToListInitial();
4190 if (!game.restart_level)
4192 PlaySound(SND_GAME_STARTING);
4194 if (setup.sound_music)
4199 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4200 int actual_player_x, int actual_player_y)
4202 /* this is used for non-R'n'D game engines to update certain engine values */
4204 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4206 actual_player_x = correctLevelPosX_EM(actual_player_x);
4207 actual_player_y = correctLevelPosY_EM(actual_player_y);
4210 /* needed to determine if sounds are played within the visible screen area */
4211 scroll_x = actual_scroll_x;
4212 scroll_y = actual_scroll_y;
4214 /* needed to get player position for "follow finger" playing input method */
4215 local_player->jx = actual_player_x;
4216 local_player->jy = actual_player_y;
4219 void InitMovDir(int x, int y)
4221 int i, element = Feld[x][y];
4222 static int xy[4][2] =
4229 static int direction[3][4] =
4231 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4232 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4233 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4242 Feld[x][y] = EL_BUG;
4243 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4246 case EL_SPACESHIP_RIGHT:
4247 case EL_SPACESHIP_UP:
4248 case EL_SPACESHIP_LEFT:
4249 case EL_SPACESHIP_DOWN:
4250 Feld[x][y] = EL_SPACESHIP;
4251 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4254 case EL_BD_BUTTERFLY_RIGHT:
4255 case EL_BD_BUTTERFLY_UP:
4256 case EL_BD_BUTTERFLY_LEFT:
4257 case EL_BD_BUTTERFLY_DOWN:
4258 Feld[x][y] = EL_BD_BUTTERFLY;
4259 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4262 case EL_BD_FIREFLY_RIGHT:
4263 case EL_BD_FIREFLY_UP:
4264 case EL_BD_FIREFLY_LEFT:
4265 case EL_BD_FIREFLY_DOWN:
4266 Feld[x][y] = EL_BD_FIREFLY;
4267 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4270 case EL_PACMAN_RIGHT:
4272 case EL_PACMAN_LEFT:
4273 case EL_PACMAN_DOWN:
4274 Feld[x][y] = EL_PACMAN;
4275 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4278 case EL_YAMYAM_LEFT:
4279 case EL_YAMYAM_RIGHT:
4281 case EL_YAMYAM_DOWN:
4282 Feld[x][y] = EL_YAMYAM;
4283 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4286 case EL_SP_SNIKSNAK:
4287 MovDir[x][y] = MV_UP;
4290 case EL_SP_ELECTRON:
4291 MovDir[x][y] = MV_LEFT;
4298 Feld[x][y] = EL_MOLE;
4299 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4303 if (IS_CUSTOM_ELEMENT(element))
4305 struct ElementInfo *ei = &element_info[element];
4306 int move_direction_initial = ei->move_direction_initial;
4307 int move_pattern = ei->move_pattern;
4309 if (move_direction_initial == MV_START_PREVIOUS)
4311 if (MovDir[x][y] != MV_NONE)
4314 move_direction_initial = MV_START_AUTOMATIC;
4317 if (move_direction_initial == MV_START_RANDOM)
4318 MovDir[x][y] = 1 << RND(4);
4319 else if (move_direction_initial & MV_ANY_DIRECTION)
4320 MovDir[x][y] = move_direction_initial;
4321 else if (move_pattern == MV_ALL_DIRECTIONS ||
4322 move_pattern == MV_TURNING_LEFT ||
4323 move_pattern == MV_TURNING_RIGHT ||
4324 move_pattern == MV_TURNING_LEFT_RIGHT ||
4325 move_pattern == MV_TURNING_RIGHT_LEFT ||
4326 move_pattern == MV_TURNING_RANDOM)
4327 MovDir[x][y] = 1 << RND(4);
4328 else if (move_pattern == MV_HORIZONTAL)
4329 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4330 else if (move_pattern == MV_VERTICAL)
4331 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4332 else if (move_pattern & MV_ANY_DIRECTION)
4333 MovDir[x][y] = element_info[element].move_pattern;
4334 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4335 move_pattern == MV_ALONG_RIGHT_SIDE)
4337 /* use random direction as default start direction */
4338 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4339 MovDir[x][y] = 1 << RND(4);
4341 for (i = 0; i < NUM_DIRECTIONS; i++)
4343 int x1 = x + xy[i][0];
4344 int y1 = y + xy[i][1];
4346 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4348 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4349 MovDir[x][y] = direction[0][i];
4351 MovDir[x][y] = direction[1][i];
4360 MovDir[x][y] = 1 << RND(4);
4362 if (element != EL_BUG &&
4363 element != EL_SPACESHIP &&
4364 element != EL_BD_BUTTERFLY &&
4365 element != EL_BD_FIREFLY)
4368 for (i = 0; i < NUM_DIRECTIONS; i++)
4370 int x1 = x + xy[i][0];
4371 int y1 = y + xy[i][1];
4373 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4375 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4377 MovDir[x][y] = direction[0][i];
4380 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4381 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4383 MovDir[x][y] = direction[1][i];
4392 GfxDir[x][y] = MovDir[x][y];
4395 void InitAmoebaNr(int x, int y)
4398 int group_nr = AmoebeNachbarNr(x, y);
4402 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4404 if (AmoebaCnt[i] == 0)
4412 AmoebaNr[x][y] = group_nr;
4413 AmoebaCnt[group_nr]++;
4414 AmoebaCnt2[group_nr]++;
4417 static void PlayerWins(struct PlayerInfo *player)
4419 player->LevelSolved = TRUE;
4420 player->GameOver = TRUE;
4422 player->score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4423 level.native_em_level->lev->score :
4424 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4427 player->health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4428 MM_HEALTH(game_mm.laser_overload_value) :
4431 player->LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed :
4433 player->LevelSolved_CountingScore = player->score_final;
4434 player->LevelSolved_CountingHealth = player->health_final;
4439 static int time_count_steps;
4440 static int time, time_final;
4441 static int score, score_final;
4442 static int health, health_final;
4443 static int game_over_delay_1 = 0;
4444 static int game_over_delay_2 = 0;
4445 static int game_over_delay_3 = 0;
4446 int game_over_delay_value_1 = 50;
4447 int game_over_delay_value_2 = 25;
4448 int game_over_delay_value_3 = 50;
4450 if (!local_player->LevelSolved_GameWon)
4454 /* do not start end game actions before the player stops moving (to exit) */
4455 if (local_player->MovPos)
4458 local_player->LevelSolved_GameWon = TRUE;
4459 local_player->LevelSolved_SaveTape = tape.recording;
4460 local_player->LevelSolved_SaveScore = !tape.playing;
4464 LevelStats_incSolved(level_nr);
4466 SaveLevelSetup_SeriesInfo();
4469 if (tape.auto_play) /* tape might already be stopped here */
4470 tape.auto_play_level_solved = TRUE;
4474 game_over_delay_1 = 0;
4475 game_over_delay_2 = 0;
4476 game_over_delay_3 = game_over_delay_value_3;
4478 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4479 score = score_final = local_player->score_final;
4480 health = health_final = local_player->health_final;
4482 if (level.score[SC_TIME_BONUS] > 0)
4487 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4489 else if (game.no_time_limit && TimePlayed < 999)
4492 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4495 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4497 game_over_delay_1 = game_over_delay_value_1;
4499 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4502 score_final += health * level.score[SC_TIME_BONUS];
4504 game_over_delay_2 = game_over_delay_value_2;
4507 local_player->score_final = score_final;
4508 local_player->health_final = health_final;
4511 if (level_editor_test_game)
4514 score = score_final;
4516 local_player->LevelSolved_CountingTime = time;
4517 local_player->LevelSolved_CountingScore = score;
4519 game_panel_controls[GAME_PANEL_TIME].value = time;
4520 game_panel_controls[GAME_PANEL_SCORE].value = score;
4522 DisplayGameControlValues();
4525 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4527 if (ExitX >= 0 && ExitY >= 0) /* local player has left the level */
4529 /* close exit door after last player */
4530 if ((AllPlayersGone &&
4531 (Feld[ExitX][ExitY] == EL_EXIT_OPEN ||
4532 Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN ||
4533 Feld[ExitX][ExitY] == EL_STEEL_EXIT_OPEN)) ||
4534 Feld[ExitX][ExitY] == EL_EM_EXIT_OPEN ||
4535 Feld[ExitX][ExitY] == EL_EM_STEEL_EXIT_OPEN)
4537 int element = Feld[ExitX][ExitY];
4539 Feld[ExitX][ExitY] =
4540 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4541 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4542 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4543 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4544 EL_EM_STEEL_EXIT_CLOSING);
4546 PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
4549 /* player disappears */
4550 DrawLevelField(ExitX, ExitY);
4553 for (i = 0; i < MAX_PLAYERS; i++)
4555 struct PlayerInfo *player = &stored_player[i];
4557 if (player->present)
4559 RemovePlayer(player);
4561 /* player disappears */
4562 DrawLevelField(player->jx, player->jy);
4567 PlaySound(SND_GAME_WINNING);
4570 if (game_over_delay_1 > 0)
4572 game_over_delay_1--;
4577 if (time != time_final)
4579 int time_to_go = ABS(time_final - time);
4580 int time_count_dir = (time < time_final ? +1 : -1);
4582 if (time_to_go < time_count_steps)
4583 time_count_steps = 1;
4585 time += time_count_steps * time_count_dir;
4586 score += time_count_steps * level.score[SC_TIME_BONUS];
4588 local_player->LevelSolved_CountingTime = time;
4589 local_player->LevelSolved_CountingScore = score;
4591 game_panel_controls[GAME_PANEL_TIME].value = time;
4592 game_panel_controls[GAME_PANEL_SCORE].value = score;
4594 DisplayGameControlValues();
4596 if (time == time_final)
4597 StopSound(SND_GAME_LEVELTIME_BONUS);
4598 else if (setup.sound_loops)
4599 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4601 PlaySound(SND_GAME_LEVELTIME_BONUS);
4606 if (game_over_delay_2 > 0)
4608 game_over_delay_2--;
4613 if (health != health_final)
4615 int health_count_dir = (health < health_final ? +1 : -1);
4617 health += health_count_dir;
4618 score += level.score[SC_TIME_BONUS];
4620 local_player->LevelSolved_CountingHealth = health;
4621 local_player->LevelSolved_CountingScore = score;
4623 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4624 game_panel_controls[GAME_PANEL_SCORE].value = score;
4626 DisplayGameControlValues();
4628 if (health == health_final)
4629 StopSound(SND_GAME_LEVELTIME_BONUS);
4630 else if (setup.sound_loops)
4631 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4633 PlaySound(SND_GAME_LEVELTIME_BONUS);
4638 local_player->LevelSolved_PanelOff = TRUE;
4640 if (game_over_delay_3 > 0)
4642 game_over_delay_3--;
4653 int last_level_nr = level_nr;
4655 local_player->LevelSolved_GameEnd = TRUE;
4657 if (local_player->LevelSolved_SaveTape)
4659 /* make sure that request dialog to save tape does not open door again */
4660 if (!global.use_envelope_request)
4661 CloseDoor(DOOR_CLOSE_1);
4663 SaveTapeChecked_LevelSolved(tape.level_nr); /* ask to save tape */
4666 /* if no tape is to be saved, close both doors simultaneously */
4667 CloseDoor(DOOR_CLOSE_ALL);
4669 if (level_editor_test_game)
4671 SetGameStatus(GAME_MODE_MAIN);
4678 if (!local_player->LevelSolved_SaveScore)
4680 SetGameStatus(GAME_MODE_MAIN);
4687 if (level_nr == leveldir_current->handicap_level)
4689 leveldir_current->handicap_level++;
4691 SaveLevelSetup_SeriesInfo();
4694 if (setup.increment_levels &&
4695 level_nr < leveldir_current->last_level)
4697 level_nr++; /* advance to next level */
4698 TapeErase(); /* start with empty tape */
4701 hi_pos = NewHiScore(last_level_nr);
4705 SetGameStatus(GAME_MODE_SCORES);
4707 DrawHallOfFame(last_level_nr, hi_pos);
4711 SetGameStatus(GAME_MODE_MAIN);
4717 int NewHiScore(int level_nr)
4721 boolean one_score_entry_per_name = !program.many_scores_per_name;
4723 LoadScore(level_nr);
4725 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4726 local_player->score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4729 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4731 if (local_player->score_final > highscore[k].Score)
4733 /* player has made it to the hall of fame */
4735 if (k < MAX_SCORE_ENTRIES - 1)
4737 int m = MAX_SCORE_ENTRIES - 1;
4739 if (one_score_entry_per_name)
4741 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4742 if (strEqual(setup.player_name, highscore[l].Name))
4745 if (m == k) /* player's new highscore overwrites his old one */
4749 for (l = m; l > k; l--)
4751 strcpy(highscore[l].Name, highscore[l - 1].Name);
4752 highscore[l].Score = highscore[l - 1].Score;
4758 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4759 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4760 highscore[k].Score = local_player->score_final;
4765 else if (one_score_entry_per_name &&
4766 !strncmp(setup.player_name, highscore[k].Name,
4767 MAX_PLAYER_NAME_LEN))
4768 break; /* player already there with a higher score */
4772 SaveScore(level_nr);
4777 inline static int getElementMoveStepsizeExt(int x, int y, int direction)
4779 int element = Feld[x][y];
4780 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4781 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4782 int horiz_move = (dx != 0);
4783 int sign = (horiz_move ? dx : dy);
4784 int step = sign * element_info[element].move_stepsize;
4786 /* special values for move stepsize for spring and things on conveyor belt */
4789 if (CAN_FALL(element) &&
4790 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
4791 step = sign * MOVE_STEPSIZE_NORMAL / 2;
4792 else if (element == EL_SPRING)
4793 step = sign * MOVE_STEPSIZE_NORMAL * 2;
4799 inline static int getElementMoveStepsize(int x, int y)
4801 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
4804 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
4806 if (player->GfxAction != action || player->GfxDir != dir)
4808 player->GfxAction = action;
4809 player->GfxDir = dir;
4811 player->StepFrame = 0;
4815 static void ResetGfxFrame(int x, int y)
4817 // profiling showed that "autotest" spends 10~20% of its time in this function
4818 if (DrawingDeactivatedField())
4821 int element = Feld[x][y];
4822 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
4824 if (graphic_info[graphic].anim_global_sync)
4825 GfxFrame[x][y] = FrameCounter;
4826 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
4827 GfxFrame[x][y] = CustomValue[x][y];
4828 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
4829 GfxFrame[x][y] = element_info[element].collect_score;
4830 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
4831 GfxFrame[x][y] = ChangeDelay[x][y];
4834 static void ResetGfxAnimation(int x, int y)
4836 GfxAction[x][y] = ACTION_DEFAULT;
4837 GfxDir[x][y] = MovDir[x][y];
4840 ResetGfxFrame(x, y);
4843 static void ResetRandomAnimationValue(int x, int y)
4845 GfxRandom[x][y] = INIT_GFX_RANDOM();
4848 void InitMovingField(int x, int y, int direction)
4850 int element = Feld[x][y];
4851 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4852 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4855 boolean is_moving_before, is_moving_after;
4857 /* check if element was/is moving or being moved before/after mode change */
4858 is_moving_before = (WasJustMoving[x][y] != 0);
4859 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
4861 /* reset animation only for moving elements which change direction of moving
4862 or which just started or stopped moving
4863 (else CEs with property "can move" / "not moving" are reset each frame) */
4864 if (is_moving_before != is_moving_after ||
4865 direction != MovDir[x][y])
4866 ResetGfxAnimation(x, y);
4868 MovDir[x][y] = direction;
4869 GfxDir[x][y] = direction;
4871 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
4872 direction == MV_DOWN && CAN_FALL(element) ?
4873 ACTION_FALLING : ACTION_MOVING);
4875 /* this is needed for CEs with property "can move" / "not moving" */
4877 if (is_moving_after)
4879 if (Feld[newx][newy] == EL_EMPTY)
4880 Feld[newx][newy] = EL_BLOCKED;
4882 MovDir[newx][newy] = MovDir[x][y];
4884 CustomValue[newx][newy] = CustomValue[x][y];
4886 GfxFrame[newx][newy] = GfxFrame[x][y];
4887 GfxRandom[newx][newy] = GfxRandom[x][y];
4888 GfxAction[newx][newy] = GfxAction[x][y];
4889 GfxDir[newx][newy] = GfxDir[x][y];
4893 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
4895 int direction = MovDir[x][y];
4896 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
4897 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
4903 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
4905 int oldx = x, oldy = y;
4906 int direction = MovDir[x][y];
4908 if (direction == MV_LEFT)
4910 else if (direction == MV_RIGHT)
4912 else if (direction == MV_UP)
4914 else if (direction == MV_DOWN)
4917 *comes_from_x = oldx;
4918 *comes_from_y = oldy;
4921 int MovingOrBlocked2Element(int x, int y)
4923 int element = Feld[x][y];
4925 if (element == EL_BLOCKED)
4929 Blocked2Moving(x, y, &oldx, &oldy);
4930 return Feld[oldx][oldy];
4936 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
4938 /* like MovingOrBlocked2Element(), but if element is moving
4939 and (x,y) is the field the moving element is just leaving,
4940 return EL_BLOCKED instead of the element value */
4941 int element = Feld[x][y];
4943 if (IS_MOVING(x, y))
4945 if (element == EL_BLOCKED)
4949 Blocked2Moving(x, y, &oldx, &oldy);
4950 return Feld[oldx][oldy];
4959 static void RemoveField(int x, int y)
4961 Feld[x][y] = EL_EMPTY;
4967 CustomValue[x][y] = 0;
4970 ChangeDelay[x][y] = 0;
4971 ChangePage[x][y] = -1;
4972 Pushed[x][y] = FALSE;
4974 GfxElement[x][y] = EL_UNDEFINED;
4975 GfxAction[x][y] = ACTION_DEFAULT;
4976 GfxDir[x][y] = MV_NONE;
4979 void RemoveMovingField(int x, int y)
4981 int oldx = x, oldy = y, newx = x, newy = y;
4982 int element = Feld[x][y];
4983 int next_element = EL_UNDEFINED;
4985 if (element != EL_BLOCKED && !IS_MOVING(x, y))
4988 if (IS_MOVING(x, y))
4990 Moving2Blocked(x, y, &newx, &newy);
4992 if (Feld[newx][newy] != EL_BLOCKED)
4994 /* element is moving, but target field is not free (blocked), but
4995 already occupied by something different (example: acid pool);
4996 in this case, only remove the moving field, but not the target */
4998 RemoveField(oldx, oldy);
5000 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5002 TEST_DrawLevelField(oldx, oldy);
5007 else if (element == EL_BLOCKED)
5009 Blocked2Moving(x, y, &oldx, &oldy);
5010 if (!IS_MOVING(oldx, oldy))
5014 if (element == EL_BLOCKED &&
5015 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5016 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5017 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5018 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5019 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5020 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5021 next_element = get_next_element(Feld[oldx][oldy]);
5023 RemoveField(oldx, oldy);
5024 RemoveField(newx, newy);
5026 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5028 if (next_element != EL_UNDEFINED)
5029 Feld[oldx][oldy] = next_element;
5031 TEST_DrawLevelField(oldx, oldy);
5032 TEST_DrawLevelField(newx, newy);
5035 void DrawDynamite(int x, int y)
5037 int sx = SCREENX(x), sy = SCREENY(y);
5038 int graphic = el2img(Feld[x][y]);
5041 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5044 if (IS_WALKABLE_INSIDE(Back[x][y]))
5048 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5049 else if (Store[x][y])
5050 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5052 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5054 if (Back[x][y] || Store[x][y])
5055 DrawGraphicThruMask(sx, sy, graphic, frame);
5057 DrawGraphic(sx, sy, graphic, frame);
5060 void CheckDynamite(int x, int y)
5062 if (MovDelay[x][y] != 0) /* dynamite is still waiting to explode */
5066 if (MovDelay[x][y] != 0)
5069 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5075 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5080 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5082 boolean num_checked_players = 0;
5085 for (i = 0; i < MAX_PLAYERS; i++)
5087 if (stored_player[i].active)
5089 int sx = stored_player[i].jx;
5090 int sy = stored_player[i].jy;
5092 if (num_checked_players == 0)
5099 *sx1 = MIN(*sx1, sx);
5100 *sy1 = MIN(*sy1, sy);
5101 *sx2 = MAX(*sx2, sx);
5102 *sy2 = MAX(*sy2, sy);
5105 num_checked_players++;
5110 static boolean checkIfAllPlayersFitToScreen_RND()
5112 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5114 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5116 return (sx2 - sx1 < SCR_FIELDX &&
5117 sy2 - sy1 < SCR_FIELDY);
5120 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5122 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5124 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5126 *sx = (sx1 + sx2) / 2;
5127 *sy = (sy1 + sy2) / 2;
5130 void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5131 boolean center_screen, boolean quick_relocation)
5133 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5134 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5135 boolean no_delay = (tape.warp_forward);
5136 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5137 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5138 int new_scroll_x, new_scroll_y;
5140 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5142 /* case 1: quick relocation inside visible screen (without scrolling) */
5149 if (!level.shifted_relocation || center_screen)
5151 /* relocation _with_ centering of screen */
5153 new_scroll_x = SCROLL_POSITION_X(x);
5154 new_scroll_y = SCROLL_POSITION_Y(y);
5158 /* relocation _without_ centering of screen */
5160 int center_scroll_x = SCROLL_POSITION_X(old_x);
5161 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5162 int offset_x = x + (scroll_x - center_scroll_x);
5163 int offset_y = y + (scroll_y - center_scroll_y);
5165 /* for new screen position, apply previous offset to center position */
5166 new_scroll_x = SCROLL_POSITION_X(offset_x);
5167 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5170 if (quick_relocation)
5172 /* case 2: quick relocation (redraw without visible scrolling) */
5174 scroll_x = new_scroll_x;
5175 scroll_y = new_scroll_y;
5182 /* case 3: visible relocation (with scrolling to new position) */
5184 ScrollScreen(NULL, SCROLL_GO_ON); /* scroll last frame to full tile */
5186 SetVideoFrameDelay(wait_delay_value);
5188 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5191 int fx = FX, fy = FY;
5193 dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5194 dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5196 if (dx == 0 && dy == 0) /* no scrolling needed at all */
5202 fx += dx * TILEX / 2;
5203 fy += dy * TILEY / 2;
5205 ScrollLevel(dx, dy);
5208 /* scroll in two steps of half tile size to make things smoother */
5209 BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
5211 /* scroll second step to align at full tile size */
5212 BlitScreenToBitmap(window);
5218 SetVideoFrameDelay(frame_delay_value_old);
5221 void RelocatePlayer(int jx, int jy, int el_player_raw)
5223 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5224 int player_nr = GET_PLAYER_NR(el_player);
5225 struct PlayerInfo *player = &stored_player[player_nr];
5226 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5227 boolean no_delay = (tape.warp_forward);
5228 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5229 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5230 int old_jx = player->jx;
5231 int old_jy = player->jy;
5232 int old_element = Feld[old_jx][old_jy];
5233 int element = Feld[jx][jy];
5234 boolean player_relocated = (old_jx != jx || old_jy != jy);
5236 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5237 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5238 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5239 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5240 int leave_side_horiz = move_dir_horiz;
5241 int leave_side_vert = move_dir_vert;
5242 int enter_side = enter_side_horiz | enter_side_vert;
5243 int leave_side = leave_side_horiz | leave_side_vert;
5245 if (player->GameOver) /* do not reanimate dead player */
5248 if (!player_relocated) /* no need to relocate the player */
5251 if (IS_PLAYER(jx, jy)) /* player already placed at new position */
5253 RemoveField(jx, jy); /* temporarily remove newly placed player */
5254 DrawLevelField(jx, jy);
5257 if (player->present)
5259 while (player->MovPos)
5261 ScrollPlayer(player, SCROLL_GO_ON);
5262 ScrollScreen(NULL, SCROLL_GO_ON);
5264 AdvanceFrameAndPlayerCounters(player->index_nr);
5268 BackToFront_WithFrameDelay(wait_delay_value);
5271 DrawPlayer(player); /* needed here only to cleanup last field */
5272 DrawLevelField(player->jx, player->jy); /* remove player graphic */
5274 player->is_moving = FALSE;
5277 if (IS_CUSTOM_ELEMENT(old_element))
5278 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5280 player->index_bit, leave_side);
5282 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5284 player->index_bit, leave_side);
5286 Feld[jx][jy] = el_player;
5287 InitPlayerField(jx, jy, el_player, TRUE);
5289 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5290 possible that the relocation target field did not contain a player element,
5291 but a walkable element, to which the new player was relocated -- in this
5292 case, restore that (already initialized!) element on the player field */
5293 if (!ELEM_IS_PLAYER(element)) /* player may be set on walkable element */
5295 Feld[jx][jy] = element; /* restore previously existing element */
5298 /* only visually relocate centered player */
5299 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5300 FALSE, level.instant_relocation);
5302 TestIfPlayerTouchesBadThing(jx, jy);
5303 TestIfPlayerTouchesCustomElement(jx, jy);
5305 if (IS_CUSTOM_ELEMENT(element))
5306 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5307 player->index_bit, enter_side);
5309 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5310 player->index_bit, enter_side);
5312 if (player->is_switching)
5314 /* ensure that relocation while still switching an element does not cause
5315 a new element to be treated as also switched directly after relocation
5316 (this is important for teleporter switches that teleport the player to
5317 a place where another teleporter switch is in the same direction, which
5318 would then incorrectly be treated as immediately switched before the
5319 direction key that caused the switch was released) */
5321 player->switch_x += jx - old_jx;
5322 player->switch_y += jy - old_jy;
5326 void Explode(int ex, int ey, int phase, int mode)
5332 /* !!! eliminate this variable !!! */
5333 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5335 if (game.explosions_delayed)
5337 ExplodeField[ex][ey] = mode;
5341 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
5343 int center_element = Feld[ex][ey];
5344 int artwork_element, explosion_element; /* set these values later */
5346 /* remove things displayed in background while burning dynamite */
5347 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5350 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5352 /* put moving element to center field (and let it explode there) */
5353 center_element = MovingOrBlocked2Element(ex, ey);
5354 RemoveMovingField(ex, ey);
5355 Feld[ex][ey] = center_element;
5358 /* now "center_element" is finally determined -- set related values now */
5359 artwork_element = center_element; /* for custom player artwork */
5360 explosion_element = center_element; /* for custom player artwork */
5362 if (IS_PLAYER(ex, ey))
5364 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5366 artwork_element = stored_player[player_nr].artwork_element;
5368 if (level.use_explosion_element[player_nr])
5370 explosion_element = level.explosion_element[player_nr];
5371 artwork_element = explosion_element;
5375 if (mode == EX_TYPE_NORMAL ||
5376 mode == EX_TYPE_CENTER ||
5377 mode == EX_TYPE_CROSS)
5378 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5380 last_phase = element_info[explosion_element].explosion_delay + 1;
5382 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5384 int xx = x - ex + 1;
5385 int yy = y - ey + 1;
5388 if (!IN_LEV_FIELD(x, y) ||
5389 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5390 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5393 element = Feld[x][y];
5395 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5397 element = MovingOrBlocked2Element(x, y);
5399 if (!IS_EXPLOSION_PROOF(element))
5400 RemoveMovingField(x, y);
5403 /* indestructible elements can only explode in center (but not flames) */
5404 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5405 mode == EX_TYPE_BORDER)) ||
5406 element == EL_FLAMES)
5409 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5410 behaviour, for example when touching a yamyam that explodes to rocks
5411 with active deadly shield, a rock is created under the player !!! */
5412 /* (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8) */
5414 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5415 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5416 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5418 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5421 if (IS_ACTIVE_BOMB(element))
5423 /* re-activate things under the bomb like gate or penguin */
5424 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5431 /* save walkable background elements while explosion on same tile */
5432 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5433 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5434 Back[x][y] = element;
5436 /* ignite explodable elements reached by other explosion */
5437 if (element == EL_EXPLOSION)
5438 element = Store2[x][y];
5440 if (AmoebaNr[x][y] &&
5441 (element == EL_AMOEBA_FULL ||
5442 element == EL_BD_AMOEBA ||
5443 element == EL_AMOEBA_GROWING))
5445 AmoebaCnt[AmoebaNr[x][y]]--;
5446 AmoebaCnt2[AmoebaNr[x][y]]--;
5451 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5453 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5455 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5457 if (PLAYERINFO(ex, ey)->use_murphy)
5458 Store[x][y] = EL_EMPTY;
5461 /* !!! check this case -- currently needed for rnd_rado_negundo_v,
5462 !!! levels 015 018 019 020 021 022 023 026 027 028 !!! */
5463 else if (ELEM_IS_PLAYER(center_element))
5464 Store[x][y] = EL_EMPTY;
5465 else if (center_element == EL_YAMYAM)
5466 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5467 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5468 Store[x][y] = element_info[center_element].content.e[xx][yy];
5470 /* needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5471 (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5472 otherwise) -- FIX THIS !!! */
5473 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5474 Store[x][y] = element_info[element].content.e[1][1];
5476 else if (!CAN_EXPLODE(element))
5477 Store[x][y] = element_info[element].content.e[1][1];
5480 Store[x][y] = EL_EMPTY;
5482 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5483 center_element == EL_AMOEBA_TO_DIAMOND)
5484 Store2[x][y] = element;
5486 Feld[x][y] = EL_EXPLOSION;
5487 GfxElement[x][y] = artwork_element;
5489 ExplodePhase[x][y] = 1;
5490 ExplodeDelay[x][y] = last_phase;
5495 if (center_element == EL_YAMYAM)
5496 game.yamyam_content_nr =
5497 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5509 GfxFrame[x][y] = 0; /* restart explosion animation */
5511 last_phase = ExplodeDelay[x][y];
5513 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5515 /* this can happen if the player leaves an explosion just in time */
5516 if (GfxElement[x][y] == EL_UNDEFINED)
5517 GfxElement[x][y] = EL_EMPTY;
5519 border_element = Store2[x][y];
5520 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5521 border_element = StorePlayer[x][y];
5523 if (phase == element_info[border_element].ignition_delay ||
5524 phase == last_phase)
5526 boolean border_explosion = FALSE;
5528 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5529 !PLAYER_EXPLOSION_PROTECTED(x, y))
5531 KillPlayerUnlessExplosionProtected(x, y);
5532 border_explosion = TRUE;
5534 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5536 Feld[x][y] = Store2[x][y];
5539 border_explosion = TRUE;
5541 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5543 AmoebeUmwandeln(x, y);
5545 border_explosion = TRUE;
5548 /* if an element just explodes due to another explosion (chain-reaction),
5549 do not immediately end the new explosion when it was the last frame of
5550 the explosion (as it would be done in the following "if"-statement!) */
5551 if (border_explosion && phase == last_phase)
5555 if (phase == last_phase)
5559 element = Feld[x][y] = Store[x][y];
5560 Store[x][y] = Store2[x][y] = 0;
5561 GfxElement[x][y] = EL_UNDEFINED;
5563 /* player can escape from explosions and might therefore be still alive */
5564 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5565 element <= EL_PLAYER_IS_EXPLODING_4)
5567 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5568 int explosion_element = EL_PLAYER_1 + player_nr;
5569 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5570 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5572 if (level.use_explosion_element[player_nr])
5573 explosion_element = level.explosion_element[player_nr];
5575 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5576 element_info[explosion_element].content.e[xx][yy]);
5579 /* restore probably existing indestructible background element */
5580 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5581 element = Feld[x][y] = Back[x][y];
5584 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5585 GfxDir[x][y] = MV_NONE;
5586 ChangeDelay[x][y] = 0;
5587 ChangePage[x][y] = -1;
5589 CustomValue[x][y] = 0;
5591 InitField_WithBug2(x, y, FALSE);
5593 TEST_DrawLevelField(x, y);
5595 TestIfElementTouchesCustomElement(x, y);
5597 if (GFX_CRUMBLED(element))
5598 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5600 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5601 StorePlayer[x][y] = 0;
5603 if (ELEM_IS_PLAYER(element))
5604 RelocatePlayer(x, y, element);
5606 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5608 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5609 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5612 TEST_DrawLevelFieldCrumbled(x, y);
5614 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5616 DrawLevelElement(x, y, Back[x][y]);
5617 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5619 else if (IS_WALKABLE_UNDER(Back[x][y]))
5621 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5622 DrawLevelElementThruMask(x, y, Back[x][y]);
5624 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5625 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5629 void DynaExplode(int ex, int ey)
5632 int dynabomb_element = Feld[ex][ey];
5633 int dynabomb_size = 1;
5634 boolean dynabomb_xl = FALSE;
5635 struct PlayerInfo *player;
5636 static int xy[4][2] =
5644 if (IS_ACTIVE_BOMB(dynabomb_element))
5646 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5647 dynabomb_size = player->dynabomb_size;
5648 dynabomb_xl = player->dynabomb_xl;
5649 player->dynabombs_left++;
5652 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5654 for (i = 0; i < NUM_DIRECTIONS; i++)
5656 for (j = 1; j <= dynabomb_size; j++)
5658 int x = ex + j * xy[i][0];
5659 int y = ey + j * xy[i][1];
5662 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5665 element = Feld[x][y];
5667 /* do not restart explosions of fields with active bombs */
5668 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5671 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5673 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5674 !IS_DIGGABLE(element) && !dynabomb_xl)
5680 void Bang(int x, int y)
5682 int element = MovingOrBlocked2Element(x, y);
5683 int explosion_type = EX_TYPE_NORMAL;
5685 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5687 struct PlayerInfo *player = PLAYERINFO(x, y);
5689 element = Feld[x][y] = player->initial_element;
5691 if (level.use_explosion_element[player->index_nr])
5693 int explosion_element = level.explosion_element[player->index_nr];
5695 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5696 explosion_type = EX_TYPE_CROSS;
5697 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5698 explosion_type = EX_TYPE_CENTER;
5706 case EL_BD_BUTTERFLY:
5709 case EL_DARK_YAMYAM:
5713 RaiseScoreElement(element);
5716 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5717 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5718 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5719 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5720 case EL_DYNABOMB_INCREASE_NUMBER:
5721 case EL_DYNABOMB_INCREASE_SIZE:
5722 case EL_DYNABOMB_INCREASE_POWER:
5723 explosion_type = EX_TYPE_DYNA;
5726 case EL_DC_LANDMINE:
5727 explosion_type = EX_TYPE_CENTER;
5732 case EL_LAMP_ACTIVE:
5733 case EL_AMOEBA_TO_DIAMOND:
5734 if (!IS_PLAYER(x, y)) /* penguin and player may be at same field */
5735 explosion_type = EX_TYPE_CENTER;
5739 if (element_info[element].explosion_type == EXPLODES_CROSS)
5740 explosion_type = EX_TYPE_CROSS;
5741 else if (element_info[element].explosion_type == EXPLODES_1X1)
5742 explosion_type = EX_TYPE_CENTER;
5746 if (explosion_type == EX_TYPE_DYNA)
5749 Explode(x, y, EX_PHASE_START, explosion_type);
5751 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5754 void SplashAcid(int x, int y)
5756 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5757 (!IN_LEV_FIELD(x - 1, y - 2) ||
5758 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5759 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5761 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5762 (!IN_LEV_FIELD(x + 1, y - 2) ||
5763 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5764 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5766 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5769 static void InitBeltMovement()
5771 static int belt_base_element[4] =
5773 EL_CONVEYOR_BELT_1_LEFT,
5774 EL_CONVEYOR_BELT_2_LEFT,
5775 EL_CONVEYOR_BELT_3_LEFT,
5776 EL_CONVEYOR_BELT_4_LEFT
5778 static int belt_base_active_element[4] =
5780 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5781 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5782 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5783 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5788 /* set frame order for belt animation graphic according to belt direction */
5789 for (i = 0; i < NUM_BELTS; i++)
5793 for (j = 0; j < NUM_BELT_PARTS; j++)
5795 int element = belt_base_active_element[belt_nr] + j;
5796 int graphic_1 = el2img(element);
5797 int graphic_2 = el2panelimg(element);
5799 if (game.belt_dir[i] == MV_LEFT)
5801 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5802 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5806 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5807 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5812 SCAN_PLAYFIELD(x, y)
5814 int element = Feld[x][y];
5816 for (i = 0; i < NUM_BELTS; i++)
5818 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
5820 int e_belt_nr = getBeltNrFromBeltElement(element);
5823 if (e_belt_nr == belt_nr)
5825 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
5827 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
5834 static void ToggleBeltSwitch(int x, int y)
5836 static int belt_base_element[4] =
5838 EL_CONVEYOR_BELT_1_LEFT,
5839 EL_CONVEYOR_BELT_2_LEFT,
5840 EL_CONVEYOR_BELT_3_LEFT,
5841 EL_CONVEYOR_BELT_4_LEFT
5843 static int belt_base_active_element[4] =
5845 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5846 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5847 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5848 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5850 static int belt_base_switch_element[4] =
5852 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
5853 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
5854 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
5855 EL_CONVEYOR_BELT_4_SWITCH_LEFT
5857 static int belt_move_dir[4] =
5865 int element = Feld[x][y];
5866 int belt_nr = getBeltNrFromBeltSwitchElement(element);
5867 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
5868 int belt_dir = belt_move_dir[belt_dir_nr];
5871 if (!IS_BELT_SWITCH(element))
5874 game.belt_dir_nr[belt_nr] = belt_dir_nr;
5875 game.belt_dir[belt_nr] = belt_dir;
5877 if (belt_dir_nr == 3)
5880 /* set frame order for belt animation graphic according to belt direction */
5881 for (i = 0; i < NUM_BELT_PARTS; i++)
5883 int element = belt_base_active_element[belt_nr] + i;
5884 int graphic_1 = el2img(element);
5885 int graphic_2 = el2panelimg(element);
5887 if (belt_dir == MV_LEFT)
5889 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5890 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5894 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5895 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5899 SCAN_PLAYFIELD(xx, yy)
5901 int element = Feld[xx][yy];
5903 if (IS_BELT_SWITCH(element))
5905 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
5907 if (e_belt_nr == belt_nr)
5909 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
5910 TEST_DrawLevelField(xx, yy);
5913 else if (IS_BELT(element) && belt_dir != MV_NONE)
5915 int e_belt_nr = getBeltNrFromBeltElement(element);
5917 if (e_belt_nr == belt_nr)
5919 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
5921 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
5922 TEST_DrawLevelField(xx, yy);
5925 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
5927 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
5929 if (e_belt_nr == belt_nr)
5931 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
5933 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
5934 TEST_DrawLevelField(xx, yy);
5940 static void ToggleSwitchgateSwitch(int x, int y)
5944 game.switchgate_pos = !game.switchgate_pos;
5946 SCAN_PLAYFIELD(xx, yy)
5948 int element = Feld[xx][yy];
5950 if (element == EL_SWITCHGATE_SWITCH_UP)
5952 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
5953 TEST_DrawLevelField(xx, yy);
5955 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
5957 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
5958 TEST_DrawLevelField(xx, yy);
5960 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
5962 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
5963 TEST_DrawLevelField(xx, yy);
5965 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
5967 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
5968 TEST_DrawLevelField(xx, yy);
5970 else if (element == EL_SWITCHGATE_OPEN ||
5971 element == EL_SWITCHGATE_OPENING)
5973 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
5975 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
5977 else if (element == EL_SWITCHGATE_CLOSED ||
5978 element == EL_SWITCHGATE_CLOSING)
5980 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
5982 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
5987 static int getInvisibleActiveFromInvisibleElement(int element)
5989 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
5990 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
5991 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
5995 static int getInvisibleFromInvisibleActiveElement(int element)
5997 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
5998 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
5999 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6003 static void RedrawAllLightSwitchesAndInvisibleElements()
6007 SCAN_PLAYFIELD(x, y)
6009 int element = Feld[x][y];
6011 if (element == EL_LIGHT_SWITCH &&
6012 game.light_time_left > 0)
6014 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6015 TEST_DrawLevelField(x, y);
6017 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6018 game.light_time_left == 0)
6020 Feld[x][y] = EL_LIGHT_SWITCH;
6021 TEST_DrawLevelField(x, y);
6023 else if (element == EL_EMC_DRIPPER &&
6024 game.light_time_left > 0)
6026 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6027 TEST_DrawLevelField(x, y);
6029 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6030 game.light_time_left == 0)
6032 Feld[x][y] = EL_EMC_DRIPPER;
6033 TEST_DrawLevelField(x, y);
6035 else if (element == EL_INVISIBLE_STEELWALL ||
6036 element == EL_INVISIBLE_WALL ||
6037 element == EL_INVISIBLE_SAND)
6039 if (game.light_time_left > 0)
6040 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6042 TEST_DrawLevelField(x, y);
6044 /* uncrumble neighbour fields, if needed */
6045 if (element == EL_INVISIBLE_SAND)
6046 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6048 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6049 element == EL_INVISIBLE_WALL_ACTIVE ||
6050 element == EL_INVISIBLE_SAND_ACTIVE)
6052 if (game.light_time_left == 0)
6053 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6055 TEST_DrawLevelField(x, y);
6057 /* re-crumble neighbour fields, if needed */
6058 if (element == EL_INVISIBLE_SAND)
6059 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6064 static void RedrawAllInvisibleElementsForLenses()
6068 SCAN_PLAYFIELD(x, y)
6070 int element = Feld[x][y];
6072 if (element == EL_EMC_DRIPPER &&
6073 game.lenses_time_left > 0)
6075 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6076 TEST_DrawLevelField(x, y);
6078 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6079 game.lenses_time_left == 0)
6081 Feld[x][y] = EL_EMC_DRIPPER;
6082 TEST_DrawLevelField(x, y);
6084 else if (element == EL_INVISIBLE_STEELWALL ||
6085 element == EL_INVISIBLE_WALL ||
6086 element == EL_INVISIBLE_SAND)
6088 if (game.lenses_time_left > 0)
6089 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6091 TEST_DrawLevelField(x, y);
6093 /* uncrumble neighbour fields, if needed */
6094 if (element == EL_INVISIBLE_SAND)
6095 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6097 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6098 element == EL_INVISIBLE_WALL_ACTIVE ||
6099 element == EL_INVISIBLE_SAND_ACTIVE)
6101 if (game.lenses_time_left == 0)
6102 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6104 TEST_DrawLevelField(x, y);
6106 /* re-crumble neighbour fields, if needed */
6107 if (element == EL_INVISIBLE_SAND)
6108 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6113 static void RedrawAllInvisibleElementsForMagnifier()
6117 SCAN_PLAYFIELD(x, y)
6119 int element = Feld[x][y];
6121 if (element == EL_EMC_FAKE_GRASS &&
6122 game.magnify_time_left > 0)
6124 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6125 TEST_DrawLevelField(x, y);
6127 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6128 game.magnify_time_left == 0)
6130 Feld[x][y] = EL_EMC_FAKE_GRASS;
6131 TEST_DrawLevelField(x, y);
6133 else if (IS_GATE_GRAY(element) &&
6134 game.magnify_time_left > 0)
6136 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6137 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6138 IS_EM_GATE_GRAY(element) ?
6139 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6140 IS_EMC_GATE_GRAY(element) ?
6141 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6142 IS_DC_GATE_GRAY(element) ?
6143 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6145 TEST_DrawLevelField(x, y);
6147 else if (IS_GATE_GRAY_ACTIVE(element) &&
6148 game.magnify_time_left == 0)
6150 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6151 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6152 IS_EM_GATE_GRAY_ACTIVE(element) ?
6153 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6154 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6155 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6156 IS_DC_GATE_GRAY_ACTIVE(element) ?
6157 EL_DC_GATE_WHITE_GRAY :
6159 TEST_DrawLevelField(x, y);
6164 static void ToggleLightSwitch(int x, int y)
6166 int element = Feld[x][y];
6168 game.light_time_left =
6169 (element == EL_LIGHT_SWITCH ?
6170 level.time_light * FRAMES_PER_SECOND : 0);
6172 RedrawAllLightSwitchesAndInvisibleElements();
6175 static void ActivateTimegateSwitch(int x, int y)
6179 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6181 SCAN_PLAYFIELD(xx, yy)
6183 int element = Feld[xx][yy];
6185 if (element == EL_TIMEGATE_CLOSED ||
6186 element == EL_TIMEGATE_CLOSING)
6188 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6189 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6193 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6195 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6196 TEST_DrawLevelField(xx, yy);
6202 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6203 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6206 void Impact(int x, int y)
6208 boolean last_line = (y == lev_fieldy - 1);
6209 boolean object_hit = FALSE;
6210 boolean impact = (last_line || object_hit);
6211 int element = Feld[x][y];
6212 int smashed = EL_STEELWALL;
6214 if (!last_line) /* check if element below was hit */
6216 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6219 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6220 MovDir[x][y + 1] != MV_DOWN ||
6221 MovPos[x][y + 1] <= TILEY / 2));
6223 /* do not smash moving elements that left the smashed field in time */
6224 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6225 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6228 #if USE_QUICKSAND_IMPACT_BUGFIX
6229 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6231 RemoveMovingField(x, y + 1);
6232 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6233 Feld[x][y + 2] = EL_ROCK;
6234 TEST_DrawLevelField(x, y + 2);
6239 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6241 RemoveMovingField(x, y + 1);
6242 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6243 Feld[x][y + 2] = EL_ROCK;
6244 TEST_DrawLevelField(x, y + 2);
6251 smashed = MovingOrBlocked2Element(x, y + 1);
6253 impact = (last_line || object_hit);
6256 if (!last_line && smashed == EL_ACID) /* element falls into acid */
6258 SplashAcid(x, y + 1);
6262 /* !!! not sufficient for all cases -- see EL_PEARL below !!! */
6263 /* only reset graphic animation if graphic really changes after impact */
6265 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6267 ResetGfxAnimation(x, y);
6268 TEST_DrawLevelField(x, y);
6271 if (impact && CAN_EXPLODE_IMPACT(element))
6276 else if (impact && element == EL_PEARL &&
6277 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6279 ResetGfxAnimation(x, y);
6281 Feld[x][y] = EL_PEARL_BREAKING;
6282 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6285 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6287 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6292 if (impact && element == EL_AMOEBA_DROP)
6294 if (object_hit && IS_PLAYER(x, y + 1))
6295 KillPlayerUnlessEnemyProtected(x, y + 1);
6296 else if (object_hit && smashed == EL_PENGUIN)
6300 Feld[x][y] = EL_AMOEBA_GROWING;
6301 Store[x][y] = EL_AMOEBA_WET;
6303 ResetRandomAnimationValue(x, y);
6308 if (object_hit) /* check which object was hit */
6310 if ((CAN_PASS_MAGIC_WALL(element) &&
6311 (smashed == EL_MAGIC_WALL ||
6312 smashed == EL_BD_MAGIC_WALL)) ||
6313 (CAN_PASS_DC_MAGIC_WALL(element) &&
6314 smashed == EL_DC_MAGIC_WALL))
6317 int activated_magic_wall =
6318 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6319 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6320 EL_DC_MAGIC_WALL_ACTIVE);
6322 /* activate magic wall / mill */
6323 SCAN_PLAYFIELD(xx, yy)
6325 if (Feld[xx][yy] == smashed)
6326 Feld[xx][yy] = activated_magic_wall;
6329 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6330 game.magic_wall_active = TRUE;
6332 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6333 SND_MAGIC_WALL_ACTIVATING :
6334 smashed == EL_BD_MAGIC_WALL ?
6335 SND_BD_MAGIC_WALL_ACTIVATING :
6336 SND_DC_MAGIC_WALL_ACTIVATING));
6339 if (IS_PLAYER(x, y + 1))
6341 if (CAN_SMASH_PLAYER(element))
6343 KillPlayerUnlessEnemyProtected(x, y + 1);
6347 else if (smashed == EL_PENGUIN)
6349 if (CAN_SMASH_PLAYER(element))
6355 else if (element == EL_BD_DIAMOND)
6357 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6363 else if (((element == EL_SP_INFOTRON ||
6364 element == EL_SP_ZONK) &&
6365 (smashed == EL_SP_SNIKSNAK ||
6366 smashed == EL_SP_ELECTRON ||
6367 smashed == EL_SP_DISK_ORANGE)) ||
6368 (element == EL_SP_INFOTRON &&
6369 smashed == EL_SP_DISK_YELLOW))
6374 else if (CAN_SMASH_EVERYTHING(element))
6376 if (IS_CLASSIC_ENEMY(smashed) ||
6377 CAN_EXPLODE_SMASHED(smashed))
6382 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6384 if (smashed == EL_LAMP ||
6385 smashed == EL_LAMP_ACTIVE)
6390 else if (smashed == EL_NUT)
6392 Feld[x][y + 1] = EL_NUT_BREAKING;
6393 PlayLevelSound(x, y, SND_NUT_BREAKING);
6394 RaiseScoreElement(EL_NUT);
6397 else if (smashed == EL_PEARL)
6399 ResetGfxAnimation(x, y);
6401 Feld[x][y + 1] = EL_PEARL_BREAKING;
6402 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6405 else if (smashed == EL_DIAMOND)
6407 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6408 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6411 else if (IS_BELT_SWITCH(smashed))
6413 ToggleBeltSwitch(x, y + 1);
6415 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6416 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6417 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6418 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6420 ToggleSwitchgateSwitch(x, y + 1);
6422 else if (smashed == EL_LIGHT_SWITCH ||
6423 smashed == EL_LIGHT_SWITCH_ACTIVE)
6425 ToggleLightSwitch(x, y + 1);
6429 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6431 CheckElementChangeBySide(x, y + 1, smashed, element,
6432 CE_SWITCHED, CH_SIDE_TOP);
6433 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6439 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6444 /* play sound of magic wall / mill */
6446 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6447 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6448 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6450 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6451 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6452 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6453 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6454 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6455 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6460 /* play sound of object that hits the ground */
6461 if (last_line || object_hit)
6462 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6465 inline static void TurnRoundExt(int x, int y)
6477 { 0, 0 }, { 0, 0 }, { 0, 0 },
6482 int left, right, back;
6486 { MV_DOWN, MV_UP, MV_RIGHT },
6487 { MV_UP, MV_DOWN, MV_LEFT },
6489 { MV_LEFT, MV_RIGHT, MV_DOWN },
6493 { MV_RIGHT, MV_LEFT, MV_UP }
6496 int element = Feld[x][y];
6497 int move_pattern = element_info[element].move_pattern;
6499 int old_move_dir = MovDir[x][y];
6500 int left_dir = turn[old_move_dir].left;
6501 int right_dir = turn[old_move_dir].right;
6502 int back_dir = turn[old_move_dir].back;
6504 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6505 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6506 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6507 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6509 int left_x = x + left_dx, left_y = y + left_dy;
6510 int right_x = x + right_dx, right_y = y + right_dy;
6511 int move_x = x + move_dx, move_y = y + move_dy;
6515 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6517 TestIfBadThingTouchesOtherBadThing(x, y);
6519 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6520 MovDir[x][y] = right_dir;
6521 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6522 MovDir[x][y] = left_dir;
6524 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6526 else if (element == EL_BD_BUTTERFLY) /* && MovDir[x][y] == left_dir) */
6529 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6531 TestIfBadThingTouchesOtherBadThing(x, y);
6533 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6534 MovDir[x][y] = left_dir;
6535 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6536 MovDir[x][y] = right_dir;
6538 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6540 else if (element == EL_BD_FIREFLY) /* && MovDir[x][y] == right_dir) */
6543 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6545 TestIfBadThingTouchesOtherBadThing(x, y);
6547 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6548 MovDir[x][y] = left_dir;
6549 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6550 MovDir[x][y] = right_dir;
6552 if (MovDir[x][y] != old_move_dir)
6555 else if (element == EL_YAMYAM)
6557 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6558 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6560 if (can_turn_left && can_turn_right)
6561 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6562 else if (can_turn_left)
6563 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6564 else if (can_turn_right)
6565 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6567 MovDir[x][y] = back_dir;
6569 MovDelay[x][y] = 16 + 16 * RND(3);
6571 else if (element == EL_DARK_YAMYAM)
6573 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6575 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6578 if (can_turn_left && can_turn_right)
6579 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6580 else if (can_turn_left)
6581 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6582 else if (can_turn_right)
6583 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6585 MovDir[x][y] = back_dir;
6587 MovDelay[x][y] = 16 + 16 * RND(3);
6589 else if (element == EL_PACMAN)
6591 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6592 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6594 if (can_turn_left && can_turn_right)
6595 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6596 else if (can_turn_left)
6597 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6598 else if (can_turn_right)
6599 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6601 MovDir[x][y] = back_dir;
6603 MovDelay[x][y] = 6 + RND(40);
6605 else if (element == EL_PIG)
6607 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6608 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6609 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6610 boolean should_turn_left, should_turn_right, should_move_on;
6612 int rnd = RND(rnd_value);
6614 should_turn_left = (can_turn_left &&
6616 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6617 y + back_dy + left_dy)));
6618 should_turn_right = (can_turn_right &&
6620 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6621 y + back_dy + right_dy)));
6622 should_move_on = (can_move_on &&
6625 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6626 y + move_dy + left_dy) ||
6627 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6628 y + move_dy + right_dy)));
6630 if (should_turn_left || should_turn_right || should_move_on)
6632 if (should_turn_left && should_turn_right && should_move_on)
6633 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6634 rnd < 2 * rnd_value / 3 ? right_dir :
6636 else if (should_turn_left && should_turn_right)
6637 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6638 else if (should_turn_left && should_move_on)
6639 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6640 else if (should_turn_right && should_move_on)
6641 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6642 else if (should_turn_left)
6643 MovDir[x][y] = left_dir;
6644 else if (should_turn_right)
6645 MovDir[x][y] = right_dir;
6646 else if (should_move_on)
6647 MovDir[x][y] = old_move_dir;
6649 else if (can_move_on && rnd > rnd_value / 8)
6650 MovDir[x][y] = old_move_dir;
6651 else if (can_turn_left && can_turn_right)
6652 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6653 else if (can_turn_left && rnd > rnd_value / 8)
6654 MovDir[x][y] = left_dir;
6655 else if (can_turn_right && rnd > rnd_value/8)
6656 MovDir[x][y] = right_dir;
6658 MovDir[x][y] = back_dir;
6660 xx = x + move_xy[MovDir[x][y]].dx;
6661 yy = y + move_xy[MovDir[x][y]].dy;
6663 if (!IN_LEV_FIELD(xx, yy) ||
6664 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6665 MovDir[x][y] = old_move_dir;
6669 else if (element == EL_DRAGON)
6671 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6672 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6673 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6675 int rnd = RND(rnd_value);
6677 if (can_move_on && rnd > rnd_value / 8)
6678 MovDir[x][y] = old_move_dir;
6679 else if (can_turn_left && can_turn_right)
6680 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6681 else if (can_turn_left && rnd > rnd_value / 8)
6682 MovDir[x][y] = left_dir;
6683 else if (can_turn_right && rnd > rnd_value / 8)
6684 MovDir[x][y] = right_dir;
6686 MovDir[x][y] = back_dir;
6688 xx = x + move_xy[MovDir[x][y]].dx;
6689 yy = y + move_xy[MovDir[x][y]].dy;
6691 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6692 MovDir[x][y] = old_move_dir;
6696 else if (element == EL_MOLE)
6698 boolean can_move_on =
6699 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6700 IS_AMOEBOID(Feld[move_x][move_y]) ||
6701 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6704 boolean can_turn_left =
6705 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6706 IS_AMOEBOID(Feld[left_x][left_y])));
6708 boolean can_turn_right =
6709 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6710 IS_AMOEBOID(Feld[right_x][right_y])));
6712 if (can_turn_left && can_turn_right)
6713 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6714 else if (can_turn_left)
6715 MovDir[x][y] = left_dir;
6717 MovDir[x][y] = right_dir;
6720 if (MovDir[x][y] != old_move_dir)
6723 else if (element == EL_BALLOON)
6725 MovDir[x][y] = game.wind_direction;
6728 else if (element == EL_SPRING)
6730 if (MovDir[x][y] & MV_HORIZONTAL)
6732 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6733 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6735 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6736 ResetGfxAnimation(move_x, move_y);
6737 TEST_DrawLevelField(move_x, move_y);
6739 MovDir[x][y] = back_dir;
6741 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6742 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6743 MovDir[x][y] = MV_NONE;
6748 else if (element == EL_ROBOT ||
6749 element == EL_SATELLITE ||
6750 element == EL_PENGUIN ||
6751 element == EL_EMC_ANDROID)
6753 int attr_x = -1, attr_y = -1;
6764 for (i = 0; i < MAX_PLAYERS; i++)
6766 struct PlayerInfo *player = &stored_player[i];
6767 int jx = player->jx, jy = player->jy;
6769 if (!player->active)
6773 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6781 if (element == EL_ROBOT && ZX >= 0 && ZY >= 0 &&
6782 (Feld[ZX][ZY] == EL_ROBOT_WHEEL_ACTIVE ||
6783 game.engine_version < VERSION_IDENT(3,1,0,0)))
6789 if (element == EL_PENGUIN)
6792 static int xy[4][2] =
6800 for (i = 0; i < NUM_DIRECTIONS; i++)
6802 int ex = x + xy[i][0];
6803 int ey = y + xy[i][1];
6805 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
6806 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
6807 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
6808 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
6817 MovDir[x][y] = MV_NONE;
6819 MovDir[x][y] |= (AllPlayersGone ? MV_RIGHT : MV_LEFT);
6820 else if (attr_x > x)
6821 MovDir[x][y] |= (AllPlayersGone ? MV_LEFT : MV_RIGHT);
6823 MovDir[x][y] |= (AllPlayersGone ? MV_DOWN : MV_UP);
6824 else if (attr_y > y)
6825 MovDir[x][y] |= (AllPlayersGone ? MV_UP : MV_DOWN);
6827 if (element == EL_ROBOT)
6831 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6832 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
6833 Moving2Blocked(x, y, &newx, &newy);
6835 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
6836 MovDelay[x][y] = 8 + 8 * !RND(3);
6838 MovDelay[x][y] = 16;
6840 else if (element == EL_PENGUIN)
6846 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6848 boolean first_horiz = RND(2);
6849 int new_move_dir = MovDir[x][y];
6852 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6853 Moving2Blocked(x, y, &newx, &newy);
6855 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6859 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6860 Moving2Blocked(x, y, &newx, &newy);
6862 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6865 MovDir[x][y] = old_move_dir;
6869 else if (element == EL_SATELLITE)
6875 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6877 boolean first_horiz = RND(2);
6878 int new_move_dir = MovDir[x][y];
6881 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6882 Moving2Blocked(x, y, &newx, &newy);
6884 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6888 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6889 Moving2Blocked(x, y, &newx, &newy);
6891 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6894 MovDir[x][y] = old_move_dir;
6898 else if (element == EL_EMC_ANDROID)
6900 static int check_pos[16] =
6902 -1, /* 0 => (invalid) */
6903 7, /* 1 => MV_LEFT */
6904 3, /* 2 => MV_RIGHT */
6905 -1, /* 3 => (invalid) */
6907 0, /* 5 => MV_LEFT | MV_UP */
6908 2, /* 6 => MV_RIGHT | MV_UP */
6909 -1, /* 7 => (invalid) */
6910 5, /* 8 => MV_DOWN */
6911 6, /* 9 => MV_LEFT | MV_DOWN */
6912 4, /* 10 => MV_RIGHT | MV_DOWN */
6913 -1, /* 11 => (invalid) */
6914 -1, /* 12 => (invalid) */
6915 -1, /* 13 => (invalid) */
6916 -1, /* 14 => (invalid) */
6917 -1, /* 15 => (invalid) */
6925 { -1, -1, MV_LEFT | MV_UP },
6927 { +1, -1, MV_RIGHT | MV_UP },
6928 { +1, 0, MV_RIGHT },
6929 { +1, +1, MV_RIGHT | MV_DOWN },
6931 { -1, +1, MV_LEFT | MV_DOWN },
6934 int start_pos, check_order;
6935 boolean can_clone = FALSE;
6938 /* check if there is any free field around current position */
6939 for (i = 0; i < 8; i++)
6941 int newx = x + check_xy[i].dx;
6942 int newy = y + check_xy[i].dy;
6944 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
6952 if (can_clone) /* randomly find an element to clone */
6956 start_pos = check_pos[RND(8)];
6957 check_order = (RND(2) ? -1 : +1);
6959 for (i = 0; i < 8; i++)
6961 int pos_raw = start_pos + i * check_order;
6962 int pos = (pos_raw + 8) % 8;
6963 int newx = x + check_xy[pos].dx;
6964 int newy = y + check_xy[pos].dy;
6966 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
6968 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
6969 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
6971 Store[x][y] = Feld[newx][newy];
6980 if (can_clone) /* randomly find a direction to move */
6984 start_pos = check_pos[RND(8)];
6985 check_order = (RND(2) ? -1 : +1);
6987 for (i = 0; i < 8; i++)
6989 int pos_raw = start_pos + i * check_order;
6990 int pos = (pos_raw + 8) % 8;
6991 int newx = x + check_xy[pos].dx;
6992 int newy = y + check_xy[pos].dy;
6993 int new_move_dir = check_xy[pos].dir;
6995 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
6997 MovDir[x][y] = new_move_dir;
6998 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7007 if (can_clone) /* cloning and moving successful */
7010 /* cannot clone -- try to move towards player */
7012 start_pos = check_pos[MovDir[x][y] & 0x0f];
7013 check_order = (RND(2) ? -1 : +1);
7015 for (i = 0; i < 3; i++)
7017 /* first check start_pos, then previous/next or (next/previous) pos */
7018 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7019 int pos = (pos_raw + 8) % 8;
7020 int newx = x + check_xy[pos].dx;
7021 int newy = y + check_xy[pos].dy;
7022 int new_move_dir = check_xy[pos].dir;
7024 if (IS_PLAYER(newx, newy))
7027 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7029 MovDir[x][y] = new_move_dir;
7030 MovDelay[x][y] = level.android_move_time * 8 + 1;
7037 else if (move_pattern == MV_TURNING_LEFT ||
7038 move_pattern == MV_TURNING_RIGHT ||
7039 move_pattern == MV_TURNING_LEFT_RIGHT ||
7040 move_pattern == MV_TURNING_RIGHT_LEFT ||
7041 move_pattern == MV_TURNING_RANDOM ||
7042 move_pattern == MV_ALL_DIRECTIONS)
7044 boolean can_turn_left =
7045 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7046 boolean can_turn_right =
7047 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7049 if (element_info[element].move_stepsize == 0) /* "not moving" */
7052 if (move_pattern == MV_TURNING_LEFT)
7053 MovDir[x][y] = left_dir;
7054 else if (move_pattern == MV_TURNING_RIGHT)
7055 MovDir[x][y] = right_dir;
7056 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7057 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7058 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7059 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7060 else if (move_pattern == MV_TURNING_RANDOM)
7061 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7062 can_turn_right && !can_turn_left ? right_dir :
7063 RND(2) ? left_dir : right_dir);
7064 else if (can_turn_left && can_turn_right)
7065 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7066 else if (can_turn_left)
7067 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7068 else if (can_turn_right)
7069 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7071 MovDir[x][y] = back_dir;
7073 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7075 else if (move_pattern == MV_HORIZONTAL ||
7076 move_pattern == MV_VERTICAL)
7078 if (move_pattern & old_move_dir)
7079 MovDir[x][y] = back_dir;
7080 else if (move_pattern == MV_HORIZONTAL)
7081 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7082 else if (move_pattern == MV_VERTICAL)
7083 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7085 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7087 else if (move_pattern & MV_ANY_DIRECTION)
7089 MovDir[x][y] = move_pattern;
7090 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7092 else if (move_pattern & MV_WIND_DIRECTION)
7094 MovDir[x][y] = game.wind_direction;
7095 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7097 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7099 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7100 MovDir[x][y] = left_dir;
7101 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7102 MovDir[x][y] = right_dir;
7104 if (MovDir[x][y] != old_move_dir)
7105 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7107 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7109 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7110 MovDir[x][y] = right_dir;
7111 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7112 MovDir[x][y] = left_dir;
7114 if (MovDir[x][y] != old_move_dir)
7115 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7117 else if (move_pattern == MV_TOWARDS_PLAYER ||
7118 move_pattern == MV_AWAY_FROM_PLAYER)
7120 int attr_x = -1, attr_y = -1;
7122 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7133 for (i = 0; i < MAX_PLAYERS; i++)
7135 struct PlayerInfo *player = &stored_player[i];
7136 int jx = player->jx, jy = player->jy;
7138 if (!player->active)
7142 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7150 MovDir[x][y] = MV_NONE;
7152 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7153 else if (attr_x > x)
7154 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7156 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7157 else if (attr_y > y)
7158 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7160 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7162 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7164 boolean first_horiz = RND(2);
7165 int new_move_dir = MovDir[x][y];
7167 if (element_info[element].move_stepsize == 0) /* "not moving" */
7169 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7170 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7176 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7177 Moving2Blocked(x, y, &newx, &newy);
7179 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7183 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7184 Moving2Blocked(x, y, &newx, &newy);
7186 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7189 MovDir[x][y] = old_move_dir;
7192 else if (move_pattern == MV_WHEN_PUSHED ||
7193 move_pattern == MV_WHEN_DROPPED)
7195 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7196 MovDir[x][y] = MV_NONE;
7200 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7202 static int test_xy[7][2] =
7212 static int test_dir[7] =
7222 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7223 int move_preference = -1000000; /* start with very low preference */
7224 int new_move_dir = MV_NONE;
7225 int start_test = RND(4);
7228 for (i = 0; i < NUM_DIRECTIONS; i++)
7230 int move_dir = test_dir[start_test + i];
7231 int move_dir_preference;
7233 xx = x + test_xy[start_test + i][0];
7234 yy = y + test_xy[start_test + i][1];
7236 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7237 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7239 new_move_dir = move_dir;
7244 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7247 move_dir_preference = -1 * RunnerVisit[xx][yy];
7248 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7249 move_dir_preference = PlayerVisit[xx][yy];
7251 if (move_dir_preference > move_preference)
7253 /* prefer field that has not been visited for the longest time */
7254 move_preference = move_dir_preference;
7255 new_move_dir = move_dir;
7257 else if (move_dir_preference == move_preference &&
7258 move_dir == old_move_dir)
7260 /* prefer last direction when all directions are preferred equally */
7261 move_preference = move_dir_preference;
7262 new_move_dir = move_dir;
7266 MovDir[x][y] = new_move_dir;
7267 if (old_move_dir != new_move_dir)
7268 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7272 static void TurnRound(int x, int y)
7274 int direction = MovDir[x][y];
7278 GfxDir[x][y] = MovDir[x][y];
7280 if (direction != MovDir[x][y])
7284 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7286 ResetGfxFrame(x, y);
7289 static boolean JustBeingPushed(int x, int y)
7293 for (i = 0; i < MAX_PLAYERS; i++)
7295 struct PlayerInfo *player = &stored_player[i];
7297 if (player->active && player->is_pushing && player->MovPos)
7299 int next_jx = player->jx + (player->jx - player->last_jx);
7300 int next_jy = player->jy + (player->jy - player->last_jy);
7302 if (x == next_jx && y == next_jy)
7310 void StartMoving(int x, int y)
7312 boolean started_moving = FALSE; /* some elements can fall _and_ move */
7313 int element = Feld[x][y];
7318 if (MovDelay[x][y] == 0)
7319 GfxAction[x][y] = ACTION_DEFAULT;
7321 if (CAN_FALL(element) && y < lev_fieldy - 1)
7323 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7324 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7325 if (JustBeingPushed(x, y))
7328 if (element == EL_QUICKSAND_FULL)
7330 if (IS_FREE(x, y + 1))
7332 InitMovingField(x, y, MV_DOWN);
7333 started_moving = TRUE;
7335 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7336 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7337 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7338 Store[x][y] = EL_ROCK;
7340 Store[x][y] = EL_ROCK;
7343 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7345 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7347 if (!MovDelay[x][y])
7349 MovDelay[x][y] = TILEY + 1;
7351 ResetGfxAnimation(x, y);
7352 ResetGfxAnimation(x, y + 1);
7357 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7358 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7365 Feld[x][y] = EL_QUICKSAND_EMPTY;
7366 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7367 Store[x][y + 1] = Store[x][y];
7370 PlayLevelSoundAction(x, y, ACTION_FILLING);
7372 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7374 if (!MovDelay[x][y])
7376 MovDelay[x][y] = TILEY + 1;
7378 ResetGfxAnimation(x, y);
7379 ResetGfxAnimation(x, y + 1);
7384 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7385 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7392 Feld[x][y] = EL_QUICKSAND_EMPTY;
7393 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7394 Store[x][y + 1] = Store[x][y];
7397 PlayLevelSoundAction(x, y, ACTION_FILLING);
7400 else if (element == EL_QUICKSAND_FAST_FULL)
7402 if (IS_FREE(x, y + 1))
7404 InitMovingField(x, y, MV_DOWN);
7405 started_moving = TRUE;
7407 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7408 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7409 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7410 Store[x][y] = EL_ROCK;
7412 Store[x][y] = EL_ROCK;
7415 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7417 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7419 if (!MovDelay[x][y])
7421 MovDelay[x][y] = TILEY + 1;
7423 ResetGfxAnimation(x, y);
7424 ResetGfxAnimation(x, y + 1);
7429 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7430 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7437 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7438 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7439 Store[x][y + 1] = Store[x][y];
7442 PlayLevelSoundAction(x, y, ACTION_FILLING);
7444 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7446 if (!MovDelay[x][y])
7448 MovDelay[x][y] = TILEY + 1;
7450 ResetGfxAnimation(x, y);
7451 ResetGfxAnimation(x, y + 1);
7456 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7457 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7464 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7465 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7466 Store[x][y + 1] = Store[x][y];
7469 PlayLevelSoundAction(x, y, ACTION_FILLING);
7472 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7473 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7475 InitMovingField(x, y, MV_DOWN);
7476 started_moving = TRUE;
7478 Feld[x][y] = EL_QUICKSAND_FILLING;
7479 Store[x][y] = element;
7481 PlayLevelSoundAction(x, y, ACTION_FILLING);
7483 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7484 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7486 InitMovingField(x, y, MV_DOWN);
7487 started_moving = TRUE;
7489 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7490 Store[x][y] = element;
7492 PlayLevelSoundAction(x, y, ACTION_FILLING);
7494 else if (element == EL_MAGIC_WALL_FULL)
7496 if (IS_FREE(x, y + 1))
7498 InitMovingField(x, y, MV_DOWN);
7499 started_moving = TRUE;
7501 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7502 Store[x][y] = EL_CHANGED(Store[x][y]);
7504 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7506 if (!MovDelay[x][y])
7507 MovDelay[x][y] = TILEY / 4 + 1;
7516 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7517 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7518 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7522 else if (element == EL_BD_MAGIC_WALL_FULL)
7524 if (IS_FREE(x, y + 1))
7526 InitMovingField(x, y, MV_DOWN);
7527 started_moving = TRUE;
7529 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7530 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7532 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7534 if (!MovDelay[x][y])
7535 MovDelay[x][y] = TILEY / 4 + 1;
7544 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7545 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7546 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7550 else if (element == EL_DC_MAGIC_WALL_FULL)
7552 if (IS_FREE(x, y + 1))
7554 InitMovingField(x, y, MV_DOWN);
7555 started_moving = TRUE;
7557 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7558 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7560 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7562 if (!MovDelay[x][y])
7563 MovDelay[x][y] = TILEY / 4 + 1;
7572 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7573 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7574 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7578 else if ((CAN_PASS_MAGIC_WALL(element) &&
7579 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7580 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7581 (CAN_PASS_DC_MAGIC_WALL(element) &&
7582 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7585 InitMovingField(x, y, MV_DOWN);
7586 started_moving = TRUE;
7589 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7590 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7591 EL_DC_MAGIC_WALL_FILLING);
7592 Store[x][y] = element;
7594 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7596 SplashAcid(x, y + 1);
7598 InitMovingField(x, y, MV_DOWN);
7599 started_moving = TRUE;
7601 Store[x][y] = EL_ACID;
7604 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7605 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7606 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7607 CAN_FALL(element) && WasJustFalling[x][y] &&
7608 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7610 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7611 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7612 (Feld[x][y + 1] == EL_BLOCKED)))
7614 /* this is needed for a special case not covered by calling "Impact()"
7615 from "ContinueMoving()": if an element moves to a tile directly below
7616 another element which was just falling on that tile (which was empty
7617 in the previous frame), the falling element above would just stop
7618 instead of smashing the element below (in previous version, the above
7619 element was just checked for "moving" instead of "falling", resulting
7620 in incorrect smashes caused by horizontal movement of the above
7621 element; also, the case of the player being the element to smash was
7622 simply not covered here... :-/ ) */
7624 CheckCollision[x][y] = 0;
7625 CheckImpact[x][y] = 0;
7629 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7631 if (MovDir[x][y] == MV_NONE)
7633 InitMovingField(x, y, MV_DOWN);
7634 started_moving = TRUE;
7637 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7639 if (WasJustFalling[x][y]) /* prevent animation from being restarted */
7640 MovDir[x][y] = MV_DOWN;
7642 InitMovingField(x, y, MV_DOWN);
7643 started_moving = TRUE;
7645 else if (element == EL_AMOEBA_DROP)
7647 Feld[x][y] = EL_AMOEBA_GROWING;
7648 Store[x][y] = EL_AMOEBA_WET;
7650 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7651 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7652 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7653 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7655 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7656 (IS_FREE(x - 1, y + 1) ||
7657 Feld[x - 1][y + 1] == EL_ACID));
7658 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7659 (IS_FREE(x + 1, y + 1) ||
7660 Feld[x + 1][y + 1] == EL_ACID));
7661 boolean can_fall_any = (can_fall_left || can_fall_right);
7662 boolean can_fall_both = (can_fall_left && can_fall_right);
7663 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7665 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7667 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7668 can_fall_right = FALSE;
7669 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7670 can_fall_left = FALSE;
7671 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7672 can_fall_right = FALSE;
7673 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7674 can_fall_left = FALSE;
7676 can_fall_any = (can_fall_left || can_fall_right);
7677 can_fall_both = FALSE;
7682 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7683 can_fall_right = FALSE; /* slip down on left side */
7685 can_fall_left = !(can_fall_right = RND(2));
7687 can_fall_both = FALSE;
7692 /* if not determined otherwise, prefer left side for slipping down */
7693 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7694 started_moving = TRUE;
7697 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7699 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7700 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7701 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7702 int belt_dir = game.belt_dir[belt_nr];
7704 if ((belt_dir == MV_LEFT && left_is_free) ||
7705 (belt_dir == MV_RIGHT && right_is_free))
7707 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7709 InitMovingField(x, y, belt_dir);
7710 started_moving = TRUE;
7712 Pushed[x][y] = TRUE;
7713 Pushed[nextx][y] = TRUE;
7715 GfxAction[x][y] = ACTION_DEFAULT;
7719 MovDir[x][y] = 0; /* if element was moving, stop it */
7724 /* not "else if" because of elements that can fall and move (EL_SPRING) */
7725 if (CAN_MOVE(element) && !started_moving)
7727 int move_pattern = element_info[element].move_pattern;
7730 Moving2Blocked(x, y, &newx, &newy);
7732 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7735 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7736 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7738 WasJustMoving[x][y] = 0;
7739 CheckCollision[x][y] = 0;
7741 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7743 if (Feld[x][y] != element) /* element has changed */
7747 if (!MovDelay[x][y]) /* start new movement phase */
7749 /* all objects that can change their move direction after each step
7750 (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall */
7752 if (element != EL_YAMYAM &&
7753 element != EL_DARK_YAMYAM &&
7754 element != EL_PACMAN &&
7755 !(move_pattern & MV_ANY_DIRECTION) &&
7756 move_pattern != MV_TURNING_LEFT &&
7757 move_pattern != MV_TURNING_RIGHT &&
7758 move_pattern != MV_TURNING_LEFT_RIGHT &&
7759 move_pattern != MV_TURNING_RIGHT_LEFT &&
7760 move_pattern != MV_TURNING_RANDOM)
7764 if (MovDelay[x][y] && (element == EL_BUG ||
7765 element == EL_SPACESHIP ||
7766 element == EL_SP_SNIKSNAK ||
7767 element == EL_SP_ELECTRON ||
7768 element == EL_MOLE))
7769 TEST_DrawLevelField(x, y);
7773 if (MovDelay[x][y]) /* wait some time before next movement */
7777 if (element == EL_ROBOT ||
7778 element == EL_YAMYAM ||
7779 element == EL_DARK_YAMYAM)
7781 DrawLevelElementAnimationIfNeeded(x, y, element);
7782 PlayLevelSoundAction(x, y, ACTION_WAITING);
7784 else if (element == EL_SP_ELECTRON)
7785 DrawLevelElementAnimationIfNeeded(x, y, element);
7786 else if (element == EL_DRAGON)
7789 int dir = MovDir[x][y];
7790 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
7791 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
7792 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
7793 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
7794 dir == MV_UP ? IMG_FLAMES_1_UP :
7795 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
7796 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
7798 GfxAction[x][y] = ACTION_ATTACKING;
7800 if (IS_PLAYER(x, y))
7801 DrawPlayerField(x, y);
7803 TEST_DrawLevelField(x, y);
7805 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
7807 for (i = 1; i <= 3; i++)
7809 int xx = x + i * dx;
7810 int yy = y + i * dy;
7811 int sx = SCREENX(xx);
7812 int sy = SCREENY(yy);
7813 int flame_graphic = graphic + (i - 1);
7815 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
7820 int flamed = MovingOrBlocked2Element(xx, yy);
7822 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
7825 RemoveMovingField(xx, yy);
7827 ChangeDelay[xx][yy] = 0;
7829 Feld[xx][yy] = EL_FLAMES;
7831 if (IN_SCR_FIELD(sx, sy))
7833 TEST_DrawLevelFieldCrumbled(xx, yy);
7834 DrawGraphic(sx, sy, flame_graphic, frame);
7839 if (Feld[xx][yy] == EL_FLAMES)
7840 Feld[xx][yy] = EL_EMPTY;
7841 TEST_DrawLevelField(xx, yy);
7846 if (MovDelay[x][y]) /* element still has to wait some time */
7848 PlayLevelSoundAction(x, y, ACTION_WAITING);
7854 /* now make next step */
7856 Moving2Blocked(x, y, &newx, &newy); /* get next screen position */
7858 if (DONT_COLLIDE_WITH(element) &&
7859 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
7860 !PLAYER_ENEMY_PROTECTED(newx, newy))
7862 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
7867 else if (CAN_MOVE_INTO_ACID(element) &&
7868 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
7869 !IS_MV_DIAGONAL(MovDir[x][y]) &&
7870 (MovDir[x][y] == MV_DOWN ||
7871 game.engine_version >= VERSION_IDENT(3,1,0,0)))
7873 SplashAcid(newx, newy);
7874 Store[x][y] = EL_ACID;
7876 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
7878 if (Feld[newx][newy] == EL_EXIT_OPEN ||
7879 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
7880 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
7881 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
7884 TEST_DrawLevelField(x, y);
7886 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
7887 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
7888 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
7890 local_player->friends_still_needed--;
7891 if (!local_player->friends_still_needed &&
7892 !local_player->GameOver && AllPlayersGone)
7893 PlayerWins(local_player);
7897 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
7899 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
7900 TEST_DrawLevelField(newx, newy);
7902 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
7904 else if (!IS_FREE(newx, newy))
7906 GfxAction[x][y] = ACTION_WAITING;
7908 if (IS_PLAYER(x, y))
7909 DrawPlayerField(x, y);
7911 TEST_DrawLevelField(x, y);
7916 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
7918 if (IS_FOOD_PIG(Feld[newx][newy]))
7920 if (IS_MOVING(newx, newy))
7921 RemoveMovingField(newx, newy);
7924 Feld[newx][newy] = EL_EMPTY;
7925 TEST_DrawLevelField(newx, newy);
7928 PlayLevelSound(x, y, SND_PIG_DIGGING);
7930 else if (!IS_FREE(newx, newy))
7932 if (IS_PLAYER(x, y))
7933 DrawPlayerField(x, y);
7935 TEST_DrawLevelField(x, y);
7940 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
7942 if (Store[x][y] != EL_EMPTY)
7944 boolean can_clone = FALSE;
7947 /* check if element to clone is still there */
7948 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
7950 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
7958 /* cannot clone or target field not free anymore -- do not clone */
7959 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7960 Store[x][y] = EL_EMPTY;
7963 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7965 if (IS_MV_DIAGONAL(MovDir[x][y]))
7967 int diagonal_move_dir = MovDir[x][y];
7968 int stored = Store[x][y];
7969 int change_delay = 8;
7972 /* android is moving diagonally */
7974 CreateField(x, y, EL_DIAGONAL_SHRINKING);
7976 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
7977 GfxElement[x][y] = EL_EMC_ANDROID;
7978 GfxAction[x][y] = ACTION_SHRINKING;
7979 GfxDir[x][y] = diagonal_move_dir;
7980 ChangeDelay[x][y] = change_delay;
7982 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
7985 DrawLevelGraphicAnimation(x, y, graphic);
7986 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
7988 if (Feld[newx][newy] == EL_ACID)
7990 SplashAcid(newx, newy);
7995 CreateField(newx, newy, EL_DIAGONAL_GROWING);
7997 Store[newx][newy] = EL_EMC_ANDROID;
7998 GfxElement[newx][newy] = EL_EMC_ANDROID;
7999 GfxAction[newx][newy] = ACTION_GROWING;
8000 GfxDir[newx][newy] = diagonal_move_dir;
8001 ChangeDelay[newx][newy] = change_delay;
8003 graphic = el_act_dir2img(GfxElement[newx][newy],
8004 GfxAction[newx][newy], GfxDir[newx][newy]);
8006 DrawLevelGraphicAnimation(newx, newy, graphic);
8007 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8013 Feld[newx][newy] = EL_EMPTY;
8014 TEST_DrawLevelField(newx, newy);
8016 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8019 else if (!IS_FREE(newx, newy))
8024 else if (IS_CUSTOM_ELEMENT(element) &&
8025 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8027 if (!DigFieldByCE(newx, newy, element))
8030 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8032 RunnerVisit[x][y] = FrameCounter;
8033 PlayerVisit[x][y] /= 8; /* expire player visit path */
8036 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8038 if (!IS_FREE(newx, newy))
8040 if (IS_PLAYER(x, y))
8041 DrawPlayerField(x, y);
8043 TEST_DrawLevelField(x, y);
8049 boolean wanna_flame = !RND(10);
8050 int dx = newx - x, dy = newy - y;
8051 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8052 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8053 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8054 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8055 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8056 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8059 IS_CLASSIC_ENEMY(element1) ||
8060 IS_CLASSIC_ENEMY(element2)) &&
8061 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8062 element1 != EL_FLAMES && element2 != EL_FLAMES)
8064 ResetGfxAnimation(x, y);
8065 GfxAction[x][y] = ACTION_ATTACKING;
8067 if (IS_PLAYER(x, y))
8068 DrawPlayerField(x, y);
8070 TEST_DrawLevelField(x, y);
8072 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8074 MovDelay[x][y] = 50;
8076 Feld[newx][newy] = EL_FLAMES;
8077 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8078 Feld[newx1][newy1] = EL_FLAMES;
8079 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8080 Feld[newx2][newy2] = EL_FLAMES;
8086 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8087 Feld[newx][newy] == EL_DIAMOND)
8089 if (IS_MOVING(newx, newy))
8090 RemoveMovingField(newx, newy);
8093 Feld[newx][newy] = EL_EMPTY;
8094 TEST_DrawLevelField(newx, newy);
8097 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8099 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8100 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8102 if (AmoebaNr[newx][newy])
8104 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8105 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8106 Feld[newx][newy] == EL_BD_AMOEBA)
8107 AmoebaCnt[AmoebaNr[newx][newy]]--;
8110 if (IS_MOVING(newx, newy))
8112 RemoveMovingField(newx, newy);
8116 Feld[newx][newy] = EL_EMPTY;
8117 TEST_DrawLevelField(newx, newy);
8120 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8122 else if ((element == EL_PACMAN || element == EL_MOLE)
8123 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8125 if (AmoebaNr[newx][newy])
8127 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8128 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8129 Feld[newx][newy] == EL_BD_AMOEBA)
8130 AmoebaCnt[AmoebaNr[newx][newy]]--;
8133 if (element == EL_MOLE)
8135 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8136 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8138 ResetGfxAnimation(x, y);
8139 GfxAction[x][y] = ACTION_DIGGING;
8140 TEST_DrawLevelField(x, y);
8142 MovDelay[newx][newy] = 0; /* start amoeba shrinking delay */
8144 return; /* wait for shrinking amoeba */
8146 else /* element == EL_PACMAN */
8148 Feld[newx][newy] = EL_EMPTY;
8149 TEST_DrawLevelField(newx, newy);
8150 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8153 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8154 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8155 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8157 /* wait for shrinking amoeba to completely disappear */
8160 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8162 /* object was running against a wall */
8166 if (GFX_ELEMENT(element) != EL_SAND) /* !!! FIX THIS (crumble) !!! */
8167 DrawLevelElementAnimation(x, y, element);
8169 if (DONT_TOUCH(element))
8170 TestIfBadThingTouchesPlayer(x, y);
8175 InitMovingField(x, y, MovDir[x][y]);
8177 PlayLevelSoundAction(x, y, ACTION_MOVING);
8181 ContinueMoving(x, y);
8184 void ContinueMoving(int x, int y)
8186 int element = Feld[x][y];
8187 struct ElementInfo *ei = &element_info[element];
8188 int direction = MovDir[x][y];
8189 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8190 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8191 int newx = x + dx, newy = y + dy;
8192 int stored = Store[x][y];
8193 int stored_new = Store[newx][newy];
8194 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8195 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8196 boolean last_line = (newy == lev_fieldy - 1);
8198 MovPos[x][y] += getElementMoveStepsize(x, y);
8200 if (pushed_by_player) /* special case: moving object pushed by player */
8201 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8203 if (ABS(MovPos[x][y]) < TILEX)
8205 TEST_DrawLevelField(x, y);
8207 return; /* element is still moving */
8210 /* element reached destination field */
8212 Feld[x][y] = EL_EMPTY;
8213 Feld[newx][newy] = element;
8214 MovPos[x][y] = 0; /* force "not moving" for "crumbled sand" */
8216 if (Store[x][y] == EL_ACID) /* element is moving into acid pool */
8218 element = Feld[newx][newy] = EL_ACID;
8220 else if (element == EL_MOLE)
8222 Feld[x][y] = EL_SAND;
8224 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8226 else if (element == EL_QUICKSAND_FILLING)
8228 element = Feld[newx][newy] = get_next_element(element);
8229 Store[newx][newy] = Store[x][y];
8231 else if (element == EL_QUICKSAND_EMPTYING)
8233 Feld[x][y] = get_next_element(element);
8234 element = Feld[newx][newy] = Store[x][y];
8236 else if (element == EL_QUICKSAND_FAST_FILLING)
8238 element = Feld[newx][newy] = get_next_element(element);
8239 Store[newx][newy] = Store[x][y];
8241 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8243 Feld[x][y] = get_next_element(element);
8244 element = Feld[newx][newy] = Store[x][y];
8246 else if (element == EL_MAGIC_WALL_FILLING)
8248 element = Feld[newx][newy] = get_next_element(element);
8249 if (!game.magic_wall_active)
8250 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8251 Store[newx][newy] = Store[x][y];
8253 else if (element == EL_MAGIC_WALL_EMPTYING)
8255 Feld[x][y] = get_next_element(element);
8256 if (!game.magic_wall_active)
8257 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8258 element = Feld[newx][newy] = Store[x][y];
8260 InitField(newx, newy, FALSE);
8262 else if (element == EL_BD_MAGIC_WALL_FILLING)
8264 element = Feld[newx][newy] = get_next_element(element);
8265 if (!game.magic_wall_active)
8266 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8267 Store[newx][newy] = Store[x][y];
8269 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8271 Feld[x][y] = get_next_element(element);
8272 if (!game.magic_wall_active)
8273 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8274 element = Feld[newx][newy] = Store[x][y];
8276 InitField(newx, newy, FALSE);
8278 else if (element == EL_DC_MAGIC_WALL_FILLING)
8280 element = Feld[newx][newy] = get_next_element(element);
8281 if (!game.magic_wall_active)
8282 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8283 Store[newx][newy] = Store[x][y];
8285 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8287 Feld[x][y] = get_next_element(element);
8288 if (!game.magic_wall_active)
8289 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8290 element = Feld[newx][newy] = Store[x][y];
8292 InitField(newx, newy, FALSE);
8294 else if (element == EL_AMOEBA_DROPPING)
8296 Feld[x][y] = get_next_element(element);
8297 element = Feld[newx][newy] = Store[x][y];
8299 else if (element == EL_SOKOBAN_OBJECT)
8302 Feld[x][y] = Back[x][y];
8304 if (Back[newx][newy])
8305 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8307 Back[x][y] = Back[newx][newy] = 0;
8310 Store[x][y] = EL_EMPTY;
8315 MovDelay[newx][newy] = 0;
8317 if (CAN_CHANGE_OR_HAS_ACTION(element))
8319 /* copy element change control values to new field */
8320 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8321 ChangePage[newx][newy] = ChangePage[x][y];
8322 ChangeCount[newx][newy] = ChangeCount[x][y];
8323 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8326 CustomValue[newx][newy] = CustomValue[x][y];
8328 ChangeDelay[x][y] = 0;
8329 ChangePage[x][y] = -1;
8330 ChangeCount[x][y] = 0;
8331 ChangeEvent[x][y] = -1;
8333 CustomValue[x][y] = 0;
8335 /* copy animation control values to new field */
8336 GfxFrame[newx][newy] = GfxFrame[x][y];
8337 GfxRandom[newx][newy] = GfxRandom[x][y]; /* keep same random value */
8338 GfxAction[newx][newy] = GfxAction[x][y]; /* keep action one frame */
8339 GfxDir[newx][newy] = GfxDir[x][y]; /* keep element direction */
8341 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8343 /* some elements can leave other elements behind after moving */
8344 if (ei->move_leave_element != EL_EMPTY &&
8345 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8346 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8348 int move_leave_element = ei->move_leave_element;
8350 /* this makes it possible to leave the removed element again */
8351 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8352 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8354 Feld[x][y] = move_leave_element;
8356 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8357 MovDir[x][y] = direction;
8359 InitField(x, y, FALSE);
8361 if (GFX_CRUMBLED(Feld[x][y]))
8362 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8364 if (ELEM_IS_PLAYER(move_leave_element))
8365 RelocatePlayer(x, y, move_leave_element);
8368 /* do this after checking for left-behind element */
8369 ResetGfxAnimation(x, y); /* reset animation values for old field */
8371 if (!CAN_MOVE(element) ||
8372 (CAN_FALL(element) && direction == MV_DOWN &&
8373 (element == EL_SPRING ||
8374 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8375 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8376 GfxDir[x][y] = MovDir[newx][newy] = 0;
8378 TEST_DrawLevelField(x, y);
8379 TEST_DrawLevelField(newx, newy);
8381 Stop[newx][newy] = TRUE; /* ignore this element until the next frame */
8383 /* prevent pushed element from moving on in pushed direction */
8384 if (pushed_by_player && CAN_MOVE(element) &&
8385 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8386 !(element_info[element].move_pattern & direction))
8387 TurnRound(newx, newy);
8389 /* prevent elements on conveyor belt from moving on in last direction */
8390 if (pushed_by_conveyor && CAN_FALL(element) &&
8391 direction & MV_HORIZONTAL)
8392 MovDir[newx][newy] = 0;
8394 if (!pushed_by_player)
8396 int nextx = newx + dx, nexty = newy + dy;
8397 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8399 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8401 if (CAN_FALL(element) && direction == MV_DOWN)
8402 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8404 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8405 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8407 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8408 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8411 if (DONT_TOUCH(element)) /* object may be nasty to player or others */
8413 TestIfBadThingTouchesPlayer(newx, newy);
8414 TestIfBadThingTouchesFriend(newx, newy);
8416 if (!IS_CUSTOM_ELEMENT(element))
8417 TestIfBadThingTouchesOtherBadThing(newx, newy);
8419 else if (element == EL_PENGUIN)
8420 TestIfFriendTouchesBadThing(newx, newy);
8422 if (DONT_GET_HIT_BY(element))
8424 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8427 /* give the player one last chance (one more frame) to move away */
8428 if (CAN_FALL(element) && direction == MV_DOWN &&
8429 (last_line || (!IS_FREE(x, newy + 1) &&
8430 (!IS_PLAYER(x, newy + 1) ||
8431 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8434 if (pushed_by_player && !game.use_change_when_pushing_bug)
8436 int push_side = MV_DIR_OPPOSITE(direction);
8437 struct PlayerInfo *player = PLAYERINFO(x, y);
8439 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8440 player->index_bit, push_side);
8441 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8442 player->index_bit, push_side);
8445 if (element == EL_EMC_ANDROID && pushed_by_player) /* make another move */
8446 MovDelay[newx][newy] = 1;
8448 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8450 TestIfElementTouchesCustomElement(x, y); /* empty or new element */
8451 TestIfElementHitsCustomElement(newx, newy, direction);
8452 TestIfPlayerTouchesCustomElement(newx, newy);
8453 TestIfElementTouchesCustomElement(newx, newy);
8455 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8456 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8457 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8458 MV_DIR_OPPOSITE(direction));
8461 int AmoebeNachbarNr(int ax, int ay)
8464 int element = Feld[ax][ay];
8466 static int xy[4][2] =
8474 for (i = 0; i < NUM_DIRECTIONS; i++)
8476 int x = ax + xy[i][0];
8477 int y = ay + xy[i][1];
8479 if (!IN_LEV_FIELD(x, y))
8482 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8483 group_nr = AmoebaNr[x][y];
8489 void AmoebenVereinigen(int ax, int ay)
8491 int i, x, y, xx, yy;
8492 int new_group_nr = AmoebaNr[ax][ay];
8493 static int xy[4][2] =
8501 if (new_group_nr == 0)
8504 for (i = 0; i < NUM_DIRECTIONS; i++)
8509 if (!IN_LEV_FIELD(x, y))
8512 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8513 Feld[x][y] == EL_BD_AMOEBA ||
8514 Feld[x][y] == EL_AMOEBA_DEAD) &&
8515 AmoebaNr[x][y] != new_group_nr)
8517 int old_group_nr = AmoebaNr[x][y];
8519 if (old_group_nr == 0)
8522 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8523 AmoebaCnt[old_group_nr] = 0;
8524 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8525 AmoebaCnt2[old_group_nr] = 0;
8527 SCAN_PLAYFIELD(xx, yy)
8529 if (AmoebaNr[xx][yy] == old_group_nr)
8530 AmoebaNr[xx][yy] = new_group_nr;
8536 void AmoebeUmwandeln(int ax, int ay)
8540 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8542 int group_nr = AmoebaNr[ax][ay];
8547 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8548 printf("AmoebeUmwandeln(): This should never happen!\n");
8553 SCAN_PLAYFIELD(x, y)
8555 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8558 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8562 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8563 SND_AMOEBA_TURNING_TO_GEM :
8564 SND_AMOEBA_TURNING_TO_ROCK));
8569 static int xy[4][2] =
8577 for (i = 0; i < NUM_DIRECTIONS; i++)
8582 if (!IN_LEV_FIELD(x, y))
8585 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8587 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8588 SND_AMOEBA_TURNING_TO_GEM :
8589 SND_AMOEBA_TURNING_TO_ROCK));
8596 void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8599 int group_nr = AmoebaNr[ax][ay];
8600 boolean done = FALSE;
8605 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8606 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8611 SCAN_PLAYFIELD(x, y)
8613 if (AmoebaNr[x][y] == group_nr &&
8614 (Feld[x][y] == EL_AMOEBA_DEAD ||
8615 Feld[x][y] == EL_BD_AMOEBA ||
8616 Feld[x][y] == EL_AMOEBA_GROWING))
8619 Feld[x][y] = new_element;
8620 InitField(x, y, FALSE);
8621 TEST_DrawLevelField(x, y);
8627 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8628 SND_BD_AMOEBA_TURNING_TO_ROCK :
8629 SND_BD_AMOEBA_TURNING_TO_GEM));
8632 void AmoebeWaechst(int x, int y)
8634 static unsigned int sound_delay = 0;
8635 static unsigned int sound_delay_value = 0;
8637 if (!MovDelay[x][y]) /* start new growing cycle */
8641 if (DelayReached(&sound_delay, sound_delay_value))
8643 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8644 sound_delay_value = 30;
8648 if (MovDelay[x][y]) /* wait some time before growing bigger */
8651 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8653 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8654 6 - MovDelay[x][y]);
8656 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8659 if (!MovDelay[x][y])
8661 Feld[x][y] = Store[x][y];
8663 TEST_DrawLevelField(x, y);
8668 void AmoebaDisappearing(int x, int y)
8670 static unsigned int sound_delay = 0;
8671 static unsigned int sound_delay_value = 0;
8673 if (!MovDelay[x][y]) /* start new shrinking cycle */
8677 if (DelayReached(&sound_delay, sound_delay_value))
8678 sound_delay_value = 30;
8681 if (MovDelay[x][y]) /* wait some time before shrinking */
8684 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8686 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8687 6 - MovDelay[x][y]);
8689 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8692 if (!MovDelay[x][y])
8694 Feld[x][y] = EL_EMPTY;
8695 TEST_DrawLevelField(x, y);
8697 /* don't let mole enter this field in this cycle;
8698 (give priority to objects falling to this field from above) */
8704 void AmoebeAbleger(int ax, int ay)
8707 int element = Feld[ax][ay];
8708 int graphic = el2img(element);
8709 int newax = ax, neway = ay;
8710 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8711 static int xy[4][2] =
8719 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8721 Feld[ax][ay] = EL_AMOEBA_DEAD;
8722 TEST_DrawLevelField(ax, ay);
8726 if (IS_ANIMATED(graphic))
8727 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8729 if (!MovDelay[ax][ay]) /* start making new amoeba field */
8730 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8732 if (MovDelay[ax][ay]) /* wait some time before making new amoeba */
8735 if (MovDelay[ax][ay])
8739 if (can_drop) /* EL_AMOEBA_WET or EL_EMC_DRIPPER */
8742 int x = ax + xy[start][0];
8743 int y = ay + xy[start][1];
8745 if (!IN_LEV_FIELD(x, y))
8748 if (IS_FREE(x, y) ||
8749 CAN_GROW_INTO(Feld[x][y]) ||
8750 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8751 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8757 if (newax == ax && neway == ay)
8760 else /* normal or "filled" (BD style) amoeba */
8763 boolean waiting_for_player = FALSE;
8765 for (i = 0; i < NUM_DIRECTIONS; i++)
8767 int j = (start + i) % 4;
8768 int x = ax + xy[j][0];
8769 int y = ay + xy[j][1];
8771 if (!IN_LEV_FIELD(x, y))
8774 if (IS_FREE(x, y) ||
8775 CAN_GROW_INTO(Feld[x][y]) ||
8776 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8777 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8783 else if (IS_PLAYER(x, y))
8784 waiting_for_player = TRUE;
8787 if (newax == ax && neway == ay) /* amoeba cannot grow */
8789 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
8791 Feld[ax][ay] = EL_AMOEBA_DEAD;
8792 TEST_DrawLevelField(ax, ay);
8793 AmoebaCnt[AmoebaNr[ax][ay]]--;
8795 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) /* amoeba is completely dead */
8797 if (element == EL_AMOEBA_FULL)
8798 AmoebeUmwandeln(ax, ay);
8799 else if (element == EL_BD_AMOEBA)
8800 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
8805 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
8807 /* amoeba gets larger by growing in some direction */
8809 int new_group_nr = AmoebaNr[ax][ay];
8812 if (new_group_nr == 0)
8814 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
8815 printf("AmoebeAbleger(): This should never happen!\n");
8820 AmoebaNr[newax][neway] = new_group_nr;
8821 AmoebaCnt[new_group_nr]++;
8822 AmoebaCnt2[new_group_nr]++;
8824 /* if amoeba touches other amoeba(s) after growing, unify them */
8825 AmoebenVereinigen(newax, neway);
8827 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
8829 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
8835 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
8836 (neway == lev_fieldy - 1 && newax != ax))
8838 Feld[newax][neway] = EL_AMOEBA_GROWING; /* creation of new amoeba */
8839 Store[newax][neway] = element;
8841 else if (neway == ay || element == EL_EMC_DRIPPER)
8843 Feld[newax][neway] = EL_AMOEBA_DROP; /* drop left/right of amoeba */
8845 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
8849 InitMovingField(ax, ay, MV_DOWN); /* drop dripping from amoeba */
8850 Feld[ax][ay] = EL_AMOEBA_DROPPING;
8851 Store[ax][ay] = EL_AMOEBA_DROP;
8852 ContinueMoving(ax, ay);
8856 TEST_DrawLevelField(newax, neway);
8859 void Life(int ax, int ay)
8863 int element = Feld[ax][ay];
8864 int graphic = el2img(element);
8865 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
8867 boolean changed = FALSE;
8869 if (IS_ANIMATED(graphic))
8870 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8875 if (!MovDelay[ax][ay]) /* start new "game of life" cycle */
8876 MovDelay[ax][ay] = life_time;
8878 if (MovDelay[ax][ay]) /* wait some time before next cycle */
8881 if (MovDelay[ax][ay])
8885 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
8887 int xx = ax+x1, yy = ay+y1;
8890 if (!IN_LEV_FIELD(xx, yy))
8893 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
8895 int x = xx+x2, y = yy+y2;
8897 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
8900 if (((Feld[x][y] == element ||
8901 (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y))) &&
8903 (IS_FREE(x, y) && Stop[x][y]))
8907 if (xx == ax && yy == ay) /* field in the middle */
8909 if (nachbarn < life_parameter[0] ||
8910 nachbarn > life_parameter[1])
8912 Feld[xx][yy] = EL_EMPTY;
8914 TEST_DrawLevelField(xx, yy);
8915 Stop[xx][yy] = TRUE;
8919 else if (IS_FREE(xx, yy) || CAN_GROW_INTO(Feld[xx][yy]))
8920 { /* free border field */
8921 if (nachbarn >= life_parameter[2] &&
8922 nachbarn <= life_parameter[3])
8924 Feld[xx][yy] = element;
8925 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
8927 TEST_DrawLevelField(xx, yy);
8928 Stop[xx][yy] = TRUE;
8935 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
8936 SND_GAME_OF_LIFE_GROWING);
8939 static void InitRobotWheel(int x, int y)
8941 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
8944 static void RunRobotWheel(int x, int y)
8946 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
8949 static void StopRobotWheel(int x, int y)
8951 if (ZX == x && ZY == y)
8955 game.robot_wheel_active = FALSE;
8959 static void InitTimegateWheel(int x, int y)
8961 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
8964 static void RunTimegateWheel(int x, int y)
8966 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
8969 static void InitMagicBallDelay(int x, int y)
8971 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
8974 static void ActivateMagicBall(int bx, int by)
8978 if (level.ball_random)
8980 int pos_border = RND(8); /* select one of the eight border elements */
8981 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
8982 int xx = pos_content % 3;
8983 int yy = pos_content / 3;
8988 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
8989 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
8993 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
8995 int xx = x - bx + 1;
8996 int yy = y - by + 1;
8998 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
8999 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9003 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9006 void CheckExit(int x, int y)
9008 if (local_player->gems_still_needed > 0 ||
9009 local_player->sokobanfields_still_needed > 0 ||
9010 local_player->lights_still_needed > 0)
9012 int element = Feld[x][y];
9013 int graphic = el2img(element);
9015 if (IS_ANIMATED(graphic))
9016 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9021 if (AllPlayersGone) /* do not re-open exit door closed after last player */
9024 Feld[x][y] = EL_EXIT_OPENING;
9026 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9029 void CheckExitEM(int x, int y)
9031 if (local_player->gems_still_needed > 0 ||
9032 local_player->sokobanfields_still_needed > 0 ||
9033 local_player->lights_still_needed > 0)
9035 int element = Feld[x][y];
9036 int graphic = el2img(element);
9038 if (IS_ANIMATED(graphic))
9039 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9044 if (AllPlayersGone) /* do not re-open exit door closed after last player */
9047 Feld[x][y] = EL_EM_EXIT_OPENING;
9049 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9052 void CheckExitSteel(int x, int y)
9054 if (local_player->gems_still_needed > 0 ||
9055 local_player->sokobanfields_still_needed > 0 ||
9056 local_player->lights_still_needed > 0)
9058 int element = Feld[x][y];
9059 int graphic = el2img(element);
9061 if (IS_ANIMATED(graphic))
9062 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9067 if (AllPlayersGone) /* do not re-open exit door closed after last player */
9070 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9072 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9075 void CheckExitSteelEM(int x, int y)
9077 if (local_player->gems_still_needed > 0 ||
9078 local_player->sokobanfields_still_needed > 0 ||
9079 local_player->lights_still_needed > 0)
9081 int element = Feld[x][y];
9082 int graphic = el2img(element);
9084 if (IS_ANIMATED(graphic))
9085 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9090 if (AllPlayersGone) /* do not re-open exit door closed after last player */
9093 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9095 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9098 void CheckExitSP(int x, int y)
9100 if (local_player->gems_still_needed > 0)
9102 int element = Feld[x][y];
9103 int graphic = el2img(element);
9105 if (IS_ANIMATED(graphic))
9106 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9111 if (AllPlayersGone) /* do not re-open exit door closed after last player */
9114 Feld[x][y] = EL_SP_EXIT_OPENING;
9116 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9119 static void CloseAllOpenTimegates()
9123 SCAN_PLAYFIELD(x, y)
9125 int element = Feld[x][y];
9127 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9129 Feld[x][y] = EL_TIMEGATE_CLOSING;
9131 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9136 void DrawTwinkleOnField(int x, int y)
9138 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9141 if (Feld[x][y] == EL_BD_DIAMOND)
9144 if (MovDelay[x][y] == 0) /* next animation frame */
9145 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9147 if (MovDelay[x][y] != 0) /* wait some time before next frame */
9151 DrawLevelElementAnimation(x, y, Feld[x][y]);
9153 if (MovDelay[x][y] != 0)
9155 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9156 10 - MovDelay[x][y]);
9158 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9163 void MauerWaechst(int x, int y)
9167 if (!MovDelay[x][y]) /* next animation frame */
9168 MovDelay[x][y] = 3 * delay;
9170 if (MovDelay[x][y]) /* wait some time before next frame */
9174 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9176 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9177 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9179 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9182 if (!MovDelay[x][y])
9184 if (MovDir[x][y] == MV_LEFT)
9186 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9187 TEST_DrawLevelField(x - 1, y);
9189 else if (MovDir[x][y] == MV_RIGHT)
9191 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9192 TEST_DrawLevelField(x + 1, y);
9194 else if (MovDir[x][y] == MV_UP)
9196 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9197 TEST_DrawLevelField(x, y - 1);
9201 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9202 TEST_DrawLevelField(x, y + 1);
9205 Feld[x][y] = Store[x][y];
9207 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9208 TEST_DrawLevelField(x, y);
9213 void MauerAbleger(int ax, int ay)
9215 int element = Feld[ax][ay];
9216 int graphic = el2img(element);
9217 boolean oben_frei = FALSE, unten_frei = FALSE;
9218 boolean links_frei = FALSE, rechts_frei = FALSE;
9219 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9220 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9221 boolean new_wall = FALSE;
9223 if (IS_ANIMATED(graphic))
9224 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9226 if (!MovDelay[ax][ay]) /* start building new wall */
9227 MovDelay[ax][ay] = 6;
9229 if (MovDelay[ax][ay]) /* wait some time before building new wall */
9232 if (MovDelay[ax][ay])
9236 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9238 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9240 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9242 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9245 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9246 element == EL_EXPANDABLE_WALL_ANY)
9250 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9251 Store[ax][ay-1] = element;
9252 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9253 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9254 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9255 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9260 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9261 Store[ax][ay+1] = element;
9262 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9263 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9264 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9265 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9270 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9271 element == EL_EXPANDABLE_WALL_ANY ||
9272 element == EL_EXPANDABLE_WALL ||
9273 element == EL_BD_EXPANDABLE_WALL)
9277 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9278 Store[ax-1][ay] = element;
9279 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9280 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9281 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9282 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9288 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9289 Store[ax+1][ay] = element;
9290 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9291 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9292 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9293 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9298 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9299 TEST_DrawLevelField(ax, ay);
9301 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9303 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9304 unten_massiv = TRUE;
9305 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9306 links_massiv = TRUE;
9307 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9308 rechts_massiv = TRUE;
9310 if (((oben_massiv && unten_massiv) ||
9311 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9312 element == EL_EXPANDABLE_WALL) &&
9313 ((links_massiv && rechts_massiv) ||
9314 element == EL_EXPANDABLE_WALL_VERTICAL))
9315 Feld[ax][ay] = EL_WALL;
9318 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9321 void MauerAblegerStahl(int ax, int ay)
9323 int element = Feld[ax][ay];
9324 int graphic = el2img(element);
9325 boolean oben_frei = FALSE, unten_frei = FALSE;
9326 boolean links_frei = FALSE, rechts_frei = FALSE;
9327 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9328 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9329 boolean new_wall = FALSE;
9331 if (IS_ANIMATED(graphic))
9332 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9334 if (!MovDelay[ax][ay]) /* start building new wall */
9335 MovDelay[ax][ay] = 6;
9337 if (MovDelay[ax][ay]) /* wait some time before building new wall */
9340 if (MovDelay[ax][ay])
9344 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9346 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9348 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9350 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9353 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9354 element == EL_EXPANDABLE_STEELWALL_ANY)
9358 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9359 Store[ax][ay-1] = element;
9360 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9361 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9362 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9363 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9368 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9369 Store[ax][ay+1] = element;
9370 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9371 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9372 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9373 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9378 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9379 element == EL_EXPANDABLE_STEELWALL_ANY)
9383 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9384 Store[ax-1][ay] = element;
9385 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9386 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9387 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9388 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9394 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9395 Store[ax+1][ay] = element;
9396 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9397 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9398 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9399 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9404 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9406 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9407 unten_massiv = TRUE;
9408 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9409 links_massiv = TRUE;
9410 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9411 rechts_massiv = TRUE;
9413 if (((oben_massiv && unten_massiv) ||
9414 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9415 ((links_massiv && rechts_massiv) ||
9416 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9417 Feld[ax][ay] = EL_STEELWALL;
9420 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9423 void CheckForDragon(int x, int y)
9426 boolean dragon_found = FALSE;
9427 static int xy[4][2] =
9435 for (i = 0; i < NUM_DIRECTIONS; i++)
9437 for (j = 0; j < 4; j++)
9439 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9441 if (IN_LEV_FIELD(xx, yy) &&
9442 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9444 if (Feld[xx][yy] == EL_DRAGON)
9445 dragon_found = TRUE;
9454 for (i = 0; i < NUM_DIRECTIONS; i++)
9456 for (j = 0; j < 3; j++)
9458 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9460 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9462 Feld[xx][yy] = EL_EMPTY;
9463 TEST_DrawLevelField(xx, yy);
9472 static void InitBuggyBase(int x, int y)
9474 int element = Feld[x][y];
9475 int activating_delay = FRAMES_PER_SECOND / 4;
9478 (element == EL_SP_BUGGY_BASE ?
9479 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9480 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9482 element == EL_SP_BUGGY_BASE_ACTIVE ?
9483 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9486 static void WarnBuggyBase(int x, int y)
9489 static int xy[4][2] =
9497 for (i = 0; i < NUM_DIRECTIONS; i++)
9499 int xx = x + xy[i][0];
9500 int yy = y + xy[i][1];
9502 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9504 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9511 static void InitTrap(int x, int y)
9513 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9516 static void ActivateTrap(int x, int y)
9518 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9521 static void ChangeActiveTrap(int x, int y)
9523 int graphic = IMG_TRAP_ACTIVE;
9525 /* if new animation frame was drawn, correct crumbled sand border */
9526 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9527 TEST_DrawLevelFieldCrumbled(x, y);
9530 static int getSpecialActionElement(int element, int number, int base_element)
9532 return (element != EL_EMPTY ? element :
9533 number != -1 ? base_element + number - 1 :
9537 static int getModifiedActionNumber(int value_old, int operator, int operand,
9538 int value_min, int value_max)
9540 int value_new = (operator == CA_MODE_SET ? operand :
9541 operator == CA_MODE_ADD ? value_old + operand :
9542 operator == CA_MODE_SUBTRACT ? value_old - operand :
9543 operator == CA_MODE_MULTIPLY ? value_old * operand :
9544 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9545 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9548 return (value_new < value_min ? value_min :
9549 value_new > value_max ? value_max :
9553 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9555 struct ElementInfo *ei = &element_info[element];
9556 struct ElementChangeInfo *change = &ei->change_page[page];
9557 int target_element = change->target_element;
9558 int action_type = change->action_type;
9559 int action_mode = change->action_mode;
9560 int action_arg = change->action_arg;
9561 int action_element = change->action_element;
9564 if (!change->has_action)
9567 /* ---------- determine action paramater values -------------------------- */
9569 int level_time_value =
9570 (level.time > 0 ? TimeLeft :
9573 int action_arg_element_raw =
9574 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9575 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9576 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9577 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9578 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9579 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9580 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9582 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9584 int action_arg_direction =
9585 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9586 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9587 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9588 change->actual_trigger_side :
9589 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9590 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9593 int action_arg_number_min =
9594 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9597 int action_arg_number_max =
9598 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9599 action_type == CA_SET_LEVEL_GEMS ? 999 :
9600 action_type == CA_SET_LEVEL_TIME ? 9999 :
9601 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9602 action_type == CA_SET_CE_VALUE ? 9999 :
9603 action_type == CA_SET_CE_SCORE ? 9999 :
9606 int action_arg_number_reset =
9607 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9608 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9609 action_type == CA_SET_LEVEL_TIME ? level.time :
9610 action_type == CA_SET_LEVEL_SCORE ? 0 :
9611 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9612 action_type == CA_SET_CE_SCORE ? 0 :
9615 int action_arg_number =
9616 (action_arg <= CA_ARG_MAX ? action_arg :
9617 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9618 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9619 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9620 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9621 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9622 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9623 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9624 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9625 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9626 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9627 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? local_player->gems_still_needed :
9628 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->score :
9629 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9630 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9631 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9632 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9633 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9634 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9635 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9636 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9637 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9640 int action_arg_number_old =
9641 (action_type == CA_SET_LEVEL_GEMS ? local_player->gems_still_needed :
9642 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9643 action_type == CA_SET_LEVEL_SCORE ? local_player->score :
9644 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9645 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9648 int action_arg_number_new =
9649 getModifiedActionNumber(action_arg_number_old,
9650 action_mode, action_arg_number,
9651 action_arg_number_min, action_arg_number_max);
9653 int trigger_player_bits =
9654 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9655 change->actual_trigger_player_bits : change->trigger_player);
9657 int action_arg_player_bits =
9658 (action_arg >= CA_ARG_PLAYER_1 &&
9659 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9660 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9661 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9664 /* ---------- execute action -------------------------------------------- */
9666 switch (action_type)
9673 /* ---------- level actions ------------------------------------------- */
9675 case CA_RESTART_LEVEL:
9677 game.restart_level = TRUE;
9682 case CA_SHOW_ENVELOPE:
9684 int element = getSpecialActionElement(action_arg_element,
9685 action_arg_number, EL_ENVELOPE_1);
9687 if (IS_ENVELOPE(element))
9688 local_player->show_envelope = element;
9693 case CA_SET_LEVEL_TIME:
9695 if (level.time > 0) /* only modify limited time value */
9697 TimeLeft = action_arg_number_new;
9699 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9701 DisplayGameControlValues();
9703 if (!TimeLeft && setup.time_limit)
9704 for (i = 0; i < MAX_PLAYERS; i++)
9705 KillPlayer(&stored_player[i]);
9711 case CA_SET_LEVEL_SCORE:
9713 local_player->score = action_arg_number_new;
9715 game_panel_controls[GAME_PANEL_SCORE].value = local_player->score;
9717 DisplayGameControlValues();
9722 case CA_SET_LEVEL_GEMS:
9724 local_player->gems_still_needed = action_arg_number_new;
9726 game.snapshot.collected_item = TRUE;
9728 game_panel_controls[GAME_PANEL_GEMS].value =
9729 local_player->gems_still_needed;
9731 DisplayGameControlValues();
9736 case CA_SET_LEVEL_WIND:
9738 game.wind_direction = action_arg_direction;
9743 case CA_SET_LEVEL_RANDOM_SEED:
9745 /* ensure that setting a new random seed while playing is predictable */
9746 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9751 /* ---------- player actions ------------------------------------------ */
9753 case CA_MOVE_PLAYER:
9755 /* automatically move to the next field in specified direction */
9756 for (i = 0; i < MAX_PLAYERS; i++)
9757 if (trigger_player_bits & (1 << i))
9758 stored_player[i].programmed_action = action_arg_direction;
9763 case CA_EXIT_PLAYER:
9765 for (i = 0; i < MAX_PLAYERS; i++)
9766 if (action_arg_player_bits & (1 << i))
9767 PlayerWins(&stored_player[i]);
9772 case CA_KILL_PLAYER:
9774 for (i = 0; i < MAX_PLAYERS; i++)
9775 if (action_arg_player_bits & (1 << i))
9776 KillPlayer(&stored_player[i]);
9781 case CA_SET_PLAYER_KEYS:
9783 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
9784 int element = getSpecialActionElement(action_arg_element,
9785 action_arg_number, EL_KEY_1);
9787 if (IS_KEY(element))
9789 for (i = 0; i < MAX_PLAYERS; i++)
9791 if (trigger_player_bits & (1 << i))
9793 stored_player[i].key[KEY_NR(element)] = key_state;
9795 DrawGameDoorValues();
9803 case CA_SET_PLAYER_SPEED:
9805 for (i = 0; i < MAX_PLAYERS; i++)
9807 if (trigger_player_bits & (1 << i))
9809 int move_stepsize = TILEX / stored_player[i].move_delay_value;
9811 if (action_arg == CA_ARG_SPEED_FASTER &&
9812 stored_player[i].cannot_move)
9814 action_arg_number = STEPSIZE_VERY_SLOW;
9816 else if (action_arg == CA_ARG_SPEED_SLOWER ||
9817 action_arg == CA_ARG_SPEED_FASTER)
9819 action_arg_number = 2;
9820 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
9823 else if (action_arg == CA_ARG_NUMBER_RESET)
9825 action_arg_number = level.initial_player_stepsize[i];
9829 getModifiedActionNumber(move_stepsize,
9832 action_arg_number_min,
9833 action_arg_number_max);
9835 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
9842 case CA_SET_PLAYER_SHIELD:
9844 for (i = 0; i < MAX_PLAYERS; i++)
9846 if (trigger_player_bits & (1 << i))
9848 if (action_arg == CA_ARG_SHIELD_OFF)
9850 stored_player[i].shield_normal_time_left = 0;
9851 stored_player[i].shield_deadly_time_left = 0;
9853 else if (action_arg == CA_ARG_SHIELD_NORMAL)
9855 stored_player[i].shield_normal_time_left = 999999;
9857 else if (action_arg == CA_ARG_SHIELD_DEADLY)
9859 stored_player[i].shield_normal_time_left = 999999;
9860 stored_player[i].shield_deadly_time_left = 999999;
9868 case CA_SET_PLAYER_GRAVITY:
9870 for (i = 0; i < MAX_PLAYERS; i++)
9872 if (trigger_player_bits & (1 << i))
9874 stored_player[i].gravity =
9875 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
9876 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
9877 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
9878 stored_player[i].gravity);
9885 case CA_SET_PLAYER_ARTWORK:
9887 for (i = 0; i < MAX_PLAYERS; i++)
9889 if (trigger_player_bits & (1 << i))
9891 int artwork_element = action_arg_element;
9893 if (action_arg == CA_ARG_ELEMENT_RESET)
9895 (level.use_artwork_element[i] ? level.artwork_element[i] :
9896 stored_player[i].element_nr);
9898 if (stored_player[i].artwork_element != artwork_element)
9899 stored_player[i].Frame = 0;
9901 stored_player[i].artwork_element = artwork_element;
9903 SetPlayerWaiting(&stored_player[i], FALSE);
9905 /* set number of special actions for bored and sleeping animation */
9906 stored_player[i].num_special_action_bored =
9907 get_num_special_action(artwork_element,
9908 ACTION_BORING_1, ACTION_BORING_LAST);
9909 stored_player[i].num_special_action_sleeping =
9910 get_num_special_action(artwork_element,
9911 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
9918 case CA_SET_PLAYER_INVENTORY:
9920 for (i = 0; i < MAX_PLAYERS; i++)
9922 struct PlayerInfo *player = &stored_player[i];
9925 if (trigger_player_bits & (1 << i))
9927 int inventory_element = action_arg_element;
9929 if (action_arg == CA_ARG_ELEMENT_TARGET ||
9930 action_arg == CA_ARG_ELEMENT_TRIGGER ||
9931 action_arg == CA_ARG_ELEMENT_ACTION)
9933 int element = inventory_element;
9934 int collect_count = element_info[element].collect_count_initial;
9936 if (!IS_CUSTOM_ELEMENT(element))
9939 if (collect_count == 0)
9940 player->inventory_infinite_element = element;
9942 for (k = 0; k < collect_count; k++)
9943 if (player->inventory_size < MAX_INVENTORY_SIZE)
9944 player->inventory_element[player->inventory_size++] =
9947 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
9948 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
9949 action_arg == CA_ARG_INVENTORY_RM_ACTION)
9951 if (player->inventory_infinite_element != EL_UNDEFINED &&
9952 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
9953 action_arg_element_raw))
9954 player->inventory_infinite_element = EL_UNDEFINED;
9956 for (k = 0, j = 0; j < player->inventory_size; j++)
9958 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
9959 action_arg_element_raw))
9960 player->inventory_element[k++] = player->inventory_element[j];
9963 player->inventory_size = k;
9965 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
9967 if (player->inventory_size > 0)
9969 for (j = 0; j < player->inventory_size - 1; j++)
9970 player->inventory_element[j] = player->inventory_element[j + 1];
9972 player->inventory_size--;
9975 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
9977 if (player->inventory_size > 0)
9978 player->inventory_size--;
9980 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
9982 player->inventory_infinite_element = EL_UNDEFINED;
9983 player->inventory_size = 0;
9985 else if (action_arg == CA_ARG_INVENTORY_RESET)
9987 player->inventory_infinite_element = EL_UNDEFINED;
9988 player->inventory_size = 0;
9990 if (level.use_initial_inventory[i])
9992 for (j = 0; j < level.initial_inventory_size[i]; j++)
9994 int element = level.initial_inventory_content[i][j];
9995 int collect_count = element_info[element].collect_count_initial;
9997 if (!IS_CUSTOM_ELEMENT(element))
10000 if (collect_count == 0)
10001 player->inventory_infinite_element = element;
10003 for (k = 0; k < collect_count; k++)
10004 if (player->inventory_size < MAX_INVENTORY_SIZE)
10005 player->inventory_element[player->inventory_size++] =
10016 /* ---------- CE actions ---------------------------------------------- */
10018 case CA_SET_CE_VALUE:
10020 int last_ce_value = CustomValue[x][y];
10022 CustomValue[x][y] = action_arg_number_new;
10024 if (CustomValue[x][y] != last_ce_value)
10026 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10027 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10029 if (CustomValue[x][y] == 0)
10031 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10032 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10039 case CA_SET_CE_SCORE:
10041 int last_ce_score = ei->collect_score;
10043 ei->collect_score = action_arg_number_new;
10045 if (ei->collect_score != last_ce_score)
10047 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10048 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10050 if (ei->collect_score == 0)
10054 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10055 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10058 This is a very special case that seems to be a mixture between
10059 CheckElementChange() and CheckTriggeredElementChange(): while
10060 the first one only affects single elements that are triggered
10061 directly, the second one affects multiple elements in the playfield
10062 that are triggered indirectly by another element. This is a third
10063 case: Changing the CE score always affects multiple identical CEs,
10064 so every affected CE must be checked, not only the single CE for
10065 which the CE score was changed in the first place (as every instance
10066 of that CE shares the same CE score, and therefore also can change)!
10068 SCAN_PLAYFIELD(xx, yy)
10070 if (Feld[xx][yy] == element)
10071 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10072 CE_SCORE_GETS_ZERO);
10080 case CA_SET_CE_ARTWORK:
10082 int artwork_element = action_arg_element;
10083 boolean reset_frame = FALSE;
10086 if (action_arg == CA_ARG_ELEMENT_RESET)
10087 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10090 if (ei->gfx_element != artwork_element)
10091 reset_frame = TRUE;
10093 ei->gfx_element = artwork_element;
10095 SCAN_PLAYFIELD(xx, yy)
10097 if (Feld[xx][yy] == element)
10101 ResetGfxAnimation(xx, yy);
10102 ResetRandomAnimationValue(xx, yy);
10105 TEST_DrawLevelField(xx, yy);
10112 /* ---------- engine actions ------------------------------------------ */
10114 case CA_SET_ENGINE_SCAN_MODE:
10116 InitPlayfieldScanMode(action_arg);
10126 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10128 int old_element = Feld[x][y];
10129 int new_element = GetElementFromGroupElement(element);
10130 int previous_move_direction = MovDir[x][y];
10131 int last_ce_value = CustomValue[x][y];
10132 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10133 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10134 boolean add_player_onto_element = (new_element_is_player &&
10135 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10136 IS_WALKABLE(old_element));
10138 if (!add_player_onto_element)
10140 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10141 RemoveMovingField(x, y);
10145 Feld[x][y] = new_element;
10147 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10148 MovDir[x][y] = previous_move_direction;
10150 if (element_info[new_element].use_last_ce_value)
10151 CustomValue[x][y] = last_ce_value;
10153 InitField_WithBug1(x, y, FALSE);
10155 new_element = Feld[x][y]; /* element may have changed */
10157 ResetGfxAnimation(x, y);
10158 ResetRandomAnimationValue(x, y);
10160 TEST_DrawLevelField(x, y);
10162 if (GFX_CRUMBLED(new_element))
10163 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10166 /* check if element under the player changes from accessible to unaccessible
10167 (needed for special case of dropping element which then changes) */
10168 /* (must be checked after creating new element for walkable group elements) */
10169 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10170 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10177 /* "ChangeCount" not set yet to allow "entered by player" change one time */
10178 if (new_element_is_player)
10179 RelocatePlayer(x, y, new_element);
10182 ChangeCount[x][y]++; /* count number of changes in the same frame */
10184 TestIfBadThingTouchesPlayer(x, y);
10185 TestIfPlayerTouchesCustomElement(x, y);
10186 TestIfElementTouchesCustomElement(x, y);
10189 static void CreateField(int x, int y, int element)
10191 CreateFieldExt(x, y, element, FALSE);
10194 static void CreateElementFromChange(int x, int y, int element)
10196 element = GET_VALID_RUNTIME_ELEMENT(element);
10198 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10200 int old_element = Feld[x][y];
10202 /* prevent changed element from moving in same engine frame
10203 unless both old and new element can either fall or move */
10204 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10205 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10209 CreateFieldExt(x, y, element, TRUE);
10212 static boolean ChangeElement(int x, int y, int element, int page)
10214 struct ElementInfo *ei = &element_info[element];
10215 struct ElementChangeInfo *change = &ei->change_page[page];
10216 int ce_value = CustomValue[x][y];
10217 int ce_score = ei->collect_score;
10218 int target_element;
10219 int old_element = Feld[x][y];
10221 /* always use default change event to prevent running into a loop */
10222 if (ChangeEvent[x][y] == -1)
10223 ChangeEvent[x][y] = CE_DELAY;
10225 if (ChangeEvent[x][y] == CE_DELAY)
10227 /* reset actual trigger element, trigger player and action element */
10228 change->actual_trigger_element = EL_EMPTY;
10229 change->actual_trigger_player = EL_EMPTY;
10230 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10231 change->actual_trigger_side = CH_SIDE_NONE;
10232 change->actual_trigger_ce_value = 0;
10233 change->actual_trigger_ce_score = 0;
10236 /* do not change elements more than a specified maximum number of changes */
10237 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10240 ChangeCount[x][y]++; /* count number of changes in the same frame */
10242 if (change->explode)
10249 if (change->use_target_content)
10251 boolean complete_replace = TRUE;
10252 boolean can_replace[3][3];
10255 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10258 boolean is_walkable;
10259 boolean is_diggable;
10260 boolean is_collectible;
10261 boolean is_removable;
10262 boolean is_destructible;
10263 int ex = x + xx - 1;
10264 int ey = y + yy - 1;
10265 int content_element = change->target_content.e[xx][yy];
10268 can_replace[xx][yy] = TRUE;
10270 if (ex == x && ey == y) /* do not check changing element itself */
10273 if (content_element == EL_EMPTY_SPACE)
10275 can_replace[xx][yy] = FALSE; /* do not replace border with space */
10280 if (!IN_LEV_FIELD(ex, ey))
10282 can_replace[xx][yy] = FALSE;
10283 complete_replace = FALSE;
10290 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10291 e = MovingOrBlocked2Element(ex, ey);
10293 is_empty = (IS_FREE(ex, ey) ||
10294 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10296 is_walkable = (is_empty || IS_WALKABLE(e));
10297 is_diggable = (is_empty || IS_DIGGABLE(e));
10298 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10299 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10300 is_removable = (is_diggable || is_collectible);
10302 can_replace[xx][yy] =
10303 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10304 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10305 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10306 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10307 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10308 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10309 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10311 if (!can_replace[xx][yy])
10312 complete_replace = FALSE;
10315 if (!change->only_if_complete || complete_replace)
10317 boolean something_has_changed = FALSE;
10319 if (change->only_if_complete && change->use_random_replace &&
10320 RND(100) < change->random_percentage)
10323 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10325 int ex = x + xx - 1;
10326 int ey = y + yy - 1;
10327 int content_element;
10329 if (can_replace[xx][yy] && (!change->use_random_replace ||
10330 RND(100) < change->random_percentage))
10332 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10333 RemoveMovingField(ex, ey);
10335 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10337 content_element = change->target_content.e[xx][yy];
10338 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10339 ce_value, ce_score);
10341 CreateElementFromChange(ex, ey, target_element);
10343 something_has_changed = TRUE;
10345 /* for symmetry reasons, freeze newly created border elements */
10346 if (ex != x || ey != y)
10347 Stop[ex][ey] = TRUE; /* no more moving in this frame */
10351 if (something_has_changed)
10353 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10354 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10360 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10361 ce_value, ce_score);
10363 if (element == EL_DIAGONAL_GROWING ||
10364 element == EL_DIAGONAL_SHRINKING)
10366 target_element = Store[x][y];
10368 Store[x][y] = EL_EMPTY;
10371 CreateElementFromChange(x, y, target_element);
10373 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10374 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10377 /* this uses direct change before indirect change */
10378 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10383 static void HandleElementChange(int x, int y, int page)
10385 int element = MovingOrBlocked2Element(x, y);
10386 struct ElementInfo *ei = &element_info[element];
10387 struct ElementChangeInfo *change = &ei->change_page[page];
10388 boolean handle_action_before_change = FALSE;
10391 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10392 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10395 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10396 x, y, element, element_info[element].token_name);
10397 printf("HandleElementChange(): This should never happen!\n");
10402 /* this can happen with classic bombs on walkable, changing elements */
10403 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10408 if (ChangeDelay[x][y] == 0) /* initialize element change */
10410 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10412 if (change->can_change)
10414 /* !!! not clear why graphic animation should be reset at all here !!! */
10415 /* !!! UPDATE: but is needed for correct Snake Bite tail animation !!! */
10416 /* !!! SOLUTION: do not reset if graphics engine set to 4 or above !!! */
10419 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10421 When using an animation frame delay of 1 (this only happens with
10422 "sp_zonk.moving.left/right" in the classic graphics), the default
10423 (non-moving) animation shows wrong animation frames (while the
10424 moving animation, like "sp_zonk.moving.left/right", is correct,
10425 so this graphical bug never shows up with the classic graphics).
10426 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10427 be drawn instead of the correct frames 0,1,2,3. This is caused by
10428 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10429 an element change: First when the change delay ("ChangeDelay[][]")
10430 counter has reached zero after decrementing, then a second time in
10431 the next frame (after "GfxFrame[][]" was already incremented) when
10432 "ChangeDelay[][]" is reset to the initial delay value again.
10434 This causes frame 0 to be drawn twice, while the last frame won't
10435 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10437 As some animations may already be cleverly designed around this bug
10438 (at least the "Snake Bite" snake tail animation does this), it cannot
10439 simply be fixed here without breaking such existing animations.
10440 Unfortunately, it cannot easily be detected if a graphics set was
10441 designed "before" or "after" the bug was fixed. As a workaround,
10442 a new graphics set option "game.graphics_engine_version" was added
10443 to be able to specify the game's major release version for which the
10444 graphics set was designed, which can then be used to decide if the
10445 bugfix should be used (version 4 and above) or not (version 3 or
10446 below, or if no version was specified at all, as with old sets).
10448 (The wrong/fixed animation frames can be tested with the test level set
10449 "test_gfxframe" and level "000", which contains a specially prepared
10450 custom element at level position (x/y) == (11/9) which uses the zonk
10451 animation mentioned above. Using "game.graphics_engine_version: 4"
10452 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10453 This can also be seen from the debug output for this test element.)
10456 /* when a custom element is about to change (for example by change delay),
10457 do not reset graphic animation when the custom element is moving */
10458 if (game.graphics_engine_version < 4 &&
10461 ResetGfxAnimation(x, y);
10462 ResetRandomAnimationValue(x, y);
10465 if (change->pre_change_function)
10466 change->pre_change_function(x, y);
10470 ChangeDelay[x][y]--;
10472 if (ChangeDelay[x][y] != 0) /* continue element change */
10474 if (change->can_change)
10476 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10478 if (IS_ANIMATED(graphic))
10479 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10481 if (change->change_function)
10482 change->change_function(x, y);
10485 else /* finish element change */
10487 if (ChangePage[x][y] != -1) /* remember page from delayed change */
10489 page = ChangePage[x][y];
10490 ChangePage[x][y] = -1;
10492 change = &ei->change_page[page];
10495 if (IS_MOVING(x, y)) /* never change a running system ;-) */
10497 ChangeDelay[x][y] = 1; /* try change after next move step */
10498 ChangePage[x][y] = page; /* remember page to use for change */
10503 /* special case: set new level random seed before changing element */
10504 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10505 handle_action_before_change = TRUE;
10507 if (change->has_action && handle_action_before_change)
10508 ExecuteCustomElementAction(x, y, element, page);
10510 if (change->can_change)
10512 if (ChangeElement(x, y, element, page))
10514 if (change->post_change_function)
10515 change->post_change_function(x, y);
10519 if (change->has_action && !handle_action_before_change)
10520 ExecuteCustomElementAction(x, y, element, page);
10524 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10525 int trigger_element,
10527 int trigger_player,
10531 boolean change_done_any = FALSE;
10532 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10535 if (!(trigger_events[trigger_element][trigger_event]))
10538 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10540 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10542 int element = EL_CUSTOM_START + i;
10543 boolean change_done = FALSE;
10546 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10547 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10550 for (p = 0; p < element_info[element].num_change_pages; p++)
10552 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10554 if (change->can_change_or_has_action &&
10555 change->has_event[trigger_event] &&
10556 change->trigger_side & trigger_side &&
10557 change->trigger_player & trigger_player &&
10558 change->trigger_page & trigger_page_bits &&
10559 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10561 change->actual_trigger_element = trigger_element;
10562 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10563 change->actual_trigger_player_bits = trigger_player;
10564 change->actual_trigger_side = trigger_side;
10565 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10566 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10568 if ((change->can_change && !change_done) || change->has_action)
10572 SCAN_PLAYFIELD(x, y)
10574 if (Feld[x][y] == element)
10576 if (change->can_change && !change_done)
10578 /* if element already changed in this frame, not only prevent
10579 another element change (checked in ChangeElement()), but
10580 also prevent additional element actions for this element */
10582 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10583 !level.use_action_after_change_bug)
10586 ChangeDelay[x][y] = 1;
10587 ChangeEvent[x][y] = trigger_event;
10589 HandleElementChange(x, y, p);
10591 else if (change->has_action)
10593 /* if element already changed in this frame, not only prevent
10594 another element change (checked in ChangeElement()), but
10595 also prevent additional element actions for this element */
10597 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10598 !level.use_action_after_change_bug)
10601 ExecuteCustomElementAction(x, y, element, p);
10602 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10607 if (change->can_change)
10609 change_done = TRUE;
10610 change_done_any = TRUE;
10617 RECURSION_LOOP_DETECTION_END();
10619 return change_done_any;
10622 static boolean CheckElementChangeExt(int x, int y,
10624 int trigger_element,
10626 int trigger_player,
10629 boolean change_done = FALSE;
10632 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10633 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10636 if (Feld[x][y] == EL_BLOCKED)
10638 Blocked2Moving(x, y, &x, &y);
10639 element = Feld[x][y];
10642 /* check if element has already changed or is about to change after moving */
10643 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10644 Feld[x][y] != element) ||
10646 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10647 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10648 ChangePage[x][y] != -1)))
10651 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10653 for (p = 0; p < element_info[element].num_change_pages; p++)
10655 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10657 /* check trigger element for all events where the element that is checked
10658 for changing interacts with a directly adjacent element -- this is
10659 different to element changes that affect other elements to change on the
10660 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10661 boolean check_trigger_element =
10662 (trigger_event == CE_TOUCHING_X ||
10663 trigger_event == CE_HITTING_X ||
10664 trigger_event == CE_HIT_BY_X ||
10665 trigger_event == CE_DIGGING_X); /* this one was forgotten until 3.2.3 */
10667 if (change->can_change_or_has_action &&
10668 change->has_event[trigger_event] &&
10669 change->trigger_side & trigger_side &&
10670 change->trigger_player & trigger_player &&
10671 (!check_trigger_element ||
10672 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10674 change->actual_trigger_element = trigger_element;
10675 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10676 change->actual_trigger_player_bits = trigger_player;
10677 change->actual_trigger_side = trigger_side;
10678 change->actual_trigger_ce_value = CustomValue[x][y];
10679 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10681 /* special case: trigger element not at (x,y) position for some events */
10682 if (check_trigger_element)
10694 { 0, 0 }, { 0, 0 }, { 0, 0 },
10698 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10699 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10701 change->actual_trigger_ce_value = CustomValue[xx][yy];
10702 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10705 if (change->can_change && !change_done)
10707 ChangeDelay[x][y] = 1;
10708 ChangeEvent[x][y] = trigger_event;
10710 HandleElementChange(x, y, p);
10712 change_done = TRUE;
10714 else if (change->has_action)
10716 ExecuteCustomElementAction(x, y, element, p);
10717 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10722 RECURSION_LOOP_DETECTION_END();
10724 return change_done;
10727 static void PlayPlayerSound(struct PlayerInfo *player)
10729 int jx = player->jx, jy = player->jy;
10730 int sound_element = player->artwork_element;
10731 int last_action = player->last_action_waiting;
10732 int action = player->action_waiting;
10734 if (player->is_waiting)
10736 if (action != last_action)
10737 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10739 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10743 if (action != last_action)
10744 StopSound(element_info[sound_element].sound[last_action]);
10746 if (last_action == ACTION_SLEEPING)
10747 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
10751 static void PlayAllPlayersSound()
10755 for (i = 0; i < MAX_PLAYERS; i++)
10756 if (stored_player[i].active)
10757 PlayPlayerSound(&stored_player[i]);
10760 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
10762 boolean last_waiting = player->is_waiting;
10763 int move_dir = player->MovDir;
10765 player->dir_waiting = move_dir;
10766 player->last_action_waiting = player->action_waiting;
10770 if (!last_waiting) /* not waiting -> waiting */
10772 player->is_waiting = TRUE;
10774 player->frame_counter_bored =
10776 game.player_boring_delay_fixed +
10777 GetSimpleRandom(game.player_boring_delay_random);
10778 player->frame_counter_sleeping =
10780 game.player_sleeping_delay_fixed +
10781 GetSimpleRandom(game.player_sleeping_delay_random);
10783 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
10786 if (game.player_sleeping_delay_fixed +
10787 game.player_sleeping_delay_random > 0 &&
10788 player->anim_delay_counter == 0 &&
10789 player->post_delay_counter == 0 &&
10790 FrameCounter >= player->frame_counter_sleeping)
10791 player->is_sleeping = TRUE;
10792 else if (game.player_boring_delay_fixed +
10793 game.player_boring_delay_random > 0 &&
10794 FrameCounter >= player->frame_counter_bored)
10795 player->is_bored = TRUE;
10797 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
10798 player->is_bored ? ACTION_BORING :
10801 if (player->is_sleeping && player->use_murphy)
10803 /* special case for sleeping Murphy when leaning against non-free tile */
10805 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
10806 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
10807 !IS_MOVING(player->jx - 1, player->jy)))
10808 move_dir = MV_LEFT;
10809 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
10810 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
10811 !IS_MOVING(player->jx + 1, player->jy)))
10812 move_dir = MV_RIGHT;
10814 player->is_sleeping = FALSE;
10816 player->dir_waiting = move_dir;
10819 if (player->is_sleeping)
10821 if (player->num_special_action_sleeping > 0)
10823 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10825 int last_special_action = player->special_action_sleeping;
10826 int num_special_action = player->num_special_action_sleeping;
10827 int special_action =
10828 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
10829 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
10830 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
10831 last_special_action + 1 : ACTION_SLEEPING);
10832 int special_graphic =
10833 el_act_dir2img(player->artwork_element, special_action, move_dir);
10835 player->anim_delay_counter =
10836 graphic_info[special_graphic].anim_delay_fixed +
10837 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10838 player->post_delay_counter =
10839 graphic_info[special_graphic].post_delay_fixed +
10840 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10842 player->special_action_sleeping = special_action;
10845 if (player->anim_delay_counter > 0)
10847 player->action_waiting = player->special_action_sleeping;
10848 player->anim_delay_counter--;
10850 else if (player->post_delay_counter > 0)
10852 player->post_delay_counter--;
10856 else if (player->is_bored)
10858 if (player->num_special_action_bored > 0)
10860 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10862 int special_action =
10863 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
10864 int special_graphic =
10865 el_act_dir2img(player->artwork_element, special_action, move_dir);
10867 player->anim_delay_counter =
10868 graphic_info[special_graphic].anim_delay_fixed +
10869 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10870 player->post_delay_counter =
10871 graphic_info[special_graphic].post_delay_fixed +
10872 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10874 player->special_action_bored = special_action;
10877 if (player->anim_delay_counter > 0)
10879 player->action_waiting = player->special_action_bored;
10880 player->anim_delay_counter--;
10882 else if (player->post_delay_counter > 0)
10884 player->post_delay_counter--;
10889 else if (last_waiting) /* waiting -> not waiting */
10891 player->is_waiting = FALSE;
10892 player->is_bored = FALSE;
10893 player->is_sleeping = FALSE;
10895 player->frame_counter_bored = -1;
10896 player->frame_counter_sleeping = -1;
10898 player->anim_delay_counter = 0;
10899 player->post_delay_counter = 0;
10901 player->dir_waiting = player->MovDir;
10902 player->action_waiting = ACTION_DEFAULT;
10904 player->special_action_bored = ACTION_DEFAULT;
10905 player->special_action_sleeping = ACTION_DEFAULT;
10909 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
10911 if ((!player->is_moving && player->was_moving) ||
10912 (player->MovPos == 0 && player->was_moving) ||
10913 (player->is_snapping && !player->was_snapping) ||
10914 (player->is_dropping && !player->was_dropping))
10916 if (!CheckSaveEngineSnapshotToList())
10919 player->was_moving = FALSE;
10920 player->was_snapping = TRUE;
10921 player->was_dropping = TRUE;
10925 if (player->is_moving)
10926 player->was_moving = TRUE;
10928 if (!player->is_snapping)
10929 player->was_snapping = FALSE;
10931 if (!player->is_dropping)
10932 player->was_dropping = FALSE;
10936 static void CheckSingleStepMode(struct PlayerInfo *player)
10938 if (tape.single_step && tape.recording && !tape.pausing)
10940 /* as it is called "single step mode", just return to pause mode when the
10941 player stopped moving after one tile (or never starts moving at all) */
10942 if (!player->is_moving &&
10943 !player->is_pushing &&
10944 !player->is_dropping_pressed)
10946 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
10947 SnapField(player, 0, 0); /* stop snapping */
10951 CheckSaveEngineSnapshot(player);
10954 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
10956 int left = player_action & JOY_LEFT;
10957 int right = player_action & JOY_RIGHT;
10958 int up = player_action & JOY_UP;
10959 int down = player_action & JOY_DOWN;
10960 int button1 = player_action & JOY_BUTTON_1;
10961 int button2 = player_action & JOY_BUTTON_2;
10962 int dx = (left ? -1 : right ? 1 : 0);
10963 int dy = (up ? -1 : down ? 1 : 0);
10965 if (!player->active || tape.pausing)
10971 SnapField(player, dx, dy);
10975 DropElement(player);
10977 MovePlayer(player, dx, dy);
10980 CheckSingleStepMode(player);
10982 SetPlayerWaiting(player, FALSE);
10984 return player_action;
10988 /* no actions for this player (no input at player's configured device) */
10990 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
10991 SnapField(player, 0, 0);
10992 CheckGravityMovementWhenNotMoving(player);
10994 if (player->MovPos == 0)
10995 SetPlayerWaiting(player, TRUE);
10997 if (player->MovPos == 0) /* needed for tape.playing */
10998 player->is_moving = FALSE;
11000 player->is_dropping = FALSE;
11001 player->is_dropping_pressed = FALSE;
11002 player->drop_pressed_delay = 0;
11004 CheckSingleStepMode(player);
11010 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11013 if (!tape.use_mouse)
11016 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11017 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11018 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11021 static void SetTapeActionFromMouseAction(byte *tape_action,
11022 struct MouseActionInfo *mouse_action)
11024 if (!tape.use_mouse)
11027 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11028 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11029 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11032 static void CheckLevelTime()
11036 /* !!! SAME CODE AS IN "GameActions()" -- FIX THIS !!! */
11037 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11039 if (level.native_em_level->lev->home == 0) /* all players at home */
11041 PlayerWins(local_player);
11043 AllPlayersGone = TRUE;
11045 level.native_em_level->lev->home = -1;
11048 if (level.native_em_level->ply[0]->alive == 0 &&
11049 level.native_em_level->ply[1]->alive == 0 &&
11050 level.native_em_level->ply[2]->alive == 0 &&
11051 level.native_em_level->ply[3]->alive == 0) /* all dead */
11052 AllPlayersGone = TRUE;
11054 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11056 if (game_sp.LevelSolved &&
11057 !game_sp.GameOver) /* game won */
11059 PlayerWins(local_player);
11061 game_sp.GameOver = TRUE;
11063 AllPlayersGone = TRUE;
11066 if (game_sp.GameOver) /* game lost */
11067 AllPlayersGone = TRUE;
11069 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11071 if (game_mm.level_solved &&
11072 !game_mm.game_over) /* game won */
11074 PlayerWins(local_player);
11076 game_mm.game_over = TRUE;
11078 AllPlayersGone = TRUE;
11081 if (game_mm.game_over) /* game lost */
11082 AllPlayersGone = TRUE;
11085 if (TimeFrames >= FRAMES_PER_SECOND)
11090 for (i = 0; i < MAX_PLAYERS; i++)
11092 struct PlayerInfo *player = &stored_player[i];
11094 if (SHIELD_ON(player))
11096 player->shield_normal_time_left--;
11098 if (player->shield_deadly_time_left > 0)
11099 player->shield_deadly_time_left--;
11103 if (!local_player->LevelSolved && !level.use_step_counter)
11111 if (TimeLeft <= 10 && setup.time_limit)
11112 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11114 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11115 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11117 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11119 if (!TimeLeft && setup.time_limit)
11121 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11122 level.native_em_level->lev->killed_out_of_time = TRUE;
11124 for (i = 0; i < MAX_PLAYERS; i++)
11125 KillPlayer(&stored_player[i]);
11128 else if (game.no_time_limit && !AllPlayersGone) /* level w/o time limit */
11130 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11133 level.native_em_level->lev->time =
11134 (game.no_time_limit ? TimePlayed : TimeLeft);
11137 if (tape.recording || tape.playing)
11138 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11141 if (tape.recording || tape.playing)
11142 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11144 UpdateAndDisplayGameControlValues();
11147 void AdvanceFrameAndPlayerCounters(int player_nr)
11151 /* advance frame counters (global frame counter and time frame counter) */
11155 /* advance player counters (counters for move delay, move animation etc.) */
11156 for (i = 0; i < MAX_PLAYERS; i++)
11158 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11159 int move_delay_value = stored_player[i].move_delay_value;
11160 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11162 if (!advance_player_counters) /* not all players may be affected */
11165 if (move_frames == 0) /* less than one move per game frame */
11167 int stepsize = TILEX / move_delay_value;
11168 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11169 int count = (stored_player[i].is_moving ?
11170 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11172 if (count % delay == 0)
11176 stored_player[i].Frame += move_frames;
11178 if (stored_player[i].MovPos != 0)
11179 stored_player[i].StepFrame += move_frames;
11181 if (stored_player[i].move_delay > 0)
11182 stored_player[i].move_delay--;
11184 /* due to bugs in previous versions, counter must count up, not down */
11185 if (stored_player[i].push_delay != -1)
11186 stored_player[i].push_delay++;
11188 if (stored_player[i].drop_delay > 0)
11189 stored_player[i].drop_delay--;
11191 if (stored_player[i].is_dropping_pressed)
11192 stored_player[i].drop_pressed_delay++;
11196 void StartGameActions(boolean init_network_game, boolean record_tape,
11199 unsigned int new_random_seed = InitRND(random_seed);
11202 TapeStartRecording(new_random_seed);
11204 if (init_network_game)
11206 SendToServer_StartPlaying();
11214 void GameActionsExt()
11217 static unsigned int game_frame_delay = 0;
11219 unsigned int game_frame_delay_value;
11220 byte *recorded_player_action;
11221 byte summarized_player_action = 0;
11222 byte tape_action[MAX_PLAYERS];
11225 /* detect endless loops, caused by custom element programming */
11226 if (recursion_loop_detected && recursion_loop_depth == 0)
11228 char *message = getStringCat3("Internal Error! Element ",
11229 EL_NAME(recursion_loop_element),
11230 " caused endless loop! Quit the game?");
11232 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11233 EL_NAME(recursion_loop_element));
11235 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11237 recursion_loop_detected = FALSE; /* if game should be continued */
11244 if (game.restart_level)
11245 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11247 /* !!! SAME CODE AS IN "CheckLevelTime()" -- FIX THIS !!! */
11248 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11250 if (level.native_em_level->lev->home == 0) /* all players at home */
11252 PlayerWins(local_player);
11254 AllPlayersGone = TRUE;
11256 level.native_em_level->lev->home = -1;
11259 if (level.native_em_level->ply[0]->alive == 0 &&
11260 level.native_em_level->ply[1]->alive == 0 &&
11261 level.native_em_level->ply[2]->alive == 0 &&
11262 level.native_em_level->ply[3]->alive == 0) /* all dead */
11263 AllPlayersGone = TRUE;
11265 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11267 if (game_sp.LevelSolved &&
11268 !game_sp.GameOver) /* game won */
11270 PlayerWins(local_player);
11272 game_sp.GameOver = TRUE;
11274 AllPlayersGone = TRUE;
11277 if (game_sp.GameOver) /* game lost */
11278 AllPlayersGone = TRUE;
11280 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11282 if (game_mm.level_solved &&
11283 !game_mm.game_over) /* game won */
11285 PlayerWins(local_player);
11287 game_mm.game_over = TRUE;
11289 AllPlayersGone = TRUE;
11292 if (game_mm.game_over) /* game lost */
11293 AllPlayersGone = TRUE;
11296 if (local_player->LevelSolved && !local_player->LevelSolved_GameEnd)
11299 if (AllPlayersGone && !TAPE_IS_STOPPED(tape))
11302 if (game_status != GAME_MODE_PLAYING) /* status might have changed */
11305 game_frame_delay_value =
11306 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11308 if (tape.playing && tape.warp_forward && !tape.pausing)
11309 game_frame_delay_value = 0;
11311 SetVideoFrameDelay(game_frame_delay_value);
11315 /* ---------- main game synchronization point ---------- */
11317 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11319 printf("::: skip == %d\n", skip);
11322 /* ---------- main game synchronization point ---------- */
11324 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11328 if (network_playing && !network_player_action_received)
11330 /* try to get network player actions in time */
11332 /* last chance to get network player actions without main loop delay */
11333 HandleNetworking();
11335 /* game was quit by network peer */
11336 if (game_status != GAME_MODE_PLAYING)
11339 if (!network_player_action_received)
11340 return; /* failed to get network player actions in time */
11342 /* do not yet reset "network_player_action_received" (for tape.pausing) */
11348 /* at this point we know that we really continue executing the game */
11350 network_player_action_received = FALSE;
11352 /* when playing tape, read previously recorded player input from tape data */
11353 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11355 local_player->effective_mouse_action = local_player->mouse_action;
11357 if (recorded_player_action != NULL)
11358 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11359 recorded_player_action);
11361 /* TapePlayAction() may return NULL when toggling to "pause before death" */
11365 if (tape.set_centered_player)
11367 game.centered_player_nr_next = tape.centered_player_nr_next;
11368 game.set_centered_player = TRUE;
11371 for (i = 0; i < MAX_PLAYERS; i++)
11373 summarized_player_action |= stored_player[i].action;
11375 if (!network_playing && (game.team_mode || tape.playing))
11376 stored_player[i].effective_action = stored_player[i].action;
11379 if (network_playing)
11380 SendToServer_MovePlayer(summarized_player_action);
11382 // summarize all actions at local players mapped input device position
11383 // (this allows using different input devices in single player mode)
11384 if (!network.enabled && !game.team_mode)
11385 stored_player[map_player_action[local_player->index_nr]].effective_action =
11386 summarized_player_action;
11388 if (tape.recording &&
11390 setup.input_on_focus &&
11391 game.centered_player_nr != -1)
11393 for (i = 0; i < MAX_PLAYERS; i++)
11394 stored_player[i].effective_action =
11395 (i == game.centered_player_nr ? summarized_player_action : 0);
11398 if (recorded_player_action != NULL)
11399 for (i = 0; i < MAX_PLAYERS; i++)
11400 stored_player[i].effective_action = recorded_player_action[i];
11402 for (i = 0; i < MAX_PLAYERS; i++)
11404 tape_action[i] = stored_player[i].effective_action;
11406 /* (this may happen in the RND game engine if a player was not present on
11407 the playfield on level start, but appeared later from a custom element */
11408 if (setup.team_mode &&
11411 !tape.player_participates[i])
11412 tape.player_participates[i] = TRUE;
11415 SetTapeActionFromMouseAction(tape_action,
11416 &local_player->effective_mouse_action);
11418 /* only record actions from input devices, but not programmed actions */
11419 if (tape.recording)
11420 TapeRecordAction(tape_action);
11422 #if USE_NEW_PLAYER_ASSIGNMENTS
11423 // !!! also map player actions in single player mode !!!
11424 // if (game.team_mode)
11427 byte mapped_action[MAX_PLAYERS];
11429 #if DEBUG_PLAYER_ACTIONS
11431 for (i = 0; i < MAX_PLAYERS; i++)
11432 printf(" %d, ", stored_player[i].effective_action);
11435 for (i = 0; i < MAX_PLAYERS; i++)
11436 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11438 for (i = 0; i < MAX_PLAYERS; i++)
11439 stored_player[i].effective_action = mapped_action[i];
11441 #if DEBUG_PLAYER_ACTIONS
11443 for (i = 0; i < MAX_PLAYERS; i++)
11444 printf(" %d, ", stored_player[i].effective_action);
11448 #if DEBUG_PLAYER_ACTIONS
11452 for (i = 0; i < MAX_PLAYERS; i++)
11453 printf(" %d, ", stored_player[i].effective_action);
11459 for (i = 0; i < MAX_PLAYERS; i++)
11461 // allow engine snapshot in case of changed movement attempt
11462 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11463 (stored_player[i].effective_action & KEY_MOTION))
11464 game.snapshot.changed_action = TRUE;
11466 // allow engine snapshot in case of snapping/dropping attempt
11467 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11468 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11469 game.snapshot.changed_action = TRUE;
11471 game.snapshot.last_action[i] = stored_player[i].effective_action;
11474 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11476 GameActions_EM_Main();
11478 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11480 GameActions_SP_Main();
11482 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11484 GameActions_MM_Main();
11488 GameActions_RND_Main();
11491 BlitScreenToBitmap(backbuffer);
11495 AdvanceFrameAndPlayerCounters(-1); /* advance counters for all players */
11497 if (global.show_frames_per_second)
11499 static unsigned int fps_counter = 0;
11500 static int fps_frames = 0;
11501 unsigned int fps_delay_ms = Counter() - fps_counter;
11505 if (fps_delay_ms >= 500) /* calculate FPS every 0.5 seconds */
11507 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11510 fps_counter = Counter();
11512 /* always draw FPS to screen after FPS value was updated */
11513 redraw_mask |= REDRAW_FPS;
11516 /* only draw FPS if no screen areas are deactivated (invisible warp mode) */
11517 if (GetDrawDeactivationMask() == REDRAW_NONE)
11518 redraw_mask |= REDRAW_FPS;
11522 static void GameActions_CheckSaveEngineSnapshot()
11524 if (!game.snapshot.save_snapshot)
11527 // clear flag for saving snapshot _before_ saving snapshot
11528 game.snapshot.save_snapshot = FALSE;
11530 SaveEngineSnapshotToList();
11537 GameActions_CheckSaveEngineSnapshot();
11540 void GameActions_EM_Main()
11542 byte effective_action[MAX_PLAYERS];
11543 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11546 for (i = 0; i < MAX_PLAYERS; i++)
11547 effective_action[i] = stored_player[i].effective_action;
11549 GameActions_EM(effective_action, warp_mode);
11552 void GameActions_SP_Main()
11554 byte effective_action[MAX_PLAYERS];
11555 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11558 for (i = 0; i < MAX_PLAYERS; i++)
11559 effective_action[i] = stored_player[i].effective_action;
11561 GameActions_SP(effective_action, warp_mode);
11563 for (i = 0; i < MAX_PLAYERS; i++)
11565 if (stored_player[i].force_dropping)
11566 stored_player[i].action |= KEY_BUTTON_DROP;
11568 stored_player[i].force_dropping = FALSE;
11572 void GameActions_MM_Main()
11574 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11576 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11579 void GameActions_RND_Main()
11584 void GameActions_RND()
11586 int magic_wall_x = 0, magic_wall_y = 0;
11587 int i, x, y, element, graphic, last_gfx_frame;
11589 InitPlayfieldScanModeVars();
11591 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11593 SCAN_PLAYFIELD(x, y)
11595 ChangeCount[x][y] = 0;
11596 ChangeEvent[x][y] = -1;
11600 if (game.set_centered_player)
11602 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11604 /* switching to "all players" only possible if all players fit to screen */
11605 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11607 game.centered_player_nr_next = game.centered_player_nr;
11608 game.set_centered_player = FALSE;
11611 /* do not switch focus to non-existing (or non-active) player */
11612 if (game.centered_player_nr_next >= 0 &&
11613 !stored_player[game.centered_player_nr_next].active)
11615 game.centered_player_nr_next = game.centered_player_nr;
11616 game.set_centered_player = FALSE;
11620 if (game.set_centered_player &&
11621 ScreenMovPos == 0) /* screen currently aligned at tile position */
11625 if (game.centered_player_nr_next == -1)
11627 setScreenCenteredToAllPlayers(&sx, &sy);
11631 sx = stored_player[game.centered_player_nr_next].jx;
11632 sy = stored_player[game.centered_player_nr_next].jy;
11635 game.centered_player_nr = game.centered_player_nr_next;
11636 game.set_centered_player = FALSE;
11638 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11639 DrawGameDoorValues();
11642 for (i = 0; i < MAX_PLAYERS; i++)
11644 int actual_player_action = stored_player[i].effective_action;
11647 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11648 - rnd_equinox_tetrachloride 048
11649 - rnd_equinox_tetrachloride_ii 096
11650 - rnd_emanuel_schmieg 002
11651 - doctor_sloan_ww 001, 020
11653 if (stored_player[i].MovPos == 0)
11654 CheckGravityMovement(&stored_player[i]);
11657 /* overwrite programmed action with tape action */
11658 if (stored_player[i].programmed_action)
11659 actual_player_action = stored_player[i].programmed_action;
11661 PlayerActions(&stored_player[i], actual_player_action);
11663 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11666 ScrollScreen(NULL, SCROLL_GO_ON);
11668 /* for backwards compatibility, the following code emulates a fixed bug that
11669 occured when pushing elements (causing elements that just made their last
11670 pushing step to already (if possible) make their first falling step in the
11671 same game frame, which is bad); this code is also needed to use the famous
11672 "spring push bug" which is used in older levels and might be wanted to be
11673 used also in newer levels, but in this case the buggy pushing code is only
11674 affecting the "spring" element and no other elements */
11676 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11678 for (i = 0; i < MAX_PLAYERS; i++)
11680 struct PlayerInfo *player = &stored_player[i];
11681 int x = player->jx;
11682 int y = player->jy;
11684 if (player->active && player->is_pushing && player->is_moving &&
11686 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11687 Feld[x][y] == EL_SPRING))
11689 ContinueMoving(x, y);
11691 /* continue moving after pushing (this is actually a bug) */
11692 if (!IS_MOVING(x, y))
11693 Stop[x][y] = FALSE;
11698 SCAN_PLAYFIELD(x, y)
11700 ChangeCount[x][y] = 0;
11701 ChangeEvent[x][y] = -1;
11703 /* this must be handled before main playfield loop */
11704 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11707 if (MovDelay[x][y] <= 0)
11711 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11714 if (MovDelay[x][y] <= 0)
11717 TEST_DrawLevelField(x, y);
11719 TestIfElementTouchesCustomElement(x, y); /* for empty space */
11724 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11726 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11727 printf("GameActions(): This should never happen!\n");
11729 ChangePage[x][y] = -1;
11733 Stop[x][y] = FALSE;
11734 if (WasJustMoving[x][y] > 0)
11735 WasJustMoving[x][y]--;
11736 if (WasJustFalling[x][y] > 0)
11737 WasJustFalling[x][y]--;
11738 if (CheckCollision[x][y] > 0)
11739 CheckCollision[x][y]--;
11740 if (CheckImpact[x][y] > 0)
11741 CheckImpact[x][y]--;
11745 /* reset finished pushing action (not done in ContinueMoving() to allow
11746 continuous pushing animation for elements with zero push delay) */
11747 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11749 ResetGfxAnimation(x, y);
11750 TEST_DrawLevelField(x, y);
11754 if (IS_BLOCKED(x, y))
11758 Blocked2Moving(x, y, &oldx, &oldy);
11759 if (!IS_MOVING(oldx, oldy))
11761 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11762 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11763 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11764 printf("GameActions(): This should never happen!\n");
11770 SCAN_PLAYFIELD(x, y)
11772 element = Feld[x][y];
11773 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11774 last_gfx_frame = GfxFrame[x][y];
11776 ResetGfxFrame(x, y);
11778 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
11779 DrawLevelGraphicAnimation(x, y, graphic);
11781 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
11782 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
11783 ResetRandomAnimationValue(x, y);
11785 SetRandomAnimationValue(x, y);
11787 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
11789 if (IS_INACTIVE(element))
11791 if (IS_ANIMATED(graphic))
11792 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11797 /* this may take place after moving, so 'element' may have changed */
11798 if (IS_CHANGING(x, y) &&
11799 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
11801 int page = element_info[element].event_page_nr[CE_DELAY];
11803 HandleElementChange(x, y, page);
11805 element = Feld[x][y];
11806 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11809 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
11813 element = Feld[x][y];
11814 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11816 if (IS_ANIMATED(graphic) &&
11817 !IS_MOVING(x, y) &&
11819 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11821 if (IS_GEM(element) || element == EL_SP_INFOTRON)
11822 TEST_DrawTwinkleOnField(x, y);
11824 else if (element == EL_ACID)
11827 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11829 else if ((element == EL_EXIT_OPEN ||
11830 element == EL_EM_EXIT_OPEN ||
11831 element == EL_SP_EXIT_OPEN ||
11832 element == EL_STEEL_EXIT_OPEN ||
11833 element == EL_EM_STEEL_EXIT_OPEN ||
11834 element == EL_SP_TERMINAL ||
11835 element == EL_SP_TERMINAL_ACTIVE ||
11836 element == EL_EXTRA_TIME ||
11837 element == EL_SHIELD_NORMAL ||
11838 element == EL_SHIELD_DEADLY) &&
11839 IS_ANIMATED(graphic))
11840 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11841 else if (IS_MOVING(x, y))
11842 ContinueMoving(x, y);
11843 else if (IS_ACTIVE_BOMB(element))
11844 CheckDynamite(x, y);
11845 else if (element == EL_AMOEBA_GROWING)
11846 AmoebeWaechst(x, y);
11847 else if (element == EL_AMOEBA_SHRINKING)
11848 AmoebaDisappearing(x, y);
11850 #if !USE_NEW_AMOEBA_CODE
11851 else if (IS_AMOEBALIVE(element))
11852 AmoebeAbleger(x, y);
11855 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
11857 else if (element == EL_EXIT_CLOSED)
11859 else if (element == EL_EM_EXIT_CLOSED)
11861 else if (element == EL_STEEL_EXIT_CLOSED)
11862 CheckExitSteel(x, y);
11863 else if (element == EL_EM_STEEL_EXIT_CLOSED)
11864 CheckExitSteelEM(x, y);
11865 else if (element == EL_SP_EXIT_CLOSED)
11867 else if (element == EL_EXPANDABLE_WALL_GROWING ||
11868 element == EL_EXPANDABLE_STEELWALL_GROWING)
11869 MauerWaechst(x, y);
11870 else if (element == EL_EXPANDABLE_WALL ||
11871 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
11872 element == EL_EXPANDABLE_WALL_VERTICAL ||
11873 element == EL_EXPANDABLE_WALL_ANY ||
11874 element == EL_BD_EXPANDABLE_WALL)
11875 MauerAbleger(x, y);
11876 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
11877 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
11878 element == EL_EXPANDABLE_STEELWALL_ANY)
11879 MauerAblegerStahl(x, y);
11880 else if (element == EL_FLAMES)
11881 CheckForDragon(x, y);
11882 else if (element == EL_EXPLOSION)
11883 ; /* drawing of correct explosion animation is handled separately */
11884 else if (element == EL_ELEMENT_SNAPPING ||
11885 element == EL_DIAGONAL_SHRINKING ||
11886 element == EL_DIAGONAL_GROWING)
11888 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
11890 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11892 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
11893 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11895 if (IS_BELT_ACTIVE(element))
11896 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
11898 if (game.magic_wall_active)
11900 int jx = local_player->jx, jy = local_player->jy;
11902 /* play the element sound at the position nearest to the player */
11903 if ((element == EL_MAGIC_WALL_FULL ||
11904 element == EL_MAGIC_WALL_ACTIVE ||
11905 element == EL_MAGIC_WALL_EMPTYING ||
11906 element == EL_BD_MAGIC_WALL_FULL ||
11907 element == EL_BD_MAGIC_WALL_ACTIVE ||
11908 element == EL_BD_MAGIC_WALL_EMPTYING ||
11909 element == EL_DC_MAGIC_WALL_FULL ||
11910 element == EL_DC_MAGIC_WALL_ACTIVE ||
11911 element == EL_DC_MAGIC_WALL_EMPTYING) &&
11912 ABS(x-jx) + ABS(y-jy) < ABS(magic_wall_x-jx) + ABS(magic_wall_y-jy))
11920 #if USE_NEW_AMOEBA_CODE
11921 /* new experimental amoeba growth stuff */
11922 if (!(FrameCounter % 8))
11924 static unsigned int random = 1684108901;
11926 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
11928 x = RND(lev_fieldx);
11929 y = RND(lev_fieldy);
11930 element = Feld[x][y];
11932 if (!IS_PLAYER(x,y) &&
11933 (element == EL_EMPTY ||
11934 CAN_GROW_INTO(element) ||
11935 element == EL_QUICKSAND_EMPTY ||
11936 element == EL_QUICKSAND_FAST_EMPTY ||
11937 element == EL_ACID_SPLASH_LEFT ||
11938 element == EL_ACID_SPLASH_RIGHT))
11940 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
11941 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
11942 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
11943 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
11944 Feld[x][y] = EL_AMOEBA_DROP;
11947 random = random * 129 + 1;
11952 game.explosions_delayed = FALSE;
11954 SCAN_PLAYFIELD(x, y)
11956 element = Feld[x][y];
11958 if (ExplodeField[x][y])
11959 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
11960 else if (element == EL_EXPLOSION)
11961 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
11963 ExplodeField[x][y] = EX_TYPE_NONE;
11966 game.explosions_delayed = TRUE;
11968 if (game.magic_wall_active)
11970 if (!(game.magic_wall_time_left % 4))
11972 int element = Feld[magic_wall_x][magic_wall_y];
11974 if (element == EL_BD_MAGIC_WALL_FULL ||
11975 element == EL_BD_MAGIC_WALL_ACTIVE ||
11976 element == EL_BD_MAGIC_WALL_EMPTYING)
11977 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
11978 else if (element == EL_DC_MAGIC_WALL_FULL ||
11979 element == EL_DC_MAGIC_WALL_ACTIVE ||
11980 element == EL_DC_MAGIC_WALL_EMPTYING)
11981 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
11983 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
11986 if (game.magic_wall_time_left > 0)
11988 game.magic_wall_time_left--;
11990 if (!game.magic_wall_time_left)
11992 SCAN_PLAYFIELD(x, y)
11994 element = Feld[x][y];
11996 if (element == EL_MAGIC_WALL_ACTIVE ||
11997 element == EL_MAGIC_WALL_FULL)
11999 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12000 TEST_DrawLevelField(x, y);
12002 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12003 element == EL_BD_MAGIC_WALL_FULL)
12005 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12006 TEST_DrawLevelField(x, y);
12008 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12009 element == EL_DC_MAGIC_WALL_FULL)
12011 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12012 TEST_DrawLevelField(x, y);
12016 game.magic_wall_active = FALSE;
12021 if (game.light_time_left > 0)
12023 game.light_time_left--;
12025 if (game.light_time_left == 0)
12026 RedrawAllLightSwitchesAndInvisibleElements();
12029 if (game.timegate_time_left > 0)
12031 game.timegate_time_left--;
12033 if (game.timegate_time_left == 0)
12034 CloseAllOpenTimegates();
12037 if (game.lenses_time_left > 0)
12039 game.lenses_time_left--;
12041 if (game.lenses_time_left == 0)
12042 RedrawAllInvisibleElementsForLenses();
12045 if (game.magnify_time_left > 0)
12047 game.magnify_time_left--;
12049 if (game.magnify_time_left == 0)
12050 RedrawAllInvisibleElementsForMagnifier();
12053 for (i = 0; i < MAX_PLAYERS; i++)
12055 struct PlayerInfo *player = &stored_player[i];
12057 if (SHIELD_ON(player))
12059 if (player->shield_deadly_time_left)
12060 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12061 else if (player->shield_normal_time_left)
12062 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12066 #if USE_DELAYED_GFX_REDRAW
12067 SCAN_PLAYFIELD(x, y)
12069 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12071 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12072 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12074 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12075 DrawLevelField(x, y);
12077 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12078 DrawLevelFieldCrumbled(x, y);
12080 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12081 DrawLevelFieldCrumbledNeighbours(x, y);
12083 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12084 DrawTwinkleOnField(x, y);
12087 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12092 PlayAllPlayersSound();
12094 if (local_player->show_envelope != 0 && local_player->MovPos == 0)
12096 ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
12098 local_player->show_envelope = 0;
12101 /* use random number generator in every frame to make it less predictable */
12102 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12106 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12108 int min_x = x, min_y = y, max_x = x, max_y = y;
12111 for (i = 0; i < MAX_PLAYERS; i++)
12113 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12115 if (!stored_player[i].active || &stored_player[i] == player)
12118 min_x = MIN(min_x, jx);
12119 min_y = MIN(min_y, jy);
12120 max_x = MAX(max_x, jx);
12121 max_y = MAX(max_y, jy);
12124 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12127 static boolean AllPlayersInVisibleScreen()
12131 for (i = 0; i < MAX_PLAYERS; i++)
12133 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12135 if (!stored_player[i].active)
12138 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12145 void ScrollLevel(int dx, int dy)
12147 int scroll_offset = 2 * TILEX_VAR;
12150 BlitBitmap(drawto_field, drawto_field,
12151 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12152 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12153 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12154 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12155 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12156 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12160 x = (dx == 1 ? BX1 : BX2);
12161 for (y = BY1; y <= BY2; y++)
12162 DrawScreenField(x, y);
12167 y = (dy == 1 ? BY1 : BY2);
12168 for (x = BX1; x <= BX2; x++)
12169 DrawScreenField(x, y);
12172 redraw_mask |= REDRAW_FIELD;
12175 static boolean canFallDown(struct PlayerInfo *player)
12177 int jx = player->jx, jy = player->jy;
12179 return (IN_LEV_FIELD(jx, jy + 1) &&
12180 (IS_FREE(jx, jy + 1) ||
12181 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12182 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12183 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12186 static boolean canPassField(int x, int y, int move_dir)
12188 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12189 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12190 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12191 int nextx = x + dx;
12192 int nexty = y + dy;
12193 int element = Feld[x][y];
12195 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12196 !CAN_MOVE(element) &&
12197 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12198 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12199 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12202 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12204 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12205 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12206 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12210 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12211 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12212 (IS_DIGGABLE(Feld[newx][newy]) ||
12213 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12214 canPassField(newx, newy, move_dir)));
12217 static void CheckGravityMovement(struct PlayerInfo *player)
12219 if (player->gravity && !player->programmed_action)
12221 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12222 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12223 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12224 int jx = player->jx, jy = player->jy;
12225 boolean player_is_moving_to_valid_field =
12226 (!player_is_snapping &&
12227 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12228 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12229 boolean player_can_fall_down = canFallDown(player);
12231 if (player_can_fall_down &&
12232 !player_is_moving_to_valid_field)
12233 player->programmed_action = MV_DOWN;
12237 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12239 return CheckGravityMovement(player);
12241 if (player->gravity && !player->programmed_action)
12243 int jx = player->jx, jy = player->jy;
12244 boolean field_under_player_is_free =
12245 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12246 boolean player_is_standing_on_valid_field =
12247 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12248 (IS_WALKABLE(Feld[jx][jy]) &&
12249 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12251 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12252 player->programmed_action = MV_DOWN;
12257 MovePlayerOneStep()
12258 -----------------------------------------------------------------------------
12259 dx, dy: direction (non-diagonal) to try to move the player to
12260 real_dx, real_dy: direction as read from input device (can be diagonal)
12263 boolean MovePlayerOneStep(struct PlayerInfo *player,
12264 int dx, int dy, int real_dx, int real_dy)
12266 int jx = player->jx, jy = player->jy;
12267 int new_jx = jx + dx, new_jy = jy + dy;
12269 boolean player_can_move = !player->cannot_move;
12271 if (!player->active || (!dx && !dy))
12272 return MP_NO_ACTION;
12274 player->MovDir = (dx < 0 ? MV_LEFT :
12275 dx > 0 ? MV_RIGHT :
12277 dy > 0 ? MV_DOWN : MV_NONE);
12279 if (!IN_LEV_FIELD(new_jx, new_jy))
12280 return MP_NO_ACTION;
12282 if (!player_can_move)
12284 if (player->MovPos == 0)
12286 player->is_moving = FALSE;
12287 player->is_digging = FALSE;
12288 player->is_collecting = FALSE;
12289 player->is_snapping = FALSE;
12290 player->is_pushing = FALSE;
12294 if (!network.enabled && game.centered_player_nr == -1 &&
12295 !AllPlayersInSight(player, new_jx, new_jy))
12296 return MP_NO_ACTION;
12298 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12299 if (can_move != MP_MOVING)
12302 /* check if DigField() has caused relocation of the player */
12303 if (player->jx != jx || player->jy != jy)
12304 return MP_NO_ACTION; /* <-- !!! CHECK THIS [-> MP_ACTION ?] !!! */
12306 StorePlayer[jx][jy] = 0;
12307 player->last_jx = jx;
12308 player->last_jy = jy;
12309 player->jx = new_jx;
12310 player->jy = new_jy;
12311 StorePlayer[new_jx][new_jy] = player->element_nr;
12313 if (player->move_delay_value_next != -1)
12315 player->move_delay_value = player->move_delay_value_next;
12316 player->move_delay_value_next = -1;
12320 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12322 player->step_counter++;
12324 PlayerVisit[jx][jy] = FrameCounter;
12326 player->is_moving = TRUE;
12329 /* should better be called in MovePlayer(), but this breaks some tapes */
12330 ScrollPlayer(player, SCROLL_INIT);
12336 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12338 int jx = player->jx, jy = player->jy;
12339 int old_jx = jx, old_jy = jy;
12340 int moved = MP_NO_ACTION;
12342 if (!player->active)
12347 if (player->MovPos == 0)
12349 player->is_moving = FALSE;
12350 player->is_digging = FALSE;
12351 player->is_collecting = FALSE;
12352 player->is_snapping = FALSE;
12353 player->is_pushing = FALSE;
12359 if (player->move_delay > 0)
12362 player->move_delay = -1; /* set to "uninitialized" value */
12364 /* store if player is automatically moved to next field */
12365 player->is_auto_moving = (player->programmed_action != MV_NONE);
12367 /* remove the last programmed player action */
12368 player->programmed_action = 0;
12370 if (player->MovPos)
12372 /* should only happen if pre-1.2 tape recordings are played */
12373 /* this is only for backward compatibility */
12375 int original_move_delay_value = player->move_delay_value;
12378 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12382 /* scroll remaining steps with finest movement resolution */
12383 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12385 while (player->MovPos)
12387 ScrollPlayer(player, SCROLL_GO_ON);
12388 ScrollScreen(NULL, SCROLL_GO_ON);
12390 AdvanceFrameAndPlayerCounters(player->index_nr);
12393 BackToFront_WithFrameDelay(0);
12396 player->move_delay_value = original_move_delay_value;
12399 player->is_active = FALSE;
12401 if (player->last_move_dir & MV_HORIZONTAL)
12403 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12404 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12408 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12409 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12412 if (!moved && !player->is_active)
12414 player->is_moving = FALSE;
12415 player->is_digging = FALSE;
12416 player->is_collecting = FALSE;
12417 player->is_snapping = FALSE;
12418 player->is_pushing = FALSE;
12424 if (moved & MP_MOVING && !ScreenMovPos &&
12425 (player->index_nr == game.centered_player_nr ||
12426 game.centered_player_nr == -1))
12428 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12429 int offset = game.scroll_delay_value;
12431 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12433 /* actual player has left the screen -- scroll in that direction */
12434 if (jx != old_jx) /* player has moved horizontally */
12435 scroll_x += (jx - old_jx);
12436 else /* player has moved vertically */
12437 scroll_y += (jy - old_jy);
12441 if (jx != old_jx) /* player has moved horizontally */
12443 if ((player->MovDir == MV_LEFT && scroll_x > jx - MIDPOSX + offset) ||
12444 (player->MovDir == MV_RIGHT && scroll_x < jx - MIDPOSX - offset))
12445 scroll_x = jx-MIDPOSX + (scroll_x < jx-MIDPOSX ? -offset : +offset);
12447 /* don't scroll over playfield boundaries */
12448 if (scroll_x < SBX_Left || scroll_x > SBX_Right)
12449 scroll_x = (scroll_x < SBX_Left ? SBX_Left : SBX_Right);
12451 /* don't scroll more than one field at a time */
12452 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12454 /* don't scroll against the player's moving direction */
12455 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12456 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12457 scroll_x = old_scroll_x;
12459 else /* player has moved vertically */
12461 if ((player->MovDir == MV_UP && scroll_y > jy - MIDPOSY + offset) ||
12462 (player->MovDir == MV_DOWN && scroll_y < jy - MIDPOSY - offset))
12463 scroll_y = jy-MIDPOSY + (scroll_y < jy-MIDPOSY ? -offset : +offset);
12465 /* don't scroll over playfield boundaries */
12466 if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
12467 scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
12469 /* don't scroll more than one field at a time */
12470 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12472 /* don't scroll against the player's moving direction */
12473 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12474 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12475 scroll_y = old_scroll_y;
12479 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12481 if (!network.enabled && game.centered_player_nr == -1 &&
12482 !AllPlayersInVisibleScreen())
12484 scroll_x = old_scroll_x;
12485 scroll_y = old_scroll_y;
12489 ScrollScreen(player, SCROLL_INIT);
12490 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12495 player->StepFrame = 0;
12497 if (moved & MP_MOVING)
12499 if (old_jx != jx && old_jy == jy)
12500 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12501 else if (old_jx == jx && old_jy != jy)
12502 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12504 TEST_DrawLevelField(jx, jy); /* for "crumbled sand" */
12506 player->last_move_dir = player->MovDir;
12507 player->is_moving = TRUE;
12508 player->is_snapping = FALSE;
12509 player->is_switching = FALSE;
12510 player->is_dropping = FALSE;
12511 player->is_dropping_pressed = FALSE;
12512 player->drop_pressed_delay = 0;
12515 /* should better be called here than above, but this breaks some tapes */
12516 ScrollPlayer(player, SCROLL_INIT);
12521 CheckGravityMovementWhenNotMoving(player);
12523 player->is_moving = FALSE;
12525 /* at this point, the player is allowed to move, but cannot move right now
12526 (e.g. because of something blocking the way) -- ensure that the player
12527 is also allowed to move in the next frame (in old versions before 3.1.1,
12528 the player was forced to wait again for eight frames before next try) */
12530 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12531 player->move_delay = 0; /* allow direct movement in the next frame */
12534 if (player->move_delay == -1) /* not yet initialized by DigField() */
12535 player->move_delay = player->move_delay_value;
12537 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12539 TestIfPlayerTouchesBadThing(jx, jy);
12540 TestIfPlayerTouchesCustomElement(jx, jy);
12543 if (!player->active)
12544 RemovePlayer(player);
12549 void ScrollPlayer(struct PlayerInfo *player, int mode)
12551 int jx = player->jx, jy = player->jy;
12552 int last_jx = player->last_jx, last_jy = player->last_jy;
12553 int move_stepsize = TILEX / player->move_delay_value;
12555 if (!player->active)
12558 if (player->MovPos == 0 && mode == SCROLL_GO_ON) /* player not moving */
12561 if (mode == SCROLL_INIT)
12563 player->actual_frame_counter = FrameCounter;
12564 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12566 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12567 Feld[last_jx][last_jy] == EL_EMPTY)
12569 int last_field_block_delay = 0; /* start with no blocking at all */
12570 int block_delay_adjustment = player->block_delay_adjustment;
12572 /* if player blocks last field, add delay for exactly one move */
12573 if (player->block_last_field)
12575 last_field_block_delay += player->move_delay_value;
12577 /* when blocking enabled, prevent moving up despite gravity */
12578 if (player->gravity && player->MovDir == MV_UP)
12579 block_delay_adjustment = -1;
12582 /* add block delay adjustment (also possible when not blocking) */
12583 last_field_block_delay += block_delay_adjustment;
12585 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12586 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12589 if (player->MovPos != 0) /* player has not yet reached destination */
12592 else if (!FrameReached(&player->actual_frame_counter, 1))
12595 if (player->MovPos != 0)
12597 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12598 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12600 /* before DrawPlayer() to draw correct player graphic for this case */
12601 if (player->MovPos == 0)
12602 CheckGravityMovement(player);
12605 if (player->MovPos == 0) /* player reached destination field */
12607 if (player->move_delay_reset_counter > 0)
12609 player->move_delay_reset_counter--;
12611 if (player->move_delay_reset_counter == 0)
12613 /* continue with normal speed after quickly moving through gate */
12614 HALVE_PLAYER_SPEED(player);
12616 /* be able to make the next move without delay */
12617 player->move_delay = 0;
12621 player->last_jx = jx;
12622 player->last_jy = jy;
12624 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12625 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12626 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12627 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12628 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12629 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12630 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12631 Feld[jx][jy] == EL_SP_EXIT_OPENING) /* <-- special case */
12633 DrawPlayer(player); /* needed here only to cleanup last field */
12634 RemovePlayer(player);
12636 if (local_player->friends_still_needed == 0 ||
12637 IS_SP_ELEMENT(Feld[jx][jy]))
12638 PlayerWins(player);
12641 /* this breaks one level: "machine", level 000 */
12643 int move_direction = player->MovDir;
12644 int enter_side = MV_DIR_OPPOSITE(move_direction);
12645 int leave_side = move_direction;
12646 int old_jx = last_jx;
12647 int old_jy = last_jy;
12648 int old_element = Feld[old_jx][old_jy];
12649 int new_element = Feld[jx][jy];
12651 if (IS_CUSTOM_ELEMENT(old_element))
12652 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12654 player->index_bit, leave_side);
12656 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12657 CE_PLAYER_LEAVES_X,
12658 player->index_bit, leave_side);
12660 if (IS_CUSTOM_ELEMENT(new_element))
12661 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12662 player->index_bit, enter_side);
12664 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12665 CE_PLAYER_ENTERS_X,
12666 player->index_bit, enter_side);
12668 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12669 CE_MOVE_OF_X, move_direction);
12672 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12674 TestIfPlayerTouchesBadThing(jx, jy);
12675 TestIfPlayerTouchesCustomElement(jx, jy);
12677 /* needed because pushed element has not yet reached its destination,
12678 so it would trigger a change event at its previous field location */
12679 if (!player->is_pushing)
12680 TestIfElementTouchesCustomElement(jx, jy); /* for empty space */
12682 if (!player->active)
12683 RemovePlayer(player);
12686 if (!local_player->LevelSolved && level.use_step_counter)
12696 if (TimeLeft <= 10 && setup.time_limit)
12697 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12699 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12701 DisplayGameControlValues();
12703 if (!TimeLeft && setup.time_limit)
12704 for (i = 0; i < MAX_PLAYERS; i++)
12705 KillPlayer(&stored_player[i]);
12707 else if (game.no_time_limit && !AllPlayersGone) /* level w/o time limit */
12709 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12711 DisplayGameControlValues();
12715 if (tape.single_step && tape.recording && !tape.pausing &&
12716 !player->programmed_action)
12717 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12719 if (!player->programmed_action)
12720 CheckSaveEngineSnapshot(player);
12724 void ScrollScreen(struct PlayerInfo *player, int mode)
12726 static unsigned int screen_frame_counter = 0;
12728 if (mode == SCROLL_INIT)
12730 /* set scrolling step size according to actual player's moving speed */
12731 ScrollStepSize = TILEX / player->move_delay_value;
12733 screen_frame_counter = FrameCounter;
12734 ScreenMovDir = player->MovDir;
12735 ScreenMovPos = player->MovPos;
12736 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12739 else if (!FrameReached(&screen_frame_counter, 1))
12744 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
12745 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12746 redraw_mask |= REDRAW_FIELD;
12749 ScreenMovDir = MV_NONE;
12752 void TestIfPlayerTouchesCustomElement(int x, int y)
12754 static int xy[4][2] =
12761 static int trigger_sides[4][2] =
12763 /* center side border side */
12764 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, /* check top */
12765 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, /* check left */
12766 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, /* check right */
12767 { CH_SIDE_BOTTOM, CH_SIDE_TOP } /* check bottom */
12769 static int touch_dir[4] =
12771 MV_LEFT | MV_RIGHT,
12776 int center_element = Feld[x][y]; /* should always be non-moving! */
12779 for (i = 0; i < NUM_DIRECTIONS; i++)
12781 int xx = x + xy[i][0];
12782 int yy = y + xy[i][1];
12783 int center_side = trigger_sides[i][0];
12784 int border_side = trigger_sides[i][1];
12785 int border_element;
12787 if (!IN_LEV_FIELD(xx, yy))
12790 if (IS_PLAYER(x, y)) /* player found at center element */
12792 struct PlayerInfo *player = PLAYERINFO(x, y);
12794 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12795 border_element = Feld[xx][yy]; /* may be moving! */
12796 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12797 border_element = Feld[xx][yy];
12798 else if (MovDir[xx][yy] & touch_dir[i]) /* elements are touching */
12799 border_element = MovingOrBlocked2Element(xx, yy);
12801 continue; /* center and border element do not touch */
12803 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
12804 player->index_bit, border_side);
12805 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
12806 CE_PLAYER_TOUCHES_X,
12807 player->index_bit, border_side);
12810 /* use player element that is initially defined in the level playfield,
12811 not the player element that corresponds to the runtime player number
12812 (example: a level that contains EL_PLAYER_3 as the only player would
12813 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12814 int player_element = PLAYERINFO(x, y)->initial_element;
12816 CheckElementChangeBySide(xx, yy, border_element, player_element,
12817 CE_TOUCHING_X, border_side);
12820 else if (IS_PLAYER(xx, yy)) /* player found at border element */
12822 struct PlayerInfo *player = PLAYERINFO(xx, yy);
12824 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12826 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
12827 continue; /* center and border element do not touch */
12830 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
12831 player->index_bit, center_side);
12832 CheckTriggeredElementChangeByPlayer(x, y, center_element,
12833 CE_PLAYER_TOUCHES_X,
12834 player->index_bit, center_side);
12837 /* use player element that is initially defined in the level playfield,
12838 not the player element that corresponds to the runtime player number
12839 (example: a level that contains EL_PLAYER_3 as the only player would
12840 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12841 int player_element = PLAYERINFO(xx, yy)->initial_element;
12843 CheckElementChangeBySide(x, y, center_element, player_element,
12844 CE_TOUCHING_X, center_side);
12852 void TestIfElementTouchesCustomElement(int x, int y)
12854 static int xy[4][2] =
12861 static int trigger_sides[4][2] =
12863 /* center side border side */
12864 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, /* check top */
12865 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, /* check left */
12866 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, /* check right */
12867 { CH_SIDE_BOTTOM, CH_SIDE_TOP } /* check bottom */
12869 static int touch_dir[4] =
12871 MV_LEFT | MV_RIGHT,
12876 boolean change_center_element = FALSE;
12877 int center_element = Feld[x][y]; /* should always be non-moving! */
12878 int border_element_old[NUM_DIRECTIONS];
12881 for (i = 0; i < NUM_DIRECTIONS; i++)
12883 int xx = x + xy[i][0];
12884 int yy = y + xy[i][1];
12885 int border_element;
12887 border_element_old[i] = -1;
12889 if (!IN_LEV_FIELD(xx, yy))
12892 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12893 border_element = Feld[xx][yy]; /* may be moving! */
12894 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12895 border_element = Feld[xx][yy];
12896 else if (MovDir[xx][yy] & touch_dir[i]) /* elements are touching */
12897 border_element = MovingOrBlocked2Element(xx, yy);
12899 continue; /* center and border element do not touch */
12901 border_element_old[i] = border_element;
12904 for (i = 0; i < NUM_DIRECTIONS; i++)
12906 int xx = x + xy[i][0];
12907 int yy = y + xy[i][1];
12908 int center_side = trigger_sides[i][0];
12909 int border_element = border_element_old[i];
12911 if (border_element == -1)
12914 /* check for change of border element */
12915 CheckElementChangeBySide(xx, yy, border_element, center_element,
12916 CE_TOUCHING_X, center_side);
12918 /* (center element cannot be player, so we dont have to check this here) */
12921 for (i = 0; i < NUM_DIRECTIONS; i++)
12923 int xx = x + xy[i][0];
12924 int yy = y + xy[i][1];
12925 int border_side = trigger_sides[i][1];
12926 int border_element = border_element_old[i];
12928 if (border_element == -1)
12931 /* check for change of center element (but change it only once) */
12932 if (!change_center_element)
12933 change_center_element =
12934 CheckElementChangeBySide(x, y, center_element, border_element,
12935 CE_TOUCHING_X, border_side);
12937 if (IS_PLAYER(xx, yy))
12939 /* use player element that is initially defined in the level playfield,
12940 not the player element that corresponds to the runtime player number
12941 (example: a level that contains EL_PLAYER_3 as the only player would
12942 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12943 int player_element = PLAYERINFO(xx, yy)->initial_element;
12945 CheckElementChangeBySide(x, y, center_element, player_element,
12946 CE_TOUCHING_X, border_side);
12951 void TestIfElementHitsCustomElement(int x, int y, int direction)
12953 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
12954 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
12955 int hitx = x + dx, hity = y + dy;
12956 int hitting_element = Feld[x][y];
12957 int touched_element;
12959 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
12962 touched_element = (IN_LEV_FIELD(hitx, hity) ?
12963 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
12965 if (IN_LEV_FIELD(hitx, hity))
12967 int opposite_direction = MV_DIR_OPPOSITE(direction);
12968 int hitting_side = direction;
12969 int touched_side = opposite_direction;
12970 boolean object_hit = (!IS_MOVING(hitx, hity) ||
12971 MovDir[hitx][hity] != direction ||
12972 ABS(MovPos[hitx][hity]) <= TILEY / 2);
12978 CheckElementChangeBySide(x, y, hitting_element, touched_element,
12979 CE_HITTING_X, touched_side);
12981 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
12982 CE_HIT_BY_X, hitting_side);
12984 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
12985 CE_HIT_BY_SOMETHING, opposite_direction);
12987 if (IS_PLAYER(hitx, hity))
12989 /* use player element that is initially defined in the level playfield,
12990 not the player element that corresponds to the runtime player number
12991 (example: a level that contains EL_PLAYER_3 as the only player would
12992 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12993 int player_element = PLAYERINFO(hitx, hity)->initial_element;
12995 CheckElementChangeBySide(x, y, hitting_element, player_element,
12996 CE_HITTING_X, touched_side);
13001 /* "hitting something" is also true when hitting the playfield border */
13002 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13003 CE_HITTING_SOMETHING, direction);
13006 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13008 int i, kill_x = -1, kill_y = -1;
13010 int bad_element = -1;
13011 static int test_xy[4][2] =
13018 static int test_dir[4] =
13026 for (i = 0; i < NUM_DIRECTIONS; i++)
13028 int test_x, test_y, test_move_dir, test_element;
13030 test_x = good_x + test_xy[i][0];
13031 test_y = good_y + test_xy[i][1];
13033 if (!IN_LEV_FIELD(test_x, test_y))
13037 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13039 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13041 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13042 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13044 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13045 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13049 bad_element = test_element;
13055 if (kill_x != -1 || kill_y != -1)
13057 if (IS_PLAYER(good_x, good_y))
13059 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13061 if (player->shield_deadly_time_left > 0 &&
13062 !IS_INDESTRUCTIBLE(bad_element))
13063 Bang(kill_x, kill_y);
13064 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13065 KillPlayer(player);
13068 Bang(good_x, good_y);
13072 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13074 int i, kill_x = -1, kill_y = -1;
13075 int bad_element = Feld[bad_x][bad_y];
13076 static int test_xy[4][2] =
13083 static int touch_dir[4] =
13085 MV_LEFT | MV_RIGHT,
13090 static int test_dir[4] =
13098 if (bad_element == EL_EXPLOSION) /* skip just exploding bad things */
13101 for (i = 0; i < NUM_DIRECTIONS; i++)
13103 int test_x, test_y, test_move_dir, test_element;
13105 test_x = bad_x + test_xy[i][0];
13106 test_y = bad_y + test_xy[i][1];
13108 if (!IN_LEV_FIELD(test_x, test_y))
13112 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13114 test_element = Feld[test_x][test_y];
13116 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13117 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13119 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13120 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13122 /* good thing is player or penguin that does not move away */
13123 if (IS_PLAYER(test_x, test_y))
13125 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13127 if (bad_element == EL_ROBOT && player->is_moving)
13128 continue; /* robot does not kill player if he is moving */
13130 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13132 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13133 continue; /* center and border element do not touch */
13141 else if (test_element == EL_PENGUIN)
13151 if (kill_x != -1 || kill_y != -1)
13153 if (IS_PLAYER(kill_x, kill_y))
13155 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13157 if (player->shield_deadly_time_left > 0 &&
13158 !IS_INDESTRUCTIBLE(bad_element))
13159 Bang(bad_x, bad_y);
13160 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13161 KillPlayer(player);
13164 Bang(kill_x, kill_y);
13168 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13170 int bad_element = Feld[bad_x][bad_y];
13171 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13172 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13173 int test_x = bad_x + dx, test_y = bad_y + dy;
13174 int test_move_dir, test_element;
13175 int kill_x = -1, kill_y = -1;
13177 if (!IN_LEV_FIELD(test_x, test_y))
13181 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13183 test_element = Feld[test_x][test_y];
13185 if (test_move_dir != bad_move_dir)
13187 /* good thing can be player or penguin that does not move away */
13188 if (IS_PLAYER(test_x, test_y))
13190 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13192 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13193 player as being hit when he is moving towards the bad thing, because
13194 the "get hit by" condition would be lost after the player stops) */
13195 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13196 return; /* player moves away from bad thing */
13201 else if (test_element == EL_PENGUIN)
13208 if (kill_x != -1 || kill_y != -1)
13210 if (IS_PLAYER(kill_x, kill_y))
13212 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13214 if (player->shield_deadly_time_left > 0 &&
13215 !IS_INDESTRUCTIBLE(bad_element))
13216 Bang(bad_x, bad_y);
13217 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13218 KillPlayer(player);
13221 Bang(kill_x, kill_y);
13225 void TestIfPlayerTouchesBadThing(int x, int y)
13227 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13230 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13232 TestIfGoodThingHitsBadThing(x, y, move_dir);
13235 void TestIfBadThingTouchesPlayer(int x, int y)
13237 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13240 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13242 TestIfBadThingHitsGoodThing(x, y, move_dir);
13245 void TestIfFriendTouchesBadThing(int x, int y)
13247 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13250 void TestIfBadThingTouchesFriend(int x, int y)
13252 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13255 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13257 int i, kill_x = bad_x, kill_y = bad_y;
13258 static int xy[4][2] =
13266 for (i = 0; i < NUM_DIRECTIONS; i++)
13270 x = bad_x + xy[i][0];
13271 y = bad_y + xy[i][1];
13272 if (!IN_LEV_FIELD(x, y))
13275 element = Feld[x][y];
13276 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13277 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13285 if (kill_x != bad_x || kill_y != bad_y)
13286 Bang(bad_x, bad_y);
13289 void KillPlayer(struct PlayerInfo *player)
13291 int jx = player->jx, jy = player->jy;
13293 if (!player->active)
13297 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13298 player->killed, player->active, player->reanimated);
13301 /* the following code was introduced to prevent an infinite loop when calling
13303 -> CheckTriggeredElementChangeExt()
13304 -> ExecuteCustomElementAction()
13306 -> (infinitely repeating the above sequence of function calls)
13307 which occurs when killing the player while having a CE with the setting
13308 "kill player X when explosion of <player X>"; the solution using a new
13309 field "player->killed" was chosen for backwards compatibility, although
13310 clever use of the fields "player->active" etc. would probably also work */
13312 if (player->killed)
13316 player->killed = TRUE;
13318 /* remove accessible field at the player's position */
13319 Feld[jx][jy] = EL_EMPTY;
13321 /* deactivate shield (else Bang()/Explode() would not work right) */
13322 player->shield_normal_time_left = 0;
13323 player->shield_deadly_time_left = 0;
13326 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13327 player->killed, player->active, player->reanimated);
13333 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13334 player->killed, player->active, player->reanimated);
13337 if (player->reanimated) /* killed player may have been reanimated */
13338 player->killed = player->reanimated = FALSE;
13340 BuryPlayer(player);
13343 static void KillPlayerUnlessEnemyProtected(int x, int y)
13345 if (!PLAYER_ENEMY_PROTECTED(x, y))
13346 KillPlayer(PLAYERINFO(x, y));
13349 static void KillPlayerUnlessExplosionProtected(int x, int y)
13351 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13352 KillPlayer(PLAYERINFO(x, y));
13355 void BuryPlayer(struct PlayerInfo *player)
13357 int jx = player->jx, jy = player->jy;
13359 if (!player->active)
13362 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13363 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13365 player->GameOver = TRUE;
13366 RemovePlayer(player);
13369 void RemovePlayer(struct PlayerInfo *player)
13371 int jx = player->jx, jy = player->jy;
13372 int i, found = FALSE;
13374 player->present = FALSE;
13375 player->active = FALSE;
13377 if (!ExplodeField[jx][jy])
13378 StorePlayer[jx][jy] = 0;
13380 if (player->is_moving)
13381 TEST_DrawLevelField(player->last_jx, player->last_jy);
13383 for (i = 0; i < MAX_PLAYERS; i++)
13384 if (stored_player[i].active)
13388 AllPlayersGone = TRUE;
13394 static void setFieldForSnapping(int x, int y, int element, int direction)
13396 struct ElementInfo *ei = &element_info[element];
13397 int direction_bit = MV_DIR_TO_BIT(direction);
13398 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13399 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13400 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13402 Feld[x][y] = EL_ELEMENT_SNAPPING;
13403 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13405 ResetGfxAnimation(x, y);
13407 GfxElement[x][y] = element;
13408 GfxAction[x][y] = action;
13409 GfxDir[x][y] = direction;
13410 GfxFrame[x][y] = -1;
13414 =============================================================================
13415 checkDiagonalPushing()
13416 -----------------------------------------------------------------------------
13417 check if diagonal input device direction results in pushing of object
13418 (by checking if the alternative direction is walkable, diggable, ...)
13419 =============================================================================
13422 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13423 int x, int y, int real_dx, int real_dy)
13425 int jx, jy, dx, dy, xx, yy;
13427 if (real_dx == 0 || real_dy == 0) /* no diagonal direction => push */
13430 /* diagonal direction: check alternative direction */
13435 xx = jx + (dx == 0 ? real_dx : 0);
13436 yy = jy + (dy == 0 ? real_dy : 0);
13438 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13442 =============================================================================
13444 -----------------------------------------------------------------------------
13445 x, y: field next to player (non-diagonal) to try to dig to
13446 real_dx, real_dy: direction as read from input device (can be diagonal)
13447 =============================================================================
13450 static int DigField(struct PlayerInfo *player,
13451 int oldx, int oldy, int x, int y,
13452 int real_dx, int real_dy, int mode)
13454 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13455 boolean player_was_pushing = player->is_pushing;
13456 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13457 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13458 int jx = oldx, jy = oldy;
13459 int dx = x - jx, dy = y - jy;
13460 int nextx = x + dx, nexty = y + dy;
13461 int move_direction = (dx == -1 ? MV_LEFT :
13462 dx == +1 ? MV_RIGHT :
13464 dy == +1 ? MV_DOWN : MV_NONE);
13465 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13466 int dig_side = MV_DIR_OPPOSITE(move_direction);
13467 int old_element = Feld[jx][jy];
13468 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13471 if (is_player) /* function can also be called by EL_PENGUIN */
13473 if (player->MovPos == 0)
13475 player->is_digging = FALSE;
13476 player->is_collecting = FALSE;
13479 if (player->MovPos == 0) /* last pushing move finished */
13480 player->is_pushing = FALSE;
13482 if (mode == DF_NO_PUSH) /* player just stopped pushing */
13484 player->is_switching = FALSE;
13485 player->push_delay = -1;
13487 return MP_NO_ACTION;
13491 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13492 old_element = Back[jx][jy];
13494 /* in case of element dropped at player position, check background */
13495 else if (Back[jx][jy] != EL_EMPTY &&
13496 game.engine_version >= VERSION_IDENT(2,2,0,0))
13497 old_element = Back[jx][jy];
13499 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13500 return MP_NO_ACTION; /* field has no opening in this direction */
13502 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13503 return MP_NO_ACTION; /* field has no opening in this direction */
13505 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13509 Feld[jx][jy] = player->artwork_element;
13510 InitMovingField(jx, jy, MV_DOWN);
13511 Store[jx][jy] = EL_ACID;
13512 ContinueMoving(jx, jy);
13513 BuryPlayer(player);
13515 return MP_DONT_RUN_INTO;
13518 if (player_can_move && DONT_RUN_INTO(element))
13520 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13522 return MP_DONT_RUN_INTO;
13525 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13526 return MP_NO_ACTION;
13528 collect_count = element_info[element].collect_count_initial;
13530 if (!is_player && !IS_COLLECTIBLE(element)) /* penguin cannot collect it */
13531 return MP_NO_ACTION;
13533 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13534 player_can_move = player_can_move_or_snap;
13536 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13537 game.engine_version >= VERSION_IDENT(2,2,0,0))
13539 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13540 player->index_bit, dig_side);
13541 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13542 player->index_bit, dig_side);
13544 if (element == EL_DC_LANDMINE)
13547 if (Feld[x][y] != element) /* field changed by snapping */
13550 return MP_NO_ACTION;
13553 if (player->gravity && is_player && !player->is_auto_moving &&
13554 canFallDown(player) && move_direction != MV_DOWN &&
13555 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13556 return MP_NO_ACTION; /* player cannot walk here due to gravity */
13558 if (player_can_move &&
13559 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13561 int sound_element = SND_ELEMENT(element);
13562 int sound_action = ACTION_WALKING;
13564 if (IS_RND_GATE(element))
13566 if (!player->key[RND_GATE_NR(element)])
13567 return MP_NO_ACTION;
13569 else if (IS_RND_GATE_GRAY(element))
13571 if (!player->key[RND_GATE_GRAY_NR(element)])
13572 return MP_NO_ACTION;
13574 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13576 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13577 return MP_NO_ACTION;
13579 else if (element == EL_EXIT_OPEN ||
13580 element == EL_EM_EXIT_OPEN ||
13581 element == EL_EM_EXIT_OPENING ||
13582 element == EL_STEEL_EXIT_OPEN ||
13583 element == EL_EM_STEEL_EXIT_OPEN ||
13584 element == EL_EM_STEEL_EXIT_OPENING ||
13585 element == EL_SP_EXIT_OPEN ||
13586 element == EL_SP_EXIT_OPENING)
13588 sound_action = ACTION_PASSING; /* player is passing exit */
13590 else if (element == EL_EMPTY)
13592 sound_action = ACTION_MOVING; /* nothing to walk on */
13595 /* play sound from background or player, whatever is available */
13596 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13597 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13599 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13601 else if (player_can_move &&
13602 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13604 if (!ACCESS_FROM(element, opposite_direction))
13605 return MP_NO_ACTION; /* field not accessible from this direction */
13607 if (CAN_MOVE(element)) /* only fixed elements can be passed! */
13608 return MP_NO_ACTION;
13610 if (IS_EM_GATE(element))
13612 if (!player->key[EM_GATE_NR(element)])
13613 return MP_NO_ACTION;
13615 else if (IS_EM_GATE_GRAY(element))
13617 if (!player->key[EM_GATE_GRAY_NR(element)])
13618 return MP_NO_ACTION;
13620 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13622 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13623 return MP_NO_ACTION;
13625 else if (IS_EMC_GATE(element))
13627 if (!player->key[EMC_GATE_NR(element)])
13628 return MP_NO_ACTION;
13630 else if (IS_EMC_GATE_GRAY(element))
13632 if (!player->key[EMC_GATE_GRAY_NR(element)])
13633 return MP_NO_ACTION;
13635 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13637 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13638 return MP_NO_ACTION;
13640 else if (element == EL_DC_GATE_WHITE ||
13641 element == EL_DC_GATE_WHITE_GRAY ||
13642 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13644 if (player->num_white_keys == 0)
13645 return MP_NO_ACTION;
13647 player->num_white_keys--;
13649 else if (IS_SP_PORT(element))
13651 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13652 element == EL_SP_GRAVITY_PORT_RIGHT ||
13653 element == EL_SP_GRAVITY_PORT_UP ||
13654 element == EL_SP_GRAVITY_PORT_DOWN)
13655 player->gravity = !player->gravity;
13656 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13657 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13658 element == EL_SP_GRAVITY_ON_PORT_UP ||
13659 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13660 player->gravity = TRUE;
13661 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13662 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13663 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13664 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13665 player->gravity = FALSE;
13668 /* automatically move to the next field with double speed */
13669 player->programmed_action = move_direction;
13671 if (player->move_delay_reset_counter == 0)
13673 player->move_delay_reset_counter = 2; /* two double speed steps */
13675 DOUBLE_PLAYER_SPEED(player);
13678 PlayLevelSoundAction(x, y, ACTION_PASSING);
13680 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13684 if (mode != DF_SNAP)
13686 GfxElement[x][y] = GFX_ELEMENT(element);
13687 player->is_digging = TRUE;
13690 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13692 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13693 player->index_bit, dig_side);
13695 if (mode == DF_SNAP)
13697 if (level.block_snap_field)
13698 setFieldForSnapping(x, y, element, move_direction);
13700 TestIfElementTouchesCustomElement(x, y); /* for empty space */
13702 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13703 player->index_bit, dig_side);
13706 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13710 if (is_player && mode != DF_SNAP)
13712 GfxElement[x][y] = element;
13713 player->is_collecting = TRUE;
13716 if (element == EL_SPEED_PILL)
13718 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13720 else if (element == EL_EXTRA_TIME && level.time > 0)
13722 TimeLeft += level.extra_time;
13724 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13726 DisplayGameControlValues();
13728 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
13730 player->shield_normal_time_left += level.shield_normal_time;
13731 if (element == EL_SHIELD_DEADLY)
13732 player->shield_deadly_time_left += level.shield_deadly_time;
13734 else if (element == EL_DYNAMITE ||
13735 element == EL_EM_DYNAMITE ||
13736 element == EL_SP_DISK_RED)
13738 if (player->inventory_size < MAX_INVENTORY_SIZE)
13739 player->inventory_element[player->inventory_size++] = element;
13741 DrawGameDoorValues();
13743 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
13745 player->dynabomb_count++;
13746 player->dynabombs_left++;
13748 else if (element == EL_DYNABOMB_INCREASE_SIZE)
13750 player->dynabomb_size++;
13752 else if (element == EL_DYNABOMB_INCREASE_POWER)
13754 player->dynabomb_xl = TRUE;
13756 else if (IS_KEY(element))
13758 player->key[KEY_NR(element)] = TRUE;
13760 DrawGameDoorValues();
13762 else if (element == EL_DC_KEY_WHITE)
13764 player->num_white_keys++;
13766 /* display white keys? */
13767 /* DrawGameDoorValues(); */
13769 else if (IS_ENVELOPE(element))
13771 player->show_envelope = element;
13773 else if (element == EL_EMC_LENSES)
13775 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
13777 RedrawAllInvisibleElementsForLenses();
13779 else if (element == EL_EMC_MAGNIFIER)
13781 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
13783 RedrawAllInvisibleElementsForMagnifier();
13785 else if (IS_DROPPABLE(element) ||
13786 IS_THROWABLE(element)) /* can be collected and dropped */
13790 if (collect_count == 0)
13791 player->inventory_infinite_element = element;
13793 for (i = 0; i < collect_count; i++)
13794 if (player->inventory_size < MAX_INVENTORY_SIZE)
13795 player->inventory_element[player->inventory_size++] = element;
13797 DrawGameDoorValues();
13799 else if (collect_count > 0)
13801 local_player->gems_still_needed -= collect_count;
13802 if (local_player->gems_still_needed < 0)
13803 local_player->gems_still_needed = 0;
13805 game.snapshot.collected_item = TRUE;
13807 game_panel_controls[GAME_PANEL_GEMS].value = local_player->gems_still_needed;
13809 DisplayGameControlValues();
13812 RaiseScoreElement(element);
13813 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
13816 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
13817 player->index_bit, dig_side);
13819 if (mode == DF_SNAP)
13821 if (level.block_snap_field)
13822 setFieldForSnapping(x, y, element, move_direction);
13824 TestIfElementTouchesCustomElement(x, y); /* for empty space */
13826 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13827 player->index_bit, dig_side);
13830 else if (player_can_move_or_snap && IS_PUSHABLE(element))
13832 if (mode == DF_SNAP && element != EL_BD_ROCK)
13833 return MP_NO_ACTION;
13835 if (CAN_FALL(element) && dy)
13836 return MP_NO_ACTION;
13838 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
13839 !(element == EL_SPRING && level.use_spring_bug))
13840 return MP_NO_ACTION;
13842 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
13843 ((move_direction & MV_VERTICAL &&
13844 ((element_info[element].move_pattern & MV_LEFT &&
13845 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
13846 (element_info[element].move_pattern & MV_RIGHT &&
13847 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
13848 (move_direction & MV_HORIZONTAL &&
13849 ((element_info[element].move_pattern & MV_UP &&
13850 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
13851 (element_info[element].move_pattern & MV_DOWN &&
13852 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
13853 return MP_NO_ACTION;
13855 /* do not push elements already moving away faster than player */
13856 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
13857 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
13858 return MP_NO_ACTION;
13860 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
13862 if (player->push_delay_value == -1 || !player_was_pushing)
13863 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13865 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13867 if (player->push_delay_value == -1)
13868 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13870 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
13872 if (!player->is_pushing)
13873 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13876 player->is_pushing = TRUE;
13877 player->is_active = TRUE;
13879 if (!(IN_LEV_FIELD(nextx, nexty) &&
13880 (IS_FREE(nextx, nexty) ||
13881 (IS_SB_ELEMENT(element) &&
13882 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
13883 (IS_CUSTOM_ELEMENT(element) &&
13884 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
13885 return MP_NO_ACTION;
13887 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
13888 return MP_NO_ACTION;
13890 if (player->push_delay == -1) /* new pushing; restart delay */
13891 player->push_delay = 0;
13893 if (player->push_delay < player->push_delay_value &&
13894 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
13895 element != EL_SPRING && element != EL_BALLOON)
13897 /* make sure that there is no move delay before next try to push */
13898 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13899 player->move_delay = 0;
13901 return MP_NO_ACTION;
13904 if (IS_CUSTOM_ELEMENT(element) &&
13905 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
13907 if (!DigFieldByCE(nextx, nexty, element))
13908 return MP_NO_ACTION;
13911 if (IS_SB_ELEMENT(element))
13913 if (element == EL_SOKOBAN_FIELD_FULL)
13915 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
13916 local_player->sokobanfields_still_needed++;
13919 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
13921 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
13922 local_player->sokobanfields_still_needed--;
13925 Feld[x][y] = EL_SOKOBAN_OBJECT;
13927 if (Back[x][y] == Back[nextx][nexty])
13928 PlayLevelSoundAction(x, y, ACTION_PUSHING);
13929 else if (Back[x][y] != 0)
13930 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
13933 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
13936 if (local_player->sokobanfields_still_needed == 0 &&
13937 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
13939 PlayerWins(player);
13941 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
13945 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
13947 InitMovingField(x, y, move_direction);
13948 GfxAction[x][y] = ACTION_PUSHING;
13950 if (mode == DF_SNAP)
13951 ContinueMoving(x, y);
13953 MovPos[x][y] = (dx != 0 ? dx : dy);
13955 Pushed[x][y] = TRUE;
13956 Pushed[nextx][nexty] = TRUE;
13958 if (game.engine_version < VERSION_IDENT(2,2,0,7))
13959 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13961 player->push_delay_value = -1; /* get new value later */
13963 /* check for element change _after_ element has been pushed */
13964 if (game.use_change_when_pushing_bug)
13966 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
13967 player->index_bit, dig_side);
13968 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
13969 player->index_bit, dig_side);
13972 else if (IS_SWITCHABLE(element))
13974 if (PLAYER_SWITCHING(player, x, y))
13976 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
13977 player->index_bit, dig_side);
13982 player->is_switching = TRUE;
13983 player->switch_x = x;
13984 player->switch_y = y;
13986 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
13988 if (element == EL_ROBOT_WHEEL)
13990 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
13994 game.robot_wheel_active = TRUE;
13996 TEST_DrawLevelField(x, y);
13998 else if (element == EL_SP_TERMINAL)
14002 SCAN_PLAYFIELD(xx, yy)
14004 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14008 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14010 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14012 ResetGfxAnimation(xx, yy);
14013 TEST_DrawLevelField(xx, yy);
14017 else if (IS_BELT_SWITCH(element))
14019 ToggleBeltSwitch(x, y);
14021 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14022 element == EL_SWITCHGATE_SWITCH_DOWN ||
14023 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14024 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14026 ToggleSwitchgateSwitch(x, y);
14028 else if (element == EL_LIGHT_SWITCH ||
14029 element == EL_LIGHT_SWITCH_ACTIVE)
14031 ToggleLightSwitch(x, y);
14033 else if (element == EL_TIMEGATE_SWITCH ||
14034 element == EL_DC_TIMEGATE_SWITCH)
14036 ActivateTimegateSwitch(x, y);
14038 else if (element == EL_BALLOON_SWITCH_LEFT ||
14039 element == EL_BALLOON_SWITCH_RIGHT ||
14040 element == EL_BALLOON_SWITCH_UP ||
14041 element == EL_BALLOON_SWITCH_DOWN ||
14042 element == EL_BALLOON_SWITCH_NONE ||
14043 element == EL_BALLOON_SWITCH_ANY)
14045 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14046 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14047 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14048 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14049 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14052 else if (element == EL_LAMP)
14054 Feld[x][y] = EL_LAMP_ACTIVE;
14055 local_player->lights_still_needed--;
14057 ResetGfxAnimation(x, y);
14058 TEST_DrawLevelField(x, y);
14060 else if (element == EL_TIME_ORB_FULL)
14062 Feld[x][y] = EL_TIME_ORB_EMPTY;
14064 if (level.time > 0 || level.use_time_orb_bug)
14066 TimeLeft += level.time_orb_time;
14067 game.no_time_limit = FALSE;
14069 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14071 DisplayGameControlValues();
14074 ResetGfxAnimation(x, y);
14075 TEST_DrawLevelField(x, y);
14077 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14078 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14082 game.ball_state = !game.ball_state;
14084 SCAN_PLAYFIELD(xx, yy)
14086 int e = Feld[xx][yy];
14088 if (game.ball_state)
14090 if (e == EL_EMC_MAGIC_BALL)
14091 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14092 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14093 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14097 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14098 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14099 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14100 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14105 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14106 player->index_bit, dig_side);
14108 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14109 player->index_bit, dig_side);
14111 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14112 player->index_bit, dig_side);
14118 if (!PLAYER_SWITCHING(player, x, y))
14120 player->is_switching = TRUE;
14121 player->switch_x = x;
14122 player->switch_y = y;
14124 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14125 player->index_bit, dig_side);
14126 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14127 player->index_bit, dig_side);
14129 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14130 player->index_bit, dig_side);
14131 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14132 player->index_bit, dig_side);
14135 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14136 player->index_bit, dig_side);
14137 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14138 player->index_bit, dig_side);
14140 return MP_NO_ACTION;
14143 player->push_delay = -1;
14145 if (is_player) /* function can also be called by EL_PENGUIN */
14147 if (Feld[x][y] != element) /* really digged/collected something */
14149 player->is_collecting = !player->is_digging;
14150 player->is_active = TRUE;
14157 static boolean DigFieldByCE(int x, int y, int digging_element)
14159 int element = Feld[x][y];
14161 if (!IS_FREE(x, y))
14163 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14164 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14167 /* no element can dig solid indestructible elements */
14168 if (IS_INDESTRUCTIBLE(element) &&
14169 !IS_DIGGABLE(element) &&
14170 !IS_COLLECTIBLE(element))
14173 if (AmoebaNr[x][y] &&
14174 (element == EL_AMOEBA_FULL ||
14175 element == EL_BD_AMOEBA ||
14176 element == EL_AMOEBA_GROWING))
14178 AmoebaCnt[AmoebaNr[x][y]]--;
14179 AmoebaCnt2[AmoebaNr[x][y]]--;
14182 if (IS_MOVING(x, y))
14183 RemoveMovingField(x, y);
14187 TEST_DrawLevelField(x, y);
14190 /* if digged element was about to explode, prevent the explosion */
14191 ExplodeField[x][y] = EX_TYPE_NONE;
14193 PlayLevelSoundAction(x, y, action);
14196 Store[x][y] = EL_EMPTY;
14198 /* this makes it possible to leave the removed element again */
14199 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14200 Store[x][y] = element;
14205 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14207 int jx = player->jx, jy = player->jy;
14208 int x = jx + dx, y = jy + dy;
14209 int snap_direction = (dx == -1 ? MV_LEFT :
14210 dx == +1 ? MV_RIGHT :
14212 dy == +1 ? MV_DOWN : MV_NONE);
14213 boolean can_continue_snapping = (level.continuous_snapping &&
14214 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14216 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14219 if (!player->active || !IN_LEV_FIELD(x, y))
14227 if (player->MovPos == 0)
14228 player->is_pushing = FALSE;
14230 player->is_snapping = FALSE;
14232 if (player->MovPos == 0)
14234 player->is_moving = FALSE;
14235 player->is_digging = FALSE;
14236 player->is_collecting = FALSE;
14242 /* prevent snapping with already pressed snap key when not allowed */
14243 if (player->is_snapping && !can_continue_snapping)
14246 player->MovDir = snap_direction;
14248 if (player->MovPos == 0)
14250 player->is_moving = FALSE;
14251 player->is_digging = FALSE;
14252 player->is_collecting = FALSE;
14255 player->is_dropping = FALSE;
14256 player->is_dropping_pressed = FALSE;
14257 player->drop_pressed_delay = 0;
14259 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14262 player->is_snapping = TRUE;
14263 player->is_active = TRUE;
14265 if (player->MovPos == 0)
14267 player->is_moving = FALSE;
14268 player->is_digging = FALSE;
14269 player->is_collecting = FALSE;
14272 if (player->MovPos != 0) /* prevent graphic bugs in versions < 2.2.0 */
14273 TEST_DrawLevelField(player->last_jx, player->last_jy);
14275 TEST_DrawLevelField(x, y);
14280 static boolean DropElement(struct PlayerInfo *player)
14282 int old_element, new_element;
14283 int dropx = player->jx, dropy = player->jy;
14284 int drop_direction = player->MovDir;
14285 int drop_side = drop_direction;
14286 int drop_element = get_next_dropped_element(player);
14288 /* do not drop an element on top of another element; when holding drop key
14289 pressed without moving, dropped element must move away before the next
14290 element can be dropped (this is especially important if the next element
14291 is dynamite, which can be placed on background for historical reasons) */
14292 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14295 if (IS_THROWABLE(drop_element))
14297 dropx += GET_DX_FROM_DIR(drop_direction);
14298 dropy += GET_DY_FROM_DIR(drop_direction);
14300 if (!IN_LEV_FIELD(dropx, dropy))
14304 old_element = Feld[dropx][dropy]; /* old element at dropping position */
14305 new_element = drop_element; /* default: no change when dropping */
14307 /* check if player is active, not moving and ready to drop */
14308 if (!player->active || player->MovPos || player->drop_delay > 0)
14311 /* check if player has anything that can be dropped */
14312 if (new_element == EL_UNDEFINED)
14315 /* only set if player has anything that can be dropped */
14316 player->is_dropping_pressed = TRUE;
14318 /* check if drop key was pressed long enough for EM style dynamite */
14319 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14322 /* check if anything can be dropped at the current position */
14323 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14326 /* collected custom elements can only be dropped on empty fields */
14327 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14330 if (old_element != EL_EMPTY)
14331 Back[dropx][dropy] = old_element; /* store old element on this field */
14333 ResetGfxAnimation(dropx, dropy);
14334 ResetRandomAnimationValue(dropx, dropy);
14336 if (player->inventory_size > 0 ||
14337 player->inventory_infinite_element != EL_UNDEFINED)
14339 if (player->inventory_size > 0)
14341 player->inventory_size--;
14343 DrawGameDoorValues();
14345 if (new_element == EL_DYNAMITE)
14346 new_element = EL_DYNAMITE_ACTIVE;
14347 else if (new_element == EL_EM_DYNAMITE)
14348 new_element = EL_EM_DYNAMITE_ACTIVE;
14349 else if (new_element == EL_SP_DISK_RED)
14350 new_element = EL_SP_DISK_RED_ACTIVE;
14353 Feld[dropx][dropy] = new_element;
14355 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14356 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14357 el2img(Feld[dropx][dropy]), 0);
14359 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14361 /* needed if previous element just changed to "empty" in the last frame */
14362 ChangeCount[dropx][dropy] = 0; /* allow at least one more change */
14364 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14365 player->index_bit, drop_side);
14366 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14368 player->index_bit, drop_side);
14370 TestIfElementTouchesCustomElement(dropx, dropy);
14372 else /* player is dropping a dyna bomb */
14374 player->dynabombs_left--;
14376 Feld[dropx][dropy] = new_element;
14378 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14379 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14380 el2img(Feld[dropx][dropy]), 0);
14382 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14385 if (Feld[dropx][dropy] == new_element) /* uninitialized unless CE change */
14386 InitField_WithBug1(dropx, dropy, FALSE);
14388 new_element = Feld[dropx][dropy]; /* element might have changed */
14390 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14391 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14393 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14394 MovDir[dropx][dropy] = drop_direction;
14396 ChangeCount[dropx][dropy] = 0; /* allow at least one more change */
14398 /* do not cause impact style collision by dropping elements that can fall */
14399 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14402 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14403 player->is_dropping = TRUE;
14405 player->drop_pressed_delay = 0;
14406 player->is_dropping_pressed = FALSE;
14408 player->drop_x = dropx;
14409 player->drop_y = dropy;
14414 /* ------------------------------------------------------------------------- */
14415 /* game sound playing functions */
14416 /* ------------------------------------------------------------------------- */
14418 static int *loop_sound_frame = NULL;
14419 static int *loop_sound_volume = NULL;
14421 void InitPlayLevelSound()
14423 int num_sounds = getSoundListSize();
14425 checked_free(loop_sound_frame);
14426 checked_free(loop_sound_volume);
14428 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14429 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14432 static void PlayLevelSound(int x, int y, int nr)
14434 int sx = SCREENX(x), sy = SCREENY(y);
14435 int volume, stereo_position;
14436 int max_distance = 8;
14437 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14439 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14440 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14443 if (!IN_LEV_FIELD(x, y) ||
14444 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14445 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14448 volume = SOUND_MAX_VOLUME;
14450 if (!IN_SCR_FIELD(sx, sy))
14452 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14453 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14455 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14458 stereo_position = (SOUND_MAX_LEFT +
14459 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14460 (SCR_FIELDX + 2 * max_distance));
14462 if (IS_LOOP_SOUND(nr))
14464 /* This assures that quieter loop sounds do not overwrite louder ones,
14465 while restarting sound volume comparison with each new game frame. */
14467 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14470 loop_sound_volume[nr] = volume;
14471 loop_sound_frame[nr] = FrameCounter;
14474 PlaySoundExt(nr, volume, stereo_position, type);
14477 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14479 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14480 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14481 y < LEVELY(BY1) ? LEVELY(BY1) :
14482 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14486 static void PlayLevelSoundAction(int x, int y, int action)
14488 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14491 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14493 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14495 if (sound_effect != SND_UNDEFINED)
14496 PlayLevelSound(x, y, sound_effect);
14499 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14502 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14504 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14505 PlayLevelSound(x, y, sound_effect);
14508 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14510 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14512 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14513 PlayLevelSound(x, y, sound_effect);
14516 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14518 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14520 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14521 StopSound(sound_effect);
14524 static int getLevelMusicNr()
14526 if (levelset.music[level_nr] != MUS_UNDEFINED)
14527 return levelset.music[level_nr]; /* from config file */
14529 return MAP_NOCONF_MUSIC(level_nr); /* from music dir */
14532 static void FadeLevelSounds()
14537 static void FadeLevelMusic()
14539 int music_nr = getLevelMusicNr();
14540 char *curr_music = getCurrentlyPlayingMusicFilename();
14541 char *next_music = getMusicInfoEntryFilename(music_nr);
14543 if (!strEqual(curr_music, next_music))
14547 void FadeLevelSoundsAndMusic()
14553 static void PlayLevelMusic()
14555 int music_nr = getLevelMusicNr();
14556 char *curr_music = getCurrentlyPlayingMusicFilename();
14557 char *next_music = getMusicInfoEntryFilename(music_nr);
14559 if (!strEqual(curr_music, next_music))
14560 PlayMusic(music_nr);
14563 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14565 int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0);
14566 int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
14567 int x = xx - 1 - offset;
14568 int y = yy - 1 - offset;
14573 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14577 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14581 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14585 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14589 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14593 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14597 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14600 case SAMPLE_android_clone:
14601 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14604 case SAMPLE_android_move:
14605 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14608 case SAMPLE_spring:
14609 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14613 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14617 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14620 case SAMPLE_eater_eat:
14621 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14625 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14628 case SAMPLE_collect:
14629 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14632 case SAMPLE_diamond:
14633 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14636 case SAMPLE_squash:
14637 /* !!! CHECK THIS !!! */
14639 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14641 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14645 case SAMPLE_wonderfall:
14646 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14650 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14654 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14658 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14662 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14666 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14670 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14673 case SAMPLE_wonder:
14674 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14678 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14681 case SAMPLE_exit_open:
14682 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14685 case SAMPLE_exit_leave:
14686 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14689 case SAMPLE_dynamite:
14690 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14694 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14698 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14702 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14706 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14710 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
14714 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
14718 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
14723 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
14725 int element = map_element_SP_to_RND(element_sp);
14726 int action = map_action_SP_to_RND(action_sp);
14727 int offset = (setup.sp_show_border_elements ? 0 : 1);
14728 int x = xx - offset;
14729 int y = yy - offset;
14731 PlayLevelSoundElementAction(x, y, element, action);
14734 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
14736 int element = map_element_MM_to_RND(element_mm);
14737 int action = map_action_MM_to_RND(action_mm);
14739 int x = xx - offset;
14740 int y = yy - offset;
14742 if (!IS_MM_ELEMENT(element))
14743 element = EL_MM_DEFAULT;
14745 PlayLevelSoundElementAction(x, y, element, action);
14748 void PlaySound_MM(int sound_mm)
14750 int sound = map_sound_MM_to_RND(sound_mm);
14752 if (sound == SND_UNDEFINED)
14758 void PlaySoundLoop_MM(int sound_mm)
14760 int sound = map_sound_MM_to_RND(sound_mm);
14762 if (sound == SND_UNDEFINED)
14765 PlaySoundLoop(sound);
14768 void StopSound_MM(int sound_mm)
14770 int sound = map_sound_MM_to_RND(sound_mm);
14772 if (sound == SND_UNDEFINED)
14778 void RaiseScore(int value)
14780 local_player->score += value;
14782 game_panel_controls[GAME_PANEL_SCORE].value = local_player->score;
14784 DisplayGameControlValues();
14787 void RaiseScoreElement(int element)
14792 case EL_BD_DIAMOND:
14793 case EL_EMERALD_YELLOW:
14794 case EL_EMERALD_RED:
14795 case EL_EMERALD_PURPLE:
14796 case EL_SP_INFOTRON:
14797 RaiseScore(level.score[SC_EMERALD]);
14800 RaiseScore(level.score[SC_DIAMOND]);
14803 RaiseScore(level.score[SC_CRYSTAL]);
14806 RaiseScore(level.score[SC_PEARL]);
14809 case EL_BD_BUTTERFLY:
14810 case EL_SP_ELECTRON:
14811 RaiseScore(level.score[SC_BUG]);
14814 case EL_BD_FIREFLY:
14815 case EL_SP_SNIKSNAK:
14816 RaiseScore(level.score[SC_SPACESHIP]);
14819 case EL_DARK_YAMYAM:
14820 RaiseScore(level.score[SC_YAMYAM]);
14823 RaiseScore(level.score[SC_ROBOT]);
14826 RaiseScore(level.score[SC_PACMAN]);
14829 RaiseScore(level.score[SC_NUT]);
14832 case EL_EM_DYNAMITE:
14833 case EL_SP_DISK_RED:
14834 case EL_DYNABOMB_INCREASE_NUMBER:
14835 case EL_DYNABOMB_INCREASE_SIZE:
14836 case EL_DYNABOMB_INCREASE_POWER:
14837 RaiseScore(level.score[SC_DYNAMITE]);
14839 case EL_SHIELD_NORMAL:
14840 case EL_SHIELD_DEADLY:
14841 RaiseScore(level.score[SC_SHIELD]);
14843 case EL_EXTRA_TIME:
14844 RaiseScore(level.extra_time_score);
14858 case EL_DC_KEY_WHITE:
14859 RaiseScore(level.score[SC_KEY]);
14862 RaiseScore(element_info[element].collect_score);
14867 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
14869 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
14871 /* closing door required in case of envelope style request dialogs */
14873 CloseDoor(DOOR_CLOSE_1);
14875 if (network.enabled)
14876 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
14880 FadeSkipNextFadeIn();
14882 SetGameStatus(GAME_MODE_MAIN);
14887 else /* continue playing the game */
14889 if (tape.playing && tape.deactivate_display)
14890 TapeDeactivateDisplayOff(TRUE);
14892 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
14894 if (tape.playing && tape.deactivate_display)
14895 TapeDeactivateDisplayOn();
14899 void RequestQuitGame(boolean ask_if_really_quit)
14901 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
14902 boolean skip_request = AllPlayersGone || quick_quit;
14904 RequestQuitGameExt(skip_request, quick_quit,
14905 "Do you really want to quit the game?");
14908 void RequestRestartGame(char *message)
14910 game.restart_game_message = NULL;
14912 if (Request(message, REQ_ASK | REQ_STAY_CLOSED))
14914 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
14918 SetGameStatus(GAME_MODE_MAIN);
14925 /* ------------------------------------------------------------------------- */
14926 /* random generator functions */
14927 /* ------------------------------------------------------------------------- */
14929 unsigned int InitEngineRandom_RND(int seed)
14931 game.num_random_calls = 0;
14933 return InitEngineRandom(seed);
14936 unsigned int RND(int max)
14940 game.num_random_calls++;
14942 return GetEngineRandom(max);
14949 /* ------------------------------------------------------------------------- */
14950 /* game engine snapshot handling functions */
14951 /* ------------------------------------------------------------------------- */
14953 struct EngineSnapshotInfo
14955 /* runtime values for custom element collect score */
14956 int collect_score[NUM_CUSTOM_ELEMENTS];
14958 /* runtime values for group element choice position */
14959 int choice_pos[NUM_GROUP_ELEMENTS];
14961 /* runtime values for belt position animations */
14962 int belt_graphic[4][NUM_BELT_PARTS];
14963 int belt_anim_mode[4][NUM_BELT_PARTS];
14966 static struct EngineSnapshotInfo engine_snapshot_rnd;
14967 static char *snapshot_level_identifier = NULL;
14968 static int snapshot_level_nr = -1;
14970 static void SaveEngineSnapshotValues_RND()
14972 static int belt_base_active_element[4] =
14974 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
14975 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
14976 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
14977 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
14981 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
14983 int element = EL_CUSTOM_START + i;
14985 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
14988 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
14990 int element = EL_GROUP_START + i;
14992 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
14995 for (i = 0; i < 4; i++)
14997 for (j = 0; j < NUM_BELT_PARTS; j++)
14999 int element = belt_base_active_element[i] + j;
15000 int graphic = el2img(element);
15001 int anim_mode = graphic_info[graphic].anim_mode;
15003 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15004 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15009 static void LoadEngineSnapshotValues_RND()
15011 unsigned int num_random_calls = game.num_random_calls;
15014 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15016 int element = EL_CUSTOM_START + i;
15018 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15021 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15023 int element = EL_GROUP_START + i;
15025 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15028 for (i = 0; i < 4; i++)
15030 for (j = 0; j < NUM_BELT_PARTS; j++)
15032 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15033 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15035 graphic_info[graphic].anim_mode = anim_mode;
15039 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15041 InitRND(tape.random_seed);
15042 for (i = 0; i < num_random_calls; i++)
15046 if (game.num_random_calls != num_random_calls)
15048 Error(ERR_INFO, "number of random calls out of sync");
15049 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15050 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15051 Error(ERR_EXIT, "this should not happen -- please debug");
15055 void FreeEngineSnapshotSingle()
15057 FreeSnapshotSingle();
15059 setString(&snapshot_level_identifier, NULL);
15060 snapshot_level_nr = -1;
15063 void FreeEngineSnapshotList()
15065 FreeSnapshotList();
15068 ListNode *SaveEngineSnapshotBuffers()
15070 ListNode *buffers = NULL;
15072 /* copy some special values to a structure better suited for the snapshot */
15074 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15075 SaveEngineSnapshotValues_RND();
15076 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15077 SaveEngineSnapshotValues_EM();
15078 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15079 SaveEngineSnapshotValues_SP(&buffers);
15080 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15081 SaveEngineSnapshotValues_MM(&buffers);
15083 /* save values stored in special snapshot structure */
15085 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15086 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15087 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15088 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15089 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15090 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15091 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15092 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15094 /* save further RND engine values */
15096 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15097 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15098 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15100 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZX));
15101 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZY));
15102 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitX));
15103 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitY));
15105 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15106 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15107 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15108 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15109 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15111 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15112 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15113 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15115 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15117 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AllPlayersGone));
15119 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15120 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15122 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15123 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15124 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15125 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15126 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15127 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15128 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15129 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15130 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15131 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15132 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15133 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15134 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15135 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15136 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15137 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15138 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15139 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15141 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15142 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15144 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15145 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15146 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15148 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15149 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15151 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15152 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15153 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15154 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15155 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15157 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15158 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15161 ListNode *node = engine_snapshot_list_rnd;
15164 while (node != NULL)
15166 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15171 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15177 void SaveEngineSnapshotSingle()
15179 ListNode *buffers = SaveEngineSnapshotBuffers();
15181 /* finally save all snapshot buffers to single snapshot */
15182 SaveSnapshotSingle(buffers);
15184 /* save level identification information */
15185 setString(&snapshot_level_identifier, leveldir_current->identifier);
15186 snapshot_level_nr = level_nr;
15189 boolean CheckSaveEngineSnapshotToList()
15191 boolean save_snapshot =
15192 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15193 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15194 game.snapshot.changed_action) ||
15195 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15196 game.snapshot.collected_item));
15198 game.snapshot.changed_action = FALSE;
15199 game.snapshot.collected_item = FALSE;
15200 game.snapshot.save_snapshot = save_snapshot;
15202 return save_snapshot;
15205 void SaveEngineSnapshotToList()
15207 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15211 ListNode *buffers = SaveEngineSnapshotBuffers();
15213 /* finally save all snapshot buffers to snapshot list */
15214 SaveSnapshotToList(buffers);
15217 void SaveEngineSnapshotToListInitial()
15219 FreeEngineSnapshotList();
15221 SaveEngineSnapshotToList();
15224 void LoadEngineSnapshotValues()
15226 /* restore special values from snapshot structure */
15228 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15229 LoadEngineSnapshotValues_RND();
15230 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15231 LoadEngineSnapshotValues_EM();
15232 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15233 LoadEngineSnapshotValues_SP();
15234 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15235 LoadEngineSnapshotValues_MM();
15238 void LoadEngineSnapshotSingle()
15240 LoadSnapshotSingle();
15242 LoadEngineSnapshotValues();
15245 void LoadEngineSnapshot_Undo(int steps)
15247 LoadSnapshotFromList_Older(steps);
15249 LoadEngineSnapshotValues();
15252 void LoadEngineSnapshot_Redo(int steps)
15254 LoadSnapshotFromList_Newer(steps);
15256 LoadEngineSnapshotValues();
15259 boolean CheckEngineSnapshotSingle()
15261 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15262 snapshot_level_nr == level_nr);
15265 boolean CheckEngineSnapshotList()
15267 return CheckSnapshotList();
15271 /* ---------- new game button stuff ---------------------------------------- */
15278 boolean *setup_value;
15279 boolean allowed_on_tape;
15281 } gamebutton_info[NUM_GAME_BUTTONS] =
15284 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15285 GAME_CTRL_ID_STOP, NULL,
15289 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15290 GAME_CTRL_ID_PAUSE, NULL,
15294 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15295 GAME_CTRL_ID_PLAY, NULL,
15299 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15300 GAME_CTRL_ID_UNDO, NULL,
15304 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15305 GAME_CTRL_ID_REDO, NULL,
15309 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15310 GAME_CTRL_ID_SAVE, NULL,
15314 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15315 GAME_CTRL_ID_PAUSE2, NULL,
15319 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15320 GAME_CTRL_ID_LOAD, NULL,
15324 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15325 GAME_CTRL_ID_PANEL_STOP, NULL,
15329 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15330 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15331 FALSE, "pause game"
15334 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15335 GAME_CTRL_ID_PANEL_PLAY, NULL,
15339 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15340 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15341 TRUE, "background music on/off"
15344 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15345 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15346 TRUE, "sound loops on/off"
15349 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15350 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15351 TRUE, "normal sounds on/off"
15354 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15355 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15356 FALSE, "background music on/off"
15359 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15360 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15361 FALSE, "sound loops on/off"
15364 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15365 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15366 FALSE, "normal sounds on/off"
15370 void CreateGameButtons()
15374 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15376 int graphic = gamebutton_info[i].graphic;
15377 struct GraphicInfo *gfx = &graphic_info[graphic];
15378 struct XY *pos = gamebutton_info[i].pos;
15379 struct GadgetInfo *gi;
15382 unsigned int event_mask;
15383 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15384 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15385 int base_x = (on_tape ? VX : DX);
15386 int base_y = (on_tape ? VY : DY);
15387 int gd_x = gfx->src_x;
15388 int gd_y = gfx->src_y;
15389 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15390 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15391 int gd_xa = gfx->src_x + gfx->active_xoffset;
15392 int gd_ya = gfx->src_y + gfx->active_yoffset;
15393 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15394 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15397 if (gfx->bitmap == NULL)
15399 game_gadget[id] = NULL;
15404 if (id == GAME_CTRL_ID_STOP ||
15405 id == GAME_CTRL_ID_PANEL_STOP ||
15406 id == GAME_CTRL_ID_PLAY ||
15407 id == GAME_CTRL_ID_PANEL_PLAY ||
15408 id == GAME_CTRL_ID_SAVE ||
15409 id == GAME_CTRL_ID_LOAD)
15411 button_type = GD_TYPE_NORMAL_BUTTON;
15413 event_mask = GD_EVENT_RELEASED;
15415 else if (id == GAME_CTRL_ID_UNDO ||
15416 id == GAME_CTRL_ID_REDO)
15418 button_type = GD_TYPE_NORMAL_BUTTON;
15420 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15424 button_type = GD_TYPE_CHECK_BUTTON;
15425 checked = (gamebutton_info[i].setup_value != NULL ?
15426 *gamebutton_info[i].setup_value : FALSE);
15427 event_mask = GD_EVENT_PRESSED;
15430 gi = CreateGadget(GDI_CUSTOM_ID, id,
15431 GDI_IMAGE_ID, graphic,
15432 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15433 GDI_X, base_x + GDI_ACTIVE_POS(pos->x),
15434 GDI_Y, base_y + GDI_ACTIVE_POS(pos->y),
15435 GDI_WIDTH, gfx->width,
15436 GDI_HEIGHT, gfx->height,
15437 GDI_TYPE, button_type,
15438 GDI_STATE, GD_BUTTON_UNPRESSED,
15439 GDI_CHECKED, checked,
15440 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15441 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15442 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15443 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15444 GDI_DIRECT_DRAW, FALSE,
15445 GDI_EVENT_MASK, event_mask,
15446 GDI_CALLBACK_ACTION, HandleGameButtons,
15450 Error(ERR_EXIT, "cannot create gadget");
15452 game_gadget[id] = gi;
15456 void FreeGameButtons()
15460 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15461 FreeGadget(game_gadget[i]);
15464 static void UnmapGameButtonsAtSamePosition(int id)
15468 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15470 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15471 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15472 UnmapGadget(game_gadget[i]);
15475 static void UnmapGameButtonsAtSamePosition_All()
15477 if (setup.show_snapshot_buttons)
15479 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15480 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15481 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15485 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15486 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15487 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15489 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15490 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15491 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15495 static void MapGameButtonsAtSamePosition(int id)
15499 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15501 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15502 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15503 MapGadget(game_gadget[i]);
15505 UnmapGameButtonsAtSamePosition_All();
15508 void MapUndoRedoButtons()
15510 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15511 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15513 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15514 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15516 ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, TRUE, GDI_END);
15519 void UnmapUndoRedoButtons()
15521 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15522 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15524 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15525 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15527 ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, FALSE, GDI_END);
15530 void MapGameButtonsExt(boolean on_tape)
15534 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15535 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15536 i != GAME_CTRL_ID_UNDO &&
15537 i != GAME_CTRL_ID_REDO)
15538 MapGadget(game_gadget[i]);
15540 UnmapGameButtonsAtSamePosition_All();
15542 RedrawGameButtons();
15545 void UnmapGameButtonsExt(boolean on_tape)
15549 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15550 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15551 UnmapGadget(game_gadget[i]);
15554 void RedrawGameButtonsExt(boolean on_tape)
15558 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15559 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15560 RedrawGadget(game_gadget[i]);
15562 // RedrawGadget() may have set REDRAW_ALL if buttons are defined off-area
15563 redraw_mask &= ~REDRAW_ALL;
15566 void SetGadgetState(struct GadgetInfo *gi, boolean state)
15571 gi->checked = state;
15574 void RedrawSoundButtonGadget(int id)
15576 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15577 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15578 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15579 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15580 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15581 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15584 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15585 RedrawGadget(game_gadget[id2]);
15588 void MapGameButtons()
15590 MapGameButtonsExt(FALSE);
15593 void UnmapGameButtons()
15595 UnmapGameButtonsExt(FALSE);
15598 void RedrawGameButtons()
15600 RedrawGameButtonsExt(FALSE);
15603 void MapGameButtonsOnTape()
15605 MapGameButtonsExt(TRUE);
15608 void UnmapGameButtonsOnTape()
15610 UnmapGameButtonsExt(TRUE);
15613 void RedrawGameButtonsOnTape()
15615 RedrawGameButtonsExt(TRUE);
15618 void GameUndoRedoExt()
15620 ClearPlayerAction();
15622 tape.pausing = TRUE;
15625 UpdateAndDisplayGameControlValues();
15627 DrawCompleteVideoDisplay();
15628 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
15629 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
15630 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
15635 void GameUndo(int steps)
15637 if (!CheckEngineSnapshotList())
15640 LoadEngineSnapshot_Undo(steps);
15645 void GameRedo(int steps)
15647 if (!CheckEngineSnapshotList())
15650 LoadEngineSnapshot_Redo(steps);
15655 static void HandleGameButtonsExt(int id, int button)
15657 static boolean game_undo_executed = FALSE;
15658 int steps = BUTTON_STEPSIZE(button);
15659 boolean handle_game_buttons =
15660 (game_status == GAME_MODE_PLAYING ||
15661 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
15663 if (!handle_game_buttons)
15668 case GAME_CTRL_ID_STOP:
15669 case GAME_CTRL_ID_PANEL_STOP:
15670 if (game_status == GAME_MODE_MAIN)
15676 RequestQuitGame(TRUE);
15680 case GAME_CTRL_ID_PAUSE:
15681 case GAME_CTRL_ID_PAUSE2:
15682 case GAME_CTRL_ID_PANEL_PAUSE:
15683 if (network.enabled && game_status == GAME_MODE_PLAYING)
15686 SendToServer_ContinuePlaying();
15688 SendToServer_PausePlaying();
15691 TapeTogglePause(TAPE_TOGGLE_MANUAL);
15693 game_undo_executed = FALSE;
15697 case GAME_CTRL_ID_PLAY:
15698 case GAME_CTRL_ID_PANEL_PLAY:
15699 if (game_status == GAME_MODE_MAIN)
15701 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15703 else if (tape.pausing)
15705 if (network.enabled)
15706 SendToServer_ContinuePlaying();
15708 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
15712 case GAME_CTRL_ID_UNDO:
15713 // Important: When using "save snapshot when collecting an item" mode,
15714 // load last (current) snapshot for first "undo" after pressing "pause"
15715 // (else the last-but-one snapshot would be loaded, because the snapshot
15716 // pointer already points to the last snapshot when pressing "pause",
15717 // which is fine for "every step/move" mode, but not for "every collect")
15718 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15719 !game_undo_executed)
15722 game_undo_executed = TRUE;
15727 case GAME_CTRL_ID_REDO:
15731 case GAME_CTRL_ID_SAVE:
15735 case GAME_CTRL_ID_LOAD:
15739 case SOUND_CTRL_ID_MUSIC:
15740 case SOUND_CTRL_ID_PANEL_MUSIC:
15741 if (setup.sound_music)
15743 setup.sound_music = FALSE;
15747 else if (audio.music_available)
15749 setup.sound = setup.sound_music = TRUE;
15751 SetAudioMode(setup.sound);
15753 if (game_status == GAME_MODE_PLAYING)
15757 RedrawSoundButtonGadget(id);
15761 case SOUND_CTRL_ID_LOOPS:
15762 case SOUND_CTRL_ID_PANEL_LOOPS:
15763 if (setup.sound_loops)
15764 setup.sound_loops = FALSE;
15765 else if (audio.loops_available)
15767 setup.sound = setup.sound_loops = TRUE;
15769 SetAudioMode(setup.sound);
15772 RedrawSoundButtonGadget(id);
15776 case SOUND_CTRL_ID_SIMPLE:
15777 case SOUND_CTRL_ID_PANEL_SIMPLE:
15778 if (setup.sound_simple)
15779 setup.sound_simple = FALSE;
15780 else if (audio.sound_available)
15782 setup.sound = setup.sound_simple = TRUE;
15784 SetAudioMode(setup.sound);
15787 RedrawSoundButtonGadget(id);
15796 static void HandleGameButtons(struct GadgetInfo *gi)
15798 HandleGameButtonsExt(gi->custom_id, gi->event.button);
15801 void HandleSoundButtonKeys(Key key)
15803 if (key == setup.shortcut.sound_simple)
15804 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
15805 else if (key == setup.shortcut.sound_loops)
15806 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
15807 else if (key == setup.shortcut.sound_music)
15808 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);