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 boolean raise_level = FALSE;
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)
4696 raise_level = TRUE; /* advance to next level */
4698 if ((hi_pos = NewHiScore(level_nr)) >= 0)
4700 SetGameStatus(GAME_MODE_SCORES);
4702 DrawHallOfFame(level_nr, hi_pos);
4712 SetGameStatus(GAME_MODE_MAIN);
4724 int NewHiScore(int level_nr)
4728 boolean one_score_entry_per_name = !program.many_scores_per_name;
4730 LoadScore(level_nr);
4732 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4733 local_player->score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4736 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4738 if (local_player->score_final > highscore[k].Score)
4740 /* player has made it to the hall of fame */
4742 if (k < MAX_SCORE_ENTRIES - 1)
4744 int m = MAX_SCORE_ENTRIES - 1;
4746 if (one_score_entry_per_name)
4748 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4749 if (strEqual(setup.player_name, highscore[l].Name))
4752 if (m == k) /* player's new highscore overwrites his old one */
4756 for (l = m; l > k; l--)
4758 strcpy(highscore[l].Name, highscore[l - 1].Name);
4759 highscore[l].Score = highscore[l - 1].Score;
4765 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4766 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4767 highscore[k].Score = local_player->score_final;
4772 else if (one_score_entry_per_name &&
4773 !strncmp(setup.player_name, highscore[k].Name,
4774 MAX_PLAYER_NAME_LEN))
4775 break; /* player already there with a higher score */
4779 SaveScore(level_nr);
4784 inline static int getElementMoveStepsizeExt(int x, int y, int direction)
4786 int element = Feld[x][y];
4787 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4788 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4789 int horiz_move = (dx != 0);
4790 int sign = (horiz_move ? dx : dy);
4791 int step = sign * element_info[element].move_stepsize;
4793 /* special values for move stepsize for spring and things on conveyor belt */
4796 if (CAN_FALL(element) &&
4797 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
4798 step = sign * MOVE_STEPSIZE_NORMAL / 2;
4799 else if (element == EL_SPRING)
4800 step = sign * MOVE_STEPSIZE_NORMAL * 2;
4806 inline static int getElementMoveStepsize(int x, int y)
4808 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
4811 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
4813 if (player->GfxAction != action || player->GfxDir != dir)
4815 player->GfxAction = action;
4816 player->GfxDir = dir;
4818 player->StepFrame = 0;
4822 static void ResetGfxFrame(int x, int y)
4824 // profiling showed that "autotest" spends 10~20% of its time in this function
4825 if (DrawingDeactivatedField())
4828 int element = Feld[x][y];
4829 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
4831 if (graphic_info[graphic].anim_global_sync)
4832 GfxFrame[x][y] = FrameCounter;
4833 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
4834 GfxFrame[x][y] = CustomValue[x][y];
4835 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
4836 GfxFrame[x][y] = element_info[element].collect_score;
4837 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
4838 GfxFrame[x][y] = ChangeDelay[x][y];
4841 static void ResetGfxAnimation(int x, int y)
4843 GfxAction[x][y] = ACTION_DEFAULT;
4844 GfxDir[x][y] = MovDir[x][y];
4847 ResetGfxFrame(x, y);
4850 static void ResetRandomAnimationValue(int x, int y)
4852 GfxRandom[x][y] = INIT_GFX_RANDOM();
4855 void InitMovingField(int x, int y, int direction)
4857 int element = Feld[x][y];
4858 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4859 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4862 boolean is_moving_before, is_moving_after;
4864 /* check if element was/is moving or being moved before/after mode change */
4865 is_moving_before = (WasJustMoving[x][y] != 0);
4866 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
4868 /* reset animation only for moving elements which change direction of moving
4869 or which just started or stopped moving
4870 (else CEs with property "can move" / "not moving" are reset each frame) */
4871 if (is_moving_before != is_moving_after ||
4872 direction != MovDir[x][y])
4873 ResetGfxAnimation(x, y);
4875 MovDir[x][y] = direction;
4876 GfxDir[x][y] = direction;
4878 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
4879 direction == MV_DOWN && CAN_FALL(element) ?
4880 ACTION_FALLING : ACTION_MOVING);
4882 /* this is needed for CEs with property "can move" / "not moving" */
4884 if (is_moving_after)
4886 if (Feld[newx][newy] == EL_EMPTY)
4887 Feld[newx][newy] = EL_BLOCKED;
4889 MovDir[newx][newy] = MovDir[x][y];
4891 CustomValue[newx][newy] = CustomValue[x][y];
4893 GfxFrame[newx][newy] = GfxFrame[x][y];
4894 GfxRandom[newx][newy] = GfxRandom[x][y];
4895 GfxAction[newx][newy] = GfxAction[x][y];
4896 GfxDir[newx][newy] = GfxDir[x][y];
4900 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
4902 int direction = MovDir[x][y];
4903 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
4904 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
4910 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
4912 int oldx = x, oldy = y;
4913 int direction = MovDir[x][y];
4915 if (direction == MV_LEFT)
4917 else if (direction == MV_RIGHT)
4919 else if (direction == MV_UP)
4921 else if (direction == MV_DOWN)
4924 *comes_from_x = oldx;
4925 *comes_from_y = oldy;
4928 int MovingOrBlocked2Element(int x, int y)
4930 int element = Feld[x][y];
4932 if (element == EL_BLOCKED)
4936 Blocked2Moving(x, y, &oldx, &oldy);
4937 return Feld[oldx][oldy];
4943 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
4945 /* like MovingOrBlocked2Element(), but if element is moving
4946 and (x,y) is the field the moving element is just leaving,
4947 return EL_BLOCKED instead of the element value */
4948 int element = Feld[x][y];
4950 if (IS_MOVING(x, y))
4952 if (element == EL_BLOCKED)
4956 Blocked2Moving(x, y, &oldx, &oldy);
4957 return Feld[oldx][oldy];
4966 static void RemoveField(int x, int y)
4968 Feld[x][y] = EL_EMPTY;
4974 CustomValue[x][y] = 0;
4977 ChangeDelay[x][y] = 0;
4978 ChangePage[x][y] = -1;
4979 Pushed[x][y] = FALSE;
4981 GfxElement[x][y] = EL_UNDEFINED;
4982 GfxAction[x][y] = ACTION_DEFAULT;
4983 GfxDir[x][y] = MV_NONE;
4986 void RemoveMovingField(int x, int y)
4988 int oldx = x, oldy = y, newx = x, newy = y;
4989 int element = Feld[x][y];
4990 int next_element = EL_UNDEFINED;
4992 if (element != EL_BLOCKED && !IS_MOVING(x, y))
4995 if (IS_MOVING(x, y))
4997 Moving2Blocked(x, y, &newx, &newy);
4999 if (Feld[newx][newy] != EL_BLOCKED)
5001 /* element is moving, but target field is not free (blocked), but
5002 already occupied by something different (example: acid pool);
5003 in this case, only remove the moving field, but not the target */
5005 RemoveField(oldx, oldy);
5007 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5009 TEST_DrawLevelField(oldx, oldy);
5014 else if (element == EL_BLOCKED)
5016 Blocked2Moving(x, y, &oldx, &oldy);
5017 if (!IS_MOVING(oldx, oldy))
5021 if (element == EL_BLOCKED &&
5022 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5023 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5024 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5025 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5026 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5027 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5028 next_element = get_next_element(Feld[oldx][oldy]);
5030 RemoveField(oldx, oldy);
5031 RemoveField(newx, newy);
5033 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5035 if (next_element != EL_UNDEFINED)
5036 Feld[oldx][oldy] = next_element;
5038 TEST_DrawLevelField(oldx, oldy);
5039 TEST_DrawLevelField(newx, newy);
5042 void DrawDynamite(int x, int y)
5044 int sx = SCREENX(x), sy = SCREENY(y);
5045 int graphic = el2img(Feld[x][y]);
5048 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5051 if (IS_WALKABLE_INSIDE(Back[x][y]))
5055 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5056 else if (Store[x][y])
5057 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5059 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5061 if (Back[x][y] || Store[x][y])
5062 DrawGraphicThruMask(sx, sy, graphic, frame);
5064 DrawGraphic(sx, sy, graphic, frame);
5067 void CheckDynamite(int x, int y)
5069 if (MovDelay[x][y] != 0) /* dynamite is still waiting to explode */
5073 if (MovDelay[x][y] != 0)
5076 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5082 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5087 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5089 boolean num_checked_players = 0;
5092 for (i = 0; i < MAX_PLAYERS; i++)
5094 if (stored_player[i].active)
5096 int sx = stored_player[i].jx;
5097 int sy = stored_player[i].jy;
5099 if (num_checked_players == 0)
5106 *sx1 = MIN(*sx1, sx);
5107 *sy1 = MIN(*sy1, sy);
5108 *sx2 = MAX(*sx2, sx);
5109 *sy2 = MAX(*sy2, sy);
5112 num_checked_players++;
5117 static boolean checkIfAllPlayersFitToScreen_RND()
5119 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5121 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5123 return (sx2 - sx1 < SCR_FIELDX &&
5124 sy2 - sy1 < SCR_FIELDY);
5127 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5129 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5131 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5133 *sx = (sx1 + sx2) / 2;
5134 *sy = (sy1 + sy2) / 2;
5137 void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5138 boolean center_screen, boolean quick_relocation)
5140 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5141 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5142 boolean no_delay = (tape.warp_forward);
5143 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5144 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5145 int new_scroll_x, new_scroll_y;
5147 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5149 /* case 1: quick relocation inside visible screen (without scrolling) */
5156 if (!level.shifted_relocation || center_screen)
5158 /* relocation _with_ centering of screen */
5160 new_scroll_x = SCROLL_POSITION_X(x);
5161 new_scroll_y = SCROLL_POSITION_Y(y);
5165 /* relocation _without_ centering of screen */
5167 int center_scroll_x = SCROLL_POSITION_X(old_x);
5168 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5169 int offset_x = x + (scroll_x - center_scroll_x);
5170 int offset_y = y + (scroll_y - center_scroll_y);
5172 /* for new screen position, apply previous offset to center position */
5173 new_scroll_x = SCROLL_POSITION_X(offset_x);
5174 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5177 if (quick_relocation)
5179 /* case 2: quick relocation (redraw without visible scrolling) */
5181 scroll_x = new_scroll_x;
5182 scroll_y = new_scroll_y;
5189 /* case 3: visible relocation (with scrolling to new position) */
5191 ScrollScreen(NULL, SCROLL_GO_ON); /* scroll last frame to full tile */
5193 SetVideoFrameDelay(wait_delay_value);
5195 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5198 int fx = FX, fy = FY;
5200 dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5201 dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5203 if (dx == 0 && dy == 0) /* no scrolling needed at all */
5209 fx += dx * TILEX / 2;
5210 fy += dy * TILEY / 2;
5212 ScrollLevel(dx, dy);
5215 /* scroll in two steps of half tile size to make things smoother */
5216 BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
5218 /* scroll second step to align at full tile size */
5219 BlitScreenToBitmap(window);
5225 SetVideoFrameDelay(frame_delay_value_old);
5228 void RelocatePlayer(int jx, int jy, int el_player_raw)
5230 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5231 int player_nr = GET_PLAYER_NR(el_player);
5232 struct PlayerInfo *player = &stored_player[player_nr];
5233 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5234 boolean no_delay = (tape.warp_forward);
5235 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5236 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5237 int old_jx = player->jx;
5238 int old_jy = player->jy;
5239 int old_element = Feld[old_jx][old_jy];
5240 int element = Feld[jx][jy];
5241 boolean player_relocated = (old_jx != jx || old_jy != jy);
5243 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5244 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5245 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5246 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5247 int leave_side_horiz = move_dir_horiz;
5248 int leave_side_vert = move_dir_vert;
5249 int enter_side = enter_side_horiz | enter_side_vert;
5250 int leave_side = leave_side_horiz | leave_side_vert;
5252 if (player->GameOver) /* do not reanimate dead player */
5255 if (!player_relocated) /* no need to relocate the player */
5258 if (IS_PLAYER(jx, jy)) /* player already placed at new position */
5260 RemoveField(jx, jy); /* temporarily remove newly placed player */
5261 DrawLevelField(jx, jy);
5264 if (player->present)
5266 while (player->MovPos)
5268 ScrollPlayer(player, SCROLL_GO_ON);
5269 ScrollScreen(NULL, SCROLL_GO_ON);
5271 AdvanceFrameAndPlayerCounters(player->index_nr);
5275 BackToFront_WithFrameDelay(wait_delay_value);
5278 DrawPlayer(player); /* needed here only to cleanup last field */
5279 DrawLevelField(player->jx, player->jy); /* remove player graphic */
5281 player->is_moving = FALSE;
5284 if (IS_CUSTOM_ELEMENT(old_element))
5285 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5287 player->index_bit, leave_side);
5289 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5291 player->index_bit, leave_side);
5293 Feld[jx][jy] = el_player;
5294 InitPlayerField(jx, jy, el_player, TRUE);
5296 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5297 possible that the relocation target field did not contain a player element,
5298 but a walkable element, to which the new player was relocated -- in this
5299 case, restore that (already initialized!) element on the player field */
5300 if (!ELEM_IS_PLAYER(element)) /* player may be set on walkable element */
5302 Feld[jx][jy] = element; /* restore previously existing element */
5305 /* only visually relocate centered player */
5306 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5307 FALSE, level.instant_relocation);
5309 TestIfPlayerTouchesBadThing(jx, jy);
5310 TestIfPlayerTouchesCustomElement(jx, jy);
5312 if (IS_CUSTOM_ELEMENT(element))
5313 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5314 player->index_bit, enter_side);
5316 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5317 player->index_bit, enter_side);
5319 if (player->is_switching)
5321 /* ensure that relocation while still switching an element does not cause
5322 a new element to be treated as also switched directly after relocation
5323 (this is important for teleporter switches that teleport the player to
5324 a place where another teleporter switch is in the same direction, which
5325 would then incorrectly be treated as immediately switched before the
5326 direction key that caused the switch was released) */
5328 player->switch_x += jx - old_jx;
5329 player->switch_y += jy - old_jy;
5333 void Explode(int ex, int ey, int phase, int mode)
5339 /* !!! eliminate this variable !!! */
5340 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5342 if (game.explosions_delayed)
5344 ExplodeField[ex][ey] = mode;
5348 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
5350 int center_element = Feld[ex][ey];
5351 int artwork_element, explosion_element; /* set these values later */
5353 /* remove things displayed in background while burning dynamite */
5354 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5357 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5359 /* put moving element to center field (and let it explode there) */
5360 center_element = MovingOrBlocked2Element(ex, ey);
5361 RemoveMovingField(ex, ey);
5362 Feld[ex][ey] = center_element;
5365 /* now "center_element" is finally determined -- set related values now */
5366 artwork_element = center_element; /* for custom player artwork */
5367 explosion_element = center_element; /* for custom player artwork */
5369 if (IS_PLAYER(ex, ey))
5371 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5373 artwork_element = stored_player[player_nr].artwork_element;
5375 if (level.use_explosion_element[player_nr])
5377 explosion_element = level.explosion_element[player_nr];
5378 artwork_element = explosion_element;
5382 if (mode == EX_TYPE_NORMAL ||
5383 mode == EX_TYPE_CENTER ||
5384 mode == EX_TYPE_CROSS)
5385 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5387 last_phase = element_info[explosion_element].explosion_delay + 1;
5389 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5391 int xx = x - ex + 1;
5392 int yy = y - ey + 1;
5395 if (!IN_LEV_FIELD(x, y) ||
5396 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5397 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5400 element = Feld[x][y];
5402 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5404 element = MovingOrBlocked2Element(x, y);
5406 if (!IS_EXPLOSION_PROOF(element))
5407 RemoveMovingField(x, y);
5410 /* indestructible elements can only explode in center (but not flames) */
5411 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5412 mode == EX_TYPE_BORDER)) ||
5413 element == EL_FLAMES)
5416 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5417 behaviour, for example when touching a yamyam that explodes to rocks
5418 with active deadly shield, a rock is created under the player !!! */
5419 /* (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8) */
5421 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5422 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5423 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5425 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5428 if (IS_ACTIVE_BOMB(element))
5430 /* re-activate things under the bomb like gate or penguin */
5431 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5438 /* save walkable background elements while explosion on same tile */
5439 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5440 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5441 Back[x][y] = element;
5443 /* ignite explodable elements reached by other explosion */
5444 if (element == EL_EXPLOSION)
5445 element = Store2[x][y];
5447 if (AmoebaNr[x][y] &&
5448 (element == EL_AMOEBA_FULL ||
5449 element == EL_BD_AMOEBA ||
5450 element == EL_AMOEBA_GROWING))
5452 AmoebaCnt[AmoebaNr[x][y]]--;
5453 AmoebaCnt2[AmoebaNr[x][y]]--;
5458 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5460 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5462 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5464 if (PLAYERINFO(ex, ey)->use_murphy)
5465 Store[x][y] = EL_EMPTY;
5468 /* !!! check this case -- currently needed for rnd_rado_negundo_v,
5469 !!! levels 015 018 019 020 021 022 023 026 027 028 !!! */
5470 else if (ELEM_IS_PLAYER(center_element))
5471 Store[x][y] = EL_EMPTY;
5472 else if (center_element == EL_YAMYAM)
5473 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5474 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5475 Store[x][y] = element_info[center_element].content.e[xx][yy];
5477 /* needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5478 (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5479 otherwise) -- FIX THIS !!! */
5480 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5481 Store[x][y] = element_info[element].content.e[1][1];
5483 else if (!CAN_EXPLODE(element))
5484 Store[x][y] = element_info[element].content.e[1][1];
5487 Store[x][y] = EL_EMPTY;
5489 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5490 center_element == EL_AMOEBA_TO_DIAMOND)
5491 Store2[x][y] = element;
5493 Feld[x][y] = EL_EXPLOSION;
5494 GfxElement[x][y] = artwork_element;
5496 ExplodePhase[x][y] = 1;
5497 ExplodeDelay[x][y] = last_phase;
5502 if (center_element == EL_YAMYAM)
5503 game.yamyam_content_nr =
5504 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5516 GfxFrame[x][y] = 0; /* restart explosion animation */
5518 last_phase = ExplodeDelay[x][y];
5520 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5522 /* this can happen if the player leaves an explosion just in time */
5523 if (GfxElement[x][y] == EL_UNDEFINED)
5524 GfxElement[x][y] = EL_EMPTY;
5526 border_element = Store2[x][y];
5527 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5528 border_element = StorePlayer[x][y];
5530 if (phase == element_info[border_element].ignition_delay ||
5531 phase == last_phase)
5533 boolean border_explosion = FALSE;
5535 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5536 !PLAYER_EXPLOSION_PROTECTED(x, y))
5538 KillPlayerUnlessExplosionProtected(x, y);
5539 border_explosion = TRUE;
5541 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5543 Feld[x][y] = Store2[x][y];
5546 border_explosion = TRUE;
5548 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5550 AmoebeUmwandeln(x, y);
5552 border_explosion = TRUE;
5555 /* if an element just explodes due to another explosion (chain-reaction),
5556 do not immediately end the new explosion when it was the last frame of
5557 the explosion (as it would be done in the following "if"-statement!) */
5558 if (border_explosion && phase == last_phase)
5562 if (phase == last_phase)
5566 element = Feld[x][y] = Store[x][y];
5567 Store[x][y] = Store2[x][y] = 0;
5568 GfxElement[x][y] = EL_UNDEFINED;
5570 /* player can escape from explosions and might therefore be still alive */
5571 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5572 element <= EL_PLAYER_IS_EXPLODING_4)
5574 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5575 int explosion_element = EL_PLAYER_1 + player_nr;
5576 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5577 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5579 if (level.use_explosion_element[player_nr])
5580 explosion_element = level.explosion_element[player_nr];
5582 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5583 element_info[explosion_element].content.e[xx][yy]);
5586 /* restore probably existing indestructible background element */
5587 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5588 element = Feld[x][y] = Back[x][y];
5591 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5592 GfxDir[x][y] = MV_NONE;
5593 ChangeDelay[x][y] = 0;
5594 ChangePage[x][y] = -1;
5596 CustomValue[x][y] = 0;
5598 InitField_WithBug2(x, y, FALSE);
5600 TEST_DrawLevelField(x, y);
5602 TestIfElementTouchesCustomElement(x, y);
5604 if (GFX_CRUMBLED(element))
5605 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5607 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5608 StorePlayer[x][y] = 0;
5610 if (ELEM_IS_PLAYER(element))
5611 RelocatePlayer(x, y, element);
5613 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5615 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5616 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5619 TEST_DrawLevelFieldCrumbled(x, y);
5621 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5623 DrawLevelElement(x, y, Back[x][y]);
5624 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5626 else if (IS_WALKABLE_UNDER(Back[x][y]))
5628 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5629 DrawLevelElementThruMask(x, y, Back[x][y]);
5631 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5632 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5636 void DynaExplode(int ex, int ey)
5639 int dynabomb_element = Feld[ex][ey];
5640 int dynabomb_size = 1;
5641 boolean dynabomb_xl = FALSE;
5642 struct PlayerInfo *player;
5643 static int xy[4][2] =
5651 if (IS_ACTIVE_BOMB(dynabomb_element))
5653 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5654 dynabomb_size = player->dynabomb_size;
5655 dynabomb_xl = player->dynabomb_xl;
5656 player->dynabombs_left++;
5659 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5661 for (i = 0; i < NUM_DIRECTIONS; i++)
5663 for (j = 1; j <= dynabomb_size; j++)
5665 int x = ex + j * xy[i][0];
5666 int y = ey + j * xy[i][1];
5669 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5672 element = Feld[x][y];
5674 /* do not restart explosions of fields with active bombs */
5675 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5678 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5680 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5681 !IS_DIGGABLE(element) && !dynabomb_xl)
5687 void Bang(int x, int y)
5689 int element = MovingOrBlocked2Element(x, y);
5690 int explosion_type = EX_TYPE_NORMAL;
5692 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5694 struct PlayerInfo *player = PLAYERINFO(x, y);
5696 element = Feld[x][y] = player->initial_element;
5698 if (level.use_explosion_element[player->index_nr])
5700 int explosion_element = level.explosion_element[player->index_nr];
5702 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5703 explosion_type = EX_TYPE_CROSS;
5704 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5705 explosion_type = EX_TYPE_CENTER;
5713 case EL_BD_BUTTERFLY:
5716 case EL_DARK_YAMYAM:
5720 RaiseScoreElement(element);
5723 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5724 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5725 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5726 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5727 case EL_DYNABOMB_INCREASE_NUMBER:
5728 case EL_DYNABOMB_INCREASE_SIZE:
5729 case EL_DYNABOMB_INCREASE_POWER:
5730 explosion_type = EX_TYPE_DYNA;
5733 case EL_DC_LANDMINE:
5734 explosion_type = EX_TYPE_CENTER;
5739 case EL_LAMP_ACTIVE:
5740 case EL_AMOEBA_TO_DIAMOND:
5741 if (!IS_PLAYER(x, y)) /* penguin and player may be at same field */
5742 explosion_type = EX_TYPE_CENTER;
5746 if (element_info[element].explosion_type == EXPLODES_CROSS)
5747 explosion_type = EX_TYPE_CROSS;
5748 else if (element_info[element].explosion_type == EXPLODES_1X1)
5749 explosion_type = EX_TYPE_CENTER;
5753 if (explosion_type == EX_TYPE_DYNA)
5756 Explode(x, y, EX_PHASE_START, explosion_type);
5758 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5761 void SplashAcid(int x, int y)
5763 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5764 (!IN_LEV_FIELD(x - 1, y - 2) ||
5765 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5766 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5768 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5769 (!IN_LEV_FIELD(x + 1, y - 2) ||
5770 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5771 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5773 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5776 static void InitBeltMovement()
5778 static int belt_base_element[4] =
5780 EL_CONVEYOR_BELT_1_LEFT,
5781 EL_CONVEYOR_BELT_2_LEFT,
5782 EL_CONVEYOR_BELT_3_LEFT,
5783 EL_CONVEYOR_BELT_4_LEFT
5785 static int belt_base_active_element[4] =
5787 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5788 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5789 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5790 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5795 /* set frame order for belt animation graphic according to belt direction */
5796 for (i = 0; i < NUM_BELTS; i++)
5800 for (j = 0; j < NUM_BELT_PARTS; j++)
5802 int element = belt_base_active_element[belt_nr] + j;
5803 int graphic_1 = el2img(element);
5804 int graphic_2 = el2panelimg(element);
5806 if (game.belt_dir[i] == MV_LEFT)
5808 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5809 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5813 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5814 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5819 SCAN_PLAYFIELD(x, y)
5821 int element = Feld[x][y];
5823 for (i = 0; i < NUM_BELTS; i++)
5825 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
5827 int e_belt_nr = getBeltNrFromBeltElement(element);
5830 if (e_belt_nr == belt_nr)
5832 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
5834 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
5841 static void ToggleBeltSwitch(int x, int y)
5843 static int belt_base_element[4] =
5845 EL_CONVEYOR_BELT_1_LEFT,
5846 EL_CONVEYOR_BELT_2_LEFT,
5847 EL_CONVEYOR_BELT_3_LEFT,
5848 EL_CONVEYOR_BELT_4_LEFT
5850 static int belt_base_active_element[4] =
5852 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5853 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5854 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5855 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5857 static int belt_base_switch_element[4] =
5859 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
5860 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
5861 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
5862 EL_CONVEYOR_BELT_4_SWITCH_LEFT
5864 static int belt_move_dir[4] =
5872 int element = Feld[x][y];
5873 int belt_nr = getBeltNrFromBeltSwitchElement(element);
5874 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
5875 int belt_dir = belt_move_dir[belt_dir_nr];
5878 if (!IS_BELT_SWITCH(element))
5881 game.belt_dir_nr[belt_nr] = belt_dir_nr;
5882 game.belt_dir[belt_nr] = belt_dir;
5884 if (belt_dir_nr == 3)
5887 /* set frame order for belt animation graphic according to belt direction */
5888 for (i = 0; i < NUM_BELT_PARTS; i++)
5890 int element = belt_base_active_element[belt_nr] + i;
5891 int graphic_1 = el2img(element);
5892 int graphic_2 = el2panelimg(element);
5894 if (belt_dir == MV_LEFT)
5896 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5897 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5901 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5902 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5906 SCAN_PLAYFIELD(xx, yy)
5908 int element = Feld[xx][yy];
5910 if (IS_BELT_SWITCH(element))
5912 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
5914 if (e_belt_nr == belt_nr)
5916 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
5917 TEST_DrawLevelField(xx, yy);
5920 else if (IS_BELT(element) && belt_dir != MV_NONE)
5922 int e_belt_nr = getBeltNrFromBeltElement(element);
5924 if (e_belt_nr == belt_nr)
5926 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
5928 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
5929 TEST_DrawLevelField(xx, yy);
5932 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
5934 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
5936 if (e_belt_nr == belt_nr)
5938 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
5940 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
5941 TEST_DrawLevelField(xx, yy);
5947 static void ToggleSwitchgateSwitch(int x, int y)
5951 game.switchgate_pos = !game.switchgate_pos;
5953 SCAN_PLAYFIELD(xx, yy)
5955 int element = Feld[xx][yy];
5957 if (element == EL_SWITCHGATE_SWITCH_UP)
5959 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
5960 TEST_DrawLevelField(xx, yy);
5962 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
5964 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
5965 TEST_DrawLevelField(xx, yy);
5967 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
5969 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
5970 TEST_DrawLevelField(xx, yy);
5972 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
5974 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
5975 TEST_DrawLevelField(xx, yy);
5977 else if (element == EL_SWITCHGATE_OPEN ||
5978 element == EL_SWITCHGATE_OPENING)
5980 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
5982 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
5984 else if (element == EL_SWITCHGATE_CLOSED ||
5985 element == EL_SWITCHGATE_CLOSING)
5987 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
5989 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
5994 static int getInvisibleActiveFromInvisibleElement(int element)
5996 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
5997 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
5998 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6002 static int getInvisibleFromInvisibleActiveElement(int element)
6004 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6005 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6006 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6010 static void RedrawAllLightSwitchesAndInvisibleElements()
6014 SCAN_PLAYFIELD(x, y)
6016 int element = Feld[x][y];
6018 if (element == EL_LIGHT_SWITCH &&
6019 game.light_time_left > 0)
6021 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6022 TEST_DrawLevelField(x, y);
6024 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6025 game.light_time_left == 0)
6027 Feld[x][y] = EL_LIGHT_SWITCH;
6028 TEST_DrawLevelField(x, y);
6030 else if (element == EL_EMC_DRIPPER &&
6031 game.light_time_left > 0)
6033 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6034 TEST_DrawLevelField(x, y);
6036 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6037 game.light_time_left == 0)
6039 Feld[x][y] = EL_EMC_DRIPPER;
6040 TEST_DrawLevelField(x, y);
6042 else if (element == EL_INVISIBLE_STEELWALL ||
6043 element == EL_INVISIBLE_WALL ||
6044 element == EL_INVISIBLE_SAND)
6046 if (game.light_time_left > 0)
6047 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6049 TEST_DrawLevelField(x, y);
6051 /* uncrumble neighbour fields, if needed */
6052 if (element == EL_INVISIBLE_SAND)
6053 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6055 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6056 element == EL_INVISIBLE_WALL_ACTIVE ||
6057 element == EL_INVISIBLE_SAND_ACTIVE)
6059 if (game.light_time_left == 0)
6060 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6062 TEST_DrawLevelField(x, y);
6064 /* re-crumble neighbour fields, if needed */
6065 if (element == EL_INVISIBLE_SAND)
6066 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6071 static void RedrawAllInvisibleElementsForLenses()
6075 SCAN_PLAYFIELD(x, y)
6077 int element = Feld[x][y];
6079 if (element == EL_EMC_DRIPPER &&
6080 game.lenses_time_left > 0)
6082 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6083 TEST_DrawLevelField(x, y);
6085 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6086 game.lenses_time_left == 0)
6088 Feld[x][y] = EL_EMC_DRIPPER;
6089 TEST_DrawLevelField(x, y);
6091 else if (element == EL_INVISIBLE_STEELWALL ||
6092 element == EL_INVISIBLE_WALL ||
6093 element == EL_INVISIBLE_SAND)
6095 if (game.lenses_time_left > 0)
6096 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6098 TEST_DrawLevelField(x, y);
6100 /* uncrumble neighbour fields, if needed */
6101 if (element == EL_INVISIBLE_SAND)
6102 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6104 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6105 element == EL_INVISIBLE_WALL_ACTIVE ||
6106 element == EL_INVISIBLE_SAND_ACTIVE)
6108 if (game.lenses_time_left == 0)
6109 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6111 TEST_DrawLevelField(x, y);
6113 /* re-crumble neighbour fields, if needed */
6114 if (element == EL_INVISIBLE_SAND)
6115 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6120 static void RedrawAllInvisibleElementsForMagnifier()
6124 SCAN_PLAYFIELD(x, y)
6126 int element = Feld[x][y];
6128 if (element == EL_EMC_FAKE_GRASS &&
6129 game.magnify_time_left > 0)
6131 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6132 TEST_DrawLevelField(x, y);
6134 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6135 game.magnify_time_left == 0)
6137 Feld[x][y] = EL_EMC_FAKE_GRASS;
6138 TEST_DrawLevelField(x, y);
6140 else if (IS_GATE_GRAY(element) &&
6141 game.magnify_time_left > 0)
6143 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6144 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6145 IS_EM_GATE_GRAY(element) ?
6146 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6147 IS_EMC_GATE_GRAY(element) ?
6148 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6149 IS_DC_GATE_GRAY(element) ?
6150 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6152 TEST_DrawLevelField(x, y);
6154 else if (IS_GATE_GRAY_ACTIVE(element) &&
6155 game.magnify_time_left == 0)
6157 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6158 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6159 IS_EM_GATE_GRAY_ACTIVE(element) ?
6160 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6161 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6162 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6163 IS_DC_GATE_GRAY_ACTIVE(element) ?
6164 EL_DC_GATE_WHITE_GRAY :
6166 TEST_DrawLevelField(x, y);
6171 static void ToggleLightSwitch(int x, int y)
6173 int element = Feld[x][y];
6175 game.light_time_left =
6176 (element == EL_LIGHT_SWITCH ?
6177 level.time_light * FRAMES_PER_SECOND : 0);
6179 RedrawAllLightSwitchesAndInvisibleElements();
6182 static void ActivateTimegateSwitch(int x, int y)
6186 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6188 SCAN_PLAYFIELD(xx, yy)
6190 int element = Feld[xx][yy];
6192 if (element == EL_TIMEGATE_CLOSED ||
6193 element == EL_TIMEGATE_CLOSING)
6195 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6196 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6200 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6202 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6203 TEST_DrawLevelField(xx, yy);
6209 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6210 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6213 void Impact(int x, int y)
6215 boolean last_line = (y == lev_fieldy - 1);
6216 boolean object_hit = FALSE;
6217 boolean impact = (last_line || object_hit);
6218 int element = Feld[x][y];
6219 int smashed = EL_STEELWALL;
6221 if (!last_line) /* check if element below was hit */
6223 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6226 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6227 MovDir[x][y + 1] != MV_DOWN ||
6228 MovPos[x][y + 1] <= TILEY / 2));
6230 /* do not smash moving elements that left the smashed field in time */
6231 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6232 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6235 #if USE_QUICKSAND_IMPACT_BUGFIX
6236 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6238 RemoveMovingField(x, y + 1);
6239 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6240 Feld[x][y + 2] = EL_ROCK;
6241 TEST_DrawLevelField(x, y + 2);
6246 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6248 RemoveMovingField(x, y + 1);
6249 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6250 Feld[x][y + 2] = EL_ROCK;
6251 TEST_DrawLevelField(x, y + 2);
6258 smashed = MovingOrBlocked2Element(x, y + 1);
6260 impact = (last_line || object_hit);
6263 if (!last_line && smashed == EL_ACID) /* element falls into acid */
6265 SplashAcid(x, y + 1);
6269 /* !!! not sufficient for all cases -- see EL_PEARL below !!! */
6270 /* only reset graphic animation if graphic really changes after impact */
6272 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6274 ResetGfxAnimation(x, y);
6275 TEST_DrawLevelField(x, y);
6278 if (impact && CAN_EXPLODE_IMPACT(element))
6283 else if (impact && element == EL_PEARL &&
6284 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6286 ResetGfxAnimation(x, y);
6288 Feld[x][y] = EL_PEARL_BREAKING;
6289 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6292 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6294 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6299 if (impact && element == EL_AMOEBA_DROP)
6301 if (object_hit && IS_PLAYER(x, y + 1))
6302 KillPlayerUnlessEnemyProtected(x, y + 1);
6303 else if (object_hit && smashed == EL_PENGUIN)
6307 Feld[x][y] = EL_AMOEBA_GROWING;
6308 Store[x][y] = EL_AMOEBA_WET;
6310 ResetRandomAnimationValue(x, y);
6315 if (object_hit) /* check which object was hit */
6317 if ((CAN_PASS_MAGIC_WALL(element) &&
6318 (smashed == EL_MAGIC_WALL ||
6319 smashed == EL_BD_MAGIC_WALL)) ||
6320 (CAN_PASS_DC_MAGIC_WALL(element) &&
6321 smashed == EL_DC_MAGIC_WALL))
6324 int activated_magic_wall =
6325 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6326 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6327 EL_DC_MAGIC_WALL_ACTIVE);
6329 /* activate magic wall / mill */
6330 SCAN_PLAYFIELD(xx, yy)
6332 if (Feld[xx][yy] == smashed)
6333 Feld[xx][yy] = activated_magic_wall;
6336 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6337 game.magic_wall_active = TRUE;
6339 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6340 SND_MAGIC_WALL_ACTIVATING :
6341 smashed == EL_BD_MAGIC_WALL ?
6342 SND_BD_MAGIC_WALL_ACTIVATING :
6343 SND_DC_MAGIC_WALL_ACTIVATING));
6346 if (IS_PLAYER(x, y + 1))
6348 if (CAN_SMASH_PLAYER(element))
6350 KillPlayerUnlessEnemyProtected(x, y + 1);
6354 else if (smashed == EL_PENGUIN)
6356 if (CAN_SMASH_PLAYER(element))
6362 else if (element == EL_BD_DIAMOND)
6364 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6370 else if (((element == EL_SP_INFOTRON ||
6371 element == EL_SP_ZONK) &&
6372 (smashed == EL_SP_SNIKSNAK ||
6373 smashed == EL_SP_ELECTRON ||
6374 smashed == EL_SP_DISK_ORANGE)) ||
6375 (element == EL_SP_INFOTRON &&
6376 smashed == EL_SP_DISK_YELLOW))
6381 else if (CAN_SMASH_EVERYTHING(element))
6383 if (IS_CLASSIC_ENEMY(smashed) ||
6384 CAN_EXPLODE_SMASHED(smashed))
6389 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6391 if (smashed == EL_LAMP ||
6392 smashed == EL_LAMP_ACTIVE)
6397 else if (smashed == EL_NUT)
6399 Feld[x][y + 1] = EL_NUT_BREAKING;
6400 PlayLevelSound(x, y, SND_NUT_BREAKING);
6401 RaiseScoreElement(EL_NUT);
6404 else if (smashed == EL_PEARL)
6406 ResetGfxAnimation(x, y);
6408 Feld[x][y + 1] = EL_PEARL_BREAKING;
6409 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6412 else if (smashed == EL_DIAMOND)
6414 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6415 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6418 else if (IS_BELT_SWITCH(smashed))
6420 ToggleBeltSwitch(x, y + 1);
6422 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6423 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6424 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6425 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6427 ToggleSwitchgateSwitch(x, y + 1);
6429 else if (smashed == EL_LIGHT_SWITCH ||
6430 smashed == EL_LIGHT_SWITCH_ACTIVE)
6432 ToggleLightSwitch(x, y + 1);
6436 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6438 CheckElementChangeBySide(x, y + 1, smashed, element,
6439 CE_SWITCHED, CH_SIDE_TOP);
6440 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6446 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6451 /* play sound of magic wall / mill */
6453 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6454 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6455 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6457 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6458 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6459 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6460 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6461 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6462 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6467 /* play sound of object that hits the ground */
6468 if (last_line || object_hit)
6469 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6472 inline static void TurnRoundExt(int x, int y)
6484 { 0, 0 }, { 0, 0 }, { 0, 0 },
6489 int left, right, back;
6493 { MV_DOWN, MV_UP, MV_RIGHT },
6494 { MV_UP, MV_DOWN, MV_LEFT },
6496 { MV_LEFT, MV_RIGHT, MV_DOWN },
6500 { MV_RIGHT, MV_LEFT, MV_UP }
6503 int element = Feld[x][y];
6504 int move_pattern = element_info[element].move_pattern;
6506 int old_move_dir = MovDir[x][y];
6507 int left_dir = turn[old_move_dir].left;
6508 int right_dir = turn[old_move_dir].right;
6509 int back_dir = turn[old_move_dir].back;
6511 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6512 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6513 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6514 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6516 int left_x = x + left_dx, left_y = y + left_dy;
6517 int right_x = x + right_dx, right_y = y + right_dy;
6518 int move_x = x + move_dx, move_y = y + move_dy;
6522 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6524 TestIfBadThingTouchesOtherBadThing(x, y);
6526 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6527 MovDir[x][y] = right_dir;
6528 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6529 MovDir[x][y] = left_dir;
6531 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6533 else if (element == EL_BD_BUTTERFLY) /* && MovDir[x][y] == left_dir) */
6536 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6538 TestIfBadThingTouchesOtherBadThing(x, y);
6540 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6541 MovDir[x][y] = left_dir;
6542 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6543 MovDir[x][y] = right_dir;
6545 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6547 else if (element == EL_BD_FIREFLY) /* && MovDir[x][y] == right_dir) */
6550 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6552 TestIfBadThingTouchesOtherBadThing(x, y);
6554 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6555 MovDir[x][y] = left_dir;
6556 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6557 MovDir[x][y] = right_dir;
6559 if (MovDir[x][y] != old_move_dir)
6562 else if (element == EL_YAMYAM)
6564 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6565 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6567 if (can_turn_left && can_turn_right)
6568 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6569 else if (can_turn_left)
6570 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6571 else if (can_turn_right)
6572 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6574 MovDir[x][y] = back_dir;
6576 MovDelay[x][y] = 16 + 16 * RND(3);
6578 else if (element == EL_DARK_YAMYAM)
6580 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6582 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6585 if (can_turn_left && can_turn_right)
6586 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6587 else if (can_turn_left)
6588 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6589 else if (can_turn_right)
6590 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6592 MovDir[x][y] = back_dir;
6594 MovDelay[x][y] = 16 + 16 * RND(3);
6596 else if (element == EL_PACMAN)
6598 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6599 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6601 if (can_turn_left && can_turn_right)
6602 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6603 else if (can_turn_left)
6604 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6605 else if (can_turn_right)
6606 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6608 MovDir[x][y] = back_dir;
6610 MovDelay[x][y] = 6 + RND(40);
6612 else if (element == EL_PIG)
6614 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6615 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6616 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6617 boolean should_turn_left, should_turn_right, should_move_on;
6619 int rnd = RND(rnd_value);
6621 should_turn_left = (can_turn_left &&
6623 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6624 y + back_dy + left_dy)));
6625 should_turn_right = (can_turn_right &&
6627 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6628 y + back_dy + right_dy)));
6629 should_move_on = (can_move_on &&
6632 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6633 y + move_dy + left_dy) ||
6634 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6635 y + move_dy + right_dy)));
6637 if (should_turn_left || should_turn_right || should_move_on)
6639 if (should_turn_left && should_turn_right && should_move_on)
6640 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6641 rnd < 2 * rnd_value / 3 ? right_dir :
6643 else if (should_turn_left && should_turn_right)
6644 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6645 else if (should_turn_left && should_move_on)
6646 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6647 else if (should_turn_right && should_move_on)
6648 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6649 else if (should_turn_left)
6650 MovDir[x][y] = left_dir;
6651 else if (should_turn_right)
6652 MovDir[x][y] = right_dir;
6653 else if (should_move_on)
6654 MovDir[x][y] = old_move_dir;
6656 else if (can_move_on && rnd > rnd_value / 8)
6657 MovDir[x][y] = old_move_dir;
6658 else if (can_turn_left && can_turn_right)
6659 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6660 else if (can_turn_left && rnd > rnd_value / 8)
6661 MovDir[x][y] = left_dir;
6662 else if (can_turn_right && rnd > rnd_value/8)
6663 MovDir[x][y] = right_dir;
6665 MovDir[x][y] = back_dir;
6667 xx = x + move_xy[MovDir[x][y]].dx;
6668 yy = y + move_xy[MovDir[x][y]].dy;
6670 if (!IN_LEV_FIELD(xx, yy) ||
6671 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6672 MovDir[x][y] = old_move_dir;
6676 else if (element == EL_DRAGON)
6678 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6679 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6680 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6682 int rnd = RND(rnd_value);
6684 if (can_move_on && rnd > rnd_value / 8)
6685 MovDir[x][y] = old_move_dir;
6686 else if (can_turn_left && can_turn_right)
6687 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6688 else if (can_turn_left && rnd > rnd_value / 8)
6689 MovDir[x][y] = left_dir;
6690 else if (can_turn_right && rnd > rnd_value / 8)
6691 MovDir[x][y] = right_dir;
6693 MovDir[x][y] = back_dir;
6695 xx = x + move_xy[MovDir[x][y]].dx;
6696 yy = y + move_xy[MovDir[x][y]].dy;
6698 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6699 MovDir[x][y] = old_move_dir;
6703 else if (element == EL_MOLE)
6705 boolean can_move_on =
6706 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6707 IS_AMOEBOID(Feld[move_x][move_y]) ||
6708 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6711 boolean can_turn_left =
6712 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6713 IS_AMOEBOID(Feld[left_x][left_y])));
6715 boolean can_turn_right =
6716 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6717 IS_AMOEBOID(Feld[right_x][right_y])));
6719 if (can_turn_left && can_turn_right)
6720 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6721 else if (can_turn_left)
6722 MovDir[x][y] = left_dir;
6724 MovDir[x][y] = right_dir;
6727 if (MovDir[x][y] != old_move_dir)
6730 else if (element == EL_BALLOON)
6732 MovDir[x][y] = game.wind_direction;
6735 else if (element == EL_SPRING)
6737 if (MovDir[x][y] & MV_HORIZONTAL)
6739 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6740 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6742 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6743 ResetGfxAnimation(move_x, move_y);
6744 TEST_DrawLevelField(move_x, move_y);
6746 MovDir[x][y] = back_dir;
6748 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6749 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6750 MovDir[x][y] = MV_NONE;
6755 else if (element == EL_ROBOT ||
6756 element == EL_SATELLITE ||
6757 element == EL_PENGUIN ||
6758 element == EL_EMC_ANDROID)
6760 int attr_x = -1, attr_y = -1;
6771 for (i = 0; i < MAX_PLAYERS; i++)
6773 struct PlayerInfo *player = &stored_player[i];
6774 int jx = player->jx, jy = player->jy;
6776 if (!player->active)
6780 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6788 if (element == EL_ROBOT && ZX >= 0 && ZY >= 0 &&
6789 (Feld[ZX][ZY] == EL_ROBOT_WHEEL_ACTIVE ||
6790 game.engine_version < VERSION_IDENT(3,1,0,0)))
6796 if (element == EL_PENGUIN)
6799 static int xy[4][2] =
6807 for (i = 0; i < NUM_DIRECTIONS; i++)
6809 int ex = x + xy[i][0];
6810 int ey = y + xy[i][1];
6812 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
6813 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
6814 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
6815 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
6824 MovDir[x][y] = MV_NONE;
6826 MovDir[x][y] |= (AllPlayersGone ? MV_RIGHT : MV_LEFT);
6827 else if (attr_x > x)
6828 MovDir[x][y] |= (AllPlayersGone ? MV_LEFT : MV_RIGHT);
6830 MovDir[x][y] |= (AllPlayersGone ? MV_DOWN : MV_UP);
6831 else if (attr_y > y)
6832 MovDir[x][y] |= (AllPlayersGone ? MV_UP : MV_DOWN);
6834 if (element == EL_ROBOT)
6838 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6839 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
6840 Moving2Blocked(x, y, &newx, &newy);
6842 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
6843 MovDelay[x][y] = 8 + 8 * !RND(3);
6845 MovDelay[x][y] = 16;
6847 else if (element == EL_PENGUIN)
6853 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6855 boolean first_horiz = RND(2);
6856 int new_move_dir = MovDir[x][y];
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))
6866 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6867 Moving2Blocked(x, y, &newx, &newy);
6869 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6872 MovDir[x][y] = old_move_dir;
6876 else if (element == EL_SATELLITE)
6882 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6884 boolean first_horiz = RND(2);
6885 int new_move_dir = MovDir[x][y];
6888 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6889 Moving2Blocked(x, y, &newx, &newy);
6891 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6895 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6896 Moving2Blocked(x, y, &newx, &newy);
6898 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6901 MovDir[x][y] = old_move_dir;
6905 else if (element == EL_EMC_ANDROID)
6907 static int check_pos[16] =
6909 -1, /* 0 => (invalid) */
6910 7, /* 1 => MV_LEFT */
6911 3, /* 2 => MV_RIGHT */
6912 -1, /* 3 => (invalid) */
6914 0, /* 5 => MV_LEFT | MV_UP */
6915 2, /* 6 => MV_RIGHT | MV_UP */
6916 -1, /* 7 => (invalid) */
6917 5, /* 8 => MV_DOWN */
6918 6, /* 9 => MV_LEFT | MV_DOWN */
6919 4, /* 10 => MV_RIGHT | MV_DOWN */
6920 -1, /* 11 => (invalid) */
6921 -1, /* 12 => (invalid) */
6922 -1, /* 13 => (invalid) */
6923 -1, /* 14 => (invalid) */
6924 -1, /* 15 => (invalid) */
6932 { -1, -1, MV_LEFT | MV_UP },
6934 { +1, -1, MV_RIGHT | MV_UP },
6935 { +1, 0, MV_RIGHT },
6936 { +1, +1, MV_RIGHT | MV_DOWN },
6938 { -1, +1, MV_LEFT | MV_DOWN },
6941 int start_pos, check_order;
6942 boolean can_clone = FALSE;
6945 /* check if there is any free field around current position */
6946 for (i = 0; i < 8; i++)
6948 int newx = x + check_xy[i].dx;
6949 int newy = y + check_xy[i].dy;
6951 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
6959 if (can_clone) /* randomly find an element to clone */
6963 start_pos = check_pos[RND(8)];
6964 check_order = (RND(2) ? -1 : +1);
6966 for (i = 0; i < 8; i++)
6968 int pos_raw = start_pos + i * check_order;
6969 int pos = (pos_raw + 8) % 8;
6970 int newx = x + check_xy[pos].dx;
6971 int newy = y + check_xy[pos].dy;
6973 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
6975 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
6976 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
6978 Store[x][y] = Feld[newx][newy];
6987 if (can_clone) /* randomly find a direction to move */
6991 start_pos = check_pos[RND(8)];
6992 check_order = (RND(2) ? -1 : +1);
6994 for (i = 0; i < 8; i++)
6996 int pos_raw = start_pos + i * check_order;
6997 int pos = (pos_raw + 8) % 8;
6998 int newx = x + check_xy[pos].dx;
6999 int newy = y + check_xy[pos].dy;
7000 int new_move_dir = check_xy[pos].dir;
7002 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7004 MovDir[x][y] = new_move_dir;
7005 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7014 if (can_clone) /* cloning and moving successful */
7017 /* cannot clone -- try to move towards player */
7019 start_pos = check_pos[MovDir[x][y] & 0x0f];
7020 check_order = (RND(2) ? -1 : +1);
7022 for (i = 0; i < 3; i++)
7024 /* first check start_pos, then previous/next or (next/previous) pos */
7025 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7026 int pos = (pos_raw + 8) % 8;
7027 int newx = x + check_xy[pos].dx;
7028 int newy = y + check_xy[pos].dy;
7029 int new_move_dir = check_xy[pos].dir;
7031 if (IS_PLAYER(newx, newy))
7034 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7036 MovDir[x][y] = new_move_dir;
7037 MovDelay[x][y] = level.android_move_time * 8 + 1;
7044 else if (move_pattern == MV_TURNING_LEFT ||
7045 move_pattern == MV_TURNING_RIGHT ||
7046 move_pattern == MV_TURNING_LEFT_RIGHT ||
7047 move_pattern == MV_TURNING_RIGHT_LEFT ||
7048 move_pattern == MV_TURNING_RANDOM ||
7049 move_pattern == MV_ALL_DIRECTIONS)
7051 boolean can_turn_left =
7052 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7053 boolean can_turn_right =
7054 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7056 if (element_info[element].move_stepsize == 0) /* "not moving" */
7059 if (move_pattern == MV_TURNING_LEFT)
7060 MovDir[x][y] = left_dir;
7061 else if (move_pattern == MV_TURNING_RIGHT)
7062 MovDir[x][y] = right_dir;
7063 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7064 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7065 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7066 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7067 else if (move_pattern == MV_TURNING_RANDOM)
7068 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7069 can_turn_right && !can_turn_left ? right_dir :
7070 RND(2) ? left_dir : right_dir);
7071 else if (can_turn_left && can_turn_right)
7072 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7073 else if (can_turn_left)
7074 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7075 else if (can_turn_right)
7076 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7078 MovDir[x][y] = back_dir;
7080 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7082 else if (move_pattern == MV_HORIZONTAL ||
7083 move_pattern == MV_VERTICAL)
7085 if (move_pattern & old_move_dir)
7086 MovDir[x][y] = back_dir;
7087 else if (move_pattern == MV_HORIZONTAL)
7088 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7089 else if (move_pattern == MV_VERTICAL)
7090 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7092 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7094 else if (move_pattern & MV_ANY_DIRECTION)
7096 MovDir[x][y] = move_pattern;
7097 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7099 else if (move_pattern & MV_WIND_DIRECTION)
7101 MovDir[x][y] = game.wind_direction;
7102 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7104 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7106 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7107 MovDir[x][y] = left_dir;
7108 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7109 MovDir[x][y] = right_dir;
7111 if (MovDir[x][y] != old_move_dir)
7112 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7114 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7116 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7117 MovDir[x][y] = right_dir;
7118 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7119 MovDir[x][y] = left_dir;
7121 if (MovDir[x][y] != old_move_dir)
7122 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7124 else if (move_pattern == MV_TOWARDS_PLAYER ||
7125 move_pattern == MV_AWAY_FROM_PLAYER)
7127 int attr_x = -1, attr_y = -1;
7129 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7140 for (i = 0; i < MAX_PLAYERS; i++)
7142 struct PlayerInfo *player = &stored_player[i];
7143 int jx = player->jx, jy = player->jy;
7145 if (!player->active)
7149 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7157 MovDir[x][y] = MV_NONE;
7159 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7160 else if (attr_x > x)
7161 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7163 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7164 else if (attr_y > y)
7165 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7167 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7169 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7171 boolean first_horiz = RND(2);
7172 int new_move_dir = MovDir[x][y];
7174 if (element_info[element].move_stepsize == 0) /* "not moving" */
7176 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7177 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
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))
7190 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7191 Moving2Blocked(x, y, &newx, &newy);
7193 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7196 MovDir[x][y] = old_move_dir;
7199 else if (move_pattern == MV_WHEN_PUSHED ||
7200 move_pattern == MV_WHEN_DROPPED)
7202 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7203 MovDir[x][y] = MV_NONE;
7207 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7209 static int test_xy[7][2] =
7219 static int test_dir[7] =
7229 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7230 int move_preference = -1000000; /* start with very low preference */
7231 int new_move_dir = MV_NONE;
7232 int start_test = RND(4);
7235 for (i = 0; i < NUM_DIRECTIONS; i++)
7237 int move_dir = test_dir[start_test + i];
7238 int move_dir_preference;
7240 xx = x + test_xy[start_test + i][0];
7241 yy = y + test_xy[start_test + i][1];
7243 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7244 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7246 new_move_dir = move_dir;
7251 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7254 move_dir_preference = -1 * RunnerVisit[xx][yy];
7255 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7256 move_dir_preference = PlayerVisit[xx][yy];
7258 if (move_dir_preference > move_preference)
7260 /* prefer field that has not been visited for the longest time */
7261 move_preference = move_dir_preference;
7262 new_move_dir = move_dir;
7264 else if (move_dir_preference == move_preference &&
7265 move_dir == old_move_dir)
7267 /* prefer last direction when all directions are preferred equally */
7268 move_preference = move_dir_preference;
7269 new_move_dir = move_dir;
7273 MovDir[x][y] = new_move_dir;
7274 if (old_move_dir != new_move_dir)
7275 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7279 static void TurnRound(int x, int y)
7281 int direction = MovDir[x][y];
7285 GfxDir[x][y] = MovDir[x][y];
7287 if (direction != MovDir[x][y])
7291 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7293 ResetGfxFrame(x, y);
7296 static boolean JustBeingPushed(int x, int y)
7300 for (i = 0; i < MAX_PLAYERS; i++)
7302 struct PlayerInfo *player = &stored_player[i];
7304 if (player->active && player->is_pushing && player->MovPos)
7306 int next_jx = player->jx + (player->jx - player->last_jx);
7307 int next_jy = player->jy + (player->jy - player->last_jy);
7309 if (x == next_jx && y == next_jy)
7317 void StartMoving(int x, int y)
7319 boolean started_moving = FALSE; /* some elements can fall _and_ move */
7320 int element = Feld[x][y];
7325 if (MovDelay[x][y] == 0)
7326 GfxAction[x][y] = ACTION_DEFAULT;
7328 if (CAN_FALL(element) && y < lev_fieldy - 1)
7330 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7331 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7332 if (JustBeingPushed(x, y))
7335 if (element == EL_QUICKSAND_FULL)
7337 if (IS_FREE(x, y + 1))
7339 InitMovingField(x, y, MV_DOWN);
7340 started_moving = TRUE;
7342 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7343 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7344 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7345 Store[x][y] = EL_ROCK;
7347 Store[x][y] = EL_ROCK;
7350 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7352 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7354 if (!MovDelay[x][y])
7356 MovDelay[x][y] = TILEY + 1;
7358 ResetGfxAnimation(x, y);
7359 ResetGfxAnimation(x, y + 1);
7364 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7365 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7372 Feld[x][y] = EL_QUICKSAND_EMPTY;
7373 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7374 Store[x][y + 1] = Store[x][y];
7377 PlayLevelSoundAction(x, y, ACTION_FILLING);
7379 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7381 if (!MovDelay[x][y])
7383 MovDelay[x][y] = TILEY + 1;
7385 ResetGfxAnimation(x, y);
7386 ResetGfxAnimation(x, y + 1);
7391 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7392 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7399 Feld[x][y] = EL_QUICKSAND_EMPTY;
7400 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7401 Store[x][y + 1] = Store[x][y];
7404 PlayLevelSoundAction(x, y, ACTION_FILLING);
7407 else if (element == EL_QUICKSAND_FAST_FULL)
7409 if (IS_FREE(x, y + 1))
7411 InitMovingField(x, y, MV_DOWN);
7412 started_moving = TRUE;
7414 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7415 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7416 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7417 Store[x][y] = EL_ROCK;
7419 Store[x][y] = EL_ROCK;
7422 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7424 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7426 if (!MovDelay[x][y])
7428 MovDelay[x][y] = TILEY + 1;
7430 ResetGfxAnimation(x, y);
7431 ResetGfxAnimation(x, y + 1);
7436 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7437 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7444 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7445 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7446 Store[x][y + 1] = Store[x][y];
7449 PlayLevelSoundAction(x, y, ACTION_FILLING);
7451 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7453 if (!MovDelay[x][y])
7455 MovDelay[x][y] = TILEY + 1;
7457 ResetGfxAnimation(x, y);
7458 ResetGfxAnimation(x, y + 1);
7463 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7464 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7471 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7472 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7473 Store[x][y + 1] = Store[x][y];
7476 PlayLevelSoundAction(x, y, ACTION_FILLING);
7479 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7480 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7482 InitMovingField(x, y, MV_DOWN);
7483 started_moving = TRUE;
7485 Feld[x][y] = EL_QUICKSAND_FILLING;
7486 Store[x][y] = element;
7488 PlayLevelSoundAction(x, y, ACTION_FILLING);
7490 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7491 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7493 InitMovingField(x, y, MV_DOWN);
7494 started_moving = TRUE;
7496 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7497 Store[x][y] = element;
7499 PlayLevelSoundAction(x, y, ACTION_FILLING);
7501 else if (element == EL_MAGIC_WALL_FULL)
7503 if (IS_FREE(x, y + 1))
7505 InitMovingField(x, y, MV_DOWN);
7506 started_moving = TRUE;
7508 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7509 Store[x][y] = EL_CHANGED(Store[x][y]);
7511 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7513 if (!MovDelay[x][y])
7514 MovDelay[x][y] = TILEY / 4 + 1;
7523 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7524 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7525 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7529 else if (element == EL_BD_MAGIC_WALL_FULL)
7531 if (IS_FREE(x, y + 1))
7533 InitMovingField(x, y, MV_DOWN);
7534 started_moving = TRUE;
7536 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7537 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7539 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7541 if (!MovDelay[x][y])
7542 MovDelay[x][y] = TILEY / 4 + 1;
7551 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7552 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7553 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7557 else if (element == EL_DC_MAGIC_WALL_FULL)
7559 if (IS_FREE(x, y + 1))
7561 InitMovingField(x, y, MV_DOWN);
7562 started_moving = TRUE;
7564 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7565 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7567 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7569 if (!MovDelay[x][y])
7570 MovDelay[x][y] = TILEY / 4 + 1;
7579 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7580 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7581 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7585 else if ((CAN_PASS_MAGIC_WALL(element) &&
7586 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7587 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7588 (CAN_PASS_DC_MAGIC_WALL(element) &&
7589 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7592 InitMovingField(x, y, MV_DOWN);
7593 started_moving = TRUE;
7596 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7597 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7598 EL_DC_MAGIC_WALL_FILLING);
7599 Store[x][y] = element;
7601 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7603 SplashAcid(x, y + 1);
7605 InitMovingField(x, y, MV_DOWN);
7606 started_moving = TRUE;
7608 Store[x][y] = EL_ACID;
7611 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7612 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7613 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7614 CAN_FALL(element) && WasJustFalling[x][y] &&
7615 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7617 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7618 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7619 (Feld[x][y + 1] == EL_BLOCKED)))
7621 /* this is needed for a special case not covered by calling "Impact()"
7622 from "ContinueMoving()": if an element moves to a tile directly below
7623 another element which was just falling on that tile (which was empty
7624 in the previous frame), the falling element above would just stop
7625 instead of smashing the element below (in previous version, the above
7626 element was just checked for "moving" instead of "falling", resulting
7627 in incorrect smashes caused by horizontal movement of the above
7628 element; also, the case of the player being the element to smash was
7629 simply not covered here... :-/ ) */
7631 CheckCollision[x][y] = 0;
7632 CheckImpact[x][y] = 0;
7636 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7638 if (MovDir[x][y] == MV_NONE)
7640 InitMovingField(x, y, MV_DOWN);
7641 started_moving = TRUE;
7644 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7646 if (WasJustFalling[x][y]) /* prevent animation from being restarted */
7647 MovDir[x][y] = MV_DOWN;
7649 InitMovingField(x, y, MV_DOWN);
7650 started_moving = TRUE;
7652 else if (element == EL_AMOEBA_DROP)
7654 Feld[x][y] = EL_AMOEBA_GROWING;
7655 Store[x][y] = EL_AMOEBA_WET;
7657 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7658 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7659 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7660 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7662 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7663 (IS_FREE(x - 1, y + 1) ||
7664 Feld[x - 1][y + 1] == EL_ACID));
7665 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7666 (IS_FREE(x + 1, y + 1) ||
7667 Feld[x + 1][y + 1] == EL_ACID));
7668 boolean can_fall_any = (can_fall_left || can_fall_right);
7669 boolean can_fall_both = (can_fall_left && can_fall_right);
7670 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7672 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7674 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7675 can_fall_right = FALSE;
7676 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7677 can_fall_left = FALSE;
7678 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7679 can_fall_right = FALSE;
7680 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7681 can_fall_left = FALSE;
7683 can_fall_any = (can_fall_left || can_fall_right);
7684 can_fall_both = FALSE;
7689 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7690 can_fall_right = FALSE; /* slip down on left side */
7692 can_fall_left = !(can_fall_right = RND(2));
7694 can_fall_both = FALSE;
7699 /* if not determined otherwise, prefer left side for slipping down */
7700 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7701 started_moving = TRUE;
7704 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7706 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7707 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7708 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7709 int belt_dir = game.belt_dir[belt_nr];
7711 if ((belt_dir == MV_LEFT && left_is_free) ||
7712 (belt_dir == MV_RIGHT && right_is_free))
7714 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7716 InitMovingField(x, y, belt_dir);
7717 started_moving = TRUE;
7719 Pushed[x][y] = TRUE;
7720 Pushed[nextx][y] = TRUE;
7722 GfxAction[x][y] = ACTION_DEFAULT;
7726 MovDir[x][y] = 0; /* if element was moving, stop it */
7731 /* not "else if" because of elements that can fall and move (EL_SPRING) */
7732 if (CAN_MOVE(element) && !started_moving)
7734 int move_pattern = element_info[element].move_pattern;
7737 Moving2Blocked(x, y, &newx, &newy);
7739 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7742 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7743 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7745 WasJustMoving[x][y] = 0;
7746 CheckCollision[x][y] = 0;
7748 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7750 if (Feld[x][y] != element) /* element has changed */
7754 if (!MovDelay[x][y]) /* start new movement phase */
7756 /* all objects that can change their move direction after each step
7757 (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall */
7759 if (element != EL_YAMYAM &&
7760 element != EL_DARK_YAMYAM &&
7761 element != EL_PACMAN &&
7762 !(move_pattern & MV_ANY_DIRECTION) &&
7763 move_pattern != MV_TURNING_LEFT &&
7764 move_pattern != MV_TURNING_RIGHT &&
7765 move_pattern != MV_TURNING_LEFT_RIGHT &&
7766 move_pattern != MV_TURNING_RIGHT_LEFT &&
7767 move_pattern != MV_TURNING_RANDOM)
7771 if (MovDelay[x][y] && (element == EL_BUG ||
7772 element == EL_SPACESHIP ||
7773 element == EL_SP_SNIKSNAK ||
7774 element == EL_SP_ELECTRON ||
7775 element == EL_MOLE))
7776 TEST_DrawLevelField(x, y);
7780 if (MovDelay[x][y]) /* wait some time before next movement */
7784 if (element == EL_ROBOT ||
7785 element == EL_YAMYAM ||
7786 element == EL_DARK_YAMYAM)
7788 DrawLevelElementAnimationIfNeeded(x, y, element);
7789 PlayLevelSoundAction(x, y, ACTION_WAITING);
7791 else if (element == EL_SP_ELECTRON)
7792 DrawLevelElementAnimationIfNeeded(x, y, element);
7793 else if (element == EL_DRAGON)
7796 int dir = MovDir[x][y];
7797 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
7798 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
7799 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
7800 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
7801 dir == MV_UP ? IMG_FLAMES_1_UP :
7802 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
7803 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
7805 GfxAction[x][y] = ACTION_ATTACKING;
7807 if (IS_PLAYER(x, y))
7808 DrawPlayerField(x, y);
7810 TEST_DrawLevelField(x, y);
7812 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
7814 for (i = 1; i <= 3; i++)
7816 int xx = x + i * dx;
7817 int yy = y + i * dy;
7818 int sx = SCREENX(xx);
7819 int sy = SCREENY(yy);
7820 int flame_graphic = graphic + (i - 1);
7822 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
7827 int flamed = MovingOrBlocked2Element(xx, yy);
7829 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
7832 RemoveMovingField(xx, yy);
7834 ChangeDelay[xx][yy] = 0;
7836 Feld[xx][yy] = EL_FLAMES;
7838 if (IN_SCR_FIELD(sx, sy))
7840 TEST_DrawLevelFieldCrumbled(xx, yy);
7841 DrawGraphic(sx, sy, flame_graphic, frame);
7846 if (Feld[xx][yy] == EL_FLAMES)
7847 Feld[xx][yy] = EL_EMPTY;
7848 TEST_DrawLevelField(xx, yy);
7853 if (MovDelay[x][y]) /* element still has to wait some time */
7855 PlayLevelSoundAction(x, y, ACTION_WAITING);
7861 /* now make next step */
7863 Moving2Blocked(x, y, &newx, &newy); /* get next screen position */
7865 if (DONT_COLLIDE_WITH(element) &&
7866 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
7867 !PLAYER_ENEMY_PROTECTED(newx, newy))
7869 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
7874 else if (CAN_MOVE_INTO_ACID(element) &&
7875 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
7876 !IS_MV_DIAGONAL(MovDir[x][y]) &&
7877 (MovDir[x][y] == MV_DOWN ||
7878 game.engine_version >= VERSION_IDENT(3,1,0,0)))
7880 SplashAcid(newx, newy);
7881 Store[x][y] = EL_ACID;
7883 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
7885 if (Feld[newx][newy] == EL_EXIT_OPEN ||
7886 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
7887 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
7888 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
7891 TEST_DrawLevelField(x, y);
7893 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
7894 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
7895 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
7897 local_player->friends_still_needed--;
7898 if (!local_player->friends_still_needed &&
7899 !local_player->GameOver && AllPlayersGone)
7900 PlayerWins(local_player);
7904 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
7906 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
7907 TEST_DrawLevelField(newx, newy);
7909 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
7911 else if (!IS_FREE(newx, newy))
7913 GfxAction[x][y] = ACTION_WAITING;
7915 if (IS_PLAYER(x, y))
7916 DrawPlayerField(x, y);
7918 TEST_DrawLevelField(x, y);
7923 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
7925 if (IS_FOOD_PIG(Feld[newx][newy]))
7927 if (IS_MOVING(newx, newy))
7928 RemoveMovingField(newx, newy);
7931 Feld[newx][newy] = EL_EMPTY;
7932 TEST_DrawLevelField(newx, newy);
7935 PlayLevelSound(x, y, SND_PIG_DIGGING);
7937 else if (!IS_FREE(newx, newy))
7939 if (IS_PLAYER(x, y))
7940 DrawPlayerField(x, y);
7942 TEST_DrawLevelField(x, y);
7947 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
7949 if (Store[x][y] != EL_EMPTY)
7951 boolean can_clone = FALSE;
7954 /* check if element to clone is still there */
7955 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
7957 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
7965 /* cannot clone or target field not free anymore -- do not clone */
7966 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7967 Store[x][y] = EL_EMPTY;
7970 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7972 if (IS_MV_DIAGONAL(MovDir[x][y]))
7974 int diagonal_move_dir = MovDir[x][y];
7975 int stored = Store[x][y];
7976 int change_delay = 8;
7979 /* android is moving diagonally */
7981 CreateField(x, y, EL_DIAGONAL_SHRINKING);
7983 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
7984 GfxElement[x][y] = EL_EMC_ANDROID;
7985 GfxAction[x][y] = ACTION_SHRINKING;
7986 GfxDir[x][y] = diagonal_move_dir;
7987 ChangeDelay[x][y] = change_delay;
7989 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
7992 DrawLevelGraphicAnimation(x, y, graphic);
7993 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
7995 if (Feld[newx][newy] == EL_ACID)
7997 SplashAcid(newx, newy);
8002 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8004 Store[newx][newy] = EL_EMC_ANDROID;
8005 GfxElement[newx][newy] = EL_EMC_ANDROID;
8006 GfxAction[newx][newy] = ACTION_GROWING;
8007 GfxDir[newx][newy] = diagonal_move_dir;
8008 ChangeDelay[newx][newy] = change_delay;
8010 graphic = el_act_dir2img(GfxElement[newx][newy],
8011 GfxAction[newx][newy], GfxDir[newx][newy]);
8013 DrawLevelGraphicAnimation(newx, newy, graphic);
8014 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8020 Feld[newx][newy] = EL_EMPTY;
8021 TEST_DrawLevelField(newx, newy);
8023 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8026 else if (!IS_FREE(newx, newy))
8031 else if (IS_CUSTOM_ELEMENT(element) &&
8032 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8034 if (!DigFieldByCE(newx, newy, element))
8037 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8039 RunnerVisit[x][y] = FrameCounter;
8040 PlayerVisit[x][y] /= 8; /* expire player visit path */
8043 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8045 if (!IS_FREE(newx, newy))
8047 if (IS_PLAYER(x, y))
8048 DrawPlayerField(x, y);
8050 TEST_DrawLevelField(x, y);
8056 boolean wanna_flame = !RND(10);
8057 int dx = newx - x, dy = newy - y;
8058 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8059 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8060 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8061 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8062 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8063 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8066 IS_CLASSIC_ENEMY(element1) ||
8067 IS_CLASSIC_ENEMY(element2)) &&
8068 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8069 element1 != EL_FLAMES && element2 != EL_FLAMES)
8071 ResetGfxAnimation(x, y);
8072 GfxAction[x][y] = ACTION_ATTACKING;
8074 if (IS_PLAYER(x, y))
8075 DrawPlayerField(x, y);
8077 TEST_DrawLevelField(x, y);
8079 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8081 MovDelay[x][y] = 50;
8083 Feld[newx][newy] = EL_FLAMES;
8084 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8085 Feld[newx1][newy1] = EL_FLAMES;
8086 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8087 Feld[newx2][newy2] = EL_FLAMES;
8093 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8094 Feld[newx][newy] == EL_DIAMOND)
8096 if (IS_MOVING(newx, newy))
8097 RemoveMovingField(newx, newy);
8100 Feld[newx][newy] = EL_EMPTY;
8101 TEST_DrawLevelField(newx, newy);
8104 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8106 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8107 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8109 if (AmoebaNr[newx][newy])
8111 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8112 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8113 Feld[newx][newy] == EL_BD_AMOEBA)
8114 AmoebaCnt[AmoebaNr[newx][newy]]--;
8117 if (IS_MOVING(newx, newy))
8119 RemoveMovingField(newx, newy);
8123 Feld[newx][newy] = EL_EMPTY;
8124 TEST_DrawLevelField(newx, newy);
8127 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8129 else if ((element == EL_PACMAN || element == EL_MOLE)
8130 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8132 if (AmoebaNr[newx][newy])
8134 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8135 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8136 Feld[newx][newy] == EL_BD_AMOEBA)
8137 AmoebaCnt[AmoebaNr[newx][newy]]--;
8140 if (element == EL_MOLE)
8142 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8143 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8145 ResetGfxAnimation(x, y);
8146 GfxAction[x][y] = ACTION_DIGGING;
8147 TEST_DrawLevelField(x, y);
8149 MovDelay[newx][newy] = 0; /* start amoeba shrinking delay */
8151 return; /* wait for shrinking amoeba */
8153 else /* element == EL_PACMAN */
8155 Feld[newx][newy] = EL_EMPTY;
8156 TEST_DrawLevelField(newx, newy);
8157 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8160 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8161 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8162 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8164 /* wait for shrinking amoeba to completely disappear */
8167 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8169 /* object was running against a wall */
8173 if (GFX_ELEMENT(element) != EL_SAND) /* !!! FIX THIS (crumble) !!! */
8174 DrawLevelElementAnimation(x, y, element);
8176 if (DONT_TOUCH(element))
8177 TestIfBadThingTouchesPlayer(x, y);
8182 InitMovingField(x, y, MovDir[x][y]);
8184 PlayLevelSoundAction(x, y, ACTION_MOVING);
8188 ContinueMoving(x, y);
8191 void ContinueMoving(int x, int y)
8193 int element = Feld[x][y];
8194 struct ElementInfo *ei = &element_info[element];
8195 int direction = MovDir[x][y];
8196 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8197 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8198 int newx = x + dx, newy = y + dy;
8199 int stored = Store[x][y];
8200 int stored_new = Store[newx][newy];
8201 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8202 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8203 boolean last_line = (newy == lev_fieldy - 1);
8205 MovPos[x][y] += getElementMoveStepsize(x, y);
8207 if (pushed_by_player) /* special case: moving object pushed by player */
8208 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8210 if (ABS(MovPos[x][y]) < TILEX)
8212 TEST_DrawLevelField(x, y);
8214 return; /* element is still moving */
8217 /* element reached destination field */
8219 Feld[x][y] = EL_EMPTY;
8220 Feld[newx][newy] = element;
8221 MovPos[x][y] = 0; /* force "not moving" for "crumbled sand" */
8223 if (Store[x][y] == EL_ACID) /* element is moving into acid pool */
8225 element = Feld[newx][newy] = EL_ACID;
8227 else if (element == EL_MOLE)
8229 Feld[x][y] = EL_SAND;
8231 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8233 else if (element == EL_QUICKSAND_FILLING)
8235 element = Feld[newx][newy] = get_next_element(element);
8236 Store[newx][newy] = Store[x][y];
8238 else if (element == EL_QUICKSAND_EMPTYING)
8240 Feld[x][y] = get_next_element(element);
8241 element = Feld[newx][newy] = Store[x][y];
8243 else if (element == EL_QUICKSAND_FAST_FILLING)
8245 element = Feld[newx][newy] = get_next_element(element);
8246 Store[newx][newy] = Store[x][y];
8248 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8250 Feld[x][y] = get_next_element(element);
8251 element = Feld[newx][newy] = Store[x][y];
8253 else if (element == EL_MAGIC_WALL_FILLING)
8255 element = Feld[newx][newy] = get_next_element(element);
8256 if (!game.magic_wall_active)
8257 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8258 Store[newx][newy] = Store[x][y];
8260 else if (element == EL_MAGIC_WALL_EMPTYING)
8262 Feld[x][y] = get_next_element(element);
8263 if (!game.magic_wall_active)
8264 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8265 element = Feld[newx][newy] = Store[x][y];
8267 InitField(newx, newy, FALSE);
8269 else if (element == EL_BD_MAGIC_WALL_FILLING)
8271 element = Feld[newx][newy] = get_next_element(element);
8272 if (!game.magic_wall_active)
8273 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8274 Store[newx][newy] = Store[x][y];
8276 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8278 Feld[x][y] = get_next_element(element);
8279 if (!game.magic_wall_active)
8280 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8281 element = Feld[newx][newy] = Store[x][y];
8283 InitField(newx, newy, FALSE);
8285 else if (element == EL_DC_MAGIC_WALL_FILLING)
8287 element = Feld[newx][newy] = get_next_element(element);
8288 if (!game.magic_wall_active)
8289 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8290 Store[newx][newy] = Store[x][y];
8292 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8294 Feld[x][y] = get_next_element(element);
8295 if (!game.magic_wall_active)
8296 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8297 element = Feld[newx][newy] = Store[x][y];
8299 InitField(newx, newy, FALSE);
8301 else if (element == EL_AMOEBA_DROPPING)
8303 Feld[x][y] = get_next_element(element);
8304 element = Feld[newx][newy] = Store[x][y];
8306 else if (element == EL_SOKOBAN_OBJECT)
8309 Feld[x][y] = Back[x][y];
8311 if (Back[newx][newy])
8312 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8314 Back[x][y] = Back[newx][newy] = 0;
8317 Store[x][y] = EL_EMPTY;
8322 MovDelay[newx][newy] = 0;
8324 if (CAN_CHANGE_OR_HAS_ACTION(element))
8326 /* copy element change control values to new field */
8327 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8328 ChangePage[newx][newy] = ChangePage[x][y];
8329 ChangeCount[newx][newy] = ChangeCount[x][y];
8330 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8333 CustomValue[newx][newy] = CustomValue[x][y];
8335 ChangeDelay[x][y] = 0;
8336 ChangePage[x][y] = -1;
8337 ChangeCount[x][y] = 0;
8338 ChangeEvent[x][y] = -1;
8340 CustomValue[x][y] = 0;
8342 /* copy animation control values to new field */
8343 GfxFrame[newx][newy] = GfxFrame[x][y];
8344 GfxRandom[newx][newy] = GfxRandom[x][y]; /* keep same random value */
8345 GfxAction[newx][newy] = GfxAction[x][y]; /* keep action one frame */
8346 GfxDir[newx][newy] = GfxDir[x][y]; /* keep element direction */
8348 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8350 /* some elements can leave other elements behind after moving */
8351 if (ei->move_leave_element != EL_EMPTY &&
8352 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8353 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8355 int move_leave_element = ei->move_leave_element;
8357 /* this makes it possible to leave the removed element again */
8358 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8359 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8361 Feld[x][y] = move_leave_element;
8363 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8364 MovDir[x][y] = direction;
8366 InitField(x, y, FALSE);
8368 if (GFX_CRUMBLED(Feld[x][y]))
8369 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8371 if (ELEM_IS_PLAYER(move_leave_element))
8372 RelocatePlayer(x, y, move_leave_element);
8375 /* do this after checking for left-behind element */
8376 ResetGfxAnimation(x, y); /* reset animation values for old field */
8378 if (!CAN_MOVE(element) ||
8379 (CAN_FALL(element) && direction == MV_DOWN &&
8380 (element == EL_SPRING ||
8381 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8382 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8383 GfxDir[x][y] = MovDir[newx][newy] = 0;
8385 TEST_DrawLevelField(x, y);
8386 TEST_DrawLevelField(newx, newy);
8388 Stop[newx][newy] = TRUE; /* ignore this element until the next frame */
8390 /* prevent pushed element from moving on in pushed direction */
8391 if (pushed_by_player && CAN_MOVE(element) &&
8392 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8393 !(element_info[element].move_pattern & direction))
8394 TurnRound(newx, newy);
8396 /* prevent elements on conveyor belt from moving on in last direction */
8397 if (pushed_by_conveyor && CAN_FALL(element) &&
8398 direction & MV_HORIZONTAL)
8399 MovDir[newx][newy] = 0;
8401 if (!pushed_by_player)
8403 int nextx = newx + dx, nexty = newy + dy;
8404 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8406 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8408 if (CAN_FALL(element) && direction == MV_DOWN)
8409 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8411 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8412 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8414 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8415 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8418 if (DONT_TOUCH(element)) /* object may be nasty to player or others */
8420 TestIfBadThingTouchesPlayer(newx, newy);
8421 TestIfBadThingTouchesFriend(newx, newy);
8423 if (!IS_CUSTOM_ELEMENT(element))
8424 TestIfBadThingTouchesOtherBadThing(newx, newy);
8426 else if (element == EL_PENGUIN)
8427 TestIfFriendTouchesBadThing(newx, newy);
8429 if (DONT_GET_HIT_BY(element))
8431 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8434 /* give the player one last chance (one more frame) to move away */
8435 if (CAN_FALL(element) && direction == MV_DOWN &&
8436 (last_line || (!IS_FREE(x, newy + 1) &&
8437 (!IS_PLAYER(x, newy + 1) ||
8438 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8441 if (pushed_by_player && !game.use_change_when_pushing_bug)
8443 int push_side = MV_DIR_OPPOSITE(direction);
8444 struct PlayerInfo *player = PLAYERINFO(x, y);
8446 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8447 player->index_bit, push_side);
8448 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8449 player->index_bit, push_side);
8452 if (element == EL_EMC_ANDROID && pushed_by_player) /* make another move */
8453 MovDelay[newx][newy] = 1;
8455 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8457 TestIfElementTouchesCustomElement(x, y); /* empty or new element */
8458 TestIfElementHitsCustomElement(newx, newy, direction);
8459 TestIfPlayerTouchesCustomElement(newx, newy);
8460 TestIfElementTouchesCustomElement(newx, newy);
8462 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8463 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8464 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8465 MV_DIR_OPPOSITE(direction));
8468 int AmoebeNachbarNr(int ax, int ay)
8471 int element = Feld[ax][ay];
8473 static int xy[4][2] =
8481 for (i = 0; i < NUM_DIRECTIONS; i++)
8483 int x = ax + xy[i][0];
8484 int y = ay + xy[i][1];
8486 if (!IN_LEV_FIELD(x, y))
8489 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8490 group_nr = AmoebaNr[x][y];
8496 void AmoebenVereinigen(int ax, int ay)
8498 int i, x, y, xx, yy;
8499 int new_group_nr = AmoebaNr[ax][ay];
8500 static int xy[4][2] =
8508 if (new_group_nr == 0)
8511 for (i = 0; i < NUM_DIRECTIONS; i++)
8516 if (!IN_LEV_FIELD(x, y))
8519 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8520 Feld[x][y] == EL_BD_AMOEBA ||
8521 Feld[x][y] == EL_AMOEBA_DEAD) &&
8522 AmoebaNr[x][y] != new_group_nr)
8524 int old_group_nr = AmoebaNr[x][y];
8526 if (old_group_nr == 0)
8529 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8530 AmoebaCnt[old_group_nr] = 0;
8531 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8532 AmoebaCnt2[old_group_nr] = 0;
8534 SCAN_PLAYFIELD(xx, yy)
8536 if (AmoebaNr[xx][yy] == old_group_nr)
8537 AmoebaNr[xx][yy] = new_group_nr;
8543 void AmoebeUmwandeln(int ax, int ay)
8547 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8549 int group_nr = AmoebaNr[ax][ay];
8554 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8555 printf("AmoebeUmwandeln(): This should never happen!\n");
8560 SCAN_PLAYFIELD(x, y)
8562 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8565 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8569 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8570 SND_AMOEBA_TURNING_TO_GEM :
8571 SND_AMOEBA_TURNING_TO_ROCK));
8576 static int xy[4][2] =
8584 for (i = 0; i < NUM_DIRECTIONS; i++)
8589 if (!IN_LEV_FIELD(x, y))
8592 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8594 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8595 SND_AMOEBA_TURNING_TO_GEM :
8596 SND_AMOEBA_TURNING_TO_ROCK));
8603 void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8606 int group_nr = AmoebaNr[ax][ay];
8607 boolean done = FALSE;
8612 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8613 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8618 SCAN_PLAYFIELD(x, y)
8620 if (AmoebaNr[x][y] == group_nr &&
8621 (Feld[x][y] == EL_AMOEBA_DEAD ||
8622 Feld[x][y] == EL_BD_AMOEBA ||
8623 Feld[x][y] == EL_AMOEBA_GROWING))
8626 Feld[x][y] = new_element;
8627 InitField(x, y, FALSE);
8628 TEST_DrawLevelField(x, y);
8634 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8635 SND_BD_AMOEBA_TURNING_TO_ROCK :
8636 SND_BD_AMOEBA_TURNING_TO_GEM));
8639 void AmoebeWaechst(int x, int y)
8641 static unsigned int sound_delay = 0;
8642 static unsigned int sound_delay_value = 0;
8644 if (!MovDelay[x][y]) /* start new growing cycle */
8648 if (DelayReached(&sound_delay, sound_delay_value))
8650 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8651 sound_delay_value = 30;
8655 if (MovDelay[x][y]) /* wait some time before growing bigger */
8658 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8660 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8661 6 - MovDelay[x][y]);
8663 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8666 if (!MovDelay[x][y])
8668 Feld[x][y] = Store[x][y];
8670 TEST_DrawLevelField(x, y);
8675 void AmoebaDisappearing(int x, int y)
8677 static unsigned int sound_delay = 0;
8678 static unsigned int sound_delay_value = 0;
8680 if (!MovDelay[x][y]) /* start new shrinking cycle */
8684 if (DelayReached(&sound_delay, sound_delay_value))
8685 sound_delay_value = 30;
8688 if (MovDelay[x][y]) /* wait some time before shrinking */
8691 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8693 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8694 6 - MovDelay[x][y]);
8696 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8699 if (!MovDelay[x][y])
8701 Feld[x][y] = EL_EMPTY;
8702 TEST_DrawLevelField(x, y);
8704 /* don't let mole enter this field in this cycle;
8705 (give priority to objects falling to this field from above) */
8711 void AmoebeAbleger(int ax, int ay)
8714 int element = Feld[ax][ay];
8715 int graphic = el2img(element);
8716 int newax = ax, neway = ay;
8717 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8718 static int xy[4][2] =
8726 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8728 Feld[ax][ay] = EL_AMOEBA_DEAD;
8729 TEST_DrawLevelField(ax, ay);
8733 if (IS_ANIMATED(graphic))
8734 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8736 if (!MovDelay[ax][ay]) /* start making new amoeba field */
8737 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8739 if (MovDelay[ax][ay]) /* wait some time before making new amoeba */
8742 if (MovDelay[ax][ay])
8746 if (can_drop) /* EL_AMOEBA_WET or EL_EMC_DRIPPER */
8749 int x = ax + xy[start][0];
8750 int y = ay + xy[start][1];
8752 if (!IN_LEV_FIELD(x, y))
8755 if (IS_FREE(x, y) ||
8756 CAN_GROW_INTO(Feld[x][y]) ||
8757 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8758 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8764 if (newax == ax && neway == ay)
8767 else /* normal or "filled" (BD style) amoeba */
8770 boolean waiting_for_player = FALSE;
8772 for (i = 0; i < NUM_DIRECTIONS; i++)
8774 int j = (start + i) % 4;
8775 int x = ax + xy[j][0];
8776 int y = ay + xy[j][1];
8778 if (!IN_LEV_FIELD(x, y))
8781 if (IS_FREE(x, y) ||
8782 CAN_GROW_INTO(Feld[x][y]) ||
8783 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8784 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8790 else if (IS_PLAYER(x, y))
8791 waiting_for_player = TRUE;
8794 if (newax == ax && neway == ay) /* amoeba cannot grow */
8796 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
8798 Feld[ax][ay] = EL_AMOEBA_DEAD;
8799 TEST_DrawLevelField(ax, ay);
8800 AmoebaCnt[AmoebaNr[ax][ay]]--;
8802 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) /* amoeba is completely dead */
8804 if (element == EL_AMOEBA_FULL)
8805 AmoebeUmwandeln(ax, ay);
8806 else if (element == EL_BD_AMOEBA)
8807 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
8812 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
8814 /* amoeba gets larger by growing in some direction */
8816 int new_group_nr = AmoebaNr[ax][ay];
8819 if (new_group_nr == 0)
8821 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
8822 printf("AmoebeAbleger(): This should never happen!\n");
8827 AmoebaNr[newax][neway] = new_group_nr;
8828 AmoebaCnt[new_group_nr]++;
8829 AmoebaCnt2[new_group_nr]++;
8831 /* if amoeba touches other amoeba(s) after growing, unify them */
8832 AmoebenVereinigen(newax, neway);
8834 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
8836 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
8842 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
8843 (neway == lev_fieldy - 1 && newax != ax))
8845 Feld[newax][neway] = EL_AMOEBA_GROWING; /* creation of new amoeba */
8846 Store[newax][neway] = element;
8848 else if (neway == ay || element == EL_EMC_DRIPPER)
8850 Feld[newax][neway] = EL_AMOEBA_DROP; /* drop left/right of amoeba */
8852 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
8856 InitMovingField(ax, ay, MV_DOWN); /* drop dripping from amoeba */
8857 Feld[ax][ay] = EL_AMOEBA_DROPPING;
8858 Store[ax][ay] = EL_AMOEBA_DROP;
8859 ContinueMoving(ax, ay);
8863 TEST_DrawLevelField(newax, neway);
8866 void Life(int ax, int ay)
8870 int element = Feld[ax][ay];
8871 int graphic = el2img(element);
8872 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
8874 boolean changed = FALSE;
8876 if (IS_ANIMATED(graphic))
8877 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8882 if (!MovDelay[ax][ay]) /* start new "game of life" cycle */
8883 MovDelay[ax][ay] = life_time;
8885 if (MovDelay[ax][ay]) /* wait some time before next cycle */
8888 if (MovDelay[ax][ay])
8892 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
8894 int xx = ax+x1, yy = ay+y1;
8897 if (!IN_LEV_FIELD(xx, yy))
8900 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
8902 int x = xx+x2, y = yy+y2;
8904 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
8907 if (((Feld[x][y] == element ||
8908 (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y))) &&
8910 (IS_FREE(x, y) && Stop[x][y]))
8914 if (xx == ax && yy == ay) /* field in the middle */
8916 if (nachbarn < life_parameter[0] ||
8917 nachbarn > life_parameter[1])
8919 Feld[xx][yy] = EL_EMPTY;
8921 TEST_DrawLevelField(xx, yy);
8922 Stop[xx][yy] = TRUE;
8926 else if (IS_FREE(xx, yy) || CAN_GROW_INTO(Feld[xx][yy]))
8927 { /* free border field */
8928 if (nachbarn >= life_parameter[2] &&
8929 nachbarn <= life_parameter[3])
8931 Feld[xx][yy] = element;
8932 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
8934 TEST_DrawLevelField(xx, yy);
8935 Stop[xx][yy] = TRUE;
8942 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
8943 SND_GAME_OF_LIFE_GROWING);
8946 static void InitRobotWheel(int x, int y)
8948 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
8951 static void RunRobotWheel(int x, int y)
8953 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
8956 static void StopRobotWheel(int x, int y)
8958 if (ZX == x && ZY == y)
8962 game.robot_wheel_active = FALSE;
8966 static void InitTimegateWheel(int x, int y)
8968 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
8971 static void RunTimegateWheel(int x, int y)
8973 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
8976 static void InitMagicBallDelay(int x, int y)
8978 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
8981 static void ActivateMagicBall(int bx, int by)
8985 if (level.ball_random)
8987 int pos_border = RND(8); /* select one of the eight border elements */
8988 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
8989 int xx = pos_content % 3;
8990 int yy = pos_content / 3;
8995 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
8996 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9000 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9002 int xx = x - bx + 1;
9003 int yy = y - by + 1;
9005 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9006 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9010 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9013 void CheckExit(int x, int y)
9015 if (local_player->gems_still_needed > 0 ||
9016 local_player->sokobanfields_still_needed > 0 ||
9017 local_player->lights_still_needed > 0)
9019 int element = Feld[x][y];
9020 int graphic = el2img(element);
9022 if (IS_ANIMATED(graphic))
9023 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9028 if (AllPlayersGone) /* do not re-open exit door closed after last player */
9031 Feld[x][y] = EL_EXIT_OPENING;
9033 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9036 void CheckExitEM(int x, int y)
9038 if (local_player->gems_still_needed > 0 ||
9039 local_player->sokobanfields_still_needed > 0 ||
9040 local_player->lights_still_needed > 0)
9042 int element = Feld[x][y];
9043 int graphic = el2img(element);
9045 if (IS_ANIMATED(graphic))
9046 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9051 if (AllPlayersGone) /* do not re-open exit door closed after last player */
9054 Feld[x][y] = EL_EM_EXIT_OPENING;
9056 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9059 void CheckExitSteel(int x, int y)
9061 if (local_player->gems_still_needed > 0 ||
9062 local_player->sokobanfields_still_needed > 0 ||
9063 local_player->lights_still_needed > 0)
9065 int element = Feld[x][y];
9066 int graphic = el2img(element);
9068 if (IS_ANIMATED(graphic))
9069 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9074 if (AllPlayersGone) /* do not re-open exit door closed after last player */
9077 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9079 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9082 void CheckExitSteelEM(int x, int y)
9084 if (local_player->gems_still_needed > 0 ||
9085 local_player->sokobanfields_still_needed > 0 ||
9086 local_player->lights_still_needed > 0)
9088 int element = Feld[x][y];
9089 int graphic = el2img(element);
9091 if (IS_ANIMATED(graphic))
9092 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9097 if (AllPlayersGone) /* do not re-open exit door closed after last player */
9100 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9102 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9105 void CheckExitSP(int x, int y)
9107 if (local_player->gems_still_needed > 0)
9109 int element = Feld[x][y];
9110 int graphic = el2img(element);
9112 if (IS_ANIMATED(graphic))
9113 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9118 if (AllPlayersGone) /* do not re-open exit door closed after last player */
9121 Feld[x][y] = EL_SP_EXIT_OPENING;
9123 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9126 static void CloseAllOpenTimegates()
9130 SCAN_PLAYFIELD(x, y)
9132 int element = Feld[x][y];
9134 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9136 Feld[x][y] = EL_TIMEGATE_CLOSING;
9138 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9143 void DrawTwinkleOnField(int x, int y)
9145 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9148 if (Feld[x][y] == EL_BD_DIAMOND)
9151 if (MovDelay[x][y] == 0) /* next animation frame */
9152 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9154 if (MovDelay[x][y] != 0) /* wait some time before next frame */
9158 DrawLevelElementAnimation(x, y, Feld[x][y]);
9160 if (MovDelay[x][y] != 0)
9162 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9163 10 - MovDelay[x][y]);
9165 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9170 void MauerWaechst(int x, int y)
9174 if (!MovDelay[x][y]) /* next animation frame */
9175 MovDelay[x][y] = 3 * delay;
9177 if (MovDelay[x][y]) /* wait some time before next frame */
9181 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9183 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9184 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9186 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9189 if (!MovDelay[x][y])
9191 if (MovDir[x][y] == MV_LEFT)
9193 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9194 TEST_DrawLevelField(x - 1, y);
9196 else if (MovDir[x][y] == MV_RIGHT)
9198 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9199 TEST_DrawLevelField(x + 1, y);
9201 else if (MovDir[x][y] == MV_UP)
9203 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9204 TEST_DrawLevelField(x, y - 1);
9208 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9209 TEST_DrawLevelField(x, y + 1);
9212 Feld[x][y] = Store[x][y];
9214 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9215 TEST_DrawLevelField(x, y);
9220 void MauerAbleger(int ax, int ay)
9222 int element = Feld[ax][ay];
9223 int graphic = el2img(element);
9224 boolean oben_frei = FALSE, unten_frei = FALSE;
9225 boolean links_frei = FALSE, rechts_frei = FALSE;
9226 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9227 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9228 boolean new_wall = FALSE;
9230 if (IS_ANIMATED(graphic))
9231 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9233 if (!MovDelay[ax][ay]) /* start building new wall */
9234 MovDelay[ax][ay] = 6;
9236 if (MovDelay[ax][ay]) /* wait some time before building new wall */
9239 if (MovDelay[ax][ay])
9243 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9245 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9247 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9249 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9252 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9253 element == EL_EXPANDABLE_WALL_ANY)
9257 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9258 Store[ax][ay-1] = element;
9259 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9260 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9261 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9262 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9267 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9268 Store[ax][ay+1] = element;
9269 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9270 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9271 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9272 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9277 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9278 element == EL_EXPANDABLE_WALL_ANY ||
9279 element == EL_EXPANDABLE_WALL ||
9280 element == EL_BD_EXPANDABLE_WALL)
9284 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9285 Store[ax-1][ay] = element;
9286 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9287 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9288 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9289 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9295 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9296 Store[ax+1][ay] = element;
9297 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9298 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9299 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9300 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9305 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9306 TEST_DrawLevelField(ax, ay);
9308 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9310 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9311 unten_massiv = TRUE;
9312 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9313 links_massiv = TRUE;
9314 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9315 rechts_massiv = TRUE;
9317 if (((oben_massiv && unten_massiv) ||
9318 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9319 element == EL_EXPANDABLE_WALL) &&
9320 ((links_massiv && rechts_massiv) ||
9321 element == EL_EXPANDABLE_WALL_VERTICAL))
9322 Feld[ax][ay] = EL_WALL;
9325 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9328 void MauerAblegerStahl(int ax, int ay)
9330 int element = Feld[ax][ay];
9331 int graphic = el2img(element);
9332 boolean oben_frei = FALSE, unten_frei = FALSE;
9333 boolean links_frei = FALSE, rechts_frei = FALSE;
9334 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9335 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9336 boolean new_wall = FALSE;
9338 if (IS_ANIMATED(graphic))
9339 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9341 if (!MovDelay[ax][ay]) /* start building new wall */
9342 MovDelay[ax][ay] = 6;
9344 if (MovDelay[ax][ay]) /* wait some time before building new wall */
9347 if (MovDelay[ax][ay])
9351 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9353 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9355 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9357 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9360 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9361 element == EL_EXPANDABLE_STEELWALL_ANY)
9365 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9366 Store[ax][ay-1] = element;
9367 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9368 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9369 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9370 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9375 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9376 Store[ax][ay+1] = element;
9377 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9378 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9379 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9380 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9385 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9386 element == EL_EXPANDABLE_STEELWALL_ANY)
9390 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9391 Store[ax-1][ay] = element;
9392 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9393 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9394 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9395 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9401 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9402 Store[ax+1][ay] = element;
9403 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9404 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9405 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9406 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9411 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9413 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9414 unten_massiv = TRUE;
9415 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9416 links_massiv = TRUE;
9417 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9418 rechts_massiv = TRUE;
9420 if (((oben_massiv && unten_massiv) ||
9421 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9422 ((links_massiv && rechts_massiv) ||
9423 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9424 Feld[ax][ay] = EL_STEELWALL;
9427 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9430 void CheckForDragon(int x, int y)
9433 boolean dragon_found = FALSE;
9434 static int xy[4][2] =
9442 for (i = 0; i < NUM_DIRECTIONS; i++)
9444 for (j = 0; j < 4; j++)
9446 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9448 if (IN_LEV_FIELD(xx, yy) &&
9449 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9451 if (Feld[xx][yy] == EL_DRAGON)
9452 dragon_found = TRUE;
9461 for (i = 0; i < NUM_DIRECTIONS; i++)
9463 for (j = 0; j < 3; j++)
9465 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9467 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9469 Feld[xx][yy] = EL_EMPTY;
9470 TEST_DrawLevelField(xx, yy);
9479 static void InitBuggyBase(int x, int y)
9481 int element = Feld[x][y];
9482 int activating_delay = FRAMES_PER_SECOND / 4;
9485 (element == EL_SP_BUGGY_BASE ?
9486 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9487 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9489 element == EL_SP_BUGGY_BASE_ACTIVE ?
9490 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9493 static void WarnBuggyBase(int x, int y)
9496 static int xy[4][2] =
9504 for (i = 0; i < NUM_DIRECTIONS; i++)
9506 int xx = x + xy[i][0];
9507 int yy = y + xy[i][1];
9509 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9511 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9518 static void InitTrap(int x, int y)
9520 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9523 static void ActivateTrap(int x, int y)
9525 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9528 static void ChangeActiveTrap(int x, int y)
9530 int graphic = IMG_TRAP_ACTIVE;
9532 /* if new animation frame was drawn, correct crumbled sand border */
9533 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9534 TEST_DrawLevelFieldCrumbled(x, y);
9537 static int getSpecialActionElement(int element, int number, int base_element)
9539 return (element != EL_EMPTY ? element :
9540 number != -1 ? base_element + number - 1 :
9544 static int getModifiedActionNumber(int value_old, int operator, int operand,
9545 int value_min, int value_max)
9547 int value_new = (operator == CA_MODE_SET ? operand :
9548 operator == CA_MODE_ADD ? value_old + operand :
9549 operator == CA_MODE_SUBTRACT ? value_old - operand :
9550 operator == CA_MODE_MULTIPLY ? value_old * operand :
9551 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9552 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9555 return (value_new < value_min ? value_min :
9556 value_new > value_max ? value_max :
9560 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9562 struct ElementInfo *ei = &element_info[element];
9563 struct ElementChangeInfo *change = &ei->change_page[page];
9564 int target_element = change->target_element;
9565 int action_type = change->action_type;
9566 int action_mode = change->action_mode;
9567 int action_arg = change->action_arg;
9568 int action_element = change->action_element;
9571 if (!change->has_action)
9574 /* ---------- determine action paramater values -------------------------- */
9576 int level_time_value =
9577 (level.time > 0 ? TimeLeft :
9580 int action_arg_element_raw =
9581 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9582 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9583 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9584 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9585 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9586 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9587 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9589 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9591 int action_arg_direction =
9592 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9593 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9594 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9595 change->actual_trigger_side :
9596 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9597 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9600 int action_arg_number_min =
9601 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9604 int action_arg_number_max =
9605 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9606 action_type == CA_SET_LEVEL_GEMS ? 999 :
9607 action_type == CA_SET_LEVEL_TIME ? 9999 :
9608 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9609 action_type == CA_SET_CE_VALUE ? 9999 :
9610 action_type == CA_SET_CE_SCORE ? 9999 :
9613 int action_arg_number_reset =
9614 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9615 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9616 action_type == CA_SET_LEVEL_TIME ? level.time :
9617 action_type == CA_SET_LEVEL_SCORE ? 0 :
9618 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9619 action_type == CA_SET_CE_SCORE ? 0 :
9622 int action_arg_number =
9623 (action_arg <= CA_ARG_MAX ? action_arg :
9624 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9625 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9626 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9627 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9628 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9629 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9630 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9631 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9632 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9633 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9634 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? local_player->gems_still_needed :
9635 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->score :
9636 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9637 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9638 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9639 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9640 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9641 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9642 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9643 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9644 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9647 int action_arg_number_old =
9648 (action_type == CA_SET_LEVEL_GEMS ? local_player->gems_still_needed :
9649 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9650 action_type == CA_SET_LEVEL_SCORE ? local_player->score :
9651 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9652 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9655 int action_arg_number_new =
9656 getModifiedActionNumber(action_arg_number_old,
9657 action_mode, action_arg_number,
9658 action_arg_number_min, action_arg_number_max);
9660 int trigger_player_bits =
9661 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9662 change->actual_trigger_player_bits : change->trigger_player);
9664 int action_arg_player_bits =
9665 (action_arg >= CA_ARG_PLAYER_1 &&
9666 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9667 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9668 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9671 /* ---------- execute action -------------------------------------------- */
9673 switch (action_type)
9680 /* ---------- level actions ------------------------------------------- */
9682 case CA_RESTART_LEVEL:
9684 game.restart_level = TRUE;
9689 case CA_SHOW_ENVELOPE:
9691 int element = getSpecialActionElement(action_arg_element,
9692 action_arg_number, EL_ENVELOPE_1);
9694 if (IS_ENVELOPE(element))
9695 local_player->show_envelope = element;
9700 case CA_SET_LEVEL_TIME:
9702 if (level.time > 0) /* only modify limited time value */
9704 TimeLeft = action_arg_number_new;
9706 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9708 DisplayGameControlValues();
9710 if (!TimeLeft && setup.time_limit)
9711 for (i = 0; i < MAX_PLAYERS; i++)
9712 KillPlayer(&stored_player[i]);
9718 case CA_SET_LEVEL_SCORE:
9720 local_player->score = action_arg_number_new;
9722 game_panel_controls[GAME_PANEL_SCORE].value = local_player->score;
9724 DisplayGameControlValues();
9729 case CA_SET_LEVEL_GEMS:
9731 local_player->gems_still_needed = action_arg_number_new;
9733 game.snapshot.collected_item = TRUE;
9735 game_panel_controls[GAME_PANEL_GEMS].value =
9736 local_player->gems_still_needed;
9738 DisplayGameControlValues();
9743 case CA_SET_LEVEL_WIND:
9745 game.wind_direction = action_arg_direction;
9750 case CA_SET_LEVEL_RANDOM_SEED:
9752 /* ensure that setting a new random seed while playing is predictable */
9753 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9758 /* ---------- player actions ------------------------------------------ */
9760 case CA_MOVE_PLAYER:
9762 /* automatically move to the next field in specified direction */
9763 for (i = 0; i < MAX_PLAYERS; i++)
9764 if (trigger_player_bits & (1 << i))
9765 stored_player[i].programmed_action = action_arg_direction;
9770 case CA_EXIT_PLAYER:
9772 for (i = 0; i < MAX_PLAYERS; i++)
9773 if (action_arg_player_bits & (1 << i))
9774 PlayerWins(&stored_player[i]);
9779 case CA_KILL_PLAYER:
9781 for (i = 0; i < MAX_PLAYERS; i++)
9782 if (action_arg_player_bits & (1 << i))
9783 KillPlayer(&stored_player[i]);
9788 case CA_SET_PLAYER_KEYS:
9790 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
9791 int element = getSpecialActionElement(action_arg_element,
9792 action_arg_number, EL_KEY_1);
9794 if (IS_KEY(element))
9796 for (i = 0; i < MAX_PLAYERS; i++)
9798 if (trigger_player_bits & (1 << i))
9800 stored_player[i].key[KEY_NR(element)] = key_state;
9802 DrawGameDoorValues();
9810 case CA_SET_PLAYER_SPEED:
9812 for (i = 0; i < MAX_PLAYERS; i++)
9814 if (trigger_player_bits & (1 << i))
9816 int move_stepsize = TILEX / stored_player[i].move_delay_value;
9818 if (action_arg == CA_ARG_SPEED_FASTER &&
9819 stored_player[i].cannot_move)
9821 action_arg_number = STEPSIZE_VERY_SLOW;
9823 else if (action_arg == CA_ARG_SPEED_SLOWER ||
9824 action_arg == CA_ARG_SPEED_FASTER)
9826 action_arg_number = 2;
9827 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
9830 else if (action_arg == CA_ARG_NUMBER_RESET)
9832 action_arg_number = level.initial_player_stepsize[i];
9836 getModifiedActionNumber(move_stepsize,
9839 action_arg_number_min,
9840 action_arg_number_max);
9842 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
9849 case CA_SET_PLAYER_SHIELD:
9851 for (i = 0; i < MAX_PLAYERS; i++)
9853 if (trigger_player_bits & (1 << i))
9855 if (action_arg == CA_ARG_SHIELD_OFF)
9857 stored_player[i].shield_normal_time_left = 0;
9858 stored_player[i].shield_deadly_time_left = 0;
9860 else if (action_arg == CA_ARG_SHIELD_NORMAL)
9862 stored_player[i].shield_normal_time_left = 999999;
9864 else if (action_arg == CA_ARG_SHIELD_DEADLY)
9866 stored_player[i].shield_normal_time_left = 999999;
9867 stored_player[i].shield_deadly_time_left = 999999;
9875 case CA_SET_PLAYER_GRAVITY:
9877 for (i = 0; i < MAX_PLAYERS; i++)
9879 if (trigger_player_bits & (1 << i))
9881 stored_player[i].gravity =
9882 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
9883 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
9884 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
9885 stored_player[i].gravity);
9892 case CA_SET_PLAYER_ARTWORK:
9894 for (i = 0; i < MAX_PLAYERS; i++)
9896 if (trigger_player_bits & (1 << i))
9898 int artwork_element = action_arg_element;
9900 if (action_arg == CA_ARG_ELEMENT_RESET)
9902 (level.use_artwork_element[i] ? level.artwork_element[i] :
9903 stored_player[i].element_nr);
9905 if (stored_player[i].artwork_element != artwork_element)
9906 stored_player[i].Frame = 0;
9908 stored_player[i].artwork_element = artwork_element;
9910 SetPlayerWaiting(&stored_player[i], FALSE);
9912 /* set number of special actions for bored and sleeping animation */
9913 stored_player[i].num_special_action_bored =
9914 get_num_special_action(artwork_element,
9915 ACTION_BORING_1, ACTION_BORING_LAST);
9916 stored_player[i].num_special_action_sleeping =
9917 get_num_special_action(artwork_element,
9918 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
9925 case CA_SET_PLAYER_INVENTORY:
9927 for (i = 0; i < MAX_PLAYERS; i++)
9929 struct PlayerInfo *player = &stored_player[i];
9932 if (trigger_player_bits & (1 << i))
9934 int inventory_element = action_arg_element;
9936 if (action_arg == CA_ARG_ELEMENT_TARGET ||
9937 action_arg == CA_ARG_ELEMENT_TRIGGER ||
9938 action_arg == CA_ARG_ELEMENT_ACTION)
9940 int element = inventory_element;
9941 int collect_count = element_info[element].collect_count_initial;
9943 if (!IS_CUSTOM_ELEMENT(element))
9946 if (collect_count == 0)
9947 player->inventory_infinite_element = element;
9949 for (k = 0; k < collect_count; k++)
9950 if (player->inventory_size < MAX_INVENTORY_SIZE)
9951 player->inventory_element[player->inventory_size++] =
9954 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
9955 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
9956 action_arg == CA_ARG_INVENTORY_RM_ACTION)
9958 if (player->inventory_infinite_element != EL_UNDEFINED &&
9959 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
9960 action_arg_element_raw))
9961 player->inventory_infinite_element = EL_UNDEFINED;
9963 for (k = 0, j = 0; j < player->inventory_size; j++)
9965 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
9966 action_arg_element_raw))
9967 player->inventory_element[k++] = player->inventory_element[j];
9970 player->inventory_size = k;
9972 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
9974 if (player->inventory_size > 0)
9976 for (j = 0; j < player->inventory_size - 1; j++)
9977 player->inventory_element[j] = player->inventory_element[j + 1];
9979 player->inventory_size--;
9982 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
9984 if (player->inventory_size > 0)
9985 player->inventory_size--;
9987 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
9989 player->inventory_infinite_element = EL_UNDEFINED;
9990 player->inventory_size = 0;
9992 else if (action_arg == CA_ARG_INVENTORY_RESET)
9994 player->inventory_infinite_element = EL_UNDEFINED;
9995 player->inventory_size = 0;
9997 if (level.use_initial_inventory[i])
9999 for (j = 0; j < level.initial_inventory_size[i]; j++)
10001 int element = level.initial_inventory_content[i][j];
10002 int collect_count = element_info[element].collect_count_initial;
10004 if (!IS_CUSTOM_ELEMENT(element))
10007 if (collect_count == 0)
10008 player->inventory_infinite_element = element;
10010 for (k = 0; k < collect_count; k++)
10011 if (player->inventory_size < MAX_INVENTORY_SIZE)
10012 player->inventory_element[player->inventory_size++] =
10023 /* ---------- CE actions ---------------------------------------------- */
10025 case CA_SET_CE_VALUE:
10027 int last_ce_value = CustomValue[x][y];
10029 CustomValue[x][y] = action_arg_number_new;
10031 if (CustomValue[x][y] != last_ce_value)
10033 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10034 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10036 if (CustomValue[x][y] == 0)
10038 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10039 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10046 case CA_SET_CE_SCORE:
10048 int last_ce_score = ei->collect_score;
10050 ei->collect_score = action_arg_number_new;
10052 if (ei->collect_score != last_ce_score)
10054 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10055 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10057 if (ei->collect_score == 0)
10061 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10062 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10065 This is a very special case that seems to be a mixture between
10066 CheckElementChange() and CheckTriggeredElementChange(): while
10067 the first one only affects single elements that are triggered
10068 directly, the second one affects multiple elements in the playfield
10069 that are triggered indirectly by another element. This is a third
10070 case: Changing the CE score always affects multiple identical CEs,
10071 so every affected CE must be checked, not only the single CE for
10072 which the CE score was changed in the first place (as every instance
10073 of that CE shares the same CE score, and therefore also can change)!
10075 SCAN_PLAYFIELD(xx, yy)
10077 if (Feld[xx][yy] == element)
10078 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10079 CE_SCORE_GETS_ZERO);
10087 case CA_SET_CE_ARTWORK:
10089 int artwork_element = action_arg_element;
10090 boolean reset_frame = FALSE;
10093 if (action_arg == CA_ARG_ELEMENT_RESET)
10094 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10097 if (ei->gfx_element != artwork_element)
10098 reset_frame = TRUE;
10100 ei->gfx_element = artwork_element;
10102 SCAN_PLAYFIELD(xx, yy)
10104 if (Feld[xx][yy] == element)
10108 ResetGfxAnimation(xx, yy);
10109 ResetRandomAnimationValue(xx, yy);
10112 TEST_DrawLevelField(xx, yy);
10119 /* ---------- engine actions ------------------------------------------ */
10121 case CA_SET_ENGINE_SCAN_MODE:
10123 InitPlayfieldScanMode(action_arg);
10133 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10135 int old_element = Feld[x][y];
10136 int new_element = GetElementFromGroupElement(element);
10137 int previous_move_direction = MovDir[x][y];
10138 int last_ce_value = CustomValue[x][y];
10139 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10140 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10141 boolean add_player_onto_element = (new_element_is_player &&
10142 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10143 IS_WALKABLE(old_element));
10145 if (!add_player_onto_element)
10147 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10148 RemoveMovingField(x, y);
10152 Feld[x][y] = new_element;
10154 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10155 MovDir[x][y] = previous_move_direction;
10157 if (element_info[new_element].use_last_ce_value)
10158 CustomValue[x][y] = last_ce_value;
10160 InitField_WithBug1(x, y, FALSE);
10162 new_element = Feld[x][y]; /* element may have changed */
10164 ResetGfxAnimation(x, y);
10165 ResetRandomAnimationValue(x, y);
10167 TEST_DrawLevelField(x, y);
10169 if (GFX_CRUMBLED(new_element))
10170 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10173 /* check if element under the player changes from accessible to unaccessible
10174 (needed for special case of dropping element which then changes) */
10175 /* (must be checked after creating new element for walkable group elements) */
10176 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10177 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10184 /* "ChangeCount" not set yet to allow "entered by player" change one time */
10185 if (new_element_is_player)
10186 RelocatePlayer(x, y, new_element);
10189 ChangeCount[x][y]++; /* count number of changes in the same frame */
10191 TestIfBadThingTouchesPlayer(x, y);
10192 TestIfPlayerTouchesCustomElement(x, y);
10193 TestIfElementTouchesCustomElement(x, y);
10196 static void CreateField(int x, int y, int element)
10198 CreateFieldExt(x, y, element, FALSE);
10201 static void CreateElementFromChange(int x, int y, int element)
10203 element = GET_VALID_RUNTIME_ELEMENT(element);
10205 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10207 int old_element = Feld[x][y];
10209 /* prevent changed element from moving in same engine frame
10210 unless both old and new element can either fall or move */
10211 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10212 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10216 CreateFieldExt(x, y, element, TRUE);
10219 static boolean ChangeElement(int x, int y, int element, int page)
10221 struct ElementInfo *ei = &element_info[element];
10222 struct ElementChangeInfo *change = &ei->change_page[page];
10223 int ce_value = CustomValue[x][y];
10224 int ce_score = ei->collect_score;
10225 int target_element;
10226 int old_element = Feld[x][y];
10228 /* always use default change event to prevent running into a loop */
10229 if (ChangeEvent[x][y] == -1)
10230 ChangeEvent[x][y] = CE_DELAY;
10232 if (ChangeEvent[x][y] == CE_DELAY)
10234 /* reset actual trigger element, trigger player and action element */
10235 change->actual_trigger_element = EL_EMPTY;
10236 change->actual_trigger_player = EL_EMPTY;
10237 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10238 change->actual_trigger_side = CH_SIDE_NONE;
10239 change->actual_trigger_ce_value = 0;
10240 change->actual_trigger_ce_score = 0;
10243 /* do not change elements more than a specified maximum number of changes */
10244 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10247 ChangeCount[x][y]++; /* count number of changes in the same frame */
10249 if (change->explode)
10256 if (change->use_target_content)
10258 boolean complete_replace = TRUE;
10259 boolean can_replace[3][3];
10262 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10265 boolean is_walkable;
10266 boolean is_diggable;
10267 boolean is_collectible;
10268 boolean is_removable;
10269 boolean is_destructible;
10270 int ex = x + xx - 1;
10271 int ey = y + yy - 1;
10272 int content_element = change->target_content.e[xx][yy];
10275 can_replace[xx][yy] = TRUE;
10277 if (ex == x && ey == y) /* do not check changing element itself */
10280 if (content_element == EL_EMPTY_SPACE)
10282 can_replace[xx][yy] = FALSE; /* do not replace border with space */
10287 if (!IN_LEV_FIELD(ex, ey))
10289 can_replace[xx][yy] = FALSE;
10290 complete_replace = FALSE;
10297 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10298 e = MovingOrBlocked2Element(ex, ey);
10300 is_empty = (IS_FREE(ex, ey) ||
10301 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10303 is_walkable = (is_empty || IS_WALKABLE(e));
10304 is_diggable = (is_empty || IS_DIGGABLE(e));
10305 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10306 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10307 is_removable = (is_diggable || is_collectible);
10309 can_replace[xx][yy] =
10310 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10311 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10312 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10313 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10314 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10315 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10316 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10318 if (!can_replace[xx][yy])
10319 complete_replace = FALSE;
10322 if (!change->only_if_complete || complete_replace)
10324 boolean something_has_changed = FALSE;
10326 if (change->only_if_complete && change->use_random_replace &&
10327 RND(100) < change->random_percentage)
10330 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10332 int ex = x + xx - 1;
10333 int ey = y + yy - 1;
10334 int content_element;
10336 if (can_replace[xx][yy] && (!change->use_random_replace ||
10337 RND(100) < change->random_percentage))
10339 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10340 RemoveMovingField(ex, ey);
10342 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10344 content_element = change->target_content.e[xx][yy];
10345 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10346 ce_value, ce_score);
10348 CreateElementFromChange(ex, ey, target_element);
10350 something_has_changed = TRUE;
10352 /* for symmetry reasons, freeze newly created border elements */
10353 if (ex != x || ey != y)
10354 Stop[ex][ey] = TRUE; /* no more moving in this frame */
10358 if (something_has_changed)
10360 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10361 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10367 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10368 ce_value, ce_score);
10370 if (element == EL_DIAGONAL_GROWING ||
10371 element == EL_DIAGONAL_SHRINKING)
10373 target_element = Store[x][y];
10375 Store[x][y] = EL_EMPTY;
10378 CreateElementFromChange(x, y, target_element);
10380 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10381 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10384 /* this uses direct change before indirect change */
10385 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10390 static void HandleElementChange(int x, int y, int page)
10392 int element = MovingOrBlocked2Element(x, y);
10393 struct ElementInfo *ei = &element_info[element];
10394 struct ElementChangeInfo *change = &ei->change_page[page];
10395 boolean handle_action_before_change = FALSE;
10398 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10399 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10402 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10403 x, y, element, element_info[element].token_name);
10404 printf("HandleElementChange(): This should never happen!\n");
10409 /* this can happen with classic bombs on walkable, changing elements */
10410 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10415 if (ChangeDelay[x][y] == 0) /* initialize element change */
10417 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10419 if (change->can_change)
10421 /* !!! not clear why graphic animation should be reset at all here !!! */
10422 /* !!! UPDATE: but is needed for correct Snake Bite tail animation !!! */
10423 /* !!! SOLUTION: do not reset if graphics engine set to 4 or above !!! */
10426 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10428 When using an animation frame delay of 1 (this only happens with
10429 "sp_zonk.moving.left/right" in the classic graphics), the default
10430 (non-moving) animation shows wrong animation frames (while the
10431 moving animation, like "sp_zonk.moving.left/right", is correct,
10432 so this graphical bug never shows up with the classic graphics).
10433 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10434 be drawn instead of the correct frames 0,1,2,3. This is caused by
10435 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10436 an element change: First when the change delay ("ChangeDelay[][]")
10437 counter has reached zero after decrementing, then a second time in
10438 the next frame (after "GfxFrame[][]" was already incremented) when
10439 "ChangeDelay[][]" is reset to the initial delay value again.
10441 This causes frame 0 to be drawn twice, while the last frame won't
10442 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10444 As some animations may already be cleverly designed around this bug
10445 (at least the "Snake Bite" snake tail animation does this), it cannot
10446 simply be fixed here without breaking such existing animations.
10447 Unfortunately, it cannot easily be detected if a graphics set was
10448 designed "before" or "after" the bug was fixed. As a workaround,
10449 a new graphics set option "game.graphics_engine_version" was added
10450 to be able to specify the game's major release version for which the
10451 graphics set was designed, which can then be used to decide if the
10452 bugfix should be used (version 4 and above) or not (version 3 or
10453 below, or if no version was specified at all, as with old sets).
10455 (The wrong/fixed animation frames can be tested with the test level set
10456 "test_gfxframe" and level "000", which contains a specially prepared
10457 custom element at level position (x/y) == (11/9) which uses the zonk
10458 animation mentioned above. Using "game.graphics_engine_version: 4"
10459 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10460 This can also be seen from the debug output for this test element.)
10463 /* when a custom element is about to change (for example by change delay),
10464 do not reset graphic animation when the custom element is moving */
10465 if (game.graphics_engine_version < 4 &&
10468 ResetGfxAnimation(x, y);
10469 ResetRandomAnimationValue(x, y);
10472 if (change->pre_change_function)
10473 change->pre_change_function(x, y);
10477 ChangeDelay[x][y]--;
10479 if (ChangeDelay[x][y] != 0) /* continue element change */
10481 if (change->can_change)
10483 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10485 if (IS_ANIMATED(graphic))
10486 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10488 if (change->change_function)
10489 change->change_function(x, y);
10492 else /* finish element change */
10494 if (ChangePage[x][y] != -1) /* remember page from delayed change */
10496 page = ChangePage[x][y];
10497 ChangePage[x][y] = -1;
10499 change = &ei->change_page[page];
10502 if (IS_MOVING(x, y)) /* never change a running system ;-) */
10504 ChangeDelay[x][y] = 1; /* try change after next move step */
10505 ChangePage[x][y] = page; /* remember page to use for change */
10510 /* special case: set new level random seed before changing element */
10511 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10512 handle_action_before_change = TRUE;
10514 if (change->has_action && handle_action_before_change)
10515 ExecuteCustomElementAction(x, y, element, page);
10517 if (change->can_change)
10519 if (ChangeElement(x, y, element, page))
10521 if (change->post_change_function)
10522 change->post_change_function(x, y);
10526 if (change->has_action && !handle_action_before_change)
10527 ExecuteCustomElementAction(x, y, element, page);
10531 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10532 int trigger_element,
10534 int trigger_player,
10538 boolean change_done_any = FALSE;
10539 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10542 if (!(trigger_events[trigger_element][trigger_event]))
10545 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10547 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10549 int element = EL_CUSTOM_START + i;
10550 boolean change_done = FALSE;
10553 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10554 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10557 for (p = 0; p < element_info[element].num_change_pages; p++)
10559 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10561 if (change->can_change_or_has_action &&
10562 change->has_event[trigger_event] &&
10563 change->trigger_side & trigger_side &&
10564 change->trigger_player & trigger_player &&
10565 change->trigger_page & trigger_page_bits &&
10566 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10568 change->actual_trigger_element = trigger_element;
10569 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10570 change->actual_trigger_player_bits = trigger_player;
10571 change->actual_trigger_side = trigger_side;
10572 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10573 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10575 if ((change->can_change && !change_done) || change->has_action)
10579 SCAN_PLAYFIELD(x, y)
10581 if (Feld[x][y] == element)
10583 if (change->can_change && !change_done)
10585 /* if element already changed in this frame, not only prevent
10586 another element change (checked in ChangeElement()), but
10587 also prevent additional element actions for this element */
10589 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10590 !level.use_action_after_change_bug)
10593 ChangeDelay[x][y] = 1;
10594 ChangeEvent[x][y] = trigger_event;
10596 HandleElementChange(x, y, p);
10598 else if (change->has_action)
10600 /* if element already changed in this frame, not only prevent
10601 another element change (checked in ChangeElement()), but
10602 also prevent additional element actions for this element */
10604 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10605 !level.use_action_after_change_bug)
10608 ExecuteCustomElementAction(x, y, element, p);
10609 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10614 if (change->can_change)
10616 change_done = TRUE;
10617 change_done_any = TRUE;
10624 RECURSION_LOOP_DETECTION_END();
10626 return change_done_any;
10629 static boolean CheckElementChangeExt(int x, int y,
10631 int trigger_element,
10633 int trigger_player,
10636 boolean change_done = FALSE;
10639 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10640 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10643 if (Feld[x][y] == EL_BLOCKED)
10645 Blocked2Moving(x, y, &x, &y);
10646 element = Feld[x][y];
10649 /* check if element has already changed or is about to change after moving */
10650 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10651 Feld[x][y] != element) ||
10653 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10654 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10655 ChangePage[x][y] != -1)))
10658 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10660 for (p = 0; p < element_info[element].num_change_pages; p++)
10662 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10664 /* check trigger element for all events where the element that is checked
10665 for changing interacts with a directly adjacent element -- this is
10666 different to element changes that affect other elements to change on the
10667 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10668 boolean check_trigger_element =
10669 (trigger_event == CE_TOUCHING_X ||
10670 trigger_event == CE_HITTING_X ||
10671 trigger_event == CE_HIT_BY_X ||
10672 trigger_event == CE_DIGGING_X); /* this one was forgotten until 3.2.3 */
10674 if (change->can_change_or_has_action &&
10675 change->has_event[trigger_event] &&
10676 change->trigger_side & trigger_side &&
10677 change->trigger_player & trigger_player &&
10678 (!check_trigger_element ||
10679 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10681 change->actual_trigger_element = trigger_element;
10682 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10683 change->actual_trigger_player_bits = trigger_player;
10684 change->actual_trigger_side = trigger_side;
10685 change->actual_trigger_ce_value = CustomValue[x][y];
10686 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10688 /* special case: trigger element not at (x,y) position for some events */
10689 if (check_trigger_element)
10701 { 0, 0 }, { 0, 0 }, { 0, 0 },
10705 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10706 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10708 change->actual_trigger_ce_value = CustomValue[xx][yy];
10709 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10712 if (change->can_change && !change_done)
10714 ChangeDelay[x][y] = 1;
10715 ChangeEvent[x][y] = trigger_event;
10717 HandleElementChange(x, y, p);
10719 change_done = TRUE;
10721 else if (change->has_action)
10723 ExecuteCustomElementAction(x, y, element, p);
10724 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10729 RECURSION_LOOP_DETECTION_END();
10731 return change_done;
10734 static void PlayPlayerSound(struct PlayerInfo *player)
10736 int jx = player->jx, jy = player->jy;
10737 int sound_element = player->artwork_element;
10738 int last_action = player->last_action_waiting;
10739 int action = player->action_waiting;
10741 if (player->is_waiting)
10743 if (action != last_action)
10744 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10746 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10750 if (action != last_action)
10751 StopSound(element_info[sound_element].sound[last_action]);
10753 if (last_action == ACTION_SLEEPING)
10754 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
10758 static void PlayAllPlayersSound()
10762 for (i = 0; i < MAX_PLAYERS; i++)
10763 if (stored_player[i].active)
10764 PlayPlayerSound(&stored_player[i]);
10767 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
10769 boolean last_waiting = player->is_waiting;
10770 int move_dir = player->MovDir;
10772 player->dir_waiting = move_dir;
10773 player->last_action_waiting = player->action_waiting;
10777 if (!last_waiting) /* not waiting -> waiting */
10779 player->is_waiting = TRUE;
10781 player->frame_counter_bored =
10783 game.player_boring_delay_fixed +
10784 GetSimpleRandom(game.player_boring_delay_random);
10785 player->frame_counter_sleeping =
10787 game.player_sleeping_delay_fixed +
10788 GetSimpleRandom(game.player_sleeping_delay_random);
10790 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
10793 if (game.player_sleeping_delay_fixed +
10794 game.player_sleeping_delay_random > 0 &&
10795 player->anim_delay_counter == 0 &&
10796 player->post_delay_counter == 0 &&
10797 FrameCounter >= player->frame_counter_sleeping)
10798 player->is_sleeping = TRUE;
10799 else if (game.player_boring_delay_fixed +
10800 game.player_boring_delay_random > 0 &&
10801 FrameCounter >= player->frame_counter_bored)
10802 player->is_bored = TRUE;
10804 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
10805 player->is_bored ? ACTION_BORING :
10808 if (player->is_sleeping && player->use_murphy)
10810 /* special case for sleeping Murphy when leaning against non-free tile */
10812 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
10813 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
10814 !IS_MOVING(player->jx - 1, player->jy)))
10815 move_dir = MV_LEFT;
10816 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
10817 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
10818 !IS_MOVING(player->jx + 1, player->jy)))
10819 move_dir = MV_RIGHT;
10821 player->is_sleeping = FALSE;
10823 player->dir_waiting = move_dir;
10826 if (player->is_sleeping)
10828 if (player->num_special_action_sleeping > 0)
10830 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10832 int last_special_action = player->special_action_sleeping;
10833 int num_special_action = player->num_special_action_sleeping;
10834 int special_action =
10835 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
10836 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
10837 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
10838 last_special_action + 1 : ACTION_SLEEPING);
10839 int special_graphic =
10840 el_act_dir2img(player->artwork_element, special_action, move_dir);
10842 player->anim_delay_counter =
10843 graphic_info[special_graphic].anim_delay_fixed +
10844 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10845 player->post_delay_counter =
10846 graphic_info[special_graphic].post_delay_fixed +
10847 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10849 player->special_action_sleeping = special_action;
10852 if (player->anim_delay_counter > 0)
10854 player->action_waiting = player->special_action_sleeping;
10855 player->anim_delay_counter--;
10857 else if (player->post_delay_counter > 0)
10859 player->post_delay_counter--;
10863 else if (player->is_bored)
10865 if (player->num_special_action_bored > 0)
10867 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10869 int special_action =
10870 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
10871 int special_graphic =
10872 el_act_dir2img(player->artwork_element, special_action, move_dir);
10874 player->anim_delay_counter =
10875 graphic_info[special_graphic].anim_delay_fixed +
10876 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10877 player->post_delay_counter =
10878 graphic_info[special_graphic].post_delay_fixed +
10879 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10881 player->special_action_bored = special_action;
10884 if (player->anim_delay_counter > 0)
10886 player->action_waiting = player->special_action_bored;
10887 player->anim_delay_counter--;
10889 else if (player->post_delay_counter > 0)
10891 player->post_delay_counter--;
10896 else if (last_waiting) /* waiting -> not waiting */
10898 player->is_waiting = FALSE;
10899 player->is_bored = FALSE;
10900 player->is_sleeping = FALSE;
10902 player->frame_counter_bored = -1;
10903 player->frame_counter_sleeping = -1;
10905 player->anim_delay_counter = 0;
10906 player->post_delay_counter = 0;
10908 player->dir_waiting = player->MovDir;
10909 player->action_waiting = ACTION_DEFAULT;
10911 player->special_action_bored = ACTION_DEFAULT;
10912 player->special_action_sleeping = ACTION_DEFAULT;
10916 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
10918 if ((!player->is_moving && player->was_moving) ||
10919 (player->MovPos == 0 && player->was_moving) ||
10920 (player->is_snapping && !player->was_snapping) ||
10921 (player->is_dropping && !player->was_dropping))
10923 if (!CheckSaveEngineSnapshotToList())
10926 player->was_moving = FALSE;
10927 player->was_snapping = TRUE;
10928 player->was_dropping = TRUE;
10932 if (player->is_moving)
10933 player->was_moving = TRUE;
10935 if (!player->is_snapping)
10936 player->was_snapping = FALSE;
10938 if (!player->is_dropping)
10939 player->was_dropping = FALSE;
10943 static void CheckSingleStepMode(struct PlayerInfo *player)
10945 if (tape.single_step && tape.recording && !tape.pausing)
10947 /* as it is called "single step mode", just return to pause mode when the
10948 player stopped moving after one tile (or never starts moving at all) */
10949 if (!player->is_moving &&
10950 !player->is_pushing &&
10951 !player->is_dropping_pressed)
10953 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
10954 SnapField(player, 0, 0); /* stop snapping */
10958 CheckSaveEngineSnapshot(player);
10961 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
10963 int left = player_action & JOY_LEFT;
10964 int right = player_action & JOY_RIGHT;
10965 int up = player_action & JOY_UP;
10966 int down = player_action & JOY_DOWN;
10967 int button1 = player_action & JOY_BUTTON_1;
10968 int button2 = player_action & JOY_BUTTON_2;
10969 int dx = (left ? -1 : right ? 1 : 0);
10970 int dy = (up ? -1 : down ? 1 : 0);
10972 if (!player->active || tape.pausing)
10978 SnapField(player, dx, dy);
10982 DropElement(player);
10984 MovePlayer(player, dx, dy);
10987 CheckSingleStepMode(player);
10989 SetPlayerWaiting(player, FALSE);
10991 return player_action;
10995 /* no actions for this player (no input at player's configured device) */
10997 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
10998 SnapField(player, 0, 0);
10999 CheckGravityMovementWhenNotMoving(player);
11001 if (player->MovPos == 0)
11002 SetPlayerWaiting(player, TRUE);
11004 if (player->MovPos == 0) /* needed for tape.playing */
11005 player->is_moving = FALSE;
11007 player->is_dropping = FALSE;
11008 player->is_dropping_pressed = FALSE;
11009 player->drop_pressed_delay = 0;
11011 CheckSingleStepMode(player);
11017 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11020 if (!tape.use_mouse)
11023 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11024 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11025 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11028 static void SetTapeActionFromMouseAction(byte *tape_action,
11029 struct MouseActionInfo *mouse_action)
11031 if (!tape.use_mouse)
11034 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11035 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11036 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11039 static void CheckLevelTime()
11043 /* !!! SAME CODE AS IN "GameActions()" -- FIX THIS !!! */
11044 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11046 if (level.native_em_level->lev->home == 0) /* all players at home */
11048 PlayerWins(local_player);
11050 AllPlayersGone = TRUE;
11052 level.native_em_level->lev->home = -1;
11055 if (level.native_em_level->ply[0]->alive == 0 &&
11056 level.native_em_level->ply[1]->alive == 0 &&
11057 level.native_em_level->ply[2]->alive == 0 &&
11058 level.native_em_level->ply[3]->alive == 0) /* all dead */
11059 AllPlayersGone = TRUE;
11061 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11063 if (game_sp.LevelSolved &&
11064 !game_sp.GameOver) /* game won */
11066 PlayerWins(local_player);
11068 game_sp.GameOver = TRUE;
11070 AllPlayersGone = TRUE;
11073 if (game_sp.GameOver) /* game lost */
11074 AllPlayersGone = TRUE;
11076 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11078 if (game_mm.level_solved &&
11079 !game_mm.game_over) /* game won */
11081 PlayerWins(local_player);
11083 game_mm.game_over = TRUE;
11085 AllPlayersGone = TRUE;
11088 if (game_mm.game_over) /* game lost */
11089 AllPlayersGone = TRUE;
11092 if (TimeFrames >= FRAMES_PER_SECOND)
11097 for (i = 0; i < MAX_PLAYERS; i++)
11099 struct PlayerInfo *player = &stored_player[i];
11101 if (SHIELD_ON(player))
11103 player->shield_normal_time_left--;
11105 if (player->shield_deadly_time_left > 0)
11106 player->shield_deadly_time_left--;
11110 if (!local_player->LevelSolved && !level.use_step_counter)
11118 if (TimeLeft <= 10 && setup.time_limit)
11119 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11121 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11122 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11124 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11126 if (!TimeLeft && setup.time_limit)
11128 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11129 level.native_em_level->lev->killed_out_of_time = TRUE;
11131 for (i = 0; i < MAX_PLAYERS; i++)
11132 KillPlayer(&stored_player[i]);
11135 else if (game.no_time_limit && !AllPlayersGone) /* level w/o time limit */
11137 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11140 level.native_em_level->lev->time =
11141 (game.no_time_limit ? TimePlayed : TimeLeft);
11144 if (tape.recording || tape.playing)
11145 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11148 if (tape.recording || tape.playing)
11149 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11151 UpdateAndDisplayGameControlValues();
11154 void AdvanceFrameAndPlayerCounters(int player_nr)
11158 /* advance frame counters (global frame counter and time frame counter) */
11162 /* advance player counters (counters for move delay, move animation etc.) */
11163 for (i = 0; i < MAX_PLAYERS; i++)
11165 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11166 int move_delay_value = stored_player[i].move_delay_value;
11167 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11169 if (!advance_player_counters) /* not all players may be affected */
11172 if (move_frames == 0) /* less than one move per game frame */
11174 int stepsize = TILEX / move_delay_value;
11175 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11176 int count = (stored_player[i].is_moving ?
11177 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11179 if (count % delay == 0)
11183 stored_player[i].Frame += move_frames;
11185 if (stored_player[i].MovPos != 0)
11186 stored_player[i].StepFrame += move_frames;
11188 if (stored_player[i].move_delay > 0)
11189 stored_player[i].move_delay--;
11191 /* due to bugs in previous versions, counter must count up, not down */
11192 if (stored_player[i].push_delay != -1)
11193 stored_player[i].push_delay++;
11195 if (stored_player[i].drop_delay > 0)
11196 stored_player[i].drop_delay--;
11198 if (stored_player[i].is_dropping_pressed)
11199 stored_player[i].drop_pressed_delay++;
11203 void StartGameActions(boolean init_network_game, boolean record_tape,
11206 unsigned int new_random_seed = InitRND(random_seed);
11209 TapeStartRecording(new_random_seed);
11211 if (init_network_game)
11213 SendToServer_StartPlaying();
11221 void GameActionsExt()
11224 static unsigned int game_frame_delay = 0;
11226 unsigned int game_frame_delay_value;
11227 byte *recorded_player_action;
11228 byte summarized_player_action = 0;
11229 byte tape_action[MAX_PLAYERS];
11232 /* detect endless loops, caused by custom element programming */
11233 if (recursion_loop_detected && recursion_loop_depth == 0)
11235 char *message = getStringCat3("Internal Error! Element ",
11236 EL_NAME(recursion_loop_element),
11237 " caused endless loop! Quit the game?");
11239 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11240 EL_NAME(recursion_loop_element));
11242 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11244 recursion_loop_detected = FALSE; /* if game should be continued */
11251 if (game.restart_level)
11252 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11254 /* !!! SAME CODE AS IN "CheckLevelTime()" -- FIX THIS !!! */
11255 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11257 if (level.native_em_level->lev->home == 0) /* all players at home */
11259 PlayerWins(local_player);
11261 AllPlayersGone = TRUE;
11263 level.native_em_level->lev->home = -1;
11266 if (level.native_em_level->ply[0]->alive == 0 &&
11267 level.native_em_level->ply[1]->alive == 0 &&
11268 level.native_em_level->ply[2]->alive == 0 &&
11269 level.native_em_level->ply[3]->alive == 0) /* all dead */
11270 AllPlayersGone = TRUE;
11272 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11274 if (game_sp.LevelSolved &&
11275 !game_sp.GameOver) /* game won */
11277 PlayerWins(local_player);
11279 game_sp.GameOver = TRUE;
11281 AllPlayersGone = TRUE;
11284 if (game_sp.GameOver) /* game lost */
11285 AllPlayersGone = TRUE;
11287 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11289 if (game_mm.level_solved &&
11290 !game_mm.game_over) /* game won */
11292 PlayerWins(local_player);
11294 game_mm.game_over = TRUE;
11296 AllPlayersGone = TRUE;
11299 if (game_mm.game_over) /* game lost */
11300 AllPlayersGone = TRUE;
11303 if (local_player->LevelSolved && !local_player->LevelSolved_GameEnd)
11306 if (AllPlayersGone && !TAPE_IS_STOPPED(tape))
11309 if (game_status != GAME_MODE_PLAYING) /* status might have changed */
11312 game_frame_delay_value =
11313 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11315 if (tape.playing && tape.warp_forward && !tape.pausing)
11316 game_frame_delay_value = 0;
11318 SetVideoFrameDelay(game_frame_delay_value);
11322 /* ---------- main game synchronization point ---------- */
11324 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11326 printf("::: skip == %d\n", skip);
11329 /* ---------- main game synchronization point ---------- */
11331 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11335 if (network_playing && !network_player_action_received)
11337 /* try to get network player actions in time */
11339 /* last chance to get network player actions without main loop delay */
11340 HandleNetworking();
11342 /* game was quit by network peer */
11343 if (game_status != GAME_MODE_PLAYING)
11346 if (!network_player_action_received)
11347 return; /* failed to get network player actions in time */
11349 /* do not yet reset "network_player_action_received" (for tape.pausing) */
11355 /* at this point we know that we really continue executing the game */
11357 network_player_action_received = FALSE;
11359 /* when playing tape, read previously recorded player input from tape data */
11360 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11362 local_player->effective_mouse_action = local_player->mouse_action;
11364 if (recorded_player_action != NULL)
11365 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11366 recorded_player_action);
11368 /* TapePlayAction() may return NULL when toggling to "pause before death" */
11372 if (tape.set_centered_player)
11374 game.centered_player_nr_next = tape.centered_player_nr_next;
11375 game.set_centered_player = TRUE;
11378 for (i = 0; i < MAX_PLAYERS; i++)
11380 summarized_player_action |= stored_player[i].action;
11382 if (!network_playing && (game.team_mode || tape.playing))
11383 stored_player[i].effective_action = stored_player[i].action;
11386 if (network_playing)
11387 SendToServer_MovePlayer(summarized_player_action);
11389 // summarize all actions at local players mapped input device position
11390 // (this allows using different input devices in single player mode)
11391 if (!network.enabled && !game.team_mode)
11392 stored_player[map_player_action[local_player->index_nr]].effective_action =
11393 summarized_player_action;
11395 if (tape.recording &&
11397 setup.input_on_focus &&
11398 game.centered_player_nr != -1)
11400 for (i = 0; i < MAX_PLAYERS; i++)
11401 stored_player[i].effective_action =
11402 (i == game.centered_player_nr ? summarized_player_action : 0);
11405 if (recorded_player_action != NULL)
11406 for (i = 0; i < MAX_PLAYERS; i++)
11407 stored_player[i].effective_action = recorded_player_action[i];
11409 for (i = 0; i < MAX_PLAYERS; i++)
11411 tape_action[i] = stored_player[i].effective_action;
11413 /* (this may happen in the RND game engine if a player was not present on
11414 the playfield on level start, but appeared later from a custom element */
11415 if (setup.team_mode &&
11418 !tape.player_participates[i])
11419 tape.player_participates[i] = TRUE;
11422 SetTapeActionFromMouseAction(tape_action,
11423 &local_player->effective_mouse_action);
11425 /* only record actions from input devices, but not programmed actions */
11426 if (tape.recording)
11427 TapeRecordAction(tape_action);
11429 #if USE_NEW_PLAYER_ASSIGNMENTS
11430 // !!! also map player actions in single player mode !!!
11431 // if (game.team_mode)
11434 byte mapped_action[MAX_PLAYERS];
11436 #if DEBUG_PLAYER_ACTIONS
11438 for (i = 0; i < MAX_PLAYERS; i++)
11439 printf(" %d, ", stored_player[i].effective_action);
11442 for (i = 0; i < MAX_PLAYERS; i++)
11443 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11445 for (i = 0; i < MAX_PLAYERS; i++)
11446 stored_player[i].effective_action = mapped_action[i];
11448 #if DEBUG_PLAYER_ACTIONS
11450 for (i = 0; i < MAX_PLAYERS; i++)
11451 printf(" %d, ", stored_player[i].effective_action);
11455 #if DEBUG_PLAYER_ACTIONS
11459 for (i = 0; i < MAX_PLAYERS; i++)
11460 printf(" %d, ", stored_player[i].effective_action);
11466 for (i = 0; i < MAX_PLAYERS; i++)
11468 // allow engine snapshot in case of changed movement attempt
11469 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11470 (stored_player[i].effective_action & KEY_MOTION))
11471 game.snapshot.changed_action = TRUE;
11473 // allow engine snapshot in case of snapping/dropping attempt
11474 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11475 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11476 game.snapshot.changed_action = TRUE;
11478 game.snapshot.last_action[i] = stored_player[i].effective_action;
11481 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11483 GameActions_EM_Main();
11485 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11487 GameActions_SP_Main();
11489 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11491 GameActions_MM_Main();
11495 GameActions_RND_Main();
11498 BlitScreenToBitmap(backbuffer);
11502 AdvanceFrameAndPlayerCounters(-1); /* advance counters for all players */
11504 if (global.show_frames_per_second)
11506 static unsigned int fps_counter = 0;
11507 static int fps_frames = 0;
11508 unsigned int fps_delay_ms = Counter() - fps_counter;
11512 if (fps_delay_ms >= 500) /* calculate FPS every 0.5 seconds */
11514 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11517 fps_counter = Counter();
11519 /* always draw FPS to screen after FPS value was updated */
11520 redraw_mask |= REDRAW_FPS;
11523 /* only draw FPS if no screen areas are deactivated (invisible warp mode) */
11524 if (GetDrawDeactivationMask() == REDRAW_NONE)
11525 redraw_mask |= REDRAW_FPS;
11529 static void GameActions_CheckSaveEngineSnapshot()
11531 if (!game.snapshot.save_snapshot)
11534 // clear flag for saving snapshot _before_ saving snapshot
11535 game.snapshot.save_snapshot = FALSE;
11537 SaveEngineSnapshotToList();
11544 GameActions_CheckSaveEngineSnapshot();
11547 void GameActions_EM_Main()
11549 byte effective_action[MAX_PLAYERS];
11550 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11553 for (i = 0; i < MAX_PLAYERS; i++)
11554 effective_action[i] = stored_player[i].effective_action;
11556 GameActions_EM(effective_action, warp_mode);
11559 void GameActions_SP_Main()
11561 byte effective_action[MAX_PLAYERS];
11562 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11565 for (i = 0; i < MAX_PLAYERS; i++)
11566 effective_action[i] = stored_player[i].effective_action;
11568 GameActions_SP(effective_action, warp_mode);
11570 for (i = 0; i < MAX_PLAYERS; i++)
11572 if (stored_player[i].force_dropping)
11573 stored_player[i].action |= KEY_BUTTON_DROP;
11575 stored_player[i].force_dropping = FALSE;
11579 void GameActions_MM_Main()
11581 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11583 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11586 void GameActions_RND_Main()
11591 void GameActions_RND()
11593 int magic_wall_x = 0, magic_wall_y = 0;
11594 int i, x, y, element, graphic, last_gfx_frame;
11596 InitPlayfieldScanModeVars();
11598 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11600 SCAN_PLAYFIELD(x, y)
11602 ChangeCount[x][y] = 0;
11603 ChangeEvent[x][y] = -1;
11607 if (game.set_centered_player)
11609 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11611 /* switching to "all players" only possible if all players fit to screen */
11612 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11614 game.centered_player_nr_next = game.centered_player_nr;
11615 game.set_centered_player = FALSE;
11618 /* do not switch focus to non-existing (or non-active) player */
11619 if (game.centered_player_nr_next >= 0 &&
11620 !stored_player[game.centered_player_nr_next].active)
11622 game.centered_player_nr_next = game.centered_player_nr;
11623 game.set_centered_player = FALSE;
11627 if (game.set_centered_player &&
11628 ScreenMovPos == 0) /* screen currently aligned at tile position */
11632 if (game.centered_player_nr_next == -1)
11634 setScreenCenteredToAllPlayers(&sx, &sy);
11638 sx = stored_player[game.centered_player_nr_next].jx;
11639 sy = stored_player[game.centered_player_nr_next].jy;
11642 game.centered_player_nr = game.centered_player_nr_next;
11643 game.set_centered_player = FALSE;
11645 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11646 DrawGameDoorValues();
11649 for (i = 0; i < MAX_PLAYERS; i++)
11651 int actual_player_action = stored_player[i].effective_action;
11654 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11655 - rnd_equinox_tetrachloride 048
11656 - rnd_equinox_tetrachloride_ii 096
11657 - rnd_emanuel_schmieg 002
11658 - doctor_sloan_ww 001, 020
11660 if (stored_player[i].MovPos == 0)
11661 CheckGravityMovement(&stored_player[i]);
11664 /* overwrite programmed action with tape action */
11665 if (stored_player[i].programmed_action)
11666 actual_player_action = stored_player[i].programmed_action;
11668 PlayerActions(&stored_player[i], actual_player_action);
11670 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11673 ScrollScreen(NULL, SCROLL_GO_ON);
11675 /* for backwards compatibility, the following code emulates a fixed bug that
11676 occured when pushing elements (causing elements that just made their last
11677 pushing step to already (if possible) make their first falling step in the
11678 same game frame, which is bad); this code is also needed to use the famous
11679 "spring push bug" which is used in older levels and might be wanted to be
11680 used also in newer levels, but in this case the buggy pushing code is only
11681 affecting the "spring" element and no other elements */
11683 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11685 for (i = 0; i < MAX_PLAYERS; i++)
11687 struct PlayerInfo *player = &stored_player[i];
11688 int x = player->jx;
11689 int y = player->jy;
11691 if (player->active && player->is_pushing && player->is_moving &&
11693 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11694 Feld[x][y] == EL_SPRING))
11696 ContinueMoving(x, y);
11698 /* continue moving after pushing (this is actually a bug) */
11699 if (!IS_MOVING(x, y))
11700 Stop[x][y] = FALSE;
11705 SCAN_PLAYFIELD(x, y)
11707 ChangeCount[x][y] = 0;
11708 ChangeEvent[x][y] = -1;
11710 /* this must be handled before main playfield loop */
11711 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11714 if (MovDelay[x][y] <= 0)
11718 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11721 if (MovDelay[x][y] <= 0)
11724 TEST_DrawLevelField(x, y);
11726 TestIfElementTouchesCustomElement(x, y); /* for empty space */
11731 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11733 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11734 printf("GameActions(): This should never happen!\n");
11736 ChangePage[x][y] = -1;
11740 Stop[x][y] = FALSE;
11741 if (WasJustMoving[x][y] > 0)
11742 WasJustMoving[x][y]--;
11743 if (WasJustFalling[x][y] > 0)
11744 WasJustFalling[x][y]--;
11745 if (CheckCollision[x][y] > 0)
11746 CheckCollision[x][y]--;
11747 if (CheckImpact[x][y] > 0)
11748 CheckImpact[x][y]--;
11752 /* reset finished pushing action (not done in ContinueMoving() to allow
11753 continuous pushing animation for elements with zero push delay) */
11754 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11756 ResetGfxAnimation(x, y);
11757 TEST_DrawLevelField(x, y);
11761 if (IS_BLOCKED(x, y))
11765 Blocked2Moving(x, y, &oldx, &oldy);
11766 if (!IS_MOVING(oldx, oldy))
11768 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11769 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11770 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11771 printf("GameActions(): This should never happen!\n");
11777 SCAN_PLAYFIELD(x, y)
11779 element = Feld[x][y];
11780 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11781 last_gfx_frame = GfxFrame[x][y];
11783 ResetGfxFrame(x, y);
11785 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
11786 DrawLevelGraphicAnimation(x, y, graphic);
11788 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
11789 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
11790 ResetRandomAnimationValue(x, y);
11792 SetRandomAnimationValue(x, y);
11794 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
11796 if (IS_INACTIVE(element))
11798 if (IS_ANIMATED(graphic))
11799 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11804 /* this may take place after moving, so 'element' may have changed */
11805 if (IS_CHANGING(x, y) &&
11806 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
11808 int page = element_info[element].event_page_nr[CE_DELAY];
11810 HandleElementChange(x, y, page);
11812 element = Feld[x][y];
11813 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11816 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
11820 element = Feld[x][y];
11821 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11823 if (IS_ANIMATED(graphic) &&
11824 !IS_MOVING(x, y) &&
11826 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11828 if (IS_GEM(element) || element == EL_SP_INFOTRON)
11829 TEST_DrawTwinkleOnField(x, y);
11831 else if (element == EL_ACID)
11834 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11836 else if ((element == EL_EXIT_OPEN ||
11837 element == EL_EM_EXIT_OPEN ||
11838 element == EL_SP_EXIT_OPEN ||
11839 element == EL_STEEL_EXIT_OPEN ||
11840 element == EL_EM_STEEL_EXIT_OPEN ||
11841 element == EL_SP_TERMINAL ||
11842 element == EL_SP_TERMINAL_ACTIVE ||
11843 element == EL_EXTRA_TIME ||
11844 element == EL_SHIELD_NORMAL ||
11845 element == EL_SHIELD_DEADLY) &&
11846 IS_ANIMATED(graphic))
11847 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11848 else if (IS_MOVING(x, y))
11849 ContinueMoving(x, y);
11850 else if (IS_ACTIVE_BOMB(element))
11851 CheckDynamite(x, y);
11852 else if (element == EL_AMOEBA_GROWING)
11853 AmoebeWaechst(x, y);
11854 else if (element == EL_AMOEBA_SHRINKING)
11855 AmoebaDisappearing(x, y);
11857 #if !USE_NEW_AMOEBA_CODE
11858 else if (IS_AMOEBALIVE(element))
11859 AmoebeAbleger(x, y);
11862 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
11864 else if (element == EL_EXIT_CLOSED)
11866 else if (element == EL_EM_EXIT_CLOSED)
11868 else if (element == EL_STEEL_EXIT_CLOSED)
11869 CheckExitSteel(x, y);
11870 else if (element == EL_EM_STEEL_EXIT_CLOSED)
11871 CheckExitSteelEM(x, y);
11872 else if (element == EL_SP_EXIT_CLOSED)
11874 else if (element == EL_EXPANDABLE_WALL_GROWING ||
11875 element == EL_EXPANDABLE_STEELWALL_GROWING)
11876 MauerWaechst(x, y);
11877 else if (element == EL_EXPANDABLE_WALL ||
11878 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
11879 element == EL_EXPANDABLE_WALL_VERTICAL ||
11880 element == EL_EXPANDABLE_WALL_ANY ||
11881 element == EL_BD_EXPANDABLE_WALL)
11882 MauerAbleger(x, y);
11883 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
11884 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
11885 element == EL_EXPANDABLE_STEELWALL_ANY)
11886 MauerAblegerStahl(x, y);
11887 else if (element == EL_FLAMES)
11888 CheckForDragon(x, y);
11889 else if (element == EL_EXPLOSION)
11890 ; /* drawing of correct explosion animation is handled separately */
11891 else if (element == EL_ELEMENT_SNAPPING ||
11892 element == EL_DIAGONAL_SHRINKING ||
11893 element == EL_DIAGONAL_GROWING)
11895 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
11897 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11899 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
11900 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11902 if (IS_BELT_ACTIVE(element))
11903 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
11905 if (game.magic_wall_active)
11907 int jx = local_player->jx, jy = local_player->jy;
11909 /* play the element sound at the position nearest to the player */
11910 if ((element == EL_MAGIC_WALL_FULL ||
11911 element == EL_MAGIC_WALL_ACTIVE ||
11912 element == EL_MAGIC_WALL_EMPTYING ||
11913 element == EL_BD_MAGIC_WALL_FULL ||
11914 element == EL_BD_MAGIC_WALL_ACTIVE ||
11915 element == EL_BD_MAGIC_WALL_EMPTYING ||
11916 element == EL_DC_MAGIC_WALL_FULL ||
11917 element == EL_DC_MAGIC_WALL_ACTIVE ||
11918 element == EL_DC_MAGIC_WALL_EMPTYING) &&
11919 ABS(x-jx) + ABS(y-jy) < ABS(magic_wall_x-jx) + ABS(magic_wall_y-jy))
11927 #if USE_NEW_AMOEBA_CODE
11928 /* new experimental amoeba growth stuff */
11929 if (!(FrameCounter % 8))
11931 static unsigned int random = 1684108901;
11933 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
11935 x = RND(lev_fieldx);
11936 y = RND(lev_fieldy);
11937 element = Feld[x][y];
11939 if (!IS_PLAYER(x,y) &&
11940 (element == EL_EMPTY ||
11941 CAN_GROW_INTO(element) ||
11942 element == EL_QUICKSAND_EMPTY ||
11943 element == EL_QUICKSAND_FAST_EMPTY ||
11944 element == EL_ACID_SPLASH_LEFT ||
11945 element == EL_ACID_SPLASH_RIGHT))
11947 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
11948 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
11949 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
11950 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
11951 Feld[x][y] = EL_AMOEBA_DROP;
11954 random = random * 129 + 1;
11959 game.explosions_delayed = FALSE;
11961 SCAN_PLAYFIELD(x, y)
11963 element = Feld[x][y];
11965 if (ExplodeField[x][y])
11966 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
11967 else if (element == EL_EXPLOSION)
11968 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
11970 ExplodeField[x][y] = EX_TYPE_NONE;
11973 game.explosions_delayed = TRUE;
11975 if (game.magic_wall_active)
11977 if (!(game.magic_wall_time_left % 4))
11979 int element = Feld[magic_wall_x][magic_wall_y];
11981 if (element == EL_BD_MAGIC_WALL_FULL ||
11982 element == EL_BD_MAGIC_WALL_ACTIVE ||
11983 element == EL_BD_MAGIC_WALL_EMPTYING)
11984 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
11985 else if (element == EL_DC_MAGIC_WALL_FULL ||
11986 element == EL_DC_MAGIC_WALL_ACTIVE ||
11987 element == EL_DC_MAGIC_WALL_EMPTYING)
11988 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
11990 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
11993 if (game.magic_wall_time_left > 0)
11995 game.magic_wall_time_left--;
11997 if (!game.magic_wall_time_left)
11999 SCAN_PLAYFIELD(x, y)
12001 element = Feld[x][y];
12003 if (element == EL_MAGIC_WALL_ACTIVE ||
12004 element == EL_MAGIC_WALL_FULL)
12006 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12007 TEST_DrawLevelField(x, y);
12009 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12010 element == EL_BD_MAGIC_WALL_FULL)
12012 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12013 TEST_DrawLevelField(x, y);
12015 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12016 element == EL_DC_MAGIC_WALL_FULL)
12018 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12019 TEST_DrawLevelField(x, y);
12023 game.magic_wall_active = FALSE;
12028 if (game.light_time_left > 0)
12030 game.light_time_left--;
12032 if (game.light_time_left == 0)
12033 RedrawAllLightSwitchesAndInvisibleElements();
12036 if (game.timegate_time_left > 0)
12038 game.timegate_time_left--;
12040 if (game.timegate_time_left == 0)
12041 CloseAllOpenTimegates();
12044 if (game.lenses_time_left > 0)
12046 game.lenses_time_left--;
12048 if (game.lenses_time_left == 0)
12049 RedrawAllInvisibleElementsForLenses();
12052 if (game.magnify_time_left > 0)
12054 game.magnify_time_left--;
12056 if (game.magnify_time_left == 0)
12057 RedrawAllInvisibleElementsForMagnifier();
12060 for (i = 0; i < MAX_PLAYERS; i++)
12062 struct PlayerInfo *player = &stored_player[i];
12064 if (SHIELD_ON(player))
12066 if (player->shield_deadly_time_left)
12067 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12068 else if (player->shield_normal_time_left)
12069 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12073 #if USE_DELAYED_GFX_REDRAW
12074 SCAN_PLAYFIELD(x, y)
12076 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12078 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12079 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12081 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12082 DrawLevelField(x, y);
12084 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12085 DrawLevelFieldCrumbled(x, y);
12087 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12088 DrawLevelFieldCrumbledNeighbours(x, y);
12090 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12091 DrawTwinkleOnField(x, y);
12094 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12099 PlayAllPlayersSound();
12101 if (local_player->show_envelope != 0 && local_player->MovPos == 0)
12103 ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
12105 local_player->show_envelope = 0;
12108 /* use random number generator in every frame to make it less predictable */
12109 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12113 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12115 int min_x = x, min_y = y, max_x = x, max_y = y;
12118 for (i = 0; i < MAX_PLAYERS; i++)
12120 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12122 if (!stored_player[i].active || &stored_player[i] == player)
12125 min_x = MIN(min_x, jx);
12126 min_y = MIN(min_y, jy);
12127 max_x = MAX(max_x, jx);
12128 max_y = MAX(max_y, jy);
12131 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12134 static boolean AllPlayersInVisibleScreen()
12138 for (i = 0; i < MAX_PLAYERS; i++)
12140 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12142 if (!stored_player[i].active)
12145 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12152 void ScrollLevel(int dx, int dy)
12154 int scroll_offset = 2 * TILEX_VAR;
12157 BlitBitmap(drawto_field, drawto_field,
12158 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12159 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12160 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12161 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12162 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12163 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12167 x = (dx == 1 ? BX1 : BX2);
12168 for (y = BY1; y <= BY2; y++)
12169 DrawScreenField(x, y);
12174 y = (dy == 1 ? BY1 : BY2);
12175 for (x = BX1; x <= BX2; x++)
12176 DrawScreenField(x, y);
12179 redraw_mask |= REDRAW_FIELD;
12182 static boolean canFallDown(struct PlayerInfo *player)
12184 int jx = player->jx, jy = player->jy;
12186 return (IN_LEV_FIELD(jx, jy + 1) &&
12187 (IS_FREE(jx, jy + 1) ||
12188 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12189 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12190 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12193 static boolean canPassField(int x, int y, int move_dir)
12195 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12196 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12197 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12198 int nextx = x + dx;
12199 int nexty = y + dy;
12200 int element = Feld[x][y];
12202 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12203 !CAN_MOVE(element) &&
12204 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12205 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12206 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12209 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12211 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12212 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12213 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12217 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12218 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12219 (IS_DIGGABLE(Feld[newx][newy]) ||
12220 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12221 canPassField(newx, newy, move_dir)));
12224 static void CheckGravityMovement(struct PlayerInfo *player)
12226 if (player->gravity && !player->programmed_action)
12228 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12229 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12230 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12231 int jx = player->jx, jy = player->jy;
12232 boolean player_is_moving_to_valid_field =
12233 (!player_is_snapping &&
12234 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12235 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12236 boolean player_can_fall_down = canFallDown(player);
12238 if (player_can_fall_down &&
12239 !player_is_moving_to_valid_field)
12240 player->programmed_action = MV_DOWN;
12244 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12246 return CheckGravityMovement(player);
12248 if (player->gravity && !player->programmed_action)
12250 int jx = player->jx, jy = player->jy;
12251 boolean field_under_player_is_free =
12252 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12253 boolean player_is_standing_on_valid_field =
12254 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12255 (IS_WALKABLE(Feld[jx][jy]) &&
12256 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12258 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12259 player->programmed_action = MV_DOWN;
12264 MovePlayerOneStep()
12265 -----------------------------------------------------------------------------
12266 dx, dy: direction (non-diagonal) to try to move the player to
12267 real_dx, real_dy: direction as read from input device (can be diagonal)
12270 boolean MovePlayerOneStep(struct PlayerInfo *player,
12271 int dx, int dy, int real_dx, int real_dy)
12273 int jx = player->jx, jy = player->jy;
12274 int new_jx = jx + dx, new_jy = jy + dy;
12276 boolean player_can_move = !player->cannot_move;
12278 if (!player->active || (!dx && !dy))
12279 return MP_NO_ACTION;
12281 player->MovDir = (dx < 0 ? MV_LEFT :
12282 dx > 0 ? MV_RIGHT :
12284 dy > 0 ? MV_DOWN : MV_NONE);
12286 if (!IN_LEV_FIELD(new_jx, new_jy))
12287 return MP_NO_ACTION;
12289 if (!player_can_move)
12291 if (player->MovPos == 0)
12293 player->is_moving = FALSE;
12294 player->is_digging = FALSE;
12295 player->is_collecting = FALSE;
12296 player->is_snapping = FALSE;
12297 player->is_pushing = FALSE;
12301 if (!network.enabled && game.centered_player_nr == -1 &&
12302 !AllPlayersInSight(player, new_jx, new_jy))
12303 return MP_NO_ACTION;
12305 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12306 if (can_move != MP_MOVING)
12309 /* check if DigField() has caused relocation of the player */
12310 if (player->jx != jx || player->jy != jy)
12311 return MP_NO_ACTION; /* <-- !!! CHECK THIS [-> MP_ACTION ?] !!! */
12313 StorePlayer[jx][jy] = 0;
12314 player->last_jx = jx;
12315 player->last_jy = jy;
12316 player->jx = new_jx;
12317 player->jy = new_jy;
12318 StorePlayer[new_jx][new_jy] = player->element_nr;
12320 if (player->move_delay_value_next != -1)
12322 player->move_delay_value = player->move_delay_value_next;
12323 player->move_delay_value_next = -1;
12327 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12329 player->step_counter++;
12331 PlayerVisit[jx][jy] = FrameCounter;
12333 player->is_moving = TRUE;
12336 /* should better be called in MovePlayer(), but this breaks some tapes */
12337 ScrollPlayer(player, SCROLL_INIT);
12343 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12345 int jx = player->jx, jy = player->jy;
12346 int old_jx = jx, old_jy = jy;
12347 int moved = MP_NO_ACTION;
12349 if (!player->active)
12354 if (player->MovPos == 0)
12356 player->is_moving = FALSE;
12357 player->is_digging = FALSE;
12358 player->is_collecting = FALSE;
12359 player->is_snapping = FALSE;
12360 player->is_pushing = FALSE;
12366 if (player->move_delay > 0)
12369 player->move_delay = -1; /* set to "uninitialized" value */
12371 /* store if player is automatically moved to next field */
12372 player->is_auto_moving = (player->programmed_action != MV_NONE);
12374 /* remove the last programmed player action */
12375 player->programmed_action = 0;
12377 if (player->MovPos)
12379 /* should only happen if pre-1.2 tape recordings are played */
12380 /* this is only for backward compatibility */
12382 int original_move_delay_value = player->move_delay_value;
12385 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12389 /* scroll remaining steps with finest movement resolution */
12390 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12392 while (player->MovPos)
12394 ScrollPlayer(player, SCROLL_GO_ON);
12395 ScrollScreen(NULL, SCROLL_GO_ON);
12397 AdvanceFrameAndPlayerCounters(player->index_nr);
12400 BackToFront_WithFrameDelay(0);
12403 player->move_delay_value = original_move_delay_value;
12406 player->is_active = FALSE;
12408 if (player->last_move_dir & MV_HORIZONTAL)
12410 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12411 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12415 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12416 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12419 if (!moved && !player->is_active)
12421 player->is_moving = FALSE;
12422 player->is_digging = FALSE;
12423 player->is_collecting = FALSE;
12424 player->is_snapping = FALSE;
12425 player->is_pushing = FALSE;
12431 if (moved & MP_MOVING && !ScreenMovPos &&
12432 (player->index_nr == game.centered_player_nr ||
12433 game.centered_player_nr == -1))
12435 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12436 int offset = game.scroll_delay_value;
12438 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12440 /* actual player has left the screen -- scroll in that direction */
12441 if (jx != old_jx) /* player has moved horizontally */
12442 scroll_x += (jx - old_jx);
12443 else /* player has moved vertically */
12444 scroll_y += (jy - old_jy);
12448 if (jx != old_jx) /* player has moved horizontally */
12450 if ((player->MovDir == MV_LEFT && scroll_x > jx - MIDPOSX + offset) ||
12451 (player->MovDir == MV_RIGHT && scroll_x < jx - MIDPOSX - offset))
12452 scroll_x = jx-MIDPOSX + (scroll_x < jx-MIDPOSX ? -offset : +offset);
12454 /* don't scroll over playfield boundaries */
12455 if (scroll_x < SBX_Left || scroll_x > SBX_Right)
12456 scroll_x = (scroll_x < SBX_Left ? SBX_Left : SBX_Right);
12458 /* don't scroll more than one field at a time */
12459 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12461 /* don't scroll against the player's moving direction */
12462 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12463 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12464 scroll_x = old_scroll_x;
12466 else /* player has moved vertically */
12468 if ((player->MovDir == MV_UP && scroll_y > jy - MIDPOSY + offset) ||
12469 (player->MovDir == MV_DOWN && scroll_y < jy - MIDPOSY - offset))
12470 scroll_y = jy-MIDPOSY + (scroll_y < jy-MIDPOSY ? -offset : +offset);
12472 /* don't scroll over playfield boundaries */
12473 if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
12474 scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
12476 /* don't scroll more than one field at a time */
12477 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12479 /* don't scroll against the player's moving direction */
12480 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12481 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12482 scroll_y = old_scroll_y;
12486 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12488 if (!network.enabled && game.centered_player_nr == -1 &&
12489 !AllPlayersInVisibleScreen())
12491 scroll_x = old_scroll_x;
12492 scroll_y = old_scroll_y;
12496 ScrollScreen(player, SCROLL_INIT);
12497 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12502 player->StepFrame = 0;
12504 if (moved & MP_MOVING)
12506 if (old_jx != jx && old_jy == jy)
12507 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12508 else if (old_jx == jx && old_jy != jy)
12509 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12511 TEST_DrawLevelField(jx, jy); /* for "crumbled sand" */
12513 player->last_move_dir = player->MovDir;
12514 player->is_moving = TRUE;
12515 player->is_snapping = FALSE;
12516 player->is_switching = FALSE;
12517 player->is_dropping = FALSE;
12518 player->is_dropping_pressed = FALSE;
12519 player->drop_pressed_delay = 0;
12522 /* should better be called here than above, but this breaks some tapes */
12523 ScrollPlayer(player, SCROLL_INIT);
12528 CheckGravityMovementWhenNotMoving(player);
12530 player->is_moving = FALSE;
12532 /* at this point, the player is allowed to move, but cannot move right now
12533 (e.g. because of something blocking the way) -- ensure that the player
12534 is also allowed to move in the next frame (in old versions before 3.1.1,
12535 the player was forced to wait again for eight frames before next try) */
12537 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12538 player->move_delay = 0; /* allow direct movement in the next frame */
12541 if (player->move_delay == -1) /* not yet initialized by DigField() */
12542 player->move_delay = player->move_delay_value;
12544 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12546 TestIfPlayerTouchesBadThing(jx, jy);
12547 TestIfPlayerTouchesCustomElement(jx, jy);
12550 if (!player->active)
12551 RemovePlayer(player);
12556 void ScrollPlayer(struct PlayerInfo *player, int mode)
12558 int jx = player->jx, jy = player->jy;
12559 int last_jx = player->last_jx, last_jy = player->last_jy;
12560 int move_stepsize = TILEX / player->move_delay_value;
12562 if (!player->active)
12565 if (player->MovPos == 0 && mode == SCROLL_GO_ON) /* player not moving */
12568 if (mode == SCROLL_INIT)
12570 player->actual_frame_counter = FrameCounter;
12571 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12573 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12574 Feld[last_jx][last_jy] == EL_EMPTY)
12576 int last_field_block_delay = 0; /* start with no blocking at all */
12577 int block_delay_adjustment = player->block_delay_adjustment;
12579 /* if player blocks last field, add delay for exactly one move */
12580 if (player->block_last_field)
12582 last_field_block_delay += player->move_delay_value;
12584 /* when blocking enabled, prevent moving up despite gravity */
12585 if (player->gravity && player->MovDir == MV_UP)
12586 block_delay_adjustment = -1;
12589 /* add block delay adjustment (also possible when not blocking) */
12590 last_field_block_delay += block_delay_adjustment;
12592 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12593 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12596 if (player->MovPos != 0) /* player has not yet reached destination */
12599 else if (!FrameReached(&player->actual_frame_counter, 1))
12602 if (player->MovPos != 0)
12604 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12605 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12607 /* before DrawPlayer() to draw correct player graphic for this case */
12608 if (player->MovPos == 0)
12609 CheckGravityMovement(player);
12612 if (player->MovPos == 0) /* player reached destination field */
12614 if (player->move_delay_reset_counter > 0)
12616 player->move_delay_reset_counter--;
12618 if (player->move_delay_reset_counter == 0)
12620 /* continue with normal speed after quickly moving through gate */
12621 HALVE_PLAYER_SPEED(player);
12623 /* be able to make the next move without delay */
12624 player->move_delay = 0;
12628 player->last_jx = jx;
12629 player->last_jy = jy;
12631 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12632 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12633 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12634 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12635 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12636 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12637 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12638 Feld[jx][jy] == EL_SP_EXIT_OPENING) /* <-- special case */
12640 DrawPlayer(player); /* needed here only to cleanup last field */
12641 RemovePlayer(player);
12643 if (local_player->friends_still_needed == 0 ||
12644 IS_SP_ELEMENT(Feld[jx][jy]))
12645 PlayerWins(player);
12648 /* this breaks one level: "machine", level 000 */
12650 int move_direction = player->MovDir;
12651 int enter_side = MV_DIR_OPPOSITE(move_direction);
12652 int leave_side = move_direction;
12653 int old_jx = last_jx;
12654 int old_jy = last_jy;
12655 int old_element = Feld[old_jx][old_jy];
12656 int new_element = Feld[jx][jy];
12658 if (IS_CUSTOM_ELEMENT(old_element))
12659 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12661 player->index_bit, leave_side);
12663 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12664 CE_PLAYER_LEAVES_X,
12665 player->index_bit, leave_side);
12667 if (IS_CUSTOM_ELEMENT(new_element))
12668 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12669 player->index_bit, enter_side);
12671 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12672 CE_PLAYER_ENTERS_X,
12673 player->index_bit, enter_side);
12675 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12676 CE_MOVE_OF_X, move_direction);
12679 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12681 TestIfPlayerTouchesBadThing(jx, jy);
12682 TestIfPlayerTouchesCustomElement(jx, jy);
12684 /* needed because pushed element has not yet reached its destination,
12685 so it would trigger a change event at its previous field location */
12686 if (!player->is_pushing)
12687 TestIfElementTouchesCustomElement(jx, jy); /* for empty space */
12689 if (!player->active)
12690 RemovePlayer(player);
12693 if (!local_player->LevelSolved && level.use_step_counter)
12703 if (TimeLeft <= 10 && setup.time_limit)
12704 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12706 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12708 DisplayGameControlValues();
12710 if (!TimeLeft && setup.time_limit)
12711 for (i = 0; i < MAX_PLAYERS; i++)
12712 KillPlayer(&stored_player[i]);
12714 else if (game.no_time_limit && !AllPlayersGone) /* level w/o time limit */
12716 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12718 DisplayGameControlValues();
12722 if (tape.single_step && tape.recording && !tape.pausing &&
12723 !player->programmed_action)
12724 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12726 if (!player->programmed_action)
12727 CheckSaveEngineSnapshot(player);
12731 void ScrollScreen(struct PlayerInfo *player, int mode)
12733 static unsigned int screen_frame_counter = 0;
12735 if (mode == SCROLL_INIT)
12737 /* set scrolling step size according to actual player's moving speed */
12738 ScrollStepSize = TILEX / player->move_delay_value;
12740 screen_frame_counter = FrameCounter;
12741 ScreenMovDir = player->MovDir;
12742 ScreenMovPos = player->MovPos;
12743 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12746 else if (!FrameReached(&screen_frame_counter, 1))
12751 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
12752 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12753 redraw_mask |= REDRAW_FIELD;
12756 ScreenMovDir = MV_NONE;
12759 void TestIfPlayerTouchesCustomElement(int x, int y)
12761 static int xy[4][2] =
12768 static int trigger_sides[4][2] =
12770 /* center side border side */
12771 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, /* check top */
12772 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, /* check left */
12773 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, /* check right */
12774 { CH_SIDE_BOTTOM, CH_SIDE_TOP } /* check bottom */
12776 static int touch_dir[4] =
12778 MV_LEFT | MV_RIGHT,
12783 int center_element = Feld[x][y]; /* should always be non-moving! */
12786 for (i = 0; i < NUM_DIRECTIONS; i++)
12788 int xx = x + xy[i][0];
12789 int yy = y + xy[i][1];
12790 int center_side = trigger_sides[i][0];
12791 int border_side = trigger_sides[i][1];
12792 int border_element;
12794 if (!IN_LEV_FIELD(xx, yy))
12797 if (IS_PLAYER(x, y)) /* player found at center element */
12799 struct PlayerInfo *player = PLAYERINFO(x, y);
12801 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12802 border_element = Feld[xx][yy]; /* may be moving! */
12803 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12804 border_element = Feld[xx][yy];
12805 else if (MovDir[xx][yy] & touch_dir[i]) /* elements are touching */
12806 border_element = MovingOrBlocked2Element(xx, yy);
12808 continue; /* center and border element do not touch */
12810 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
12811 player->index_bit, border_side);
12812 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
12813 CE_PLAYER_TOUCHES_X,
12814 player->index_bit, border_side);
12817 /* use player element that is initially defined in the level playfield,
12818 not the player element that corresponds to the runtime player number
12819 (example: a level that contains EL_PLAYER_3 as the only player would
12820 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12821 int player_element = PLAYERINFO(x, y)->initial_element;
12823 CheckElementChangeBySide(xx, yy, border_element, player_element,
12824 CE_TOUCHING_X, border_side);
12827 else if (IS_PLAYER(xx, yy)) /* player found at border element */
12829 struct PlayerInfo *player = PLAYERINFO(xx, yy);
12831 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12833 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
12834 continue; /* center and border element do not touch */
12837 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
12838 player->index_bit, center_side);
12839 CheckTriggeredElementChangeByPlayer(x, y, center_element,
12840 CE_PLAYER_TOUCHES_X,
12841 player->index_bit, center_side);
12844 /* use player element that is initially defined in the level playfield,
12845 not the player element that corresponds to the runtime player number
12846 (example: a level that contains EL_PLAYER_3 as the only player would
12847 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12848 int player_element = PLAYERINFO(xx, yy)->initial_element;
12850 CheckElementChangeBySide(x, y, center_element, player_element,
12851 CE_TOUCHING_X, center_side);
12859 void TestIfElementTouchesCustomElement(int x, int y)
12861 static int xy[4][2] =
12868 static int trigger_sides[4][2] =
12870 /* center side border side */
12871 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, /* check top */
12872 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, /* check left */
12873 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, /* check right */
12874 { CH_SIDE_BOTTOM, CH_SIDE_TOP } /* check bottom */
12876 static int touch_dir[4] =
12878 MV_LEFT | MV_RIGHT,
12883 boolean change_center_element = FALSE;
12884 int center_element = Feld[x][y]; /* should always be non-moving! */
12885 int border_element_old[NUM_DIRECTIONS];
12888 for (i = 0; i < NUM_DIRECTIONS; i++)
12890 int xx = x + xy[i][0];
12891 int yy = y + xy[i][1];
12892 int border_element;
12894 border_element_old[i] = -1;
12896 if (!IN_LEV_FIELD(xx, yy))
12899 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12900 border_element = Feld[xx][yy]; /* may be moving! */
12901 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12902 border_element = Feld[xx][yy];
12903 else if (MovDir[xx][yy] & touch_dir[i]) /* elements are touching */
12904 border_element = MovingOrBlocked2Element(xx, yy);
12906 continue; /* center and border element do not touch */
12908 border_element_old[i] = border_element;
12911 for (i = 0; i < NUM_DIRECTIONS; i++)
12913 int xx = x + xy[i][0];
12914 int yy = y + xy[i][1];
12915 int center_side = trigger_sides[i][0];
12916 int border_element = border_element_old[i];
12918 if (border_element == -1)
12921 /* check for change of border element */
12922 CheckElementChangeBySide(xx, yy, border_element, center_element,
12923 CE_TOUCHING_X, center_side);
12925 /* (center element cannot be player, so we dont have to check this here) */
12928 for (i = 0; i < NUM_DIRECTIONS; i++)
12930 int xx = x + xy[i][0];
12931 int yy = y + xy[i][1];
12932 int border_side = trigger_sides[i][1];
12933 int border_element = border_element_old[i];
12935 if (border_element == -1)
12938 /* check for change of center element (but change it only once) */
12939 if (!change_center_element)
12940 change_center_element =
12941 CheckElementChangeBySide(x, y, center_element, border_element,
12942 CE_TOUCHING_X, border_side);
12944 if (IS_PLAYER(xx, yy))
12946 /* use player element that is initially defined in the level playfield,
12947 not the player element that corresponds to the runtime player number
12948 (example: a level that contains EL_PLAYER_3 as the only player would
12949 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12950 int player_element = PLAYERINFO(xx, yy)->initial_element;
12952 CheckElementChangeBySide(x, y, center_element, player_element,
12953 CE_TOUCHING_X, border_side);
12958 void TestIfElementHitsCustomElement(int x, int y, int direction)
12960 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
12961 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
12962 int hitx = x + dx, hity = y + dy;
12963 int hitting_element = Feld[x][y];
12964 int touched_element;
12966 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
12969 touched_element = (IN_LEV_FIELD(hitx, hity) ?
12970 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
12972 if (IN_LEV_FIELD(hitx, hity))
12974 int opposite_direction = MV_DIR_OPPOSITE(direction);
12975 int hitting_side = direction;
12976 int touched_side = opposite_direction;
12977 boolean object_hit = (!IS_MOVING(hitx, hity) ||
12978 MovDir[hitx][hity] != direction ||
12979 ABS(MovPos[hitx][hity]) <= TILEY / 2);
12985 CheckElementChangeBySide(x, y, hitting_element, touched_element,
12986 CE_HITTING_X, touched_side);
12988 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
12989 CE_HIT_BY_X, hitting_side);
12991 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
12992 CE_HIT_BY_SOMETHING, opposite_direction);
12994 if (IS_PLAYER(hitx, hity))
12996 /* use player element that is initially defined in the level playfield,
12997 not the player element that corresponds to the runtime player number
12998 (example: a level that contains EL_PLAYER_3 as the only player would
12999 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13000 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13002 CheckElementChangeBySide(x, y, hitting_element, player_element,
13003 CE_HITTING_X, touched_side);
13008 /* "hitting something" is also true when hitting the playfield border */
13009 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13010 CE_HITTING_SOMETHING, direction);
13013 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13015 int i, kill_x = -1, kill_y = -1;
13017 int bad_element = -1;
13018 static int test_xy[4][2] =
13025 static int test_dir[4] =
13033 for (i = 0; i < NUM_DIRECTIONS; i++)
13035 int test_x, test_y, test_move_dir, test_element;
13037 test_x = good_x + test_xy[i][0];
13038 test_y = good_y + test_xy[i][1];
13040 if (!IN_LEV_FIELD(test_x, test_y))
13044 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13046 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13048 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13049 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13051 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13052 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13056 bad_element = test_element;
13062 if (kill_x != -1 || kill_y != -1)
13064 if (IS_PLAYER(good_x, good_y))
13066 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13068 if (player->shield_deadly_time_left > 0 &&
13069 !IS_INDESTRUCTIBLE(bad_element))
13070 Bang(kill_x, kill_y);
13071 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13072 KillPlayer(player);
13075 Bang(good_x, good_y);
13079 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13081 int i, kill_x = -1, kill_y = -1;
13082 int bad_element = Feld[bad_x][bad_y];
13083 static int test_xy[4][2] =
13090 static int touch_dir[4] =
13092 MV_LEFT | MV_RIGHT,
13097 static int test_dir[4] =
13105 if (bad_element == EL_EXPLOSION) /* skip just exploding bad things */
13108 for (i = 0; i < NUM_DIRECTIONS; i++)
13110 int test_x, test_y, test_move_dir, test_element;
13112 test_x = bad_x + test_xy[i][0];
13113 test_y = bad_y + test_xy[i][1];
13115 if (!IN_LEV_FIELD(test_x, test_y))
13119 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13121 test_element = Feld[test_x][test_y];
13123 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13124 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13126 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13127 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13129 /* good thing is player or penguin that does not move away */
13130 if (IS_PLAYER(test_x, test_y))
13132 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13134 if (bad_element == EL_ROBOT && player->is_moving)
13135 continue; /* robot does not kill player if he is moving */
13137 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13139 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13140 continue; /* center and border element do not touch */
13148 else if (test_element == EL_PENGUIN)
13158 if (kill_x != -1 || kill_y != -1)
13160 if (IS_PLAYER(kill_x, kill_y))
13162 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13164 if (player->shield_deadly_time_left > 0 &&
13165 !IS_INDESTRUCTIBLE(bad_element))
13166 Bang(bad_x, bad_y);
13167 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13168 KillPlayer(player);
13171 Bang(kill_x, kill_y);
13175 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13177 int bad_element = Feld[bad_x][bad_y];
13178 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13179 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13180 int test_x = bad_x + dx, test_y = bad_y + dy;
13181 int test_move_dir, test_element;
13182 int kill_x = -1, kill_y = -1;
13184 if (!IN_LEV_FIELD(test_x, test_y))
13188 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13190 test_element = Feld[test_x][test_y];
13192 if (test_move_dir != bad_move_dir)
13194 /* good thing can be player or penguin that does not move away */
13195 if (IS_PLAYER(test_x, test_y))
13197 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13199 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13200 player as being hit when he is moving towards the bad thing, because
13201 the "get hit by" condition would be lost after the player stops) */
13202 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13203 return; /* player moves away from bad thing */
13208 else if (test_element == EL_PENGUIN)
13215 if (kill_x != -1 || kill_y != -1)
13217 if (IS_PLAYER(kill_x, kill_y))
13219 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13221 if (player->shield_deadly_time_left > 0 &&
13222 !IS_INDESTRUCTIBLE(bad_element))
13223 Bang(bad_x, bad_y);
13224 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13225 KillPlayer(player);
13228 Bang(kill_x, kill_y);
13232 void TestIfPlayerTouchesBadThing(int x, int y)
13234 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13237 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13239 TestIfGoodThingHitsBadThing(x, y, move_dir);
13242 void TestIfBadThingTouchesPlayer(int x, int y)
13244 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13247 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13249 TestIfBadThingHitsGoodThing(x, y, move_dir);
13252 void TestIfFriendTouchesBadThing(int x, int y)
13254 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13257 void TestIfBadThingTouchesFriend(int x, int y)
13259 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13262 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13264 int i, kill_x = bad_x, kill_y = bad_y;
13265 static int xy[4][2] =
13273 for (i = 0; i < NUM_DIRECTIONS; i++)
13277 x = bad_x + xy[i][0];
13278 y = bad_y + xy[i][1];
13279 if (!IN_LEV_FIELD(x, y))
13282 element = Feld[x][y];
13283 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13284 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13292 if (kill_x != bad_x || kill_y != bad_y)
13293 Bang(bad_x, bad_y);
13296 void KillPlayer(struct PlayerInfo *player)
13298 int jx = player->jx, jy = player->jy;
13300 if (!player->active)
13304 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13305 player->killed, player->active, player->reanimated);
13308 /* the following code was introduced to prevent an infinite loop when calling
13310 -> CheckTriggeredElementChangeExt()
13311 -> ExecuteCustomElementAction()
13313 -> (infinitely repeating the above sequence of function calls)
13314 which occurs when killing the player while having a CE with the setting
13315 "kill player X when explosion of <player X>"; the solution using a new
13316 field "player->killed" was chosen for backwards compatibility, although
13317 clever use of the fields "player->active" etc. would probably also work */
13319 if (player->killed)
13323 player->killed = TRUE;
13325 /* remove accessible field at the player's position */
13326 Feld[jx][jy] = EL_EMPTY;
13328 /* deactivate shield (else Bang()/Explode() would not work right) */
13329 player->shield_normal_time_left = 0;
13330 player->shield_deadly_time_left = 0;
13333 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13334 player->killed, player->active, player->reanimated);
13340 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13341 player->killed, player->active, player->reanimated);
13344 if (player->reanimated) /* killed player may have been reanimated */
13345 player->killed = player->reanimated = FALSE;
13347 BuryPlayer(player);
13350 static void KillPlayerUnlessEnemyProtected(int x, int y)
13352 if (!PLAYER_ENEMY_PROTECTED(x, y))
13353 KillPlayer(PLAYERINFO(x, y));
13356 static void KillPlayerUnlessExplosionProtected(int x, int y)
13358 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13359 KillPlayer(PLAYERINFO(x, y));
13362 void BuryPlayer(struct PlayerInfo *player)
13364 int jx = player->jx, jy = player->jy;
13366 if (!player->active)
13369 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13370 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13372 player->GameOver = TRUE;
13373 RemovePlayer(player);
13376 void RemovePlayer(struct PlayerInfo *player)
13378 int jx = player->jx, jy = player->jy;
13379 int i, found = FALSE;
13381 player->present = FALSE;
13382 player->active = FALSE;
13384 if (!ExplodeField[jx][jy])
13385 StorePlayer[jx][jy] = 0;
13387 if (player->is_moving)
13388 TEST_DrawLevelField(player->last_jx, player->last_jy);
13390 for (i = 0; i < MAX_PLAYERS; i++)
13391 if (stored_player[i].active)
13395 AllPlayersGone = TRUE;
13401 static void setFieldForSnapping(int x, int y, int element, int direction)
13403 struct ElementInfo *ei = &element_info[element];
13404 int direction_bit = MV_DIR_TO_BIT(direction);
13405 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13406 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13407 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13409 Feld[x][y] = EL_ELEMENT_SNAPPING;
13410 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13412 ResetGfxAnimation(x, y);
13414 GfxElement[x][y] = element;
13415 GfxAction[x][y] = action;
13416 GfxDir[x][y] = direction;
13417 GfxFrame[x][y] = -1;
13421 =============================================================================
13422 checkDiagonalPushing()
13423 -----------------------------------------------------------------------------
13424 check if diagonal input device direction results in pushing of object
13425 (by checking if the alternative direction is walkable, diggable, ...)
13426 =============================================================================
13429 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13430 int x, int y, int real_dx, int real_dy)
13432 int jx, jy, dx, dy, xx, yy;
13434 if (real_dx == 0 || real_dy == 0) /* no diagonal direction => push */
13437 /* diagonal direction: check alternative direction */
13442 xx = jx + (dx == 0 ? real_dx : 0);
13443 yy = jy + (dy == 0 ? real_dy : 0);
13445 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13449 =============================================================================
13451 -----------------------------------------------------------------------------
13452 x, y: field next to player (non-diagonal) to try to dig to
13453 real_dx, real_dy: direction as read from input device (can be diagonal)
13454 =============================================================================
13457 static int DigField(struct PlayerInfo *player,
13458 int oldx, int oldy, int x, int y,
13459 int real_dx, int real_dy, int mode)
13461 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13462 boolean player_was_pushing = player->is_pushing;
13463 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13464 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13465 int jx = oldx, jy = oldy;
13466 int dx = x - jx, dy = y - jy;
13467 int nextx = x + dx, nexty = y + dy;
13468 int move_direction = (dx == -1 ? MV_LEFT :
13469 dx == +1 ? MV_RIGHT :
13471 dy == +1 ? MV_DOWN : MV_NONE);
13472 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13473 int dig_side = MV_DIR_OPPOSITE(move_direction);
13474 int old_element = Feld[jx][jy];
13475 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13478 if (is_player) /* function can also be called by EL_PENGUIN */
13480 if (player->MovPos == 0)
13482 player->is_digging = FALSE;
13483 player->is_collecting = FALSE;
13486 if (player->MovPos == 0) /* last pushing move finished */
13487 player->is_pushing = FALSE;
13489 if (mode == DF_NO_PUSH) /* player just stopped pushing */
13491 player->is_switching = FALSE;
13492 player->push_delay = -1;
13494 return MP_NO_ACTION;
13498 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13499 old_element = Back[jx][jy];
13501 /* in case of element dropped at player position, check background */
13502 else if (Back[jx][jy] != EL_EMPTY &&
13503 game.engine_version >= VERSION_IDENT(2,2,0,0))
13504 old_element = Back[jx][jy];
13506 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13507 return MP_NO_ACTION; /* field has no opening in this direction */
13509 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13510 return MP_NO_ACTION; /* field has no opening in this direction */
13512 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13516 Feld[jx][jy] = player->artwork_element;
13517 InitMovingField(jx, jy, MV_DOWN);
13518 Store[jx][jy] = EL_ACID;
13519 ContinueMoving(jx, jy);
13520 BuryPlayer(player);
13522 return MP_DONT_RUN_INTO;
13525 if (player_can_move && DONT_RUN_INTO(element))
13527 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13529 return MP_DONT_RUN_INTO;
13532 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13533 return MP_NO_ACTION;
13535 collect_count = element_info[element].collect_count_initial;
13537 if (!is_player && !IS_COLLECTIBLE(element)) /* penguin cannot collect it */
13538 return MP_NO_ACTION;
13540 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13541 player_can_move = player_can_move_or_snap;
13543 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13544 game.engine_version >= VERSION_IDENT(2,2,0,0))
13546 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13547 player->index_bit, dig_side);
13548 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13549 player->index_bit, dig_side);
13551 if (element == EL_DC_LANDMINE)
13554 if (Feld[x][y] != element) /* field changed by snapping */
13557 return MP_NO_ACTION;
13560 if (player->gravity && is_player && !player->is_auto_moving &&
13561 canFallDown(player) && move_direction != MV_DOWN &&
13562 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13563 return MP_NO_ACTION; /* player cannot walk here due to gravity */
13565 if (player_can_move &&
13566 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13568 int sound_element = SND_ELEMENT(element);
13569 int sound_action = ACTION_WALKING;
13571 if (IS_RND_GATE(element))
13573 if (!player->key[RND_GATE_NR(element)])
13574 return MP_NO_ACTION;
13576 else if (IS_RND_GATE_GRAY(element))
13578 if (!player->key[RND_GATE_GRAY_NR(element)])
13579 return MP_NO_ACTION;
13581 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13583 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13584 return MP_NO_ACTION;
13586 else if (element == EL_EXIT_OPEN ||
13587 element == EL_EM_EXIT_OPEN ||
13588 element == EL_EM_EXIT_OPENING ||
13589 element == EL_STEEL_EXIT_OPEN ||
13590 element == EL_EM_STEEL_EXIT_OPEN ||
13591 element == EL_EM_STEEL_EXIT_OPENING ||
13592 element == EL_SP_EXIT_OPEN ||
13593 element == EL_SP_EXIT_OPENING)
13595 sound_action = ACTION_PASSING; /* player is passing exit */
13597 else if (element == EL_EMPTY)
13599 sound_action = ACTION_MOVING; /* nothing to walk on */
13602 /* play sound from background or player, whatever is available */
13603 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13604 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13606 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13608 else if (player_can_move &&
13609 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13611 if (!ACCESS_FROM(element, opposite_direction))
13612 return MP_NO_ACTION; /* field not accessible from this direction */
13614 if (CAN_MOVE(element)) /* only fixed elements can be passed! */
13615 return MP_NO_ACTION;
13617 if (IS_EM_GATE(element))
13619 if (!player->key[EM_GATE_NR(element)])
13620 return MP_NO_ACTION;
13622 else if (IS_EM_GATE_GRAY(element))
13624 if (!player->key[EM_GATE_GRAY_NR(element)])
13625 return MP_NO_ACTION;
13627 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13629 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13630 return MP_NO_ACTION;
13632 else if (IS_EMC_GATE(element))
13634 if (!player->key[EMC_GATE_NR(element)])
13635 return MP_NO_ACTION;
13637 else if (IS_EMC_GATE_GRAY(element))
13639 if (!player->key[EMC_GATE_GRAY_NR(element)])
13640 return MP_NO_ACTION;
13642 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13644 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13645 return MP_NO_ACTION;
13647 else if (element == EL_DC_GATE_WHITE ||
13648 element == EL_DC_GATE_WHITE_GRAY ||
13649 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13651 if (player->num_white_keys == 0)
13652 return MP_NO_ACTION;
13654 player->num_white_keys--;
13656 else if (IS_SP_PORT(element))
13658 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13659 element == EL_SP_GRAVITY_PORT_RIGHT ||
13660 element == EL_SP_GRAVITY_PORT_UP ||
13661 element == EL_SP_GRAVITY_PORT_DOWN)
13662 player->gravity = !player->gravity;
13663 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13664 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13665 element == EL_SP_GRAVITY_ON_PORT_UP ||
13666 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13667 player->gravity = TRUE;
13668 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13669 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13670 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13671 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13672 player->gravity = FALSE;
13675 /* automatically move to the next field with double speed */
13676 player->programmed_action = move_direction;
13678 if (player->move_delay_reset_counter == 0)
13680 player->move_delay_reset_counter = 2; /* two double speed steps */
13682 DOUBLE_PLAYER_SPEED(player);
13685 PlayLevelSoundAction(x, y, ACTION_PASSING);
13687 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13691 if (mode != DF_SNAP)
13693 GfxElement[x][y] = GFX_ELEMENT(element);
13694 player->is_digging = TRUE;
13697 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13699 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13700 player->index_bit, dig_side);
13702 if (mode == DF_SNAP)
13704 if (level.block_snap_field)
13705 setFieldForSnapping(x, y, element, move_direction);
13707 TestIfElementTouchesCustomElement(x, y); /* for empty space */
13709 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13710 player->index_bit, dig_side);
13713 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13717 if (is_player && mode != DF_SNAP)
13719 GfxElement[x][y] = element;
13720 player->is_collecting = TRUE;
13723 if (element == EL_SPEED_PILL)
13725 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13727 else if (element == EL_EXTRA_TIME && level.time > 0)
13729 TimeLeft += level.extra_time;
13731 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13733 DisplayGameControlValues();
13735 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
13737 player->shield_normal_time_left += level.shield_normal_time;
13738 if (element == EL_SHIELD_DEADLY)
13739 player->shield_deadly_time_left += level.shield_deadly_time;
13741 else if (element == EL_DYNAMITE ||
13742 element == EL_EM_DYNAMITE ||
13743 element == EL_SP_DISK_RED)
13745 if (player->inventory_size < MAX_INVENTORY_SIZE)
13746 player->inventory_element[player->inventory_size++] = element;
13748 DrawGameDoorValues();
13750 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
13752 player->dynabomb_count++;
13753 player->dynabombs_left++;
13755 else if (element == EL_DYNABOMB_INCREASE_SIZE)
13757 player->dynabomb_size++;
13759 else if (element == EL_DYNABOMB_INCREASE_POWER)
13761 player->dynabomb_xl = TRUE;
13763 else if (IS_KEY(element))
13765 player->key[KEY_NR(element)] = TRUE;
13767 DrawGameDoorValues();
13769 else if (element == EL_DC_KEY_WHITE)
13771 player->num_white_keys++;
13773 /* display white keys? */
13774 /* DrawGameDoorValues(); */
13776 else if (IS_ENVELOPE(element))
13778 player->show_envelope = element;
13780 else if (element == EL_EMC_LENSES)
13782 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
13784 RedrawAllInvisibleElementsForLenses();
13786 else if (element == EL_EMC_MAGNIFIER)
13788 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
13790 RedrawAllInvisibleElementsForMagnifier();
13792 else if (IS_DROPPABLE(element) ||
13793 IS_THROWABLE(element)) /* can be collected and dropped */
13797 if (collect_count == 0)
13798 player->inventory_infinite_element = element;
13800 for (i = 0; i < collect_count; i++)
13801 if (player->inventory_size < MAX_INVENTORY_SIZE)
13802 player->inventory_element[player->inventory_size++] = element;
13804 DrawGameDoorValues();
13806 else if (collect_count > 0)
13808 local_player->gems_still_needed -= collect_count;
13809 if (local_player->gems_still_needed < 0)
13810 local_player->gems_still_needed = 0;
13812 game.snapshot.collected_item = TRUE;
13814 game_panel_controls[GAME_PANEL_GEMS].value = local_player->gems_still_needed;
13816 DisplayGameControlValues();
13819 RaiseScoreElement(element);
13820 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
13823 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
13824 player->index_bit, dig_side);
13826 if (mode == DF_SNAP)
13828 if (level.block_snap_field)
13829 setFieldForSnapping(x, y, element, move_direction);
13831 TestIfElementTouchesCustomElement(x, y); /* for empty space */
13833 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13834 player->index_bit, dig_side);
13837 else if (player_can_move_or_snap && IS_PUSHABLE(element))
13839 if (mode == DF_SNAP && element != EL_BD_ROCK)
13840 return MP_NO_ACTION;
13842 if (CAN_FALL(element) && dy)
13843 return MP_NO_ACTION;
13845 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
13846 !(element == EL_SPRING && level.use_spring_bug))
13847 return MP_NO_ACTION;
13849 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
13850 ((move_direction & MV_VERTICAL &&
13851 ((element_info[element].move_pattern & MV_LEFT &&
13852 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
13853 (element_info[element].move_pattern & MV_RIGHT &&
13854 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
13855 (move_direction & MV_HORIZONTAL &&
13856 ((element_info[element].move_pattern & MV_UP &&
13857 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
13858 (element_info[element].move_pattern & MV_DOWN &&
13859 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
13860 return MP_NO_ACTION;
13862 /* do not push elements already moving away faster than player */
13863 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
13864 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
13865 return MP_NO_ACTION;
13867 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
13869 if (player->push_delay_value == -1 || !player_was_pushing)
13870 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13872 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13874 if (player->push_delay_value == -1)
13875 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13877 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
13879 if (!player->is_pushing)
13880 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13883 player->is_pushing = TRUE;
13884 player->is_active = TRUE;
13886 if (!(IN_LEV_FIELD(nextx, nexty) &&
13887 (IS_FREE(nextx, nexty) ||
13888 (IS_SB_ELEMENT(element) &&
13889 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
13890 (IS_CUSTOM_ELEMENT(element) &&
13891 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
13892 return MP_NO_ACTION;
13894 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
13895 return MP_NO_ACTION;
13897 if (player->push_delay == -1) /* new pushing; restart delay */
13898 player->push_delay = 0;
13900 if (player->push_delay < player->push_delay_value &&
13901 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
13902 element != EL_SPRING && element != EL_BALLOON)
13904 /* make sure that there is no move delay before next try to push */
13905 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13906 player->move_delay = 0;
13908 return MP_NO_ACTION;
13911 if (IS_CUSTOM_ELEMENT(element) &&
13912 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
13914 if (!DigFieldByCE(nextx, nexty, element))
13915 return MP_NO_ACTION;
13918 if (IS_SB_ELEMENT(element))
13920 if (element == EL_SOKOBAN_FIELD_FULL)
13922 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
13923 local_player->sokobanfields_still_needed++;
13926 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
13928 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
13929 local_player->sokobanfields_still_needed--;
13932 Feld[x][y] = EL_SOKOBAN_OBJECT;
13934 if (Back[x][y] == Back[nextx][nexty])
13935 PlayLevelSoundAction(x, y, ACTION_PUSHING);
13936 else if (Back[x][y] != 0)
13937 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
13940 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
13943 if (local_player->sokobanfields_still_needed == 0 &&
13944 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
13946 PlayerWins(player);
13948 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
13952 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
13954 InitMovingField(x, y, move_direction);
13955 GfxAction[x][y] = ACTION_PUSHING;
13957 if (mode == DF_SNAP)
13958 ContinueMoving(x, y);
13960 MovPos[x][y] = (dx != 0 ? dx : dy);
13962 Pushed[x][y] = TRUE;
13963 Pushed[nextx][nexty] = TRUE;
13965 if (game.engine_version < VERSION_IDENT(2,2,0,7))
13966 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13968 player->push_delay_value = -1; /* get new value later */
13970 /* check for element change _after_ element has been pushed */
13971 if (game.use_change_when_pushing_bug)
13973 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
13974 player->index_bit, dig_side);
13975 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
13976 player->index_bit, dig_side);
13979 else if (IS_SWITCHABLE(element))
13981 if (PLAYER_SWITCHING(player, x, y))
13983 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
13984 player->index_bit, dig_side);
13989 player->is_switching = TRUE;
13990 player->switch_x = x;
13991 player->switch_y = y;
13993 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
13995 if (element == EL_ROBOT_WHEEL)
13997 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14001 game.robot_wheel_active = TRUE;
14003 TEST_DrawLevelField(x, y);
14005 else if (element == EL_SP_TERMINAL)
14009 SCAN_PLAYFIELD(xx, yy)
14011 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14015 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14017 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14019 ResetGfxAnimation(xx, yy);
14020 TEST_DrawLevelField(xx, yy);
14024 else if (IS_BELT_SWITCH(element))
14026 ToggleBeltSwitch(x, y);
14028 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14029 element == EL_SWITCHGATE_SWITCH_DOWN ||
14030 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14031 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14033 ToggleSwitchgateSwitch(x, y);
14035 else if (element == EL_LIGHT_SWITCH ||
14036 element == EL_LIGHT_SWITCH_ACTIVE)
14038 ToggleLightSwitch(x, y);
14040 else if (element == EL_TIMEGATE_SWITCH ||
14041 element == EL_DC_TIMEGATE_SWITCH)
14043 ActivateTimegateSwitch(x, y);
14045 else if (element == EL_BALLOON_SWITCH_LEFT ||
14046 element == EL_BALLOON_SWITCH_RIGHT ||
14047 element == EL_BALLOON_SWITCH_UP ||
14048 element == EL_BALLOON_SWITCH_DOWN ||
14049 element == EL_BALLOON_SWITCH_NONE ||
14050 element == EL_BALLOON_SWITCH_ANY)
14052 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14053 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14054 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14055 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14056 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14059 else if (element == EL_LAMP)
14061 Feld[x][y] = EL_LAMP_ACTIVE;
14062 local_player->lights_still_needed--;
14064 ResetGfxAnimation(x, y);
14065 TEST_DrawLevelField(x, y);
14067 else if (element == EL_TIME_ORB_FULL)
14069 Feld[x][y] = EL_TIME_ORB_EMPTY;
14071 if (level.time > 0 || level.use_time_orb_bug)
14073 TimeLeft += level.time_orb_time;
14074 game.no_time_limit = FALSE;
14076 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14078 DisplayGameControlValues();
14081 ResetGfxAnimation(x, y);
14082 TEST_DrawLevelField(x, y);
14084 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14085 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14089 game.ball_state = !game.ball_state;
14091 SCAN_PLAYFIELD(xx, yy)
14093 int e = Feld[xx][yy];
14095 if (game.ball_state)
14097 if (e == EL_EMC_MAGIC_BALL)
14098 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14099 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14100 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14104 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14105 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14106 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14107 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14112 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14113 player->index_bit, dig_side);
14115 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14116 player->index_bit, dig_side);
14118 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14119 player->index_bit, dig_side);
14125 if (!PLAYER_SWITCHING(player, x, y))
14127 player->is_switching = TRUE;
14128 player->switch_x = x;
14129 player->switch_y = y;
14131 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14132 player->index_bit, dig_side);
14133 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14134 player->index_bit, dig_side);
14136 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14137 player->index_bit, dig_side);
14138 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14139 player->index_bit, dig_side);
14142 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14143 player->index_bit, dig_side);
14144 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14145 player->index_bit, dig_side);
14147 return MP_NO_ACTION;
14150 player->push_delay = -1;
14152 if (is_player) /* function can also be called by EL_PENGUIN */
14154 if (Feld[x][y] != element) /* really digged/collected something */
14156 player->is_collecting = !player->is_digging;
14157 player->is_active = TRUE;
14164 static boolean DigFieldByCE(int x, int y, int digging_element)
14166 int element = Feld[x][y];
14168 if (!IS_FREE(x, y))
14170 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14171 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14174 /* no element can dig solid indestructible elements */
14175 if (IS_INDESTRUCTIBLE(element) &&
14176 !IS_DIGGABLE(element) &&
14177 !IS_COLLECTIBLE(element))
14180 if (AmoebaNr[x][y] &&
14181 (element == EL_AMOEBA_FULL ||
14182 element == EL_BD_AMOEBA ||
14183 element == EL_AMOEBA_GROWING))
14185 AmoebaCnt[AmoebaNr[x][y]]--;
14186 AmoebaCnt2[AmoebaNr[x][y]]--;
14189 if (IS_MOVING(x, y))
14190 RemoveMovingField(x, y);
14194 TEST_DrawLevelField(x, y);
14197 /* if digged element was about to explode, prevent the explosion */
14198 ExplodeField[x][y] = EX_TYPE_NONE;
14200 PlayLevelSoundAction(x, y, action);
14203 Store[x][y] = EL_EMPTY;
14205 /* this makes it possible to leave the removed element again */
14206 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14207 Store[x][y] = element;
14212 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14214 int jx = player->jx, jy = player->jy;
14215 int x = jx + dx, y = jy + dy;
14216 int snap_direction = (dx == -1 ? MV_LEFT :
14217 dx == +1 ? MV_RIGHT :
14219 dy == +1 ? MV_DOWN : MV_NONE);
14220 boolean can_continue_snapping = (level.continuous_snapping &&
14221 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14223 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14226 if (!player->active || !IN_LEV_FIELD(x, y))
14234 if (player->MovPos == 0)
14235 player->is_pushing = FALSE;
14237 player->is_snapping = FALSE;
14239 if (player->MovPos == 0)
14241 player->is_moving = FALSE;
14242 player->is_digging = FALSE;
14243 player->is_collecting = FALSE;
14249 /* prevent snapping with already pressed snap key when not allowed */
14250 if (player->is_snapping && !can_continue_snapping)
14253 player->MovDir = snap_direction;
14255 if (player->MovPos == 0)
14257 player->is_moving = FALSE;
14258 player->is_digging = FALSE;
14259 player->is_collecting = FALSE;
14262 player->is_dropping = FALSE;
14263 player->is_dropping_pressed = FALSE;
14264 player->drop_pressed_delay = 0;
14266 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14269 player->is_snapping = TRUE;
14270 player->is_active = TRUE;
14272 if (player->MovPos == 0)
14274 player->is_moving = FALSE;
14275 player->is_digging = FALSE;
14276 player->is_collecting = FALSE;
14279 if (player->MovPos != 0) /* prevent graphic bugs in versions < 2.2.0 */
14280 TEST_DrawLevelField(player->last_jx, player->last_jy);
14282 TEST_DrawLevelField(x, y);
14287 static boolean DropElement(struct PlayerInfo *player)
14289 int old_element, new_element;
14290 int dropx = player->jx, dropy = player->jy;
14291 int drop_direction = player->MovDir;
14292 int drop_side = drop_direction;
14293 int drop_element = get_next_dropped_element(player);
14295 /* do not drop an element on top of another element; when holding drop key
14296 pressed without moving, dropped element must move away before the next
14297 element can be dropped (this is especially important if the next element
14298 is dynamite, which can be placed on background for historical reasons) */
14299 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14302 if (IS_THROWABLE(drop_element))
14304 dropx += GET_DX_FROM_DIR(drop_direction);
14305 dropy += GET_DY_FROM_DIR(drop_direction);
14307 if (!IN_LEV_FIELD(dropx, dropy))
14311 old_element = Feld[dropx][dropy]; /* old element at dropping position */
14312 new_element = drop_element; /* default: no change when dropping */
14314 /* check if player is active, not moving and ready to drop */
14315 if (!player->active || player->MovPos || player->drop_delay > 0)
14318 /* check if player has anything that can be dropped */
14319 if (new_element == EL_UNDEFINED)
14322 /* only set if player has anything that can be dropped */
14323 player->is_dropping_pressed = TRUE;
14325 /* check if drop key was pressed long enough for EM style dynamite */
14326 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14329 /* check if anything can be dropped at the current position */
14330 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14333 /* collected custom elements can only be dropped on empty fields */
14334 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14337 if (old_element != EL_EMPTY)
14338 Back[dropx][dropy] = old_element; /* store old element on this field */
14340 ResetGfxAnimation(dropx, dropy);
14341 ResetRandomAnimationValue(dropx, dropy);
14343 if (player->inventory_size > 0 ||
14344 player->inventory_infinite_element != EL_UNDEFINED)
14346 if (player->inventory_size > 0)
14348 player->inventory_size--;
14350 DrawGameDoorValues();
14352 if (new_element == EL_DYNAMITE)
14353 new_element = EL_DYNAMITE_ACTIVE;
14354 else if (new_element == EL_EM_DYNAMITE)
14355 new_element = EL_EM_DYNAMITE_ACTIVE;
14356 else if (new_element == EL_SP_DISK_RED)
14357 new_element = EL_SP_DISK_RED_ACTIVE;
14360 Feld[dropx][dropy] = new_element;
14362 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14363 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14364 el2img(Feld[dropx][dropy]), 0);
14366 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14368 /* needed if previous element just changed to "empty" in the last frame */
14369 ChangeCount[dropx][dropy] = 0; /* allow at least one more change */
14371 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14372 player->index_bit, drop_side);
14373 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14375 player->index_bit, drop_side);
14377 TestIfElementTouchesCustomElement(dropx, dropy);
14379 else /* player is dropping a dyna bomb */
14381 player->dynabombs_left--;
14383 Feld[dropx][dropy] = new_element;
14385 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14386 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14387 el2img(Feld[dropx][dropy]), 0);
14389 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14392 if (Feld[dropx][dropy] == new_element) /* uninitialized unless CE change */
14393 InitField_WithBug1(dropx, dropy, FALSE);
14395 new_element = Feld[dropx][dropy]; /* element might have changed */
14397 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14398 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14400 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14401 MovDir[dropx][dropy] = drop_direction;
14403 ChangeCount[dropx][dropy] = 0; /* allow at least one more change */
14405 /* do not cause impact style collision by dropping elements that can fall */
14406 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14409 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14410 player->is_dropping = TRUE;
14412 player->drop_pressed_delay = 0;
14413 player->is_dropping_pressed = FALSE;
14415 player->drop_x = dropx;
14416 player->drop_y = dropy;
14421 /* ------------------------------------------------------------------------- */
14422 /* game sound playing functions */
14423 /* ------------------------------------------------------------------------- */
14425 static int *loop_sound_frame = NULL;
14426 static int *loop_sound_volume = NULL;
14428 void InitPlayLevelSound()
14430 int num_sounds = getSoundListSize();
14432 checked_free(loop_sound_frame);
14433 checked_free(loop_sound_volume);
14435 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14436 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14439 static void PlayLevelSound(int x, int y, int nr)
14441 int sx = SCREENX(x), sy = SCREENY(y);
14442 int volume, stereo_position;
14443 int max_distance = 8;
14444 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14446 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14447 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14450 if (!IN_LEV_FIELD(x, y) ||
14451 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14452 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14455 volume = SOUND_MAX_VOLUME;
14457 if (!IN_SCR_FIELD(sx, sy))
14459 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14460 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14462 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14465 stereo_position = (SOUND_MAX_LEFT +
14466 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14467 (SCR_FIELDX + 2 * max_distance));
14469 if (IS_LOOP_SOUND(nr))
14471 /* This assures that quieter loop sounds do not overwrite louder ones,
14472 while restarting sound volume comparison with each new game frame. */
14474 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14477 loop_sound_volume[nr] = volume;
14478 loop_sound_frame[nr] = FrameCounter;
14481 PlaySoundExt(nr, volume, stereo_position, type);
14484 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14486 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14487 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14488 y < LEVELY(BY1) ? LEVELY(BY1) :
14489 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14493 static void PlayLevelSoundAction(int x, int y, int action)
14495 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14498 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14500 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14502 if (sound_effect != SND_UNDEFINED)
14503 PlayLevelSound(x, y, sound_effect);
14506 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14509 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14511 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14512 PlayLevelSound(x, y, sound_effect);
14515 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14517 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14519 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14520 PlayLevelSound(x, y, sound_effect);
14523 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14525 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14527 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14528 StopSound(sound_effect);
14531 static int getLevelMusicNr()
14533 if (levelset.music[level_nr] != MUS_UNDEFINED)
14534 return levelset.music[level_nr]; /* from config file */
14536 return MAP_NOCONF_MUSIC(level_nr); /* from music dir */
14539 static void FadeLevelSounds()
14544 static void FadeLevelMusic()
14546 int music_nr = getLevelMusicNr();
14547 char *curr_music = getCurrentlyPlayingMusicFilename();
14548 char *next_music = getMusicInfoEntryFilename(music_nr);
14550 if (!strEqual(curr_music, next_music))
14554 void FadeLevelSoundsAndMusic()
14560 static void PlayLevelMusic()
14562 int music_nr = getLevelMusicNr();
14563 char *curr_music = getCurrentlyPlayingMusicFilename();
14564 char *next_music = getMusicInfoEntryFilename(music_nr);
14566 if (!strEqual(curr_music, next_music))
14567 PlayMusic(music_nr);
14570 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14572 int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0);
14573 int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
14574 int x = xx - 1 - offset;
14575 int y = yy - 1 - offset;
14580 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14584 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14588 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14592 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14596 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14600 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14604 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14607 case SAMPLE_android_clone:
14608 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14611 case SAMPLE_android_move:
14612 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14615 case SAMPLE_spring:
14616 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14620 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14624 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14627 case SAMPLE_eater_eat:
14628 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14632 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14635 case SAMPLE_collect:
14636 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14639 case SAMPLE_diamond:
14640 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14643 case SAMPLE_squash:
14644 /* !!! CHECK THIS !!! */
14646 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14648 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14652 case SAMPLE_wonderfall:
14653 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14657 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14661 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14665 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14669 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14673 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14677 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14680 case SAMPLE_wonder:
14681 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14685 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14688 case SAMPLE_exit_open:
14689 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14692 case SAMPLE_exit_leave:
14693 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14696 case SAMPLE_dynamite:
14697 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14701 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14705 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14709 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14713 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14717 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
14721 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
14725 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
14730 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
14732 int element = map_element_SP_to_RND(element_sp);
14733 int action = map_action_SP_to_RND(action_sp);
14734 int offset = (setup.sp_show_border_elements ? 0 : 1);
14735 int x = xx - offset;
14736 int y = yy - offset;
14738 PlayLevelSoundElementAction(x, y, element, action);
14741 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
14743 int element = map_element_MM_to_RND(element_mm);
14744 int action = map_action_MM_to_RND(action_mm);
14746 int x = xx - offset;
14747 int y = yy - offset;
14749 if (!IS_MM_ELEMENT(element))
14750 element = EL_MM_DEFAULT;
14752 PlayLevelSoundElementAction(x, y, element, action);
14755 void PlaySound_MM(int sound_mm)
14757 int sound = map_sound_MM_to_RND(sound_mm);
14759 if (sound == SND_UNDEFINED)
14765 void PlaySoundLoop_MM(int sound_mm)
14767 int sound = map_sound_MM_to_RND(sound_mm);
14769 if (sound == SND_UNDEFINED)
14772 PlaySoundLoop(sound);
14775 void StopSound_MM(int sound_mm)
14777 int sound = map_sound_MM_to_RND(sound_mm);
14779 if (sound == SND_UNDEFINED)
14785 void RaiseScore(int value)
14787 local_player->score += value;
14789 game_panel_controls[GAME_PANEL_SCORE].value = local_player->score;
14791 DisplayGameControlValues();
14794 void RaiseScoreElement(int element)
14799 case EL_BD_DIAMOND:
14800 case EL_EMERALD_YELLOW:
14801 case EL_EMERALD_RED:
14802 case EL_EMERALD_PURPLE:
14803 case EL_SP_INFOTRON:
14804 RaiseScore(level.score[SC_EMERALD]);
14807 RaiseScore(level.score[SC_DIAMOND]);
14810 RaiseScore(level.score[SC_CRYSTAL]);
14813 RaiseScore(level.score[SC_PEARL]);
14816 case EL_BD_BUTTERFLY:
14817 case EL_SP_ELECTRON:
14818 RaiseScore(level.score[SC_BUG]);
14821 case EL_BD_FIREFLY:
14822 case EL_SP_SNIKSNAK:
14823 RaiseScore(level.score[SC_SPACESHIP]);
14826 case EL_DARK_YAMYAM:
14827 RaiseScore(level.score[SC_YAMYAM]);
14830 RaiseScore(level.score[SC_ROBOT]);
14833 RaiseScore(level.score[SC_PACMAN]);
14836 RaiseScore(level.score[SC_NUT]);
14839 case EL_EM_DYNAMITE:
14840 case EL_SP_DISK_RED:
14841 case EL_DYNABOMB_INCREASE_NUMBER:
14842 case EL_DYNABOMB_INCREASE_SIZE:
14843 case EL_DYNABOMB_INCREASE_POWER:
14844 RaiseScore(level.score[SC_DYNAMITE]);
14846 case EL_SHIELD_NORMAL:
14847 case EL_SHIELD_DEADLY:
14848 RaiseScore(level.score[SC_SHIELD]);
14850 case EL_EXTRA_TIME:
14851 RaiseScore(level.extra_time_score);
14865 case EL_DC_KEY_WHITE:
14866 RaiseScore(level.score[SC_KEY]);
14869 RaiseScore(element_info[element].collect_score);
14874 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
14876 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
14878 /* closing door required in case of envelope style request dialogs */
14880 CloseDoor(DOOR_CLOSE_1);
14882 if (network.enabled)
14883 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
14887 FadeSkipNextFadeIn();
14889 SetGameStatus(GAME_MODE_MAIN);
14894 else /* continue playing the game */
14896 if (tape.playing && tape.deactivate_display)
14897 TapeDeactivateDisplayOff(TRUE);
14899 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
14901 if (tape.playing && tape.deactivate_display)
14902 TapeDeactivateDisplayOn();
14906 void RequestQuitGame(boolean ask_if_really_quit)
14908 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
14909 boolean skip_request = AllPlayersGone || quick_quit;
14911 RequestQuitGameExt(skip_request, quick_quit,
14912 "Do you really want to quit the game?");
14915 void RequestRestartGame(char *message)
14917 game.restart_game_message = NULL;
14919 if (Request(message, REQ_ASK | REQ_STAY_CLOSED))
14921 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
14925 SetGameStatus(GAME_MODE_MAIN);
14932 /* ------------------------------------------------------------------------- */
14933 /* random generator functions */
14934 /* ------------------------------------------------------------------------- */
14936 unsigned int InitEngineRandom_RND(int seed)
14938 game.num_random_calls = 0;
14940 return InitEngineRandom(seed);
14943 unsigned int RND(int max)
14947 game.num_random_calls++;
14949 return GetEngineRandom(max);
14956 /* ------------------------------------------------------------------------- */
14957 /* game engine snapshot handling functions */
14958 /* ------------------------------------------------------------------------- */
14960 struct EngineSnapshotInfo
14962 /* runtime values for custom element collect score */
14963 int collect_score[NUM_CUSTOM_ELEMENTS];
14965 /* runtime values for group element choice position */
14966 int choice_pos[NUM_GROUP_ELEMENTS];
14968 /* runtime values for belt position animations */
14969 int belt_graphic[4][NUM_BELT_PARTS];
14970 int belt_anim_mode[4][NUM_BELT_PARTS];
14973 static struct EngineSnapshotInfo engine_snapshot_rnd;
14974 static char *snapshot_level_identifier = NULL;
14975 static int snapshot_level_nr = -1;
14977 static void SaveEngineSnapshotValues_RND()
14979 static int belt_base_active_element[4] =
14981 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
14982 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
14983 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
14984 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
14988 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
14990 int element = EL_CUSTOM_START + i;
14992 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
14995 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
14997 int element = EL_GROUP_START + i;
14999 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15002 for (i = 0; i < 4; i++)
15004 for (j = 0; j < NUM_BELT_PARTS; j++)
15006 int element = belt_base_active_element[i] + j;
15007 int graphic = el2img(element);
15008 int anim_mode = graphic_info[graphic].anim_mode;
15010 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15011 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15016 static void LoadEngineSnapshotValues_RND()
15018 unsigned int num_random_calls = game.num_random_calls;
15021 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15023 int element = EL_CUSTOM_START + i;
15025 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15028 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15030 int element = EL_GROUP_START + i;
15032 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15035 for (i = 0; i < 4; i++)
15037 for (j = 0; j < NUM_BELT_PARTS; j++)
15039 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15040 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15042 graphic_info[graphic].anim_mode = anim_mode;
15046 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15048 InitRND(tape.random_seed);
15049 for (i = 0; i < num_random_calls; i++)
15053 if (game.num_random_calls != num_random_calls)
15055 Error(ERR_INFO, "number of random calls out of sync");
15056 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15057 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15058 Error(ERR_EXIT, "this should not happen -- please debug");
15062 void FreeEngineSnapshotSingle()
15064 FreeSnapshotSingle();
15066 setString(&snapshot_level_identifier, NULL);
15067 snapshot_level_nr = -1;
15070 void FreeEngineSnapshotList()
15072 FreeSnapshotList();
15075 ListNode *SaveEngineSnapshotBuffers()
15077 ListNode *buffers = NULL;
15079 /* copy some special values to a structure better suited for the snapshot */
15081 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15082 SaveEngineSnapshotValues_RND();
15083 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15084 SaveEngineSnapshotValues_EM();
15085 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15086 SaveEngineSnapshotValues_SP(&buffers);
15087 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15088 SaveEngineSnapshotValues_MM(&buffers);
15090 /* save values stored in special snapshot structure */
15092 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15093 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15094 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15095 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15096 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15097 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15098 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15099 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15101 /* save further RND engine values */
15103 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15104 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15105 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15107 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZX));
15108 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZY));
15109 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitX));
15110 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitY));
15112 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15113 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15114 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15115 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15116 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15118 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15119 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15120 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15122 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15124 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AllPlayersGone));
15126 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15127 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15129 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15130 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15131 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15132 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15133 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15134 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15135 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15136 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15137 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15138 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15139 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15140 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15141 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15142 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15143 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15144 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15145 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15146 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15148 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15149 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15151 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15152 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15153 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15155 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15156 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15158 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15159 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15160 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15161 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15162 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15164 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15165 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15168 ListNode *node = engine_snapshot_list_rnd;
15171 while (node != NULL)
15173 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15178 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15184 void SaveEngineSnapshotSingle()
15186 ListNode *buffers = SaveEngineSnapshotBuffers();
15188 /* finally save all snapshot buffers to single snapshot */
15189 SaveSnapshotSingle(buffers);
15191 /* save level identification information */
15192 setString(&snapshot_level_identifier, leveldir_current->identifier);
15193 snapshot_level_nr = level_nr;
15196 boolean CheckSaveEngineSnapshotToList()
15198 boolean save_snapshot =
15199 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15200 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15201 game.snapshot.changed_action) ||
15202 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15203 game.snapshot.collected_item));
15205 game.snapshot.changed_action = FALSE;
15206 game.snapshot.collected_item = FALSE;
15207 game.snapshot.save_snapshot = save_snapshot;
15209 return save_snapshot;
15212 void SaveEngineSnapshotToList()
15214 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15218 ListNode *buffers = SaveEngineSnapshotBuffers();
15220 /* finally save all snapshot buffers to snapshot list */
15221 SaveSnapshotToList(buffers);
15224 void SaveEngineSnapshotToListInitial()
15226 FreeEngineSnapshotList();
15228 SaveEngineSnapshotToList();
15231 void LoadEngineSnapshotValues()
15233 /* restore special values from snapshot structure */
15235 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15236 LoadEngineSnapshotValues_RND();
15237 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15238 LoadEngineSnapshotValues_EM();
15239 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15240 LoadEngineSnapshotValues_SP();
15241 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15242 LoadEngineSnapshotValues_MM();
15245 void LoadEngineSnapshotSingle()
15247 LoadSnapshotSingle();
15249 LoadEngineSnapshotValues();
15252 void LoadEngineSnapshot_Undo(int steps)
15254 LoadSnapshotFromList_Older(steps);
15256 LoadEngineSnapshotValues();
15259 void LoadEngineSnapshot_Redo(int steps)
15261 LoadSnapshotFromList_Newer(steps);
15263 LoadEngineSnapshotValues();
15266 boolean CheckEngineSnapshotSingle()
15268 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15269 snapshot_level_nr == level_nr);
15272 boolean CheckEngineSnapshotList()
15274 return CheckSnapshotList();
15278 /* ---------- new game button stuff ---------------------------------------- */
15285 boolean *setup_value;
15286 boolean allowed_on_tape;
15288 } gamebutton_info[NUM_GAME_BUTTONS] =
15291 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15292 GAME_CTRL_ID_STOP, NULL,
15296 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15297 GAME_CTRL_ID_PAUSE, NULL,
15301 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15302 GAME_CTRL_ID_PLAY, NULL,
15306 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15307 GAME_CTRL_ID_UNDO, NULL,
15311 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15312 GAME_CTRL_ID_REDO, NULL,
15316 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15317 GAME_CTRL_ID_SAVE, NULL,
15321 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15322 GAME_CTRL_ID_PAUSE2, NULL,
15326 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15327 GAME_CTRL_ID_LOAD, NULL,
15331 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15332 GAME_CTRL_ID_PANEL_STOP, NULL,
15336 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15337 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15338 FALSE, "pause game"
15341 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15342 GAME_CTRL_ID_PANEL_PLAY, NULL,
15346 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15347 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15348 TRUE, "background music on/off"
15351 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15352 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15353 TRUE, "sound loops on/off"
15356 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15357 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15358 TRUE, "normal sounds on/off"
15361 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15362 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15363 FALSE, "background music on/off"
15366 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15367 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15368 FALSE, "sound loops on/off"
15371 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15372 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15373 FALSE, "normal sounds on/off"
15377 void CreateGameButtons()
15381 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15383 int graphic = gamebutton_info[i].graphic;
15384 struct GraphicInfo *gfx = &graphic_info[graphic];
15385 struct XY *pos = gamebutton_info[i].pos;
15386 struct GadgetInfo *gi;
15389 unsigned int event_mask;
15390 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15391 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15392 int base_x = (on_tape ? VX : DX);
15393 int base_y = (on_tape ? VY : DY);
15394 int gd_x = gfx->src_x;
15395 int gd_y = gfx->src_y;
15396 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15397 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15398 int gd_xa = gfx->src_x + gfx->active_xoffset;
15399 int gd_ya = gfx->src_y + gfx->active_yoffset;
15400 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15401 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15404 if (gfx->bitmap == NULL)
15406 game_gadget[id] = NULL;
15411 if (id == GAME_CTRL_ID_STOP ||
15412 id == GAME_CTRL_ID_PANEL_STOP ||
15413 id == GAME_CTRL_ID_PLAY ||
15414 id == GAME_CTRL_ID_PANEL_PLAY ||
15415 id == GAME_CTRL_ID_SAVE ||
15416 id == GAME_CTRL_ID_LOAD)
15418 button_type = GD_TYPE_NORMAL_BUTTON;
15420 event_mask = GD_EVENT_RELEASED;
15422 else if (id == GAME_CTRL_ID_UNDO ||
15423 id == GAME_CTRL_ID_REDO)
15425 button_type = GD_TYPE_NORMAL_BUTTON;
15427 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15431 button_type = GD_TYPE_CHECK_BUTTON;
15432 checked = (gamebutton_info[i].setup_value != NULL ?
15433 *gamebutton_info[i].setup_value : FALSE);
15434 event_mask = GD_EVENT_PRESSED;
15437 gi = CreateGadget(GDI_CUSTOM_ID, id,
15438 GDI_IMAGE_ID, graphic,
15439 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15440 GDI_X, base_x + GDI_ACTIVE_POS(pos->x),
15441 GDI_Y, base_y + GDI_ACTIVE_POS(pos->y),
15442 GDI_WIDTH, gfx->width,
15443 GDI_HEIGHT, gfx->height,
15444 GDI_TYPE, button_type,
15445 GDI_STATE, GD_BUTTON_UNPRESSED,
15446 GDI_CHECKED, checked,
15447 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15448 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15449 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15450 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15451 GDI_DIRECT_DRAW, FALSE,
15452 GDI_EVENT_MASK, event_mask,
15453 GDI_CALLBACK_ACTION, HandleGameButtons,
15457 Error(ERR_EXIT, "cannot create gadget");
15459 game_gadget[id] = gi;
15463 void FreeGameButtons()
15467 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15468 FreeGadget(game_gadget[i]);
15471 static void UnmapGameButtonsAtSamePosition(int id)
15475 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15477 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15478 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15479 UnmapGadget(game_gadget[i]);
15482 static void UnmapGameButtonsAtSamePosition_All()
15484 if (setup.show_snapshot_buttons)
15486 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15487 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15488 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15492 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15493 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15494 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15496 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15497 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15498 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15502 static void MapGameButtonsAtSamePosition(int id)
15506 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15508 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15509 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15510 MapGadget(game_gadget[i]);
15512 UnmapGameButtonsAtSamePosition_All();
15515 void MapUndoRedoButtons()
15517 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15518 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15520 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15521 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15523 ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, TRUE, GDI_END);
15526 void UnmapUndoRedoButtons()
15528 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15529 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15531 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15532 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15534 ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, FALSE, GDI_END);
15537 void MapGameButtonsExt(boolean on_tape)
15541 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15542 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15543 i != GAME_CTRL_ID_UNDO &&
15544 i != GAME_CTRL_ID_REDO)
15545 MapGadget(game_gadget[i]);
15547 UnmapGameButtonsAtSamePosition_All();
15549 RedrawGameButtons();
15552 void UnmapGameButtonsExt(boolean on_tape)
15556 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15557 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15558 UnmapGadget(game_gadget[i]);
15561 void RedrawGameButtonsExt(boolean on_tape)
15565 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15566 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15567 RedrawGadget(game_gadget[i]);
15569 // RedrawGadget() may have set REDRAW_ALL if buttons are defined off-area
15570 redraw_mask &= ~REDRAW_ALL;
15573 void SetGadgetState(struct GadgetInfo *gi, boolean state)
15578 gi->checked = state;
15581 void RedrawSoundButtonGadget(int id)
15583 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15584 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15585 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15586 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15587 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15588 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15591 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15592 RedrawGadget(game_gadget[id2]);
15595 void MapGameButtons()
15597 MapGameButtonsExt(FALSE);
15600 void UnmapGameButtons()
15602 UnmapGameButtonsExt(FALSE);
15605 void RedrawGameButtons()
15607 RedrawGameButtonsExt(FALSE);
15610 void MapGameButtonsOnTape()
15612 MapGameButtonsExt(TRUE);
15615 void UnmapGameButtonsOnTape()
15617 UnmapGameButtonsExt(TRUE);
15620 void RedrawGameButtonsOnTape()
15622 RedrawGameButtonsExt(TRUE);
15625 void GameUndoRedoExt()
15627 ClearPlayerAction();
15629 tape.pausing = TRUE;
15632 UpdateAndDisplayGameControlValues();
15634 DrawCompleteVideoDisplay();
15635 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
15636 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
15637 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
15642 void GameUndo(int steps)
15644 if (!CheckEngineSnapshotList())
15647 LoadEngineSnapshot_Undo(steps);
15652 void GameRedo(int steps)
15654 if (!CheckEngineSnapshotList())
15657 LoadEngineSnapshot_Redo(steps);
15662 static void HandleGameButtonsExt(int id, int button)
15664 static boolean game_undo_executed = FALSE;
15665 int steps = BUTTON_STEPSIZE(button);
15666 boolean handle_game_buttons =
15667 (game_status == GAME_MODE_PLAYING ||
15668 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
15670 if (!handle_game_buttons)
15675 case GAME_CTRL_ID_STOP:
15676 case GAME_CTRL_ID_PANEL_STOP:
15677 if (game_status == GAME_MODE_MAIN)
15683 RequestQuitGame(TRUE);
15687 case GAME_CTRL_ID_PAUSE:
15688 case GAME_CTRL_ID_PAUSE2:
15689 case GAME_CTRL_ID_PANEL_PAUSE:
15690 if (network.enabled && game_status == GAME_MODE_PLAYING)
15693 SendToServer_ContinuePlaying();
15695 SendToServer_PausePlaying();
15698 TapeTogglePause(TAPE_TOGGLE_MANUAL);
15700 game_undo_executed = FALSE;
15704 case GAME_CTRL_ID_PLAY:
15705 case GAME_CTRL_ID_PANEL_PLAY:
15706 if (game_status == GAME_MODE_MAIN)
15708 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15710 else if (tape.pausing)
15712 if (network.enabled)
15713 SendToServer_ContinuePlaying();
15715 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
15719 case GAME_CTRL_ID_UNDO:
15720 // Important: When using "save snapshot when collecting an item" mode,
15721 // load last (current) snapshot for first "undo" after pressing "pause"
15722 // (else the last-but-one snapshot would be loaded, because the snapshot
15723 // pointer already points to the last snapshot when pressing "pause",
15724 // which is fine for "every step/move" mode, but not for "every collect")
15725 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15726 !game_undo_executed)
15729 game_undo_executed = TRUE;
15734 case GAME_CTRL_ID_REDO:
15738 case GAME_CTRL_ID_SAVE:
15742 case GAME_CTRL_ID_LOAD:
15746 case SOUND_CTRL_ID_MUSIC:
15747 case SOUND_CTRL_ID_PANEL_MUSIC:
15748 if (setup.sound_music)
15750 setup.sound_music = FALSE;
15754 else if (audio.music_available)
15756 setup.sound = setup.sound_music = TRUE;
15758 SetAudioMode(setup.sound);
15760 if (game_status == GAME_MODE_PLAYING)
15764 RedrawSoundButtonGadget(id);
15768 case SOUND_CTRL_ID_LOOPS:
15769 case SOUND_CTRL_ID_PANEL_LOOPS:
15770 if (setup.sound_loops)
15771 setup.sound_loops = FALSE;
15772 else if (audio.loops_available)
15774 setup.sound = setup.sound_loops = TRUE;
15776 SetAudioMode(setup.sound);
15779 RedrawSoundButtonGadget(id);
15783 case SOUND_CTRL_ID_SIMPLE:
15784 case SOUND_CTRL_ID_PANEL_SIMPLE:
15785 if (setup.sound_simple)
15786 setup.sound_simple = FALSE;
15787 else if (audio.sound_available)
15789 setup.sound = setup.sound_simple = TRUE;
15791 SetAudioMode(setup.sound);
15794 RedrawSoundButtonGadget(id);
15803 static void HandleGameButtons(struct GadgetInfo *gi)
15805 HandleGameButtonsExt(gi->custom_id, gi->event.button);
15808 void HandleSoundButtonKeys(Key key)
15810 if (key == setup.shortcut.sound_simple)
15811 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
15812 else if (key == setup.shortcut.sound_loops)
15813 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
15814 else if (key == setup.shortcut.sound_music)
15815 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);