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(void);
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 (!options.network || player->connected)
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 =============================================================================
3278 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3279 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3280 int fade_mask = REDRAW_FIELD;
3282 boolean emulate_bd = TRUE; /* unless non-BOULDERDASH elements found */
3283 boolean emulate_sb = TRUE; /* unless non-SOKOBAN elements found */
3284 boolean emulate_sp = TRUE; /* unless non-SUPAPLEX elements found */
3285 int initial_move_dir = MV_DOWN;
3288 // required here to update video display before fading (FIX THIS)
3289 DrawMaskedBorder(REDRAW_DOOR_2);
3291 if (!game.restart_level)
3292 CloseDoor(DOOR_CLOSE_1);
3294 SetGameStatus(GAME_MODE_PLAYING);
3296 if (level_editor_test_game)
3297 FadeSkipNextFadeIn();
3299 FadeSetEnterScreen();
3301 if (CheckIfGlobalBorderOrPlayfieldViewportHasChanged())
3302 fade_mask = REDRAW_ALL;
3304 FadeLevelSoundsAndMusic();
3306 ExpireSoundLoops(TRUE);
3310 /* needed if different viewport properties defined for playing */
3311 ChangeViewportPropertiesIfNeeded();
3315 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3317 DrawCompleteVideoDisplay();
3320 InitGameControlValues();
3322 /* don't play tapes over network */
3323 network_playing = (options.network && !tape.playing);
3325 for (i = 0; i < MAX_PLAYERS; i++)
3327 struct PlayerInfo *player = &stored_player[i];
3329 player->index_nr = i;
3330 player->index_bit = (1 << i);
3331 player->element_nr = EL_PLAYER_1 + i;
3333 player->present = FALSE;
3334 player->active = FALSE;
3335 player->mapped = FALSE;
3337 player->killed = FALSE;
3338 player->reanimated = FALSE;
3341 player->effective_action = 0;
3342 player->programmed_action = 0;
3344 player->mouse_action.lx = 0;
3345 player->mouse_action.ly = 0;
3346 player->mouse_action.button = 0;
3348 player->effective_mouse_action.lx = 0;
3349 player->effective_mouse_action.ly = 0;
3350 player->effective_mouse_action.button = 0;
3353 player->score_final = 0;
3355 player->health = MAX_HEALTH;
3356 player->health_final = MAX_HEALTH;
3358 player->gems_still_needed = level.gems_needed;
3359 player->sokobanfields_still_needed = 0;
3360 player->lights_still_needed = 0;
3361 player->friends_still_needed = 0;
3363 for (j = 0; j < MAX_NUM_KEYS; j++)
3364 player->key[j] = FALSE;
3366 player->num_white_keys = 0;
3368 player->dynabomb_count = 0;
3369 player->dynabomb_size = 1;
3370 player->dynabombs_left = 0;
3371 player->dynabomb_xl = FALSE;
3373 player->MovDir = initial_move_dir;
3376 player->GfxDir = initial_move_dir;
3377 player->GfxAction = ACTION_DEFAULT;
3379 player->StepFrame = 0;
3381 player->initial_element = player->element_nr;
3382 player->artwork_element =
3383 (level.use_artwork_element[i] ? level.artwork_element[i] :
3384 player->element_nr);
3385 player->use_murphy = FALSE;
3387 player->block_last_field = FALSE; /* initialized in InitPlayerField() */
3388 player->block_delay_adjustment = 0; /* initialized in InitPlayerField() */
3390 player->gravity = level.initial_player_gravity[i];
3392 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3394 player->actual_frame_counter = 0;
3396 player->step_counter = 0;
3398 player->last_move_dir = initial_move_dir;
3400 player->is_active = FALSE;
3402 player->is_waiting = FALSE;
3403 player->is_moving = FALSE;
3404 player->is_auto_moving = FALSE;
3405 player->is_digging = FALSE;
3406 player->is_snapping = FALSE;
3407 player->is_collecting = FALSE;
3408 player->is_pushing = FALSE;
3409 player->is_switching = FALSE;
3410 player->is_dropping = FALSE;
3411 player->is_dropping_pressed = FALSE;
3413 player->is_bored = FALSE;
3414 player->is_sleeping = FALSE;
3416 player->was_waiting = TRUE;
3417 player->was_moving = FALSE;
3418 player->was_snapping = FALSE;
3419 player->was_dropping = FALSE;
3421 player->force_dropping = FALSE;
3423 player->frame_counter_bored = -1;
3424 player->frame_counter_sleeping = -1;
3426 player->anim_delay_counter = 0;
3427 player->post_delay_counter = 0;
3429 player->dir_waiting = initial_move_dir;
3430 player->action_waiting = ACTION_DEFAULT;
3431 player->last_action_waiting = ACTION_DEFAULT;
3432 player->special_action_bored = ACTION_DEFAULT;
3433 player->special_action_sleeping = ACTION_DEFAULT;
3435 player->switch_x = -1;
3436 player->switch_y = -1;
3438 player->drop_x = -1;
3439 player->drop_y = -1;
3441 player->show_envelope = 0;
3443 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3445 player->push_delay = -1; /* initialized when pushing starts */
3446 player->push_delay_value = game.initial_push_delay_value;
3448 player->drop_delay = 0;
3449 player->drop_pressed_delay = 0;
3451 player->last_jx = -1;
3452 player->last_jy = -1;
3456 player->shield_normal_time_left = 0;
3457 player->shield_deadly_time_left = 0;
3459 player->inventory_infinite_element = EL_UNDEFINED;
3460 player->inventory_size = 0;
3462 if (level.use_initial_inventory[i])
3464 for (j = 0; j < level.initial_inventory_size[i]; j++)
3466 int element = level.initial_inventory_content[i][j];
3467 int collect_count = element_info[element].collect_count_initial;
3470 if (!IS_CUSTOM_ELEMENT(element))
3473 if (collect_count == 0)
3474 player->inventory_infinite_element = element;
3476 for (k = 0; k < collect_count; k++)
3477 if (player->inventory_size < MAX_INVENTORY_SIZE)
3478 player->inventory_element[player->inventory_size++] = element;
3482 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3483 SnapField(player, 0, 0);
3485 player->LevelSolved = FALSE;
3486 player->GameOver = FALSE;
3488 player->LevelSolved_GameWon = FALSE;
3489 player->LevelSolved_GameEnd = FALSE;
3490 player->LevelSolved_PanelOff = FALSE;
3491 player->LevelSolved_SaveTape = FALSE;
3492 player->LevelSolved_SaveScore = FALSE;
3494 player->LevelSolved_CountingTime = 0;
3495 player->LevelSolved_CountingScore = 0;
3496 player->LevelSolved_CountingHealth = 0;
3498 map_player_action[i] = i;
3501 network_player_action_received = FALSE;
3503 #if defined(NETWORK_AVALIABLE)
3504 /* initial null action */
3505 if (network_playing)
3506 SendToServer_MovePlayer(MV_NONE);
3515 TimeLeft = level.time;
3518 ScreenMovDir = MV_NONE;
3522 ScrollStepSize = 0; /* will be correctly initialized by ScrollScreen() */
3524 AllPlayersGone = FALSE;
3526 game.no_time_limit = (level.time == 0);
3528 game.yamyam_content_nr = 0;
3529 game.robot_wheel_active = FALSE;
3530 game.magic_wall_active = FALSE;
3531 game.magic_wall_time_left = 0;
3532 game.light_time_left = 0;
3533 game.timegate_time_left = 0;
3534 game.switchgate_pos = 0;
3535 game.wind_direction = level.wind_direction_initial;
3537 game.lenses_time_left = 0;
3538 game.magnify_time_left = 0;
3540 game.ball_state = level.ball_state_initial;
3541 game.ball_content_nr = 0;
3543 game.envelope_active = FALSE;
3545 /* set focus to local player for network games, else to all players */
3546 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
3547 game.centered_player_nr_next = game.centered_player_nr;
3548 game.set_centered_player = FALSE;
3550 if (network_playing && tape.recording)
3552 /* store client dependent player focus when recording network games */
3553 tape.centered_player_nr_next = game.centered_player_nr_next;
3554 tape.set_centered_player = TRUE;
3557 for (i = 0; i < NUM_BELTS; i++)
3559 game.belt_dir[i] = MV_NONE;
3560 game.belt_dir_nr[i] = 3; /* not moving, next moving left */
3563 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3564 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3566 #if DEBUG_INIT_PLAYER
3569 printf("Player status at level initialization:\n");
3573 SCAN_PLAYFIELD(x, y)
3575 Feld[x][y] = level.field[x][y];
3576 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3577 ChangeDelay[x][y] = 0;
3578 ChangePage[x][y] = -1;
3579 CustomValue[x][y] = 0; /* initialized in InitField() */
3580 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3582 WasJustMoving[x][y] = 0;
3583 WasJustFalling[x][y] = 0;
3584 CheckCollision[x][y] = 0;
3585 CheckImpact[x][y] = 0;
3587 Pushed[x][y] = FALSE;
3589 ChangeCount[x][y] = 0;
3590 ChangeEvent[x][y] = -1;
3592 ExplodePhase[x][y] = 0;
3593 ExplodeDelay[x][y] = 0;
3594 ExplodeField[x][y] = EX_TYPE_NONE;
3596 RunnerVisit[x][y] = 0;
3597 PlayerVisit[x][y] = 0;
3600 GfxRandom[x][y] = INIT_GFX_RANDOM();
3601 GfxElement[x][y] = EL_UNDEFINED;
3602 GfxAction[x][y] = ACTION_DEFAULT;
3603 GfxDir[x][y] = MV_NONE;
3604 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3607 SCAN_PLAYFIELD(x, y)
3609 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3611 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3613 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3616 InitField(x, y, TRUE);
3618 ResetGfxAnimation(x, y);
3623 for (i = 0; i < MAX_PLAYERS; i++)
3625 struct PlayerInfo *player = &stored_player[i];
3627 /* set number of special actions for bored and sleeping animation */
3628 player->num_special_action_bored =
3629 get_num_special_action(player->artwork_element,
3630 ACTION_BORING_1, ACTION_BORING_LAST);
3631 player->num_special_action_sleeping =
3632 get_num_special_action(player->artwork_element,
3633 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3636 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3637 emulate_sb ? EMU_SOKOBAN :
3638 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3640 /* initialize type of slippery elements */
3641 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3643 if (!IS_CUSTOM_ELEMENT(i))
3645 /* default: elements slip down either to the left or right randomly */
3646 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3648 /* SP style elements prefer to slip down on the left side */
3649 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3650 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3652 /* BD style elements prefer to slip down on the left side */
3653 if (game.emulation == EMU_BOULDERDASH)
3654 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3658 /* initialize explosion and ignition delay */
3659 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3661 if (!IS_CUSTOM_ELEMENT(i))
3664 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3665 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3666 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3667 int last_phase = (num_phase + 1) * delay;
3668 int half_phase = (num_phase / 2) * delay;
3670 element_info[i].explosion_delay = last_phase - 1;
3671 element_info[i].ignition_delay = half_phase;
3673 if (i == EL_BLACK_ORB)
3674 element_info[i].ignition_delay = 1;
3678 /* correct non-moving belts to start moving left */
3679 for (i = 0; i < NUM_BELTS; i++)
3680 if (game.belt_dir[i] == MV_NONE)
3681 game.belt_dir_nr[i] = 3; /* not moving, next moving left */
3683 #if USE_NEW_PLAYER_ASSIGNMENTS
3684 /* !!! SAME AS init.c:InitPlayerInfo() -- FIX THIS !!! */
3685 /* choose default local player */
3686 local_player = &stored_player[0];
3688 for (i = 0; i < MAX_PLAYERS; i++)
3689 stored_player[i].connected = FALSE;
3691 local_player->connected = TRUE;
3692 /* !!! SAME AS init.c:InitPlayerInfo() -- FIX THIS !!! */
3696 for (i = 0; i < MAX_PLAYERS; i++)
3697 stored_player[i].connected = tape.player_participates[i];
3699 else if (game.team_mode && !options.network)
3701 /* try to guess locally connected team mode players (needed for correct
3702 assignment of player figures from level to locally playing players) */
3704 for (i = 0; i < MAX_PLAYERS; i++)
3705 if (setup.input[i].use_joystick ||
3706 setup.input[i].key.left != KSYM_UNDEFINED)
3707 stored_player[i].connected = TRUE;
3710 #if DEBUG_INIT_PLAYER
3713 printf("Player status after level initialization:\n");
3715 for (i = 0; i < MAX_PLAYERS; i++)
3717 struct PlayerInfo *player = &stored_player[i];
3719 printf("- player %d: present == %d, connected == %d, active == %d",
3725 if (local_player == player)
3726 printf(" (local player)");
3733 #if DEBUG_INIT_PLAYER
3735 printf("Reassigning players ...\n");
3738 /* check if any connected player was not found in playfield */
3739 for (i = 0; i < MAX_PLAYERS; i++)
3741 struct PlayerInfo *player = &stored_player[i];
3743 if (player->connected && !player->present)
3745 struct PlayerInfo *field_player = NULL;
3747 #if DEBUG_INIT_PLAYER
3749 printf("- looking for field player for player %d ...\n", i + 1);
3752 /* assign first free player found that is present in the playfield */
3754 /* first try: look for unmapped playfield player that is not connected */
3755 for (j = 0; j < MAX_PLAYERS; j++)
3756 if (field_player == NULL &&
3757 stored_player[j].present &&
3758 !stored_player[j].mapped &&
3759 !stored_player[j].connected)
3760 field_player = &stored_player[j];
3762 /* second try: look for *any* unmapped playfield player */
3763 for (j = 0; j < MAX_PLAYERS; j++)
3764 if (field_player == NULL &&
3765 stored_player[j].present &&
3766 !stored_player[j].mapped)
3767 field_player = &stored_player[j];
3769 if (field_player != NULL)
3771 int jx = field_player->jx, jy = field_player->jy;
3773 #if DEBUG_INIT_PLAYER
3775 printf("- found player %d\n", field_player->index_nr + 1);
3778 player->present = FALSE;
3779 player->active = FALSE;
3781 field_player->present = TRUE;
3782 field_player->active = TRUE;
3785 player->initial_element = field_player->initial_element;
3786 player->artwork_element = field_player->artwork_element;
3788 player->block_last_field = field_player->block_last_field;
3789 player->block_delay_adjustment = field_player->block_delay_adjustment;
3792 StorePlayer[jx][jy] = field_player->element_nr;
3794 field_player->jx = field_player->last_jx = jx;
3795 field_player->jy = field_player->last_jy = jy;
3797 if (local_player == player)
3798 local_player = field_player;
3800 map_player_action[field_player->index_nr] = i;
3802 field_player->mapped = TRUE;
3804 #if DEBUG_INIT_PLAYER
3806 printf("- map_player_action[%d] == %d\n",
3807 field_player->index_nr + 1, i + 1);
3812 if (player->connected && player->present)
3813 player->mapped = TRUE;
3816 #if DEBUG_INIT_PLAYER
3819 printf("Player status after player assignment (first stage):\n");
3821 for (i = 0; i < MAX_PLAYERS; i++)
3823 struct PlayerInfo *player = &stored_player[i];
3825 printf("- player %d: present == %d, connected == %d, active == %d",
3831 if (local_player == player)
3832 printf(" (local player)");
3841 /* check if any connected player was not found in playfield */
3842 for (i = 0; i < MAX_PLAYERS; i++)
3844 struct PlayerInfo *player = &stored_player[i];
3846 if (player->connected && !player->present)
3848 for (j = 0; j < MAX_PLAYERS; j++)
3850 struct PlayerInfo *field_player = &stored_player[j];
3851 int jx = field_player->jx, jy = field_player->jy;
3853 /* assign first free player found that is present in the playfield */
3854 if (field_player->present && !field_player->connected)
3856 player->present = TRUE;
3857 player->active = TRUE;
3859 field_player->present = FALSE;
3860 field_player->active = FALSE;
3862 player->initial_element = field_player->initial_element;
3863 player->artwork_element = field_player->artwork_element;
3865 player->block_last_field = field_player->block_last_field;
3866 player->block_delay_adjustment = field_player->block_delay_adjustment;
3868 StorePlayer[jx][jy] = player->element_nr;
3870 player->jx = player->last_jx = jx;
3871 player->jy = player->last_jy = jy;
3881 printf("::: local_player->present == %d\n", local_player->present);
3886 /* when playing a tape, eliminate all players who do not participate */
3888 #if USE_NEW_PLAYER_ASSIGNMENTS
3890 if (!game.team_mode)
3892 for (i = 0; i < MAX_PLAYERS; i++)
3894 if (stored_player[i].active &&
3895 !tape.player_participates[map_player_action[i]])
3897 struct PlayerInfo *player = &stored_player[i];
3898 int jx = player->jx, jy = player->jy;
3900 #if DEBUG_INIT_PLAYER
3902 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
3905 player->active = FALSE;
3906 StorePlayer[jx][jy] = 0;
3907 Feld[jx][jy] = EL_EMPTY;
3914 for (i = 0; i < MAX_PLAYERS; i++)
3916 if (stored_player[i].active &&
3917 !tape.player_participates[i])
3919 struct PlayerInfo *player = &stored_player[i];
3920 int jx = player->jx, jy = player->jy;
3922 player->active = FALSE;
3923 StorePlayer[jx][jy] = 0;
3924 Feld[jx][jy] = EL_EMPTY;
3929 else if (!options.network && !game.team_mode) /* && !tape.playing */
3931 /* when in single player mode, eliminate all but the first active player */
3933 for (i = 0; i < MAX_PLAYERS; i++)
3935 if (stored_player[i].active)
3937 for (j = i + 1; j < MAX_PLAYERS; j++)
3939 if (stored_player[j].active)
3941 struct PlayerInfo *player = &stored_player[j];
3942 int jx = player->jx, jy = player->jy;
3944 player->active = FALSE;
3945 player->present = FALSE;
3947 StorePlayer[jx][jy] = 0;
3948 Feld[jx][jy] = EL_EMPTY;
3955 /* when recording the game, store which players take part in the game */
3958 #if USE_NEW_PLAYER_ASSIGNMENTS
3959 for (i = 0; i < MAX_PLAYERS; i++)
3960 if (stored_player[i].connected)
3961 tape.player_participates[i] = TRUE;
3963 for (i = 0; i < MAX_PLAYERS; i++)
3964 if (stored_player[i].active)
3965 tape.player_participates[i] = TRUE;
3969 #if DEBUG_INIT_PLAYER
3972 printf("Player status after player assignment (final stage):\n");
3974 for (i = 0; i < MAX_PLAYERS; i++)
3976 struct PlayerInfo *player = &stored_player[i];
3978 printf("- player %d: present == %d, connected == %d, active == %d",
3984 if (local_player == player)
3985 printf(" (local player)");
3992 if (BorderElement == EL_EMPTY)
3995 SBX_Right = lev_fieldx - SCR_FIELDX;
3997 SBY_Lower = lev_fieldy - SCR_FIELDY;
4002 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4004 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4007 if (full_lev_fieldx <= SCR_FIELDX)
4008 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4009 if (full_lev_fieldy <= SCR_FIELDY)
4010 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4012 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4014 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4017 /* if local player not found, look for custom element that might create
4018 the player (make some assumptions about the right custom element) */
4019 if (!local_player->present)
4021 int start_x = 0, start_y = 0;
4022 int found_rating = 0;
4023 int found_element = EL_UNDEFINED;
4024 int player_nr = local_player->index_nr;
4026 SCAN_PLAYFIELD(x, y)
4028 int element = Feld[x][y];
4033 if (level.use_start_element[player_nr] &&
4034 level.start_element[player_nr] == element &&
4041 found_element = element;
4044 if (!IS_CUSTOM_ELEMENT(element))
4047 if (CAN_CHANGE(element))
4049 for (i = 0; i < element_info[element].num_change_pages; i++)
4051 /* check for player created from custom element as single target */
4052 content = element_info[element].change_page[i].target_element;
4053 is_player = ELEM_IS_PLAYER(content);
4055 if (is_player && (found_rating < 3 ||
4056 (found_rating == 3 && element < found_element)))
4062 found_element = element;
4067 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4069 /* check for player created from custom element as explosion content */
4070 content = element_info[element].content.e[xx][yy];
4071 is_player = ELEM_IS_PLAYER(content);
4073 if (is_player && (found_rating < 2 ||
4074 (found_rating == 2 && element < found_element)))
4076 start_x = x + xx - 1;
4077 start_y = y + yy - 1;
4080 found_element = element;
4083 if (!CAN_CHANGE(element))
4086 for (i = 0; i < element_info[element].num_change_pages; i++)
4088 /* check for player created from custom element as extended target */
4090 element_info[element].change_page[i].target_content.e[xx][yy];
4092 is_player = ELEM_IS_PLAYER(content);
4094 if (is_player && (found_rating < 1 ||
4095 (found_rating == 1 && element < found_element)))
4097 start_x = x + xx - 1;
4098 start_y = y + yy - 1;
4101 found_element = element;
4107 scroll_x = SCROLL_POSITION_X(start_x);
4108 scroll_y = SCROLL_POSITION_Y(start_y);
4112 scroll_x = SCROLL_POSITION_X(local_player->jx);
4113 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4116 /* !!! FIX THIS (START) !!! */
4117 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4119 InitGameEngine_EM();
4121 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4123 InitGameEngine_SP();
4125 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4127 InitGameEngine_MM();
4131 DrawLevel(REDRAW_FIELD);
4134 /* after drawing the level, correct some elements */
4135 if (game.timegate_time_left == 0)
4136 CloseAllOpenTimegates();
4139 /* blit playfield from scroll buffer to normal back buffer for fading in */
4140 BlitScreenToBitmap(backbuffer);
4141 /* !!! FIX THIS (END) !!! */
4143 DrawMaskedBorder(fade_mask);
4148 // full screen redraw is required at this point in the following cases:
4149 // - special editor door undrawn when game was started from level editor
4150 // - drawing area (playfield) was changed and has to be removed completely
4151 redraw_mask = REDRAW_ALL;
4155 if (!game.restart_level)
4157 /* copy default game door content to main double buffer */
4159 /* !!! CHECK AGAIN !!! */
4160 SetPanelBackground();
4161 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4162 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4165 SetPanelBackground();
4166 SetDrawBackgroundMask(REDRAW_DOOR_1);
4168 UpdateAndDisplayGameControlValues();
4170 if (!game.restart_level)
4176 CreateGameButtons();
4181 /* copy actual game door content to door double buffer for OpenDoor() */
4182 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4184 OpenDoor(DOOR_OPEN_ALL);
4186 PlaySound(SND_GAME_STARTING);
4188 if (setup.sound_music)
4191 KeyboardAutoRepeatOffUnlessAutoplay();
4193 #if DEBUG_INIT_PLAYER
4196 printf("Player status (final):\n");
4198 for (i = 0; i < MAX_PLAYERS; i++)
4200 struct PlayerInfo *player = &stored_player[i];
4202 printf("- player %d: present == %d, connected == %d, active == %d",
4208 if (local_player == player)
4209 printf(" (local player)");
4222 if (!game.restart_level && !tape.playing)
4224 LevelStats_incPlayed(level_nr);
4226 SaveLevelSetup_SeriesInfo();
4229 game.restart_level = FALSE;
4231 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4232 InitGameActions_MM();
4234 SaveEngineSnapshotToListInitial();
4237 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4238 int actual_player_x, int actual_player_y)
4240 /* this is used for non-R'n'D game engines to update certain engine values */
4242 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4244 actual_player_x = correctLevelPosX_EM(actual_player_x);
4245 actual_player_y = correctLevelPosY_EM(actual_player_y);
4248 /* needed to determine if sounds are played within the visible screen area */
4249 scroll_x = actual_scroll_x;
4250 scroll_y = actual_scroll_y;
4252 /* needed to get player position for "follow finger" playing input method */
4253 local_player->jx = actual_player_x;
4254 local_player->jy = actual_player_y;
4257 void InitMovDir(int x, int y)
4259 int i, element = Feld[x][y];
4260 static int xy[4][2] =
4267 static int direction[3][4] =
4269 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4270 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4271 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4280 Feld[x][y] = EL_BUG;
4281 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4284 case EL_SPACESHIP_RIGHT:
4285 case EL_SPACESHIP_UP:
4286 case EL_SPACESHIP_LEFT:
4287 case EL_SPACESHIP_DOWN:
4288 Feld[x][y] = EL_SPACESHIP;
4289 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4292 case EL_BD_BUTTERFLY_RIGHT:
4293 case EL_BD_BUTTERFLY_UP:
4294 case EL_BD_BUTTERFLY_LEFT:
4295 case EL_BD_BUTTERFLY_DOWN:
4296 Feld[x][y] = EL_BD_BUTTERFLY;
4297 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4300 case EL_BD_FIREFLY_RIGHT:
4301 case EL_BD_FIREFLY_UP:
4302 case EL_BD_FIREFLY_LEFT:
4303 case EL_BD_FIREFLY_DOWN:
4304 Feld[x][y] = EL_BD_FIREFLY;
4305 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4308 case EL_PACMAN_RIGHT:
4310 case EL_PACMAN_LEFT:
4311 case EL_PACMAN_DOWN:
4312 Feld[x][y] = EL_PACMAN;
4313 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4316 case EL_YAMYAM_LEFT:
4317 case EL_YAMYAM_RIGHT:
4319 case EL_YAMYAM_DOWN:
4320 Feld[x][y] = EL_YAMYAM;
4321 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4324 case EL_SP_SNIKSNAK:
4325 MovDir[x][y] = MV_UP;
4328 case EL_SP_ELECTRON:
4329 MovDir[x][y] = MV_LEFT;
4336 Feld[x][y] = EL_MOLE;
4337 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4341 if (IS_CUSTOM_ELEMENT(element))
4343 struct ElementInfo *ei = &element_info[element];
4344 int move_direction_initial = ei->move_direction_initial;
4345 int move_pattern = ei->move_pattern;
4347 if (move_direction_initial == MV_START_PREVIOUS)
4349 if (MovDir[x][y] != MV_NONE)
4352 move_direction_initial = MV_START_AUTOMATIC;
4355 if (move_direction_initial == MV_START_RANDOM)
4356 MovDir[x][y] = 1 << RND(4);
4357 else if (move_direction_initial & MV_ANY_DIRECTION)
4358 MovDir[x][y] = move_direction_initial;
4359 else if (move_pattern == MV_ALL_DIRECTIONS ||
4360 move_pattern == MV_TURNING_LEFT ||
4361 move_pattern == MV_TURNING_RIGHT ||
4362 move_pattern == MV_TURNING_LEFT_RIGHT ||
4363 move_pattern == MV_TURNING_RIGHT_LEFT ||
4364 move_pattern == MV_TURNING_RANDOM)
4365 MovDir[x][y] = 1 << RND(4);
4366 else if (move_pattern == MV_HORIZONTAL)
4367 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4368 else if (move_pattern == MV_VERTICAL)
4369 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4370 else if (move_pattern & MV_ANY_DIRECTION)
4371 MovDir[x][y] = element_info[element].move_pattern;
4372 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4373 move_pattern == MV_ALONG_RIGHT_SIDE)
4375 /* use random direction as default start direction */
4376 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4377 MovDir[x][y] = 1 << RND(4);
4379 for (i = 0; i < NUM_DIRECTIONS; i++)
4381 int x1 = x + xy[i][0];
4382 int y1 = y + xy[i][1];
4384 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4386 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4387 MovDir[x][y] = direction[0][i];
4389 MovDir[x][y] = direction[1][i];
4398 MovDir[x][y] = 1 << RND(4);
4400 if (element != EL_BUG &&
4401 element != EL_SPACESHIP &&
4402 element != EL_BD_BUTTERFLY &&
4403 element != EL_BD_FIREFLY)
4406 for (i = 0; i < NUM_DIRECTIONS; i++)
4408 int x1 = x + xy[i][0];
4409 int y1 = y + xy[i][1];
4411 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4413 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4415 MovDir[x][y] = direction[0][i];
4418 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4419 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4421 MovDir[x][y] = direction[1][i];
4430 GfxDir[x][y] = MovDir[x][y];
4433 void InitAmoebaNr(int x, int y)
4436 int group_nr = AmoebeNachbarNr(x, y);
4440 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4442 if (AmoebaCnt[i] == 0)
4450 AmoebaNr[x][y] = group_nr;
4451 AmoebaCnt[group_nr]++;
4452 AmoebaCnt2[group_nr]++;
4455 static void PlayerWins(struct PlayerInfo *player)
4457 player->LevelSolved = TRUE;
4458 player->GameOver = TRUE;
4460 player->score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4461 level.native_em_level->lev->score :
4462 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4465 player->health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4466 MM_HEALTH(game_mm.laser_overload_value) :
4469 player->LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed :
4471 player->LevelSolved_CountingScore = player->score_final;
4472 player->LevelSolved_CountingHealth = player->health_final;
4477 static int time_count_steps;
4478 static int time, time_final;
4479 static int score, score_final;
4480 static int health, health_final;
4481 static int game_over_delay_1 = 0;
4482 static int game_over_delay_2 = 0;
4483 static int game_over_delay_3 = 0;
4484 int game_over_delay_value_1 = 50;
4485 int game_over_delay_value_2 = 25;
4486 int game_over_delay_value_3 = 50;
4488 if (!local_player->LevelSolved_GameWon)
4492 /* do not start end game actions before the player stops moving (to exit) */
4493 if (local_player->MovPos)
4496 local_player->LevelSolved_GameWon = TRUE;
4497 local_player->LevelSolved_SaveTape = tape.recording;
4498 local_player->LevelSolved_SaveScore = !tape.playing;
4502 LevelStats_incSolved(level_nr);
4504 SaveLevelSetup_SeriesInfo();
4507 if (tape.auto_play) /* tape might already be stopped here */
4508 tape.auto_play_level_solved = TRUE;
4512 game_over_delay_1 = 0;
4513 game_over_delay_2 = 0;
4514 game_over_delay_3 = game_over_delay_value_3;
4516 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4517 score = score_final = local_player->score_final;
4518 health = health_final = local_player->health_final;
4520 if (level.score[SC_TIME_BONUS] > 0)
4525 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4527 else if (game.no_time_limit && TimePlayed < 999)
4530 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4533 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4535 game_over_delay_1 = game_over_delay_value_1;
4537 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4540 score_final += health * level.score[SC_TIME_BONUS];
4542 game_over_delay_2 = game_over_delay_value_2;
4545 local_player->score_final = score_final;
4546 local_player->health_final = health_final;
4549 if (level_editor_test_game)
4552 score = score_final;
4554 local_player->LevelSolved_CountingTime = time;
4555 local_player->LevelSolved_CountingScore = score;
4557 game_panel_controls[GAME_PANEL_TIME].value = time;
4558 game_panel_controls[GAME_PANEL_SCORE].value = score;
4560 DisplayGameControlValues();
4563 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4565 if (ExitX >= 0 && ExitY >= 0) /* local player has left the level */
4567 /* close exit door after last player */
4568 if ((AllPlayersGone &&
4569 (Feld[ExitX][ExitY] == EL_EXIT_OPEN ||
4570 Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN ||
4571 Feld[ExitX][ExitY] == EL_STEEL_EXIT_OPEN)) ||
4572 Feld[ExitX][ExitY] == EL_EM_EXIT_OPEN ||
4573 Feld[ExitX][ExitY] == EL_EM_STEEL_EXIT_OPEN)
4575 int element = Feld[ExitX][ExitY];
4577 Feld[ExitX][ExitY] =
4578 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4579 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4580 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4581 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4582 EL_EM_STEEL_EXIT_CLOSING);
4584 PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
4587 /* player disappears */
4588 DrawLevelField(ExitX, ExitY);
4591 for (i = 0; i < MAX_PLAYERS; i++)
4593 struct PlayerInfo *player = &stored_player[i];
4595 if (player->present)
4597 RemovePlayer(player);
4599 /* player disappears */
4600 DrawLevelField(player->jx, player->jy);
4605 PlaySound(SND_GAME_WINNING);
4608 if (game_over_delay_1 > 0)
4610 game_over_delay_1--;
4615 if (time != time_final)
4617 int time_to_go = ABS(time_final - time);
4618 int time_count_dir = (time < time_final ? +1 : -1);
4620 if (time_to_go < time_count_steps)
4621 time_count_steps = 1;
4623 time += time_count_steps * time_count_dir;
4624 score += time_count_steps * level.score[SC_TIME_BONUS];
4626 local_player->LevelSolved_CountingTime = time;
4627 local_player->LevelSolved_CountingScore = score;
4629 game_panel_controls[GAME_PANEL_TIME].value = time;
4630 game_panel_controls[GAME_PANEL_SCORE].value = score;
4632 DisplayGameControlValues();
4634 if (time == time_final)
4635 StopSound(SND_GAME_LEVELTIME_BONUS);
4636 else if (setup.sound_loops)
4637 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4639 PlaySound(SND_GAME_LEVELTIME_BONUS);
4644 if (game_over_delay_2 > 0)
4646 game_over_delay_2--;
4651 if (health != health_final)
4653 int health_count_dir = (health < health_final ? +1 : -1);
4655 health += health_count_dir;
4656 score += level.score[SC_TIME_BONUS];
4658 local_player->LevelSolved_CountingHealth = health;
4659 local_player->LevelSolved_CountingScore = score;
4661 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4662 game_panel_controls[GAME_PANEL_SCORE].value = score;
4664 DisplayGameControlValues();
4666 if (health == health_final)
4667 StopSound(SND_GAME_LEVELTIME_BONUS);
4668 else if (setup.sound_loops)
4669 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4671 PlaySound(SND_GAME_LEVELTIME_BONUS);
4676 local_player->LevelSolved_PanelOff = TRUE;
4678 if (game_over_delay_3 > 0)
4680 game_over_delay_3--;
4691 boolean raise_level = FALSE;
4693 local_player->LevelSolved_GameEnd = TRUE;
4695 if (local_player->LevelSolved_SaveTape)
4697 /* make sure that request dialog to save tape does not open door again */
4698 if (!global.use_envelope_request)
4699 CloseDoor(DOOR_CLOSE_1);
4701 SaveTapeChecked(tape.level_nr); /* ask to save tape */
4704 /* if no tape is to be saved, close both doors simultaneously */
4705 CloseDoor(DOOR_CLOSE_ALL);
4707 if (level_editor_test_game)
4709 SetGameStatus(GAME_MODE_MAIN);
4716 if (!local_player->LevelSolved_SaveScore)
4718 SetGameStatus(GAME_MODE_MAIN);
4725 if (level_nr == leveldir_current->handicap_level)
4727 leveldir_current->handicap_level++;
4729 SaveLevelSetup_SeriesInfo();
4732 if (setup.increment_levels &&
4733 level_nr < leveldir_current->last_level)
4734 raise_level = TRUE; /* advance to next level */
4736 if ((hi_pos = NewHiScore()) >= 0)
4738 SetGameStatus(GAME_MODE_SCORES);
4740 DrawHallOfFame(hi_pos);
4750 SetGameStatus(GAME_MODE_MAIN);
4766 boolean one_score_entry_per_name = !program.many_scores_per_name;
4768 LoadScore(level_nr);
4770 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4771 local_player->score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4774 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4776 if (local_player->score_final > highscore[k].Score)
4778 /* player has made it to the hall of fame */
4780 if (k < MAX_SCORE_ENTRIES - 1)
4782 int m = MAX_SCORE_ENTRIES - 1;
4784 if (one_score_entry_per_name)
4786 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4787 if (strEqual(setup.player_name, highscore[l].Name))
4790 if (m == k) /* player's new highscore overwrites his old one */
4794 for (l = m; l > k; l--)
4796 strcpy(highscore[l].Name, highscore[l - 1].Name);
4797 highscore[l].Score = highscore[l - 1].Score;
4803 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4804 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4805 highscore[k].Score = local_player->score_final;
4810 else if (one_score_entry_per_name &&
4811 !strncmp(setup.player_name, highscore[k].Name,
4812 MAX_PLAYER_NAME_LEN))
4813 break; /* player already there with a higher score */
4817 SaveScore(level_nr);
4822 inline static int getElementMoveStepsizeExt(int x, int y, int direction)
4824 int element = Feld[x][y];
4825 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4826 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4827 int horiz_move = (dx != 0);
4828 int sign = (horiz_move ? dx : dy);
4829 int step = sign * element_info[element].move_stepsize;
4831 /* special values for move stepsize for spring and things on conveyor belt */
4834 if (CAN_FALL(element) &&
4835 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
4836 step = sign * MOVE_STEPSIZE_NORMAL / 2;
4837 else if (element == EL_SPRING)
4838 step = sign * MOVE_STEPSIZE_NORMAL * 2;
4844 inline static int getElementMoveStepsize(int x, int y)
4846 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
4849 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
4851 if (player->GfxAction != action || player->GfxDir != dir)
4853 player->GfxAction = action;
4854 player->GfxDir = dir;
4856 player->StepFrame = 0;
4860 static void ResetGfxFrame(int x, int y)
4862 // profiling showed that "autotest" spends 10~20% of its time in this function
4863 if (DrawingDeactivatedField())
4866 int element = Feld[x][y];
4867 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
4869 if (graphic_info[graphic].anim_global_sync)
4870 GfxFrame[x][y] = FrameCounter;
4871 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
4872 GfxFrame[x][y] = CustomValue[x][y];
4873 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
4874 GfxFrame[x][y] = element_info[element].collect_score;
4875 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
4876 GfxFrame[x][y] = ChangeDelay[x][y];
4879 static void ResetGfxAnimation(int x, int y)
4881 GfxAction[x][y] = ACTION_DEFAULT;
4882 GfxDir[x][y] = MovDir[x][y];
4885 ResetGfxFrame(x, y);
4888 static void ResetRandomAnimationValue(int x, int y)
4890 GfxRandom[x][y] = INIT_GFX_RANDOM();
4893 void InitMovingField(int x, int y, int direction)
4895 int element = Feld[x][y];
4896 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4897 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4900 boolean is_moving_before, is_moving_after;
4902 /* check if element was/is moving or being moved before/after mode change */
4903 is_moving_before = (WasJustMoving[x][y] != 0);
4904 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
4906 /* reset animation only for moving elements which change direction of moving
4907 or which just started or stopped moving
4908 (else CEs with property "can move" / "not moving" are reset each frame) */
4909 if (is_moving_before != is_moving_after ||
4910 direction != MovDir[x][y])
4911 ResetGfxAnimation(x, y);
4913 MovDir[x][y] = direction;
4914 GfxDir[x][y] = direction;
4916 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
4917 direction == MV_DOWN && CAN_FALL(element) ?
4918 ACTION_FALLING : ACTION_MOVING);
4920 /* this is needed for CEs with property "can move" / "not moving" */
4922 if (is_moving_after)
4924 if (Feld[newx][newy] == EL_EMPTY)
4925 Feld[newx][newy] = EL_BLOCKED;
4927 MovDir[newx][newy] = MovDir[x][y];
4929 CustomValue[newx][newy] = CustomValue[x][y];
4931 GfxFrame[newx][newy] = GfxFrame[x][y];
4932 GfxRandom[newx][newy] = GfxRandom[x][y];
4933 GfxAction[newx][newy] = GfxAction[x][y];
4934 GfxDir[newx][newy] = GfxDir[x][y];
4938 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
4940 int direction = MovDir[x][y];
4941 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
4942 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
4948 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
4950 int oldx = x, oldy = y;
4951 int direction = MovDir[x][y];
4953 if (direction == MV_LEFT)
4955 else if (direction == MV_RIGHT)
4957 else if (direction == MV_UP)
4959 else if (direction == MV_DOWN)
4962 *comes_from_x = oldx;
4963 *comes_from_y = oldy;
4966 int MovingOrBlocked2Element(int x, int y)
4968 int element = Feld[x][y];
4970 if (element == EL_BLOCKED)
4974 Blocked2Moving(x, y, &oldx, &oldy);
4975 return Feld[oldx][oldy];
4981 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
4983 /* like MovingOrBlocked2Element(), but if element is moving
4984 and (x,y) is the field the moving element is just leaving,
4985 return EL_BLOCKED instead of the element value */
4986 int element = Feld[x][y];
4988 if (IS_MOVING(x, y))
4990 if (element == EL_BLOCKED)
4994 Blocked2Moving(x, y, &oldx, &oldy);
4995 return Feld[oldx][oldy];
5004 static void RemoveField(int x, int y)
5006 Feld[x][y] = EL_EMPTY;
5012 CustomValue[x][y] = 0;
5015 ChangeDelay[x][y] = 0;
5016 ChangePage[x][y] = -1;
5017 Pushed[x][y] = FALSE;
5019 GfxElement[x][y] = EL_UNDEFINED;
5020 GfxAction[x][y] = ACTION_DEFAULT;
5021 GfxDir[x][y] = MV_NONE;
5024 void RemoveMovingField(int x, int y)
5026 int oldx = x, oldy = y, newx = x, newy = y;
5027 int element = Feld[x][y];
5028 int next_element = EL_UNDEFINED;
5030 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5033 if (IS_MOVING(x, y))
5035 Moving2Blocked(x, y, &newx, &newy);
5037 if (Feld[newx][newy] != EL_BLOCKED)
5039 /* element is moving, but target field is not free (blocked), but
5040 already occupied by something different (example: acid pool);
5041 in this case, only remove the moving field, but not the target */
5043 RemoveField(oldx, oldy);
5045 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5047 TEST_DrawLevelField(oldx, oldy);
5052 else if (element == EL_BLOCKED)
5054 Blocked2Moving(x, y, &oldx, &oldy);
5055 if (!IS_MOVING(oldx, oldy))
5059 if (element == EL_BLOCKED &&
5060 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5061 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5062 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5063 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5064 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5065 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5066 next_element = get_next_element(Feld[oldx][oldy]);
5068 RemoveField(oldx, oldy);
5069 RemoveField(newx, newy);
5071 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5073 if (next_element != EL_UNDEFINED)
5074 Feld[oldx][oldy] = next_element;
5076 TEST_DrawLevelField(oldx, oldy);
5077 TEST_DrawLevelField(newx, newy);
5080 void DrawDynamite(int x, int y)
5082 int sx = SCREENX(x), sy = SCREENY(y);
5083 int graphic = el2img(Feld[x][y]);
5086 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5089 if (IS_WALKABLE_INSIDE(Back[x][y]))
5093 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5094 else if (Store[x][y])
5095 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5097 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5099 if (Back[x][y] || Store[x][y])
5100 DrawGraphicThruMask(sx, sy, graphic, frame);
5102 DrawGraphic(sx, sy, graphic, frame);
5105 void CheckDynamite(int x, int y)
5107 if (MovDelay[x][y] != 0) /* dynamite is still waiting to explode */
5111 if (MovDelay[x][y] != 0)
5114 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5120 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5125 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5127 boolean num_checked_players = 0;
5130 for (i = 0; i < MAX_PLAYERS; i++)
5132 if (stored_player[i].active)
5134 int sx = stored_player[i].jx;
5135 int sy = stored_player[i].jy;
5137 if (num_checked_players == 0)
5144 *sx1 = MIN(*sx1, sx);
5145 *sy1 = MIN(*sy1, sy);
5146 *sx2 = MAX(*sx2, sx);
5147 *sy2 = MAX(*sy2, sy);
5150 num_checked_players++;
5155 static boolean checkIfAllPlayersFitToScreen_RND()
5157 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5159 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5161 return (sx2 - sx1 < SCR_FIELDX &&
5162 sy2 - sy1 < SCR_FIELDY);
5165 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5167 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5169 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5171 *sx = (sx1 + sx2) / 2;
5172 *sy = (sy1 + sy2) / 2;
5175 void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5176 boolean center_screen, boolean quick_relocation)
5178 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5179 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5180 boolean no_delay = (tape.warp_forward);
5181 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5182 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5183 int new_scroll_x, new_scroll_y;
5185 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5187 /* case 1: quick relocation inside visible screen (without scrolling) */
5194 if (!level.shifted_relocation || center_screen)
5196 /* relocation _with_ centering of screen */
5198 new_scroll_x = SCROLL_POSITION_X(x);
5199 new_scroll_y = SCROLL_POSITION_Y(y);
5203 /* relocation _without_ centering of screen */
5205 int center_scroll_x = SCROLL_POSITION_X(old_x);
5206 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5207 int offset_x = x + (scroll_x - center_scroll_x);
5208 int offset_y = y + (scroll_y - center_scroll_y);
5210 /* for new screen position, apply previous offset to center position */
5211 new_scroll_x = SCROLL_POSITION_X(offset_x);
5212 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5215 if (quick_relocation)
5217 /* case 2: quick relocation (redraw without visible scrolling) */
5219 scroll_x = new_scroll_x;
5220 scroll_y = new_scroll_y;
5227 /* case 3: visible relocation (with scrolling to new position) */
5229 ScrollScreen(NULL, SCROLL_GO_ON); /* scroll last frame to full tile */
5231 SetVideoFrameDelay(wait_delay_value);
5233 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5236 int fx = FX, fy = FY;
5238 dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5239 dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5241 if (dx == 0 && dy == 0) /* no scrolling needed at all */
5247 fx += dx * TILEX / 2;
5248 fy += dy * TILEY / 2;
5250 ScrollLevel(dx, dy);
5253 /* scroll in two steps of half tile size to make things smoother */
5254 BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
5256 /* scroll second step to align at full tile size */
5257 BlitScreenToBitmap(window);
5263 SetVideoFrameDelay(frame_delay_value_old);
5266 void RelocatePlayer(int jx, int jy, int el_player_raw)
5268 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5269 int player_nr = GET_PLAYER_NR(el_player);
5270 struct PlayerInfo *player = &stored_player[player_nr];
5271 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5272 boolean no_delay = (tape.warp_forward);
5273 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5274 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5275 int old_jx = player->jx;
5276 int old_jy = player->jy;
5277 int old_element = Feld[old_jx][old_jy];
5278 int element = Feld[jx][jy];
5279 boolean player_relocated = (old_jx != jx || old_jy != jy);
5281 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5282 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5283 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5284 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5285 int leave_side_horiz = move_dir_horiz;
5286 int leave_side_vert = move_dir_vert;
5287 int enter_side = enter_side_horiz | enter_side_vert;
5288 int leave_side = leave_side_horiz | leave_side_vert;
5290 if (player->GameOver) /* do not reanimate dead player */
5293 if (!player_relocated) /* no need to relocate the player */
5296 if (IS_PLAYER(jx, jy)) /* player already placed at new position */
5298 RemoveField(jx, jy); /* temporarily remove newly placed player */
5299 DrawLevelField(jx, jy);
5302 if (player->present)
5304 while (player->MovPos)
5306 ScrollPlayer(player, SCROLL_GO_ON);
5307 ScrollScreen(NULL, SCROLL_GO_ON);
5309 AdvanceFrameAndPlayerCounters(player->index_nr);
5313 BackToFront_WithFrameDelay(wait_delay_value);
5316 DrawPlayer(player); /* needed here only to cleanup last field */
5317 DrawLevelField(player->jx, player->jy); /* remove player graphic */
5319 player->is_moving = FALSE;
5322 if (IS_CUSTOM_ELEMENT(old_element))
5323 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5325 player->index_bit, leave_side);
5327 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5329 player->index_bit, leave_side);
5331 Feld[jx][jy] = el_player;
5332 InitPlayerField(jx, jy, el_player, TRUE);
5334 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5335 possible that the relocation target field did not contain a player element,
5336 but a walkable element, to which the new player was relocated -- in this
5337 case, restore that (already initialized!) element on the player field */
5338 if (!ELEM_IS_PLAYER(element)) /* player may be set on walkable element */
5340 Feld[jx][jy] = element; /* restore previously existing element */
5343 /* only visually relocate centered player */
5344 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5345 FALSE, level.instant_relocation);
5347 TestIfPlayerTouchesBadThing(jx, jy);
5348 TestIfPlayerTouchesCustomElement(jx, jy);
5350 if (IS_CUSTOM_ELEMENT(element))
5351 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5352 player->index_bit, enter_side);
5354 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5355 player->index_bit, enter_side);
5357 if (player->is_switching)
5359 /* ensure that relocation while still switching an element does not cause
5360 a new element to be treated as also switched directly after relocation
5361 (this is important for teleporter switches that teleport the player to
5362 a place where another teleporter switch is in the same direction, which
5363 would then incorrectly be treated as immediately switched before the
5364 direction key that caused the switch was released) */
5366 player->switch_x += jx - old_jx;
5367 player->switch_y += jy - old_jy;
5371 void Explode(int ex, int ey, int phase, int mode)
5377 /* !!! eliminate this variable !!! */
5378 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5380 if (game.explosions_delayed)
5382 ExplodeField[ex][ey] = mode;
5386 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
5388 int center_element = Feld[ex][ey];
5389 int artwork_element, explosion_element; /* set these values later */
5391 /* remove things displayed in background while burning dynamite */
5392 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5395 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5397 /* put moving element to center field (and let it explode there) */
5398 center_element = MovingOrBlocked2Element(ex, ey);
5399 RemoveMovingField(ex, ey);
5400 Feld[ex][ey] = center_element;
5403 /* now "center_element" is finally determined -- set related values now */
5404 artwork_element = center_element; /* for custom player artwork */
5405 explosion_element = center_element; /* for custom player artwork */
5407 if (IS_PLAYER(ex, ey))
5409 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5411 artwork_element = stored_player[player_nr].artwork_element;
5413 if (level.use_explosion_element[player_nr])
5415 explosion_element = level.explosion_element[player_nr];
5416 artwork_element = explosion_element;
5420 if (mode == EX_TYPE_NORMAL ||
5421 mode == EX_TYPE_CENTER ||
5422 mode == EX_TYPE_CROSS)
5423 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5425 last_phase = element_info[explosion_element].explosion_delay + 1;
5427 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5429 int xx = x - ex + 1;
5430 int yy = y - ey + 1;
5433 if (!IN_LEV_FIELD(x, y) ||
5434 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5435 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5438 element = Feld[x][y];
5440 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5442 element = MovingOrBlocked2Element(x, y);
5444 if (!IS_EXPLOSION_PROOF(element))
5445 RemoveMovingField(x, y);
5448 /* indestructible elements can only explode in center (but not flames) */
5449 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5450 mode == EX_TYPE_BORDER)) ||
5451 element == EL_FLAMES)
5454 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5455 behaviour, for example when touching a yamyam that explodes to rocks
5456 with active deadly shield, a rock is created under the player !!! */
5457 /* (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8) */
5459 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5460 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5461 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5463 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5466 if (IS_ACTIVE_BOMB(element))
5468 /* re-activate things under the bomb like gate or penguin */
5469 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5476 /* save walkable background elements while explosion on same tile */
5477 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5478 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5479 Back[x][y] = element;
5481 /* ignite explodable elements reached by other explosion */
5482 if (element == EL_EXPLOSION)
5483 element = Store2[x][y];
5485 if (AmoebaNr[x][y] &&
5486 (element == EL_AMOEBA_FULL ||
5487 element == EL_BD_AMOEBA ||
5488 element == EL_AMOEBA_GROWING))
5490 AmoebaCnt[AmoebaNr[x][y]]--;
5491 AmoebaCnt2[AmoebaNr[x][y]]--;
5496 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5498 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5500 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5502 if (PLAYERINFO(ex, ey)->use_murphy)
5503 Store[x][y] = EL_EMPTY;
5506 /* !!! check this case -- currently needed for rnd_rado_negundo_v,
5507 !!! levels 015 018 019 020 021 022 023 026 027 028 !!! */
5508 else if (ELEM_IS_PLAYER(center_element))
5509 Store[x][y] = EL_EMPTY;
5510 else if (center_element == EL_YAMYAM)
5511 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5512 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5513 Store[x][y] = element_info[center_element].content.e[xx][yy];
5515 /* needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5516 (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5517 otherwise) -- FIX THIS !!! */
5518 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5519 Store[x][y] = element_info[element].content.e[1][1];
5521 else if (!CAN_EXPLODE(element))
5522 Store[x][y] = element_info[element].content.e[1][1];
5525 Store[x][y] = EL_EMPTY;
5527 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5528 center_element == EL_AMOEBA_TO_DIAMOND)
5529 Store2[x][y] = element;
5531 Feld[x][y] = EL_EXPLOSION;
5532 GfxElement[x][y] = artwork_element;
5534 ExplodePhase[x][y] = 1;
5535 ExplodeDelay[x][y] = last_phase;
5540 if (center_element == EL_YAMYAM)
5541 game.yamyam_content_nr =
5542 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5554 GfxFrame[x][y] = 0; /* restart explosion animation */
5556 last_phase = ExplodeDelay[x][y];
5558 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5560 /* this can happen if the player leaves an explosion just in time */
5561 if (GfxElement[x][y] == EL_UNDEFINED)
5562 GfxElement[x][y] = EL_EMPTY;
5564 border_element = Store2[x][y];
5565 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5566 border_element = StorePlayer[x][y];
5568 if (phase == element_info[border_element].ignition_delay ||
5569 phase == last_phase)
5571 boolean border_explosion = FALSE;
5573 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5574 !PLAYER_EXPLOSION_PROTECTED(x, y))
5576 KillPlayerUnlessExplosionProtected(x, y);
5577 border_explosion = TRUE;
5579 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5581 Feld[x][y] = Store2[x][y];
5584 border_explosion = TRUE;
5586 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5588 AmoebeUmwandeln(x, y);
5590 border_explosion = TRUE;
5593 /* if an element just explodes due to another explosion (chain-reaction),
5594 do not immediately end the new explosion when it was the last frame of
5595 the explosion (as it would be done in the following "if"-statement!) */
5596 if (border_explosion && phase == last_phase)
5600 if (phase == last_phase)
5604 element = Feld[x][y] = Store[x][y];
5605 Store[x][y] = Store2[x][y] = 0;
5606 GfxElement[x][y] = EL_UNDEFINED;
5608 /* player can escape from explosions and might therefore be still alive */
5609 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5610 element <= EL_PLAYER_IS_EXPLODING_4)
5612 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5613 int explosion_element = EL_PLAYER_1 + player_nr;
5614 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5615 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5617 if (level.use_explosion_element[player_nr])
5618 explosion_element = level.explosion_element[player_nr];
5620 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5621 element_info[explosion_element].content.e[xx][yy]);
5624 /* restore probably existing indestructible background element */
5625 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5626 element = Feld[x][y] = Back[x][y];
5629 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5630 GfxDir[x][y] = MV_NONE;
5631 ChangeDelay[x][y] = 0;
5632 ChangePage[x][y] = -1;
5634 CustomValue[x][y] = 0;
5636 InitField_WithBug2(x, y, FALSE);
5638 TEST_DrawLevelField(x, y);
5640 TestIfElementTouchesCustomElement(x, y);
5642 if (GFX_CRUMBLED(element))
5643 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5645 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5646 StorePlayer[x][y] = 0;
5648 if (ELEM_IS_PLAYER(element))
5649 RelocatePlayer(x, y, element);
5651 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5653 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5654 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5657 TEST_DrawLevelFieldCrumbled(x, y);
5659 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5661 DrawLevelElement(x, y, Back[x][y]);
5662 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5664 else if (IS_WALKABLE_UNDER(Back[x][y]))
5666 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5667 DrawLevelElementThruMask(x, y, Back[x][y]);
5669 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5670 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5674 void DynaExplode(int ex, int ey)
5677 int dynabomb_element = Feld[ex][ey];
5678 int dynabomb_size = 1;
5679 boolean dynabomb_xl = FALSE;
5680 struct PlayerInfo *player;
5681 static int xy[4][2] =
5689 if (IS_ACTIVE_BOMB(dynabomb_element))
5691 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5692 dynabomb_size = player->dynabomb_size;
5693 dynabomb_xl = player->dynabomb_xl;
5694 player->dynabombs_left++;
5697 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5699 for (i = 0; i < NUM_DIRECTIONS; i++)
5701 for (j = 1; j <= dynabomb_size; j++)
5703 int x = ex + j * xy[i][0];
5704 int y = ey + j * xy[i][1];
5707 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5710 element = Feld[x][y];
5712 /* do not restart explosions of fields with active bombs */
5713 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5716 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5718 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5719 !IS_DIGGABLE(element) && !dynabomb_xl)
5725 void Bang(int x, int y)
5727 int element = MovingOrBlocked2Element(x, y);
5728 int explosion_type = EX_TYPE_NORMAL;
5730 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5732 struct PlayerInfo *player = PLAYERINFO(x, y);
5734 element = Feld[x][y] = player->initial_element;
5736 if (level.use_explosion_element[player->index_nr])
5738 int explosion_element = level.explosion_element[player->index_nr];
5740 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5741 explosion_type = EX_TYPE_CROSS;
5742 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5743 explosion_type = EX_TYPE_CENTER;
5751 case EL_BD_BUTTERFLY:
5754 case EL_DARK_YAMYAM:
5758 RaiseScoreElement(element);
5761 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5762 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5763 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5764 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5765 case EL_DYNABOMB_INCREASE_NUMBER:
5766 case EL_DYNABOMB_INCREASE_SIZE:
5767 case EL_DYNABOMB_INCREASE_POWER:
5768 explosion_type = EX_TYPE_DYNA;
5771 case EL_DC_LANDMINE:
5772 explosion_type = EX_TYPE_CENTER;
5777 case EL_LAMP_ACTIVE:
5778 case EL_AMOEBA_TO_DIAMOND:
5779 if (!IS_PLAYER(x, y)) /* penguin and player may be at same field */
5780 explosion_type = EX_TYPE_CENTER;
5784 if (element_info[element].explosion_type == EXPLODES_CROSS)
5785 explosion_type = EX_TYPE_CROSS;
5786 else if (element_info[element].explosion_type == EXPLODES_1X1)
5787 explosion_type = EX_TYPE_CENTER;
5791 if (explosion_type == EX_TYPE_DYNA)
5794 Explode(x, y, EX_PHASE_START, explosion_type);
5796 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5799 void SplashAcid(int x, int y)
5801 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5802 (!IN_LEV_FIELD(x - 1, y - 2) ||
5803 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5804 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5806 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5807 (!IN_LEV_FIELD(x + 1, y - 2) ||
5808 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5809 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5811 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5814 static void InitBeltMovement()
5816 static int belt_base_element[4] =
5818 EL_CONVEYOR_BELT_1_LEFT,
5819 EL_CONVEYOR_BELT_2_LEFT,
5820 EL_CONVEYOR_BELT_3_LEFT,
5821 EL_CONVEYOR_BELT_4_LEFT
5823 static int belt_base_active_element[4] =
5825 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5826 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5827 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5828 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5833 /* set frame order for belt animation graphic according to belt direction */
5834 for (i = 0; i < NUM_BELTS; i++)
5838 for (j = 0; j < NUM_BELT_PARTS; j++)
5840 int element = belt_base_active_element[belt_nr] + j;
5841 int graphic_1 = el2img(element);
5842 int graphic_2 = el2panelimg(element);
5844 if (game.belt_dir[i] == MV_LEFT)
5846 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5847 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5851 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5852 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5857 SCAN_PLAYFIELD(x, y)
5859 int element = Feld[x][y];
5861 for (i = 0; i < NUM_BELTS; i++)
5863 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
5865 int e_belt_nr = getBeltNrFromBeltElement(element);
5868 if (e_belt_nr == belt_nr)
5870 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
5872 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
5879 static void ToggleBeltSwitch(int x, int y)
5881 static int belt_base_element[4] =
5883 EL_CONVEYOR_BELT_1_LEFT,
5884 EL_CONVEYOR_BELT_2_LEFT,
5885 EL_CONVEYOR_BELT_3_LEFT,
5886 EL_CONVEYOR_BELT_4_LEFT
5888 static int belt_base_active_element[4] =
5890 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5891 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5892 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5893 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5895 static int belt_base_switch_element[4] =
5897 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
5898 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
5899 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
5900 EL_CONVEYOR_BELT_4_SWITCH_LEFT
5902 static int belt_move_dir[4] =
5910 int element = Feld[x][y];
5911 int belt_nr = getBeltNrFromBeltSwitchElement(element);
5912 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
5913 int belt_dir = belt_move_dir[belt_dir_nr];
5916 if (!IS_BELT_SWITCH(element))
5919 game.belt_dir_nr[belt_nr] = belt_dir_nr;
5920 game.belt_dir[belt_nr] = belt_dir;
5922 if (belt_dir_nr == 3)
5925 /* set frame order for belt animation graphic according to belt direction */
5926 for (i = 0; i < NUM_BELT_PARTS; i++)
5928 int element = belt_base_active_element[belt_nr] + i;
5929 int graphic_1 = el2img(element);
5930 int graphic_2 = el2panelimg(element);
5932 if (belt_dir == MV_LEFT)
5934 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5935 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5939 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5940 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5944 SCAN_PLAYFIELD(xx, yy)
5946 int element = Feld[xx][yy];
5948 if (IS_BELT_SWITCH(element))
5950 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
5952 if (e_belt_nr == belt_nr)
5954 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
5955 TEST_DrawLevelField(xx, yy);
5958 else if (IS_BELT(element) && belt_dir != MV_NONE)
5960 int e_belt_nr = getBeltNrFromBeltElement(element);
5962 if (e_belt_nr == belt_nr)
5964 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
5966 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
5967 TEST_DrawLevelField(xx, yy);
5970 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
5972 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
5974 if (e_belt_nr == belt_nr)
5976 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
5978 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
5979 TEST_DrawLevelField(xx, yy);
5985 static void ToggleSwitchgateSwitch(int x, int y)
5989 game.switchgate_pos = !game.switchgate_pos;
5991 SCAN_PLAYFIELD(xx, yy)
5993 int element = Feld[xx][yy];
5995 if (element == EL_SWITCHGATE_SWITCH_UP)
5997 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
5998 TEST_DrawLevelField(xx, yy);
6000 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6002 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6003 TEST_DrawLevelField(xx, yy);
6005 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6007 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6008 TEST_DrawLevelField(xx, yy);
6010 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6012 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6013 TEST_DrawLevelField(xx, yy);
6015 else if (element == EL_SWITCHGATE_OPEN ||
6016 element == EL_SWITCHGATE_OPENING)
6018 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6020 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6022 else if (element == EL_SWITCHGATE_CLOSED ||
6023 element == EL_SWITCHGATE_CLOSING)
6025 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6027 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6032 static int getInvisibleActiveFromInvisibleElement(int element)
6034 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6035 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6036 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6040 static int getInvisibleFromInvisibleActiveElement(int element)
6042 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6043 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6044 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6048 static void RedrawAllLightSwitchesAndInvisibleElements()
6052 SCAN_PLAYFIELD(x, y)
6054 int element = Feld[x][y];
6056 if (element == EL_LIGHT_SWITCH &&
6057 game.light_time_left > 0)
6059 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6060 TEST_DrawLevelField(x, y);
6062 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6063 game.light_time_left == 0)
6065 Feld[x][y] = EL_LIGHT_SWITCH;
6066 TEST_DrawLevelField(x, y);
6068 else if (element == EL_EMC_DRIPPER &&
6069 game.light_time_left > 0)
6071 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6072 TEST_DrawLevelField(x, y);
6074 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6075 game.light_time_left == 0)
6077 Feld[x][y] = EL_EMC_DRIPPER;
6078 TEST_DrawLevelField(x, y);
6080 else if (element == EL_INVISIBLE_STEELWALL ||
6081 element == EL_INVISIBLE_WALL ||
6082 element == EL_INVISIBLE_SAND)
6084 if (game.light_time_left > 0)
6085 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6087 TEST_DrawLevelField(x, y);
6089 /* uncrumble neighbour fields, if needed */
6090 if (element == EL_INVISIBLE_SAND)
6091 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6093 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6094 element == EL_INVISIBLE_WALL_ACTIVE ||
6095 element == EL_INVISIBLE_SAND_ACTIVE)
6097 if (game.light_time_left == 0)
6098 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6100 TEST_DrawLevelField(x, y);
6102 /* re-crumble neighbour fields, if needed */
6103 if (element == EL_INVISIBLE_SAND)
6104 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6109 static void RedrawAllInvisibleElementsForLenses()
6113 SCAN_PLAYFIELD(x, y)
6115 int element = Feld[x][y];
6117 if (element == EL_EMC_DRIPPER &&
6118 game.lenses_time_left > 0)
6120 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6121 TEST_DrawLevelField(x, y);
6123 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6124 game.lenses_time_left == 0)
6126 Feld[x][y] = EL_EMC_DRIPPER;
6127 TEST_DrawLevelField(x, y);
6129 else if (element == EL_INVISIBLE_STEELWALL ||
6130 element == EL_INVISIBLE_WALL ||
6131 element == EL_INVISIBLE_SAND)
6133 if (game.lenses_time_left > 0)
6134 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6136 TEST_DrawLevelField(x, y);
6138 /* uncrumble neighbour fields, if needed */
6139 if (element == EL_INVISIBLE_SAND)
6140 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6142 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6143 element == EL_INVISIBLE_WALL_ACTIVE ||
6144 element == EL_INVISIBLE_SAND_ACTIVE)
6146 if (game.lenses_time_left == 0)
6147 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6149 TEST_DrawLevelField(x, y);
6151 /* re-crumble neighbour fields, if needed */
6152 if (element == EL_INVISIBLE_SAND)
6153 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6158 static void RedrawAllInvisibleElementsForMagnifier()
6162 SCAN_PLAYFIELD(x, y)
6164 int element = Feld[x][y];
6166 if (element == EL_EMC_FAKE_GRASS &&
6167 game.magnify_time_left > 0)
6169 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6170 TEST_DrawLevelField(x, y);
6172 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6173 game.magnify_time_left == 0)
6175 Feld[x][y] = EL_EMC_FAKE_GRASS;
6176 TEST_DrawLevelField(x, y);
6178 else if (IS_GATE_GRAY(element) &&
6179 game.magnify_time_left > 0)
6181 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6182 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6183 IS_EM_GATE_GRAY(element) ?
6184 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6185 IS_EMC_GATE_GRAY(element) ?
6186 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6187 IS_DC_GATE_GRAY(element) ?
6188 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6190 TEST_DrawLevelField(x, y);
6192 else if (IS_GATE_GRAY_ACTIVE(element) &&
6193 game.magnify_time_left == 0)
6195 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6196 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6197 IS_EM_GATE_GRAY_ACTIVE(element) ?
6198 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6199 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6200 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6201 IS_DC_GATE_GRAY_ACTIVE(element) ?
6202 EL_DC_GATE_WHITE_GRAY :
6204 TEST_DrawLevelField(x, y);
6209 static void ToggleLightSwitch(int x, int y)
6211 int element = Feld[x][y];
6213 game.light_time_left =
6214 (element == EL_LIGHT_SWITCH ?
6215 level.time_light * FRAMES_PER_SECOND : 0);
6217 RedrawAllLightSwitchesAndInvisibleElements();
6220 static void ActivateTimegateSwitch(int x, int y)
6224 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6226 SCAN_PLAYFIELD(xx, yy)
6228 int element = Feld[xx][yy];
6230 if (element == EL_TIMEGATE_CLOSED ||
6231 element == EL_TIMEGATE_CLOSING)
6233 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6234 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6238 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6240 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6241 TEST_DrawLevelField(xx, yy);
6247 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6248 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6251 void Impact(int x, int y)
6253 boolean last_line = (y == lev_fieldy - 1);
6254 boolean object_hit = FALSE;
6255 boolean impact = (last_line || object_hit);
6256 int element = Feld[x][y];
6257 int smashed = EL_STEELWALL;
6259 if (!last_line) /* check if element below was hit */
6261 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6264 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6265 MovDir[x][y + 1] != MV_DOWN ||
6266 MovPos[x][y + 1] <= TILEY / 2));
6268 /* do not smash moving elements that left the smashed field in time */
6269 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6270 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6273 #if USE_QUICKSAND_IMPACT_BUGFIX
6274 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6276 RemoveMovingField(x, y + 1);
6277 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6278 Feld[x][y + 2] = EL_ROCK;
6279 TEST_DrawLevelField(x, y + 2);
6284 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6286 RemoveMovingField(x, y + 1);
6287 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6288 Feld[x][y + 2] = EL_ROCK;
6289 TEST_DrawLevelField(x, y + 2);
6296 smashed = MovingOrBlocked2Element(x, y + 1);
6298 impact = (last_line || object_hit);
6301 if (!last_line && smashed == EL_ACID) /* element falls into acid */
6303 SplashAcid(x, y + 1);
6307 /* !!! not sufficient for all cases -- see EL_PEARL below !!! */
6308 /* only reset graphic animation if graphic really changes after impact */
6310 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6312 ResetGfxAnimation(x, y);
6313 TEST_DrawLevelField(x, y);
6316 if (impact && CAN_EXPLODE_IMPACT(element))
6321 else if (impact && element == EL_PEARL &&
6322 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6324 ResetGfxAnimation(x, y);
6326 Feld[x][y] = EL_PEARL_BREAKING;
6327 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6330 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6332 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6337 if (impact && element == EL_AMOEBA_DROP)
6339 if (object_hit && IS_PLAYER(x, y + 1))
6340 KillPlayerUnlessEnemyProtected(x, y + 1);
6341 else if (object_hit && smashed == EL_PENGUIN)
6345 Feld[x][y] = EL_AMOEBA_GROWING;
6346 Store[x][y] = EL_AMOEBA_WET;
6348 ResetRandomAnimationValue(x, y);
6353 if (object_hit) /* check which object was hit */
6355 if ((CAN_PASS_MAGIC_WALL(element) &&
6356 (smashed == EL_MAGIC_WALL ||
6357 smashed == EL_BD_MAGIC_WALL)) ||
6358 (CAN_PASS_DC_MAGIC_WALL(element) &&
6359 smashed == EL_DC_MAGIC_WALL))
6362 int activated_magic_wall =
6363 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6364 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6365 EL_DC_MAGIC_WALL_ACTIVE);
6367 /* activate magic wall / mill */
6368 SCAN_PLAYFIELD(xx, yy)
6370 if (Feld[xx][yy] == smashed)
6371 Feld[xx][yy] = activated_magic_wall;
6374 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6375 game.magic_wall_active = TRUE;
6377 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6378 SND_MAGIC_WALL_ACTIVATING :
6379 smashed == EL_BD_MAGIC_WALL ?
6380 SND_BD_MAGIC_WALL_ACTIVATING :
6381 SND_DC_MAGIC_WALL_ACTIVATING));
6384 if (IS_PLAYER(x, y + 1))
6386 if (CAN_SMASH_PLAYER(element))
6388 KillPlayerUnlessEnemyProtected(x, y + 1);
6392 else if (smashed == EL_PENGUIN)
6394 if (CAN_SMASH_PLAYER(element))
6400 else if (element == EL_BD_DIAMOND)
6402 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6408 else if (((element == EL_SP_INFOTRON ||
6409 element == EL_SP_ZONK) &&
6410 (smashed == EL_SP_SNIKSNAK ||
6411 smashed == EL_SP_ELECTRON ||
6412 smashed == EL_SP_DISK_ORANGE)) ||
6413 (element == EL_SP_INFOTRON &&
6414 smashed == EL_SP_DISK_YELLOW))
6419 else if (CAN_SMASH_EVERYTHING(element))
6421 if (IS_CLASSIC_ENEMY(smashed) ||
6422 CAN_EXPLODE_SMASHED(smashed))
6427 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6429 if (smashed == EL_LAMP ||
6430 smashed == EL_LAMP_ACTIVE)
6435 else if (smashed == EL_NUT)
6437 Feld[x][y + 1] = EL_NUT_BREAKING;
6438 PlayLevelSound(x, y, SND_NUT_BREAKING);
6439 RaiseScoreElement(EL_NUT);
6442 else if (smashed == EL_PEARL)
6444 ResetGfxAnimation(x, y);
6446 Feld[x][y + 1] = EL_PEARL_BREAKING;
6447 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6450 else if (smashed == EL_DIAMOND)
6452 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6453 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6456 else if (IS_BELT_SWITCH(smashed))
6458 ToggleBeltSwitch(x, y + 1);
6460 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6461 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6462 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6463 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6465 ToggleSwitchgateSwitch(x, y + 1);
6467 else if (smashed == EL_LIGHT_SWITCH ||
6468 smashed == EL_LIGHT_SWITCH_ACTIVE)
6470 ToggleLightSwitch(x, y + 1);
6474 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6476 CheckElementChangeBySide(x, y + 1, smashed, element,
6477 CE_SWITCHED, CH_SIDE_TOP);
6478 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6484 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6489 /* play sound of magic wall / mill */
6491 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6492 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6493 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6495 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6496 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6497 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6498 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6499 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6500 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6505 /* play sound of object that hits the ground */
6506 if (last_line || object_hit)
6507 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6510 inline static void TurnRoundExt(int x, int y)
6522 { 0, 0 }, { 0, 0 }, { 0, 0 },
6527 int left, right, back;
6531 { MV_DOWN, MV_UP, MV_RIGHT },
6532 { MV_UP, MV_DOWN, MV_LEFT },
6534 { MV_LEFT, MV_RIGHT, MV_DOWN },
6538 { MV_RIGHT, MV_LEFT, MV_UP }
6541 int element = Feld[x][y];
6542 int move_pattern = element_info[element].move_pattern;
6544 int old_move_dir = MovDir[x][y];
6545 int left_dir = turn[old_move_dir].left;
6546 int right_dir = turn[old_move_dir].right;
6547 int back_dir = turn[old_move_dir].back;
6549 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6550 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6551 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6552 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6554 int left_x = x + left_dx, left_y = y + left_dy;
6555 int right_x = x + right_dx, right_y = y + right_dy;
6556 int move_x = x + move_dx, move_y = y + move_dy;
6560 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6562 TestIfBadThingTouchesOtherBadThing(x, y);
6564 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6565 MovDir[x][y] = right_dir;
6566 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6567 MovDir[x][y] = left_dir;
6569 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6571 else if (element == EL_BD_BUTTERFLY) /* && MovDir[x][y] == left_dir) */
6574 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6576 TestIfBadThingTouchesOtherBadThing(x, y);
6578 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6579 MovDir[x][y] = left_dir;
6580 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6581 MovDir[x][y] = right_dir;
6583 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6585 else if (element == EL_BD_FIREFLY) /* && MovDir[x][y] == right_dir) */
6588 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6590 TestIfBadThingTouchesOtherBadThing(x, y);
6592 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6593 MovDir[x][y] = left_dir;
6594 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6595 MovDir[x][y] = right_dir;
6597 if (MovDir[x][y] != old_move_dir)
6600 else if (element == EL_YAMYAM)
6602 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6603 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6605 if (can_turn_left && can_turn_right)
6606 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6607 else if (can_turn_left)
6608 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6609 else if (can_turn_right)
6610 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6612 MovDir[x][y] = back_dir;
6614 MovDelay[x][y] = 16 + 16 * RND(3);
6616 else if (element == EL_DARK_YAMYAM)
6618 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6620 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6623 if (can_turn_left && can_turn_right)
6624 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6625 else if (can_turn_left)
6626 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6627 else if (can_turn_right)
6628 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6630 MovDir[x][y] = back_dir;
6632 MovDelay[x][y] = 16 + 16 * RND(3);
6634 else if (element == EL_PACMAN)
6636 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6637 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6639 if (can_turn_left && can_turn_right)
6640 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6641 else if (can_turn_left)
6642 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6643 else if (can_turn_right)
6644 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6646 MovDir[x][y] = back_dir;
6648 MovDelay[x][y] = 6 + RND(40);
6650 else if (element == EL_PIG)
6652 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6653 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6654 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6655 boolean should_turn_left, should_turn_right, should_move_on;
6657 int rnd = RND(rnd_value);
6659 should_turn_left = (can_turn_left &&
6661 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6662 y + back_dy + left_dy)));
6663 should_turn_right = (can_turn_right &&
6665 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6666 y + back_dy + right_dy)));
6667 should_move_on = (can_move_on &&
6670 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6671 y + move_dy + left_dy) ||
6672 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6673 y + move_dy + right_dy)));
6675 if (should_turn_left || should_turn_right || should_move_on)
6677 if (should_turn_left && should_turn_right && should_move_on)
6678 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6679 rnd < 2 * rnd_value / 3 ? right_dir :
6681 else if (should_turn_left && should_turn_right)
6682 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6683 else if (should_turn_left && should_move_on)
6684 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6685 else if (should_turn_right && should_move_on)
6686 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6687 else if (should_turn_left)
6688 MovDir[x][y] = left_dir;
6689 else if (should_turn_right)
6690 MovDir[x][y] = right_dir;
6691 else if (should_move_on)
6692 MovDir[x][y] = old_move_dir;
6694 else if (can_move_on && rnd > rnd_value / 8)
6695 MovDir[x][y] = old_move_dir;
6696 else if (can_turn_left && can_turn_right)
6697 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6698 else if (can_turn_left && rnd > rnd_value / 8)
6699 MovDir[x][y] = left_dir;
6700 else if (can_turn_right && rnd > rnd_value/8)
6701 MovDir[x][y] = right_dir;
6703 MovDir[x][y] = back_dir;
6705 xx = x + move_xy[MovDir[x][y]].dx;
6706 yy = y + move_xy[MovDir[x][y]].dy;
6708 if (!IN_LEV_FIELD(xx, yy) ||
6709 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6710 MovDir[x][y] = old_move_dir;
6714 else if (element == EL_DRAGON)
6716 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6717 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6718 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6720 int rnd = RND(rnd_value);
6722 if (can_move_on && rnd > rnd_value / 8)
6723 MovDir[x][y] = old_move_dir;
6724 else if (can_turn_left && can_turn_right)
6725 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6726 else if (can_turn_left && rnd > rnd_value / 8)
6727 MovDir[x][y] = left_dir;
6728 else if (can_turn_right && rnd > rnd_value / 8)
6729 MovDir[x][y] = right_dir;
6731 MovDir[x][y] = back_dir;
6733 xx = x + move_xy[MovDir[x][y]].dx;
6734 yy = y + move_xy[MovDir[x][y]].dy;
6736 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6737 MovDir[x][y] = old_move_dir;
6741 else if (element == EL_MOLE)
6743 boolean can_move_on =
6744 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6745 IS_AMOEBOID(Feld[move_x][move_y]) ||
6746 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6749 boolean can_turn_left =
6750 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6751 IS_AMOEBOID(Feld[left_x][left_y])));
6753 boolean can_turn_right =
6754 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6755 IS_AMOEBOID(Feld[right_x][right_y])));
6757 if (can_turn_left && can_turn_right)
6758 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6759 else if (can_turn_left)
6760 MovDir[x][y] = left_dir;
6762 MovDir[x][y] = right_dir;
6765 if (MovDir[x][y] != old_move_dir)
6768 else if (element == EL_BALLOON)
6770 MovDir[x][y] = game.wind_direction;
6773 else if (element == EL_SPRING)
6775 if (MovDir[x][y] & MV_HORIZONTAL)
6777 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6778 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6780 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6781 ResetGfxAnimation(move_x, move_y);
6782 TEST_DrawLevelField(move_x, move_y);
6784 MovDir[x][y] = back_dir;
6786 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6787 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6788 MovDir[x][y] = MV_NONE;
6793 else if (element == EL_ROBOT ||
6794 element == EL_SATELLITE ||
6795 element == EL_PENGUIN ||
6796 element == EL_EMC_ANDROID)
6798 int attr_x = -1, attr_y = -1;
6809 for (i = 0; i < MAX_PLAYERS; i++)
6811 struct PlayerInfo *player = &stored_player[i];
6812 int jx = player->jx, jy = player->jy;
6814 if (!player->active)
6818 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6826 if (element == EL_ROBOT && ZX >= 0 && ZY >= 0 &&
6827 (Feld[ZX][ZY] == EL_ROBOT_WHEEL_ACTIVE ||
6828 game.engine_version < VERSION_IDENT(3,1,0,0)))
6834 if (element == EL_PENGUIN)
6837 static int xy[4][2] =
6845 for (i = 0; i < NUM_DIRECTIONS; i++)
6847 int ex = x + xy[i][0];
6848 int ey = y + xy[i][1];
6850 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
6851 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
6852 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
6853 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
6862 MovDir[x][y] = MV_NONE;
6864 MovDir[x][y] |= (AllPlayersGone ? MV_RIGHT : MV_LEFT);
6865 else if (attr_x > x)
6866 MovDir[x][y] |= (AllPlayersGone ? MV_LEFT : MV_RIGHT);
6868 MovDir[x][y] |= (AllPlayersGone ? MV_DOWN : MV_UP);
6869 else if (attr_y > y)
6870 MovDir[x][y] |= (AllPlayersGone ? MV_UP : MV_DOWN);
6872 if (element == EL_ROBOT)
6876 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6877 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
6878 Moving2Blocked(x, y, &newx, &newy);
6880 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
6881 MovDelay[x][y] = 8 + 8 * !RND(3);
6883 MovDelay[x][y] = 16;
6885 else if (element == EL_PENGUIN)
6891 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6893 boolean first_horiz = RND(2);
6894 int new_move_dir = MovDir[x][y];
6897 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6898 Moving2Blocked(x, y, &newx, &newy);
6900 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6904 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6905 Moving2Blocked(x, y, &newx, &newy);
6907 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6910 MovDir[x][y] = old_move_dir;
6914 else if (element == EL_SATELLITE)
6920 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6922 boolean first_horiz = RND(2);
6923 int new_move_dir = MovDir[x][y];
6926 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6927 Moving2Blocked(x, y, &newx, &newy);
6929 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6933 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6934 Moving2Blocked(x, y, &newx, &newy);
6936 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6939 MovDir[x][y] = old_move_dir;
6943 else if (element == EL_EMC_ANDROID)
6945 static int check_pos[16] =
6947 -1, /* 0 => (invalid) */
6948 7, /* 1 => MV_LEFT */
6949 3, /* 2 => MV_RIGHT */
6950 -1, /* 3 => (invalid) */
6952 0, /* 5 => MV_LEFT | MV_UP */
6953 2, /* 6 => MV_RIGHT | MV_UP */
6954 -1, /* 7 => (invalid) */
6955 5, /* 8 => MV_DOWN */
6956 6, /* 9 => MV_LEFT | MV_DOWN */
6957 4, /* 10 => MV_RIGHT | MV_DOWN */
6958 -1, /* 11 => (invalid) */
6959 -1, /* 12 => (invalid) */
6960 -1, /* 13 => (invalid) */
6961 -1, /* 14 => (invalid) */
6962 -1, /* 15 => (invalid) */
6970 { -1, -1, MV_LEFT | MV_UP },
6972 { +1, -1, MV_RIGHT | MV_UP },
6973 { +1, 0, MV_RIGHT },
6974 { +1, +1, MV_RIGHT | MV_DOWN },
6976 { -1, +1, MV_LEFT | MV_DOWN },
6979 int start_pos, check_order;
6980 boolean can_clone = FALSE;
6983 /* check if there is any free field around current position */
6984 for (i = 0; i < 8; i++)
6986 int newx = x + check_xy[i].dx;
6987 int newy = y + check_xy[i].dy;
6989 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
6997 if (can_clone) /* randomly find an element to clone */
7001 start_pos = check_pos[RND(8)];
7002 check_order = (RND(2) ? -1 : +1);
7004 for (i = 0; i < 8; i++)
7006 int pos_raw = start_pos + i * check_order;
7007 int pos = (pos_raw + 8) % 8;
7008 int newx = x + check_xy[pos].dx;
7009 int newy = y + check_xy[pos].dy;
7011 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7013 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7014 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7016 Store[x][y] = Feld[newx][newy];
7025 if (can_clone) /* randomly find a direction to move */
7029 start_pos = check_pos[RND(8)];
7030 check_order = (RND(2) ? -1 : +1);
7032 for (i = 0; i < 8; i++)
7034 int pos_raw = start_pos + i * check_order;
7035 int pos = (pos_raw + 8) % 8;
7036 int newx = x + check_xy[pos].dx;
7037 int newy = y + check_xy[pos].dy;
7038 int new_move_dir = check_xy[pos].dir;
7040 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7042 MovDir[x][y] = new_move_dir;
7043 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7052 if (can_clone) /* cloning and moving successful */
7055 /* cannot clone -- try to move towards player */
7057 start_pos = check_pos[MovDir[x][y] & 0x0f];
7058 check_order = (RND(2) ? -1 : +1);
7060 for (i = 0; i < 3; i++)
7062 /* first check start_pos, then previous/next or (next/previous) pos */
7063 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7064 int pos = (pos_raw + 8) % 8;
7065 int newx = x + check_xy[pos].dx;
7066 int newy = y + check_xy[pos].dy;
7067 int new_move_dir = check_xy[pos].dir;
7069 if (IS_PLAYER(newx, newy))
7072 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7074 MovDir[x][y] = new_move_dir;
7075 MovDelay[x][y] = level.android_move_time * 8 + 1;
7082 else if (move_pattern == MV_TURNING_LEFT ||
7083 move_pattern == MV_TURNING_RIGHT ||
7084 move_pattern == MV_TURNING_LEFT_RIGHT ||
7085 move_pattern == MV_TURNING_RIGHT_LEFT ||
7086 move_pattern == MV_TURNING_RANDOM ||
7087 move_pattern == MV_ALL_DIRECTIONS)
7089 boolean can_turn_left =
7090 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7091 boolean can_turn_right =
7092 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7094 if (element_info[element].move_stepsize == 0) /* "not moving" */
7097 if (move_pattern == MV_TURNING_LEFT)
7098 MovDir[x][y] = left_dir;
7099 else if (move_pattern == MV_TURNING_RIGHT)
7100 MovDir[x][y] = right_dir;
7101 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7102 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7103 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7104 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7105 else if (move_pattern == MV_TURNING_RANDOM)
7106 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7107 can_turn_right && !can_turn_left ? right_dir :
7108 RND(2) ? left_dir : right_dir);
7109 else if (can_turn_left && can_turn_right)
7110 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7111 else if (can_turn_left)
7112 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7113 else if (can_turn_right)
7114 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7116 MovDir[x][y] = back_dir;
7118 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7120 else if (move_pattern == MV_HORIZONTAL ||
7121 move_pattern == MV_VERTICAL)
7123 if (move_pattern & old_move_dir)
7124 MovDir[x][y] = back_dir;
7125 else if (move_pattern == MV_HORIZONTAL)
7126 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7127 else if (move_pattern == MV_VERTICAL)
7128 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7130 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7132 else if (move_pattern & MV_ANY_DIRECTION)
7134 MovDir[x][y] = move_pattern;
7135 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7137 else if (move_pattern & MV_WIND_DIRECTION)
7139 MovDir[x][y] = game.wind_direction;
7140 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7142 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7144 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7145 MovDir[x][y] = left_dir;
7146 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7147 MovDir[x][y] = right_dir;
7149 if (MovDir[x][y] != old_move_dir)
7150 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7152 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7154 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7155 MovDir[x][y] = right_dir;
7156 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7157 MovDir[x][y] = left_dir;
7159 if (MovDir[x][y] != old_move_dir)
7160 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7162 else if (move_pattern == MV_TOWARDS_PLAYER ||
7163 move_pattern == MV_AWAY_FROM_PLAYER)
7165 int attr_x = -1, attr_y = -1;
7167 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7178 for (i = 0; i < MAX_PLAYERS; i++)
7180 struct PlayerInfo *player = &stored_player[i];
7181 int jx = player->jx, jy = player->jy;
7183 if (!player->active)
7187 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7195 MovDir[x][y] = MV_NONE;
7197 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7198 else if (attr_x > x)
7199 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7201 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7202 else if (attr_y > y)
7203 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7205 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7207 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7209 boolean first_horiz = RND(2);
7210 int new_move_dir = MovDir[x][y];
7212 if (element_info[element].move_stepsize == 0) /* "not moving" */
7214 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7215 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7221 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7222 Moving2Blocked(x, y, &newx, &newy);
7224 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7228 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7229 Moving2Blocked(x, y, &newx, &newy);
7231 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7234 MovDir[x][y] = old_move_dir;
7237 else if (move_pattern == MV_WHEN_PUSHED ||
7238 move_pattern == MV_WHEN_DROPPED)
7240 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7241 MovDir[x][y] = MV_NONE;
7245 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7247 static int test_xy[7][2] =
7257 static int test_dir[7] =
7267 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7268 int move_preference = -1000000; /* start with very low preference */
7269 int new_move_dir = MV_NONE;
7270 int start_test = RND(4);
7273 for (i = 0; i < NUM_DIRECTIONS; i++)
7275 int move_dir = test_dir[start_test + i];
7276 int move_dir_preference;
7278 xx = x + test_xy[start_test + i][0];
7279 yy = y + test_xy[start_test + i][1];
7281 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7282 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7284 new_move_dir = move_dir;
7289 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7292 move_dir_preference = -1 * RunnerVisit[xx][yy];
7293 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7294 move_dir_preference = PlayerVisit[xx][yy];
7296 if (move_dir_preference > move_preference)
7298 /* prefer field that has not been visited for the longest time */
7299 move_preference = move_dir_preference;
7300 new_move_dir = move_dir;
7302 else if (move_dir_preference == move_preference &&
7303 move_dir == old_move_dir)
7305 /* prefer last direction when all directions are preferred equally */
7306 move_preference = move_dir_preference;
7307 new_move_dir = move_dir;
7311 MovDir[x][y] = new_move_dir;
7312 if (old_move_dir != new_move_dir)
7313 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7317 static void TurnRound(int x, int y)
7319 int direction = MovDir[x][y];
7323 GfxDir[x][y] = MovDir[x][y];
7325 if (direction != MovDir[x][y])
7329 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7331 ResetGfxFrame(x, y);
7334 static boolean JustBeingPushed(int x, int y)
7338 for (i = 0; i < MAX_PLAYERS; i++)
7340 struct PlayerInfo *player = &stored_player[i];
7342 if (player->active && player->is_pushing && player->MovPos)
7344 int next_jx = player->jx + (player->jx - player->last_jx);
7345 int next_jy = player->jy + (player->jy - player->last_jy);
7347 if (x == next_jx && y == next_jy)
7355 void StartMoving(int x, int y)
7357 boolean started_moving = FALSE; /* some elements can fall _and_ move */
7358 int element = Feld[x][y];
7363 if (MovDelay[x][y] == 0)
7364 GfxAction[x][y] = ACTION_DEFAULT;
7366 if (CAN_FALL(element) && y < lev_fieldy - 1)
7368 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7369 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7370 if (JustBeingPushed(x, y))
7373 if (element == EL_QUICKSAND_FULL)
7375 if (IS_FREE(x, y + 1))
7377 InitMovingField(x, y, MV_DOWN);
7378 started_moving = TRUE;
7380 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7381 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7382 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7383 Store[x][y] = EL_ROCK;
7385 Store[x][y] = EL_ROCK;
7388 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7390 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7392 if (!MovDelay[x][y])
7394 MovDelay[x][y] = TILEY + 1;
7396 ResetGfxAnimation(x, y);
7397 ResetGfxAnimation(x, y + 1);
7402 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7403 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7410 Feld[x][y] = EL_QUICKSAND_EMPTY;
7411 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7412 Store[x][y + 1] = Store[x][y];
7415 PlayLevelSoundAction(x, y, ACTION_FILLING);
7417 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7419 if (!MovDelay[x][y])
7421 MovDelay[x][y] = TILEY + 1;
7423 ResetGfxAnimation(x, y);
7424 ResetGfxAnimation(x, y + 1);
7429 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7430 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7437 Feld[x][y] = EL_QUICKSAND_EMPTY;
7438 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7439 Store[x][y + 1] = Store[x][y];
7442 PlayLevelSoundAction(x, y, ACTION_FILLING);
7445 else if (element == EL_QUICKSAND_FAST_FULL)
7447 if (IS_FREE(x, y + 1))
7449 InitMovingField(x, y, MV_DOWN);
7450 started_moving = TRUE;
7452 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7453 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7454 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7455 Store[x][y] = EL_ROCK;
7457 Store[x][y] = EL_ROCK;
7460 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7462 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7464 if (!MovDelay[x][y])
7466 MovDelay[x][y] = TILEY + 1;
7468 ResetGfxAnimation(x, y);
7469 ResetGfxAnimation(x, y + 1);
7474 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7475 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7482 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7483 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7484 Store[x][y + 1] = Store[x][y];
7487 PlayLevelSoundAction(x, y, ACTION_FILLING);
7489 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7491 if (!MovDelay[x][y])
7493 MovDelay[x][y] = TILEY + 1;
7495 ResetGfxAnimation(x, y);
7496 ResetGfxAnimation(x, y + 1);
7501 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7502 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7509 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7510 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7511 Store[x][y + 1] = Store[x][y];
7514 PlayLevelSoundAction(x, y, ACTION_FILLING);
7517 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7518 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7520 InitMovingField(x, y, MV_DOWN);
7521 started_moving = TRUE;
7523 Feld[x][y] = EL_QUICKSAND_FILLING;
7524 Store[x][y] = element;
7526 PlayLevelSoundAction(x, y, ACTION_FILLING);
7528 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7529 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7531 InitMovingField(x, y, MV_DOWN);
7532 started_moving = TRUE;
7534 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7535 Store[x][y] = element;
7537 PlayLevelSoundAction(x, y, ACTION_FILLING);
7539 else if (element == EL_MAGIC_WALL_FULL)
7541 if (IS_FREE(x, y + 1))
7543 InitMovingField(x, y, MV_DOWN);
7544 started_moving = TRUE;
7546 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7547 Store[x][y] = EL_CHANGED(Store[x][y]);
7549 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7551 if (!MovDelay[x][y])
7552 MovDelay[x][y] = TILEY / 4 + 1;
7561 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7562 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7563 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7567 else if (element == EL_BD_MAGIC_WALL_FULL)
7569 if (IS_FREE(x, y + 1))
7571 InitMovingField(x, y, MV_DOWN);
7572 started_moving = TRUE;
7574 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7575 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7577 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7579 if (!MovDelay[x][y])
7580 MovDelay[x][y] = TILEY / 4 + 1;
7589 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7590 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7591 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7595 else if (element == EL_DC_MAGIC_WALL_FULL)
7597 if (IS_FREE(x, y + 1))
7599 InitMovingField(x, y, MV_DOWN);
7600 started_moving = TRUE;
7602 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7603 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7605 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7607 if (!MovDelay[x][y])
7608 MovDelay[x][y] = TILEY / 4 + 1;
7617 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7618 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7619 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7623 else if ((CAN_PASS_MAGIC_WALL(element) &&
7624 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7625 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7626 (CAN_PASS_DC_MAGIC_WALL(element) &&
7627 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7630 InitMovingField(x, y, MV_DOWN);
7631 started_moving = TRUE;
7634 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7635 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7636 EL_DC_MAGIC_WALL_FILLING);
7637 Store[x][y] = element;
7639 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7641 SplashAcid(x, y + 1);
7643 InitMovingField(x, y, MV_DOWN);
7644 started_moving = TRUE;
7646 Store[x][y] = EL_ACID;
7649 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7650 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7651 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7652 CAN_FALL(element) && WasJustFalling[x][y] &&
7653 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7655 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7656 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7657 (Feld[x][y + 1] == EL_BLOCKED)))
7659 /* this is needed for a special case not covered by calling "Impact()"
7660 from "ContinueMoving()": if an element moves to a tile directly below
7661 another element which was just falling on that tile (which was empty
7662 in the previous frame), the falling element above would just stop
7663 instead of smashing the element below (in previous version, the above
7664 element was just checked for "moving" instead of "falling", resulting
7665 in incorrect smashes caused by horizontal movement of the above
7666 element; also, the case of the player being the element to smash was
7667 simply not covered here... :-/ ) */
7669 CheckCollision[x][y] = 0;
7670 CheckImpact[x][y] = 0;
7674 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7676 if (MovDir[x][y] == MV_NONE)
7678 InitMovingField(x, y, MV_DOWN);
7679 started_moving = TRUE;
7682 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7684 if (WasJustFalling[x][y]) /* prevent animation from being restarted */
7685 MovDir[x][y] = MV_DOWN;
7687 InitMovingField(x, y, MV_DOWN);
7688 started_moving = TRUE;
7690 else if (element == EL_AMOEBA_DROP)
7692 Feld[x][y] = EL_AMOEBA_GROWING;
7693 Store[x][y] = EL_AMOEBA_WET;
7695 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7696 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7697 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7698 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7700 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7701 (IS_FREE(x - 1, y + 1) ||
7702 Feld[x - 1][y + 1] == EL_ACID));
7703 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7704 (IS_FREE(x + 1, y + 1) ||
7705 Feld[x + 1][y + 1] == EL_ACID));
7706 boolean can_fall_any = (can_fall_left || can_fall_right);
7707 boolean can_fall_both = (can_fall_left && can_fall_right);
7708 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7710 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7712 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7713 can_fall_right = FALSE;
7714 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7715 can_fall_left = FALSE;
7716 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7717 can_fall_right = FALSE;
7718 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7719 can_fall_left = FALSE;
7721 can_fall_any = (can_fall_left || can_fall_right);
7722 can_fall_both = FALSE;
7727 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7728 can_fall_right = FALSE; /* slip down on left side */
7730 can_fall_left = !(can_fall_right = RND(2));
7732 can_fall_both = FALSE;
7737 /* if not determined otherwise, prefer left side for slipping down */
7738 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7739 started_moving = TRUE;
7742 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7744 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7745 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7746 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7747 int belt_dir = game.belt_dir[belt_nr];
7749 if ((belt_dir == MV_LEFT && left_is_free) ||
7750 (belt_dir == MV_RIGHT && right_is_free))
7752 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7754 InitMovingField(x, y, belt_dir);
7755 started_moving = TRUE;
7757 Pushed[x][y] = TRUE;
7758 Pushed[nextx][y] = TRUE;
7760 GfxAction[x][y] = ACTION_DEFAULT;
7764 MovDir[x][y] = 0; /* if element was moving, stop it */
7769 /* not "else if" because of elements that can fall and move (EL_SPRING) */
7770 if (CAN_MOVE(element) && !started_moving)
7772 int move_pattern = element_info[element].move_pattern;
7775 Moving2Blocked(x, y, &newx, &newy);
7777 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7780 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7781 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7783 WasJustMoving[x][y] = 0;
7784 CheckCollision[x][y] = 0;
7786 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7788 if (Feld[x][y] != element) /* element has changed */
7792 if (!MovDelay[x][y]) /* start new movement phase */
7794 /* all objects that can change their move direction after each step
7795 (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall */
7797 if (element != EL_YAMYAM &&
7798 element != EL_DARK_YAMYAM &&
7799 element != EL_PACMAN &&
7800 !(move_pattern & MV_ANY_DIRECTION) &&
7801 move_pattern != MV_TURNING_LEFT &&
7802 move_pattern != MV_TURNING_RIGHT &&
7803 move_pattern != MV_TURNING_LEFT_RIGHT &&
7804 move_pattern != MV_TURNING_RIGHT_LEFT &&
7805 move_pattern != MV_TURNING_RANDOM)
7809 if (MovDelay[x][y] && (element == EL_BUG ||
7810 element == EL_SPACESHIP ||
7811 element == EL_SP_SNIKSNAK ||
7812 element == EL_SP_ELECTRON ||
7813 element == EL_MOLE))
7814 TEST_DrawLevelField(x, y);
7818 if (MovDelay[x][y]) /* wait some time before next movement */
7822 if (element == EL_ROBOT ||
7823 element == EL_YAMYAM ||
7824 element == EL_DARK_YAMYAM)
7826 DrawLevelElementAnimationIfNeeded(x, y, element);
7827 PlayLevelSoundAction(x, y, ACTION_WAITING);
7829 else if (element == EL_SP_ELECTRON)
7830 DrawLevelElementAnimationIfNeeded(x, y, element);
7831 else if (element == EL_DRAGON)
7834 int dir = MovDir[x][y];
7835 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
7836 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
7837 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
7838 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
7839 dir == MV_UP ? IMG_FLAMES_1_UP :
7840 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
7841 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
7843 GfxAction[x][y] = ACTION_ATTACKING;
7845 if (IS_PLAYER(x, y))
7846 DrawPlayerField(x, y);
7848 TEST_DrawLevelField(x, y);
7850 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
7852 for (i = 1; i <= 3; i++)
7854 int xx = x + i * dx;
7855 int yy = y + i * dy;
7856 int sx = SCREENX(xx);
7857 int sy = SCREENY(yy);
7858 int flame_graphic = graphic + (i - 1);
7860 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
7865 int flamed = MovingOrBlocked2Element(xx, yy);
7867 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
7870 RemoveMovingField(xx, yy);
7872 ChangeDelay[xx][yy] = 0;
7874 Feld[xx][yy] = EL_FLAMES;
7876 if (IN_SCR_FIELD(sx, sy))
7878 TEST_DrawLevelFieldCrumbled(xx, yy);
7879 DrawGraphic(sx, sy, flame_graphic, frame);
7884 if (Feld[xx][yy] == EL_FLAMES)
7885 Feld[xx][yy] = EL_EMPTY;
7886 TEST_DrawLevelField(xx, yy);
7891 if (MovDelay[x][y]) /* element still has to wait some time */
7893 PlayLevelSoundAction(x, y, ACTION_WAITING);
7899 /* now make next step */
7901 Moving2Blocked(x, y, &newx, &newy); /* get next screen position */
7903 if (DONT_COLLIDE_WITH(element) &&
7904 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
7905 !PLAYER_ENEMY_PROTECTED(newx, newy))
7907 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
7912 else if (CAN_MOVE_INTO_ACID(element) &&
7913 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
7914 !IS_MV_DIAGONAL(MovDir[x][y]) &&
7915 (MovDir[x][y] == MV_DOWN ||
7916 game.engine_version >= VERSION_IDENT(3,1,0,0)))
7918 SplashAcid(newx, newy);
7919 Store[x][y] = EL_ACID;
7921 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
7923 if (Feld[newx][newy] == EL_EXIT_OPEN ||
7924 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
7925 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
7926 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
7929 TEST_DrawLevelField(x, y);
7931 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
7932 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
7933 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
7935 local_player->friends_still_needed--;
7936 if (!local_player->friends_still_needed &&
7937 !local_player->GameOver && AllPlayersGone)
7938 PlayerWins(local_player);
7942 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
7944 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
7945 TEST_DrawLevelField(newx, newy);
7947 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
7949 else if (!IS_FREE(newx, newy))
7951 GfxAction[x][y] = ACTION_WAITING;
7953 if (IS_PLAYER(x, y))
7954 DrawPlayerField(x, y);
7956 TEST_DrawLevelField(x, y);
7961 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
7963 if (IS_FOOD_PIG(Feld[newx][newy]))
7965 if (IS_MOVING(newx, newy))
7966 RemoveMovingField(newx, newy);
7969 Feld[newx][newy] = EL_EMPTY;
7970 TEST_DrawLevelField(newx, newy);
7973 PlayLevelSound(x, y, SND_PIG_DIGGING);
7975 else if (!IS_FREE(newx, newy))
7977 if (IS_PLAYER(x, y))
7978 DrawPlayerField(x, y);
7980 TEST_DrawLevelField(x, y);
7985 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
7987 if (Store[x][y] != EL_EMPTY)
7989 boolean can_clone = FALSE;
7992 /* check if element to clone is still there */
7993 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
7995 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8003 /* cannot clone or target field not free anymore -- do not clone */
8004 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8005 Store[x][y] = EL_EMPTY;
8008 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8010 if (IS_MV_DIAGONAL(MovDir[x][y]))
8012 int diagonal_move_dir = MovDir[x][y];
8013 int stored = Store[x][y];
8014 int change_delay = 8;
8017 /* android is moving diagonally */
8019 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8021 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8022 GfxElement[x][y] = EL_EMC_ANDROID;
8023 GfxAction[x][y] = ACTION_SHRINKING;
8024 GfxDir[x][y] = diagonal_move_dir;
8025 ChangeDelay[x][y] = change_delay;
8027 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8030 DrawLevelGraphicAnimation(x, y, graphic);
8031 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8033 if (Feld[newx][newy] == EL_ACID)
8035 SplashAcid(newx, newy);
8040 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8042 Store[newx][newy] = EL_EMC_ANDROID;
8043 GfxElement[newx][newy] = EL_EMC_ANDROID;
8044 GfxAction[newx][newy] = ACTION_GROWING;
8045 GfxDir[newx][newy] = diagonal_move_dir;
8046 ChangeDelay[newx][newy] = change_delay;
8048 graphic = el_act_dir2img(GfxElement[newx][newy],
8049 GfxAction[newx][newy], GfxDir[newx][newy]);
8051 DrawLevelGraphicAnimation(newx, newy, graphic);
8052 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8058 Feld[newx][newy] = EL_EMPTY;
8059 TEST_DrawLevelField(newx, newy);
8061 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8064 else if (!IS_FREE(newx, newy))
8069 else if (IS_CUSTOM_ELEMENT(element) &&
8070 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8072 if (!DigFieldByCE(newx, newy, element))
8075 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8077 RunnerVisit[x][y] = FrameCounter;
8078 PlayerVisit[x][y] /= 8; /* expire player visit path */
8081 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8083 if (!IS_FREE(newx, newy))
8085 if (IS_PLAYER(x, y))
8086 DrawPlayerField(x, y);
8088 TEST_DrawLevelField(x, y);
8094 boolean wanna_flame = !RND(10);
8095 int dx = newx - x, dy = newy - y;
8096 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8097 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8098 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8099 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8100 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8101 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8104 IS_CLASSIC_ENEMY(element1) ||
8105 IS_CLASSIC_ENEMY(element2)) &&
8106 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8107 element1 != EL_FLAMES && element2 != EL_FLAMES)
8109 ResetGfxAnimation(x, y);
8110 GfxAction[x][y] = ACTION_ATTACKING;
8112 if (IS_PLAYER(x, y))
8113 DrawPlayerField(x, y);
8115 TEST_DrawLevelField(x, y);
8117 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8119 MovDelay[x][y] = 50;
8121 Feld[newx][newy] = EL_FLAMES;
8122 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8123 Feld[newx1][newy1] = EL_FLAMES;
8124 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8125 Feld[newx2][newy2] = EL_FLAMES;
8131 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8132 Feld[newx][newy] == EL_DIAMOND)
8134 if (IS_MOVING(newx, newy))
8135 RemoveMovingField(newx, newy);
8138 Feld[newx][newy] = EL_EMPTY;
8139 TEST_DrawLevelField(newx, newy);
8142 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8144 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8145 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8147 if (AmoebaNr[newx][newy])
8149 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8150 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8151 Feld[newx][newy] == EL_BD_AMOEBA)
8152 AmoebaCnt[AmoebaNr[newx][newy]]--;
8155 if (IS_MOVING(newx, newy))
8157 RemoveMovingField(newx, newy);
8161 Feld[newx][newy] = EL_EMPTY;
8162 TEST_DrawLevelField(newx, newy);
8165 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8167 else if ((element == EL_PACMAN || element == EL_MOLE)
8168 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8170 if (AmoebaNr[newx][newy])
8172 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8173 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8174 Feld[newx][newy] == EL_BD_AMOEBA)
8175 AmoebaCnt[AmoebaNr[newx][newy]]--;
8178 if (element == EL_MOLE)
8180 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8181 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8183 ResetGfxAnimation(x, y);
8184 GfxAction[x][y] = ACTION_DIGGING;
8185 TEST_DrawLevelField(x, y);
8187 MovDelay[newx][newy] = 0; /* start amoeba shrinking delay */
8189 return; /* wait for shrinking amoeba */
8191 else /* element == EL_PACMAN */
8193 Feld[newx][newy] = EL_EMPTY;
8194 TEST_DrawLevelField(newx, newy);
8195 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8198 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8199 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8200 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8202 /* wait for shrinking amoeba to completely disappear */
8205 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8207 /* object was running against a wall */
8211 if (GFX_ELEMENT(element) != EL_SAND) /* !!! FIX THIS (crumble) !!! */
8212 DrawLevelElementAnimation(x, y, element);
8214 if (DONT_TOUCH(element))
8215 TestIfBadThingTouchesPlayer(x, y);
8220 InitMovingField(x, y, MovDir[x][y]);
8222 PlayLevelSoundAction(x, y, ACTION_MOVING);
8226 ContinueMoving(x, y);
8229 void ContinueMoving(int x, int y)
8231 int element = Feld[x][y];
8232 struct ElementInfo *ei = &element_info[element];
8233 int direction = MovDir[x][y];
8234 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8235 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8236 int newx = x + dx, newy = y + dy;
8237 int stored = Store[x][y];
8238 int stored_new = Store[newx][newy];
8239 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8240 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8241 boolean last_line = (newy == lev_fieldy - 1);
8243 MovPos[x][y] += getElementMoveStepsize(x, y);
8245 if (pushed_by_player) /* special case: moving object pushed by player */
8246 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8248 if (ABS(MovPos[x][y]) < TILEX)
8250 TEST_DrawLevelField(x, y);
8252 return; /* element is still moving */
8255 /* element reached destination field */
8257 Feld[x][y] = EL_EMPTY;
8258 Feld[newx][newy] = element;
8259 MovPos[x][y] = 0; /* force "not moving" for "crumbled sand" */
8261 if (Store[x][y] == EL_ACID) /* element is moving into acid pool */
8263 element = Feld[newx][newy] = EL_ACID;
8265 else if (element == EL_MOLE)
8267 Feld[x][y] = EL_SAND;
8269 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8271 else if (element == EL_QUICKSAND_FILLING)
8273 element = Feld[newx][newy] = get_next_element(element);
8274 Store[newx][newy] = Store[x][y];
8276 else if (element == EL_QUICKSAND_EMPTYING)
8278 Feld[x][y] = get_next_element(element);
8279 element = Feld[newx][newy] = Store[x][y];
8281 else if (element == EL_QUICKSAND_FAST_FILLING)
8283 element = Feld[newx][newy] = get_next_element(element);
8284 Store[newx][newy] = Store[x][y];
8286 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8288 Feld[x][y] = get_next_element(element);
8289 element = Feld[newx][newy] = Store[x][y];
8291 else if (element == EL_MAGIC_WALL_FILLING)
8293 element = Feld[newx][newy] = get_next_element(element);
8294 if (!game.magic_wall_active)
8295 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8296 Store[newx][newy] = Store[x][y];
8298 else if (element == EL_MAGIC_WALL_EMPTYING)
8300 Feld[x][y] = get_next_element(element);
8301 if (!game.magic_wall_active)
8302 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8303 element = Feld[newx][newy] = Store[x][y];
8305 InitField(newx, newy, FALSE);
8307 else if (element == EL_BD_MAGIC_WALL_FILLING)
8309 element = Feld[newx][newy] = get_next_element(element);
8310 if (!game.magic_wall_active)
8311 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8312 Store[newx][newy] = Store[x][y];
8314 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8316 Feld[x][y] = get_next_element(element);
8317 if (!game.magic_wall_active)
8318 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8319 element = Feld[newx][newy] = Store[x][y];
8321 InitField(newx, newy, FALSE);
8323 else if (element == EL_DC_MAGIC_WALL_FILLING)
8325 element = Feld[newx][newy] = get_next_element(element);
8326 if (!game.magic_wall_active)
8327 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8328 Store[newx][newy] = Store[x][y];
8330 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8332 Feld[x][y] = get_next_element(element);
8333 if (!game.magic_wall_active)
8334 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8335 element = Feld[newx][newy] = Store[x][y];
8337 InitField(newx, newy, FALSE);
8339 else if (element == EL_AMOEBA_DROPPING)
8341 Feld[x][y] = get_next_element(element);
8342 element = Feld[newx][newy] = Store[x][y];
8344 else if (element == EL_SOKOBAN_OBJECT)
8347 Feld[x][y] = Back[x][y];
8349 if (Back[newx][newy])
8350 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8352 Back[x][y] = Back[newx][newy] = 0;
8355 Store[x][y] = EL_EMPTY;
8360 MovDelay[newx][newy] = 0;
8362 if (CAN_CHANGE_OR_HAS_ACTION(element))
8364 /* copy element change control values to new field */
8365 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8366 ChangePage[newx][newy] = ChangePage[x][y];
8367 ChangeCount[newx][newy] = ChangeCount[x][y];
8368 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8371 CustomValue[newx][newy] = CustomValue[x][y];
8373 ChangeDelay[x][y] = 0;
8374 ChangePage[x][y] = -1;
8375 ChangeCount[x][y] = 0;
8376 ChangeEvent[x][y] = -1;
8378 CustomValue[x][y] = 0;
8380 /* copy animation control values to new field */
8381 GfxFrame[newx][newy] = GfxFrame[x][y];
8382 GfxRandom[newx][newy] = GfxRandom[x][y]; /* keep same random value */
8383 GfxAction[newx][newy] = GfxAction[x][y]; /* keep action one frame */
8384 GfxDir[newx][newy] = GfxDir[x][y]; /* keep element direction */
8386 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8388 /* some elements can leave other elements behind after moving */
8389 if (ei->move_leave_element != EL_EMPTY &&
8390 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8391 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8393 int move_leave_element = ei->move_leave_element;
8395 /* this makes it possible to leave the removed element again */
8396 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8397 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8399 Feld[x][y] = move_leave_element;
8401 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8402 MovDir[x][y] = direction;
8404 InitField(x, y, FALSE);
8406 if (GFX_CRUMBLED(Feld[x][y]))
8407 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8409 if (ELEM_IS_PLAYER(move_leave_element))
8410 RelocatePlayer(x, y, move_leave_element);
8413 /* do this after checking for left-behind element */
8414 ResetGfxAnimation(x, y); /* reset animation values for old field */
8416 if (!CAN_MOVE(element) ||
8417 (CAN_FALL(element) && direction == MV_DOWN &&
8418 (element == EL_SPRING ||
8419 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8420 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8421 GfxDir[x][y] = MovDir[newx][newy] = 0;
8423 TEST_DrawLevelField(x, y);
8424 TEST_DrawLevelField(newx, newy);
8426 Stop[newx][newy] = TRUE; /* ignore this element until the next frame */
8428 /* prevent pushed element from moving on in pushed direction */
8429 if (pushed_by_player && CAN_MOVE(element) &&
8430 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8431 !(element_info[element].move_pattern & direction))
8432 TurnRound(newx, newy);
8434 /* prevent elements on conveyor belt from moving on in last direction */
8435 if (pushed_by_conveyor && CAN_FALL(element) &&
8436 direction & MV_HORIZONTAL)
8437 MovDir[newx][newy] = 0;
8439 if (!pushed_by_player)
8441 int nextx = newx + dx, nexty = newy + dy;
8442 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8444 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8446 if (CAN_FALL(element) && direction == MV_DOWN)
8447 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8449 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8450 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8452 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8453 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8456 if (DONT_TOUCH(element)) /* object may be nasty to player or others */
8458 TestIfBadThingTouchesPlayer(newx, newy);
8459 TestIfBadThingTouchesFriend(newx, newy);
8461 if (!IS_CUSTOM_ELEMENT(element))
8462 TestIfBadThingTouchesOtherBadThing(newx, newy);
8464 else if (element == EL_PENGUIN)
8465 TestIfFriendTouchesBadThing(newx, newy);
8467 if (DONT_GET_HIT_BY(element))
8469 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8472 /* give the player one last chance (one more frame) to move away */
8473 if (CAN_FALL(element) && direction == MV_DOWN &&
8474 (last_line || (!IS_FREE(x, newy + 1) &&
8475 (!IS_PLAYER(x, newy + 1) ||
8476 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8479 if (pushed_by_player && !game.use_change_when_pushing_bug)
8481 int push_side = MV_DIR_OPPOSITE(direction);
8482 struct PlayerInfo *player = PLAYERINFO(x, y);
8484 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8485 player->index_bit, push_side);
8486 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8487 player->index_bit, push_side);
8490 if (element == EL_EMC_ANDROID && pushed_by_player) /* make another move */
8491 MovDelay[newx][newy] = 1;
8493 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8495 TestIfElementTouchesCustomElement(x, y); /* empty or new element */
8496 TestIfElementHitsCustomElement(newx, newy, direction);
8497 TestIfPlayerTouchesCustomElement(newx, newy);
8498 TestIfElementTouchesCustomElement(newx, newy);
8500 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8501 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8502 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8503 MV_DIR_OPPOSITE(direction));
8506 int AmoebeNachbarNr(int ax, int ay)
8509 int element = Feld[ax][ay];
8511 static int xy[4][2] =
8519 for (i = 0; i < NUM_DIRECTIONS; i++)
8521 int x = ax + xy[i][0];
8522 int y = ay + xy[i][1];
8524 if (!IN_LEV_FIELD(x, y))
8527 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8528 group_nr = AmoebaNr[x][y];
8534 void AmoebenVereinigen(int ax, int ay)
8536 int i, x, y, xx, yy;
8537 int new_group_nr = AmoebaNr[ax][ay];
8538 static int xy[4][2] =
8546 if (new_group_nr == 0)
8549 for (i = 0; i < NUM_DIRECTIONS; i++)
8554 if (!IN_LEV_FIELD(x, y))
8557 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8558 Feld[x][y] == EL_BD_AMOEBA ||
8559 Feld[x][y] == EL_AMOEBA_DEAD) &&
8560 AmoebaNr[x][y] != new_group_nr)
8562 int old_group_nr = AmoebaNr[x][y];
8564 if (old_group_nr == 0)
8567 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8568 AmoebaCnt[old_group_nr] = 0;
8569 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8570 AmoebaCnt2[old_group_nr] = 0;
8572 SCAN_PLAYFIELD(xx, yy)
8574 if (AmoebaNr[xx][yy] == old_group_nr)
8575 AmoebaNr[xx][yy] = new_group_nr;
8581 void AmoebeUmwandeln(int ax, int ay)
8585 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8587 int group_nr = AmoebaNr[ax][ay];
8592 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8593 printf("AmoebeUmwandeln(): This should never happen!\n");
8598 SCAN_PLAYFIELD(x, y)
8600 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8603 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8607 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8608 SND_AMOEBA_TURNING_TO_GEM :
8609 SND_AMOEBA_TURNING_TO_ROCK));
8614 static int xy[4][2] =
8622 for (i = 0; i < NUM_DIRECTIONS; i++)
8627 if (!IN_LEV_FIELD(x, y))
8630 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8632 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8633 SND_AMOEBA_TURNING_TO_GEM :
8634 SND_AMOEBA_TURNING_TO_ROCK));
8641 void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8644 int group_nr = AmoebaNr[ax][ay];
8645 boolean done = FALSE;
8650 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8651 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8656 SCAN_PLAYFIELD(x, y)
8658 if (AmoebaNr[x][y] == group_nr &&
8659 (Feld[x][y] == EL_AMOEBA_DEAD ||
8660 Feld[x][y] == EL_BD_AMOEBA ||
8661 Feld[x][y] == EL_AMOEBA_GROWING))
8664 Feld[x][y] = new_element;
8665 InitField(x, y, FALSE);
8666 TEST_DrawLevelField(x, y);
8672 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8673 SND_BD_AMOEBA_TURNING_TO_ROCK :
8674 SND_BD_AMOEBA_TURNING_TO_GEM));
8677 void AmoebeWaechst(int x, int y)
8679 static unsigned int sound_delay = 0;
8680 static unsigned int sound_delay_value = 0;
8682 if (!MovDelay[x][y]) /* start new growing cycle */
8686 if (DelayReached(&sound_delay, sound_delay_value))
8688 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8689 sound_delay_value = 30;
8693 if (MovDelay[x][y]) /* wait some time before growing bigger */
8696 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8698 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8699 6 - MovDelay[x][y]);
8701 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8704 if (!MovDelay[x][y])
8706 Feld[x][y] = Store[x][y];
8708 TEST_DrawLevelField(x, y);
8713 void AmoebaDisappearing(int x, int y)
8715 static unsigned int sound_delay = 0;
8716 static unsigned int sound_delay_value = 0;
8718 if (!MovDelay[x][y]) /* start new shrinking cycle */
8722 if (DelayReached(&sound_delay, sound_delay_value))
8723 sound_delay_value = 30;
8726 if (MovDelay[x][y]) /* wait some time before shrinking */
8729 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8731 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8732 6 - MovDelay[x][y]);
8734 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8737 if (!MovDelay[x][y])
8739 Feld[x][y] = EL_EMPTY;
8740 TEST_DrawLevelField(x, y);
8742 /* don't let mole enter this field in this cycle;
8743 (give priority to objects falling to this field from above) */
8749 void AmoebeAbleger(int ax, int ay)
8752 int element = Feld[ax][ay];
8753 int graphic = el2img(element);
8754 int newax = ax, neway = ay;
8755 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8756 static int xy[4][2] =
8764 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8766 Feld[ax][ay] = EL_AMOEBA_DEAD;
8767 TEST_DrawLevelField(ax, ay);
8771 if (IS_ANIMATED(graphic))
8772 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8774 if (!MovDelay[ax][ay]) /* start making new amoeba field */
8775 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8777 if (MovDelay[ax][ay]) /* wait some time before making new amoeba */
8780 if (MovDelay[ax][ay])
8784 if (can_drop) /* EL_AMOEBA_WET or EL_EMC_DRIPPER */
8787 int x = ax + xy[start][0];
8788 int y = ay + xy[start][1];
8790 if (!IN_LEV_FIELD(x, y))
8793 if (IS_FREE(x, y) ||
8794 CAN_GROW_INTO(Feld[x][y]) ||
8795 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8796 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8802 if (newax == ax && neway == ay)
8805 else /* normal or "filled" (BD style) amoeba */
8808 boolean waiting_for_player = FALSE;
8810 for (i = 0; i < NUM_DIRECTIONS; i++)
8812 int j = (start + i) % 4;
8813 int x = ax + xy[j][0];
8814 int y = ay + xy[j][1];
8816 if (!IN_LEV_FIELD(x, y))
8819 if (IS_FREE(x, y) ||
8820 CAN_GROW_INTO(Feld[x][y]) ||
8821 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8822 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8828 else if (IS_PLAYER(x, y))
8829 waiting_for_player = TRUE;
8832 if (newax == ax && neway == ay) /* amoeba cannot grow */
8834 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
8836 Feld[ax][ay] = EL_AMOEBA_DEAD;
8837 TEST_DrawLevelField(ax, ay);
8838 AmoebaCnt[AmoebaNr[ax][ay]]--;
8840 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) /* amoeba is completely dead */
8842 if (element == EL_AMOEBA_FULL)
8843 AmoebeUmwandeln(ax, ay);
8844 else if (element == EL_BD_AMOEBA)
8845 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
8850 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
8852 /* amoeba gets larger by growing in some direction */
8854 int new_group_nr = AmoebaNr[ax][ay];
8857 if (new_group_nr == 0)
8859 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
8860 printf("AmoebeAbleger(): This should never happen!\n");
8865 AmoebaNr[newax][neway] = new_group_nr;
8866 AmoebaCnt[new_group_nr]++;
8867 AmoebaCnt2[new_group_nr]++;
8869 /* if amoeba touches other amoeba(s) after growing, unify them */
8870 AmoebenVereinigen(newax, neway);
8872 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
8874 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
8880 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
8881 (neway == lev_fieldy - 1 && newax != ax))
8883 Feld[newax][neway] = EL_AMOEBA_GROWING; /* creation of new amoeba */
8884 Store[newax][neway] = element;
8886 else if (neway == ay || element == EL_EMC_DRIPPER)
8888 Feld[newax][neway] = EL_AMOEBA_DROP; /* drop left/right of amoeba */
8890 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
8894 InitMovingField(ax, ay, MV_DOWN); /* drop dripping from amoeba */
8895 Feld[ax][ay] = EL_AMOEBA_DROPPING;
8896 Store[ax][ay] = EL_AMOEBA_DROP;
8897 ContinueMoving(ax, ay);
8901 TEST_DrawLevelField(newax, neway);
8904 void Life(int ax, int ay)
8908 int element = Feld[ax][ay];
8909 int graphic = el2img(element);
8910 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
8912 boolean changed = FALSE;
8914 if (IS_ANIMATED(graphic))
8915 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8920 if (!MovDelay[ax][ay]) /* start new "game of life" cycle */
8921 MovDelay[ax][ay] = life_time;
8923 if (MovDelay[ax][ay]) /* wait some time before next cycle */
8926 if (MovDelay[ax][ay])
8930 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
8932 int xx = ax+x1, yy = ay+y1;
8935 if (!IN_LEV_FIELD(xx, yy))
8938 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
8940 int x = xx+x2, y = yy+y2;
8942 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
8945 if (((Feld[x][y] == element ||
8946 (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y))) &&
8948 (IS_FREE(x, y) && Stop[x][y]))
8952 if (xx == ax && yy == ay) /* field in the middle */
8954 if (nachbarn < life_parameter[0] ||
8955 nachbarn > life_parameter[1])
8957 Feld[xx][yy] = EL_EMPTY;
8959 TEST_DrawLevelField(xx, yy);
8960 Stop[xx][yy] = TRUE;
8964 else if (IS_FREE(xx, yy) || CAN_GROW_INTO(Feld[xx][yy]))
8965 { /* free border field */
8966 if (nachbarn >= life_parameter[2] &&
8967 nachbarn <= life_parameter[3])
8969 Feld[xx][yy] = element;
8970 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
8972 TEST_DrawLevelField(xx, yy);
8973 Stop[xx][yy] = TRUE;
8980 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
8981 SND_GAME_OF_LIFE_GROWING);
8984 static void InitRobotWheel(int x, int y)
8986 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
8989 static void RunRobotWheel(int x, int y)
8991 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
8994 static void StopRobotWheel(int x, int y)
8996 if (ZX == x && ZY == y)
9000 game.robot_wheel_active = FALSE;
9004 static void InitTimegateWheel(int x, int y)
9006 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9009 static void RunTimegateWheel(int x, int y)
9011 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9014 static void InitMagicBallDelay(int x, int y)
9016 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9019 static void ActivateMagicBall(int bx, int by)
9023 if (level.ball_random)
9025 int pos_border = RND(8); /* select one of the eight border elements */
9026 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9027 int xx = pos_content % 3;
9028 int yy = pos_content / 3;
9033 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9034 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9038 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9040 int xx = x - bx + 1;
9041 int yy = y - by + 1;
9043 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9044 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9048 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9051 void CheckExit(int x, int y)
9053 if (local_player->gems_still_needed > 0 ||
9054 local_player->sokobanfields_still_needed > 0 ||
9055 local_player->lights_still_needed > 0)
9057 int element = Feld[x][y];
9058 int graphic = el2img(element);
9060 if (IS_ANIMATED(graphic))
9061 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9066 if (AllPlayersGone) /* do not re-open exit door closed after last player */
9069 Feld[x][y] = EL_EXIT_OPENING;
9071 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9074 void CheckExitEM(int x, int y)
9076 if (local_player->gems_still_needed > 0 ||
9077 local_player->sokobanfields_still_needed > 0 ||
9078 local_player->lights_still_needed > 0)
9080 int element = Feld[x][y];
9081 int graphic = el2img(element);
9083 if (IS_ANIMATED(graphic))
9084 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9089 if (AllPlayersGone) /* do not re-open exit door closed after last player */
9092 Feld[x][y] = EL_EM_EXIT_OPENING;
9094 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9097 void CheckExitSteel(int x, int y)
9099 if (local_player->gems_still_needed > 0 ||
9100 local_player->sokobanfields_still_needed > 0 ||
9101 local_player->lights_still_needed > 0)
9103 int element = Feld[x][y];
9104 int graphic = el2img(element);
9106 if (IS_ANIMATED(graphic))
9107 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9112 if (AllPlayersGone) /* do not re-open exit door closed after last player */
9115 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9117 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9120 void CheckExitSteelEM(int x, int y)
9122 if (local_player->gems_still_needed > 0 ||
9123 local_player->sokobanfields_still_needed > 0 ||
9124 local_player->lights_still_needed > 0)
9126 int element = Feld[x][y];
9127 int graphic = el2img(element);
9129 if (IS_ANIMATED(graphic))
9130 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9135 if (AllPlayersGone) /* do not re-open exit door closed after last player */
9138 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9140 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9143 void CheckExitSP(int x, int y)
9145 if (local_player->gems_still_needed > 0)
9147 int element = Feld[x][y];
9148 int graphic = el2img(element);
9150 if (IS_ANIMATED(graphic))
9151 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9156 if (AllPlayersGone) /* do not re-open exit door closed after last player */
9159 Feld[x][y] = EL_SP_EXIT_OPENING;
9161 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9164 static void CloseAllOpenTimegates()
9168 SCAN_PLAYFIELD(x, y)
9170 int element = Feld[x][y];
9172 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9174 Feld[x][y] = EL_TIMEGATE_CLOSING;
9176 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9181 void DrawTwinkleOnField(int x, int y)
9183 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9186 if (Feld[x][y] == EL_BD_DIAMOND)
9189 if (MovDelay[x][y] == 0) /* next animation frame */
9190 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9192 if (MovDelay[x][y] != 0) /* wait some time before next frame */
9196 DrawLevelElementAnimation(x, y, Feld[x][y]);
9198 if (MovDelay[x][y] != 0)
9200 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9201 10 - MovDelay[x][y]);
9203 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9208 void MauerWaechst(int x, int y)
9212 if (!MovDelay[x][y]) /* next animation frame */
9213 MovDelay[x][y] = 3 * delay;
9215 if (MovDelay[x][y]) /* wait some time before next frame */
9219 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9221 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9222 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9224 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9227 if (!MovDelay[x][y])
9229 if (MovDir[x][y] == MV_LEFT)
9231 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9232 TEST_DrawLevelField(x - 1, y);
9234 else if (MovDir[x][y] == MV_RIGHT)
9236 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9237 TEST_DrawLevelField(x + 1, y);
9239 else if (MovDir[x][y] == MV_UP)
9241 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9242 TEST_DrawLevelField(x, y - 1);
9246 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9247 TEST_DrawLevelField(x, y + 1);
9250 Feld[x][y] = Store[x][y];
9252 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9253 TEST_DrawLevelField(x, y);
9258 void MauerAbleger(int ax, int ay)
9260 int element = Feld[ax][ay];
9261 int graphic = el2img(element);
9262 boolean oben_frei = FALSE, unten_frei = FALSE;
9263 boolean links_frei = FALSE, rechts_frei = FALSE;
9264 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9265 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9266 boolean new_wall = FALSE;
9268 if (IS_ANIMATED(graphic))
9269 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9271 if (!MovDelay[ax][ay]) /* start building new wall */
9272 MovDelay[ax][ay] = 6;
9274 if (MovDelay[ax][ay]) /* wait some time before building new wall */
9277 if (MovDelay[ax][ay])
9281 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9283 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9285 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9287 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9290 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9291 element == EL_EXPANDABLE_WALL_ANY)
9295 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9296 Store[ax][ay-1] = element;
9297 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9298 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9299 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9300 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9305 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9306 Store[ax][ay+1] = element;
9307 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9308 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9309 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9310 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9315 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9316 element == EL_EXPANDABLE_WALL_ANY ||
9317 element == EL_EXPANDABLE_WALL ||
9318 element == EL_BD_EXPANDABLE_WALL)
9322 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9323 Store[ax-1][ay] = element;
9324 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9325 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9326 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9327 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9333 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9334 Store[ax+1][ay] = element;
9335 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9336 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9337 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9338 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9343 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9344 TEST_DrawLevelField(ax, ay);
9346 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9348 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9349 unten_massiv = TRUE;
9350 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9351 links_massiv = TRUE;
9352 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9353 rechts_massiv = TRUE;
9355 if (((oben_massiv && unten_massiv) ||
9356 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9357 element == EL_EXPANDABLE_WALL) &&
9358 ((links_massiv && rechts_massiv) ||
9359 element == EL_EXPANDABLE_WALL_VERTICAL))
9360 Feld[ax][ay] = EL_WALL;
9363 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9366 void MauerAblegerStahl(int ax, int ay)
9368 int element = Feld[ax][ay];
9369 int graphic = el2img(element);
9370 boolean oben_frei = FALSE, unten_frei = FALSE;
9371 boolean links_frei = FALSE, rechts_frei = FALSE;
9372 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9373 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9374 boolean new_wall = FALSE;
9376 if (IS_ANIMATED(graphic))
9377 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9379 if (!MovDelay[ax][ay]) /* start building new wall */
9380 MovDelay[ax][ay] = 6;
9382 if (MovDelay[ax][ay]) /* wait some time before building new wall */
9385 if (MovDelay[ax][ay])
9389 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9391 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9393 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9395 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9398 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9399 element == EL_EXPANDABLE_STEELWALL_ANY)
9403 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9404 Store[ax][ay-1] = element;
9405 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9406 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9407 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9408 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9413 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9414 Store[ax][ay+1] = element;
9415 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9416 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9417 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9418 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9423 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9424 element == EL_EXPANDABLE_STEELWALL_ANY)
9428 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9429 Store[ax-1][ay] = element;
9430 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9431 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9432 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9433 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9439 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9440 Store[ax+1][ay] = element;
9441 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9442 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9443 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9444 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9449 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9451 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9452 unten_massiv = TRUE;
9453 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9454 links_massiv = TRUE;
9455 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9456 rechts_massiv = TRUE;
9458 if (((oben_massiv && unten_massiv) ||
9459 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9460 ((links_massiv && rechts_massiv) ||
9461 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9462 Feld[ax][ay] = EL_STEELWALL;
9465 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9468 void CheckForDragon(int x, int y)
9471 boolean dragon_found = FALSE;
9472 static int xy[4][2] =
9480 for (i = 0; i < NUM_DIRECTIONS; i++)
9482 for (j = 0; j < 4; j++)
9484 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9486 if (IN_LEV_FIELD(xx, yy) &&
9487 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9489 if (Feld[xx][yy] == EL_DRAGON)
9490 dragon_found = TRUE;
9499 for (i = 0; i < NUM_DIRECTIONS; i++)
9501 for (j = 0; j < 3; j++)
9503 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9505 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9507 Feld[xx][yy] = EL_EMPTY;
9508 TEST_DrawLevelField(xx, yy);
9517 static void InitBuggyBase(int x, int y)
9519 int element = Feld[x][y];
9520 int activating_delay = FRAMES_PER_SECOND / 4;
9523 (element == EL_SP_BUGGY_BASE ?
9524 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9525 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9527 element == EL_SP_BUGGY_BASE_ACTIVE ?
9528 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9531 static void WarnBuggyBase(int x, int y)
9534 static int xy[4][2] =
9542 for (i = 0; i < NUM_DIRECTIONS; i++)
9544 int xx = x + xy[i][0];
9545 int yy = y + xy[i][1];
9547 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9549 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9556 static void InitTrap(int x, int y)
9558 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9561 static void ActivateTrap(int x, int y)
9563 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9566 static void ChangeActiveTrap(int x, int y)
9568 int graphic = IMG_TRAP_ACTIVE;
9570 /* if new animation frame was drawn, correct crumbled sand border */
9571 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9572 TEST_DrawLevelFieldCrumbled(x, y);
9575 static int getSpecialActionElement(int element, int number, int base_element)
9577 return (element != EL_EMPTY ? element :
9578 number != -1 ? base_element + number - 1 :
9582 static int getModifiedActionNumber(int value_old, int operator, int operand,
9583 int value_min, int value_max)
9585 int value_new = (operator == CA_MODE_SET ? operand :
9586 operator == CA_MODE_ADD ? value_old + operand :
9587 operator == CA_MODE_SUBTRACT ? value_old - operand :
9588 operator == CA_MODE_MULTIPLY ? value_old * operand :
9589 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9590 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9593 return (value_new < value_min ? value_min :
9594 value_new > value_max ? value_max :
9598 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9600 struct ElementInfo *ei = &element_info[element];
9601 struct ElementChangeInfo *change = &ei->change_page[page];
9602 int target_element = change->target_element;
9603 int action_type = change->action_type;
9604 int action_mode = change->action_mode;
9605 int action_arg = change->action_arg;
9606 int action_element = change->action_element;
9609 if (!change->has_action)
9612 /* ---------- determine action paramater values -------------------------- */
9614 int level_time_value =
9615 (level.time > 0 ? TimeLeft :
9618 int action_arg_element_raw =
9619 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9620 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9621 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9622 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9623 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9624 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9625 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9627 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9629 int action_arg_direction =
9630 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9631 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9632 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9633 change->actual_trigger_side :
9634 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9635 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9638 int action_arg_number_min =
9639 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9642 int action_arg_number_max =
9643 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9644 action_type == CA_SET_LEVEL_GEMS ? 999 :
9645 action_type == CA_SET_LEVEL_TIME ? 9999 :
9646 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9647 action_type == CA_SET_CE_VALUE ? 9999 :
9648 action_type == CA_SET_CE_SCORE ? 9999 :
9651 int action_arg_number_reset =
9652 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9653 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9654 action_type == CA_SET_LEVEL_TIME ? level.time :
9655 action_type == CA_SET_LEVEL_SCORE ? 0 :
9656 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9657 action_type == CA_SET_CE_SCORE ? 0 :
9660 int action_arg_number =
9661 (action_arg <= CA_ARG_MAX ? action_arg :
9662 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9663 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9664 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9665 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9666 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9667 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9668 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9669 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9670 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9671 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9672 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? local_player->gems_still_needed :
9673 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->score :
9674 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9675 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9676 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9677 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9678 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9679 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9680 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9681 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9682 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9685 int action_arg_number_old =
9686 (action_type == CA_SET_LEVEL_GEMS ? local_player->gems_still_needed :
9687 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9688 action_type == CA_SET_LEVEL_SCORE ? local_player->score :
9689 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9690 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9693 int action_arg_number_new =
9694 getModifiedActionNumber(action_arg_number_old,
9695 action_mode, action_arg_number,
9696 action_arg_number_min, action_arg_number_max);
9698 int trigger_player_bits =
9699 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9700 change->actual_trigger_player_bits : change->trigger_player);
9702 int action_arg_player_bits =
9703 (action_arg >= CA_ARG_PLAYER_1 &&
9704 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9705 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9706 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9709 /* ---------- execute action -------------------------------------------- */
9711 switch (action_type)
9718 /* ---------- level actions ------------------------------------------- */
9720 case CA_RESTART_LEVEL:
9722 game.restart_level = TRUE;
9727 case CA_SHOW_ENVELOPE:
9729 int element = getSpecialActionElement(action_arg_element,
9730 action_arg_number, EL_ENVELOPE_1);
9732 if (IS_ENVELOPE(element))
9733 local_player->show_envelope = element;
9738 case CA_SET_LEVEL_TIME:
9740 if (level.time > 0) /* only modify limited time value */
9742 TimeLeft = action_arg_number_new;
9744 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9746 DisplayGameControlValues();
9748 if (!TimeLeft && setup.time_limit)
9749 for (i = 0; i < MAX_PLAYERS; i++)
9750 KillPlayer(&stored_player[i]);
9756 case CA_SET_LEVEL_SCORE:
9758 local_player->score = action_arg_number_new;
9760 game_panel_controls[GAME_PANEL_SCORE].value = local_player->score;
9762 DisplayGameControlValues();
9767 case CA_SET_LEVEL_GEMS:
9769 local_player->gems_still_needed = action_arg_number_new;
9771 game.snapshot.collected_item = TRUE;
9773 game_panel_controls[GAME_PANEL_GEMS].value =
9774 local_player->gems_still_needed;
9776 DisplayGameControlValues();
9781 case CA_SET_LEVEL_WIND:
9783 game.wind_direction = action_arg_direction;
9788 case CA_SET_LEVEL_RANDOM_SEED:
9790 /* ensure that setting a new random seed while playing is predictable */
9791 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9796 /* ---------- player actions ------------------------------------------ */
9798 case CA_MOVE_PLAYER:
9800 /* automatically move to the next field in specified direction */
9801 for (i = 0; i < MAX_PLAYERS; i++)
9802 if (trigger_player_bits & (1 << i))
9803 stored_player[i].programmed_action = action_arg_direction;
9808 case CA_EXIT_PLAYER:
9810 for (i = 0; i < MAX_PLAYERS; i++)
9811 if (action_arg_player_bits & (1 << i))
9812 PlayerWins(&stored_player[i]);
9817 case CA_KILL_PLAYER:
9819 for (i = 0; i < MAX_PLAYERS; i++)
9820 if (action_arg_player_bits & (1 << i))
9821 KillPlayer(&stored_player[i]);
9826 case CA_SET_PLAYER_KEYS:
9828 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
9829 int element = getSpecialActionElement(action_arg_element,
9830 action_arg_number, EL_KEY_1);
9832 if (IS_KEY(element))
9834 for (i = 0; i < MAX_PLAYERS; i++)
9836 if (trigger_player_bits & (1 << i))
9838 stored_player[i].key[KEY_NR(element)] = key_state;
9840 DrawGameDoorValues();
9848 case CA_SET_PLAYER_SPEED:
9850 for (i = 0; i < MAX_PLAYERS; i++)
9852 if (trigger_player_bits & (1 << i))
9854 int move_stepsize = TILEX / stored_player[i].move_delay_value;
9856 if (action_arg == CA_ARG_SPEED_FASTER &&
9857 stored_player[i].cannot_move)
9859 action_arg_number = STEPSIZE_VERY_SLOW;
9861 else if (action_arg == CA_ARG_SPEED_SLOWER ||
9862 action_arg == CA_ARG_SPEED_FASTER)
9864 action_arg_number = 2;
9865 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
9868 else if (action_arg == CA_ARG_NUMBER_RESET)
9870 action_arg_number = level.initial_player_stepsize[i];
9874 getModifiedActionNumber(move_stepsize,
9877 action_arg_number_min,
9878 action_arg_number_max);
9880 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
9887 case CA_SET_PLAYER_SHIELD:
9889 for (i = 0; i < MAX_PLAYERS; i++)
9891 if (trigger_player_bits & (1 << i))
9893 if (action_arg == CA_ARG_SHIELD_OFF)
9895 stored_player[i].shield_normal_time_left = 0;
9896 stored_player[i].shield_deadly_time_left = 0;
9898 else if (action_arg == CA_ARG_SHIELD_NORMAL)
9900 stored_player[i].shield_normal_time_left = 999999;
9902 else if (action_arg == CA_ARG_SHIELD_DEADLY)
9904 stored_player[i].shield_normal_time_left = 999999;
9905 stored_player[i].shield_deadly_time_left = 999999;
9913 case CA_SET_PLAYER_GRAVITY:
9915 for (i = 0; i < MAX_PLAYERS; i++)
9917 if (trigger_player_bits & (1 << i))
9919 stored_player[i].gravity =
9920 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
9921 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
9922 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
9923 stored_player[i].gravity);
9930 case CA_SET_PLAYER_ARTWORK:
9932 for (i = 0; i < MAX_PLAYERS; i++)
9934 if (trigger_player_bits & (1 << i))
9936 int artwork_element = action_arg_element;
9938 if (action_arg == CA_ARG_ELEMENT_RESET)
9940 (level.use_artwork_element[i] ? level.artwork_element[i] :
9941 stored_player[i].element_nr);
9943 if (stored_player[i].artwork_element != artwork_element)
9944 stored_player[i].Frame = 0;
9946 stored_player[i].artwork_element = artwork_element;
9948 SetPlayerWaiting(&stored_player[i], FALSE);
9950 /* set number of special actions for bored and sleeping animation */
9951 stored_player[i].num_special_action_bored =
9952 get_num_special_action(artwork_element,
9953 ACTION_BORING_1, ACTION_BORING_LAST);
9954 stored_player[i].num_special_action_sleeping =
9955 get_num_special_action(artwork_element,
9956 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
9963 case CA_SET_PLAYER_INVENTORY:
9965 for (i = 0; i < MAX_PLAYERS; i++)
9967 struct PlayerInfo *player = &stored_player[i];
9970 if (trigger_player_bits & (1 << i))
9972 int inventory_element = action_arg_element;
9974 if (action_arg == CA_ARG_ELEMENT_TARGET ||
9975 action_arg == CA_ARG_ELEMENT_TRIGGER ||
9976 action_arg == CA_ARG_ELEMENT_ACTION)
9978 int element = inventory_element;
9979 int collect_count = element_info[element].collect_count_initial;
9981 if (!IS_CUSTOM_ELEMENT(element))
9984 if (collect_count == 0)
9985 player->inventory_infinite_element = element;
9987 for (k = 0; k < collect_count; k++)
9988 if (player->inventory_size < MAX_INVENTORY_SIZE)
9989 player->inventory_element[player->inventory_size++] =
9992 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
9993 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
9994 action_arg == CA_ARG_INVENTORY_RM_ACTION)
9996 if (player->inventory_infinite_element != EL_UNDEFINED &&
9997 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
9998 action_arg_element_raw))
9999 player->inventory_infinite_element = EL_UNDEFINED;
10001 for (k = 0, j = 0; j < player->inventory_size; j++)
10003 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10004 action_arg_element_raw))
10005 player->inventory_element[k++] = player->inventory_element[j];
10008 player->inventory_size = k;
10010 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10012 if (player->inventory_size > 0)
10014 for (j = 0; j < player->inventory_size - 1; j++)
10015 player->inventory_element[j] = player->inventory_element[j + 1];
10017 player->inventory_size--;
10020 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10022 if (player->inventory_size > 0)
10023 player->inventory_size--;
10025 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10027 player->inventory_infinite_element = EL_UNDEFINED;
10028 player->inventory_size = 0;
10030 else if (action_arg == CA_ARG_INVENTORY_RESET)
10032 player->inventory_infinite_element = EL_UNDEFINED;
10033 player->inventory_size = 0;
10035 if (level.use_initial_inventory[i])
10037 for (j = 0; j < level.initial_inventory_size[i]; j++)
10039 int element = level.initial_inventory_content[i][j];
10040 int collect_count = element_info[element].collect_count_initial;
10042 if (!IS_CUSTOM_ELEMENT(element))
10045 if (collect_count == 0)
10046 player->inventory_infinite_element = element;
10048 for (k = 0; k < collect_count; k++)
10049 if (player->inventory_size < MAX_INVENTORY_SIZE)
10050 player->inventory_element[player->inventory_size++] =
10061 /* ---------- CE actions ---------------------------------------------- */
10063 case CA_SET_CE_VALUE:
10065 int last_ce_value = CustomValue[x][y];
10067 CustomValue[x][y] = action_arg_number_new;
10069 if (CustomValue[x][y] != last_ce_value)
10071 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10072 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10074 if (CustomValue[x][y] == 0)
10076 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10077 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10084 case CA_SET_CE_SCORE:
10086 int last_ce_score = ei->collect_score;
10088 ei->collect_score = action_arg_number_new;
10090 if (ei->collect_score != last_ce_score)
10092 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10093 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10095 if (ei->collect_score == 0)
10099 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10100 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10103 This is a very special case that seems to be a mixture between
10104 CheckElementChange() and CheckTriggeredElementChange(): while
10105 the first one only affects single elements that are triggered
10106 directly, the second one affects multiple elements in the playfield
10107 that are triggered indirectly by another element. This is a third
10108 case: Changing the CE score always affects multiple identical CEs,
10109 so every affected CE must be checked, not only the single CE for
10110 which the CE score was changed in the first place (as every instance
10111 of that CE shares the same CE score, and therefore also can change)!
10113 SCAN_PLAYFIELD(xx, yy)
10115 if (Feld[xx][yy] == element)
10116 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10117 CE_SCORE_GETS_ZERO);
10125 case CA_SET_CE_ARTWORK:
10127 int artwork_element = action_arg_element;
10128 boolean reset_frame = FALSE;
10131 if (action_arg == CA_ARG_ELEMENT_RESET)
10132 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10135 if (ei->gfx_element != artwork_element)
10136 reset_frame = TRUE;
10138 ei->gfx_element = artwork_element;
10140 SCAN_PLAYFIELD(xx, yy)
10142 if (Feld[xx][yy] == element)
10146 ResetGfxAnimation(xx, yy);
10147 ResetRandomAnimationValue(xx, yy);
10150 TEST_DrawLevelField(xx, yy);
10157 /* ---------- engine actions ------------------------------------------ */
10159 case CA_SET_ENGINE_SCAN_MODE:
10161 InitPlayfieldScanMode(action_arg);
10171 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10173 int old_element = Feld[x][y];
10174 int new_element = GetElementFromGroupElement(element);
10175 int previous_move_direction = MovDir[x][y];
10176 int last_ce_value = CustomValue[x][y];
10177 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10178 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10179 boolean add_player_onto_element = (new_element_is_player &&
10180 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10181 IS_WALKABLE(old_element));
10183 if (!add_player_onto_element)
10185 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10186 RemoveMovingField(x, y);
10190 Feld[x][y] = new_element;
10192 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10193 MovDir[x][y] = previous_move_direction;
10195 if (element_info[new_element].use_last_ce_value)
10196 CustomValue[x][y] = last_ce_value;
10198 InitField_WithBug1(x, y, FALSE);
10200 new_element = Feld[x][y]; /* element may have changed */
10202 ResetGfxAnimation(x, y);
10203 ResetRandomAnimationValue(x, y);
10205 TEST_DrawLevelField(x, y);
10207 if (GFX_CRUMBLED(new_element))
10208 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10211 /* check if element under the player changes from accessible to unaccessible
10212 (needed for special case of dropping element which then changes) */
10213 /* (must be checked after creating new element for walkable group elements) */
10214 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10215 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10222 /* "ChangeCount" not set yet to allow "entered by player" change one time */
10223 if (new_element_is_player)
10224 RelocatePlayer(x, y, new_element);
10227 ChangeCount[x][y]++; /* count number of changes in the same frame */
10229 TestIfBadThingTouchesPlayer(x, y);
10230 TestIfPlayerTouchesCustomElement(x, y);
10231 TestIfElementTouchesCustomElement(x, y);
10234 static void CreateField(int x, int y, int element)
10236 CreateFieldExt(x, y, element, FALSE);
10239 static void CreateElementFromChange(int x, int y, int element)
10241 element = GET_VALID_RUNTIME_ELEMENT(element);
10243 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10245 int old_element = Feld[x][y];
10247 /* prevent changed element from moving in same engine frame
10248 unless both old and new element can either fall or move */
10249 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10250 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10254 CreateFieldExt(x, y, element, TRUE);
10257 static boolean ChangeElement(int x, int y, int element, int page)
10259 struct ElementInfo *ei = &element_info[element];
10260 struct ElementChangeInfo *change = &ei->change_page[page];
10261 int ce_value = CustomValue[x][y];
10262 int ce_score = ei->collect_score;
10263 int target_element;
10264 int old_element = Feld[x][y];
10266 /* always use default change event to prevent running into a loop */
10267 if (ChangeEvent[x][y] == -1)
10268 ChangeEvent[x][y] = CE_DELAY;
10270 if (ChangeEvent[x][y] == CE_DELAY)
10272 /* reset actual trigger element, trigger player and action element */
10273 change->actual_trigger_element = EL_EMPTY;
10274 change->actual_trigger_player = EL_EMPTY;
10275 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10276 change->actual_trigger_side = CH_SIDE_NONE;
10277 change->actual_trigger_ce_value = 0;
10278 change->actual_trigger_ce_score = 0;
10281 /* do not change elements more than a specified maximum number of changes */
10282 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10285 ChangeCount[x][y]++; /* count number of changes in the same frame */
10287 if (change->explode)
10294 if (change->use_target_content)
10296 boolean complete_replace = TRUE;
10297 boolean can_replace[3][3];
10300 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10303 boolean is_walkable;
10304 boolean is_diggable;
10305 boolean is_collectible;
10306 boolean is_removable;
10307 boolean is_destructible;
10308 int ex = x + xx - 1;
10309 int ey = y + yy - 1;
10310 int content_element = change->target_content.e[xx][yy];
10313 can_replace[xx][yy] = TRUE;
10315 if (ex == x && ey == y) /* do not check changing element itself */
10318 if (content_element == EL_EMPTY_SPACE)
10320 can_replace[xx][yy] = FALSE; /* do not replace border with space */
10325 if (!IN_LEV_FIELD(ex, ey))
10327 can_replace[xx][yy] = FALSE;
10328 complete_replace = FALSE;
10335 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10336 e = MovingOrBlocked2Element(ex, ey);
10338 is_empty = (IS_FREE(ex, ey) ||
10339 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10341 is_walkable = (is_empty || IS_WALKABLE(e));
10342 is_diggable = (is_empty || IS_DIGGABLE(e));
10343 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10344 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10345 is_removable = (is_diggable || is_collectible);
10347 can_replace[xx][yy] =
10348 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10349 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10350 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10351 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10352 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10353 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10354 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10356 if (!can_replace[xx][yy])
10357 complete_replace = FALSE;
10360 if (!change->only_if_complete || complete_replace)
10362 boolean something_has_changed = FALSE;
10364 if (change->only_if_complete && change->use_random_replace &&
10365 RND(100) < change->random_percentage)
10368 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10370 int ex = x + xx - 1;
10371 int ey = y + yy - 1;
10372 int content_element;
10374 if (can_replace[xx][yy] && (!change->use_random_replace ||
10375 RND(100) < change->random_percentage))
10377 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10378 RemoveMovingField(ex, ey);
10380 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10382 content_element = change->target_content.e[xx][yy];
10383 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10384 ce_value, ce_score);
10386 CreateElementFromChange(ex, ey, target_element);
10388 something_has_changed = TRUE;
10390 /* for symmetry reasons, freeze newly created border elements */
10391 if (ex != x || ey != y)
10392 Stop[ex][ey] = TRUE; /* no more moving in this frame */
10396 if (something_has_changed)
10398 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10399 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10405 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10406 ce_value, ce_score);
10408 if (element == EL_DIAGONAL_GROWING ||
10409 element == EL_DIAGONAL_SHRINKING)
10411 target_element = Store[x][y];
10413 Store[x][y] = EL_EMPTY;
10416 CreateElementFromChange(x, y, target_element);
10418 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10419 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10422 /* this uses direct change before indirect change */
10423 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10428 static void HandleElementChange(int x, int y, int page)
10430 int element = MovingOrBlocked2Element(x, y);
10431 struct ElementInfo *ei = &element_info[element];
10432 struct ElementChangeInfo *change = &ei->change_page[page];
10433 boolean handle_action_before_change = FALSE;
10436 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10437 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10440 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10441 x, y, element, element_info[element].token_name);
10442 printf("HandleElementChange(): This should never happen!\n");
10447 /* this can happen with classic bombs on walkable, changing elements */
10448 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10453 if (ChangeDelay[x][y] == 0) /* initialize element change */
10455 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10457 if (change->can_change)
10459 /* !!! not clear why graphic animation should be reset at all here !!! */
10460 /* !!! UPDATE: but is needed for correct Snake Bite tail animation !!! */
10461 /* !!! SOLUTION: do not reset if graphics engine set to 4 or above !!! */
10464 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10466 When using an animation frame delay of 1 (this only happens with
10467 "sp_zonk.moving.left/right" in the classic graphics), the default
10468 (non-moving) animation shows wrong animation frames (while the
10469 moving animation, like "sp_zonk.moving.left/right", is correct,
10470 so this graphical bug never shows up with the classic graphics).
10471 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10472 be drawn instead of the correct frames 0,1,2,3. This is caused by
10473 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10474 an element change: First when the change delay ("ChangeDelay[][]")
10475 counter has reached zero after decrementing, then a second time in
10476 the next frame (after "GfxFrame[][]" was already incremented) when
10477 "ChangeDelay[][]" is reset to the initial delay value again.
10479 This causes frame 0 to be drawn twice, while the last frame won't
10480 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10482 As some animations may already be cleverly designed around this bug
10483 (at least the "Snake Bite" snake tail animation does this), it cannot
10484 simply be fixed here without breaking such existing animations.
10485 Unfortunately, it cannot easily be detected if a graphics set was
10486 designed "before" or "after" the bug was fixed. As a workaround,
10487 a new graphics set option "game.graphics_engine_version" was added
10488 to be able to specify the game's major release version for which the
10489 graphics set was designed, which can then be used to decide if the
10490 bugfix should be used (version 4 and above) or not (version 3 or
10491 below, or if no version was specified at all, as with old sets).
10493 (The wrong/fixed animation frames can be tested with the test level set
10494 "test_gfxframe" and level "000", which contains a specially prepared
10495 custom element at level position (x/y) == (11/9) which uses the zonk
10496 animation mentioned above. Using "game.graphics_engine_version: 4"
10497 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10498 This can also be seen from the debug output for this test element.)
10501 /* when a custom element is about to change (for example by change delay),
10502 do not reset graphic animation when the custom element is moving */
10503 if (game.graphics_engine_version < 4 &&
10506 ResetGfxAnimation(x, y);
10507 ResetRandomAnimationValue(x, y);
10510 if (change->pre_change_function)
10511 change->pre_change_function(x, y);
10515 ChangeDelay[x][y]--;
10517 if (ChangeDelay[x][y] != 0) /* continue element change */
10519 if (change->can_change)
10521 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10523 if (IS_ANIMATED(graphic))
10524 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10526 if (change->change_function)
10527 change->change_function(x, y);
10530 else /* finish element change */
10532 if (ChangePage[x][y] != -1) /* remember page from delayed change */
10534 page = ChangePage[x][y];
10535 ChangePage[x][y] = -1;
10537 change = &ei->change_page[page];
10540 if (IS_MOVING(x, y)) /* never change a running system ;-) */
10542 ChangeDelay[x][y] = 1; /* try change after next move step */
10543 ChangePage[x][y] = page; /* remember page to use for change */
10548 /* special case: set new level random seed before changing element */
10549 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10550 handle_action_before_change = TRUE;
10552 if (change->has_action && handle_action_before_change)
10553 ExecuteCustomElementAction(x, y, element, page);
10555 if (change->can_change)
10557 if (ChangeElement(x, y, element, page))
10559 if (change->post_change_function)
10560 change->post_change_function(x, y);
10564 if (change->has_action && !handle_action_before_change)
10565 ExecuteCustomElementAction(x, y, element, page);
10569 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10570 int trigger_element,
10572 int trigger_player,
10576 boolean change_done_any = FALSE;
10577 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10580 if (!(trigger_events[trigger_element][trigger_event]))
10583 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10585 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10587 int element = EL_CUSTOM_START + i;
10588 boolean change_done = FALSE;
10591 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10592 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10595 for (p = 0; p < element_info[element].num_change_pages; p++)
10597 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10599 if (change->can_change_or_has_action &&
10600 change->has_event[trigger_event] &&
10601 change->trigger_side & trigger_side &&
10602 change->trigger_player & trigger_player &&
10603 change->trigger_page & trigger_page_bits &&
10604 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10606 change->actual_trigger_element = trigger_element;
10607 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10608 change->actual_trigger_player_bits = trigger_player;
10609 change->actual_trigger_side = trigger_side;
10610 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10611 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10613 if ((change->can_change && !change_done) || change->has_action)
10617 SCAN_PLAYFIELD(x, y)
10619 if (Feld[x][y] == element)
10621 if (change->can_change && !change_done)
10623 /* if element already changed in this frame, not only prevent
10624 another element change (checked in ChangeElement()), but
10625 also prevent additional element actions for this element */
10627 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10628 !level.use_action_after_change_bug)
10631 ChangeDelay[x][y] = 1;
10632 ChangeEvent[x][y] = trigger_event;
10634 HandleElementChange(x, y, p);
10636 else if (change->has_action)
10638 /* if element already changed in this frame, not only prevent
10639 another element change (checked in ChangeElement()), but
10640 also prevent additional element actions for this element */
10642 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10643 !level.use_action_after_change_bug)
10646 ExecuteCustomElementAction(x, y, element, p);
10647 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10652 if (change->can_change)
10654 change_done = TRUE;
10655 change_done_any = TRUE;
10662 RECURSION_LOOP_DETECTION_END();
10664 return change_done_any;
10667 static boolean CheckElementChangeExt(int x, int y,
10669 int trigger_element,
10671 int trigger_player,
10674 boolean change_done = FALSE;
10677 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10678 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10681 if (Feld[x][y] == EL_BLOCKED)
10683 Blocked2Moving(x, y, &x, &y);
10684 element = Feld[x][y];
10687 /* check if element has already changed or is about to change after moving */
10688 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10689 Feld[x][y] != element) ||
10691 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10692 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10693 ChangePage[x][y] != -1)))
10696 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10698 for (p = 0; p < element_info[element].num_change_pages; p++)
10700 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10702 /* check trigger element for all events where the element that is checked
10703 for changing interacts with a directly adjacent element -- this is
10704 different to element changes that affect other elements to change on the
10705 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10706 boolean check_trigger_element =
10707 (trigger_event == CE_TOUCHING_X ||
10708 trigger_event == CE_HITTING_X ||
10709 trigger_event == CE_HIT_BY_X ||
10710 trigger_event == CE_DIGGING_X); /* this one was forgotten until 3.2.3 */
10712 if (change->can_change_or_has_action &&
10713 change->has_event[trigger_event] &&
10714 change->trigger_side & trigger_side &&
10715 change->trigger_player & trigger_player &&
10716 (!check_trigger_element ||
10717 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10719 change->actual_trigger_element = trigger_element;
10720 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10721 change->actual_trigger_player_bits = trigger_player;
10722 change->actual_trigger_side = trigger_side;
10723 change->actual_trigger_ce_value = CustomValue[x][y];
10724 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10726 /* special case: trigger element not at (x,y) position for some events */
10727 if (check_trigger_element)
10739 { 0, 0 }, { 0, 0 }, { 0, 0 },
10743 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10744 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10746 change->actual_trigger_ce_value = CustomValue[xx][yy];
10747 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10750 if (change->can_change && !change_done)
10752 ChangeDelay[x][y] = 1;
10753 ChangeEvent[x][y] = trigger_event;
10755 HandleElementChange(x, y, p);
10757 change_done = TRUE;
10759 else if (change->has_action)
10761 ExecuteCustomElementAction(x, y, element, p);
10762 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10767 RECURSION_LOOP_DETECTION_END();
10769 return change_done;
10772 static void PlayPlayerSound(struct PlayerInfo *player)
10774 int jx = player->jx, jy = player->jy;
10775 int sound_element = player->artwork_element;
10776 int last_action = player->last_action_waiting;
10777 int action = player->action_waiting;
10779 if (player->is_waiting)
10781 if (action != last_action)
10782 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10784 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10788 if (action != last_action)
10789 StopSound(element_info[sound_element].sound[last_action]);
10791 if (last_action == ACTION_SLEEPING)
10792 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
10796 static void PlayAllPlayersSound()
10800 for (i = 0; i < MAX_PLAYERS; i++)
10801 if (stored_player[i].active)
10802 PlayPlayerSound(&stored_player[i]);
10805 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
10807 boolean last_waiting = player->is_waiting;
10808 int move_dir = player->MovDir;
10810 player->dir_waiting = move_dir;
10811 player->last_action_waiting = player->action_waiting;
10815 if (!last_waiting) /* not waiting -> waiting */
10817 player->is_waiting = TRUE;
10819 player->frame_counter_bored =
10821 game.player_boring_delay_fixed +
10822 GetSimpleRandom(game.player_boring_delay_random);
10823 player->frame_counter_sleeping =
10825 game.player_sleeping_delay_fixed +
10826 GetSimpleRandom(game.player_sleeping_delay_random);
10828 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
10831 if (game.player_sleeping_delay_fixed +
10832 game.player_sleeping_delay_random > 0 &&
10833 player->anim_delay_counter == 0 &&
10834 player->post_delay_counter == 0 &&
10835 FrameCounter >= player->frame_counter_sleeping)
10836 player->is_sleeping = TRUE;
10837 else if (game.player_boring_delay_fixed +
10838 game.player_boring_delay_random > 0 &&
10839 FrameCounter >= player->frame_counter_bored)
10840 player->is_bored = TRUE;
10842 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
10843 player->is_bored ? ACTION_BORING :
10846 if (player->is_sleeping && player->use_murphy)
10848 /* special case for sleeping Murphy when leaning against non-free tile */
10850 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
10851 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
10852 !IS_MOVING(player->jx - 1, player->jy)))
10853 move_dir = MV_LEFT;
10854 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
10855 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
10856 !IS_MOVING(player->jx + 1, player->jy)))
10857 move_dir = MV_RIGHT;
10859 player->is_sleeping = FALSE;
10861 player->dir_waiting = move_dir;
10864 if (player->is_sleeping)
10866 if (player->num_special_action_sleeping > 0)
10868 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10870 int last_special_action = player->special_action_sleeping;
10871 int num_special_action = player->num_special_action_sleeping;
10872 int special_action =
10873 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
10874 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
10875 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
10876 last_special_action + 1 : ACTION_SLEEPING);
10877 int special_graphic =
10878 el_act_dir2img(player->artwork_element, special_action, move_dir);
10880 player->anim_delay_counter =
10881 graphic_info[special_graphic].anim_delay_fixed +
10882 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10883 player->post_delay_counter =
10884 graphic_info[special_graphic].post_delay_fixed +
10885 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10887 player->special_action_sleeping = special_action;
10890 if (player->anim_delay_counter > 0)
10892 player->action_waiting = player->special_action_sleeping;
10893 player->anim_delay_counter--;
10895 else if (player->post_delay_counter > 0)
10897 player->post_delay_counter--;
10901 else if (player->is_bored)
10903 if (player->num_special_action_bored > 0)
10905 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10907 int special_action =
10908 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
10909 int special_graphic =
10910 el_act_dir2img(player->artwork_element, special_action, move_dir);
10912 player->anim_delay_counter =
10913 graphic_info[special_graphic].anim_delay_fixed +
10914 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10915 player->post_delay_counter =
10916 graphic_info[special_graphic].post_delay_fixed +
10917 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10919 player->special_action_bored = special_action;
10922 if (player->anim_delay_counter > 0)
10924 player->action_waiting = player->special_action_bored;
10925 player->anim_delay_counter--;
10927 else if (player->post_delay_counter > 0)
10929 player->post_delay_counter--;
10934 else if (last_waiting) /* waiting -> not waiting */
10936 player->is_waiting = FALSE;
10937 player->is_bored = FALSE;
10938 player->is_sleeping = FALSE;
10940 player->frame_counter_bored = -1;
10941 player->frame_counter_sleeping = -1;
10943 player->anim_delay_counter = 0;
10944 player->post_delay_counter = 0;
10946 player->dir_waiting = player->MovDir;
10947 player->action_waiting = ACTION_DEFAULT;
10949 player->special_action_bored = ACTION_DEFAULT;
10950 player->special_action_sleeping = ACTION_DEFAULT;
10954 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
10956 if ((!player->is_moving && player->was_moving) ||
10957 (player->MovPos == 0 && player->was_moving) ||
10958 (player->is_snapping && !player->was_snapping) ||
10959 (player->is_dropping && !player->was_dropping))
10961 if (!CheckSaveEngineSnapshotToList())
10964 player->was_moving = FALSE;
10965 player->was_snapping = TRUE;
10966 player->was_dropping = TRUE;
10970 if (player->is_moving)
10971 player->was_moving = TRUE;
10973 if (!player->is_snapping)
10974 player->was_snapping = FALSE;
10976 if (!player->is_dropping)
10977 player->was_dropping = FALSE;
10981 static void CheckSingleStepMode(struct PlayerInfo *player)
10983 if (tape.single_step && tape.recording && !tape.pausing)
10985 /* as it is called "single step mode", just return to pause mode when the
10986 player stopped moving after one tile (or never starts moving at all) */
10987 if (!player->is_moving &&
10988 !player->is_pushing &&
10989 !player->is_dropping_pressed)
10991 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
10992 SnapField(player, 0, 0); /* stop snapping */
10996 CheckSaveEngineSnapshot(player);
10999 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11001 int left = player_action & JOY_LEFT;
11002 int right = player_action & JOY_RIGHT;
11003 int up = player_action & JOY_UP;
11004 int down = player_action & JOY_DOWN;
11005 int button1 = player_action & JOY_BUTTON_1;
11006 int button2 = player_action & JOY_BUTTON_2;
11007 int dx = (left ? -1 : right ? 1 : 0);
11008 int dy = (up ? -1 : down ? 1 : 0);
11010 if (!player->active || tape.pausing)
11016 SnapField(player, dx, dy);
11020 DropElement(player);
11022 MovePlayer(player, dx, dy);
11025 CheckSingleStepMode(player);
11027 SetPlayerWaiting(player, FALSE);
11029 return player_action;
11033 /* no actions for this player (no input at player's configured device) */
11035 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11036 SnapField(player, 0, 0);
11037 CheckGravityMovementWhenNotMoving(player);
11039 if (player->MovPos == 0)
11040 SetPlayerWaiting(player, TRUE);
11042 if (player->MovPos == 0) /* needed for tape.playing */
11043 player->is_moving = FALSE;
11045 player->is_dropping = FALSE;
11046 player->is_dropping_pressed = FALSE;
11047 player->drop_pressed_delay = 0;
11049 CheckSingleStepMode(player);
11055 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11058 if (!tape.use_mouse)
11061 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11062 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11063 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11066 static void SetTapeActionFromMouseAction(byte *tape_action,
11067 struct MouseActionInfo *mouse_action)
11069 if (!tape.use_mouse)
11072 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11073 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11074 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11077 static void CheckLevelTime()
11081 /* !!! SAME CODE AS IN "GameActions()" -- FIX THIS !!! */
11082 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11084 if (level.native_em_level->lev->home == 0) /* all players at home */
11086 PlayerWins(local_player);
11088 AllPlayersGone = TRUE;
11090 level.native_em_level->lev->home = -1;
11093 if (level.native_em_level->ply[0]->alive == 0 &&
11094 level.native_em_level->ply[1]->alive == 0 &&
11095 level.native_em_level->ply[2]->alive == 0 &&
11096 level.native_em_level->ply[3]->alive == 0) /* all dead */
11097 AllPlayersGone = TRUE;
11099 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11101 if (game_sp.LevelSolved &&
11102 !game_sp.GameOver) /* game won */
11104 PlayerWins(local_player);
11106 game_sp.GameOver = TRUE;
11108 AllPlayersGone = TRUE;
11111 if (game_sp.GameOver) /* game lost */
11112 AllPlayersGone = TRUE;
11114 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11116 if (game_mm.level_solved &&
11117 !game_mm.game_over) /* game won */
11119 PlayerWins(local_player);
11121 game_mm.game_over = TRUE;
11123 AllPlayersGone = TRUE;
11126 if (game_mm.game_over) /* game lost */
11127 AllPlayersGone = TRUE;
11130 if (TimeFrames >= FRAMES_PER_SECOND)
11135 for (i = 0; i < MAX_PLAYERS; i++)
11137 struct PlayerInfo *player = &stored_player[i];
11139 if (SHIELD_ON(player))
11141 player->shield_normal_time_left--;
11143 if (player->shield_deadly_time_left > 0)
11144 player->shield_deadly_time_left--;
11148 if (!local_player->LevelSolved && !level.use_step_counter)
11156 if (TimeLeft <= 10 && setup.time_limit)
11157 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11159 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11160 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11162 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11164 if (!TimeLeft && setup.time_limit)
11166 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11167 level.native_em_level->lev->killed_out_of_time = TRUE;
11169 for (i = 0; i < MAX_PLAYERS; i++)
11170 KillPlayer(&stored_player[i]);
11173 else if (game.no_time_limit && !AllPlayersGone) /* level w/o time limit */
11175 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11178 level.native_em_level->lev->time =
11179 (game.no_time_limit ? TimePlayed : TimeLeft);
11182 if (tape.recording || tape.playing)
11183 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11186 if (tape.recording || tape.playing)
11187 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11189 UpdateAndDisplayGameControlValues();
11192 void AdvanceFrameAndPlayerCounters(int player_nr)
11196 /* advance frame counters (global frame counter and time frame counter) */
11200 /* advance player counters (counters for move delay, move animation etc.) */
11201 for (i = 0; i < MAX_PLAYERS; i++)
11203 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11204 int move_delay_value = stored_player[i].move_delay_value;
11205 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11207 if (!advance_player_counters) /* not all players may be affected */
11210 if (move_frames == 0) /* less than one move per game frame */
11212 int stepsize = TILEX / move_delay_value;
11213 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11214 int count = (stored_player[i].is_moving ?
11215 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11217 if (count % delay == 0)
11221 stored_player[i].Frame += move_frames;
11223 if (stored_player[i].MovPos != 0)
11224 stored_player[i].StepFrame += move_frames;
11226 if (stored_player[i].move_delay > 0)
11227 stored_player[i].move_delay--;
11229 /* due to bugs in previous versions, counter must count up, not down */
11230 if (stored_player[i].push_delay != -1)
11231 stored_player[i].push_delay++;
11233 if (stored_player[i].drop_delay > 0)
11234 stored_player[i].drop_delay--;
11236 if (stored_player[i].is_dropping_pressed)
11237 stored_player[i].drop_pressed_delay++;
11241 void StartGameActions(boolean init_network_game, boolean record_tape,
11244 unsigned int new_random_seed = InitRND(random_seed);
11247 TapeStartRecording(new_random_seed);
11249 #if defined(NETWORK_AVALIABLE)
11250 if (init_network_game)
11252 SendToServer_StartPlaying();
11261 void GameActionsExt()
11264 static unsigned int game_frame_delay = 0;
11266 unsigned int game_frame_delay_value;
11267 byte *recorded_player_action;
11268 byte summarized_player_action = 0;
11269 byte tape_action[MAX_PLAYERS];
11272 /* detect endless loops, caused by custom element programming */
11273 if (recursion_loop_detected && recursion_loop_depth == 0)
11275 char *message = getStringCat3("Internal Error! Element ",
11276 EL_NAME(recursion_loop_element),
11277 " caused endless loop! Quit the game?");
11279 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11280 EL_NAME(recursion_loop_element));
11282 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11284 recursion_loop_detected = FALSE; /* if game should be continued */
11291 if (game.restart_level)
11292 StartGameActions(options.network, setup.autorecord, level.random_seed);
11294 /* !!! SAME CODE AS IN "CheckLevelTime()" -- FIX THIS !!! */
11295 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11297 if (level.native_em_level->lev->home == 0) /* all players at home */
11299 PlayerWins(local_player);
11301 AllPlayersGone = TRUE;
11303 level.native_em_level->lev->home = -1;
11306 if (level.native_em_level->ply[0]->alive == 0 &&
11307 level.native_em_level->ply[1]->alive == 0 &&
11308 level.native_em_level->ply[2]->alive == 0 &&
11309 level.native_em_level->ply[3]->alive == 0) /* all dead */
11310 AllPlayersGone = TRUE;
11312 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11314 if (game_sp.LevelSolved &&
11315 !game_sp.GameOver) /* game won */
11317 PlayerWins(local_player);
11319 game_sp.GameOver = TRUE;
11321 AllPlayersGone = TRUE;
11324 if (game_sp.GameOver) /* game lost */
11325 AllPlayersGone = TRUE;
11327 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11329 if (game_mm.level_solved &&
11330 !game_mm.game_over) /* game won */
11332 PlayerWins(local_player);
11334 game_mm.game_over = TRUE;
11336 AllPlayersGone = TRUE;
11339 if (game_mm.game_over) /* game lost */
11340 AllPlayersGone = TRUE;
11343 if (local_player->LevelSolved && !local_player->LevelSolved_GameEnd)
11346 if (AllPlayersGone && !TAPE_IS_STOPPED(tape))
11349 if (game_status != GAME_MODE_PLAYING) /* status might have changed */
11352 game_frame_delay_value =
11353 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11355 if (tape.playing && tape.warp_forward && !tape.pausing)
11356 game_frame_delay_value = 0;
11358 SetVideoFrameDelay(game_frame_delay_value);
11362 /* ---------- main game synchronization point ---------- */
11364 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11366 printf("::: skip == %d\n", skip);
11369 /* ---------- main game synchronization point ---------- */
11371 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11375 if (network_playing && !network_player_action_received)
11377 /* try to get network player actions in time */
11379 #if defined(NETWORK_AVALIABLE)
11380 /* last chance to get network player actions without main loop delay */
11381 HandleNetworking();
11384 /* game was quit by network peer */
11385 if (game_status != GAME_MODE_PLAYING)
11388 if (!network_player_action_received)
11389 return; /* failed to get network player actions in time */
11391 /* do not yet reset "network_player_action_received" (for tape.pausing) */
11397 /* at this point we know that we really continue executing the game */
11399 network_player_action_received = FALSE;
11401 /* when playing tape, read previously recorded player input from tape data */
11402 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11404 local_player->effective_mouse_action = local_player->mouse_action;
11406 if (recorded_player_action != NULL)
11407 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11408 recorded_player_action);
11410 /* TapePlayAction() may return NULL when toggling to "pause before death" */
11414 if (tape.set_centered_player)
11416 game.centered_player_nr_next = tape.centered_player_nr_next;
11417 game.set_centered_player = TRUE;
11420 for (i = 0; i < MAX_PLAYERS; i++)
11422 summarized_player_action |= stored_player[i].action;
11424 if (!network_playing && (game.team_mode || tape.playing))
11425 stored_player[i].effective_action = stored_player[i].action;
11428 #if defined(NETWORK_AVALIABLE)
11429 if (network_playing)
11430 SendToServer_MovePlayer(summarized_player_action);
11433 // summarize all actions at local players mapped input device position
11434 // (this allows using different input devices in single player mode)
11435 if (!options.network && !game.team_mode)
11436 stored_player[map_player_action[local_player->index_nr]].effective_action =
11437 summarized_player_action;
11439 if (tape.recording &&
11441 setup.input_on_focus &&
11442 game.centered_player_nr != -1)
11444 for (i = 0; i < MAX_PLAYERS; i++)
11445 stored_player[i].effective_action =
11446 (i == game.centered_player_nr ? summarized_player_action : 0);
11449 if (recorded_player_action != NULL)
11450 for (i = 0; i < MAX_PLAYERS; i++)
11451 stored_player[i].effective_action = recorded_player_action[i];
11453 for (i = 0; i < MAX_PLAYERS; i++)
11455 tape_action[i] = stored_player[i].effective_action;
11457 /* (this may happen in the RND game engine if a player was not present on
11458 the playfield on level start, but appeared later from a custom element */
11459 if (setup.team_mode &&
11462 !tape.player_participates[i])
11463 tape.player_participates[i] = TRUE;
11466 SetTapeActionFromMouseAction(tape_action,
11467 &local_player->effective_mouse_action);
11469 /* only record actions from input devices, but not programmed actions */
11470 if (tape.recording)
11471 TapeRecordAction(tape_action);
11473 #if USE_NEW_PLAYER_ASSIGNMENTS
11474 // !!! also map player actions in single player mode !!!
11475 // if (game.team_mode)
11478 byte mapped_action[MAX_PLAYERS];
11480 #if DEBUG_PLAYER_ACTIONS
11482 for (i = 0; i < MAX_PLAYERS; i++)
11483 printf(" %d, ", stored_player[i].effective_action);
11486 for (i = 0; i < MAX_PLAYERS; i++)
11487 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11489 for (i = 0; i < MAX_PLAYERS; i++)
11490 stored_player[i].effective_action = mapped_action[i];
11492 #if DEBUG_PLAYER_ACTIONS
11494 for (i = 0; i < MAX_PLAYERS; i++)
11495 printf(" %d, ", stored_player[i].effective_action);
11499 #if DEBUG_PLAYER_ACTIONS
11503 for (i = 0; i < MAX_PLAYERS; i++)
11504 printf(" %d, ", stored_player[i].effective_action);
11510 for (i = 0; i < MAX_PLAYERS; i++)
11512 // allow engine snapshot in case of changed movement attempt
11513 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11514 (stored_player[i].effective_action & KEY_MOTION))
11515 game.snapshot.changed_action = TRUE;
11517 // allow engine snapshot in case of snapping/dropping attempt
11518 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11519 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11520 game.snapshot.changed_action = TRUE;
11522 game.snapshot.last_action[i] = stored_player[i].effective_action;
11525 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11527 GameActions_EM_Main();
11529 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11531 GameActions_SP_Main();
11533 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11535 GameActions_MM_Main();
11539 GameActions_RND_Main();
11542 BlitScreenToBitmap(backbuffer);
11546 AdvanceFrameAndPlayerCounters(-1); /* advance counters for all players */
11548 if (global.show_frames_per_second)
11550 static unsigned int fps_counter = 0;
11551 static int fps_frames = 0;
11552 unsigned int fps_delay_ms = Counter() - fps_counter;
11556 if (fps_delay_ms >= 500) /* calculate FPS every 0.5 seconds */
11558 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11561 fps_counter = Counter();
11563 /* always draw FPS to screen after FPS value was updated */
11564 redraw_mask |= REDRAW_FPS;
11567 /* only draw FPS if no screen areas are deactivated (invisible warp mode) */
11568 if (GetDrawDeactivationMask() == REDRAW_NONE)
11569 redraw_mask |= REDRAW_FPS;
11573 static void GameActions_CheckSaveEngineSnapshot()
11575 if (!game.snapshot.save_snapshot)
11578 // clear flag for saving snapshot _before_ saving snapshot
11579 game.snapshot.save_snapshot = FALSE;
11581 SaveEngineSnapshotToList();
11588 GameActions_CheckSaveEngineSnapshot();
11591 void GameActions_EM_Main()
11593 byte effective_action[MAX_PLAYERS];
11594 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11597 for (i = 0; i < MAX_PLAYERS; i++)
11598 effective_action[i] = stored_player[i].effective_action;
11600 GameActions_EM(effective_action, warp_mode);
11603 void GameActions_SP_Main()
11605 byte effective_action[MAX_PLAYERS];
11606 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11609 for (i = 0; i < MAX_PLAYERS; i++)
11610 effective_action[i] = stored_player[i].effective_action;
11612 GameActions_SP(effective_action, warp_mode);
11614 for (i = 0; i < MAX_PLAYERS; i++)
11616 if (stored_player[i].force_dropping)
11617 stored_player[i].action |= KEY_BUTTON_DROP;
11619 stored_player[i].force_dropping = FALSE;
11623 void GameActions_MM_Main()
11625 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11627 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11630 void GameActions_RND_Main()
11635 void GameActions_RND()
11637 int magic_wall_x = 0, magic_wall_y = 0;
11638 int i, x, y, element, graphic, last_gfx_frame;
11640 InitPlayfieldScanModeVars();
11642 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11644 SCAN_PLAYFIELD(x, y)
11646 ChangeCount[x][y] = 0;
11647 ChangeEvent[x][y] = -1;
11651 if (game.set_centered_player)
11653 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11655 /* switching to "all players" only possible if all players fit to screen */
11656 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11658 game.centered_player_nr_next = game.centered_player_nr;
11659 game.set_centered_player = FALSE;
11662 /* do not switch focus to non-existing (or non-active) player */
11663 if (game.centered_player_nr_next >= 0 &&
11664 !stored_player[game.centered_player_nr_next].active)
11666 game.centered_player_nr_next = game.centered_player_nr;
11667 game.set_centered_player = FALSE;
11671 if (game.set_centered_player &&
11672 ScreenMovPos == 0) /* screen currently aligned at tile position */
11676 if (game.centered_player_nr_next == -1)
11678 setScreenCenteredToAllPlayers(&sx, &sy);
11682 sx = stored_player[game.centered_player_nr_next].jx;
11683 sy = stored_player[game.centered_player_nr_next].jy;
11686 game.centered_player_nr = game.centered_player_nr_next;
11687 game.set_centered_player = FALSE;
11689 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11690 DrawGameDoorValues();
11693 for (i = 0; i < MAX_PLAYERS; i++)
11695 int actual_player_action = stored_player[i].effective_action;
11698 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11699 - rnd_equinox_tetrachloride 048
11700 - rnd_equinox_tetrachloride_ii 096
11701 - rnd_emanuel_schmieg 002
11702 - doctor_sloan_ww 001, 020
11704 if (stored_player[i].MovPos == 0)
11705 CheckGravityMovement(&stored_player[i]);
11708 /* overwrite programmed action with tape action */
11709 if (stored_player[i].programmed_action)
11710 actual_player_action = stored_player[i].programmed_action;
11712 PlayerActions(&stored_player[i], actual_player_action);
11714 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11717 ScrollScreen(NULL, SCROLL_GO_ON);
11719 /* for backwards compatibility, the following code emulates a fixed bug that
11720 occured when pushing elements (causing elements that just made their last
11721 pushing step to already (if possible) make their first falling step in the
11722 same game frame, which is bad); this code is also needed to use the famous
11723 "spring push bug" which is used in older levels and might be wanted to be
11724 used also in newer levels, but in this case the buggy pushing code is only
11725 affecting the "spring" element and no other elements */
11727 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11729 for (i = 0; i < MAX_PLAYERS; i++)
11731 struct PlayerInfo *player = &stored_player[i];
11732 int x = player->jx;
11733 int y = player->jy;
11735 if (player->active && player->is_pushing && player->is_moving &&
11737 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11738 Feld[x][y] == EL_SPRING))
11740 ContinueMoving(x, y);
11742 /* continue moving after pushing (this is actually a bug) */
11743 if (!IS_MOVING(x, y))
11744 Stop[x][y] = FALSE;
11749 SCAN_PLAYFIELD(x, y)
11751 ChangeCount[x][y] = 0;
11752 ChangeEvent[x][y] = -1;
11754 /* this must be handled before main playfield loop */
11755 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11758 if (MovDelay[x][y] <= 0)
11762 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11765 if (MovDelay[x][y] <= 0)
11768 TEST_DrawLevelField(x, y);
11770 TestIfElementTouchesCustomElement(x, y); /* for empty space */
11775 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11777 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11778 printf("GameActions(): This should never happen!\n");
11780 ChangePage[x][y] = -1;
11784 Stop[x][y] = FALSE;
11785 if (WasJustMoving[x][y] > 0)
11786 WasJustMoving[x][y]--;
11787 if (WasJustFalling[x][y] > 0)
11788 WasJustFalling[x][y]--;
11789 if (CheckCollision[x][y] > 0)
11790 CheckCollision[x][y]--;
11791 if (CheckImpact[x][y] > 0)
11792 CheckImpact[x][y]--;
11796 /* reset finished pushing action (not done in ContinueMoving() to allow
11797 continuous pushing animation for elements with zero push delay) */
11798 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11800 ResetGfxAnimation(x, y);
11801 TEST_DrawLevelField(x, y);
11805 if (IS_BLOCKED(x, y))
11809 Blocked2Moving(x, y, &oldx, &oldy);
11810 if (!IS_MOVING(oldx, oldy))
11812 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11813 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11814 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11815 printf("GameActions(): This should never happen!\n");
11821 SCAN_PLAYFIELD(x, y)
11823 element = Feld[x][y];
11824 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11825 last_gfx_frame = GfxFrame[x][y];
11827 ResetGfxFrame(x, y);
11829 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
11830 DrawLevelGraphicAnimation(x, y, graphic);
11832 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
11833 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
11834 ResetRandomAnimationValue(x, y);
11836 SetRandomAnimationValue(x, y);
11838 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
11840 if (IS_INACTIVE(element))
11842 if (IS_ANIMATED(graphic))
11843 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11848 /* this may take place after moving, so 'element' may have changed */
11849 if (IS_CHANGING(x, y) &&
11850 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
11852 int page = element_info[element].event_page_nr[CE_DELAY];
11854 HandleElementChange(x, y, page);
11856 element = Feld[x][y];
11857 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11860 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
11864 element = Feld[x][y];
11865 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11867 if (IS_ANIMATED(graphic) &&
11868 !IS_MOVING(x, y) &&
11870 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11872 if (IS_GEM(element) || element == EL_SP_INFOTRON)
11873 TEST_DrawTwinkleOnField(x, y);
11875 else if (element == EL_ACID)
11878 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11880 else if ((element == EL_EXIT_OPEN ||
11881 element == EL_EM_EXIT_OPEN ||
11882 element == EL_SP_EXIT_OPEN ||
11883 element == EL_STEEL_EXIT_OPEN ||
11884 element == EL_EM_STEEL_EXIT_OPEN ||
11885 element == EL_SP_TERMINAL ||
11886 element == EL_SP_TERMINAL_ACTIVE ||
11887 element == EL_EXTRA_TIME ||
11888 element == EL_SHIELD_NORMAL ||
11889 element == EL_SHIELD_DEADLY) &&
11890 IS_ANIMATED(graphic))
11891 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11892 else if (IS_MOVING(x, y))
11893 ContinueMoving(x, y);
11894 else if (IS_ACTIVE_BOMB(element))
11895 CheckDynamite(x, y);
11896 else if (element == EL_AMOEBA_GROWING)
11897 AmoebeWaechst(x, y);
11898 else if (element == EL_AMOEBA_SHRINKING)
11899 AmoebaDisappearing(x, y);
11901 #if !USE_NEW_AMOEBA_CODE
11902 else if (IS_AMOEBALIVE(element))
11903 AmoebeAbleger(x, y);
11906 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
11908 else if (element == EL_EXIT_CLOSED)
11910 else if (element == EL_EM_EXIT_CLOSED)
11912 else if (element == EL_STEEL_EXIT_CLOSED)
11913 CheckExitSteel(x, y);
11914 else if (element == EL_EM_STEEL_EXIT_CLOSED)
11915 CheckExitSteelEM(x, y);
11916 else if (element == EL_SP_EXIT_CLOSED)
11918 else if (element == EL_EXPANDABLE_WALL_GROWING ||
11919 element == EL_EXPANDABLE_STEELWALL_GROWING)
11920 MauerWaechst(x, y);
11921 else if (element == EL_EXPANDABLE_WALL ||
11922 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
11923 element == EL_EXPANDABLE_WALL_VERTICAL ||
11924 element == EL_EXPANDABLE_WALL_ANY ||
11925 element == EL_BD_EXPANDABLE_WALL)
11926 MauerAbleger(x, y);
11927 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
11928 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
11929 element == EL_EXPANDABLE_STEELWALL_ANY)
11930 MauerAblegerStahl(x, y);
11931 else if (element == EL_FLAMES)
11932 CheckForDragon(x, y);
11933 else if (element == EL_EXPLOSION)
11934 ; /* drawing of correct explosion animation is handled separately */
11935 else if (element == EL_ELEMENT_SNAPPING ||
11936 element == EL_DIAGONAL_SHRINKING ||
11937 element == EL_DIAGONAL_GROWING)
11939 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
11941 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11943 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
11944 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11946 if (IS_BELT_ACTIVE(element))
11947 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
11949 if (game.magic_wall_active)
11951 int jx = local_player->jx, jy = local_player->jy;
11953 /* play the element sound at the position nearest to the player */
11954 if ((element == EL_MAGIC_WALL_FULL ||
11955 element == EL_MAGIC_WALL_ACTIVE ||
11956 element == EL_MAGIC_WALL_EMPTYING ||
11957 element == EL_BD_MAGIC_WALL_FULL ||
11958 element == EL_BD_MAGIC_WALL_ACTIVE ||
11959 element == EL_BD_MAGIC_WALL_EMPTYING ||
11960 element == EL_DC_MAGIC_WALL_FULL ||
11961 element == EL_DC_MAGIC_WALL_ACTIVE ||
11962 element == EL_DC_MAGIC_WALL_EMPTYING) &&
11963 ABS(x-jx) + ABS(y-jy) < ABS(magic_wall_x-jx) + ABS(magic_wall_y-jy))
11971 #if USE_NEW_AMOEBA_CODE
11972 /* new experimental amoeba growth stuff */
11973 if (!(FrameCounter % 8))
11975 static unsigned int random = 1684108901;
11977 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
11979 x = RND(lev_fieldx);
11980 y = RND(lev_fieldy);
11981 element = Feld[x][y];
11983 if (!IS_PLAYER(x,y) &&
11984 (element == EL_EMPTY ||
11985 CAN_GROW_INTO(element) ||
11986 element == EL_QUICKSAND_EMPTY ||
11987 element == EL_QUICKSAND_FAST_EMPTY ||
11988 element == EL_ACID_SPLASH_LEFT ||
11989 element == EL_ACID_SPLASH_RIGHT))
11991 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
11992 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
11993 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
11994 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
11995 Feld[x][y] = EL_AMOEBA_DROP;
11998 random = random * 129 + 1;
12003 game.explosions_delayed = FALSE;
12005 SCAN_PLAYFIELD(x, y)
12007 element = Feld[x][y];
12009 if (ExplodeField[x][y])
12010 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12011 else if (element == EL_EXPLOSION)
12012 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12014 ExplodeField[x][y] = EX_TYPE_NONE;
12017 game.explosions_delayed = TRUE;
12019 if (game.magic_wall_active)
12021 if (!(game.magic_wall_time_left % 4))
12023 int element = Feld[magic_wall_x][magic_wall_y];
12025 if (element == EL_BD_MAGIC_WALL_FULL ||
12026 element == EL_BD_MAGIC_WALL_ACTIVE ||
12027 element == EL_BD_MAGIC_WALL_EMPTYING)
12028 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12029 else if (element == EL_DC_MAGIC_WALL_FULL ||
12030 element == EL_DC_MAGIC_WALL_ACTIVE ||
12031 element == EL_DC_MAGIC_WALL_EMPTYING)
12032 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12034 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12037 if (game.magic_wall_time_left > 0)
12039 game.magic_wall_time_left--;
12041 if (!game.magic_wall_time_left)
12043 SCAN_PLAYFIELD(x, y)
12045 element = Feld[x][y];
12047 if (element == EL_MAGIC_WALL_ACTIVE ||
12048 element == EL_MAGIC_WALL_FULL)
12050 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12051 TEST_DrawLevelField(x, y);
12053 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12054 element == EL_BD_MAGIC_WALL_FULL)
12056 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12057 TEST_DrawLevelField(x, y);
12059 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12060 element == EL_DC_MAGIC_WALL_FULL)
12062 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12063 TEST_DrawLevelField(x, y);
12067 game.magic_wall_active = FALSE;
12072 if (game.light_time_left > 0)
12074 game.light_time_left--;
12076 if (game.light_time_left == 0)
12077 RedrawAllLightSwitchesAndInvisibleElements();
12080 if (game.timegate_time_left > 0)
12082 game.timegate_time_left--;
12084 if (game.timegate_time_left == 0)
12085 CloseAllOpenTimegates();
12088 if (game.lenses_time_left > 0)
12090 game.lenses_time_left--;
12092 if (game.lenses_time_left == 0)
12093 RedrawAllInvisibleElementsForLenses();
12096 if (game.magnify_time_left > 0)
12098 game.magnify_time_left--;
12100 if (game.magnify_time_left == 0)
12101 RedrawAllInvisibleElementsForMagnifier();
12104 for (i = 0; i < MAX_PLAYERS; i++)
12106 struct PlayerInfo *player = &stored_player[i];
12108 if (SHIELD_ON(player))
12110 if (player->shield_deadly_time_left)
12111 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12112 else if (player->shield_normal_time_left)
12113 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12117 #if USE_DELAYED_GFX_REDRAW
12118 SCAN_PLAYFIELD(x, y)
12120 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12122 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12123 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12125 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12126 DrawLevelField(x, y);
12128 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12129 DrawLevelFieldCrumbled(x, y);
12131 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12132 DrawLevelFieldCrumbledNeighbours(x, y);
12134 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12135 DrawTwinkleOnField(x, y);
12138 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12143 PlayAllPlayersSound();
12145 if (local_player->show_envelope != 0 && local_player->MovPos == 0)
12147 ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
12149 local_player->show_envelope = 0;
12152 /* use random number generator in every frame to make it less predictable */
12153 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12157 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12159 int min_x = x, min_y = y, max_x = x, max_y = y;
12162 for (i = 0; i < MAX_PLAYERS; i++)
12164 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12166 if (!stored_player[i].active || &stored_player[i] == player)
12169 min_x = MIN(min_x, jx);
12170 min_y = MIN(min_y, jy);
12171 max_x = MAX(max_x, jx);
12172 max_y = MAX(max_y, jy);
12175 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12178 static boolean AllPlayersInVisibleScreen()
12182 for (i = 0; i < MAX_PLAYERS; i++)
12184 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12186 if (!stored_player[i].active)
12189 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12196 void ScrollLevel(int dx, int dy)
12198 int scroll_offset = 2 * TILEX_VAR;
12201 BlitBitmap(drawto_field, drawto_field,
12202 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12203 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12204 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12205 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12206 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12207 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12211 x = (dx == 1 ? BX1 : BX2);
12212 for (y = BY1; y <= BY2; y++)
12213 DrawScreenField(x, y);
12218 y = (dy == 1 ? BY1 : BY2);
12219 for (x = BX1; x <= BX2; x++)
12220 DrawScreenField(x, y);
12223 redraw_mask |= REDRAW_FIELD;
12226 static boolean canFallDown(struct PlayerInfo *player)
12228 int jx = player->jx, jy = player->jy;
12230 return (IN_LEV_FIELD(jx, jy + 1) &&
12231 (IS_FREE(jx, jy + 1) ||
12232 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12233 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12234 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12237 static boolean canPassField(int x, int y, int move_dir)
12239 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12240 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12241 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12242 int nextx = x + dx;
12243 int nexty = y + dy;
12244 int element = Feld[x][y];
12246 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12247 !CAN_MOVE(element) &&
12248 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12249 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12250 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12253 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12255 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12256 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12257 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12261 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12262 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12263 (IS_DIGGABLE(Feld[newx][newy]) ||
12264 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12265 canPassField(newx, newy, move_dir)));
12268 static void CheckGravityMovement(struct PlayerInfo *player)
12270 if (player->gravity && !player->programmed_action)
12272 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12273 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12274 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12275 int jx = player->jx, jy = player->jy;
12276 boolean player_is_moving_to_valid_field =
12277 (!player_is_snapping &&
12278 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12279 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12280 boolean player_can_fall_down = canFallDown(player);
12282 if (player_can_fall_down &&
12283 !player_is_moving_to_valid_field)
12284 player->programmed_action = MV_DOWN;
12288 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12290 return CheckGravityMovement(player);
12292 if (player->gravity && !player->programmed_action)
12294 int jx = player->jx, jy = player->jy;
12295 boolean field_under_player_is_free =
12296 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12297 boolean player_is_standing_on_valid_field =
12298 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12299 (IS_WALKABLE(Feld[jx][jy]) &&
12300 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12302 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12303 player->programmed_action = MV_DOWN;
12308 MovePlayerOneStep()
12309 -----------------------------------------------------------------------------
12310 dx, dy: direction (non-diagonal) to try to move the player to
12311 real_dx, real_dy: direction as read from input device (can be diagonal)
12314 boolean MovePlayerOneStep(struct PlayerInfo *player,
12315 int dx, int dy, int real_dx, int real_dy)
12317 int jx = player->jx, jy = player->jy;
12318 int new_jx = jx + dx, new_jy = jy + dy;
12320 boolean player_can_move = !player->cannot_move;
12322 if (!player->active || (!dx && !dy))
12323 return MP_NO_ACTION;
12325 player->MovDir = (dx < 0 ? MV_LEFT :
12326 dx > 0 ? MV_RIGHT :
12328 dy > 0 ? MV_DOWN : MV_NONE);
12330 if (!IN_LEV_FIELD(new_jx, new_jy))
12331 return MP_NO_ACTION;
12333 if (!player_can_move)
12335 if (player->MovPos == 0)
12337 player->is_moving = FALSE;
12338 player->is_digging = FALSE;
12339 player->is_collecting = FALSE;
12340 player->is_snapping = FALSE;
12341 player->is_pushing = FALSE;
12345 if (!options.network && game.centered_player_nr == -1 &&
12346 !AllPlayersInSight(player, new_jx, new_jy))
12347 return MP_NO_ACTION;
12349 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12350 if (can_move != MP_MOVING)
12353 /* check if DigField() has caused relocation of the player */
12354 if (player->jx != jx || player->jy != jy)
12355 return MP_NO_ACTION; /* <-- !!! CHECK THIS [-> MP_ACTION ?] !!! */
12357 StorePlayer[jx][jy] = 0;
12358 player->last_jx = jx;
12359 player->last_jy = jy;
12360 player->jx = new_jx;
12361 player->jy = new_jy;
12362 StorePlayer[new_jx][new_jy] = player->element_nr;
12364 if (player->move_delay_value_next != -1)
12366 player->move_delay_value = player->move_delay_value_next;
12367 player->move_delay_value_next = -1;
12371 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12373 player->step_counter++;
12375 PlayerVisit[jx][jy] = FrameCounter;
12377 player->is_moving = TRUE;
12380 /* should better be called in MovePlayer(), but this breaks some tapes */
12381 ScrollPlayer(player, SCROLL_INIT);
12387 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12389 int jx = player->jx, jy = player->jy;
12390 int old_jx = jx, old_jy = jy;
12391 int moved = MP_NO_ACTION;
12393 if (!player->active)
12398 if (player->MovPos == 0)
12400 player->is_moving = FALSE;
12401 player->is_digging = FALSE;
12402 player->is_collecting = FALSE;
12403 player->is_snapping = FALSE;
12404 player->is_pushing = FALSE;
12410 if (player->move_delay > 0)
12413 player->move_delay = -1; /* set to "uninitialized" value */
12415 /* store if player is automatically moved to next field */
12416 player->is_auto_moving = (player->programmed_action != MV_NONE);
12418 /* remove the last programmed player action */
12419 player->programmed_action = 0;
12421 if (player->MovPos)
12423 /* should only happen if pre-1.2 tape recordings are played */
12424 /* this is only for backward compatibility */
12426 int original_move_delay_value = player->move_delay_value;
12429 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12433 /* scroll remaining steps with finest movement resolution */
12434 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12436 while (player->MovPos)
12438 ScrollPlayer(player, SCROLL_GO_ON);
12439 ScrollScreen(NULL, SCROLL_GO_ON);
12441 AdvanceFrameAndPlayerCounters(player->index_nr);
12444 BackToFront_WithFrameDelay(0);
12447 player->move_delay_value = original_move_delay_value;
12450 player->is_active = FALSE;
12452 if (player->last_move_dir & MV_HORIZONTAL)
12454 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12455 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12459 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12460 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12463 if (!moved && !player->is_active)
12465 player->is_moving = FALSE;
12466 player->is_digging = FALSE;
12467 player->is_collecting = FALSE;
12468 player->is_snapping = FALSE;
12469 player->is_pushing = FALSE;
12475 if (moved & MP_MOVING && !ScreenMovPos &&
12476 (player->index_nr == game.centered_player_nr ||
12477 game.centered_player_nr == -1))
12479 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12480 int offset = game.scroll_delay_value;
12482 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12484 /* actual player has left the screen -- scroll in that direction */
12485 if (jx != old_jx) /* player has moved horizontally */
12486 scroll_x += (jx - old_jx);
12487 else /* player has moved vertically */
12488 scroll_y += (jy - old_jy);
12492 if (jx != old_jx) /* player has moved horizontally */
12494 if ((player->MovDir == MV_LEFT && scroll_x > jx - MIDPOSX + offset) ||
12495 (player->MovDir == MV_RIGHT && scroll_x < jx - MIDPOSX - offset))
12496 scroll_x = jx-MIDPOSX + (scroll_x < jx-MIDPOSX ? -offset : +offset);
12498 /* don't scroll over playfield boundaries */
12499 if (scroll_x < SBX_Left || scroll_x > SBX_Right)
12500 scroll_x = (scroll_x < SBX_Left ? SBX_Left : SBX_Right);
12502 /* don't scroll more than one field at a time */
12503 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12505 /* don't scroll against the player's moving direction */
12506 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12507 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12508 scroll_x = old_scroll_x;
12510 else /* player has moved vertically */
12512 if ((player->MovDir == MV_UP && scroll_y > jy - MIDPOSY + offset) ||
12513 (player->MovDir == MV_DOWN && scroll_y < jy - MIDPOSY - offset))
12514 scroll_y = jy-MIDPOSY + (scroll_y < jy-MIDPOSY ? -offset : +offset);
12516 /* don't scroll over playfield boundaries */
12517 if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
12518 scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
12520 /* don't scroll more than one field at a time */
12521 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12523 /* don't scroll against the player's moving direction */
12524 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12525 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12526 scroll_y = old_scroll_y;
12530 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12532 if (!options.network && game.centered_player_nr == -1 &&
12533 !AllPlayersInVisibleScreen())
12535 scroll_x = old_scroll_x;
12536 scroll_y = old_scroll_y;
12540 ScrollScreen(player, SCROLL_INIT);
12541 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12546 player->StepFrame = 0;
12548 if (moved & MP_MOVING)
12550 if (old_jx != jx && old_jy == jy)
12551 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12552 else if (old_jx == jx && old_jy != jy)
12553 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12555 TEST_DrawLevelField(jx, jy); /* for "crumbled sand" */
12557 player->last_move_dir = player->MovDir;
12558 player->is_moving = TRUE;
12559 player->is_snapping = FALSE;
12560 player->is_switching = FALSE;
12561 player->is_dropping = FALSE;
12562 player->is_dropping_pressed = FALSE;
12563 player->drop_pressed_delay = 0;
12566 /* should better be called here than above, but this breaks some tapes */
12567 ScrollPlayer(player, SCROLL_INIT);
12572 CheckGravityMovementWhenNotMoving(player);
12574 player->is_moving = FALSE;
12576 /* at this point, the player is allowed to move, but cannot move right now
12577 (e.g. because of something blocking the way) -- ensure that the player
12578 is also allowed to move in the next frame (in old versions before 3.1.1,
12579 the player was forced to wait again for eight frames before next try) */
12581 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12582 player->move_delay = 0; /* allow direct movement in the next frame */
12585 if (player->move_delay == -1) /* not yet initialized by DigField() */
12586 player->move_delay = player->move_delay_value;
12588 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12590 TestIfPlayerTouchesBadThing(jx, jy);
12591 TestIfPlayerTouchesCustomElement(jx, jy);
12594 if (!player->active)
12595 RemovePlayer(player);
12600 void ScrollPlayer(struct PlayerInfo *player, int mode)
12602 int jx = player->jx, jy = player->jy;
12603 int last_jx = player->last_jx, last_jy = player->last_jy;
12604 int move_stepsize = TILEX / player->move_delay_value;
12606 if (!player->active)
12609 if (player->MovPos == 0 && mode == SCROLL_GO_ON) /* player not moving */
12612 if (mode == SCROLL_INIT)
12614 player->actual_frame_counter = FrameCounter;
12615 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12617 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12618 Feld[last_jx][last_jy] == EL_EMPTY)
12620 int last_field_block_delay = 0; /* start with no blocking at all */
12621 int block_delay_adjustment = player->block_delay_adjustment;
12623 /* if player blocks last field, add delay for exactly one move */
12624 if (player->block_last_field)
12626 last_field_block_delay += player->move_delay_value;
12628 /* when blocking enabled, prevent moving up despite gravity */
12629 if (player->gravity && player->MovDir == MV_UP)
12630 block_delay_adjustment = -1;
12633 /* add block delay adjustment (also possible when not blocking) */
12634 last_field_block_delay += block_delay_adjustment;
12636 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12637 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12640 if (player->MovPos != 0) /* player has not yet reached destination */
12643 else if (!FrameReached(&player->actual_frame_counter, 1))
12646 if (player->MovPos != 0)
12648 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12649 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12651 /* before DrawPlayer() to draw correct player graphic for this case */
12652 if (player->MovPos == 0)
12653 CheckGravityMovement(player);
12656 if (player->MovPos == 0) /* player reached destination field */
12658 if (player->move_delay_reset_counter > 0)
12660 player->move_delay_reset_counter--;
12662 if (player->move_delay_reset_counter == 0)
12664 /* continue with normal speed after quickly moving through gate */
12665 HALVE_PLAYER_SPEED(player);
12667 /* be able to make the next move without delay */
12668 player->move_delay = 0;
12672 player->last_jx = jx;
12673 player->last_jy = jy;
12675 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12676 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12677 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12678 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12679 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12680 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12681 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12682 Feld[jx][jy] == EL_SP_EXIT_OPENING) /* <-- special case */
12684 DrawPlayer(player); /* needed here only to cleanup last field */
12685 RemovePlayer(player);
12687 if (local_player->friends_still_needed == 0 ||
12688 IS_SP_ELEMENT(Feld[jx][jy]))
12689 PlayerWins(player);
12692 /* this breaks one level: "machine", level 000 */
12694 int move_direction = player->MovDir;
12695 int enter_side = MV_DIR_OPPOSITE(move_direction);
12696 int leave_side = move_direction;
12697 int old_jx = last_jx;
12698 int old_jy = last_jy;
12699 int old_element = Feld[old_jx][old_jy];
12700 int new_element = Feld[jx][jy];
12702 if (IS_CUSTOM_ELEMENT(old_element))
12703 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12705 player->index_bit, leave_side);
12707 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12708 CE_PLAYER_LEAVES_X,
12709 player->index_bit, leave_side);
12711 if (IS_CUSTOM_ELEMENT(new_element))
12712 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12713 player->index_bit, enter_side);
12715 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12716 CE_PLAYER_ENTERS_X,
12717 player->index_bit, enter_side);
12719 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12720 CE_MOVE_OF_X, move_direction);
12723 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12725 TestIfPlayerTouchesBadThing(jx, jy);
12726 TestIfPlayerTouchesCustomElement(jx, jy);
12728 /* needed because pushed element has not yet reached its destination,
12729 so it would trigger a change event at its previous field location */
12730 if (!player->is_pushing)
12731 TestIfElementTouchesCustomElement(jx, jy); /* for empty space */
12733 if (!player->active)
12734 RemovePlayer(player);
12737 if (!local_player->LevelSolved && level.use_step_counter)
12747 if (TimeLeft <= 10 && setup.time_limit)
12748 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12750 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12752 DisplayGameControlValues();
12754 if (!TimeLeft && setup.time_limit)
12755 for (i = 0; i < MAX_PLAYERS; i++)
12756 KillPlayer(&stored_player[i]);
12758 else if (game.no_time_limit && !AllPlayersGone) /* level w/o time limit */
12760 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12762 DisplayGameControlValues();
12766 if (tape.single_step && tape.recording && !tape.pausing &&
12767 !player->programmed_action)
12768 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12770 if (!player->programmed_action)
12771 CheckSaveEngineSnapshot(player);
12775 void ScrollScreen(struct PlayerInfo *player, int mode)
12777 static unsigned int screen_frame_counter = 0;
12779 if (mode == SCROLL_INIT)
12781 /* set scrolling step size according to actual player's moving speed */
12782 ScrollStepSize = TILEX / player->move_delay_value;
12784 screen_frame_counter = FrameCounter;
12785 ScreenMovDir = player->MovDir;
12786 ScreenMovPos = player->MovPos;
12787 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12790 else if (!FrameReached(&screen_frame_counter, 1))
12795 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
12796 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12797 redraw_mask |= REDRAW_FIELD;
12800 ScreenMovDir = MV_NONE;
12803 void TestIfPlayerTouchesCustomElement(int x, int y)
12805 static int xy[4][2] =
12812 static int trigger_sides[4][2] =
12814 /* center side border side */
12815 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, /* check top */
12816 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, /* check left */
12817 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, /* check right */
12818 { CH_SIDE_BOTTOM, CH_SIDE_TOP } /* check bottom */
12820 static int touch_dir[4] =
12822 MV_LEFT | MV_RIGHT,
12827 int center_element = Feld[x][y]; /* should always be non-moving! */
12830 for (i = 0; i < NUM_DIRECTIONS; i++)
12832 int xx = x + xy[i][0];
12833 int yy = y + xy[i][1];
12834 int center_side = trigger_sides[i][0];
12835 int border_side = trigger_sides[i][1];
12836 int border_element;
12838 if (!IN_LEV_FIELD(xx, yy))
12841 if (IS_PLAYER(x, y)) /* player found at center element */
12843 struct PlayerInfo *player = PLAYERINFO(x, y);
12845 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12846 border_element = Feld[xx][yy]; /* may be moving! */
12847 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12848 border_element = Feld[xx][yy];
12849 else if (MovDir[xx][yy] & touch_dir[i]) /* elements are touching */
12850 border_element = MovingOrBlocked2Element(xx, yy);
12852 continue; /* center and border element do not touch */
12854 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
12855 player->index_bit, border_side);
12856 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
12857 CE_PLAYER_TOUCHES_X,
12858 player->index_bit, border_side);
12861 /* use player element that is initially defined in the level playfield,
12862 not the player element that corresponds to the runtime player number
12863 (example: a level that contains EL_PLAYER_3 as the only player would
12864 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12865 int player_element = PLAYERINFO(x, y)->initial_element;
12867 CheckElementChangeBySide(xx, yy, border_element, player_element,
12868 CE_TOUCHING_X, border_side);
12871 else if (IS_PLAYER(xx, yy)) /* player found at border element */
12873 struct PlayerInfo *player = PLAYERINFO(xx, yy);
12875 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12877 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
12878 continue; /* center and border element do not touch */
12881 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
12882 player->index_bit, center_side);
12883 CheckTriggeredElementChangeByPlayer(x, y, center_element,
12884 CE_PLAYER_TOUCHES_X,
12885 player->index_bit, center_side);
12888 /* use player element that is initially defined in the level playfield,
12889 not the player element that corresponds to the runtime player number
12890 (example: a level that contains EL_PLAYER_3 as the only player would
12891 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12892 int player_element = PLAYERINFO(xx, yy)->initial_element;
12894 CheckElementChangeBySide(x, y, center_element, player_element,
12895 CE_TOUCHING_X, center_side);
12903 void TestIfElementTouchesCustomElement(int x, int y)
12905 static int xy[4][2] =
12912 static int trigger_sides[4][2] =
12914 /* center side border side */
12915 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, /* check top */
12916 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, /* check left */
12917 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, /* check right */
12918 { CH_SIDE_BOTTOM, CH_SIDE_TOP } /* check bottom */
12920 static int touch_dir[4] =
12922 MV_LEFT | MV_RIGHT,
12927 boolean change_center_element = FALSE;
12928 int center_element = Feld[x][y]; /* should always be non-moving! */
12929 int border_element_old[NUM_DIRECTIONS];
12932 for (i = 0; i < NUM_DIRECTIONS; i++)
12934 int xx = x + xy[i][0];
12935 int yy = y + xy[i][1];
12936 int border_element;
12938 border_element_old[i] = -1;
12940 if (!IN_LEV_FIELD(xx, yy))
12943 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12944 border_element = Feld[xx][yy]; /* may be moving! */
12945 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12946 border_element = Feld[xx][yy];
12947 else if (MovDir[xx][yy] & touch_dir[i]) /* elements are touching */
12948 border_element = MovingOrBlocked2Element(xx, yy);
12950 continue; /* center and border element do not touch */
12952 border_element_old[i] = border_element;
12955 for (i = 0; i < NUM_DIRECTIONS; i++)
12957 int xx = x + xy[i][0];
12958 int yy = y + xy[i][1];
12959 int center_side = trigger_sides[i][0];
12960 int border_element = border_element_old[i];
12962 if (border_element == -1)
12965 /* check for change of border element */
12966 CheckElementChangeBySide(xx, yy, border_element, center_element,
12967 CE_TOUCHING_X, center_side);
12969 /* (center element cannot be player, so we dont have to check this here) */
12972 for (i = 0; i < NUM_DIRECTIONS; i++)
12974 int xx = x + xy[i][0];
12975 int yy = y + xy[i][1];
12976 int border_side = trigger_sides[i][1];
12977 int border_element = border_element_old[i];
12979 if (border_element == -1)
12982 /* check for change of center element (but change it only once) */
12983 if (!change_center_element)
12984 change_center_element =
12985 CheckElementChangeBySide(x, y, center_element, border_element,
12986 CE_TOUCHING_X, border_side);
12988 if (IS_PLAYER(xx, yy))
12990 /* use player element that is initially defined in the level playfield,
12991 not the player element that corresponds to the runtime player number
12992 (example: a level that contains EL_PLAYER_3 as the only player would
12993 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12994 int player_element = PLAYERINFO(xx, yy)->initial_element;
12996 CheckElementChangeBySide(x, y, center_element, player_element,
12997 CE_TOUCHING_X, border_side);
13002 void TestIfElementHitsCustomElement(int x, int y, int direction)
13004 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13005 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13006 int hitx = x + dx, hity = y + dy;
13007 int hitting_element = Feld[x][y];
13008 int touched_element;
13010 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13013 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13014 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13016 if (IN_LEV_FIELD(hitx, hity))
13018 int opposite_direction = MV_DIR_OPPOSITE(direction);
13019 int hitting_side = direction;
13020 int touched_side = opposite_direction;
13021 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13022 MovDir[hitx][hity] != direction ||
13023 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13029 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13030 CE_HITTING_X, touched_side);
13032 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13033 CE_HIT_BY_X, hitting_side);
13035 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13036 CE_HIT_BY_SOMETHING, opposite_direction);
13038 if (IS_PLAYER(hitx, hity))
13040 /* use player element that is initially defined in the level playfield,
13041 not the player element that corresponds to the runtime player number
13042 (example: a level that contains EL_PLAYER_3 as the only player would
13043 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13044 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13046 CheckElementChangeBySide(x, y, hitting_element, player_element,
13047 CE_HITTING_X, touched_side);
13052 /* "hitting something" is also true when hitting the playfield border */
13053 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13054 CE_HITTING_SOMETHING, direction);
13057 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13059 int i, kill_x = -1, kill_y = -1;
13061 int bad_element = -1;
13062 static int test_xy[4][2] =
13069 static int test_dir[4] =
13077 for (i = 0; i < NUM_DIRECTIONS; i++)
13079 int test_x, test_y, test_move_dir, test_element;
13081 test_x = good_x + test_xy[i][0];
13082 test_y = good_y + test_xy[i][1];
13084 if (!IN_LEV_FIELD(test_x, test_y))
13088 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13090 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13092 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13093 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13095 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13096 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13100 bad_element = test_element;
13106 if (kill_x != -1 || kill_y != -1)
13108 if (IS_PLAYER(good_x, good_y))
13110 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13112 if (player->shield_deadly_time_left > 0 &&
13113 !IS_INDESTRUCTIBLE(bad_element))
13114 Bang(kill_x, kill_y);
13115 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13116 KillPlayer(player);
13119 Bang(good_x, good_y);
13123 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13125 int i, kill_x = -1, kill_y = -1;
13126 int bad_element = Feld[bad_x][bad_y];
13127 static int test_xy[4][2] =
13134 static int touch_dir[4] =
13136 MV_LEFT | MV_RIGHT,
13141 static int test_dir[4] =
13149 if (bad_element == EL_EXPLOSION) /* skip just exploding bad things */
13152 for (i = 0; i < NUM_DIRECTIONS; i++)
13154 int test_x, test_y, test_move_dir, test_element;
13156 test_x = bad_x + test_xy[i][0];
13157 test_y = bad_y + test_xy[i][1];
13159 if (!IN_LEV_FIELD(test_x, test_y))
13163 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13165 test_element = Feld[test_x][test_y];
13167 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13168 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13170 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13171 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13173 /* good thing is player or penguin that does not move away */
13174 if (IS_PLAYER(test_x, test_y))
13176 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13178 if (bad_element == EL_ROBOT && player->is_moving)
13179 continue; /* robot does not kill player if he is moving */
13181 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13183 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13184 continue; /* center and border element do not touch */
13192 else if (test_element == EL_PENGUIN)
13202 if (kill_x != -1 || kill_y != -1)
13204 if (IS_PLAYER(kill_x, kill_y))
13206 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13208 if (player->shield_deadly_time_left > 0 &&
13209 !IS_INDESTRUCTIBLE(bad_element))
13210 Bang(bad_x, bad_y);
13211 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13212 KillPlayer(player);
13215 Bang(kill_x, kill_y);
13219 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13221 int bad_element = Feld[bad_x][bad_y];
13222 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13223 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13224 int test_x = bad_x + dx, test_y = bad_y + dy;
13225 int test_move_dir, test_element;
13226 int kill_x = -1, kill_y = -1;
13228 if (!IN_LEV_FIELD(test_x, test_y))
13232 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13234 test_element = Feld[test_x][test_y];
13236 if (test_move_dir != bad_move_dir)
13238 /* good thing can be player or penguin that does not move away */
13239 if (IS_PLAYER(test_x, test_y))
13241 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13243 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13244 player as being hit when he is moving towards the bad thing, because
13245 the "get hit by" condition would be lost after the player stops) */
13246 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13247 return; /* player moves away from bad thing */
13252 else if (test_element == EL_PENGUIN)
13259 if (kill_x != -1 || kill_y != -1)
13261 if (IS_PLAYER(kill_x, kill_y))
13263 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13265 if (player->shield_deadly_time_left > 0 &&
13266 !IS_INDESTRUCTIBLE(bad_element))
13267 Bang(bad_x, bad_y);
13268 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13269 KillPlayer(player);
13272 Bang(kill_x, kill_y);
13276 void TestIfPlayerTouchesBadThing(int x, int y)
13278 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13281 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13283 TestIfGoodThingHitsBadThing(x, y, move_dir);
13286 void TestIfBadThingTouchesPlayer(int x, int y)
13288 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13291 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13293 TestIfBadThingHitsGoodThing(x, y, move_dir);
13296 void TestIfFriendTouchesBadThing(int x, int y)
13298 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13301 void TestIfBadThingTouchesFriend(int x, int y)
13303 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13306 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13308 int i, kill_x = bad_x, kill_y = bad_y;
13309 static int xy[4][2] =
13317 for (i = 0; i < NUM_DIRECTIONS; i++)
13321 x = bad_x + xy[i][0];
13322 y = bad_y + xy[i][1];
13323 if (!IN_LEV_FIELD(x, y))
13326 element = Feld[x][y];
13327 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13328 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13336 if (kill_x != bad_x || kill_y != bad_y)
13337 Bang(bad_x, bad_y);
13340 void KillPlayer(struct PlayerInfo *player)
13342 int jx = player->jx, jy = player->jy;
13344 if (!player->active)
13348 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13349 player->killed, player->active, player->reanimated);
13352 /* the following code was introduced to prevent an infinite loop when calling
13354 -> CheckTriggeredElementChangeExt()
13355 -> ExecuteCustomElementAction()
13357 -> (infinitely repeating the above sequence of function calls)
13358 which occurs when killing the player while having a CE with the setting
13359 "kill player X when explosion of <player X>"; the solution using a new
13360 field "player->killed" was chosen for backwards compatibility, although
13361 clever use of the fields "player->active" etc. would probably also work */
13363 if (player->killed)
13367 player->killed = TRUE;
13369 /* remove accessible field at the player's position */
13370 Feld[jx][jy] = EL_EMPTY;
13372 /* deactivate shield (else Bang()/Explode() would not work right) */
13373 player->shield_normal_time_left = 0;
13374 player->shield_deadly_time_left = 0;
13377 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13378 player->killed, player->active, player->reanimated);
13384 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13385 player->killed, player->active, player->reanimated);
13388 if (player->reanimated) /* killed player may have been reanimated */
13389 player->killed = player->reanimated = FALSE;
13391 BuryPlayer(player);
13394 static void KillPlayerUnlessEnemyProtected(int x, int y)
13396 if (!PLAYER_ENEMY_PROTECTED(x, y))
13397 KillPlayer(PLAYERINFO(x, y));
13400 static void KillPlayerUnlessExplosionProtected(int x, int y)
13402 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13403 KillPlayer(PLAYERINFO(x, y));
13406 void BuryPlayer(struct PlayerInfo *player)
13408 int jx = player->jx, jy = player->jy;
13410 if (!player->active)
13413 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13414 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13416 player->GameOver = TRUE;
13417 RemovePlayer(player);
13420 void RemovePlayer(struct PlayerInfo *player)
13422 int jx = player->jx, jy = player->jy;
13423 int i, found = FALSE;
13425 player->present = FALSE;
13426 player->active = FALSE;
13428 if (!ExplodeField[jx][jy])
13429 StorePlayer[jx][jy] = 0;
13431 if (player->is_moving)
13432 TEST_DrawLevelField(player->last_jx, player->last_jy);
13434 for (i = 0; i < MAX_PLAYERS; i++)
13435 if (stored_player[i].active)
13439 AllPlayersGone = TRUE;
13445 static void setFieldForSnapping(int x, int y, int element, int direction)
13447 struct ElementInfo *ei = &element_info[element];
13448 int direction_bit = MV_DIR_TO_BIT(direction);
13449 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13450 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13451 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13453 Feld[x][y] = EL_ELEMENT_SNAPPING;
13454 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13456 ResetGfxAnimation(x, y);
13458 GfxElement[x][y] = element;
13459 GfxAction[x][y] = action;
13460 GfxDir[x][y] = direction;
13461 GfxFrame[x][y] = -1;
13465 =============================================================================
13466 checkDiagonalPushing()
13467 -----------------------------------------------------------------------------
13468 check if diagonal input device direction results in pushing of object
13469 (by checking if the alternative direction is walkable, diggable, ...)
13470 =============================================================================
13473 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13474 int x, int y, int real_dx, int real_dy)
13476 int jx, jy, dx, dy, xx, yy;
13478 if (real_dx == 0 || real_dy == 0) /* no diagonal direction => push */
13481 /* diagonal direction: check alternative direction */
13486 xx = jx + (dx == 0 ? real_dx : 0);
13487 yy = jy + (dy == 0 ? real_dy : 0);
13489 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13493 =============================================================================
13495 -----------------------------------------------------------------------------
13496 x, y: field next to player (non-diagonal) to try to dig to
13497 real_dx, real_dy: direction as read from input device (can be diagonal)
13498 =============================================================================
13501 static int DigField(struct PlayerInfo *player,
13502 int oldx, int oldy, int x, int y,
13503 int real_dx, int real_dy, int mode)
13505 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13506 boolean player_was_pushing = player->is_pushing;
13507 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13508 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13509 int jx = oldx, jy = oldy;
13510 int dx = x - jx, dy = y - jy;
13511 int nextx = x + dx, nexty = y + dy;
13512 int move_direction = (dx == -1 ? MV_LEFT :
13513 dx == +1 ? MV_RIGHT :
13515 dy == +1 ? MV_DOWN : MV_NONE);
13516 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13517 int dig_side = MV_DIR_OPPOSITE(move_direction);
13518 int old_element = Feld[jx][jy];
13519 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13522 if (is_player) /* function can also be called by EL_PENGUIN */
13524 if (player->MovPos == 0)
13526 player->is_digging = FALSE;
13527 player->is_collecting = FALSE;
13530 if (player->MovPos == 0) /* last pushing move finished */
13531 player->is_pushing = FALSE;
13533 if (mode == DF_NO_PUSH) /* player just stopped pushing */
13535 player->is_switching = FALSE;
13536 player->push_delay = -1;
13538 return MP_NO_ACTION;
13542 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13543 old_element = Back[jx][jy];
13545 /* in case of element dropped at player position, check background */
13546 else if (Back[jx][jy] != EL_EMPTY &&
13547 game.engine_version >= VERSION_IDENT(2,2,0,0))
13548 old_element = Back[jx][jy];
13550 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13551 return MP_NO_ACTION; /* field has no opening in this direction */
13553 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13554 return MP_NO_ACTION; /* field has no opening in this direction */
13556 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13560 Feld[jx][jy] = player->artwork_element;
13561 InitMovingField(jx, jy, MV_DOWN);
13562 Store[jx][jy] = EL_ACID;
13563 ContinueMoving(jx, jy);
13564 BuryPlayer(player);
13566 return MP_DONT_RUN_INTO;
13569 if (player_can_move && DONT_RUN_INTO(element))
13571 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13573 return MP_DONT_RUN_INTO;
13576 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13577 return MP_NO_ACTION;
13579 collect_count = element_info[element].collect_count_initial;
13581 if (!is_player && !IS_COLLECTIBLE(element)) /* penguin cannot collect it */
13582 return MP_NO_ACTION;
13584 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13585 player_can_move = player_can_move_or_snap;
13587 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13588 game.engine_version >= VERSION_IDENT(2,2,0,0))
13590 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13591 player->index_bit, dig_side);
13592 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13593 player->index_bit, dig_side);
13595 if (element == EL_DC_LANDMINE)
13598 if (Feld[x][y] != element) /* field changed by snapping */
13601 return MP_NO_ACTION;
13604 if (player->gravity && is_player && !player->is_auto_moving &&
13605 canFallDown(player) && move_direction != MV_DOWN &&
13606 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13607 return MP_NO_ACTION; /* player cannot walk here due to gravity */
13609 if (player_can_move &&
13610 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13612 int sound_element = SND_ELEMENT(element);
13613 int sound_action = ACTION_WALKING;
13615 if (IS_RND_GATE(element))
13617 if (!player->key[RND_GATE_NR(element)])
13618 return MP_NO_ACTION;
13620 else if (IS_RND_GATE_GRAY(element))
13622 if (!player->key[RND_GATE_GRAY_NR(element)])
13623 return MP_NO_ACTION;
13625 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13627 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13628 return MP_NO_ACTION;
13630 else if (element == EL_EXIT_OPEN ||
13631 element == EL_EM_EXIT_OPEN ||
13632 element == EL_EM_EXIT_OPENING ||
13633 element == EL_STEEL_EXIT_OPEN ||
13634 element == EL_EM_STEEL_EXIT_OPEN ||
13635 element == EL_EM_STEEL_EXIT_OPENING ||
13636 element == EL_SP_EXIT_OPEN ||
13637 element == EL_SP_EXIT_OPENING)
13639 sound_action = ACTION_PASSING; /* player is passing exit */
13641 else if (element == EL_EMPTY)
13643 sound_action = ACTION_MOVING; /* nothing to walk on */
13646 /* play sound from background or player, whatever is available */
13647 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13648 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13650 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13652 else if (player_can_move &&
13653 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13655 if (!ACCESS_FROM(element, opposite_direction))
13656 return MP_NO_ACTION; /* field not accessible from this direction */
13658 if (CAN_MOVE(element)) /* only fixed elements can be passed! */
13659 return MP_NO_ACTION;
13661 if (IS_EM_GATE(element))
13663 if (!player->key[EM_GATE_NR(element)])
13664 return MP_NO_ACTION;
13666 else if (IS_EM_GATE_GRAY(element))
13668 if (!player->key[EM_GATE_GRAY_NR(element)])
13669 return MP_NO_ACTION;
13671 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13673 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13674 return MP_NO_ACTION;
13676 else if (IS_EMC_GATE(element))
13678 if (!player->key[EMC_GATE_NR(element)])
13679 return MP_NO_ACTION;
13681 else if (IS_EMC_GATE_GRAY(element))
13683 if (!player->key[EMC_GATE_GRAY_NR(element)])
13684 return MP_NO_ACTION;
13686 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13688 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13689 return MP_NO_ACTION;
13691 else if (element == EL_DC_GATE_WHITE ||
13692 element == EL_DC_GATE_WHITE_GRAY ||
13693 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13695 if (player->num_white_keys == 0)
13696 return MP_NO_ACTION;
13698 player->num_white_keys--;
13700 else if (IS_SP_PORT(element))
13702 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13703 element == EL_SP_GRAVITY_PORT_RIGHT ||
13704 element == EL_SP_GRAVITY_PORT_UP ||
13705 element == EL_SP_GRAVITY_PORT_DOWN)
13706 player->gravity = !player->gravity;
13707 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13708 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13709 element == EL_SP_GRAVITY_ON_PORT_UP ||
13710 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13711 player->gravity = TRUE;
13712 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13713 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13714 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13715 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13716 player->gravity = FALSE;
13719 /* automatically move to the next field with double speed */
13720 player->programmed_action = move_direction;
13722 if (player->move_delay_reset_counter == 0)
13724 player->move_delay_reset_counter = 2; /* two double speed steps */
13726 DOUBLE_PLAYER_SPEED(player);
13729 PlayLevelSoundAction(x, y, ACTION_PASSING);
13731 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13735 if (mode != DF_SNAP)
13737 GfxElement[x][y] = GFX_ELEMENT(element);
13738 player->is_digging = TRUE;
13741 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13743 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13744 player->index_bit, dig_side);
13746 if (mode == DF_SNAP)
13748 if (level.block_snap_field)
13749 setFieldForSnapping(x, y, element, move_direction);
13751 TestIfElementTouchesCustomElement(x, y); /* for empty space */
13753 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13754 player->index_bit, dig_side);
13757 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13761 if (is_player && mode != DF_SNAP)
13763 GfxElement[x][y] = element;
13764 player->is_collecting = TRUE;
13767 if (element == EL_SPEED_PILL)
13769 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13771 else if (element == EL_EXTRA_TIME && level.time > 0)
13773 TimeLeft += level.extra_time;
13775 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13777 DisplayGameControlValues();
13779 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
13781 player->shield_normal_time_left += level.shield_normal_time;
13782 if (element == EL_SHIELD_DEADLY)
13783 player->shield_deadly_time_left += level.shield_deadly_time;
13785 else if (element == EL_DYNAMITE ||
13786 element == EL_EM_DYNAMITE ||
13787 element == EL_SP_DISK_RED)
13789 if (player->inventory_size < MAX_INVENTORY_SIZE)
13790 player->inventory_element[player->inventory_size++] = element;
13792 DrawGameDoorValues();
13794 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
13796 player->dynabomb_count++;
13797 player->dynabombs_left++;
13799 else if (element == EL_DYNABOMB_INCREASE_SIZE)
13801 player->dynabomb_size++;
13803 else if (element == EL_DYNABOMB_INCREASE_POWER)
13805 player->dynabomb_xl = TRUE;
13807 else if (IS_KEY(element))
13809 player->key[KEY_NR(element)] = TRUE;
13811 DrawGameDoorValues();
13813 else if (element == EL_DC_KEY_WHITE)
13815 player->num_white_keys++;
13817 /* display white keys? */
13818 /* DrawGameDoorValues(); */
13820 else if (IS_ENVELOPE(element))
13822 player->show_envelope = element;
13824 else if (element == EL_EMC_LENSES)
13826 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
13828 RedrawAllInvisibleElementsForLenses();
13830 else if (element == EL_EMC_MAGNIFIER)
13832 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
13834 RedrawAllInvisibleElementsForMagnifier();
13836 else if (IS_DROPPABLE(element) ||
13837 IS_THROWABLE(element)) /* can be collected and dropped */
13841 if (collect_count == 0)
13842 player->inventory_infinite_element = element;
13844 for (i = 0; i < collect_count; i++)
13845 if (player->inventory_size < MAX_INVENTORY_SIZE)
13846 player->inventory_element[player->inventory_size++] = element;
13848 DrawGameDoorValues();
13850 else if (collect_count > 0)
13852 local_player->gems_still_needed -= collect_count;
13853 if (local_player->gems_still_needed < 0)
13854 local_player->gems_still_needed = 0;
13856 game.snapshot.collected_item = TRUE;
13858 game_panel_controls[GAME_PANEL_GEMS].value = local_player->gems_still_needed;
13860 DisplayGameControlValues();
13863 RaiseScoreElement(element);
13864 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
13867 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
13868 player->index_bit, dig_side);
13870 if (mode == DF_SNAP)
13872 if (level.block_snap_field)
13873 setFieldForSnapping(x, y, element, move_direction);
13875 TestIfElementTouchesCustomElement(x, y); /* for empty space */
13877 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13878 player->index_bit, dig_side);
13881 else if (player_can_move_or_snap && IS_PUSHABLE(element))
13883 if (mode == DF_SNAP && element != EL_BD_ROCK)
13884 return MP_NO_ACTION;
13886 if (CAN_FALL(element) && dy)
13887 return MP_NO_ACTION;
13889 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
13890 !(element == EL_SPRING && level.use_spring_bug))
13891 return MP_NO_ACTION;
13893 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
13894 ((move_direction & MV_VERTICAL &&
13895 ((element_info[element].move_pattern & MV_LEFT &&
13896 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
13897 (element_info[element].move_pattern & MV_RIGHT &&
13898 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
13899 (move_direction & MV_HORIZONTAL &&
13900 ((element_info[element].move_pattern & MV_UP &&
13901 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
13902 (element_info[element].move_pattern & MV_DOWN &&
13903 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
13904 return MP_NO_ACTION;
13906 /* do not push elements already moving away faster than player */
13907 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
13908 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
13909 return MP_NO_ACTION;
13911 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
13913 if (player->push_delay_value == -1 || !player_was_pushing)
13914 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13916 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13918 if (player->push_delay_value == -1)
13919 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13921 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
13923 if (!player->is_pushing)
13924 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13927 player->is_pushing = TRUE;
13928 player->is_active = TRUE;
13930 if (!(IN_LEV_FIELD(nextx, nexty) &&
13931 (IS_FREE(nextx, nexty) ||
13932 (IS_SB_ELEMENT(element) &&
13933 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
13934 (IS_CUSTOM_ELEMENT(element) &&
13935 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
13936 return MP_NO_ACTION;
13938 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
13939 return MP_NO_ACTION;
13941 if (player->push_delay == -1) /* new pushing; restart delay */
13942 player->push_delay = 0;
13944 if (player->push_delay < player->push_delay_value &&
13945 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
13946 element != EL_SPRING && element != EL_BALLOON)
13948 /* make sure that there is no move delay before next try to push */
13949 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13950 player->move_delay = 0;
13952 return MP_NO_ACTION;
13955 if (IS_CUSTOM_ELEMENT(element) &&
13956 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
13958 if (!DigFieldByCE(nextx, nexty, element))
13959 return MP_NO_ACTION;
13962 if (IS_SB_ELEMENT(element))
13964 if (element == EL_SOKOBAN_FIELD_FULL)
13966 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
13967 local_player->sokobanfields_still_needed++;
13970 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
13972 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
13973 local_player->sokobanfields_still_needed--;
13976 Feld[x][y] = EL_SOKOBAN_OBJECT;
13978 if (Back[x][y] == Back[nextx][nexty])
13979 PlayLevelSoundAction(x, y, ACTION_PUSHING);
13980 else if (Back[x][y] != 0)
13981 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
13984 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
13987 if (local_player->sokobanfields_still_needed == 0 &&
13988 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
13990 PlayerWins(player);
13992 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
13996 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
13998 InitMovingField(x, y, move_direction);
13999 GfxAction[x][y] = ACTION_PUSHING;
14001 if (mode == DF_SNAP)
14002 ContinueMoving(x, y);
14004 MovPos[x][y] = (dx != 0 ? dx : dy);
14006 Pushed[x][y] = TRUE;
14007 Pushed[nextx][nexty] = TRUE;
14009 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14010 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14012 player->push_delay_value = -1; /* get new value later */
14014 /* check for element change _after_ element has been pushed */
14015 if (game.use_change_when_pushing_bug)
14017 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14018 player->index_bit, dig_side);
14019 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14020 player->index_bit, dig_side);
14023 else if (IS_SWITCHABLE(element))
14025 if (PLAYER_SWITCHING(player, x, y))
14027 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14028 player->index_bit, dig_side);
14033 player->is_switching = TRUE;
14034 player->switch_x = x;
14035 player->switch_y = y;
14037 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14039 if (element == EL_ROBOT_WHEEL)
14041 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14045 game.robot_wheel_active = TRUE;
14047 TEST_DrawLevelField(x, y);
14049 else if (element == EL_SP_TERMINAL)
14053 SCAN_PLAYFIELD(xx, yy)
14055 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14059 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14061 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14063 ResetGfxAnimation(xx, yy);
14064 TEST_DrawLevelField(xx, yy);
14068 else if (IS_BELT_SWITCH(element))
14070 ToggleBeltSwitch(x, y);
14072 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14073 element == EL_SWITCHGATE_SWITCH_DOWN ||
14074 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14075 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14077 ToggleSwitchgateSwitch(x, y);
14079 else if (element == EL_LIGHT_SWITCH ||
14080 element == EL_LIGHT_SWITCH_ACTIVE)
14082 ToggleLightSwitch(x, y);
14084 else if (element == EL_TIMEGATE_SWITCH ||
14085 element == EL_DC_TIMEGATE_SWITCH)
14087 ActivateTimegateSwitch(x, y);
14089 else if (element == EL_BALLOON_SWITCH_LEFT ||
14090 element == EL_BALLOON_SWITCH_RIGHT ||
14091 element == EL_BALLOON_SWITCH_UP ||
14092 element == EL_BALLOON_SWITCH_DOWN ||
14093 element == EL_BALLOON_SWITCH_NONE ||
14094 element == EL_BALLOON_SWITCH_ANY)
14096 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14097 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14098 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14099 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14100 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14103 else if (element == EL_LAMP)
14105 Feld[x][y] = EL_LAMP_ACTIVE;
14106 local_player->lights_still_needed--;
14108 ResetGfxAnimation(x, y);
14109 TEST_DrawLevelField(x, y);
14111 else if (element == EL_TIME_ORB_FULL)
14113 Feld[x][y] = EL_TIME_ORB_EMPTY;
14115 if (level.time > 0 || level.use_time_orb_bug)
14117 TimeLeft += level.time_orb_time;
14118 game.no_time_limit = FALSE;
14120 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14122 DisplayGameControlValues();
14125 ResetGfxAnimation(x, y);
14126 TEST_DrawLevelField(x, y);
14128 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14129 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14133 game.ball_state = !game.ball_state;
14135 SCAN_PLAYFIELD(xx, yy)
14137 int e = Feld[xx][yy];
14139 if (game.ball_state)
14141 if (e == EL_EMC_MAGIC_BALL)
14142 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14143 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14144 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14148 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14149 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14150 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14151 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14156 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14157 player->index_bit, dig_side);
14159 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14160 player->index_bit, dig_side);
14162 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14163 player->index_bit, dig_side);
14169 if (!PLAYER_SWITCHING(player, x, y))
14171 player->is_switching = TRUE;
14172 player->switch_x = x;
14173 player->switch_y = y;
14175 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14176 player->index_bit, dig_side);
14177 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14178 player->index_bit, dig_side);
14180 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14181 player->index_bit, dig_side);
14182 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14183 player->index_bit, dig_side);
14186 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14187 player->index_bit, dig_side);
14188 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14189 player->index_bit, dig_side);
14191 return MP_NO_ACTION;
14194 player->push_delay = -1;
14196 if (is_player) /* function can also be called by EL_PENGUIN */
14198 if (Feld[x][y] != element) /* really digged/collected something */
14200 player->is_collecting = !player->is_digging;
14201 player->is_active = TRUE;
14208 static boolean DigFieldByCE(int x, int y, int digging_element)
14210 int element = Feld[x][y];
14212 if (!IS_FREE(x, y))
14214 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14215 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14218 /* no element can dig solid indestructible elements */
14219 if (IS_INDESTRUCTIBLE(element) &&
14220 !IS_DIGGABLE(element) &&
14221 !IS_COLLECTIBLE(element))
14224 if (AmoebaNr[x][y] &&
14225 (element == EL_AMOEBA_FULL ||
14226 element == EL_BD_AMOEBA ||
14227 element == EL_AMOEBA_GROWING))
14229 AmoebaCnt[AmoebaNr[x][y]]--;
14230 AmoebaCnt2[AmoebaNr[x][y]]--;
14233 if (IS_MOVING(x, y))
14234 RemoveMovingField(x, y);
14238 TEST_DrawLevelField(x, y);
14241 /* if digged element was about to explode, prevent the explosion */
14242 ExplodeField[x][y] = EX_TYPE_NONE;
14244 PlayLevelSoundAction(x, y, action);
14247 Store[x][y] = EL_EMPTY;
14249 /* this makes it possible to leave the removed element again */
14250 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14251 Store[x][y] = element;
14256 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14258 int jx = player->jx, jy = player->jy;
14259 int x = jx + dx, y = jy + dy;
14260 int snap_direction = (dx == -1 ? MV_LEFT :
14261 dx == +1 ? MV_RIGHT :
14263 dy == +1 ? MV_DOWN : MV_NONE);
14264 boolean can_continue_snapping = (level.continuous_snapping &&
14265 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14267 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14270 if (!player->active || !IN_LEV_FIELD(x, y))
14278 if (player->MovPos == 0)
14279 player->is_pushing = FALSE;
14281 player->is_snapping = FALSE;
14283 if (player->MovPos == 0)
14285 player->is_moving = FALSE;
14286 player->is_digging = FALSE;
14287 player->is_collecting = FALSE;
14293 /* prevent snapping with already pressed snap key when not allowed */
14294 if (player->is_snapping && !can_continue_snapping)
14297 player->MovDir = snap_direction;
14299 if (player->MovPos == 0)
14301 player->is_moving = FALSE;
14302 player->is_digging = FALSE;
14303 player->is_collecting = FALSE;
14306 player->is_dropping = FALSE;
14307 player->is_dropping_pressed = FALSE;
14308 player->drop_pressed_delay = 0;
14310 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14313 player->is_snapping = TRUE;
14314 player->is_active = TRUE;
14316 if (player->MovPos == 0)
14318 player->is_moving = FALSE;
14319 player->is_digging = FALSE;
14320 player->is_collecting = FALSE;
14323 if (player->MovPos != 0) /* prevent graphic bugs in versions < 2.2.0 */
14324 TEST_DrawLevelField(player->last_jx, player->last_jy);
14326 TEST_DrawLevelField(x, y);
14331 static boolean DropElement(struct PlayerInfo *player)
14333 int old_element, new_element;
14334 int dropx = player->jx, dropy = player->jy;
14335 int drop_direction = player->MovDir;
14336 int drop_side = drop_direction;
14337 int drop_element = get_next_dropped_element(player);
14339 /* do not drop an element on top of another element; when holding drop key
14340 pressed without moving, dropped element must move away before the next
14341 element can be dropped (this is especially important if the next element
14342 is dynamite, which can be placed on background for historical reasons) */
14343 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14346 if (IS_THROWABLE(drop_element))
14348 dropx += GET_DX_FROM_DIR(drop_direction);
14349 dropy += GET_DY_FROM_DIR(drop_direction);
14351 if (!IN_LEV_FIELD(dropx, dropy))
14355 old_element = Feld[dropx][dropy]; /* old element at dropping position */
14356 new_element = drop_element; /* default: no change when dropping */
14358 /* check if player is active, not moving and ready to drop */
14359 if (!player->active || player->MovPos || player->drop_delay > 0)
14362 /* check if player has anything that can be dropped */
14363 if (new_element == EL_UNDEFINED)
14366 /* only set if player has anything that can be dropped */
14367 player->is_dropping_pressed = TRUE;
14369 /* check if drop key was pressed long enough for EM style dynamite */
14370 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14373 /* check if anything can be dropped at the current position */
14374 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14377 /* collected custom elements can only be dropped on empty fields */
14378 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14381 if (old_element != EL_EMPTY)
14382 Back[dropx][dropy] = old_element; /* store old element on this field */
14384 ResetGfxAnimation(dropx, dropy);
14385 ResetRandomAnimationValue(dropx, dropy);
14387 if (player->inventory_size > 0 ||
14388 player->inventory_infinite_element != EL_UNDEFINED)
14390 if (player->inventory_size > 0)
14392 player->inventory_size--;
14394 DrawGameDoorValues();
14396 if (new_element == EL_DYNAMITE)
14397 new_element = EL_DYNAMITE_ACTIVE;
14398 else if (new_element == EL_EM_DYNAMITE)
14399 new_element = EL_EM_DYNAMITE_ACTIVE;
14400 else if (new_element == EL_SP_DISK_RED)
14401 new_element = EL_SP_DISK_RED_ACTIVE;
14404 Feld[dropx][dropy] = new_element;
14406 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14407 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14408 el2img(Feld[dropx][dropy]), 0);
14410 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14412 /* needed if previous element just changed to "empty" in the last frame */
14413 ChangeCount[dropx][dropy] = 0; /* allow at least one more change */
14415 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14416 player->index_bit, drop_side);
14417 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14419 player->index_bit, drop_side);
14421 TestIfElementTouchesCustomElement(dropx, dropy);
14423 else /* player is dropping a dyna bomb */
14425 player->dynabombs_left--;
14427 Feld[dropx][dropy] = new_element;
14429 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14430 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14431 el2img(Feld[dropx][dropy]), 0);
14433 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14436 if (Feld[dropx][dropy] == new_element) /* uninitialized unless CE change */
14437 InitField_WithBug1(dropx, dropy, FALSE);
14439 new_element = Feld[dropx][dropy]; /* element might have changed */
14441 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14442 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14444 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14445 MovDir[dropx][dropy] = drop_direction;
14447 ChangeCount[dropx][dropy] = 0; /* allow at least one more change */
14449 /* do not cause impact style collision by dropping elements that can fall */
14450 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14453 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14454 player->is_dropping = TRUE;
14456 player->drop_pressed_delay = 0;
14457 player->is_dropping_pressed = FALSE;
14459 player->drop_x = dropx;
14460 player->drop_y = dropy;
14465 /* ------------------------------------------------------------------------- */
14466 /* game sound playing functions */
14467 /* ------------------------------------------------------------------------- */
14469 static int *loop_sound_frame = NULL;
14470 static int *loop_sound_volume = NULL;
14472 void InitPlayLevelSound()
14474 int num_sounds = getSoundListSize();
14476 checked_free(loop_sound_frame);
14477 checked_free(loop_sound_volume);
14479 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14480 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14483 static void PlayLevelSound(int x, int y, int nr)
14485 int sx = SCREENX(x), sy = SCREENY(y);
14486 int volume, stereo_position;
14487 int max_distance = 8;
14488 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14490 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14491 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14494 if (!IN_LEV_FIELD(x, y) ||
14495 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14496 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14499 volume = SOUND_MAX_VOLUME;
14501 if (!IN_SCR_FIELD(sx, sy))
14503 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14504 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14506 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14509 stereo_position = (SOUND_MAX_LEFT +
14510 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14511 (SCR_FIELDX + 2 * max_distance));
14513 if (IS_LOOP_SOUND(nr))
14515 /* This assures that quieter loop sounds do not overwrite louder ones,
14516 while restarting sound volume comparison with each new game frame. */
14518 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14521 loop_sound_volume[nr] = volume;
14522 loop_sound_frame[nr] = FrameCounter;
14525 PlaySoundExt(nr, volume, stereo_position, type);
14528 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14530 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14531 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14532 y < LEVELY(BY1) ? LEVELY(BY1) :
14533 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14537 static void PlayLevelSoundAction(int x, int y, int action)
14539 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14542 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14544 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14546 if (sound_effect != SND_UNDEFINED)
14547 PlayLevelSound(x, y, sound_effect);
14550 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14553 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14555 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14556 PlayLevelSound(x, y, sound_effect);
14559 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14561 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14563 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14564 PlayLevelSound(x, y, sound_effect);
14567 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14569 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14571 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14572 StopSound(sound_effect);
14575 static int getLevelMusicNr()
14577 if (levelset.music[level_nr] != MUS_UNDEFINED)
14578 return levelset.music[level_nr]; /* from config file */
14580 return MAP_NOCONF_MUSIC(level_nr); /* from music dir */
14583 static void FadeLevelSounds()
14588 static void FadeLevelMusic()
14590 int music_nr = getLevelMusicNr();
14591 char *curr_music = getCurrentlyPlayingMusicFilename();
14592 char *next_music = getMusicInfoEntryFilename(music_nr);
14594 if (!strEqual(curr_music, next_music))
14598 void FadeLevelSoundsAndMusic()
14604 static void PlayLevelMusic()
14606 int music_nr = getLevelMusicNr();
14607 char *curr_music = getCurrentlyPlayingMusicFilename();
14608 char *next_music = getMusicInfoEntryFilename(music_nr);
14610 if (!strEqual(curr_music, next_music))
14611 PlayMusic(music_nr);
14614 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14616 int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0);
14617 int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
14618 int x = xx - 1 - offset;
14619 int y = yy - 1 - offset;
14624 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14628 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14632 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14636 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14640 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14644 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14648 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14651 case SAMPLE_android_clone:
14652 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14655 case SAMPLE_android_move:
14656 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14659 case SAMPLE_spring:
14660 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14664 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14668 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14671 case SAMPLE_eater_eat:
14672 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14676 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14679 case SAMPLE_collect:
14680 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14683 case SAMPLE_diamond:
14684 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14687 case SAMPLE_squash:
14688 /* !!! CHECK THIS !!! */
14690 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14692 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14696 case SAMPLE_wonderfall:
14697 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14701 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14705 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14709 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14713 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14717 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14721 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14724 case SAMPLE_wonder:
14725 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14729 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14732 case SAMPLE_exit_open:
14733 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14736 case SAMPLE_exit_leave:
14737 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14740 case SAMPLE_dynamite:
14741 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14745 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14749 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14753 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14757 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14761 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
14765 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
14769 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
14774 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
14776 int element = map_element_SP_to_RND(element_sp);
14777 int action = map_action_SP_to_RND(action_sp);
14778 int offset = (setup.sp_show_border_elements ? 0 : 1);
14779 int x = xx - offset;
14780 int y = yy - offset;
14782 PlayLevelSoundElementAction(x, y, element, action);
14785 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
14787 int element = map_element_MM_to_RND(element_mm);
14788 int action = map_action_MM_to_RND(action_mm);
14790 int x = xx - offset;
14791 int y = yy - offset;
14793 if (!IS_MM_ELEMENT(element))
14794 element = EL_MM_DEFAULT;
14796 PlayLevelSoundElementAction(x, y, element, action);
14799 void PlaySound_MM(int sound_mm)
14801 int sound = map_sound_MM_to_RND(sound_mm);
14803 if (sound == SND_UNDEFINED)
14809 void PlaySoundLoop_MM(int sound_mm)
14811 int sound = map_sound_MM_to_RND(sound_mm);
14813 if (sound == SND_UNDEFINED)
14816 PlaySoundLoop(sound);
14819 void StopSound_MM(int sound_mm)
14821 int sound = map_sound_MM_to_RND(sound_mm);
14823 if (sound == SND_UNDEFINED)
14829 void RaiseScore(int value)
14831 local_player->score += value;
14833 game_panel_controls[GAME_PANEL_SCORE].value = local_player->score;
14835 DisplayGameControlValues();
14838 void RaiseScoreElement(int element)
14843 case EL_BD_DIAMOND:
14844 case EL_EMERALD_YELLOW:
14845 case EL_EMERALD_RED:
14846 case EL_EMERALD_PURPLE:
14847 case EL_SP_INFOTRON:
14848 RaiseScore(level.score[SC_EMERALD]);
14851 RaiseScore(level.score[SC_DIAMOND]);
14854 RaiseScore(level.score[SC_CRYSTAL]);
14857 RaiseScore(level.score[SC_PEARL]);
14860 case EL_BD_BUTTERFLY:
14861 case EL_SP_ELECTRON:
14862 RaiseScore(level.score[SC_BUG]);
14865 case EL_BD_FIREFLY:
14866 case EL_SP_SNIKSNAK:
14867 RaiseScore(level.score[SC_SPACESHIP]);
14870 case EL_DARK_YAMYAM:
14871 RaiseScore(level.score[SC_YAMYAM]);
14874 RaiseScore(level.score[SC_ROBOT]);
14877 RaiseScore(level.score[SC_PACMAN]);
14880 RaiseScore(level.score[SC_NUT]);
14883 case EL_EM_DYNAMITE:
14884 case EL_SP_DISK_RED:
14885 case EL_DYNABOMB_INCREASE_NUMBER:
14886 case EL_DYNABOMB_INCREASE_SIZE:
14887 case EL_DYNABOMB_INCREASE_POWER:
14888 RaiseScore(level.score[SC_DYNAMITE]);
14890 case EL_SHIELD_NORMAL:
14891 case EL_SHIELD_DEADLY:
14892 RaiseScore(level.score[SC_SHIELD]);
14894 case EL_EXTRA_TIME:
14895 RaiseScore(level.extra_time_score);
14909 case EL_DC_KEY_WHITE:
14910 RaiseScore(level.score[SC_KEY]);
14913 RaiseScore(element_info[element].collect_score);
14918 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
14920 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
14922 /* closing door required in case of envelope style request dialogs */
14924 CloseDoor(DOOR_CLOSE_1);
14926 #if defined(NETWORK_AVALIABLE)
14927 if (options.network)
14928 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
14933 FadeSkipNextFadeIn();
14935 SetGameStatus(GAME_MODE_MAIN);
14940 else /* continue playing the game */
14942 if (tape.playing && tape.deactivate_display)
14943 TapeDeactivateDisplayOff(TRUE);
14945 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
14947 if (tape.playing && tape.deactivate_display)
14948 TapeDeactivateDisplayOn();
14952 void RequestQuitGame(boolean ask_if_really_quit)
14954 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
14955 boolean skip_request = AllPlayersGone || quick_quit;
14957 RequestQuitGameExt(skip_request, quick_quit,
14958 "Do you really want to quit the game?");
14962 /* ------------------------------------------------------------------------- */
14963 /* random generator functions */
14964 /* ------------------------------------------------------------------------- */
14966 unsigned int InitEngineRandom_RND(int seed)
14968 game.num_random_calls = 0;
14970 return InitEngineRandom(seed);
14973 unsigned int RND(int max)
14977 game.num_random_calls++;
14979 return GetEngineRandom(max);
14986 /* ------------------------------------------------------------------------- */
14987 /* game engine snapshot handling functions */
14988 /* ------------------------------------------------------------------------- */
14990 struct EngineSnapshotInfo
14992 /* runtime values for custom element collect score */
14993 int collect_score[NUM_CUSTOM_ELEMENTS];
14995 /* runtime values for group element choice position */
14996 int choice_pos[NUM_GROUP_ELEMENTS];
14998 /* runtime values for belt position animations */
14999 int belt_graphic[4][NUM_BELT_PARTS];
15000 int belt_anim_mode[4][NUM_BELT_PARTS];
15003 static struct EngineSnapshotInfo engine_snapshot_rnd;
15004 static char *snapshot_level_identifier = NULL;
15005 static int snapshot_level_nr = -1;
15007 static void SaveEngineSnapshotValues_RND()
15009 static int belt_base_active_element[4] =
15011 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15012 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15013 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15014 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15018 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15020 int element = EL_CUSTOM_START + i;
15022 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15025 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15027 int element = EL_GROUP_START + i;
15029 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15032 for (i = 0; i < 4; i++)
15034 for (j = 0; j < NUM_BELT_PARTS; j++)
15036 int element = belt_base_active_element[i] + j;
15037 int graphic = el2img(element);
15038 int anim_mode = graphic_info[graphic].anim_mode;
15040 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15041 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15046 static void LoadEngineSnapshotValues_RND()
15048 unsigned int num_random_calls = game.num_random_calls;
15051 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15053 int element = EL_CUSTOM_START + i;
15055 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15058 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15060 int element = EL_GROUP_START + i;
15062 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15065 for (i = 0; i < 4; i++)
15067 for (j = 0; j < NUM_BELT_PARTS; j++)
15069 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15070 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15072 graphic_info[graphic].anim_mode = anim_mode;
15076 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15078 InitRND(tape.random_seed);
15079 for (i = 0; i < num_random_calls; i++)
15083 if (game.num_random_calls != num_random_calls)
15085 Error(ERR_INFO, "number of random calls out of sync");
15086 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15087 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15088 Error(ERR_EXIT, "this should not happen -- please debug");
15092 void FreeEngineSnapshotSingle()
15094 FreeSnapshotSingle();
15096 setString(&snapshot_level_identifier, NULL);
15097 snapshot_level_nr = -1;
15100 void FreeEngineSnapshotList()
15102 FreeSnapshotList();
15105 ListNode *SaveEngineSnapshotBuffers()
15107 ListNode *buffers = NULL;
15109 /* copy some special values to a structure better suited for the snapshot */
15111 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15112 SaveEngineSnapshotValues_RND();
15113 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15114 SaveEngineSnapshotValues_EM();
15115 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15116 SaveEngineSnapshotValues_SP(&buffers);
15117 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15118 SaveEngineSnapshotValues_MM(&buffers);
15120 /* save values stored in special snapshot structure */
15122 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15123 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15124 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15125 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15126 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15127 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15128 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15129 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15131 /* save further RND engine values */
15133 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15134 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15135 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15137 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZX));
15138 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZY));
15139 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitX));
15140 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitY));
15142 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15143 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15144 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15145 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15146 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15148 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15149 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15150 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15152 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15154 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AllPlayersGone));
15156 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15157 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15159 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15160 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15161 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15162 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15163 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15164 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15165 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15166 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15167 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15168 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15169 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15170 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15171 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15172 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15173 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15174 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15175 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15176 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15178 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15179 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15181 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15182 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15183 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15185 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15186 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15188 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15189 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15190 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15191 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15192 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15194 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15195 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15198 ListNode *node = engine_snapshot_list_rnd;
15201 while (node != NULL)
15203 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15208 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15214 void SaveEngineSnapshotSingle()
15216 ListNode *buffers = SaveEngineSnapshotBuffers();
15218 /* finally save all snapshot buffers to single snapshot */
15219 SaveSnapshotSingle(buffers);
15221 /* save level identification information */
15222 setString(&snapshot_level_identifier, leveldir_current->identifier);
15223 snapshot_level_nr = level_nr;
15226 boolean CheckSaveEngineSnapshotToList()
15228 boolean save_snapshot =
15229 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15230 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15231 game.snapshot.changed_action) ||
15232 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15233 game.snapshot.collected_item));
15235 game.snapshot.changed_action = FALSE;
15236 game.snapshot.collected_item = FALSE;
15237 game.snapshot.save_snapshot = save_snapshot;
15239 return save_snapshot;
15242 void SaveEngineSnapshotToList()
15244 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15248 ListNode *buffers = SaveEngineSnapshotBuffers();
15250 /* finally save all snapshot buffers to snapshot list */
15251 SaveSnapshotToList(buffers);
15254 void SaveEngineSnapshotToListInitial()
15256 FreeEngineSnapshotList();
15258 SaveEngineSnapshotToList();
15261 void LoadEngineSnapshotValues()
15263 /* restore special values from snapshot structure */
15265 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15266 LoadEngineSnapshotValues_RND();
15267 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15268 LoadEngineSnapshotValues_EM();
15269 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15270 LoadEngineSnapshotValues_SP();
15271 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15272 LoadEngineSnapshotValues_MM();
15275 void LoadEngineSnapshotSingle()
15277 LoadSnapshotSingle();
15279 LoadEngineSnapshotValues();
15282 void LoadEngineSnapshot_Undo(int steps)
15284 LoadSnapshotFromList_Older(steps);
15286 LoadEngineSnapshotValues();
15289 void LoadEngineSnapshot_Redo(int steps)
15291 LoadSnapshotFromList_Newer(steps);
15293 LoadEngineSnapshotValues();
15296 boolean CheckEngineSnapshotSingle()
15298 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15299 snapshot_level_nr == level_nr);
15302 boolean CheckEngineSnapshotList()
15304 return CheckSnapshotList();
15308 /* ---------- new game button stuff ---------------------------------------- */
15315 boolean *setup_value;
15316 boolean allowed_on_tape;
15318 } gamebutton_info[NUM_GAME_BUTTONS] =
15321 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15322 GAME_CTRL_ID_STOP, NULL,
15326 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15327 GAME_CTRL_ID_PAUSE, NULL,
15331 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15332 GAME_CTRL_ID_PLAY, NULL,
15336 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15337 GAME_CTRL_ID_UNDO, NULL,
15341 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15342 GAME_CTRL_ID_REDO, NULL,
15346 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15347 GAME_CTRL_ID_SAVE, NULL,
15351 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15352 GAME_CTRL_ID_PAUSE2, NULL,
15356 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15357 GAME_CTRL_ID_LOAD, NULL,
15361 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15362 GAME_CTRL_ID_PANEL_STOP, NULL,
15366 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15367 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15368 FALSE, "pause game"
15371 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15372 GAME_CTRL_ID_PANEL_PLAY, NULL,
15376 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15377 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15378 TRUE, "background music on/off"
15381 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15382 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15383 TRUE, "sound loops on/off"
15386 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15387 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15388 TRUE, "normal sounds on/off"
15391 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15392 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15393 FALSE, "background music on/off"
15396 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15397 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15398 FALSE, "sound loops on/off"
15401 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15402 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15403 FALSE, "normal sounds on/off"
15407 void CreateGameButtons()
15411 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15413 struct GraphicInfo *gfx = &graphic_info[gamebutton_info[i].graphic];
15414 struct XY *pos = gamebutton_info[i].pos;
15415 struct GadgetInfo *gi;
15418 unsigned int event_mask;
15419 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15420 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15421 int base_x = (on_tape ? VX : DX);
15422 int base_y = (on_tape ? VY : DY);
15423 int gd_x = gfx->src_x;
15424 int gd_y = gfx->src_y;
15425 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15426 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15427 int gd_xa = gfx->src_x + gfx->active_xoffset;
15428 int gd_ya = gfx->src_y + gfx->active_yoffset;
15429 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15430 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15433 if (gfx->bitmap == NULL)
15435 game_gadget[id] = NULL;
15440 if (id == GAME_CTRL_ID_STOP ||
15441 id == GAME_CTRL_ID_PANEL_STOP ||
15442 id == GAME_CTRL_ID_PLAY ||
15443 id == GAME_CTRL_ID_PANEL_PLAY ||
15444 id == GAME_CTRL_ID_SAVE ||
15445 id == GAME_CTRL_ID_LOAD)
15447 button_type = GD_TYPE_NORMAL_BUTTON;
15449 event_mask = GD_EVENT_RELEASED;
15451 else if (id == GAME_CTRL_ID_UNDO ||
15452 id == GAME_CTRL_ID_REDO)
15454 button_type = GD_TYPE_NORMAL_BUTTON;
15456 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15460 button_type = GD_TYPE_CHECK_BUTTON;
15461 checked = (gamebutton_info[i].setup_value != NULL ?
15462 *gamebutton_info[i].setup_value : FALSE);
15463 event_mask = GD_EVENT_PRESSED;
15466 gi = CreateGadget(GDI_CUSTOM_ID, id,
15467 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15468 GDI_X, base_x + GDI_ACTIVE_POS(pos->x),
15469 GDI_Y, base_y + GDI_ACTIVE_POS(pos->y),
15470 GDI_WIDTH, gfx->width,
15471 GDI_HEIGHT, gfx->height,
15472 GDI_TYPE, button_type,
15473 GDI_STATE, GD_BUTTON_UNPRESSED,
15474 GDI_CHECKED, checked,
15475 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15476 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15477 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15478 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15479 GDI_DIRECT_DRAW, FALSE,
15480 GDI_EVENT_MASK, event_mask,
15481 GDI_CALLBACK_ACTION, HandleGameButtons,
15485 Error(ERR_EXIT, "cannot create gadget");
15487 game_gadget[id] = gi;
15491 void FreeGameButtons()
15495 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15496 FreeGadget(game_gadget[i]);
15499 static void UnmapGameButtonsAtSamePosition(int id)
15503 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15505 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15506 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15507 UnmapGadget(game_gadget[i]);
15510 static void UnmapGameButtonsAtSamePosition_All()
15512 if (setup.show_snapshot_buttons)
15514 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15515 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15516 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15520 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15521 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15522 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15524 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15525 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15526 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15530 static void MapGameButtonsAtSamePosition(int id)
15534 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15536 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15537 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15538 MapGadget(game_gadget[i]);
15540 UnmapGameButtonsAtSamePosition_All();
15543 void MapUndoRedoButtons()
15545 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15546 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15548 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15549 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15551 ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, TRUE, GDI_END);
15554 void UnmapUndoRedoButtons()
15556 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15557 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15559 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15560 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15562 ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, FALSE, GDI_END);
15565 void MapGameButtonsExt(boolean on_tape)
15569 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15570 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15571 i != GAME_CTRL_ID_UNDO &&
15572 i != GAME_CTRL_ID_REDO)
15573 MapGadget(game_gadget[i]);
15575 UnmapGameButtonsAtSamePosition_All();
15577 RedrawGameButtons();
15580 void UnmapGameButtonsExt(boolean on_tape)
15584 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15585 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15586 UnmapGadget(game_gadget[i]);
15589 void RedrawGameButtonsExt(boolean on_tape)
15593 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15594 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15595 RedrawGadget(game_gadget[i]);
15597 // RedrawGadget() may have set REDRAW_ALL if buttons are defined off-area
15598 redraw_mask &= ~REDRAW_ALL;
15601 void SetGadgetState(struct GadgetInfo *gi, boolean state)
15606 gi->checked = state;
15609 void RedrawSoundButtonGadget(int id)
15611 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15612 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15613 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15614 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15615 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15616 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15619 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15620 RedrawGadget(game_gadget[id2]);
15623 void MapGameButtons()
15625 MapGameButtonsExt(FALSE);
15628 void UnmapGameButtons()
15630 UnmapGameButtonsExt(FALSE);
15633 void RedrawGameButtons()
15635 RedrawGameButtonsExt(FALSE);
15638 void MapGameButtonsOnTape()
15640 MapGameButtonsExt(TRUE);
15643 void UnmapGameButtonsOnTape()
15645 UnmapGameButtonsExt(TRUE);
15648 void RedrawGameButtonsOnTape()
15650 RedrawGameButtonsExt(TRUE);
15653 void GameUndoRedoExt()
15655 ClearPlayerAction();
15657 tape.pausing = TRUE;
15660 UpdateAndDisplayGameControlValues();
15662 DrawCompleteVideoDisplay();
15663 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
15664 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
15665 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
15670 void GameUndo(int steps)
15672 if (!CheckEngineSnapshotList())
15675 LoadEngineSnapshot_Undo(steps);
15680 void GameRedo(int steps)
15682 if (!CheckEngineSnapshotList())
15685 LoadEngineSnapshot_Redo(steps);
15690 static void HandleGameButtonsExt(int id, int button)
15692 static boolean game_undo_executed = FALSE;
15693 int steps = BUTTON_STEPSIZE(button);
15694 boolean handle_game_buttons =
15695 (game_status == GAME_MODE_PLAYING ||
15696 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
15698 if (!handle_game_buttons)
15703 case GAME_CTRL_ID_STOP:
15704 case GAME_CTRL_ID_PANEL_STOP:
15705 if (game_status == GAME_MODE_MAIN)
15711 RequestQuitGame(TRUE);
15715 case GAME_CTRL_ID_PAUSE:
15716 case GAME_CTRL_ID_PAUSE2:
15717 case GAME_CTRL_ID_PANEL_PAUSE:
15718 if (options.network && game_status == GAME_MODE_PLAYING)
15720 #if defined(NETWORK_AVALIABLE)
15722 SendToServer_ContinuePlaying();
15724 SendToServer_PausePlaying();
15728 TapeTogglePause(TAPE_TOGGLE_MANUAL);
15730 game_undo_executed = FALSE;
15734 case GAME_CTRL_ID_PLAY:
15735 case GAME_CTRL_ID_PANEL_PLAY:
15736 if (game_status == GAME_MODE_MAIN)
15738 StartGameActions(options.network, setup.autorecord, level.random_seed);
15740 else if (tape.pausing)
15742 #if defined(NETWORK_AVALIABLE)
15743 if (options.network)
15744 SendToServer_ContinuePlaying();
15747 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
15751 case GAME_CTRL_ID_UNDO:
15752 // Important: When using "save snapshot when collecting an item" mode,
15753 // load last (current) snapshot for first "undo" after pressing "pause"
15754 // (else the last-but-one snapshot would be loaded, because the snapshot
15755 // pointer already points to the last snapshot when pressing "pause",
15756 // which is fine for "every step/move" mode, but not for "every collect")
15757 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15758 !game_undo_executed)
15761 game_undo_executed = TRUE;
15766 case GAME_CTRL_ID_REDO:
15770 case GAME_CTRL_ID_SAVE:
15774 case GAME_CTRL_ID_LOAD:
15778 case SOUND_CTRL_ID_MUSIC:
15779 case SOUND_CTRL_ID_PANEL_MUSIC:
15780 if (setup.sound_music)
15782 setup.sound_music = FALSE;
15786 else if (audio.music_available)
15788 setup.sound = setup.sound_music = TRUE;
15790 SetAudioMode(setup.sound);
15792 if (game_status == GAME_MODE_PLAYING)
15796 RedrawSoundButtonGadget(id);
15800 case SOUND_CTRL_ID_LOOPS:
15801 case SOUND_CTRL_ID_PANEL_LOOPS:
15802 if (setup.sound_loops)
15803 setup.sound_loops = FALSE;
15804 else if (audio.loops_available)
15806 setup.sound = setup.sound_loops = TRUE;
15808 SetAudioMode(setup.sound);
15811 RedrawSoundButtonGadget(id);
15815 case SOUND_CTRL_ID_SIMPLE:
15816 case SOUND_CTRL_ID_PANEL_SIMPLE:
15817 if (setup.sound_simple)
15818 setup.sound_simple = FALSE;
15819 else if (audio.sound_available)
15821 setup.sound = setup.sound_simple = TRUE;
15823 SetAudioMode(setup.sound);
15826 RedrawSoundButtonGadget(id);
15835 static void HandleGameButtons(struct GadgetInfo *gi)
15837 HandleGameButtonsExt(gi->custom_id, gi->event.button);
15840 void HandleSoundButtonKeys(Key key)
15842 if (key == setup.shortcut.sound_simple)
15843 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
15844 else if (key == setup.shortcut.sound_loops)
15845 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
15846 else if (key == setup.shortcut.sound_music)
15847 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);