1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_INVENTORY_COUNT 2
93 #define GAME_PANEL_INVENTORY_FIRST_1 3
94 #define GAME_PANEL_INVENTORY_FIRST_2 4
95 #define GAME_PANEL_INVENTORY_FIRST_3 5
96 #define GAME_PANEL_INVENTORY_FIRST_4 6
97 #define GAME_PANEL_INVENTORY_FIRST_5 7
98 #define GAME_PANEL_INVENTORY_FIRST_6 8
99 #define GAME_PANEL_INVENTORY_FIRST_7 9
100 #define GAME_PANEL_INVENTORY_FIRST_8 10
101 #define GAME_PANEL_INVENTORY_LAST_1 11
102 #define GAME_PANEL_INVENTORY_LAST_2 12
103 #define GAME_PANEL_INVENTORY_LAST_3 13
104 #define GAME_PANEL_INVENTORY_LAST_4 14
105 #define GAME_PANEL_INVENTORY_LAST_5 15
106 #define GAME_PANEL_INVENTORY_LAST_6 16
107 #define GAME_PANEL_INVENTORY_LAST_7 17
108 #define GAME_PANEL_INVENTORY_LAST_8 18
109 #define GAME_PANEL_KEY_1 19
110 #define GAME_PANEL_KEY_2 20
111 #define GAME_PANEL_KEY_3 21
112 #define GAME_PANEL_KEY_4 22
113 #define GAME_PANEL_KEY_5 23
114 #define GAME_PANEL_KEY_6 24
115 #define GAME_PANEL_KEY_7 25
116 #define GAME_PANEL_KEY_8 26
117 #define GAME_PANEL_KEY_WHITE 27
118 #define GAME_PANEL_KEY_WHITE_COUNT 28
119 #define GAME_PANEL_SCORE 29
120 #define GAME_PANEL_HIGHSCORE 30
121 #define GAME_PANEL_TIME 31
122 #define GAME_PANEL_TIME_HH 32
123 #define GAME_PANEL_TIME_MM 33
124 #define GAME_PANEL_TIME_SS 34
125 #define GAME_PANEL_TIME_ANIM 35
126 #define GAME_PANEL_HEALTH 36
127 #define GAME_PANEL_HEALTH_ANIM 37
128 #define GAME_PANEL_FRAME 38
129 #define GAME_PANEL_SHIELD_NORMAL 39
130 #define GAME_PANEL_SHIELD_NORMAL_TIME 40
131 #define GAME_PANEL_SHIELD_DEADLY 41
132 #define GAME_PANEL_SHIELD_DEADLY_TIME 42
133 #define GAME_PANEL_EXIT 43
134 #define GAME_PANEL_EMC_MAGIC_BALL 44
135 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 45
136 #define GAME_PANEL_LIGHT_SWITCH 46
137 #define GAME_PANEL_LIGHT_SWITCH_TIME 47
138 #define GAME_PANEL_TIMEGATE_SWITCH 48
139 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 49
140 #define GAME_PANEL_SWITCHGATE_SWITCH 50
141 #define GAME_PANEL_EMC_LENSES 51
142 #define GAME_PANEL_EMC_LENSES_TIME 52
143 #define GAME_PANEL_EMC_MAGNIFIER 53
144 #define GAME_PANEL_EMC_MAGNIFIER_TIME 54
145 #define GAME_PANEL_BALLOON_SWITCH 55
146 #define GAME_PANEL_DYNABOMB_NUMBER 56
147 #define GAME_PANEL_DYNABOMB_SIZE 57
148 #define GAME_PANEL_DYNABOMB_POWER 58
149 #define GAME_PANEL_PENGUINS 59
150 #define GAME_PANEL_SOKOBAN_OBJECTS 60
151 #define GAME_PANEL_SOKOBAN_FIELDS 61
152 #define GAME_PANEL_ROBOT_WHEEL 62
153 #define GAME_PANEL_CONVEYOR_BELT_1 63
154 #define GAME_PANEL_CONVEYOR_BELT_2 64
155 #define GAME_PANEL_CONVEYOR_BELT_3 65
156 #define GAME_PANEL_CONVEYOR_BELT_4 66
157 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 67
158 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 68
159 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 69
160 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 70
161 #define GAME_PANEL_MAGIC_WALL 71
162 #define GAME_PANEL_MAGIC_WALL_TIME 72
163 #define GAME_PANEL_GRAVITY_STATE 73
164 #define GAME_PANEL_GRAPHIC_1 74
165 #define GAME_PANEL_GRAPHIC_2 75
166 #define GAME_PANEL_GRAPHIC_3 76
167 #define GAME_PANEL_GRAPHIC_4 77
168 #define GAME_PANEL_GRAPHIC_5 78
169 #define GAME_PANEL_GRAPHIC_6 79
170 #define GAME_PANEL_GRAPHIC_7 80
171 #define GAME_PANEL_GRAPHIC_8 81
172 #define GAME_PANEL_ELEMENT_1 82
173 #define GAME_PANEL_ELEMENT_2 83
174 #define GAME_PANEL_ELEMENT_3 84
175 #define GAME_PANEL_ELEMENT_4 85
176 #define GAME_PANEL_ELEMENT_5 86
177 #define GAME_PANEL_ELEMENT_6 87
178 #define GAME_PANEL_ELEMENT_7 88
179 #define GAME_PANEL_ELEMENT_8 89
180 #define GAME_PANEL_ELEMENT_COUNT_1 90
181 #define GAME_PANEL_ELEMENT_COUNT_2 91
182 #define GAME_PANEL_ELEMENT_COUNT_3 92
183 #define GAME_PANEL_ELEMENT_COUNT_4 93
184 #define GAME_PANEL_ELEMENT_COUNT_5 94
185 #define GAME_PANEL_ELEMENT_COUNT_6 95
186 #define GAME_PANEL_ELEMENT_COUNT_7 96
187 #define GAME_PANEL_ELEMENT_COUNT_8 97
188 #define GAME_PANEL_CE_SCORE_1 98
189 #define GAME_PANEL_CE_SCORE_2 99
190 #define GAME_PANEL_CE_SCORE_3 100
191 #define GAME_PANEL_CE_SCORE_4 101
192 #define GAME_PANEL_CE_SCORE_5 102
193 #define GAME_PANEL_CE_SCORE_6 103
194 #define GAME_PANEL_CE_SCORE_7 104
195 #define GAME_PANEL_CE_SCORE_8 105
196 #define GAME_PANEL_CE_SCORE_1_ELEMENT 106
197 #define GAME_PANEL_CE_SCORE_2_ELEMENT 107
198 #define GAME_PANEL_CE_SCORE_3_ELEMENT 108
199 #define GAME_PANEL_CE_SCORE_4_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_5_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_6_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_7_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_8_ELEMENT 113
204 #define GAME_PANEL_PLAYER_NAME 114
205 #define GAME_PANEL_LEVEL_NAME 115
206 #define GAME_PANEL_LEVEL_AUTHOR 116
208 #define NUM_GAME_PANEL_CONTROLS 117
210 struct GamePanelOrderInfo
216 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
218 struct GamePanelControlInfo
222 struct TextPosInfo *pos;
225 int graphic, graphic_active;
227 int value, last_value;
228 int frame, last_frame;
233 static struct GamePanelControlInfo game_panel_controls[] =
236 GAME_PANEL_LEVEL_NUMBER,
237 &game.panel.level_number,
246 GAME_PANEL_INVENTORY_COUNT,
247 &game.panel.inventory_count,
251 GAME_PANEL_INVENTORY_FIRST_1,
252 &game.panel.inventory_first[0],
256 GAME_PANEL_INVENTORY_FIRST_2,
257 &game.panel.inventory_first[1],
261 GAME_PANEL_INVENTORY_FIRST_3,
262 &game.panel.inventory_first[2],
266 GAME_PANEL_INVENTORY_FIRST_4,
267 &game.panel.inventory_first[3],
271 GAME_PANEL_INVENTORY_FIRST_5,
272 &game.panel.inventory_first[4],
276 GAME_PANEL_INVENTORY_FIRST_6,
277 &game.panel.inventory_first[5],
281 GAME_PANEL_INVENTORY_FIRST_7,
282 &game.panel.inventory_first[6],
286 GAME_PANEL_INVENTORY_FIRST_8,
287 &game.panel.inventory_first[7],
291 GAME_PANEL_INVENTORY_LAST_1,
292 &game.panel.inventory_last[0],
296 GAME_PANEL_INVENTORY_LAST_2,
297 &game.panel.inventory_last[1],
301 GAME_PANEL_INVENTORY_LAST_3,
302 &game.panel.inventory_last[2],
306 GAME_PANEL_INVENTORY_LAST_4,
307 &game.panel.inventory_last[3],
311 GAME_PANEL_INVENTORY_LAST_5,
312 &game.panel.inventory_last[4],
316 GAME_PANEL_INVENTORY_LAST_6,
317 &game.panel.inventory_last[5],
321 GAME_PANEL_INVENTORY_LAST_7,
322 &game.panel.inventory_last[6],
326 GAME_PANEL_INVENTORY_LAST_8,
327 &game.panel.inventory_last[7],
371 GAME_PANEL_KEY_WHITE,
372 &game.panel.key_white,
376 GAME_PANEL_KEY_WHITE_COUNT,
377 &game.panel.key_white_count,
386 GAME_PANEL_HIGHSCORE,
387 &game.panel.highscore,
411 GAME_PANEL_TIME_ANIM,
412 &game.panel.time_anim,
415 IMG_GFX_GAME_PANEL_TIME_ANIM,
416 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
424 GAME_PANEL_HEALTH_ANIM,
425 &game.panel.health_anim,
428 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
429 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
437 GAME_PANEL_SHIELD_NORMAL,
438 &game.panel.shield_normal,
442 GAME_PANEL_SHIELD_NORMAL_TIME,
443 &game.panel.shield_normal_time,
447 GAME_PANEL_SHIELD_DEADLY,
448 &game.panel.shield_deadly,
452 GAME_PANEL_SHIELD_DEADLY_TIME,
453 &game.panel.shield_deadly_time,
462 GAME_PANEL_EMC_MAGIC_BALL,
463 &game.panel.emc_magic_ball,
467 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
468 &game.panel.emc_magic_ball_switch,
472 GAME_PANEL_LIGHT_SWITCH,
473 &game.panel.light_switch,
477 GAME_PANEL_LIGHT_SWITCH_TIME,
478 &game.panel.light_switch_time,
482 GAME_PANEL_TIMEGATE_SWITCH,
483 &game.panel.timegate_switch,
487 GAME_PANEL_TIMEGATE_SWITCH_TIME,
488 &game.panel.timegate_switch_time,
492 GAME_PANEL_SWITCHGATE_SWITCH,
493 &game.panel.switchgate_switch,
497 GAME_PANEL_EMC_LENSES,
498 &game.panel.emc_lenses,
502 GAME_PANEL_EMC_LENSES_TIME,
503 &game.panel.emc_lenses_time,
507 GAME_PANEL_EMC_MAGNIFIER,
508 &game.panel.emc_magnifier,
512 GAME_PANEL_EMC_MAGNIFIER_TIME,
513 &game.panel.emc_magnifier_time,
517 GAME_PANEL_BALLOON_SWITCH,
518 &game.panel.balloon_switch,
522 GAME_PANEL_DYNABOMB_NUMBER,
523 &game.panel.dynabomb_number,
527 GAME_PANEL_DYNABOMB_SIZE,
528 &game.panel.dynabomb_size,
532 GAME_PANEL_DYNABOMB_POWER,
533 &game.panel.dynabomb_power,
538 &game.panel.penguins,
542 GAME_PANEL_SOKOBAN_OBJECTS,
543 &game.panel.sokoban_objects,
547 GAME_PANEL_SOKOBAN_FIELDS,
548 &game.panel.sokoban_fields,
552 GAME_PANEL_ROBOT_WHEEL,
553 &game.panel.robot_wheel,
557 GAME_PANEL_CONVEYOR_BELT_1,
558 &game.panel.conveyor_belt[0],
562 GAME_PANEL_CONVEYOR_BELT_2,
563 &game.panel.conveyor_belt[1],
567 GAME_PANEL_CONVEYOR_BELT_3,
568 &game.panel.conveyor_belt[2],
572 GAME_PANEL_CONVEYOR_BELT_4,
573 &game.panel.conveyor_belt[3],
577 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
578 &game.panel.conveyor_belt_switch[0],
582 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
583 &game.panel.conveyor_belt_switch[1],
587 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
588 &game.panel.conveyor_belt_switch[2],
592 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
593 &game.panel.conveyor_belt_switch[3],
597 GAME_PANEL_MAGIC_WALL,
598 &game.panel.magic_wall,
602 GAME_PANEL_MAGIC_WALL_TIME,
603 &game.panel.magic_wall_time,
607 GAME_PANEL_GRAVITY_STATE,
608 &game.panel.gravity_state,
612 GAME_PANEL_GRAPHIC_1,
613 &game.panel.graphic[0],
617 GAME_PANEL_GRAPHIC_2,
618 &game.panel.graphic[1],
622 GAME_PANEL_GRAPHIC_3,
623 &game.panel.graphic[2],
627 GAME_PANEL_GRAPHIC_4,
628 &game.panel.graphic[3],
632 GAME_PANEL_GRAPHIC_5,
633 &game.panel.graphic[4],
637 GAME_PANEL_GRAPHIC_6,
638 &game.panel.graphic[5],
642 GAME_PANEL_GRAPHIC_7,
643 &game.panel.graphic[6],
647 GAME_PANEL_GRAPHIC_8,
648 &game.panel.graphic[7],
652 GAME_PANEL_ELEMENT_1,
653 &game.panel.element[0],
657 GAME_PANEL_ELEMENT_2,
658 &game.panel.element[1],
662 GAME_PANEL_ELEMENT_3,
663 &game.panel.element[2],
667 GAME_PANEL_ELEMENT_4,
668 &game.panel.element[3],
672 GAME_PANEL_ELEMENT_5,
673 &game.panel.element[4],
677 GAME_PANEL_ELEMENT_6,
678 &game.panel.element[5],
682 GAME_PANEL_ELEMENT_7,
683 &game.panel.element[6],
687 GAME_PANEL_ELEMENT_8,
688 &game.panel.element[7],
692 GAME_PANEL_ELEMENT_COUNT_1,
693 &game.panel.element_count[0],
697 GAME_PANEL_ELEMENT_COUNT_2,
698 &game.panel.element_count[1],
702 GAME_PANEL_ELEMENT_COUNT_3,
703 &game.panel.element_count[2],
707 GAME_PANEL_ELEMENT_COUNT_4,
708 &game.panel.element_count[3],
712 GAME_PANEL_ELEMENT_COUNT_5,
713 &game.panel.element_count[4],
717 GAME_PANEL_ELEMENT_COUNT_6,
718 &game.panel.element_count[5],
722 GAME_PANEL_ELEMENT_COUNT_7,
723 &game.panel.element_count[6],
727 GAME_PANEL_ELEMENT_COUNT_8,
728 &game.panel.element_count[7],
732 GAME_PANEL_CE_SCORE_1,
733 &game.panel.ce_score[0],
737 GAME_PANEL_CE_SCORE_2,
738 &game.panel.ce_score[1],
742 GAME_PANEL_CE_SCORE_3,
743 &game.panel.ce_score[2],
747 GAME_PANEL_CE_SCORE_4,
748 &game.panel.ce_score[3],
752 GAME_PANEL_CE_SCORE_5,
753 &game.panel.ce_score[4],
757 GAME_PANEL_CE_SCORE_6,
758 &game.panel.ce_score[5],
762 GAME_PANEL_CE_SCORE_7,
763 &game.panel.ce_score[6],
767 GAME_PANEL_CE_SCORE_8,
768 &game.panel.ce_score[7],
772 GAME_PANEL_CE_SCORE_1_ELEMENT,
773 &game.panel.ce_score_element[0],
777 GAME_PANEL_CE_SCORE_2_ELEMENT,
778 &game.panel.ce_score_element[1],
782 GAME_PANEL_CE_SCORE_3_ELEMENT,
783 &game.panel.ce_score_element[2],
787 GAME_PANEL_CE_SCORE_4_ELEMENT,
788 &game.panel.ce_score_element[3],
792 GAME_PANEL_CE_SCORE_5_ELEMENT,
793 &game.panel.ce_score_element[4],
797 GAME_PANEL_CE_SCORE_6_ELEMENT,
798 &game.panel.ce_score_element[5],
802 GAME_PANEL_CE_SCORE_7_ELEMENT,
803 &game.panel.ce_score_element[6],
807 GAME_PANEL_CE_SCORE_8_ELEMENT,
808 &game.panel.ce_score_element[7],
812 GAME_PANEL_PLAYER_NAME,
813 &game.panel.player_name,
817 GAME_PANEL_LEVEL_NAME,
818 &game.panel.level_name,
822 GAME_PANEL_LEVEL_AUTHOR,
823 &game.panel.level_author,
834 // values for delayed check of falling and moving elements and for collision
835 #define CHECK_DELAY_MOVING 3
836 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
837 #define CHECK_DELAY_COLLISION 2
838 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
840 // values for initial player move delay (initial delay counter value)
841 #define INITIAL_MOVE_DELAY_OFF -1
842 #define INITIAL_MOVE_DELAY_ON 0
844 // values for player movement speed (which is in fact a delay value)
845 #define MOVE_DELAY_MIN_SPEED 32
846 #define MOVE_DELAY_NORMAL_SPEED 8
847 #define MOVE_DELAY_HIGH_SPEED 4
848 #define MOVE_DELAY_MAX_SPEED 1
850 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
851 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
853 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
854 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
856 // values for scroll positions
857 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
858 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
860 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
861 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
864 // values for other actions
865 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
866 #define MOVE_STEPSIZE_MIN (1)
867 #define MOVE_STEPSIZE_MAX (TILEX)
869 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
870 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
872 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
874 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
875 RND(element_info[e].push_delay_random))
876 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
877 RND(element_info[e].drop_delay_random))
878 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
879 RND(element_info[e].move_delay_random))
880 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
881 (element_info[e].move_delay_random))
882 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
883 RND(element_info[e].ce_value_random_initial))
884 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
885 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
886 RND((c)->delay_random * (c)->delay_frames))
887 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
888 RND((c)->delay_random))
891 #define GET_VALID_RUNTIME_ELEMENT(e) \
892 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
894 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
895 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
896 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
897 (be) + (e) - EL_SELF)
899 #define GET_PLAYER_FROM_BITS(p) \
900 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
902 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
903 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
904 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
905 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
906 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
907 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
908 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
909 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
910 RESOLVED_REFERENCE_ELEMENT(be, e) : \
913 #define CAN_GROW_INTO(e) \
914 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
916 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
917 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
922 (CAN_MOVE_INTO_ACID(e) && \
923 Feld[x][y] == EL_ACID) || \
926 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
927 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
928 (CAN_MOVE_INTO_ACID(e) && \
929 Feld[x][y] == EL_ACID) || \
932 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
933 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
935 (CAN_MOVE_INTO_ACID(e) && \
936 Feld[x][y] == EL_ACID) || \
937 (DONT_COLLIDE_WITH(e) && \
939 !PLAYER_ENEMY_PROTECTED(x, y))))
941 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
942 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
944 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
945 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
947 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
948 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Feld[x][y] == EL_EMC_PLANT)
950 #define ANDROID_CAN_CLONE_FIELD(x, y) \
951 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Feld[x][y]) || \
952 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
954 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
955 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
957 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
958 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Feld[x][y] == EL_DIAMOND)
960 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
961 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Feld[x][y]))
963 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Feld[x][y]))
966 #define PIG_CAN_ENTER_FIELD(e, x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Feld[x][y]))
969 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Feld[x][y] == EL_EXIT_OPEN || \
971 Feld[x][y] == EL_EM_EXIT_OPEN || \
972 Feld[x][y] == EL_STEEL_EXIT_OPEN || \
973 Feld[x][y] == EL_EM_STEEL_EXIT_OPEN || \
974 IS_FOOD_PENGUIN(Feld[x][y])))
975 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
976 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
978 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
979 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
981 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
982 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
984 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
985 (IN_LEV_FIELD(x, y) && (Feld[x][y] == EL_EMC_SPRING_BUMPER || \
986 Feld[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
988 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
990 #define CE_ENTER_FIELD_COND(e, x, y) \
991 (!IS_PLAYER(x, y) && \
992 IS_EQUAL_OR_IN_GROUP(Feld[x][y], MOVE_ENTER_EL(e)))
994 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
995 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
997 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
998 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1000 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1001 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1002 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1003 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1005 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1007 // game button identifiers
1008 #define GAME_CTRL_ID_STOP 0
1009 #define GAME_CTRL_ID_PAUSE 1
1010 #define GAME_CTRL_ID_PLAY 2
1011 #define GAME_CTRL_ID_UNDO 3
1012 #define GAME_CTRL_ID_REDO 4
1013 #define GAME_CTRL_ID_SAVE 5
1014 #define GAME_CTRL_ID_PAUSE2 6
1015 #define GAME_CTRL_ID_LOAD 7
1016 #define GAME_CTRL_ID_PANEL_STOP 8
1017 #define GAME_CTRL_ID_PANEL_PAUSE 9
1018 #define GAME_CTRL_ID_PANEL_PLAY 10
1019 #define 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(void);
1089 static void FadeLevelSoundsAndMusic(void);
1091 static void HandleGameButtons(struct GadgetInfo *);
1093 int AmoebeNachbarNr(int, int);
1094 void AmoebeUmwandeln(int, int);
1095 void ContinueMoving(int, int);
1096 void Bang(int, int);
1097 void InitMovDir(int, int);
1098 void InitAmoebaNr(int, int);
1099 int NewHiScore(int);
1101 void TestIfGoodThingHitsBadThing(int, int, int);
1102 void TestIfBadThingHitsGoodThing(int, int, int);
1103 void TestIfPlayerTouchesBadThing(int, int);
1104 void TestIfPlayerRunsIntoBadThing(int, int, int);
1105 void TestIfBadThingTouchesPlayer(int, int);
1106 void TestIfBadThingRunsIntoPlayer(int, int, int);
1107 void TestIfFriendTouchesBadThing(int, int);
1108 void TestIfBadThingTouchesFriend(int, int);
1109 void TestIfBadThingTouchesOtherBadThing(int, int);
1110 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1112 void KillPlayer(struct PlayerInfo *);
1113 void BuryPlayer(struct PlayerInfo *);
1114 void RemovePlayer(struct PlayerInfo *);
1115 void ExitPlayer(struct PlayerInfo *);
1117 static int getInvisibleActiveFromInvisibleElement(int);
1118 static int getInvisibleFromInvisibleActiveElement(int);
1120 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1122 // for detection of endless loops, caused by custom element programming
1123 // (using maximal playfield width x 10 is just a rough approximation)
1124 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1126 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1128 if (recursion_loop_detected) \
1131 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1133 recursion_loop_detected = TRUE; \
1134 recursion_loop_element = (e); \
1137 recursion_loop_depth++; \
1140 #define RECURSION_LOOP_DETECTION_END() \
1142 recursion_loop_depth--; \
1145 static int recursion_loop_depth;
1146 static boolean recursion_loop_detected;
1147 static boolean recursion_loop_element;
1149 static int map_player_action[MAX_PLAYERS];
1152 // ----------------------------------------------------------------------------
1153 // definition of elements that automatically change to other elements after
1154 // a specified time, eventually calling a function when changing
1155 // ----------------------------------------------------------------------------
1157 // forward declaration for changer functions
1158 static void InitBuggyBase(int, int);
1159 static void WarnBuggyBase(int, int);
1161 static void InitTrap(int, int);
1162 static void ActivateTrap(int, int);
1163 static void ChangeActiveTrap(int, int);
1165 static void InitRobotWheel(int, int);
1166 static void RunRobotWheel(int, int);
1167 static void StopRobotWheel(int, int);
1169 static void InitTimegateWheel(int, int);
1170 static void RunTimegateWheel(int, int);
1172 static void InitMagicBallDelay(int, int);
1173 static void ActivateMagicBall(int, int);
1175 struct ChangingElementInfo
1180 void (*pre_change_function)(int x, int y);
1181 void (*change_function)(int x, int y);
1182 void (*post_change_function)(int x, int y);
1185 static struct ChangingElementInfo change_delay_list[] =
1220 EL_STEEL_EXIT_OPENING,
1228 EL_STEEL_EXIT_CLOSING,
1229 EL_STEEL_EXIT_CLOSED,
1252 EL_EM_STEEL_EXIT_OPENING,
1253 EL_EM_STEEL_EXIT_OPEN,
1260 EL_EM_STEEL_EXIT_CLOSING,
1284 EL_SWITCHGATE_OPENING,
1292 EL_SWITCHGATE_CLOSING,
1293 EL_SWITCHGATE_CLOSED,
1300 EL_TIMEGATE_OPENING,
1308 EL_TIMEGATE_CLOSING,
1317 EL_ACID_SPLASH_LEFT,
1325 EL_ACID_SPLASH_RIGHT,
1334 EL_SP_BUGGY_BASE_ACTIVATING,
1341 EL_SP_BUGGY_BASE_ACTIVATING,
1342 EL_SP_BUGGY_BASE_ACTIVE,
1349 EL_SP_BUGGY_BASE_ACTIVE,
1373 EL_ROBOT_WHEEL_ACTIVE,
1381 EL_TIMEGATE_SWITCH_ACTIVE,
1389 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1390 EL_DC_TIMEGATE_SWITCH,
1397 EL_EMC_MAGIC_BALL_ACTIVE,
1398 EL_EMC_MAGIC_BALL_ACTIVE,
1405 EL_EMC_SPRING_BUMPER_ACTIVE,
1406 EL_EMC_SPRING_BUMPER,
1413 EL_DIAGONAL_SHRINKING,
1421 EL_DIAGONAL_GROWING,
1442 int push_delay_fixed, push_delay_random;
1446 { EL_SPRING, 0, 0 },
1447 { EL_BALLOON, 0, 0 },
1449 { EL_SOKOBAN_OBJECT, 2, 0 },
1450 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1451 { EL_SATELLITE, 2, 0 },
1452 { EL_SP_DISK_YELLOW, 2, 0 },
1454 { EL_UNDEFINED, 0, 0 },
1462 move_stepsize_list[] =
1464 { EL_AMOEBA_DROP, 2 },
1465 { EL_AMOEBA_DROPPING, 2 },
1466 { EL_QUICKSAND_FILLING, 1 },
1467 { EL_QUICKSAND_EMPTYING, 1 },
1468 { EL_QUICKSAND_FAST_FILLING, 2 },
1469 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1470 { EL_MAGIC_WALL_FILLING, 2 },
1471 { EL_MAGIC_WALL_EMPTYING, 2 },
1472 { EL_BD_MAGIC_WALL_FILLING, 2 },
1473 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1474 { EL_DC_MAGIC_WALL_FILLING, 2 },
1475 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1477 { EL_UNDEFINED, 0 },
1485 collect_count_list[] =
1488 { EL_BD_DIAMOND, 1 },
1489 { EL_EMERALD_YELLOW, 1 },
1490 { EL_EMERALD_RED, 1 },
1491 { EL_EMERALD_PURPLE, 1 },
1493 { EL_SP_INFOTRON, 1 },
1497 { EL_UNDEFINED, 0 },
1505 access_direction_list[] =
1507 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1508 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1509 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1510 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1511 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1512 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1513 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1514 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1515 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1516 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1517 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1519 { EL_SP_PORT_LEFT, MV_RIGHT },
1520 { EL_SP_PORT_RIGHT, MV_LEFT },
1521 { EL_SP_PORT_UP, MV_DOWN },
1522 { EL_SP_PORT_DOWN, MV_UP },
1523 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1524 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1525 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1526 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1527 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1528 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1529 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1530 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1531 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1532 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1533 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1534 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1535 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1536 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1537 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1539 { EL_UNDEFINED, MV_NONE }
1542 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1544 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1545 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1546 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Feld[x][y]) || \
1547 IS_JUST_CHANGING(x, y))
1549 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1551 // static variables for playfield scan mode (scanning forward or backward)
1552 static int playfield_scan_start_x = 0;
1553 static int playfield_scan_start_y = 0;
1554 static int playfield_scan_delta_x = 1;
1555 static int playfield_scan_delta_y = 1;
1557 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1558 (y) >= 0 && (y) <= lev_fieldy - 1; \
1559 (y) += playfield_scan_delta_y) \
1560 for ((x) = playfield_scan_start_x; \
1561 (x) >= 0 && (x) <= lev_fieldx - 1; \
1562 (x) += playfield_scan_delta_x)
1565 void DEBUG_SetMaximumDynamite(void)
1569 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1570 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1571 local_player->inventory_element[local_player->inventory_size++] =
1576 static void InitPlayfieldScanModeVars(void)
1578 if (game.use_reverse_scan_direction)
1580 playfield_scan_start_x = lev_fieldx - 1;
1581 playfield_scan_start_y = lev_fieldy - 1;
1583 playfield_scan_delta_x = -1;
1584 playfield_scan_delta_y = -1;
1588 playfield_scan_start_x = 0;
1589 playfield_scan_start_y = 0;
1591 playfield_scan_delta_x = 1;
1592 playfield_scan_delta_y = 1;
1596 static void InitPlayfieldScanMode(int mode)
1598 game.use_reverse_scan_direction =
1599 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1601 InitPlayfieldScanModeVars();
1604 static int get_move_delay_from_stepsize(int move_stepsize)
1607 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1609 // make sure that stepsize value is always a power of 2
1610 move_stepsize = (1 << log_2(move_stepsize));
1612 return TILEX / move_stepsize;
1615 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1618 int player_nr = player->index_nr;
1619 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1620 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1622 // do no immediately change move delay -- the player might just be moving
1623 player->move_delay_value_next = move_delay;
1625 // information if player can move must be set separately
1626 player->cannot_move = cannot_move;
1630 player->move_delay = game.initial_move_delay[player_nr];
1631 player->move_delay_value = game.initial_move_delay_value[player_nr];
1633 player->move_delay_value_next = -1;
1635 player->move_delay_reset_counter = 0;
1639 void GetPlayerConfig(void)
1641 GameFrameDelay = setup.game_frame_delay;
1643 if (!audio.sound_available)
1644 setup.sound_simple = FALSE;
1646 if (!audio.loops_available)
1647 setup.sound_loops = FALSE;
1649 if (!audio.music_available)
1650 setup.sound_music = FALSE;
1652 if (!video.fullscreen_available)
1653 setup.fullscreen = FALSE;
1655 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1657 SetAudioMode(setup.sound);
1660 int GetElementFromGroupElement(int element)
1662 if (IS_GROUP_ELEMENT(element))
1664 struct ElementGroupInfo *group = element_info[element].group;
1665 int last_anim_random_frame = gfx.anim_random_frame;
1668 if (group->choice_mode == ANIM_RANDOM)
1669 gfx.anim_random_frame = RND(group->num_elements_resolved);
1671 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1672 group->choice_mode, 0,
1675 if (group->choice_mode == ANIM_RANDOM)
1676 gfx.anim_random_frame = last_anim_random_frame;
1678 group->choice_pos++;
1680 element = group->element_resolved[element_pos];
1686 static void IncrementSokobanFieldsNeeded(void)
1688 if (level.sb_fields_needed)
1689 game.sokoban_fields_still_needed++;
1692 static void IncrementSokobanObjectsNeeded(void)
1694 if (level.sb_objects_needed)
1695 game.sokoban_objects_still_needed++;
1698 static void DecrementSokobanFieldsNeeded(void)
1700 if (game.sokoban_fields_still_needed > 0)
1701 game.sokoban_fields_still_needed--;
1704 static void DecrementSokobanObjectsNeeded(void)
1706 if (game.sokoban_objects_still_needed > 0)
1707 game.sokoban_objects_still_needed--;
1710 static void InitPlayerField(int x, int y, int element, boolean init_game)
1712 if (element == EL_SP_MURPHY)
1716 if (stored_player[0].present)
1718 Feld[x][y] = EL_SP_MURPHY_CLONE;
1724 stored_player[0].initial_element = element;
1725 stored_player[0].use_murphy = TRUE;
1727 if (!level.use_artwork_element[0])
1728 stored_player[0].artwork_element = EL_SP_MURPHY;
1731 Feld[x][y] = EL_PLAYER_1;
1737 struct PlayerInfo *player = &stored_player[Feld[x][y] - EL_PLAYER_1];
1738 int jx = player->jx, jy = player->jy;
1740 player->present = TRUE;
1742 player->block_last_field = (element == EL_SP_MURPHY ?
1743 level.sp_block_last_field :
1744 level.block_last_field);
1746 // ---------- initialize player's last field block delay ------------------
1748 // always start with reliable default value (no adjustment needed)
1749 player->block_delay_adjustment = 0;
1751 // special case 1: in Supaplex, Murphy blocks last field one more frame
1752 if (player->block_last_field && element == EL_SP_MURPHY)
1753 player->block_delay_adjustment = 1;
1755 // special case 2: in game engines before 3.1.1, blocking was different
1756 if (game.use_block_last_field_bug)
1757 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1759 if (!network.enabled || player->connected_network)
1761 player->active = TRUE;
1763 // remove potentially duplicate players
1764 if (StorePlayer[jx][jy] == Feld[x][y])
1765 StorePlayer[jx][jy] = 0;
1767 StorePlayer[x][y] = Feld[x][y];
1769 #if DEBUG_INIT_PLAYER
1772 printf("- player element %d activated", player->element_nr);
1773 printf(" (local player is %d and currently %s)\n",
1774 local_player->element_nr,
1775 local_player->active ? "active" : "not active");
1780 Feld[x][y] = EL_EMPTY;
1782 player->jx = player->last_jx = x;
1783 player->jy = player->last_jy = y;
1788 int player_nr = GET_PLAYER_NR(element);
1789 struct PlayerInfo *player = &stored_player[player_nr];
1791 if (player->active && player->killed)
1792 player->reanimated = TRUE; // if player was just killed, reanimate him
1796 static void InitField(int x, int y, boolean init_game)
1798 int element = Feld[x][y];
1807 InitPlayerField(x, y, element, init_game);
1810 case EL_SOKOBAN_FIELD_PLAYER:
1811 element = Feld[x][y] = EL_PLAYER_1;
1812 InitField(x, y, init_game);
1814 element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1815 InitField(x, y, init_game);
1818 case EL_SOKOBAN_FIELD_EMPTY:
1819 IncrementSokobanFieldsNeeded();
1822 case EL_SOKOBAN_OBJECT:
1823 IncrementSokobanObjectsNeeded();
1827 if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID)
1828 Feld[x][y] = EL_ACID_POOL_TOPLEFT;
1829 else if (x > 0 && Feld[x-1][y] == EL_ACID)
1830 Feld[x][y] = EL_ACID_POOL_TOPRIGHT;
1831 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPLEFT)
1832 Feld[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1833 else if (y > 0 && Feld[x][y-1] == EL_ACID)
1834 Feld[x][y] = EL_ACID_POOL_BOTTOM;
1835 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1836 Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1845 case EL_SPACESHIP_RIGHT:
1846 case EL_SPACESHIP_UP:
1847 case EL_SPACESHIP_LEFT:
1848 case EL_SPACESHIP_DOWN:
1849 case EL_BD_BUTTERFLY:
1850 case EL_BD_BUTTERFLY_RIGHT:
1851 case EL_BD_BUTTERFLY_UP:
1852 case EL_BD_BUTTERFLY_LEFT:
1853 case EL_BD_BUTTERFLY_DOWN:
1855 case EL_BD_FIREFLY_RIGHT:
1856 case EL_BD_FIREFLY_UP:
1857 case EL_BD_FIREFLY_LEFT:
1858 case EL_BD_FIREFLY_DOWN:
1859 case EL_PACMAN_RIGHT:
1861 case EL_PACMAN_LEFT:
1862 case EL_PACMAN_DOWN:
1864 case EL_YAMYAM_LEFT:
1865 case EL_YAMYAM_RIGHT:
1867 case EL_YAMYAM_DOWN:
1868 case EL_DARK_YAMYAM:
1871 case EL_SP_SNIKSNAK:
1872 case EL_SP_ELECTRON:
1881 case EL_AMOEBA_FULL:
1886 case EL_AMOEBA_DROP:
1887 if (y == lev_fieldy - 1)
1889 Feld[x][y] = EL_AMOEBA_GROWING;
1890 Store[x][y] = EL_AMOEBA_WET;
1894 case EL_DYNAMITE_ACTIVE:
1895 case EL_SP_DISK_RED_ACTIVE:
1896 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1897 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1898 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1899 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1900 MovDelay[x][y] = 96;
1903 case EL_EM_DYNAMITE_ACTIVE:
1904 MovDelay[x][y] = 32;
1908 game.lights_still_needed++;
1912 game.friends_still_needed++;
1917 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1920 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1921 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1922 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1923 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1924 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1925 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1926 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1927 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1928 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1929 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1930 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1931 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1934 int belt_nr = getBeltNrFromBeltSwitchElement(Feld[x][y]);
1935 int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]);
1936 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[x][y]);
1938 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1940 game.belt_dir[belt_nr] = belt_dir;
1941 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1943 else // more than one switch -- set it like the first switch
1945 Feld[x][y] = Feld[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1950 case EL_LIGHT_SWITCH_ACTIVE:
1952 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1955 case EL_INVISIBLE_STEELWALL:
1956 case EL_INVISIBLE_WALL:
1957 case EL_INVISIBLE_SAND:
1958 if (game.light_time_left > 0 ||
1959 game.lenses_time_left > 0)
1960 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
1963 case EL_EMC_MAGIC_BALL:
1964 if (game.ball_state)
1965 Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1968 case EL_EMC_MAGIC_BALL_SWITCH:
1969 if (game.ball_state)
1970 Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1973 case EL_TRIGGER_PLAYER:
1974 case EL_TRIGGER_ELEMENT:
1975 case EL_TRIGGER_CE_VALUE:
1976 case EL_TRIGGER_CE_SCORE:
1978 case EL_ANY_ELEMENT:
1979 case EL_CURRENT_CE_VALUE:
1980 case EL_CURRENT_CE_SCORE:
1997 // reference elements should not be used on the playfield
1998 Feld[x][y] = EL_EMPTY;
2002 if (IS_CUSTOM_ELEMENT(element))
2004 if (CAN_MOVE(element))
2007 if (!element_info[element].use_last_ce_value || init_game)
2008 CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
2010 else if (IS_GROUP_ELEMENT(element))
2012 Feld[x][y] = GetElementFromGroupElement(element);
2014 InitField(x, y, init_game);
2021 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2024 static void InitField_WithBug1(int x, int y, boolean init_game)
2026 InitField(x, y, init_game);
2028 // not needed to call InitMovDir() -- already done by InitField()!
2029 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2030 CAN_MOVE(Feld[x][y]))
2034 static void InitField_WithBug2(int x, int y, boolean init_game)
2036 int old_element = Feld[x][y];
2038 InitField(x, y, init_game);
2040 // not needed to call InitMovDir() -- already done by InitField()!
2041 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2042 CAN_MOVE(old_element) &&
2043 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2046 /* this case is in fact a combination of not less than three bugs:
2047 first, it calls InitMovDir() for elements that can move, although this is
2048 already done by InitField(); then, it checks the element that was at this
2049 field _before_ the call to InitField() (which can change it); lastly, it
2050 was not called for "mole with direction" elements, which were treated as
2051 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2055 static int get_key_element_from_nr(int key_nr)
2057 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2058 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2059 EL_EM_KEY_1 : EL_KEY_1);
2061 return key_base_element + key_nr;
2064 static int get_next_dropped_element(struct PlayerInfo *player)
2066 return (player->inventory_size > 0 ?
2067 player->inventory_element[player->inventory_size - 1] :
2068 player->inventory_infinite_element != EL_UNDEFINED ?
2069 player->inventory_infinite_element :
2070 player->dynabombs_left > 0 ?
2071 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2075 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2077 // pos >= 0: get element from bottom of the stack;
2078 // pos < 0: get element from top of the stack
2082 int min_inventory_size = -pos;
2083 int inventory_pos = player->inventory_size - min_inventory_size;
2084 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2086 return (player->inventory_size >= min_inventory_size ?
2087 player->inventory_element[inventory_pos] :
2088 player->inventory_infinite_element != EL_UNDEFINED ?
2089 player->inventory_infinite_element :
2090 player->dynabombs_left >= min_dynabombs_left ?
2091 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2096 int min_dynabombs_left = pos + 1;
2097 int min_inventory_size = pos + 1 - player->dynabombs_left;
2098 int inventory_pos = pos - player->dynabombs_left;
2100 return (player->inventory_infinite_element != EL_UNDEFINED ?
2101 player->inventory_infinite_element :
2102 player->dynabombs_left >= min_dynabombs_left ?
2103 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2104 player->inventory_size >= min_inventory_size ?
2105 player->inventory_element[inventory_pos] :
2110 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2112 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2113 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2116 if (gpo1->sort_priority != gpo2->sort_priority)
2117 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2119 compare_result = gpo1->nr - gpo2->nr;
2121 return compare_result;
2124 int getPlayerInventorySize(int player_nr)
2126 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2127 return level.native_em_level->ply[player_nr]->dynamite;
2128 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2129 return level.native_sp_level->game_sp->red_disk_count;
2131 return stored_player[player_nr].inventory_size;
2134 static void InitGameControlValues(void)
2138 for (i = 0; game_panel_controls[i].nr != -1; i++)
2140 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2141 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2142 struct TextPosInfo *pos = gpc->pos;
2144 int type = gpc->type;
2148 Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
2149 Error(ERR_EXIT, "this should not happen -- please debug");
2152 // force update of game controls after initialization
2153 gpc->value = gpc->last_value = -1;
2154 gpc->frame = gpc->last_frame = -1;
2155 gpc->gfx_frame = -1;
2157 // determine panel value width for later calculation of alignment
2158 if (type == TYPE_INTEGER || type == TYPE_STRING)
2160 pos->width = pos->size * getFontWidth(pos->font);
2161 pos->height = getFontHeight(pos->font);
2163 else if (type == TYPE_ELEMENT)
2165 pos->width = pos->size;
2166 pos->height = pos->size;
2169 // fill structure for game panel draw order
2171 gpo->sort_priority = pos->sort_priority;
2174 // sort game panel controls according to sort_priority and control number
2175 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2176 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2179 static void UpdatePlayfieldElementCount(void)
2181 boolean use_element_count = FALSE;
2184 // first check if it is needed at all to calculate playfield element count
2185 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2186 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2187 use_element_count = TRUE;
2189 if (!use_element_count)
2192 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2193 element_info[i].element_count = 0;
2195 SCAN_PLAYFIELD(x, y)
2197 element_info[Feld[x][y]].element_count++;
2200 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2201 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2202 if (IS_IN_GROUP(j, i))
2203 element_info[EL_GROUP_START + i].element_count +=
2204 element_info[j].element_count;
2207 static void UpdateGameControlValues(void)
2210 int time = (game.LevelSolved ?
2211 game.LevelSolved_CountingTime :
2212 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2213 level.native_em_level->lev->time :
2214 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2215 level.native_sp_level->game_sp->time_played :
2216 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2217 game_mm.energy_left :
2218 game.no_time_limit ? TimePlayed : TimeLeft);
2219 int score = (game.LevelSolved ?
2220 game.LevelSolved_CountingScore :
2221 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2222 level.native_em_level->lev->score :
2223 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2224 level.native_sp_level->game_sp->score :
2225 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2228 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2229 level.native_em_level->lev->required :
2230 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2231 level.native_sp_level->game_sp->infotrons_still_needed :
2232 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2233 game_mm.kettles_still_needed :
2234 game.gems_still_needed);
2235 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2236 level.native_em_level->lev->required > 0 :
2237 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2238 level.native_sp_level->game_sp->infotrons_still_needed > 0 :
2239 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2240 game_mm.kettles_still_needed > 0 ||
2241 game_mm.lights_still_needed > 0 :
2242 game.gems_still_needed > 0 ||
2243 game.sokoban_fields_still_needed > 0 ||
2244 game.sokoban_objects_still_needed > 0 ||
2245 game.lights_still_needed > 0);
2246 int health = (game.LevelSolved ?
2247 game.LevelSolved_CountingHealth :
2248 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2249 MM_HEALTH(game_mm.laser_overload_value) :
2252 UpdatePlayfieldElementCount();
2254 // update game panel control values
2256 // used instead of "level_nr" (for network games)
2257 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2258 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2260 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2261 for (i = 0; i < MAX_NUM_KEYS; i++)
2262 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2263 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2264 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2266 if (game.centered_player_nr == -1)
2268 for (i = 0; i < MAX_PLAYERS; i++)
2270 // only one player in Supaplex game engine
2271 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2274 for (k = 0; k < MAX_NUM_KEYS; k++)
2276 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2278 if (level.native_em_level->ply[i]->keys & (1 << k))
2279 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2280 get_key_element_from_nr(k);
2282 else if (stored_player[i].key[k])
2283 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2284 get_key_element_from_nr(k);
2287 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2288 getPlayerInventorySize(i);
2290 if (stored_player[i].num_white_keys > 0)
2291 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2294 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2295 stored_player[i].num_white_keys;
2300 int player_nr = game.centered_player_nr;
2302 for (k = 0; k < MAX_NUM_KEYS; k++)
2304 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2306 if (level.native_em_level->ply[player_nr]->keys & (1 << k))
2307 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2308 get_key_element_from_nr(k);
2310 else if (stored_player[player_nr].key[k])
2311 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2312 get_key_element_from_nr(k);
2315 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2316 getPlayerInventorySize(player_nr);
2318 if (stored_player[player_nr].num_white_keys > 0)
2319 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2321 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2322 stored_player[player_nr].num_white_keys;
2325 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2327 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2328 get_inventory_element_from_pos(local_player, i);
2329 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2330 get_inventory_element_from_pos(local_player, -i - 1);
2333 game_panel_controls[GAME_PANEL_SCORE].value = score;
2334 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2336 game_panel_controls[GAME_PANEL_TIME].value = time;
2338 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2339 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2340 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2342 if (level.time == 0)
2343 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2345 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2347 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2348 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2350 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2352 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2353 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2355 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2356 local_player->shield_normal_time_left;
2357 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2358 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2360 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2361 local_player->shield_deadly_time_left;
2363 game_panel_controls[GAME_PANEL_EXIT].value =
2364 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2366 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2367 (game.ball_state ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2368 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2369 (game.ball_state ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2370 EL_EMC_MAGIC_BALL_SWITCH);
2372 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2373 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2374 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2375 game.light_time_left;
2377 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2378 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2379 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2380 game.timegate_time_left;
2382 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2383 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2385 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2386 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2387 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2388 game.lenses_time_left;
2390 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2391 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2392 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2393 game.magnify_time_left;
2395 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2396 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2397 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2398 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2399 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2400 EL_BALLOON_SWITCH_NONE);
2402 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2403 local_player->dynabomb_count;
2404 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2405 local_player->dynabomb_size;
2406 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2407 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2409 game_panel_controls[GAME_PANEL_PENGUINS].value =
2410 game.friends_still_needed;
2412 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2413 game.sokoban_objects_still_needed;
2414 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2415 game.sokoban_fields_still_needed;
2417 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2418 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2420 for (i = 0; i < NUM_BELTS; i++)
2422 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2423 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2424 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2425 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2426 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2429 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2430 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2431 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2432 game.magic_wall_time_left;
2434 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2435 local_player->gravity;
2437 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2438 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2440 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2441 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2442 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2443 game.panel.element[i].id : EL_UNDEFINED);
2445 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2446 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2447 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2448 element_info[game.panel.element_count[i].id].element_count : 0);
2450 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2451 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2452 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2453 element_info[game.panel.ce_score[i].id].collect_score : 0);
2455 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2456 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2457 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2458 element_info[game.panel.ce_score_element[i].id].collect_score :
2461 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2462 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2463 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2465 // update game panel control frames
2467 for (i = 0; game_panel_controls[i].nr != -1; i++)
2469 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2471 if (gpc->type == TYPE_ELEMENT)
2473 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2475 int last_anim_random_frame = gfx.anim_random_frame;
2476 int element = gpc->value;
2477 int graphic = el2panelimg(element);
2479 if (gpc->value != gpc->last_value)
2482 gpc->gfx_random = INIT_GFX_RANDOM();
2488 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2489 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2490 gpc->gfx_random = INIT_GFX_RANDOM();
2493 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2494 gfx.anim_random_frame = gpc->gfx_random;
2496 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2497 gpc->gfx_frame = element_info[element].collect_score;
2499 gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
2502 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2503 gfx.anim_random_frame = last_anim_random_frame;
2506 else if (gpc->type == TYPE_GRAPHIC)
2508 if (gpc->graphic != IMG_UNDEFINED)
2510 int last_anim_random_frame = gfx.anim_random_frame;
2511 int graphic = gpc->graphic;
2513 if (gpc->value != gpc->last_value)
2516 gpc->gfx_random = INIT_GFX_RANDOM();
2522 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2523 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2524 gpc->gfx_random = INIT_GFX_RANDOM();
2527 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2528 gfx.anim_random_frame = gpc->gfx_random;
2530 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2532 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2533 gfx.anim_random_frame = last_anim_random_frame;
2539 static void DisplayGameControlValues(void)
2541 boolean redraw_panel = FALSE;
2544 for (i = 0; game_panel_controls[i].nr != -1; i++)
2546 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2548 if (PANEL_DEACTIVATED(gpc->pos))
2551 if (gpc->value == gpc->last_value &&
2552 gpc->frame == gpc->last_frame)
2555 redraw_panel = TRUE;
2561 // copy default game door content to main double buffer
2563 // !!! CHECK AGAIN !!!
2564 SetPanelBackground();
2565 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2566 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2568 // redraw game control buttons
2569 RedrawGameButtons();
2571 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2573 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2575 int nr = game_panel_order[i].nr;
2576 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2577 struct TextPosInfo *pos = gpc->pos;
2578 int type = gpc->type;
2579 int value = gpc->value;
2580 int frame = gpc->frame;
2581 int size = pos->size;
2582 int font = pos->font;
2583 boolean draw_masked = pos->draw_masked;
2584 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2586 if (PANEL_DEACTIVATED(pos))
2589 gpc->last_value = value;
2590 gpc->last_frame = frame;
2592 if (type == TYPE_INTEGER)
2594 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2595 nr == GAME_PANEL_TIME)
2597 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2599 if (use_dynamic_size) // use dynamic number of digits
2601 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2602 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2603 int size2 = size1 + 1;
2604 int font1 = pos->font;
2605 int font2 = pos->font_alt;
2607 size = (value < value_change ? size1 : size2);
2608 font = (value < value_change ? font1 : font2);
2612 // correct text size if "digits" is zero or less
2614 size = strlen(int2str(value, size));
2616 // dynamically correct text alignment
2617 pos->width = size * getFontWidth(font);
2619 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2620 int2str(value, size), font, mask_mode);
2622 else if (type == TYPE_ELEMENT)
2624 int element, graphic;
2628 int dst_x = PANEL_XPOS(pos);
2629 int dst_y = PANEL_YPOS(pos);
2631 if (value != EL_UNDEFINED && value != EL_EMPTY)
2634 graphic = el2panelimg(value);
2636 // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
2638 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2641 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2644 width = graphic_info[graphic].width * size / TILESIZE;
2645 height = graphic_info[graphic].height * size / TILESIZE;
2648 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2651 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2655 else if (type == TYPE_GRAPHIC)
2657 int graphic = gpc->graphic;
2658 int graphic_active = gpc->graphic_active;
2662 int dst_x = PANEL_XPOS(pos);
2663 int dst_y = PANEL_YPOS(pos);
2664 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2665 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2667 if (graphic != IMG_UNDEFINED && !skip)
2669 if (pos->style == STYLE_REVERSE)
2670 value = 100 - value;
2672 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2674 if (pos->direction & MV_HORIZONTAL)
2676 width = graphic_info[graphic_active].width * value / 100;
2677 height = graphic_info[graphic_active].height;
2679 if (pos->direction == MV_LEFT)
2681 src_x += graphic_info[graphic_active].width - width;
2682 dst_x += graphic_info[graphic_active].width - width;
2687 width = graphic_info[graphic_active].width;
2688 height = graphic_info[graphic_active].height * value / 100;
2690 if (pos->direction == MV_UP)
2692 src_y += graphic_info[graphic_active].height - height;
2693 dst_y += graphic_info[graphic_active].height - height;
2698 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2701 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2704 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2706 if (pos->direction & MV_HORIZONTAL)
2708 if (pos->direction == MV_RIGHT)
2715 dst_x = PANEL_XPOS(pos);
2718 width = graphic_info[graphic].width - width;
2722 if (pos->direction == MV_DOWN)
2729 dst_y = PANEL_YPOS(pos);
2732 height = graphic_info[graphic].height - height;
2736 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2739 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2743 else if (type == TYPE_STRING)
2745 boolean active = (value != 0);
2746 char *state_normal = "off";
2747 char *state_active = "on";
2748 char *state = (active ? state_active : state_normal);
2749 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2750 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2751 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2752 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2754 if (nr == GAME_PANEL_GRAVITY_STATE)
2756 int font1 = pos->font; // (used for normal state)
2757 int font2 = pos->font_alt; // (used for active state)
2759 font = (active ? font2 : font1);
2768 // don't truncate output if "chars" is zero or less
2771 // dynamically correct text alignment
2772 pos->width = size * getFontWidth(font);
2775 s_cut = getStringCopyN(s, size);
2777 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2778 s_cut, font, mask_mode);
2784 redraw_mask |= REDRAW_DOOR_1;
2787 SetGameStatus(GAME_MODE_PLAYING);
2790 void UpdateAndDisplayGameControlValues(void)
2792 if (tape.deactivate_display)
2795 UpdateGameControlValues();
2796 DisplayGameControlValues();
2800 static void UpdateGameDoorValues(void)
2802 UpdateGameControlValues();
2806 void DrawGameDoorValues(void)
2808 DisplayGameControlValues();
2812 // ============================================================================
2814 // ----------------------------------------------------------------------------
2815 // initialize game engine due to level / tape version number
2816 // ============================================================================
2818 static void InitGameEngine(void)
2820 int i, j, k, l, x, y;
2822 // set game engine from tape file when re-playing, else from level file
2823 game.engine_version = (tape.playing ? tape.engine_version :
2824 level.game_version);
2826 // set single or multi-player game mode (needed for re-playing tapes)
2827 game.team_mode = setup.team_mode;
2831 int num_players = 0;
2833 for (i = 0; i < MAX_PLAYERS; i++)
2834 if (tape.player_participates[i])
2837 // multi-player tapes contain input data for more than one player
2838 game.team_mode = (num_players > 1);
2841 // --------------------------------------------------------------------------
2842 // set flags for bugs and changes according to active game engine version
2843 // --------------------------------------------------------------------------
2846 Summary of bugfix/change:
2847 Fixed handling for custom elements that change when pushed by the player.
2849 Fixed/changed in version:
2853 Before 3.1.0, custom elements that "change when pushing" changed directly
2854 after the player started pushing them (until then handled in "DigField()").
2855 Since 3.1.0, these custom elements are not changed until the "pushing"
2856 move of the element is finished (now handled in "ContinueMoving()").
2858 Affected levels/tapes:
2859 The first condition is generally needed for all levels/tapes before version
2860 3.1.0, which might use the old behaviour before it was changed; known tapes
2861 that are affected are some tapes from the level set "Walpurgis Gardens" by
2863 The second condition is an exception from the above case and is needed for
2864 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2865 above (including some development versions of 3.1.0), but before it was
2866 known that this change would break tapes like the above and was fixed in
2867 3.1.1, so that the changed behaviour was active although the engine version
2868 while recording maybe was before 3.1.0. There is at least one tape that is
2869 affected by this exception, which is the tape for the one-level set "Bug
2870 Machine" by Juergen Bonhagen.
2873 game.use_change_when_pushing_bug =
2874 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2876 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2877 tape.game_version < VERSION_IDENT(3,1,1,0)));
2880 Summary of bugfix/change:
2881 Fixed handling for blocking the field the player leaves when moving.
2883 Fixed/changed in version:
2887 Before 3.1.1, when "block last field when moving" was enabled, the field
2888 the player is leaving when moving was blocked for the time of the move,
2889 and was directly unblocked afterwards. This resulted in the last field
2890 being blocked for exactly one less than the number of frames of one player
2891 move. Additionally, even when blocking was disabled, the last field was
2892 blocked for exactly one frame.
2893 Since 3.1.1, due to changes in player movement handling, the last field
2894 is not blocked at all when blocking is disabled. When blocking is enabled,
2895 the last field is blocked for exactly the number of frames of one player
2896 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2897 last field is blocked for exactly one more than the number of frames of
2900 Affected levels/tapes:
2901 (!!! yet to be determined -- probably many !!!)
2904 game.use_block_last_field_bug =
2905 (game.engine_version < VERSION_IDENT(3,1,1,0));
2907 game_em.use_single_button =
2908 (game.engine_version > VERSION_IDENT(4,0,0,2));
2910 game_em.use_snap_key_bug =
2911 (game.engine_version < VERSION_IDENT(4,0,1,0));
2913 // --------------------------------------------------------------------------
2915 // set maximal allowed number of custom element changes per game frame
2916 game.max_num_changes_per_frame = 1;
2918 // default scan direction: scan playfield from top/left to bottom/right
2919 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
2921 // dynamically adjust element properties according to game engine version
2922 InitElementPropertiesEngine(game.engine_version);
2925 printf("level %d: level version == %06d\n", level_nr, level.game_version);
2926 printf(" tape version == %06d [%s] [file: %06d]\n",
2927 tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
2929 printf(" => game.engine_version == %06d\n", game.engine_version);
2932 // ---------- initialize player's initial move delay ------------------------
2934 // dynamically adjust player properties according to level information
2935 for (i = 0; i < MAX_PLAYERS; i++)
2936 game.initial_move_delay_value[i] =
2937 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
2939 // dynamically adjust player properties according to game engine version
2940 for (i = 0; i < MAX_PLAYERS; i++)
2941 game.initial_move_delay[i] =
2942 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
2943 game.initial_move_delay_value[i] : 0);
2945 // ---------- initialize player's initial push delay ------------------------
2947 // dynamically adjust player properties according to game engine version
2948 game.initial_push_delay_value =
2949 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
2951 // ---------- initialize changing elements ----------------------------------
2953 // initialize changing elements information
2954 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2956 struct ElementInfo *ei = &element_info[i];
2958 // this pointer might have been changed in the level editor
2959 ei->change = &ei->change_page[0];
2961 if (!IS_CUSTOM_ELEMENT(i))
2963 ei->change->target_element = EL_EMPTY_SPACE;
2964 ei->change->delay_fixed = 0;
2965 ei->change->delay_random = 0;
2966 ei->change->delay_frames = 1;
2969 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
2971 ei->has_change_event[j] = FALSE;
2973 ei->event_page_nr[j] = 0;
2974 ei->event_page[j] = &ei->change_page[0];
2978 // add changing elements from pre-defined list
2979 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
2981 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
2982 struct ElementInfo *ei = &element_info[ch_delay->element];
2984 ei->change->target_element = ch_delay->target_element;
2985 ei->change->delay_fixed = ch_delay->change_delay;
2987 ei->change->pre_change_function = ch_delay->pre_change_function;
2988 ei->change->change_function = ch_delay->change_function;
2989 ei->change->post_change_function = ch_delay->post_change_function;
2991 ei->change->can_change = TRUE;
2992 ei->change->can_change_or_has_action = TRUE;
2994 ei->has_change_event[CE_DELAY] = TRUE;
2996 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
2997 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3000 // ---------- initialize internal run-time variables ------------------------
3002 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3004 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3006 for (j = 0; j < ei->num_change_pages; j++)
3008 ei->change_page[j].can_change_or_has_action =
3009 (ei->change_page[j].can_change |
3010 ei->change_page[j].has_action);
3014 // add change events from custom element configuration
3015 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3017 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3019 for (j = 0; j < ei->num_change_pages; j++)
3021 if (!ei->change_page[j].can_change_or_has_action)
3024 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3026 // only add event page for the first page found with this event
3027 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3029 ei->has_change_event[k] = TRUE;
3031 ei->event_page_nr[k] = j;
3032 ei->event_page[k] = &ei->change_page[j];
3038 // ---------- initialize reference elements in change conditions ------------
3040 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3042 int element = EL_CUSTOM_START + i;
3043 struct ElementInfo *ei = &element_info[element];
3045 for (j = 0; j < ei->num_change_pages; j++)
3047 int trigger_element = ei->change_page[j].initial_trigger_element;
3049 if (trigger_element >= EL_PREV_CE_8 &&
3050 trigger_element <= EL_NEXT_CE_8)
3051 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3053 ei->change_page[j].trigger_element = trigger_element;
3057 // ---------- initialize run-time trigger player and element ----------------
3059 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3061 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3063 for (j = 0; j < ei->num_change_pages; j++)
3065 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3066 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3067 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3068 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3069 ei->change_page[j].actual_trigger_ce_value = 0;
3070 ei->change_page[j].actual_trigger_ce_score = 0;
3074 // ---------- initialize trigger events -------------------------------------
3076 // initialize trigger events information
3077 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3078 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3079 trigger_events[i][j] = FALSE;
3081 // add trigger events from element change event properties
3082 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3084 struct ElementInfo *ei = &element_info[i];
3086 for (j = 0; j < ei->num_change_pages; j++)
3088 if (!ei->change_page[j].can_change_or_has_action)
3091 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3093 int trigger_element = ei->change_page[j].trigger_element;
3095 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3097 if (ei->change_page[j].has_event[k])
3099 if (IS_GROUP_ELEMENT(trigger_element))
3101 struct ElementGroupInfo *group =
3102 element_info[trigger_element].group;
3104 for (l = 0; l < group->num_elements_resolved; l++)
3105 trigger_events[group->element_resolved[l]][k] = TRUE;
3107 else if (trigger_element == EL_ANY_ELEMENT)
3108 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3109 trigger_events[l][k] = TRUE;
3111 trigger_events[trigger_element][k] = TRUE;
3118 // ---------- initialize push delay -----------------------------------------
3120 // initialize push delay values to default
3121 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3123 if (!IS_CUSTOM_ELEMENT(i))
3125 // set default push delay values (corrected since version 3.0.7-1)
3126 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3128 element_info[i].push_delay_fixed = 2;
3129 element_info[i].push_delay_random = 8;
3133 element_info[i].push_delay_fixed = 8;
3134 element_info[i].push_delay_random = 8;
3139 // set push delay value for certain elements from pre-defined list
3140 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3142 int e = push_delay_list[i].element;
3144 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3145 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3148 // set push delay value for Supaplex elements for newer engine versions
3149 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3151 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3153 if (IS_SP_ELEMENT(i))
3155 // set SP push delay to just enough to push under a falling zonk
3156 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3158 element_info[i].push_delay_fixed = delay;
3159 element_info[i].push_delay_random = 0;
3164 // ---------- initialize move stepsize --------------------------------------
3166 // initialize move stepsize values to default
3167 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3168 if (!IS_CUSTOM_ELEMENT(i))
3169 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3171 // set move stepsize value for certain elements from pre-defined list
3172 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3174 int e = move_stepsize_list[i].element;
3176 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3179 // ---------- initialize collect score --------------------------------------
3181 // initialize collect score values for custom elements from initial value
3182 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3183 if (IS_CUSTOM_ELEMENT(i))
3184 element_info[i].collect_score = element_info[i].collect_score_initial;
3186 // ---------- initialize collect count --------------------------------------
3188 // initialize collect count values for non-custom elements
3189 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3190 if (!IS_CUSTOM_ELEMENT(i))
3191 element_info[i].collect_count_initial = 0;
3193 // add collect count values for all elements from pre-defined list
3194 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3195 element_info[collect_count_list[i].element].collect_count_initial =
3196 collect_count_list[i].count;
3198 // ---------- initialize access direction -----------------------------------
3200 // initialize access direction values to default (access from every side)
3201 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3202 if (!IS_CUSTOM_ELEMENT(i))
3203 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3205 // set access direction value for certain elements from pre-defined list
3206 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3207 element_info[access_direction_list[i].element].access_direction =
3208 access_direction_list[i].direction;
3210 // ---------- initialize explosion content ----------------------------------
3211 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3213 if (IS_CUSTOM_ELEMENT(i))
3216 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3218 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3220 element_info[i].content.e[x][y] =
3221 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3222 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3223 i == EL_PLAYER_3 ? EL_EMERALD :
3224 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3225 i == EL_MOLE ? EL_EMERALD_RED :
3226 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3227 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3228 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3229 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3230 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3231 i == EL_WALL_EMERALD ? EL_EMERALD :
3232 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3233 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3234 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3235 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3236 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3237 i == EL_WALL_PEARL ? EL_PEARL :
3238 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3243 // ---------- initialize recursion detection --------------------------------
3244 recursion_loop_depth = 0;
3245 recursion_loop_detected = FALSE;
3246 recursion_loop_element = EL_UNDEFINED;
3248 // ---------- initialize graphics engine ------------------------------------
3249 game.scroll_delay_value =
3250 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3251 setup.scroll_delay ? setup.scroll_delay_value : 0);
3252 game.scroll_delay_value =
3253 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3255 // ---------- initialize game engine snapshots ------------------------------
3256 for (i = 0; i < MAX_PLAYERS; i++)
3257 game.snapshot.last_action[i] = 0;
3258 game.snapshot.changed_action = FALSE;
3259 game.snapshot.collected_item = FALSE;
3260 game.snapshot.mode =
3261 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3262 SNAPSHOT_MODE_EVERY_STEP :
3263 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3264 SNAPSHOT_MODE_EVERY_MOVE :
3265 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3266 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3267 game.snapshot.save_snapshot = FALSE;
3269 // ---------- initialize level time for Supaplex engine ---------------------
3270 // Supaplex levels with time limit currently unsupported -- should be added
3271 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3275 static int get_num_special_action(int element, int action_first,
3278 int num_special_action = 0;
3281 for (i = action_first; i <= action_last; i++)
3283 boolean found = FALSE;
3285 for (j = 0; j < NUM_DIRECTIONS; j++)
3286 if (el_act_dir2img(element, i, j) !=
3287 el_act_dir2img(element, ACTION_DEFAULT, j))
3291 num_special_action++;
3296 return num_special_action;
3300 // ============================================================================
3302 // ----------------------------------------------------------------------------
3303 // initialize and start new game
3304 // ============================================================================
3306 #if DEBUG_INIT_PLAYER
3307 static void DebugPrintPlayerStatus(char *message)
3314 printf("%s:\n", message);
3316 for (i = 0; i < MAX_PLAYERS; i++)
3318 struct PlayerInfo *player = &stored_player[i];
3320 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3324 player->connected_locally,
3325 player->connected_network,
3328 if (local_player == player)
3329 printf(" (local player)");
3338 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3339 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3340 int fade_mask = REDRAW_FIELD;
3342 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3343 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3344 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3345 int initial_move_dir = MV_DOWN;
3348 // required here to update video display before fading (FIX THIS)
3349 DrawMaskedBorder(REDRAW_DOOR_2);
3351 if (!game.restart_level)
3352 CloseDoor(DOOR_CLOSE_1);
3354 SetGameStatus(GAME_MODE_PLAYING);
3356 if (level_editor_test_game)
3357 FadeSkipNextFadeIn();
3359 FadeSetEnterScreen();
3361 if (CheckIfGlobalBorderOrPlayfieldViewportHasChanged())
3362 fade_mask = REDRAW_ALL;
3364 FadeLevelSoundsAndMusic();
3366 ExpireSoundLoops(TRUE);
3370 // needed if different viewport properties defined for playing
3371 ChangeViewportPropertiesIfNeeded();
3375 DrawCompleteVideoDisplay();
3377 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3380 InitGameControlValues();
3382 // don't play tapes over network
3383 network_playing = (network.enabled && !tape.playing);
3385 for (i = 0; i < MAX_PLAYERS; i++)
3387 struct PlayerInfo *player = &stored_player[i];
3389 player->index_nr = i;
3390 player->index_bit = (1 << i);
3391 player->element_nr = EL_PLAYER_1 + i;
3393 player->present = FALSE;
3394 player->active = FALSE;
3395 player->mapped = FALSE;
3397 player->killed = FALSE;
3398 player->reanimated = FALSE;
3399 player->buried = FALSE;
3402 player->effective_action = 0;
3403 player->programmed_action = 0;
3405 player->mouse_action.lx = 0;
3406 player->mouse_action.ly = 0;
3407 player->mouse_action.button = 0;
3408 player->mouse_action.button_hint = 0;
3410 player->effective_mouse_action.lx = 0;
3411 player->effective_mouse_action.ly = 0;
3412 player->effective_mouse_action.button = 0;
3413 player->effective_mouse_action.button_hint = 0;
3415 for (j = 0; j < MAX_NUM_KEYS; j++)
3416 player->key[j] = FALSE;
3418 player->num_white_keys = 0;
3420 player->dynabomb_count = 0;
3421 player->dynabomb_size = 1;
3422 player->dynabombs_left = 0;
3423 player->dynabomb_xl = FALSE;
3425 player->MovDir = initial_move_dir;
3428 player->GfxDir = initial_move_dir;
3429 player->GfxAction = ACTION_DEFAULT;
3431 player->StepFrame = 0;
3433 player->initial_element = player->element_nr;
3434 player->artwork_element =
3435 (level.use_artwork_element[i] ? level.artwork_element[i] :
3436 player->element_nr);
3437 player->use_murphy = FALSE;
3439 player->block_last_field = FALSE; // initialized in InitPlayerField()
3440 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3442 player->gravity = level.initial_player_gravity[i];
3444 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3446 player->actual_frame_counter = 0;
3448 player->step_counter = 0;
3450 player->last_move_dir = initial_move_dir;
3452 player->is_active = FALSE;
3454 player->is_waiting = FALSE;
3455 player->is_moving = FALSE;
3456 player->is_auto_moving = FALSE;
3457 player->is_digging = FALSE;
3458 player->is_snapping = FALSE;
3459 player->is_collecting = FALSE;
3460 player->is_pushing = FALSE;
3461 player->is_switching = FALSE;
3462 player->is_dropping = FALSE;
3463 player->is_dropping_pressed = FALSE;
3465 player->is_bored = FALSE;
3466 player->is_sleeping = FALSE;
3468 player->was_waiting = TRUE;
3469 player->was_moving = FALSE;
3470 player->was_snapping = FALSE;
3471 player->was_dropping = FALSE;
3473 player->force_dropping = FALSE;
3475 player->frame_counter_bored = -1;
3476 player->frame_counter_sleeping = -1;
3478 player->anim_delay_counter = 0;
3479 player->post_delay_counter = 0;
3481 player->dir_waiting = initial_move_dir;
3482 player->action_waiting = ACTION_DEFAULT;
3483 player->last_action_waiting = ACTION_DEFAULT;
3484 player->special_action_bored = ACTION_DEFAULT;
3485 player->special_action_sleeping = ACTION_DEFAULT;
3487 player->switch_x = -1;
3488 player->switch_y = -1;
3490 player->drop_x = -1;
3491 player->drop_y = -1;
3493 player->show_envelope = 0;
3495 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3497 player->push_delay = -1; // initialized when pushing starts
3498 player->push_delay_value = game.initial_push_delay_value;
3500 player->drop_delay = 0;
3501 player->drop_pressed_delay = 0;
3503 player->last_jx = -1;
3504 player->last_jy = -1;
3508 player->shield_normal_time_left = 0;
3509 player->shield_deadly_time_left = 0;
3511 player->inventory_infinite_element = EL_UNDEFINED;
3512 player->inventory_size = 0;
3514 if (level.use_initial_inventory[i])
3516 for (j = 0; j < level.initial_inventory_size[i]; j++)
3518 int element = level.initial_inventory_content[i][j];
3519 int collect_count = element_info[element].collect_count_initial;
3522 if (!IS_CUSTOM_ELEMENT(element))
3525 if (collect_count == 0)
3526 player->inventory_infinite_element = element;
3528 for (k = 0; k < collect_count; k++)
3529 if (player->inventory_size < MAX_INVENTORY_SIZE)
3530 player->inventory_element[player->inventory_size++] = element;
3534 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3535 SnapField(player, 0, 0);
3537 map_player_action[i] = i;
3540 network_player_action_received = FALSE;
3542 // initial null action
3543 if (network_playing)
3544 SendToServer_MovePlayer(MV_NONE);
3551 TimeLeft = level.time;
3554 ScreenMovDir = MV_NONE;
3558 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3563 game.all_players_gone = FALSE;
3565 game.LevelSolved = FALSE;
3566 game.GameOver = FALSE;
3568 game.LevelSolved_GameWon = FALSE;
3569 game.LevelSolved_GameEnd = FALSE;
3570 game.LevelSolved_SaveTape = FALSE;
3571 game.LevelSolved_SaveScore = FALSE;
3573 game.LevelSolved_CountingTime = 0;
3574 game.LevelSolved_CountingScore = 0;
3575 game.LevelSolved_CountingHealth = 0;
3577 game.panel.active = TRUE;
3579 game.no_time_limit = (level.time == 0);
3581 game.yamyam_content_nr = 0;
3582 game.robot_wheel_active = FALSE;
3583 game.magic_wall_active = FALSE;
3584 game.magic_wall_time_left = 0;
3585 game.light_time_left = 0;
3586 game.timegate_time_left = 0;
3587 game.switchgate_pos = 0;
3588 game.wind_direction = level.wind_direction_initial;
3591 game.score_final = 0;
3593 game.health = MAX_HEALTH;
3594 game.health_final = MAX_HEALTH;
3596 game.gems_still_needed = level.gems_needed;
3597 game.sokoban_fields_still_needed = 0;
3598 game.sokoban_objects_still_needed = 0;
3599 game.lights_still_needed = 0;
3600 game.players_still_needed = 0;
3601 game.friends_still_needed = 0;
3603 game.lenses_time_left = 0;
3604 game.magnify_time_left = 0;
3606 game.ball_state = level.ball_state_initial;
3607 game.ball_content_nr = 0;
3609 game.explosions_delayed = TRUE;
3611 game.envelope_active = FALSE;
3613 for (i = 0; i < NUM_BELTS; i++)
3615 game.belt_dir[i] = MV_NONE;
3616 game.belt_dir_nr[i] = 3; // not moving, next moving left
3619 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3620 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3622 #if DEBUG_INIT_PLAYER
3623 DebugPrintPlayerStatus("Player status at level initialization");
3626 SCAN_PLAYFIELD(x, y)
3628 Feld[x][y] = Last[x][y] = level.field[x][y];
3629 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3630 ChangeDelay[x][y] = 0;
3631 ChangePage[x][y] = -1;
3632 CustomValue[x][y] = 0; // initialized in InitField()
3633 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3635 WasJustMoving[x][y] = 0;
3636 WasJustFalling[x][y] = 0;
3637 CheckCollision[x][y] = 0;
3638 CheckImpact[x][y] = 0;
3640 Pushed[x][y] = FALSE;
3642 ChangeCount[x][y] = 0;
3643 ChangeEvent[x][y] = -1;
3645 ExplodePhase[x][y] = 0;
3646 ExplodeDelay[x][y] = 0;
3647 ExplodeField[x][y] = EX_TYPE_NONE;
3649 RunnerVisit[x][y] = 0;
3650 PlayerVisit[x][y] = 0;
3653 GfxRandom[x][y] = INIT_GFX_RANDOM();
3654 GfxElement[x][y] = EL_UNDEFINED;
3655 GfxAction[x][y] = ACTION_DEFAULT;
3656 GfxDir[x][y] = MV_NONE;
3657 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3660 SCAN_PLAYFIELD(x, y)
3662 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3664 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3666 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3669 InitField(x, y, TRUE);
3671 ResetGfxAnimation(x, y);
3676 for (i = 0; i < MAX_PLAYERS; i++)
3678 struct PlayerInfo *player = &stored_player[i];
3680 // set number of special actions for bored and sleeping animation
3681 player->num_special_action_bored =
3682 get_num_special_action(player->artwork_element,
3683 ACTION_BORING_1, ACTION_BORING_LAST);
3684 player->num_special_action_sleeping =
3685 get_num_special_action(player->artwork_element,
3686 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3689 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3690 emulate_sb ? EMU_SOKOBAN :
3691 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3693 // initialize type of slippery elements
3694 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3696 if (!IS_CUSTOM_ELEMENT(i))
3698 // default: elements slip down either to the left or right randomly
3699 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3701 // SP style elements prefer to slip down on the left side
3702 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3703 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3705 // BD style elements prefer to slip down on the left side
3706 if (game.emulation == EMU_BOULDERDASH)
3707 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3711 // initialize explosion and ignition delay
3712 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3714 if (!IS_CUSTOM_ELEMENT(i))
3717 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3718 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3719 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3720 int last_phase = (num_phase + 1) * delay;
3721 int half_phase = (num_phase / 2) * delay;
3723 element_info[i].explosion_delay = last_phase - 1;
3724 element_info[i].ignition_delay = half_phase;
3726 if (i == EL_BLACK_ORB)
3727 element_info[i].ignition_delay = 1;
3731 // correct non-moving belts to start moving left
3732 for (i = 0; i < NUM_BELTS; i++)
3733 if (game.belt_dir[i] == MV_NONE)
3734 game.belt_dir_nr[i] = 3; // not moving, next moving left
3736 #if USE_NEW_PLAYER_ASSIGNMENTS
3737 for (i = 0; i < MAX_PLAYERS; i++)
3739 stored_player[i].connected = FALSE;
3741 // in network game mode, the local player might not be the first player
3742 if (stored_player[i].connected_locally)
3743 local_player = &stored_player[i];
3746 if (!network.enabled)
3747 local_player->connected = TRUE;
3751 for (i = 0; i < MAX_PLAYERS; i++)
3752 stored_player[i].connected = tape.player_participates[i];
3754 else if (network.enabled)
3756 // add team mode players connected over the network (needed for correct
3757 // assignment of player figures from level to locally playing players)
3759 for (i = 0; i < MAX_PLAYERS; i++)
3760 if (stored_player[i].connected_network)
3761 stored_player[i].connected = TRUE;
3763 else if (game.team_mode)
3765 // try to guess locally connected team mode players (needed for correct
3766 // assignment of player figures from level to locally playing players)
3768 for (i = 0; i < MAX_PLAYERS; i++)
3769 if (setup.input[i].use_joystick ||
3770 setup.input[i].key.left != KSYM_UNDEFINED)
3771 stored_player[i].connected = TRUE;
3774 #if DEBUG_INIT_PLAYER
3775 DebugPrintPlayerStatus("Player status after level initialization");
3778 #if DEBUG_INIT_PLAYER
3780 printf("Reassigning players ...\n");
3783 // check if any connected player was not found in playfield
3784 for (i = 0; i < MAX_PLAYERS; i++)
3786 struct PlayerInfo *player = &stored_player[i];
3788 if (player->connected && !player->present)
3790 struct PlayerInfo *field_player = NULL;
3792 #if DEBUG_INIT_PLAYER
3794 printf("- looking for field player for player %d ...\n", i + 1);
3797 // assign first free player found that is present in the playfield
3799 // first try: look for unmapped playfield player that is not connected
3800 for (j = 0; j < MAX_PLAYERS; j++)
3801 if (field_player == NULL &&
3802 stored_player[j].present &&
3803 !stored_player[j].mapped &&
3804 !stored_player[j].connected)
3805 field_player = &stored_player[j];
3807 // second try: look for *any* unmapped playfield player
3808 for (j = 0; j < MAX_PLAYERS; j++)
3809 if (field_player == NULL &&
3810 stored_player[j].present &&
3811 !stored_player[j].mapped)
3812 field_player = &stored_player[j];
3814 if (field_player != NULL)
3816 int jx = field_player->jx, jy = field_player->jy;
3818 #if DEBUG_INIT_PLAYER
3820 printf("- found player %d\n", field_player->index_nr + 1);
3823 player->present = FALSE;
3824 player->active = FALSE;
3826 field_player->present = TRUE;
3827 field_player->active = TRUE;
3830 player->initial_element = field_player->initial_element;
3831 player->artwork_element = field_player->artwork_element;
3833 player->block_last_field = field_player->block_last_field;
3834 player->block_delay_adjustment = field_player->block_delay_adjustment;
3837 StorePlayer[jx][jy] = field_player->element_nr;
3839 field_player->jx = field_player->last_jx = jx;
3840 field_player->jy = field_player->last_jy = jy;
3842 if (local_player == player)
3843 local_player = field_player;
3845 map_player_action[field_player->index_nr] = i;
3847 field_player->mapped = TRUE;
3849 #if DEBUG_INIT_PLAYER
3851 printf("- map_player_action[%d] == %d\n",
3852 field_player->index_nr + 1, i + 1);
3857 if (player->connected && player->present)
3858 player->mapped = TRUE;
3861 #if DEBUG_INIT_PLAYER
3862 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
3867 // check if any connected player was not found in playfield
3868 for (i = 0; i < MAX_PLAYERS; i++)
3870 struct PlayerInfo *player = &stored_player[i];
3872 if (player->connected && !player->present)
3874 for (j = 0; j < MAX_PLAYERS; j++)
3876 struct PlayerInfo *field_player = &stored_player[j];
3877 int jx = field_player->jx, jy = field_player->jy;
3879 // assign first free player found that is present in the playfield
3880 if (field_player->present && !field_player->connected)
3882 player->present = TRUE;
3883 player->active = TRUE;
3885 field_player->present = FALSE;
3886 field_player->active = FALSE;
3888 player->initial_element = field_player->initial_element;
3889 player->artwork_element = field_player->artwork_element;
3891 player->block_last_field = field_player->block_last_field;
3892 player->block_delay_adjustment = field_player->block_delay_adjustment;
3894 StorePlayer[jx][jy] = player->element_nr;
3896 player->jx = player->last_jx = jx;
3897 player->jy = player->last_jy = jy;
3907 printf("::: local_player->present == %d\n", local_player->present);
3910 // set focus to local player for network games, else to all players
3911 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
3912 game.centered_player_nr_next = game.centered_player_nr;
3913 game.set_centered_player = FALSE;
3915 if (network_playing && tape.recording)
3917 // store client dependent player focus when recording network games
3918 tape.centered_player_nr_next = game.centered_player_nr_next;
3919 tape.set_centered_player = TRUE;
3924 // when playing a tape, eliminate all players who do not participate
3926 #if USE_NEW_PLAYER_ASSIGNMENTS
3928 if (!game.team_mode)
3930 for (i = 0; i < MAX_PLAYERS; i++)
3932 if (stored_player[i].active &&
3933 !tape.player_participates[map_player_action[i]])
3935 struct PlayerInfo *player = &stored_player[i];
3936 int jx = player->jx, jy = player->jy;
3938 #if DEBUG_INIT_PLAYER
3940 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
3943 player->active = FALSE;
3944 StorePlayer[jx][jy] = 0;
3945 Feld[jx][jy] = EL_EMPTY;
3952 for (i = 0; i < MAX_PLAYERS; i++)
3954 if (stored_player[i].active &&
3955 !tape.player_participates[i])
3957 struct PlayerInfo *player = &stored_player[i];
3958 int jx = player->jx, jy = player->jy;
3960 player->active = FALSE;
3961 StorePlayer[jx][jy] = 0;
3962 Feld[jx][jy] = EL_EMPTY;
3967 else if (!network.enabled && !game.team_mode) // && !tape.playing
3969 // when in single player mode, eliminate all but the local player
3971 for (i = 0; i < MAX_PLAYERS; i++)
3973 struct PlayerInfo *player = &stored_player[i];
3975 if (player->active && player != local_player)
3977 int jx = player->jx, jy = player->jy;
3979 player->active = FALSE;
3980 player->present = FALSE;
3982 StorePlayer[jx][jy] = 0;
3983 Feld[jx][jy] = EL_EMPTY;
3988 for (i = 0; i < MAX_PLAYERS; i++)
3989 if (stored_player[i].active)
3990 game.players_still_needed++;
3992 if (level.solved_by_one_player)
3993 game.players_still_needed = 1;
3995 // when recording the game, store which players take part in the game
3998 #if USE_NEW_PLAYER_ASSIGNMENTS
3999 for (i = 0; i < MAX_PLAYERS; i++)
4000 if (stored_player[i].connected)
4001 tape.player_participates[i] = TRUE;
4003 for (i = 0; i < MAX_PLAYERS; i++)
4004 if (stored_player[i].active)
4005 tape.player_participates[i] = TRUE;
4009 #if DEBUG_INIT_PLAYER
4010 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4013 if (BorderElement == EL_EMPTY)
4016 SBX_Right = lev_fieldx - SCR_FIELDX;
4018 SBY_Lower = lev_fieldy - SCR_FIELDY;
4023 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4025 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4028 if (full_lev_fieldx <= SCR_FIELDX)
4029 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4030 if (full_lev_fieldy <= SCR_FIELDY)
4031 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4033 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4035 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4038 // if local player not found, look for custom element that might create
4039 // the player (make some assumptions about the right custom element)
4040 if (!local_player->present)
4042 int start_x = 0, start_y = 0;
4043 int found_rating = 0;
4044 int found_element = EL_UNDEFINED;
4045 int player_nr = local_player->index_nr;
4047 SCAN_PLAYFIELD(x, y)
4049 int element = Feld[x][y];
4054 if (level.use_start_element[player_nr] &&
4055 level.start_element[player_nr] == element &&
4062 found_element = element;
4065 if (!IS_CUSTOM_ELEMENT(element))
4068 if (CAN_CHANGE(element))
4070 for (i = 0; i < element_info[element].num_change_pages; i++)
4072 // check for player created from custom element as single target
4073 content = element_info[element].change_page[i].target_element;
4074 is_player = ELEM_IS_PLAYER(content);
4076 if (is_player && (found_rating < 3 ||
4077 (found_rating == 3 && element < found_element)))
4083 found_element = element;
4088 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4090 // check for player created from custom element as explosion content
4091 content = element_info[element].content.e[xx][yy];
4092 is_player = ELEM_IS_PLAYER(content);
4094 if (is_player && (found_rating < 2 ||
4095 (found_rating == 2 && element < found_element)))
4097 start_x = x + xx - 1;
4098 start_y = y + yy - 1;
4101 found_element = element;
4104 if (!CAN_CHANGE(element))
4107 for (i = 0; i < element_info[element].num_change_pages; i++)
4109 // check for player created from custom element as extended target
4111 element_info[element].change_page[i].target_content.e[xx][yy];
4113 is_player = ELEM_IS_PLAYER(content);
4115 if (is_player && (found_rating < 1 ||
4116 (found_rating == 1 && element < found_element)))
4118 start_x = x + xx - 1;
4119 start_y = y + yy - 1;
4122 found_element = element;
4128 scroll_x = SCROLL_POSITION_X(start_x);
4129 scroll_y = SCROLL_POSITION_Y(start_y);
4133 scroll_x = SCROLL_POSITION_X(local_player->jx);
4134 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4137 // !!! FIX THIS (START) !!!
4138 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4140 InitGameEngine_EM();
4142 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4144 InitGameEngine_SP();
4146 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4148 InitGameEngine_MM();
4152 DrawLevel(REDRAW_FIELD);
4155 // after drawing the level, correct some elements
4156 if (game.timegate_time_left == 0)
4157 CloseAllOpenTimegates();
4160 // blit playfield from scroll buffer to normal back buffer for fading in
4161 BlitScreenToBitmap(backbuffer);
4162 // !!! FIX THIS (END) !!!
4164 DrawMaskedBorder(fade_mask);
4169 // full screen redraw is required at this point in the following cases:
4170 // - special editor door undrawn when game was started from level editor
4171 // - drawing area (playfield) was changed and has to be removed completely
4172 redraw_mask = REDRAW_ALL;
4176 if (!game.restart_level)
4178 // copy default game door content to main double buffer
4180 // !!! CHECK AGAIN !!!
4181 SetPanelBackground();
4182 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4183 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4186 SetPanelBackground();
4187 SetDrawBackgroundMask(REDRAW_DOOR_1);
4189 UpdateAndDisplayGameControlValues();
4191 if (!game.restart_level)
4197 CreateGameButtons();
4202 // copy actual game door content to door double buffer for OpenDoor()
4203 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4205 OpenDoor(DOOR_OPEN_ALL);
4207 KeyboardAutoRepeatOffUnlessAutoplay();
4209 #if DEBUG_INIT_PLAYER
4210 DebugPrintPlayerStatus("Player status (final)");
4219 if (!game.restart_level && !tape.playing)
4221 LevelStats_incPlayed(level_nr);
4223 SaveLevelSetup_SeriesInfo();
4226 game.restart_level = FALSE;
4227 game.restart_game_message = NULL;
4228 game.request_active = FALSE;
4230 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4231 InitGameActions_MM();
4233 SaveEngineSnapshotToListInitial();
4235 if (!game.restart_level)
4237 PlaySound(SND_GAME_STARTING);
4239 if (setup.sound_music)
4244 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4245 int actual_player_x, int actual_player_y)
4247 // this is used for non-R'n'D game engines to update certain engine values
4249 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4251 actual_player_x = correctLevelPosX_EM(actual_player_x);
4252 actual_player_y = correctLevelPosY_EM(actual_player_y);
4255 // needed to determine if sounds are played within the visible screen area
4256 scroll_x = actual_scroll_x;
4257 scroll_y = actual_scroll_y;
4259 // needed to get player position for "follow finger" playing input method
4260 local_player->jx = actual_player_x;
4261 local_player->jy = actual_player_y;
4264 void InitMovDir(int x, int y)
4266 int i, element = Feld[x][y];
4267 static int xy[4][2] =
4274 static int direction[3][4] =
4276 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4277 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4278 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4287 Feld[x][y] = EL_BUG;
4288 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4291 case EL_SPACESHIP_RIGHT:
4292 case EL_SPACESHIP_UP:
4293 case EL_SPACESHIP_LEFT:
4294 case EL_SPACESHIP_DOWN:
4295 Feld[x][y] = EL_SPACESHIP;
4296 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4299 case EL_BD_BUTTERFLY_RIGHT:
4300 case EL_BD_BUTTERFLY_UP:
4301 case EL_BD_BUTTERFLY_LEFT:
4302 case EL_BD_BUTTERFLY_DOWN:
4303 Feld[x][y] = EL_BD_BUTTERFLY;
4304 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4307 case EL_BD_FIREFLY_RIGHT:
4308 case EL_BD_FIREFLY_UP:
4309 case EL_BD_FIREFLY_LEFT:
4310 case EL_BD_FIREFLY_DOWN:
4311 Feld[x][y] = EL_BD_FIREFLY;
4312 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4315 case EL_PACMAN_RIGHT:
4317 case EL_PACMAN_LEFT:
4318 case EL_PACMAN_DOWN:
4319 Feld[x][y] = EL_PACMAN;
4320 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4323 case EL_YAMYAM_LEFT:
4324 case EL_YAMYAM_RIGHT:
4326 case EL_YAMYAM_DOWN:
4327 Feld[x][y] = EL_YAMYAM;
4328 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4331 case EL_SP_SNIKSNAK:
4332 MovDir[x][y] = MV_UP;
4335 case EL_SP_ELECTRON:
4336 MovDir[x][y] = MV_LEFT;
4343 Feld[x][y] = EL_MOLE;
4344 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4348 if (IS_CUSTOM_ELEMENT(element))
4350 struct ElementInfo *ei = &element_info[element];
4351 int move_direction_initial = ei->move_direction_initial;
4352 int move_pattern = ei->move_pattern;
4354 if (move_direction_initial == MV_START_PREVIOUS)
4356 if (MovDir[x][y] != MV_NONE)
4359 move_direction_initial = MV_START_AUTOMATIC;
4362 if (move_direction_initial == MV_START_RANDOM)
4363 MovDir[x][y] = 1 << RND(4);
4364 else if (move_direction_initial & MV_ANY_DIRECTION)
4365 MovDir[x][y] = move_direction_initial;
4366 else if (move_pattern == MV_ALL_DIRECTIONS ||
4367 move_pattern == MV_TURNING_LEFT ||
4368 move_pattern == MV_TURNING_RIGHT ||
4369 move_pattern == MV_TURNING_LEFT_RIGHT ||
4370 move_pattern == MV_TURNING_RIGHT_LEFT ||
4371 move_pattern == MV_TURNING_RANDOM)
4372 MovDir[x][y] = 1 << RND(4);
4373 else if (move_pattern == MV_HORIZONTAL)
4374 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4375 else if (move_pattern == MV_VERTICAL)
4376 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4377 else if (move_pattern & MV_ANY_DIRECTION)
4378 MovDir[x][y] = element_info[element].move_pattern;
4379 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4380 move_pattern == MV_ALONG_RIGHT_SIDE)
4382 // use random direction as default start direction
4383 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4384 MovDir[x][y] = 1 << RND(4);
4386 for (i = 0; i < NUM_DIRECTIONS; i++)
4388 int x1 = x + xy[i][0];
4389 int y1 = y + xy[i][1];
4391 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4393 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4394 MovDir[x][y] = direction[0][i];
4396 MovDir[x][y] = direction[1][i];
4405 MovDir[x][y] = 1 << RND(4);
4407 if (element != EL_BUG &&
4408 element != EL_SPACESHIP &&
4409 element != EL_BD_BUTTERFLY &&
4410 element != EL_BD_FIREFLY)
4413 for (i = 0; i < NUM_DIRECTIONS; i++)
4415 int x1 = x + xy[i][0];
4416 int y1 = y + xy[i][1];
4418 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4420 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4422 MovDir[x][y] = direction[0][i];
4425 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4426 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4428 MovDir[x][y] = direction[1][i];
4437 GfxDir[x][y] = MovDir[x][y];
4440 void InitAmoebaNr(int x, int y)
4443 int group_nr = AmoebeNachbarNr(x, y);
4447 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4449 if (AmoebaCnt[i] == 0)
4457 AmoebaNr[x][y] = group_nr;
4458 AmoebaCnt[group_nr]++;
4459 AmoebaCnt2[group_nr]++;
4462 static void LevelSolved(void)
4464 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4465 game.players_still_needed > 0)
4468 game.LevelSolved = TRUE;
4469 game.GameOver = TRUE;
4471 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4472 level.native_em_level->lev->score :
4473 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4476 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4477 MM_HEALTH(game_mm.laser_overload_value) :
4480 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4481 game.LevelSolved_CountingScore = game.score_final;
4482 game.LevelSolved_CountingHealth = game.health_final;
4487 static int time_count_steps;
4488 static int time, time_final;
4489 static int score, score_final;
4490 static int health, health_final;
4491 static int game_over_delay_1 = 0;
4492 static int game_over_delay_2 = 0;
4493 static int game_over_delay_3 = 0;
4494 int game_over_delay_value_1 = 50;
4495 int game_over_delay_value_2 = 25;
4496 int game_over_delay_value_3 = 50;
4498 if (!game.LevelSolved_GameWon)
4502 // do not start end game actions before the player stops moving (to exit)
4503 if (local_player->MovPos)
4506 game.LevelSolved_GameWon = TRUE;
4507 game.LevelSolved_SaveTape = tape.recording;
4508 game.LevelSolved_SaveScore = !tape.playing;
4512 LevelStats_incSolved(level_nr);
4514 SaveLevelSetup_SeriesInfo();
4517 if (tape.auto_play) // tape might already be stopped here
4518 tape.auto_play_level_solved = TRUE;
4522 game_over_delay_1 = 0;
4523 game_over_delay_2 = 0;
4524 game_over_delay_3 = game_over_delay_value_3;
4526 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4527 score = score_final = game.score_final;
4528 health = health_final = game.health_final;
4530 if (level.score[SC_TIME_BONUS] > 0)
4535 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4537 else if (game.no_time_limit && TimePlayed < 999)
4540 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4543 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4545 game_over_delay_1 = game_over_delay_value_1;
4547 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4550 score_final += health * level.score[SC_TIME_BONUS];
4552 game_over_delay_2 = game_over_delay_value_2;
4555 game.score_final = score_final;
4556 game.health_final = health_final;
4559 if (level_editor_test_game)
4562 score = score_final;
4564 game.LevelSolved_CountingTime = time;
4565 game.LevelSolved_CountingScore = score;
4567 game_panel_controls[GAME_PANEL_TIME].value = time;
4568 game_panel_controls[GAME_PANEL_SCORE].value = score;
4570 DisplayGameControlValues();
4573 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4575 // check if last player has left the level
4576 if (game.exit_x >= 0 &&
4579 int x = game.exit_x;
4580 int y = game.exit_y;
4581 int element = Feld[x][y];
4583 // close exit door after last player
4584 if ((game.all_players_gone &&
4585 (element == EL_EXIT_OPEN ||
4586 element == EL_SP_EXIT_OPEN ||
4587 element == EL_STEEL_EXIT_OPEN)) ||
4588 element == EL_EM_EXIT_OPEN ||
4589 element == EL_EM_STEEL_EXIT_OPEN)
4593 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4594 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4595 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4596 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4597 EL_EM_STEEL_EXIT_CLOSING);
4599 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4602 // player disappears
4603 DrawLevelField(x, y);
4606 for (i = 0; i < MAX_PLAYERS; i++)
4608 struct PlayerInfo *player = &stored_player[i];
4610 if (player->present)
4612 RemovePlayer(player);
4614 // player disappears
4615 DrawLevelField(player->jx, player->jy);
4620 PlaySound(SND_GAME_WINNING);
4623 if (game_over_delay_1 > 0)
4625 game_over_delay_1--;
4630 if (time != time_final)
4632 int time_to_go = ABS(time_final - time);
4633 int time_count_dir = (time < time_final ? +1 : -1);
4635 if (time_to_go < time_count_steps)
4636 time_count_steps = 1;
4638 time += time_count_steps * time_count_dir;
4639 score += time_count_steps * level.score[SC_TIME_BONUS];
4641 game.LevelSolved_CountingTime = time;
4642 game.LevelSolved_CountingScore = score;
4644 game_panel_controls[GAME_PANEL_TIME].value = time;
4645 game_panel_controls[GAME_PANEL_SCORE].value = score;
4647 DisplayGameControlValues();
4649 if (time == time_final)
4650 StopSound(SND_GAME_LEVELTIME_BONUS);
4651 else if (setup.sound_loops)
4652 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4654 PlaySound(SND_GAME_LEVELTIME_BONUS);
4659 if (game_over_delay_2 > 0)
4661 game_over_delay_2--;
4666 if (health != health_final)
4668 int health_count_dir = (health < health_final ? +1 : -1);
4670 health += health_count_dir;
4671 score += level.score[SC_TIME_BONUS];
4673 game.LevelSolved_CountingHealth = health;
4674 game.LevelSolved_CountingScore = score;
4676 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4677 game_panel_controls[GAME_PANEL_SCORE].value = score;
4679 DisplayGameControlValues();
4681 if (health == health_final)
4682 StopSound(SND_GAME_LEVELTIME_BONUS);
4683 else if (setup.sound_loops)
4684 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4686 PlaySound(SND_GAME_LEVELTIME_BONUS);
4691 game.panel.active = FALSE;
4693 if (game_over_delay_3 > 0)
4695 game_over_delay_3--;
4705 // used instead of "level_nr" (needed for network games)
4706 int last_level_nr = levelset.level_nr;
4709 game.LevelSolved_GameEnd = TRUE;
4711 if (game.LevelSolved_SaveTape)
4713 // make sure that request dialog to save tape does not open door again
4714 if (!global.use_envelope_request)
4715 CloseDoor(DOOR_CLOSE_1);
4717 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4720 // if no tape is to be saved, close both doors simultaneously
4721 CloseDoor(DOOR_CLOSE_ALL);
4723 if (level_editor_test_game)
4725 SetGameStatus(GAME_MODE_MAIN);
4732 if (!game.LevelSolved_SaveScore)
4734 SetGameStatus(GAME_MODE_MAIN);
4741 if (level_nr == leveldir_current->handicap_level)
4743 leveldir_current->handicap_level++;
4745 SaveLevelSetup_SeriesInfo();
4748 if (setup.increment_levels &&
4749 level_nr < leveldir_current->last_level &&
4752 level_nr++; // advance to next level
4753 TapeErase(); // start with empty tape
4755 if (setup.auto_play_next_level)
4757 LoadLevel(level_nr);
4759 SaveLevelSetup_SeriesInfo();
4763 hi_pos = NewHiScore(last_level_nr);
4765 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4767 SetGameStatus(GAME_MODE_SCORES);
4769 DrawHallOfFame(last_level_nr, hi_pos);
4771 else if (setup.auto_play_next_level && setup.increment_levels &&
4772 last_level_nr < leveldir_current->last_level &&
4775 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4779 SetGameStatus(GAME_MODE_MAIN);
4785 int NewHiScore(int level_nr)
4789 boolean one_score_entry_per_name = !program.many_scores_per_name;
4791 LoadScore(level_nr);
4793 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4794 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4797 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4799 if (game.score_final > highscore[k].Score)
4801 // player has made it to the hall of fame
4803 if (k < MAX_SCORE_ENTRIES - 1)
4805 int m = MAX_SCORE_ENTRIES - 1;
4807 if (one_score_entry_per_name)
4809 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4810 if (strEqual(setup.player_name, highscore[l].Name))
4813 if (m == k) // player's new highscore overwrites his old one
4817 for (l = m; l > k; l--)
4819 strcpy(highscore[l].Name, highscore[l - 1].Name);
4820 highscore[l].Score = highscore[l - 1].Score;
4826 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4827 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4828 highscore[k].Score = game.score_final;
4833 else if (one_score_entry_per_name &&
4834 !strncmp(setup.player_name, highscore[k].Name,
4835 MAX_PLAYER_NAME_LEN))
4836 break; // player already there with a higher score
4840 SaveScore(level_nr);
4845 static int getElementMoveStepsizeExt(int x, int y, int direction)
4847 int element = Feld[x][y];
4848 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4849 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4850 int horiz_move = (dx != 0);
4851 int sign = (horiz_move ? dx : dy);
4852 int step = sign * element_info[element].move_stepsize;
4854 // special values for move stepsize for spring and things on conveyor belt
4857 if (CAN_FALL(element) &&
4858 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
4859 step = sign * MOVE_STEPSIZE_NORMAL / 2;
4860 else if (element == EL_SPRING)
4861 step = sign * MOVE_STEPSIZE_NORMAL * 2;
4867 static int getElementMoveStepsize(int x, int y)
4869 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
4872 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
4874 if (player->GfxAction != action || player->GfxDir != dir)
4876 player->GfxAction = action;
4877 player->GfxDir = dir;
4879 player->StepFrame = 0;
4883 static void ResetGfxFrame(int x, int y)
4885 // profiling showed that "autotest" spends 10~20% of its time in this function
4886 if (DrawingDeactivatedField())
4889 int element = Feld[x][y];
4890 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
4892 if (graphic_info[graphic].anim_global_sync)
4893 GfxFrame[x][y] = FrameCounter;
4894 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
4895 GfxFrame[x][y] = CustomValue[x][y];
4896 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
4897 GfxFrame[x][y] = element_info[element].collect_score;
4898 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
4899 GfxFrame[x][y] = ChangeDelay[x][y];
4902 static void ResetGfxAnimation(int x, int y)
4904 GfxAction[x][y] = ACTION_DEFAULT;
4905 GfxDir[x][y] = MovDir[x][y];
4908 ResetGfxFrame(x, y);
4911 static void ResetRandomAnimationValue(int x, int y)
4913 GfxRandom[x][y] = INIT_GFX_RANDOM();
4916 static void InitMovingField(int x, int y, int direction)
4918 int element = Feld[x][y];
4919 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4920 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4923 boolean is_moving_before, is_moving_after;
4925 // check if element was/is moving or being moved before/after mode change
4926 is_moving_before = (WasJustMoving[x][y] != 0);
4927 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
4929 // reset animation only for moving elements which change direction of moving
4930 // or which just started or stopped moving
4931 // (else CEs with property "can move" / "not moving" are reset each frame)
4932 if (is_moving_before != is_moving_after ||
4933 direction != MovDir[x][y])
4934 ResetGfxAnimation(x, y);
4936 MovDir[x][y] = direction;
4937 GfxDir[x][y] = direction;
4939 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
4940 direction == MV_DOWN && CAN_FALL(element) ?
4941 ACTION_FALLING : ACTION_MOVING);
4943 // this is needed for CEs with property "can move" / "not moving"
4945 if (is_moving_after)
4947 if (Feld[newx][newy] == EL_EMPTY)
4948 Feld[newx][newy] = EL_BLOCKED;
4950 MovDir[newx][newy] = MovDir[x][y];
4952 CustomValue[newx][newy] = CustomValue[x][y];
4954 GfxFrame[newx][newy] = GfxFrame[x][y];
4955 GfxRandom[newx][newy] = GfxRandom[x][y];
4956 GfxAction[newx][newy] = GfxAction[x][y];
4957 GfxDir[newx][newy] = GfxDir[x][y];
4961 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
4963 int direction = MovDir[x][y];
4964 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
4965 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
4971 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
4973 int oldx = x, oldy = y;
4974 int direction = MovDir[x][y];
4976 if (direction == MV_LEFT)
4978 else if (direction == MV_RIGHT)
4980 else if (direction == MV_UP)
4982 else if (direction == MV_DOWN)
4985 *comes_from_x = oldx;
4986 *comes_from_y = oldy;
4989 static int MovingOrBlocked2Element(int x, int y)
4991 int element = Feld[x][y];
4993 if (element == EL_BLOCKED)
4997 Blocked2Moving(x, y, &oldx, &oldy);
4998 return Feld[oldx][oldy];
5004 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5006 // like MovingOrBlocked2Element(), but if element is moving
5007 // and (x,y) is the field the moving element is just leaving,
5008 // return EL_BLOCKED instead of the element value
5009 int element = Feld[x][y];
5011 if (IS_MOVING(x, y))
5013 if (element == EL_BLOCKED)
5017 Blocked2Moving(x, y, &oldx, &oldy);
5018 return Feld[oldx][oldy];
5027 static void RemoveField(int x, int y)
5029 Feld[x][y] = EL_EMPTY;
5035 CustomValue[x][y] = 0;
5038 ChangeDelay[x][y] = 0;
5039 ChangePage[x][y] = -1;
5040 Pushed[x][y] = FALSE;
5042 GfxElement[x][y] = EL_UNDEFINED;
5043 GfxAction[x][y] = ACTION_DEFAULT;
5044 GfxDir[x][y] = MV_NONE;
5047 static void RemoveMovingField(int x, int y)
5049 int oldx = x, oldy = y, newx = x, newy = y;
5050 int element = Feld[x][y];
5051 int next_element = EL_UNDEFINED;
5053 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5056 if (IS_MOVING(x, y))
5058 Moving2Blocked(x, y, &newx, &newy);
5060 if (Feld[newx][newy] != EL_BLOCKED)
5062 // element is moving, but target field is not free (blocked), but
5063 // already occupied by something different (example: acid pool);
5064 // in this case, only remove the moving field, but not the target
5066 RemoveField(oldx, oldy);
5068 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5070 TEST_DrawLevelField(oldx, oldy);
5075 else if (element == EL_BLOCKED)
5077 Blocked2Moving(x, y, &oldx, &oldy);
5078 if (!IS_MOVING(oldx, oldy))
5082 if (element == EL_BLOCKED &&
5083 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5084 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5085 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5086 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5087 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5088 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5089 next_element = get_next_element(Feld[oldx][oldy]);
5091 RemoveField(oldx, oldy);
5092 RemoveField(newx, newy);
5094 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5096 if (next_element != EL_UNDEFINED)
5097 Feld[oldx][oldy] = next_element;
5099 TEST_DrawLevelField(oldx, oldy);
5100 TEST_DrawLevelField(newx, newy);
5103 void DrawDynamite(int x, int y)
5105 int sx = SCREENX(x), sy = SCREENY(y);
5106 int graphic = el2img(Feld[x][y]);
5109 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5112 if (IS_WALKABLE_INSIDE(Back[x][y]))
5116 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5117 else if (Store[x][y])
5118 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5120 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5122 if (Back[x][y] || Store[x][y])
5123 DrawGraphicThruMask(sx, sy, graphic, frame);
5125 DrawGraphic(sx, sy, graphic, frame);
5128 static void CheckDynamite(int x, int y)
5130 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5134 if (MovDelay[x][y] != 0)
5137 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5143 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5148 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5150 boolean num_checked_players = 0;
5153 for (i = 0; i < MAX_PLAYERS; i++)
5155 if (stored_player[i].active)
5157 int sx = stored_player[i].jx;
5158 int sy = stored_player[i].jy;
5160 if (num_checked_players == 0)
5167 *sx1 = MIN(*sx1, sx);
5168 *sy1 = MIN(*sy1, sy);
5169 *sx2 = MAX(*sx2, sx);
5170 *sy2 = MAX(*sy2, sy);
5173 num_checked_players++;
5178 static boolean checkIfAllPlayersFitToScreen_RND(void)
5180 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5182 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5184 return (sx2 - sx1 < SCR_FIELDX &&
5185 sy2 - sy1 < SCR_FIELDY);
5188 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5190 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5192 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5194 *sx = (sx1 + sx2) / 2;
5195 *sy = (sy1 + sy2) / 2;
5198 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5199 boolean center_screen, boolean quick_relocation)
5201 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5202 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5203 boolean no_delay = (tape.warp_forward);
5204 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5205 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5206 int new_scroll_x, new_scroll_y;
5208 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5210 // case 1: quick relocation inside visible screen (without scrolling)
5217 if (!level.shifted_relocation || center_screen)
5219 // relocation _with_ centering of screen
5221 new_scroll_x = SCROLL_POSITION_X(x);
5222 new_scroll_y = SCROLL_POSITION_Y(y);
5226 // relocation _without_ centering of screen
5228 int center_scroll_x = SCROLL_POSITION_X(old_x);
5229 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5230 int offset_x = x + (scroll_x - center_scroll_x);
5231 int offset_y = y + (scroll_y - center_scroll_y);
5233 // for new screen position, apply previous offset to center position
5234 new_scroll_x = SCROLL_POSITION_X(offset_x);
5235 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5238 if (quick_relocation)
5240 // case 2: quick relocation (redraw without visible scrolling)
5242 scroll_x = new_scroll_x;
5243 scroll_y = new_scroll_y;
5250 // case 3: visible relocation (with scrolling to new position)
5252 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5254 SetVideoFrameDelay(wait_delay_value);
5256 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5259 int fx = FX, fy = FY;
5261 dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5262 dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5264 if (dx == 0 && dy == 0) // no scrolling needed at all
5270 fx += dx * TILEX / 2;
5271 fy += dy * TILEY / 2;
5273 ScrollLevel(dx, dy);
5276 // scroll in two steps of half tile size to make things smoother
5277 BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
5279 // scroll second step to align at full tile size
5280 BlitScreenToBitmap(window);
5286 SetVideoFrameDelay(frame_delay_value_old);
5289 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5291 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5292 int player_nr = GET_PLAYER_NR(el_player);
5293 struct PlayerInfo *player = &stored_player[player_nr];
5294 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5295 boolean no_delay = (tape.warp_forward);
5296 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5297 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5298 int old_jx = player->jx;
5299 int old_jy = player->jy;
5300 int old_element = Feld[old_jx][old_jy];
5301 int element = Feld[jx][jy];
5302 boolean player_relocated = (old_jx != jx || old_jy != jy);
5304 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5305 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5306 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5307 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5308 int leave_side_horiz = move_dir_horiz;
5309 int leave_side_vert = move_dir_vert;
5310 int enter_side = enter_side_horiz | enter_side_vert;
5311 int leave_side = leave_side_horiz | leave_side_vert;
5313 if (player->buried) // do not reanimate dead player
5316 if (!player_relocated) // no need to relocate the player
5319 if (IS_PLAYER(jx, jy)) // player already placed at new position
5321 RemoveField(jx, jy); // temporarily remove newly placed player
5322 DrawLevelField(jx, jy);
5325 if (player->present)
5327 while (player->MovPos)
5329 ScrollPlayer(player, SCROLL_GO_ON);
5330 ScrollScreen(NULL, SCROLL_GO_ON);
5332 AdvanceFrameAndPlayerCounters(player->index_nr);
5336 BackToFront_WithFrameDelay(wait_delay_value);
5339 DrawPlayer(player); // needed here only to cleanup last field
5340 DrawLevelField(player->jx, player->jy); // remove player graphic
5342 player->is_moving = FALSE;
5345 if (IS_CUSTOM_ELEMENT(old_element))
5346 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5348 player->index_bit, leave_side);
5350 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5352 player->index_bit, leave_side);
5354 Feld[jx][jy] = el_player;
5355 InitPlayerField(jx, jy, el_player, TRUE);
5357 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5358 possible that the relocation target field did not contain a player element,
5359 but a walkable element, to which the new player was relocated -- in this
5360 case, restore that (already initialized!) element on the player field */
5361 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5363 Feld[jx][jy] = element; // restore previously existing element
5366 // only visually relocate centered player
5367 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5368 FALSE, level.instant_relocation);
5370 TestIfPlayerTouchesBadThing(jx, jy);
5371 TestIfPlayerTouchesCustomElement(jx, jy);
5373 if (IS_CUSTOM_ELEMENT(element))
5374 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5375 player->index_bit, enter_side);
5377 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5378 player->index_bit, enter_side);
5380 if (player->is_switching)
5382 /* ensure that relocation while still switching an element does not cause
5383 a new element to be treated as also switched directly after relocation
5384 (this is important for teleporter switches that teleport the player to
5385 a place where another teleporter switch is in the same direction, which
5386 would then incorrectly be treated as immediately switched before the
5387 direction key that caused the switch was released) */
5389 player->switch_x += jx - old_jx;
5390 player->switch_y += jy - old_jy;
5394 static void Explode(int ex, int ey, int phase, int mode)
5400 // !!! eliminate this variable !!!
5401 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5403 if (game.explosions_delayed)
5405 ExplodeField[ex][ey] = mode;
5409 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5411 int center_element = Feld[ex][ey];
5412 int artwork_element, explosion_element; // set these values later
5414 // remove things displayed in background while burning dynamite
5415 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5418 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5420 // put moving element to center field (and let it explode there)
5421 center_element = MovingOrBlocked2Element(ex, ey);
5422 RemoveMovingField(ex, ey);
5423 Feld[ex][ey] = center_element;
5426 // now "center_element" is finally determined -- set related values now
5427 artwork_element = center_element; // for custom player artwork
5428 explosion_element = center_element; // for custom player artwork
5430 if (IS_PLAYER(ex, ey))
5432 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5434 artwork_element = stored_player[player_nr].artwork_element;
5436 if (level.use_explosion_element[player_nr])
5438 explosion_element = level.explosion_element[player_nr];
5439 artwork_element = explosion_element;
5443 if (mode == EX_TYPE_NORMAL ||
5444 mode == EX_TYPE_CENTER ||
5445 mode == EX_TYPE_CROSS)
5446 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5448 last_phase = element_info[explosion_element].explosion_delay + 1;
5450 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5452 int xx = x - ex + 1;
5453 int yy = y - ey + 1;
5456 if (!IN_LEV_FIELD(x, y) ||
5457 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5458 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5461 element = Feld[x][y];
5463 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5465 element = MovingOrBlocked2Element(x, y);
5467 if (!IS_EXPLOSION_PROOF(element))
5468 RemoveMovingField(x, y);
5471 // indestructible elements can only explode in center (but not flames)
5472 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5473 mode == EX_TYPE_BORDER)) ||
5474 element == EL_FLAMES)
5477 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5478 behaviour, for example when touching a yamyam that explodes to rocks
5479 with active deadly shield, a rock is created under the player !!! */
5480 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5482 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5483 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5484 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5486 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5489 if (IS_ACTIVE_BOMB(element))
5491 // re-activate things under the bomb like gate or penguin
5492 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5499 // save walkable background elements while explosion on same tile
5500 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5501 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5502 Back[x][y] = element;
5504 // ignite explodable elements reached by other explosion
5505 if (element == EL_EXPLOSION)
5506 element = Store2[x][y];
5508 if (AmoebaNr[x][y] &&
5509 (element == EL_AMOEBA_FULL ||
5510 element == EL_BD_AMOEBA ||
5511 element == EL_AMOEBA_GROWING))
5513 AmoebaCnt[AmoebaNr[x][y]]--;
5514 AmoebaCnt2[AmoebaNr[x][y]]--;
5519 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5521 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5523 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5525 if (PLAYERINFO(ex, ey)->use_murphy)
5526 Store[x][y] = EL_EMPTY;
5529 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5530 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5531 else if (ELEM_IS_PLAYER(center_element))
5532 Store[x][y] = EL_EMPTY;
5533 else if (center_element == EL_YAMYAM)
5534 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5535 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5536 Store[x][y] = element_info[center_element].content.e[xx][yy];
5538 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5539 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5540 // otherwise) -- FIX THIS !!!
5541 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5542 Store[x][y] = element_info[element].content.e[1][1];
5544 else if (!CAN_EXPLODE(element))
5545 Store[x][y] = element_info[element].content.e[1][1];
5548 Store[x][y] = EL_EMPTY;
5550 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5551 center_element == EL_AMOEBA_TO_DIAMOND)
5552 Store2[x][y] = element;
5554 Feld[x][y] = EL_EXPLOSION;
5555 GfxElement[x][y] = artwork_element;
5557 ExplodePhase[x][y] = 1;
5558 ExplodeDelay[x][y] = last_phase;
5563 if (center_element == EL_YAMYAM)
5564 game.yamyam_content_nr =
5565 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5577 GfxFrame[x][y] = 0; // restart explosion animation
5579 last_phase = ExplodeDelay[x][y];
5581 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5583 // this can happen if the player leaves an explosion just in time
5584 if (GfxElement[x][y] == EL_UNDEFINED)
5585 GfxElement[x][y] = EL_EMPTY;
5587 border_element = Store2[x][y];
5588 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5589 border_element = StorePlayer[x][y];
5591 if (phase == element_info[border_element].ignition_delay ||
5592 phase == last_phase)
5594 boolean border_explosion = FALSE;
5596 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5597 !PLAYER_EXPLOSION_PROTECTED(x, y))
5599 KillPlayerUnlessExplosionProtected(x, y);
5600 border_explosion = TRUE;
5602 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5604 Feld[x][y] = Store2[x][y];
5607 border_explosion = TRUE;
5609 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5611 AmoebeUmwandeln(x, y);
5613 border_explosion = TRUE;
5616 // if an element just explodes due to another explosion (chain-reaction),
5617 // do not immediately end the new explosion when it was the last frame of
5618 // the explosion (as it would be done in the following "if"-statement!)
5619 if (border_explosion && phase == last_phase)
5623 if (phase == last_phase)
5627 element = Feld[x][y] = Store[x][y];
5628 Store[x][y] = Store2[x][y] = 0;
5629 GfxElement[x][y] = EL_UNDEFINED;
5631 // player can escape from explosions and might therefore be still alive
5632 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5633 element <= EL_PLAYER_IS_EXPLODING_4)
5635 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5636 int explosion_element = EL_PLAYER_1 + player_nr;
5637 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5638 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5640 if (level.use_explosion_element[player_nr])
5641 explosion_element = level.explosion_element[player_nr];
5643 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5644 element_info[explosion_element].content.e[xx][yy]);
5647 // restore probably existing indestructible background element
5648 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5649 element = Feld[x][y] = Back[x][y];
5652 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5653 GfxDir[x][y] = MV_NONE;
5654 ChangeDelay[x][y] = 0;
5655 ChangePage[x][y] = -1;
5657 CustomValue[x][y] = 0;
5659 InitField_WithBug2(x, y, FALSE);
5661 TEST_DrawLevelField(x, y);
5663 TestIfElementTouchesCustomElement(x, y);
5665 if (GFX_CRUMBLED(element))
5666 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5668 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5669 StorePlayer[x][y] = 0;
5671 if (ELEM_IS_PLAYER(element))
5672 RelocatePlayer(x, y, element);
5674 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5676 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5677 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5680 TEST_DrawLevelFieldCrumbled(x, y);
5682 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5684 DrawLevelElement(x, y, Back[x][y]);
5685 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5687 else if (IS_WALKABLE_UNDER(Back[x][y]))
5689 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5690 DrawLevelElementThruMask(x, y, Back[x][y]);
5692 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5693 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5697 static void DynaExplode(int ex, int ey)
5700 int dynabomb_element = Feld[ex][ey];
5701 int dynabomb_size = 1;
5702 boolean dynabomb_xl = FALSE;
5703 struct PlayerInfo *player;
5704 static int xy[4][2] =
5712 if (IS_ACTIVE_BOMB(dynabomb_element))
5714 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5715 dynabomb_size = player->dynabomb_size;
5716 dynabomb_xl = player->dynabomb_xl;
5717 player->dynabombs_left++;
5720 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5722 for (i = 0; i < NUM_DIRECTIONS; i++)
5724 for (j = 1; j <= dynabomb_size; j++)
5726 int x = ex + j * xy[i][0];
5727 int y = ey + j * xy[i][1];
5730 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5733 element = Feld[x][y];
5735 // do not restart explosions of fields with active bombs
5736 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5739 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5741 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5742 !IS_DIGGABLE(element) && !dynabomb_xl)
5748 void Bang(int x, int y)
5750 int element = MovingOrBlocked2Element(x, y);
5751 int explosion_type = EX_TYPE_NORMAL;
5753 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5755 struct PlayerInfo *player = PLAYERINFO(x, y);
5757 element = Feld[x][y] = player->initial_element;
5759 if (level.use_explosion_element[player->index_nr])
5761 int explosion_element = level.explosion_element[player->index_nr];
5763 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5764 explosion_type = EX_TYPE_CROSS;
5765 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5766 explosion_type = EX_TYPE_CENTER;
5774 case EL_BD_BUTTERFLY:
5777 case EL_DARK_YAMYAM:
5781 RaiseScoreElement(element);
5784 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5785 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5786 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5787 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5788 case EL_DYNABOMB_INCREASE_NUMBER:
5789 case EL_DYNABOMB_INCREASE_SIZE:
5790 case EL_DYNABOMB_INCREASE_POWER:
5791 explosion_type = EX_TYPE_DYNA;
5794 case EL_DC_LANDMINE:
5795 explosion_type = EX_TYPE_CENTER;
5800 case EL_LAMP_ACTIVE:
5801 case EL_AMOEBA_TO_DIAMOND:
5802 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5803 explosion_type = EX_TYPE_CENTER;
5807 if (element_info[element].explosion_type == EXPLODES_CROSS)
5808 explosion_type = EX_TYPE_CROSS;
5809 else if (element_info[element].explosion_type == EXPLODES_1X1)
5810 explosion_type = EX_TYPE_CENTER;
5814 if (explosion_type == EX_TYPE_DYNA)
5817 Explode(x, y, EX_PHASE_START, explosion_type);
5819 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5822 static void SplashAcid(int x, int y)
5824 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5825 (!IN_LEV_FIELD(x - 1, y - 2) ||
5826 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5827 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5829 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5830 (!IN_LEV_FIELD(x + 1, y - 2) ||
5831 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5832 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5834 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5837 static void InitBeltMovement(void)
5839 static int belt_base_element[4] =
5841 EL_CONVEYOR_BELT_1_LEFT,
5842 EL_CONVEYOR_BELT_2_LEFT,
5843 EL_CONVEYOR_BELT_3_LEFT,
5844 EL_CONVEYOR_BELT_4_LEFT
5846 static int belt_base_active_element[4] =
5848 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5849 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5850 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5851 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5856 // set frame order for belt animation graphic according to belt direction
5857 for (i = 0; i < NUM_BELTS; i++)
5861 for (j = 0; j < NUM_BELT_PARTS; j++)
5863 int element = belt_base_active_element[belt_nr] + j;
5864 int graphic_1 = el2img(element);
5865 int graphic_2 = el2panelimg(element);
5867 if (game.belt_dir[i] == MV_LEFT)
5869 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5870 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5874 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5875 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5880 SCAN_PLAYFIELD(x, y)
5882 int element = Feld[x][y];
5884 for (i = 0; i < NUM_BELTS; i++)
5886 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
5888 int e_belt_nr = getBeltNrFromBeltElement(element);
5891 if (e_belt_nr == belt_nr)
5893 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
5895 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
5902 static void ToggleBeltSwitch(int x, int y)
5904 static int belt_base_element[4] =
5906 EL_CONVEYOR_BELT_1_LEFT,
5907 EL_CONVEYOR_BELT_2_LEFT,
5908 EL_CONVEYOR_BELT_3_LEFT,
5909 EL_CONVEYOR_BELT_4_LEFT
5911 static int belt_base_active_element[4] =
5913 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5914 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5915 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5916 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5918 static int belt_base_switch_element[4] =
5920 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
5921 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
5922 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
5923 EL_CONVEYOR_BELT_4_SWITCH_LEFT
5925 static int belt_move_dir[4] =
5933 int element = Feld[x][y];
5934 int belt_nr = getBeltNrFromBeltSwitchElement(element);
5935 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
5936 int belt_dir = belt_move_dir[belt_dir_nr];
5939 if (!IS_BELT_SWITCH(element))
5942 game.belt_dir_nr[belt_nr] = belt_dir_nr;
5943 game.belt_dir[belt_nr] = belt_dir;
5945 if (belt_dir_nr == 3)
5948 // set frame order for belt animation graphic according to belt direction
5949 for (i = 0; i < NUM_BELT_PARTS; i++)
5951 int element = belt_base_active_element[belt_nr] + i;
5952 int graphic_1 = el2img(element);
5953 int graphic_2 = el2panelimg(element);
5955 if (belt_dir == MV_LEFT)
5957 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5958 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5962 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5963 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5967 SCAN_PLAYFIELD(xx, yy)
5969 int element = Feld[xx][yy];
5971 if (IS_BELT_SWITCH(element))
5973 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
5975 if (e_belt_nr == belt_nr)
5977 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
5978 TEST_DrawLevelField(xx, yy);
5981 else if (IS_BELT(element) && belt_dir != MV_NONE)
5983 int e_belt_nr = getBeltNrFromBeltElement(element);
5985 if (e_belt_nr == belt_nr)
5987 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
5989 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
5990 TEST_DrawLevelField(xx, yy);
5993 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
5995 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
5997 if (e_belt_nr == belt_nr)
5999 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6001 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6002 TEST_DrawLevelField(xx, yy);
6008 static void ToggleSwitchgateSwitch(int x, int y)
6012 game.switchgate_pos = !game.switchgate_pos;
6014 SCAN_PLAYFIELD(xx, yy)
6016 int element = Feld[xx][yy];
6018 if (element == EL_SWITCHGATE_SWITCH_UP)
6020 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6021 TEST_DrawLevelField(xx, yy);
6023 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6025 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6026 TEST_DrawLevelField(xx, yy);
6028 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6030 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6031 TEST_DrawLevelField(xx, yy);
6033 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6035 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6036 TEST_DrawLevelField(xx, yy);
6038 else if (element == EL_SWITCHGATE_OPEN ||
6039 element == EL_SWITCHGATE_OPENING)
6041 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6043 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6045 else if (element == EL_SWITCHGATE_CLOSED ||
6046 element == EL_SWITCHGATE_CLOSING)
6048 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6050 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6055 static int getInvisibleActiveFromInvisibleElement(int element)
6057 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6058 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6059 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6063 static int getInvisibleFromInvisibleActiveElement(int element)
6065 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6066 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6067 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6071 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6075 SCAN_PLAYFIELD(x, y)
6077 int element = Feld[x][y];
6079 if (element == EL_LIGHT_SWITCH &&
6080 game.light_time_left > 0)
6082 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6083 TEST_DrawLevelField(x, y);
6085 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6086 game.light_time_left == 0)
6088 Feld[x][y] = EL_LIGHT_SWITCH;
6089 TEST_DrawLevelField(x, y);
6091 else if (element == EL_EMC_DRIPPER &&
6092 game.light_time_left > 0)
6094 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6095 TEST_DrawLevelField(x, y);
6097 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6098 game.light_time_left == 0)
6100 Feld[x][y] = EL_EMC_DRIPPER;
6101 TEST_DrawLevelField(x, y);
6103 else if (element == EL_INVISIBLE_STEELWALL ||
6104 element == EL_INVISIBLE_WALL ||
6105 element == EL_INVISIBLE_SAND)
6107 if (game.light_time_left > 0)
6108 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6110 TEST_DrawLevelField(x, y);
6112 // uncrumble neighbour fields, if needed
6113 if (element == EL_INVISIBLE_SAND)
6114 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6116 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6117 element == EL_INVISIBLE_WALL_ACTIVE ||
6118 element == EL_INVISIBLE_SAND_ACTIVE)
6120 if (game.light_time_left == 0)
6121 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6123 TEST_DrawLevelField(x, y);
6125 // re-crumble neighbour fields, if needed
6126 if (element == EL_INVISIBLE_SAND)
6127 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6132 static void RedrawAllInvisibleElementsForLenses(void)
6136 SCAN_PLAYFIELD(x, y)
6138 int element = Feld[x][y];
6140 if (element == EL_EMC_DRIPPER &&
6141 game.lenses_time_left > 0)
6143 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6144 TEST_DrawLevelField(x, y);
6146 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6147 game.lenses_time_left == 0)
6149 Feld[x][y] = EL_EMC_DRIPPER;
6150 TEST_DrawLevelField(x, y);
6152 else if (element == EL_INVISIBLE_STEELWALL ||
6153 element == EL_INVISIBLE_WALL ||
6154 element == EL_INVISIBLE_SAND)
6156 if (game.lenses_time_left > 0)
6157 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6159 TEST_DrawLevelField(x, y);
6161 // uncrumble neighbour fields, if needed
6162 if (element == EL_INVISIBLE_SAND)
6163 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6165 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6166 element == EL_INVISIBLE_WALL_ACTIVE ||
6167 element == EL_INVISIBLE_SAND_ACTIVE)
6169 if (game.lenses_time_left == 0)
6170 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6172 TEST_DrawLevelField(x, y);
6174 // re-crumble neighbour fields, if needed
6175 if (element == EL_INVISIBLE_SAND)
6176 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6181 static void RedrawAllInvisibleElementsForMagnifier(void)
6185 SCAN_PLAYFIELD(x, y)
6187 int element = Feld[x][y];
6189 if (element == EL_EMC_FAKE_GRASS &&
6190 game.magnify_time_left > 0)
6192 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6193 TEST_DrawLevelField(x, y);
6195 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6196 game.magnify_time_left == 0)
6198 Feld[x][y] = EL_EMC_FAKE_GRASS;
6199 TEST_DrawLevelField(x, y);
6201 else if (IS_GATE_GRAY(element) &&
6202 game.magnify_time_left > 0)
6204 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6205 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6206 IS_EM_GATE_GRAY(element) ?
6207 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6208 IS_EMC_GATE_GRAY(element) ?
6209 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6210 IS_DC_GATE_GRAY(element) ?
6211 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6213 TEST_DrawLevelField(x, y);
6215 else if (IS_GATE_GRAY_ACTIVE(element) &&
6216 game.magnify_time_left == 0)
6218 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6219 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6220 IS_EM_GATE_GRAY_ACTIVE(element) ?
6221 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6222 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6223 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6224 IS_DC_GATE_GRAY_ACTIVE(element) ?
6225 EL_DC_GATE_WHITE_GRAY :
6227 TEST_DrawLevelField(x, y);
6232 static void ToggleLightSwitch(int x, int y)
6234 int element = Feld[x][y];
6236 game.light_time_left =
6237 (element == EL_LIGHT_SWITCH ?
6238 level.time_light * FRAMES_PER_SECOND : 0);
6240 RedrawAllLightSwitchesAndInvisibleElements();
6243 static void ActivateTimegateSwitch(int x, int y)
6247 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6249 SCAN_PLAYFIELD(xx, yy)
6251 int element = Feld[xx][yy];
6253 if (element == EL_TIMEGATE_CLOSED ||
6254 element == EL_TIMEGATE_CLOSING)
6256 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6257 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6261 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6263 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6264 TEST_DrawLevelField(xx, yy);
6270 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6271 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6274 static void Impact(int x, int y)
6276 boolean last_line = (y == lev_fieldy - 1);
6277 boolean object_hit = FALSE;
6278 boolean impact = (last_line || object_hit);
6279 int element = Feld[x][y];
6280 int smashed = EL_STEELWALL;
6282 if (!last_line) // check if element below was hit
6284 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6287 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6288 MovDir[x][y + 1] != MV_DOWN ||
6289 MovPos[x][y + 1] <= TILEY / 2));
6291 // do not smash moving elements that left the smashed field in time
6292 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6293 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6296 #if USE_QUICKSAND_IMPACT_BUGFIX
6297 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6299 RemoveMovingField(x, y + 1);
6300 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6301 Feld[x][y + 2] = EL_ROCK;
6302 TEST_DrawLevelField(x, y + 2);
6307 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6309 RemoveMovingField(x, y + 1);
6310 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6311 Feld[x][y + 2] = EL_ROCK;
6312 TEST_DrawLevelField(x, y + 2);
6319 smashed = MovingOrBlocked2Element(x, y + 1);
6321 impact = (last_line || object_hit);
6324 if (!last_line && smashed == EL_ACID) // element falls into acid
6326 SplashAcid(x, y + 1);
6330 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6331 // only reset graphic animation if graphic really changes after impact
6333 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6335 ResetGfxAnimation(x, y);
6336 TEST_DrawLevelField(x, y);
6339 if (impact && CAN_EXPLODE_IMPACT(element))
6344 else if (impact && element == EL_PEARL &&
6345 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6347 ResetGfxAnimation(x, y);
6349 Feld[x][y] = EL_PEARL_BREAKING;
6350 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6353 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6355 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6360 if (impact && element == EL_AMOEBA_DROP)
6362 if (object_hit && IS_PLAYER(x, y + 1))
6363 KillPlayerUnlessEnemyProtected(x, y + 1);
6364 else if (object_hit && smashed == EL_PENGUIN)
6368 Feld[x][y] = EL_AMOEBA_GROWING;
6369 Store[x][y] = EL_AMOEBA_WET;
6371 ResetRandomAnimationValue(x, y);
6376 if (object_hit) // check which object was hit
6378 if ((CAN_PASS_MAGIC_WALL(element) &&
6379 (smashed == EL_MAGIC_WALL ||
6380 smashed == EL_BD_MAGIC_WALL)) ||
6381 (CAN_PASS_DC_MAGIC_WALL(element) &&
6382 smashed == EL_DC_MAGIC_WALL))
6385 int activated_magic_wall =
6386 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6387 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6388 EL_DC_MAGIC_WALL_ACTIVE);
6390 // activate magic wall / mill
6391 SCAN_PLAYFIELD(xx, yy)
6393 if (Feld[xx][yy] == smashed)
6394 Feld[xx][yy] = activated_magic_wall;
6397 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6398 game.magic_wall_active = TRUE;
6400 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6401 SND_MAGIC_WALL_ACTIVATING :
6402 smashed == EL_BD_MAGIC_WALL ?
6403 SND_BD_MAGIC_WALL_ACTIVATING :
6404 SND_DC_MAGIC_WALL_ACTIVATING));
6407 if (IS_PLAYER(x, y + 1))
6409 if (CAN_SMASH_PLAYER(element))
6411 KillPlayerUnlessEnemyProtected(x, y + 1);
6415 else if (smashed == EL_PENGUIN)
6417 if (CAN_SMASH_PLAYER(element))
6423 else if (element == EL_BD_DIAMOND)
6425 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6431 else if (((element == EL_SP_INFOTRON ||
6432 element == EL_SP_ZONK) &&
6433 (smashed == EL_SP_SNIKSNAK ||
6434 smashed == EL_SP_ELECTRON ||
6435 smashed == EL_SP_DISK_ORANGE)) ||
6436 (element == EL_SP_INFOTRON &&
6437 smashed == EL_SP_DISK_YELLOW))
6442 else if (CAN_SMASH_EVERYTHING(element))
6444 if (IS_CLASSIC_ENEMY(smashed) ||
6445 CAN_EXPLODE_SMASHED(smashed))
6450 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6452 if (smashed == EL_LAMP ||
6453 smashed == EL_LAMP_ACTIVE)
6458 else if (smashed == EL_NUT)
6460 Feld[x][y + 1] = EL_NUT_BREAKING;
6461 PlayLevelSound(x, y, SND_NUT_BREAKING);
6462 RaiseScoreElement(EL_NUT);
6465 else if (smashed == EL_PEARL)
6467 ResetGfxAnimation(x, y);
6469 Feld[x][y + 1] = EL_PEARL_BREAKING;
6470 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6473 else if (smashed == EL_DIAMOND)
6475 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6476 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6479 else if (IS_BELT_SWITCH(smashed))
6481 ToggleBeltSwitch(x, y + 1);
6483 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6484 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6485 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6486 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6488 ToggleSwitchgateSwitch(x, y + 1);
6490 else if (smashed == EL_LIGHT_SWITCH ||
6491 smashed == EL_LIGHT_SWITCH_ACTIVE)
6493 ToggleLightSwitch(x, y + 1);
6497 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6499 CheckElementChangeBySide(x, y + 1, smashed, element,
6500 CE_SWITCHED, CH_SIDE_TOP);
6501 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6507 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6512 // play sound of magic wall / mill
6514 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6515 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6516 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6518 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6519 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6520 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6521 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6522 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6523 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6528 // play sound of object that hits the ground
6529 if (last_line || object_hit)
6530 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6533 static void TurnRoundExt(int x, int y)
6545 { 0, 0 }, { 0, 0 }, { 0, 0 },
6550 int left, right, back;
6554 { MV_DOWN, MV_UP, MV_RIGHT },
6555 { MV_UP, MV_DOWN, MV_LEFT },
6557 { MV_LEFT, MV_RIGHT, MV_DOWN },
6561 { MV_RIGHT, MV_LEFT, MV_UP }
6564 int element = Feld[x][y];
6565 int move_pattern = element_info[element].move_pattern;
6567 int old_move_dir = MovDir[x][y];
6568 int left_dir = turn[old_move_dir].left;
6569 int right_dir = turn[old_move_dir].right;
6570 int back_dir = turn[old_move_dir].back;
6572 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6573 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6574 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6575 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6577 int left_x = x + left_dx, left_y = y + left_dy;
6578 int right_x = x + right_dx, right_y = y + right_dy;
6579 int move_x = x + move_dx, move_y = y + move_dy;
6583 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6585 TestIfBadThingTouchesOtherBadThing(x, y);
6587 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6588 MovDir[x][y] = right_dir;
6589 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6590 MovDir[x][y] = left_dir;
6592 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6594 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6597 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6599 TestIfBadThingTouchesOtherBadThing(x, y);
6601 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6602 MovDir[x][y] = left_dir;
6603 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6604 MovDir[x][y] = right_dir;
6606 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6608 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6611 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6613 TestIfBadThingTouchesOtherBadThing(x, y);
6615 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6616 MovDir[x][y] = left_dir;
6617 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6618 MovDir[x][y] = right_dir;
6620 if (MovDir[x][y] != old_move_dir)
6623 else if (element == EL_YAMYAM)
6625 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6626 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6628 if (can_turn_left && can_turn_right)
6629 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6630 else if (can_turn_left)
6631 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6632 else if (can_turn_right)
6633 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6635 MovDir[x][y] = back_dir;
6637 MovDelay[x][y] = 16 + 16 * RND(3);
6639 else if (element == EL_DARK_YAMYAM)
6641 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6643 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6646 if (can_turn_left && can_turn_right)
6647 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6648 else if (can_turn_left)
6649 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6650 else if (can_turn_right)
6651 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6653 MovDir[x][y] = back_dir;
6655 MovDelay[x][y] = 16 + 16 * RND(3);
6657 else if (element == EL_PACMAN)
6659 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6660 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6662 if (can_turn_left && can_turn_right)
6663 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6664 else if (can_turn_left)
6665 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6666 else if (can_turn_right)
6667 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6669 MovDir[x][y] = back_dir;
6671 MovDelay[x][y] = 6 + RND(40);
6673 else if (element == EL_PIG)
6675 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6676 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6677 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6678 boolean should_turn_left, should_turn_right, should_move_on;
6680 int rnd = RND(rnd_value);
6682 should_turn_left = (can_turn_left &&
6684 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6685 y + back_dy + left_dy)));
6686 should_turn_right = (can_turn_right &&
6688 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6689 y + back_dy + right_dy)));
6690 should_move_on = (can_move_on &&
6693 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6694 y + move_dy + left_dy) ||
6695 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6696 y + move_dy + right_dy)));
6698 if (should_turn_left || should_turn_right || should_move_on)
6700 if (should_turn_left && should_turn_right && should_move_on)
6701 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6702 rnd < 2 * rnd_value / 3 ? right_dir :
6704 else if (should_turn_left && should_turn_right)
6705 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6706 else if (should_turn_left && should_move_on)
6707 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6708 else if (should_turn_right && should_move_on)
6709 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6710 else if (should_turn_left)
6711 MovDir[x][y] = left_dir;
6712 else if (should_turn_right)
6713 MovDir[x][y] = right_dir;
6714 else if (should_move_on)
6715 MovDir[x][y] = old_move_dir;
6717 else if (can_move_on && rnd > rnd_value / 8)
6718 MovDir[x][y] = old_move_dir;
6719 else if (can_turn_left && can_turn_right)
6720 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6721 else if (can_turn_left && rnd > rnd_value / 8)
6722 MovDir[x][y] = left_dir;
6723 else if (can_turn_right && rnd > rnd_value/8)
6724 MovDir[x][y] = right_dir;
6726 MovDir[x][y] = back_dir;
6728 xx = x + move_xy[MovDir[x][y]].dx;
6729 yy = y + move_xy[MovDir[x][y]].dy;
6731 if (!IN_LEV_FIELD(xx, yy) ||
6732 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6733 MovDir[x][y] = old_move_dir;
6737 else if (element == EL_DRAGON)
6739 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6740 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6741 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6743 int rnd = RND(rnd_value);
6745 if (can_move_on && rnd > rnd_value / 8)
6746 MovDir[x][y] = old_move_dir;
6747 else if (can_turn_left && can_turn_right)
6748 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6749 else if (can_turn_left && rnd > rnd_value / 8)
6750 MovDir[x][y] = left_dir;
6751 else if (can_turn_right && rnd > rnd_value / 8)
6752 MovDir[x][y] = right_dir;
6754 MovDir[x][y] = back_dir;
6756 xx = x + move_xy[MovDir[x][y]].dx;
6757 yy = y + move_xy[MovDir[x][y]].dy;
6759 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6760 MovDir[x][y] = old_move_dir;
6764 else if (element == EL_MOLE)
6766 boolean can_move_on =
6767 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6768 IS_AMOEBOID(Feld[move_x][move_y]) ||
6769 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6772 boolean can_turn_left =
6773 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6774 IS_AMOEBOID(Feld[left_x][left_y])));
6776 boolean can_turn_right =
6777 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6778 IS_AMOEBOID(Feld[right_x][right_y])));
6780 if (can_turn_left && can_turn_right)
6781 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6782 else if (can_turn_left)
6783 MovDir[x][y] = left_dir;
6785 MovDir[x][y] = right_dir;
6788 if (MovDir[x][y] != old_move_dir)
6791 else if (element == EL_BALLOON)
6793 MovDir[x][y] = game.wind_direction;
6796 else if (element == EL_SPRING)
6798 if (MovDir[x][y] & MV_HORIZONTAL)
6800 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6801 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6803 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6804 ResetGfxAnimation(move_x, move_y);
6805 TEST_DrawLevelField(move_x, move_y);
6807 MovDir[x][y] = back_dir;
6809 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6810 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6811 MovDir[x][y] = MV_NONE;
6816 else if (element == EL_ROBOT ||
6817 element == EL_SATELLITE ||
6818 element == EL_PENGUIN ||
6819 element == EL_EMC_ANDROID)
6821 int attr_x = -1, attr_y = -1;
6823 if (game.all_players_gone)
6825 attr_x = game.exit_x;
6826 attr_y = game.exit_y;
6832 for (i = 0; i < MAX_PLAYERS; i++)
6834 struct PlayerInfo *player = &stored_player[i];
6835 int jx = player->jx, jy = player->jy;
6837 if (!player->active)
6841 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6849 if (element == EL_ROBOT && ZX >= 0 && ZY >= 0 &&
6850 (Feld[ZX][ZY] == EL_ROBOT_WHEEL_ACTIVE ||
6851 game.engine_version < VERSION_IDENT(3,1,0,0)))
6857 if (element == EL_PENGUIN)
6860 static int xy[4][2] =
6868 for (i = 0; i < NUM_DIRECTIONS; i++)
6870 int ex = x + xy[i][0];
6871 int ey = y + xy[i][1];
6873 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
6874 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
6875 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
6876 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
6885 MovDir[x][y] = MV_NONE;
6887 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
6888 else if (attr_x > x)
6889 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
6891 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
6892 else if (attr_y > y)
6893 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
6895 if (element == EL_ROBOT)
6899 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6900 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
6901 Moving2Blocked(x, y, &newx, &newy);
6903 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
6904 MovDelay[x][y] = 8 + 8 * !RND(3);
6906 MovDelay[x][y] = 16;
6908 else if (element == EL_PENGUIN)
6914 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6916 boolean first_horiz = RND(2);
6917 int new_move_dir = MovDir[x][y];
6920 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6921 Moving2Blocked(x, y, &newx, &newy);
6923 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6927 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6928 Moving2Blocked(x, y, &newx, &newy);
6930 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6933 MovDir[x][y] = old_move_dir;
6937 else if (element == EL_SATELLITE)
6943 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6945 boolean first_horiz = RND(2);
6946 int new_move_dir = MovDir[x][y];
6949 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6950 Moving2Blocked(x, y, &newx, &newy);
6952 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6956 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6957 Moving2Blocked(x, y, &newx, &newy);
6959 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6962 MovDir[x][y] = old_move_dir;
6966 else if (element == EL_EMC_ANDROID)
6968 static int check_pos[16] =
6970 -1, // 0 => (invalid)
6973 -1, // 3 => (invalid)
6975 0, // 5 => MV_LEFT | MV_UP
6976 2, // 6 => MV_RIGHT | MV_UP
6977 -1, // 7 => (invalid)
6979 6, // 9 => MV_LEFT | MV_DOWN
6980 4, // 10 => MV_RIGHT | MV_DOWN
6981 -1, // 11 => (invalid)
6982 -1, // 12 => (invalid)
6983 -1, // 13 => (invalid)
6984 -1, // 14 => (invalid)
6985 -1, // 15 => (invalid)
6993 { -1, -1, MV_LEFT | MV_UP },
6995 { +1, -1, MV_RIGHT | MV_UP },
6996 { +1, 0, MV_RIGHT },
6997 { +1, +1, MV_RIGHT | MV_DOWN },
6999 { -1, +1, MV_LEFT | MV_DOWN },
7002 int start_pos, check_order;
7003 boolean can_clone = FALSE;
7006 // check if there is any free field around current position
7007 for (i = 0; i < 8; i++)
7009 int newx = x + check_xy[i].dx;
7010 int newy = y + check_xy[i].dy;
7012 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7020 if (can_clone) // randomly find an element to clone
7024 start_pos = check_pos[RND(8)];
7025 check_order = (RND(2) ? -1 : +1);
7027 for (i = 0; i < 8; i++)
7029 int pos_raw = start_pos + i * check_order;
7030 int pos = (pos_raw + 8) % 8;
7031 int newx = x + check_xy[pos].dx;
7032 int newy = y + check_xy[pos].dy;
7034 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7036 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7037 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7039 Store[x][y] = Feld[newx][newy];
7048 if (can_clone) // randomly find a direction to move
7052 start_pos = check_pos[RND(8)];
7053 check_order = (RND(2) ? -1 : +1);
7055 for (i = 0; i < 8; i++)
7057 int pos_raw = start_pos + i * check_order;
7058 int pos = (pos_raw + 8) % 8;
7059 int newx = x + check_xy[pos].dx;
7060 int newy = y + check_xy[pos].dy;
7061 int new_move_dir = check_xy[pos].dir;
7063 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7065 MovDir[x][y] = new_move_dir;
7066 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7075 if (can_clone) // cloning and moving successful
7078 // cannot clone -- try to move towards player
7080 start_pos = check_pos[MovDir[x][y] & 0x0f];
7081 check_order = (RND(2) ? -1 : +1);
7083 for (i = 0; i < 3; i++)
7085 // first check start_pos, then previous/next or (next/previous) pos
7086 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7087 int pos = (pos_raw + 8) % 8;
7088 int newx = x + check_xy[pos].dx;
7089 int newy = y + check_xy[pos].dy;
7090 int new_move_dir = check_xy[pos].dir;
7092 if (IS_PLAYER(newx, newy))
7095 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7097 MovDir[x][y] = new_move_dir;
7098 MovDelay[x][y] = level.android_move_time * 8 + 1;
7105 else if (move_pattern == MV_TURNING_LEFT ||
7106 move_pattern == MV_TURNING_RIGHT ||
7107 move_pattern == MV_TURNING_LEFT_RIGHT ||
7108 move_pattern == MV_TURNING_RIGHT_LEFT ||
7109 move_pattern == MV_TURNING_RANDOM ||
7110 move_pattern == MV_ALL_DIRECTIONS)
7112 boolean can_turn_left =
7113 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7114 boolean can_turn_right =
7115 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7117 if (element_info[element].move_stepsize == 0) // "not moving"
7120 if (move_pattern == MV_TURNING_LEFT)
7121 MovDir[x][y] = left_dir;
7122 else if (move_pattern == MV_TURNING_RIGHT)
7123 MovDir[x][y] = right_dir;
7124 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7125 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7126 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7127 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7128 else if (move_pattern == MV_TURNING_RANDOM)
7129 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7130 can_turn_right && !can_turn_left ? right_dir :
7131 RND(2) ? left_dir : right_dir);
7132 else if (can_turn_left && can_turn_right)
7133 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7134 else if (can_turn_left)
7135 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7136 else if (can_turn_right)
7137 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7139 MovDir[x][y] = back_dir;
7141 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7143 else if (move_pattern == MV_HORIZONTAL ||
7144 move_pattern == MV_VERTICAL)
7146 if (move_pattern & old_move_dir)
7147 MovDir[x][y] = back_dir;
7148 else if (move_pattern == MV_HORIZONTAL)
7149 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7150 else if (move_pattern == MV_VERTICAL)
7151 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7153 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7155 else if (move_pattern & MV_ANY_DIRECTION)
7157 MovDir[x][y] = move_pattern;
7158 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7160 else if (move_pattern & MV_WIND_DIRECTION)
7162 MovDir[x][y] = game.wind_direction;
7163 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7165 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7167 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7168 MovDir[x][y] = left_dir;
7169 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7170 MovDir[x][y] = right_dir;
7172 if (MovDir[x][y] != old_move_dir)
7173 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7175 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7177 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7178 MovDir[x][y] = right_dir;
7179 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7180 MovDir[x][y] = left_dir;
7182 if (MovDir[x][y] != old_move_dir)
7183 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7185 else if (move_pattern == MV_TOWARDS_PLAYER ||
7186 move_pattern == MV_AWAY_FROM_PLAYER)
7188 int attr_x = -1, attr_y = -1;
7190 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7192 if (game.all_players_gone)
7194 attr_x = game.exit_x;
7195 attr_y = game.exit_y;
7201 for (i = 0; i < MAX_PLAYERS; i++)
7203 struct PlayerInfo *player = &stored_player[i];
7204 int jx = player->jx, jy = player->jy;
7206 if (!player->active)
7210 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7218 MovDir[x][y] = MV_NONE;
7220 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7221 else if (attr_x > x)
7222 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7224 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7225 else if (attr_y > y)
7226 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7228 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7230 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7232 boolean first_horiz = RND(2);
7233 int new_move_dir = MovDir[x][y];
7235 if (element_info[element].move_stepsize == 0) // "not moving"
7237 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7238 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7244 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7245 Moving2Blocked(x, y, &newx, &newy);
7247 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7251 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7252 Moving2Blocked(x, y, &newx, &newy);
7254 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7257 MovDir[x][y] = old_move_dir;
7260 else if (move_pattern == MV_WHEN_PUSHED ||
7261 move_pattern == MV_WHEN_DROPPED)
7263 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7264 MovDir[x][y] = MV_NONE;
7268 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7270 static int test_xy[7][2] =
7280 static int test_dir[7] =
7290 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7291 int move_preference = -1000000; // start with very low preference
7292 int new_move_dir = MV_NONE;
7293 int start_test = RND(4);
7296 for (i = 0; i < NUM_DIRECTIONS; i++)
7298 int move_dir = test_dir[start_test + i];
7299 int move_dir_preference;
7301 xx = x + test_xy[start_test + i][0];
7302 yy = y + test_xy[start_test + i][1];
7304 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7305 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7307 new_move_dir = move_dir;
7312 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7315 move_dir_preference = -1 * RunnerVisit[xx][yy];
7316 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7317 move_dir_preference = PlayerVisit[xx][yy];
7319 if (move_dir_preference > move_preference)
7321 // prefer field that has not been visited for the longest time
7322 move_preference = move_dir_preference;
7323 new_move_dir = move_dir;
7325 else if (move_dir_preference == move_preference &&
7326 move_dir == old_move_dir)
7328 // prefer last direction when all directions are preferred equally
7329 move_preference = move_dir_preference;
7330 new_move_dir = move_dir;
7334 MovDir[x][y] = new_move_dir;
7335 if (old_move_dir != new_move_dir)
7336 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7340 static void TurnRound(int x, int y)
7342 int direction = MovDir[x][y];
7346 GfxDir[x][y] = MovDir[x][y];
7348 if (direction != MovDir[x][y])
7352 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7354 ResetGfxFrame(x, y);
7357 static boolean JustBeingPushed(int x, int y)
7361 for (i = 0; i < MAX_PLAYERS; i++)
7363 struct PlayerInfo *player = &stored_player[i];
7365 if (player->active && player->is_pushing && player->MovPos)
7367 int next_jx = player->jx + (player->jx - player->last_jx);
7368 int next_jy = player->jy + (player->jy - player->last_jy);
7370 if (x == next_jx && y == next_jy)
7378 static void StartMoving(int x, int y)
7380 boolean started_moving = FALSE; // some elements can fall _and_ move
7381 int element = Feld[x][y];
7386 if (MovDelay[x][y] == 0)
7387 GfxAction[x][y] = ACTION_DEFAULT;
7389 if (CAN_FALL(element) && y < lev_fieldy - 1)
7391 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7392 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7393 if (JustBeingPushed(x, y))
7396 if (element == EL_QUICKSAND_FULL)
7398 if (IS_FREE(x, y + 1))
7400 InitMovingField(x, y, MV_DOWN);
7401 started_moving = TRUE;
7403 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7404 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7405 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7406 Store[x][y] = EL_ROCK;
7408 Store[x][y] = EL_ROCK;
7411 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7413 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7415 if (!MovDelay[x][y])
7417 MovDelay[x][y] = TILEY + 1;
7419 ResetGfxAnimation(x, y);
7420 ResetGfxAnimation(x, y + 1);
7425 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7426 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7433 Feld[x][y] = EL_QUICKSAND_EMPTY;
7434 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7435 Store[x][y + 1] = Store[x][y];
7438 PlayLevelSoundAction(x, y, ACTION_FILLING);
7440 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7442 if (!MovDelay[x][y])
7444 MovDelay[x][y] = TILEY + 1;
7446 ResetGfxAnimation(x, y);
7447 ResetGfxAnimation(x, y + 1);
7452 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7453 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7460 Feld[x][y] = EL_QUICKSAND_EMPTY;
7461 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7462 Store[x][y + 1] = Store[x][y];
7465 PlayLevelSoundAction(x, y, ACTION_FILLING);
7468 else if (element == EL_QUICKSAND_FAST_FULL)
7470 if (IS_FREE(x, y + 1))
7472 InitMovingField(x, y, MV_DOWN);
7473 started_moving = TRUE;
7475 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7476 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7477 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7478 Store[x][y] = EL_ROCK;
7480 Store[x][y] = EL_ROCK;
7483 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7485 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7487 if (!MovDelay[x][y])
7489 MovDelay[x][y] = TILEY + 1;
7491 ResetGfxAnimation(x, y);
7492 ResetGfxAnimation(x, y + 1);
7497 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7498 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7505 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7506 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7507 Store[x][y + 1] = Store[x][y];
7510 PlayLevelSoundAction(x, y, ACTION_FILLING);
7512 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7514 if (!MovDelay[x][y])
7516 MovDelay[x][y] = TILEY + 1;
7518 ResetGfxAnimation(x, y);
7519 ResetGfxAnimation(x, y + 1);
7524 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7525 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7532 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7533 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7534 Store[x][y + 1] = Store[x][y];
7537 PlayLevelSoundAction(x, y, ACTION_FILLING);
7540 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7541 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7543 InitMovingField(x, y, MV_DOWN);
7544 started_moving = TRUE;
7546 Feld[x][y] = EL_QUICKSAND_FILLING;
7547 Store[x][y] = element;
7549 PlayLevelSoundAction(x, y, ACTION_FILLING);
7551 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7552 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7554 InitMovingField(x, y, MV_DOWN);
7555 started_moving = TRUE;
7557 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7558 Store[x][y] = element;
7560 PlayLevelSoundAction(x, y, ACTION_FILLING);
7562 else if (element == EL_MAGIC_WALL_FULL)
7564 if (IS_FREE(x, y + 1))
7566 InitMovingField(x, y, MV_DOWN);
7567 started_moving = TRUE;
7569 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7570 Store[x][y] = EL_CHANGED(Store[x][y]);
7572 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7574 if (!MovDelay[x][y])
7575 MovDelay[x][y] = TILEY / 4 + 1;
7584 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7585 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7586 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7590 else if (element == EL_BD_MAGIC_WALL_FULL)
7592 if (IS_FREE(x, y + 1))
7594 InitMovingField(x, y, MV_DOWN);
7595 started_moving = TRUE;
7597 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7598 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7600 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7602 if (!MovDelay[x][y])
7603 MovDelay[x][y] = TILEY / 4 + 1;
7612 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7613 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7614 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7618 else if (element == EL_DC_MAGIC_WALL_FULL)
7620 if (IS_FREE(x, y + 1))
7622 InitMovingField(x, y, MV_DOWN);
7623 started_moving = TRUE;
7625 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7626 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7628 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7630 if (!MovDelay[x][y])
7631 MovDelay[x][y] = TILEY / 4 + 1;
7640 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7641 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7642 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7646 else if ((CAN_PASS_MAGIC_WALL(element) &&
7647 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7648 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7649 (CAN_PASS_DC_MAGIC_WALL(element) &&
7650 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7653 InitMovingField(x, y, MV_DOWN);
7654 started_moving = TRUE;
7657 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7658 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7659 EL_DC_MAGIC_WALL_FILLING);
7660 Store[x][y] = element;
7662 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7664 SplashAcid(x, y + 1);
7666 InitMovingField(x, y, MV_DOWN);
7667 started_moving = TRUE;
7669 Store[x][y] = EL_ACID;
7672 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7673 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7674 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7675 CAN_FALL(element) && WasJustFalling[x][y] &&
7676 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7678 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7679 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7680 (Feld[x][y + 1] == EL_BLOCKED)))
7682 /* this is needed for a special case not covered by calling "Impact()"
7683 from "ContinueMoving()": if an element moves to a tile directly below
7684 another element which was just falling on that tile (which was empty
7685 in the previous frame), the falling element above would just stop
7686 instead of smashing the element below (in previous version, the above
7687 element was just checked for "moving" instead of "falling", resulting
7688 in incorrect smashes caused by horizontal movement of the above
7689 element; also, the case of the player being the element to smash was
7690 simply not covered here... :-/ ) */
7692 CheckCollision[x][y] = 0;
7693 CheckImpact[x][y] = 0;
7697 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7699 if (MovDir[x][y] == MV_NONE)
7701 InitMovingField(x, y, MV_DOWN);
7702 started_moving = TRUE;
7705 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7707 if (WasJustFalling[x][y]) // prevent animation from being restarted
7708 MovDir[x][y] = MV_DOWN;
7710 InitMovingField(x, y, MV_DOWN);
7711 started_moving = TRUE;
7713 else if (element == EL_AMOEBA_DROP)
7715 Feld[x][y] = EL_AMOEBA_GROWING;
7716 Store[x][y] = EL_AMOEBA_WET;
7718 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7719 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7720 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7721 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7723 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7724 (IS_FREE(x - 1, y + 1) ||
7725 Feld[x - 1][y + 1] == EL_ACID));
7726 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7727 (IS_FREE(x + 1, y + 1) ||
7728 Feld[x + 1][y + 1] == EL_ACID));
7729 boolean can_fall_any = (can_fall_left || can_fall_right);
7730 boolean can_fall_both = (can_fall_left && can_fall_right);
7731 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7733 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7735 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7736 can_fall_right = FALSE;
7737 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7738 can_fall_left = FALSE;
7739 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7740 can_fall_right = FALSE;
7741 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7742 can_fall_left = FALSE;
7744 can_fall_any = (can_fall_left || can_fall_right);
7745 can_fall_both = FALSE;
7750 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7751 can_fall_right = FALSE; // slip down on left side
7753 can_fall_left = !(can_fall_right = RND(2));
7755 can_fall_both = FALSE;
7760 // if not determined otherwise, prefer left side for slipping down
7761 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7762 started_moving = TRUE;
7765 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7767 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7768 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7769 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7770 int belt_dir = game.belt_dir[belt_nr];
7772 if ((belt_dir == MV_LEFT && left_is_free) ||
7773 (belt_dir == MV_RIGHT && right_is_free))
7775 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7777 InitMovingField(x, y, belt_dir);
7778 started_moving = TRUE;
7780 Pushed[x][y] = TRUE;
7781 Pushed[nextx][y] = TRUE;
7783 GfxAction[x][y] = ACTION_DEFAULT;
7787 MovDir[x][y] = 0; // if element was moving, stop it
7792 // not "else if" because of elements that can fall and move (EL_SPRING)
7793 if (CAN_MOVE(element) && !started_moving)
7795 int move_pattern = element_info[element].move_pattern;
7798 Moving2Blocked(x, y, &newx, &newy);
7800 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7803 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7804 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7806 WasJustMoving[x][y] = 0;
7807 CheckCollision[x][y] = 0;
7809 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7811 if (Feld[x][y] != element) // element has changed
7815 if (!MovDelay[x][y]) // start new movement phase
7817 // all objects that can change their move direction after each step
7818 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7820 if (element != EL_YAMYAM &&
7821 element != EL_DARK_YAMYAM &&
7822 element != EL_PACMAN &&
7823 !(move_pattern & MV_ANY_DIRECTION) &&
7824 move_pattern != MV_TURNING_LEFT &&
7825 move_pattern != MV_TURNING_RIGHT &&
7826 move_pattern != MV_TURNING_LEFT_RIGHT &&
7827 move_pattern != MV_TURNING_RIGHT_LEFT &&
7828 move_pattern != MV_TURNING_RANDOM)
7832 if (MovDelay[x][y] && (element == EL_BUG ||
7833 element == EL_SPACESHIP ||
7834 element == EL_SP_SNIKSNAK ||
7835 element == EL_SP_ELECTRON ||
7836 element == EL_MOLE))
7837 TEST_DrawLevelField(x, y);
7841 if (MovDelay[x][y]) // wait some time before next movement
7845 if (element == EL_ROBOT ||
7846 element == EL_YAMYAM ||
7847 element == EL_DARK_YAMYAM)
7849 DrawLevelElementAnimationIfNeeded(x, y, element);
7850 PlayLevelSoundAction(x, y, ACTION_WAITING);
7852 else if (element == EL_SP_ELECTRON)
7853 DrawLevelElementAnimationIfNeeded(x, y, element);
7854 else if (element == EL_DRAGON)
7857 int dir = MovDir[x][y];
7858 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
7859 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
7860 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
7861 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
7862 dir == MV_UP ? IMG_FLAMES_1_UP :
7863 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
7864 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
7866 GfxAction[x][y] = ACTION_ATTACKING;
7868 if (IS_PLAYER(x, y))
7869 DrawPlayerField(x, y);
7871 TEST_DrawLevelField(x, y);
7873 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
7875 for (i = 1; i <= 3; i++)
7877 int xx = x + i * dx;
7878 int yy = y + i * dy;
7879 int sx = SCREENX(xx);
7880 int sy = SCREENY(yy);
7881 int flame_graphic = graphic + (i - 1);
7883 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
7888 int flamed = MovingOrBlocked2Element(xx, yy);
7890 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
7893 RemoveMovingField(xx, yy);
7895 ChangeDelay[xx][yy] = 0;
7897 Feld[xx][yy] = EL_FLAMES;
7899 if (IN_SCR_FIELD(sx, sy))
7901 TEST_DrawLevelFieldCrumbled(xx, yy);
7902 DrawGraphic(sx, sy, flame_graphic, frame);
7907 if (Feld[xx][yy] == EL_FLAMES)
7908 Feld[xx][yy] = EL_EMPTY;
7909 TEST_DrawLevelField(xx, yy);
7914 if (MovDelay[x][y]) // element still has to wait some time
7916 PlayLevelSoundAction(x, y, ACTION_WAITING);
7922 // now make next step
7924 Moving2Blocked(x, y, &newx, &newy); // get next screen position
7926 if (DONT_COLLIDE_WITH(element) &&
7927 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
7928 !PLAYER_ENEMY_PROTECTED(newx, newy))
7930 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
7935 else if (CAN_MOVE_INTO_ACID(element) &&
7936 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
7937 !IS_MV_DIAGONAL(MovDir[x][y]) &&
7938 (MovDir[x][y] == MV_DOWN ||
7939 game.engine_version >= VERSION_IDENT(3,1,0,0)))
7941 SplashAcid(newx, newy);
7942 Store[x][y] = EL_ACID;
7944 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
7946 if (Feld[newx][newy] == EL_EXIT_OPEN ||
7947 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
7948 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
7949 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
7952 TEST_DrawLevelField(x, y);
7954 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
7955 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
7956 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
7958 game.friends_still_needed--;
7959 if (!game.friends_still_needed &&
7961 game.all_players_gone)
7966 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
7968 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
7969 TEST_DrawLevelField(newx, newy);
7971 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
7973 else if (!IS_FREE(newx, newy))
7975 GfxAction[x][y] = ACTION_WAITING;
7977 if (IS_PLAYER(x, y))
7978 DrawPlayerField(x, y);
7980 TEST_DrawLevelField(x, y);
7985 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
7987 if (IS_FOOD_PIG(Feld[newx][newy]))
7989 if (IS_MOVING(newx, newy))
7990 RemoveMovingField(newx, newy);
7993 Feld[newx][newy] = EL_EMPTY;
7994 TEST_DrawLevelField(newx, newy);
7997 PlayLevelSound(x, y, SND_PIG_DIGGING);
7999 else if (!IS_FREE(newx, newy))
8001 if (IS_PLAYER(x, y))
8002 DrawPlayerField(x, y);
8004 TEST_DrawLevelField(x, y);
8009 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8011 if (Store[x][y] != EL_EMPTY)
8013 boolean can_clone = FALSE;
8016 // check if element to clone is still there
8017 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8019 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8027 // cannot clone or target field not free anymore -- do not clone
8028 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8029 Store[x][y] = EL_EMPTY;
8032 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8034 if (IS_MV_DIAGONAL(MovDir[x][y]))
8036 int diagonal_move_dir = MovDir[x][y];
8037 int stored = Store[x][y];
8038 int change_delay = 8;
8041 // android is moving diagonally
8043 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8045 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8046 GfxElement[x][y] = EL_EMC_ANDROID;
8047 GfxAction[x][y] = ACTION_SHRINKING;
8048 GfxDir[x][y] = diagonal_move_dir;
8049 ChangeDelay[x][y] = change_delay;
8051 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8054 DrawLevelGraphicAnimation(x, y, graphic);
8055 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8057 if (Feld[newx][newy] == EL_ACID)
8059 SplashAcid(newx, newy);
8064 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8066 Store[newx][newy] = EL_EMC_ANDROID;
8067 GfxElement[newx][newy] = EL_EMC_ANDROID;
8068 GfxAction[newx][newy] = ACTION_GROWING;
8069 GfxDir[newx][newy] = diagonal_move_dir;
8070 ChangeDelay[newx][newy] = change_delay;
8072 graphic = el_act_dir2img(GfxElement[newx][newy],
8073 GfxAction[newx][newy], GfxDir[newx][newy]);
8075 DrawLevelGraphicAnimation(newx, newy, graphic);
8076 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8082 Feld[newx][newy] = EL_EMPTY;
8083 TEST_DrawLevelField(newx, newy);
8085 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8088 else if (!IS_FREE(newx, newy))
8093 else if (IS_CUSTOM_ELEMENT(element) &&
8094 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8096 if (!DigFieldByCE(newx, newy, element))
8099 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8101 RunnerVisit[x][y] = FrameCounter;
8102 PlayerVisit[x][y] /= 8; // expire player visit path
8105 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8107 if (!IS_FREE(newx, newy))
8109 if (IS_PLAYER(x, y))
8110 DrawPlayerField(x, y);
8112 TEST_DrawLevelField(x, y);
8118 boolean wanna_flame = !RND(10);
8119 int dx = newx - x, dy = newy - y;
8120 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8121 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8122 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8123 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8124 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8125 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8128 IS_CLASSIC_ENEMY(element1) ||
8129 IS_CLASSIC_ENEMY(element2)) &&
8130 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8131 element1 != EL_FLAMES && element2 != EL_FLAMES)
8133 ResetGfxAnimation(x, y);
8134 GfxAction[x][y] = ACTION_ATTACKING;
8136 if (IS_PLAYER(x, y))
8137 DrawPlayerField(x, y);
8139 TEST_DrawLevelField(x, y);
8141 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8143 MovDelay[x][y] = 50;
8145 Feld[newx][newy] = EL_FLAMES;
8146 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8147 Feld[newx1][newy1] = EL_FLAMES;
8148 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8149 Feld[newx2][newy2] = EL_FLAMES;
8155 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8156 Feld[newx][newy] == EL_DIAMOND)
8158 if (IS_MOVING(newx, newy))
8159 RemoveMovingField(newx, newy);
8162 Feld[newx][newy] = EL_EMPTY;
8163 TEST_DrawLevelField(newx, newy);
8166 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8168 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8169 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8171 if (AmoebaNr[newx][newy])
8173 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8174 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8175 Feld[newx][newy] == EL_BD_AMOEBA)
8176 AmoebaCnt[AmoebaNr[newx][newy]]--;
8179 if (IS_MOVING(newx, newy))
8181 RemoveMovingField(newx, newy);
8185 Feld[newx][newy] = EL_EMPTY;
8186 TEST_DrawLevelField(newx, newy);
8189 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8191 else if ((element == EL_PACMAN || element == EL_MOLE)
8192 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8194 if (AmoebaNr[newx][newy])
8196 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8197 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8198 Feld[newx][newy] == EL_BD_AMOEBA)
8199 AmoebaCnt[AmoebaNr[newx][newy]]--;
8202 if (element == EL_MOLE)
8204 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8205 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8207 ResetGfxAnimation(x, y);
8208 GfxAction[x][y] = ACTION_DIGGING;
8209 TEST_DrawLevelField(x, y);
8211 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8213 return; // wait for shrinking amoeba
8215 else // element == EL_PACMAN
8217 Feld[newx][newy] = EL_EMPTY;
8218 TEST_DrawLevelField(newx, newy);
8219 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8222 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8223 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8224 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8226 // wait for shrinking amoeba to completely disappear
8229 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8231 // object was running against a wall
8235 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8236 DrawLevelElementAnimation(x, y, element);
8238 if (DONT_TOUCH(element))
8239 TestIfBadThingTouchesPlayer(x, y);
8244 InitMovingField(x, y, MovDir[x][y]);
8246 PlayLevelSoundAction(x, y, ACTION_MOVING);
8250 ContinueMoving(x, y);
8253 void ContinueMoving(int x, int y)
8255 int element = Feld[x][y];
8256 struct ElementInfo *ei = &element_info[element];
8257 int direction = MovDir[x][y];
8258 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8259 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8260 int newx = x + dx, newy = y + dy;
8261 int stored = Store[x][y];
8262 int stored_new = Store[newx][newy];
8263 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8264 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8265 boolean last_line = (newy == lev_fieldy - 1);
8267 MovPos[x][y] += getElementMoveStepsize(x, y);
8269 if (pushed_by_player) // special case: moving object pushed by player
8270 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8272 if (ABS(MovPos[x][y]) < TILEX)
8274 TEST_DrawLevelField(x, y);
8276 return; // element is still moving
8279 // element reached destination field
8281 Feld[x][y] = EL_EMPTY;
8282 Feld[newx][newy] = element;
8283 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8285 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8287 element = Feld[newx][newy] = EL_ACID;
8289 else if (element == EL_MOLE)
8291 Feld[x][y] = EL_SAND;
8293 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8295 else if (element == EL_QUICKSAND_FILLING)
8297 element = Feld[newx][newy] = get_next_element(element);
8298 Store[newx][newy] = Store[x][y];
8300 else if (element == EL_QUICKSAND_EMPTYING)
8302 Feld[x][y] = get_next_element(element);
8303 element = Feld[newx][newy] = Store[x][y];
8305 else if (element == EL_QUICKSAND_FAST_FILLING)
8307 element = Feld[newx][newy] = get_next_element(element);
8308 Store[newx][newy] = Store[x][y];
8310 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8312 Feld[x][y] = get_next_element(element);
8313 element = Feld[newx][newy] = Store[x][y];
8315 else if (element == EL_MAGIC_WALL_FILLING)
8317 element = Feld[newx][newy] = get_next_element(element);
8318 if (!game.magic_wall_active)
8319 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8320 Store[newx][newy] = Store[x][y];
8322 else if (element == EL_MAGIC_WALL_EMPTYING)
8324 Feld[x][y] = get_next_element(element);
8325 if (!game.magic_wall_active)
8326 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8327 element = Feld[newx][newy] = Store[x][y];
8329 InitField(newx, newy, FALSE);
8331 else if (element == EL_BD_MAGIC_WALL_FILLING)
8333 element = Feld[newx][newy] = get_next_element(element);
8334 if (!game.magic_wall_active)
8335 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8336 Store[newx][newy] = Store[x][y];
8338 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8340 Feld[x][y] = get_next_element(element);
8341 if (!game.magic_wall_active)
8342 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8343 element = Feld[newx][newy] = Store[x][y];
8345 InitField(newx, newy, FALSE);
8347 else if (element == EL_DC_MAGIC_WALL_FILLING)
8349 element = Feld[newx][newy] = get_next_element(element);
8350 if (!game.magic_wall_active)
8351 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8352 Store[newx][newy] = Store[x][y];
8354 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8356 Feld[x][y] = get_next_element(element);
8357 if (!game.magic_wall_active)
8358 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8359 element = Feld[newx][newy] = Store[x][y];
8361 InitField(newx, newy, FALSE);
8363 else if (element == EL_AMOEBA_DROPPING)
8365 Feld[x][y] = get_next_element(element);
8366 element = Feld[newx][newy] = Store[x][y];
8368 else if (element == EL_SOKOBAN_OBJECT)
8371 Feld[x][y] = Back[x][y];
8373 if (Back[newx][newy])
8374 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8376 Back[x][y] = Back[newx][newy] = 0;
8379 Store[x][y] = EL_EMPTY;
8384 MovDelay[newx][newy] = 0;
8386 if (CAN_CHANGE_OR_HAS_ACTION(element))
8388 // copy element change control values to new field
8389 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8390 ChangePage[newx][newy] = ChangePage[x][y];
8391 ChangeCount[newx][newy] = ChangeCount[x][y];
8392 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8395 CustomValue[newx][newy] = CustomValue[x][y];
8397 ChangeDelay[x][y] = 0;
8398 ChangePage[x][y] = -1;
8399 ChangeCount[x][y] = 0;
8400 ChangeEvent[x][y] = -1;
8402 CustomValue[x][y] = 0;
8404 // copy animation control values to new field
8405 GfxFrame[newx][newy] = GfxFrame[x][y];
8406 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8407 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8408 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8410 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8412 // some elements can leave other elements behind after moving
8413 if (ei->move_leave_element != EL_EMPTY &&
8414 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8415 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8417 int move_leave_element = ei->move_leave_element;
8419 // this makes it possible to leave the removed element again
8420 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8421 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8423 Feld[x][y] = move_leave_element;
8425 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8426 MovDir[x][y] = direction;
8428 InitField(x, y, FALSE);
8430 if (GFX_CRUMBLED(Feld[x][y]))
8431 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8433 if (ELEM_IS_PLAYER(move_leave_element))
8434 RelocatePlayer(x, y, move_leave_element);
8437 // do this after checking for left-behind element
8438 ResetGfxAnimation(x, y); // reset animation values for old field
8440 if (!CAN_MOVE(element) ||
8441 (CAN_FALL(element) && direction == MV_DOWN &&
8442 (element == EL_SPRING ||
8443 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8444 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8445 GfxDir[x][y] = MovDir[newx][newy] = 0;
8447 TEST_DrawLevelField(x, y);
8448 TEST_DrawLevelField(newx, newy);
8450 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8452 // prevent pushed element from moving on in pushed direction
8453 if (pushed_by_player && CAN_MOVE(element) &&
8454 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8455 !(element_info[element].move_pattern & direction))
8456 TurnRound(newx, newy);
8458 // prevent elements on conveyor belt from moving on in last direction
8459 if (pushed_by_conveyor && CAN_FALL(element) &&
8460 direction & MV_HORIZONTAL)
8461 MovDir[newx][newy] = 0;
8463 if (!pushed_by_player)
8465 int nextx = newx + dx, nexty = newy + dy;
8466 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8468 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8470 if (CAN_FALL(element) && direction == MV_DOWN)
8471 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8473 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8474 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8476 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8477 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8480 if (DONT_TOUCH(element)) // object may be nasty to player or others
8482 TestIfBadThingTouchesPlayer(newx, newy);
8483 TestIfBadThingTouchesFriend(newx, newy);
8485 if (!IS_CUSTOM_ELEMENT(element))
8486 TestIfBadThingTouchesOtherBadThing(newx, newy);
8488 else if (element == EL_PENGUIN)
8489 TestIfFriendTouchesBadThing(newx, newy);
8491 if (DONT_GET_HIT_BY(element))
8493 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8496 // give the player one last chance (one more frame) to move away
8497 if (CAN_FALL(element) && direction == MV_DOWN &&
8498 (last_line || (!IS_FREE(x, newy + 1) &&
8499 (!IS_PLAYER(x, newy + 1) ||
8500 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8503 if (pushed_by_player && !game.use_change_when_pushing_bug)
8505 int push_side = MV_DIR_OPPOSITE(direction);
8506 struct PlayerInfo *player = PLAYERINFO(x, y);
8508 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8509 player->index_bit, push_side);
8510 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8511 player->index_bit, push_side);
8514 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8515 MovDelay[newx][newy] = 1;
8517 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8519 TestIfElementTouchesCustomElement(x, y); // empty or new element
8520 TestIfElementHitsCustomElement(newx, newy, direction);
8521 TestIfPlayerTouchesCustomElement(newx, newy);
8522 TestIfElementTouchesCustomElement(newx, newy);
8524 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8525 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8526 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8527 MV_DIR_OPPOSITE(direction));
8530 int AmoebeNachbarNr(int ax, int ay)
8533 int element = Feld[ax][ay];
8535 static int xy[4][2] =
8543 for (i = 0; i < NUM_DIRECTIONS; i++)
8545 int x = ax + xy[i][0];
8546 int y = ay + xy[i][1];
8548 if (!IN_LEV_FIELD(x, y))
8551 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8552 group_nr = AmoebaNr[x][y];
8558 static void AmoebenVereinigen(int ax, int ay)
8560 int i, x, y, xx, yy;
8561 int new_group_nr = AmoebaNr[ax][ay];
8562 static int xy[4][2] =
8570 if (new_group_nr == 0)
8573 for (i = 0; i < NUM_DIRECTIONS; i++)
8578 if (!IN_LEV_FIELD(x, y))
8581 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8582 Feld[x][y] == EL_BD_AMOEBA ||
8583 Feld[x][y] == EL_AMOEBA_DEAD) &&
8584 AmoebaNr[x][y] != new_group_nr)
8586 int old_group_nr = AmoebaNr[x][y];
8588 if (old_group_nr == 0)
8591 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8592 AmoebaCnt[old_group_nr] = 0;
8593 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8594 AmoebaCnt2[old_group_nr] = 0;
8596 SCAN_PLAYFIELD(xx, yy)
8598 if (AmoebaNr[xx][yy] == old_group_nr)
8599 AmoebaNr[xx][yy] = new_group_nr;
8605 void AmoebeUmwandeln(int ax, int ay)
8609 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8611 int group_nr = AmoebaNr[ax][ay];
8616 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8617 printf("AmoebeUmwandeln(): This should never happen!\n");
8622 SCAN_PLAYFIELD(x, y)
8624 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8627 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8631 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8632 SND_AMOEBA_TURNING_TO_GEM :
8633 SND_AMOEBA_TURNING_TO_ROCK));
8638 static int xy[4][2] =
8646 for (i = 0; i < NUM_DIRECTIONS; i++)
8651 if (!IN_LEV_FIELD(x, y))
8654 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8656 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8657 SND_AMOEBA_TURNING_TO_GEM :
8658 SND_AMOEBA_TURNING_TO_ROCK));
8665 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8668 int group_nr = AmoebaNr[ax][ay];
8669 boolean done = FALSE;
8674 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8675 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8680 SCAN_PLAYFIELD(x, y)
8682 if (AmoebaNr[x][y] == group_nr &&
8683 (Feld[x][y] == EL_AMOEBA_DEAD ||
8684 Feld[x][y] == EL_BD_AMOEBA ||
8685 Feld[x][y] == EL_AMOEBA_GROWING))
8688 Feld[x][y] = new_element;
8689 InitField(x, y, FALSE);
8690 TEST_DrawLevelField(x, y);
8696 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8697 SND_BD_AMOEBA_TURNING_TO_ROCK :
8698 SND_BD_AMOEBA_TURNING_TO_GEM));
8701 static void AmoebeWaechst(int x, int y)
8703 static unsigned int sound_delay = 0;
8704 static unsigned int sound_delay_value = 0;
8706 if (!MovDelay[x][y]) // start new growing cycle
8710 if (DelayReached(&sound_delay, sound_delay_value))
8712 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8713 sound_delay_value = 30;
8717 if (MovDelay[x][y]) // wait some time before growing bigger
8720 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8722 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8723 6 - MovDelay[x][y]);
8725 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8728 if (!MovDelay[x][y])
8730 Feld[x][y] = Store[x][y];
8732 TEST_DrawLevelField(x, y);
8737 static void AmoebaDisappearing(int x, int y)
8739 static unsigned int sound_delay = 0;
8740 static unsigned int sound_delay_value = 0;
8742 if (!MovDelay[x][y]) // start new shrinking cycle
8746 if (DelayReached(&sound_delay, sound_delay_value))
8747 sound_delay_value = 30;
8750 if (MovDelay[x][y]) // wait some time before shrinking
8753 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8755 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8756 6 - MovDelay[x][y]);
8758 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8761 if (!MovDelay[x][y])
8763 Feld[x][y] = EL_EMPTY;
8764 TEST_DrawLevelField(x, y);
8766 // don't let mole enter this field in this cycle;
8767 // (give priority to objects falling to this field from above)
8773 static void AmoebeAbleger(int ax, int ay)
8776 int element = Feld[ax][ay];
8777 int graphic = el2img(element);
8778 int newax = ax, neway = ay;
8779 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8780 static int xy[4][2] =
8788 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8790 Feld[ax][ay] = EL_AMOEBA_DEAD;
8791 TEST_DrawLevelField(ax, ay);
8795 if (IS_ANIMATED(graphic))
8796 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8798 if (!MovDelay[ax][ay]) // start making new amoeba field
8799 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8801 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8804 if (MovDelay[ax][ay])
8808 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8811 int x = ax + xy[start][0];
8812 int y = ay + xy[start][1];
8814 if (!IN_LEV_FIELD(x, y))
8817 if (IS_FREE(x, y) ||
8818 CAN_GROW_INTO(Feld[x][y]) ||
8819 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8820 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8826 if (newax == ax && neway == ay)
8829 else // normal or "filled" (BD style) amoeba
8832 boolean waiting_for_player = FALSE;
8834 for (i = 0; i < NUM_DIRECTIONS; i++)
8836 int j = (start + i) % 4;
8837 int x = ax + xy[j][0];
8838 int y = ay + xy[j][1];
8840 if (!IN_LEV_FIELD(x, y))
8843 if (IS_FREE(x, y) ||
8844 CAN_GROW_INTO(Feld[x][y]) ||
8845 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8846 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8852 else if (IS_PLAYER(x, y))
8853 waiting_for_player = TRUE;
8856 if (newax == ax && neway == ay) // amoeba cannot grow
8858 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
8860 Feld[ax][ay] = EL_AMOEBA_DEAD;
8861 TEST_DrawLevelField(ax, ay);
8862 AmoebaCnt[AmoebaNr[ax][ay]]--;
8864 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
8866 if (element == EL_AMOEBA_FULL)
8867 AmoebeUmwandeln(ax, ay);
8868 else if (element == EL_BD_AMOEBA)
8869 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
8874 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
8876 // amoeba gets larger by growing in some direction
8878 int new_group_nr = AmoebaNr[ax][ay];
8881 if (new_group_nr == 0)
8883 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
8884 printf("AmoebeAbleger(): This should never happen!\n");
8889 AmoebaNr[newax][neway] = new_group_nr;
8890 AmoebaCnt[new_group_nr]++;
8891 AmoebaCnt2[new_group_nr]++;
8893 // if amoeba touches other amoeba(s) after growing, unify them
8894 AmoebenVereinigen(newax, neway);
8896 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
8898 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
8904 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
8905 (neway == lev_fieldy - 1 && newax != ax))
8907 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
8908 Store[newax][neway] = element;
8910 else if (neway == ay || element == EL_EMC_DRIPPER)
8912 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
8914 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
8918 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
8919 Feld[ax][ay] = EL_AMOEBA_DROPPING;
8920 Store[ax][ay] = EL_AMOEBA_DROP;
8921 ContinueMoving(ax, ay);
8925 TEST_DrawLevelField(newax, neway);
8928 static void Life(int ax, int ay)
8932 int element = Feld[ax][ay];
8933 int graphic = el2img(element);
8934 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
8936 boolean changed = FALSE;
8938 if (IS_ANIMATED(graphic))
8939 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8944 if (!MovDelay[ax][ay]) // start new "game of life" cycle
8945 MovDelay[ax][ay] = life_time;
8947 if (MovDelay[ax][ay]) // wait some time before next cycle
8950 if (MovDelay[ax][ay])
8954 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
8956 int xx = ax+x1, yy = ay+y1;
8957 int old_element = Feld[xx][yy];
8958 int num_neighbours = 0;
8960 if (!IN_LEV_FIELD(xx, yy))
8963 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
8965 int x = xx+x2, y = yy+y2;
8967 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
8970 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
8971 boolean is_neighbour = FALSE;
8973 if (level.use_life_bugs)
8975 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
8976 (IS_FREE(x, y) && Stop[x][y]));
8979 (Last[x][y] == element || is_player_cell);
8985 boolean is_free = FALSE;
8987 if (level.use_life_bugs)
8988 is_free = (IS_FREE(xx, yy));
8990 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
8992 if (xx == ax && yy == ay) // field in the middle
8994 if (num_neighbours < life_parameter[0] ||
8995 num_neighbours > life_parameter[1])
8997 Feld[xx][yy] = EL_EMPTY;
8998 if (Feld[xx][yy] != old_element)
8999 TEST_DrawLevelField(xx, yy);
9000 Stop[xx][yy] = TRUE;
9004 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9005 { // free border field
9006 if (num_neighbours >= life_parameter[2] &&
9007 num_neighbours <= life_parameter[3])
9009 Feld[xx][yy] = element;
9010 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9011 if (Feld[xx][yy] != old_element)
9012 TEST_DrawLevelField(xx, yy);
9013 Stop[xx][yy] = TRUE;
9020 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9021 SND_GAME_OF_LIFE_GROWING);
9024 static void InitRobotWheel(int x, int y)
9026 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9029 static void RunRobotWheel(int x, int y)
9031 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9034 static void StopRobotWheel(int x, int y)
9036 if (ZX == x && ZY == y)
9040 game.robot_wheel_active = FALSE;
9044 static void InitTimegateWheel(int x, int y)
9046 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9049 static void RunTimegateWheel(int x, int y)
9051 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9054 static void InitMagicBallDelay(int x, int y)
9056 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9059 static void ActivateMagicBall(int bx, int by)
9063 if (level.ball_random)
9065 int pos_border = RND(8); // select one of the eight border elements
9066 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9067 int xx = pos_content % 3;
9068 int yy = pos_content / 3;
9073 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9074 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9078 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9080 int xx = x - bx + 1;
9081 int yy = y - by + 1;
9083 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9084 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9088 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9091 static void CheckExit(int x, int y)
9093 if (game.gems_still_needed > 0 ||
9094 game.sokoban_fields_still_needed > 0 ||
9095 game.sokoban_objects_still_needed > 0 ||
9096 game.lights_still_needed > 0)
9098 int element = Feld[x][y];
9099 int graphic = el2img(element);
9101 if (IS_ANIMATED(graphic))
9102 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9107 // do not re-open exit door closed after last player
9108 if (game.all_players_gone)
9111 Feld[x][y] = EL_EXIT_OPENING;
9113 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9116 static void CheckExitEM(int x, int y)
9118 if (game.gems_still_needed > 0 ||
9119 game.sokoban_fields_still_needed > 0 ||
9120 game.sokoban_objects_still_needed > 0 ||
9121 game.lights_still_needed > 0)
9123 int element = Feld[x][y];
9124 int graphic = el2img(element);
9126 if (IS_ANIMATED(graphic))
9127 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9132 // do not re-open exit door closed after last player
9133 if (game.all_players_gone)
9136 Feld[x][y] = EL_EM_EXIT_OPENING;
9138 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9141 static void CheckExitSteel(int x, int y)
9143 if (game.gems_still_needed > 0 ||
9144 game.sokoban_fields_still_needed > 0 ||
9145 game.sokoban_objects_still_needed > 0 ||
9146 game.lights_still_needed > 0)
9148 int element = Feld[x][y];
9149 int graphic = el2img(element);
9151 if (IS_ANIMATED(graphic))
9152 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9157 // do not re-open exit door closed after last player
9158 if (game.all_players_gone)
9161 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9163 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9166 static void CheckExitSteelEM(int x, int y)
9168 if (game.gems_still_needed > 0 ||
9169 game.sokoban_fields_still_needed > 0 ||
9170 game.sokoban_objects_still_needed > 0 ||
9171 game.lights_still_needed > 0)
9173 int element = Feld[x][y];
9174 int graphic = el2img(element);
9176 if (IS_ANIMATED(graphic))
9177 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9182 // do not re-open exit door closed after last player
9183 if (game.all_players_gone)
9186 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9188 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9191 static void CheckExitSP(int x, int y)
9193 if (game.gems_still_needed > 0)
9195 int element = Feld[x][y];
9196 int graphic = el2img(element);
9198 if (IS_ANIMATED(graphic))
9199 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9204 // do not re-open exit door closed after last player
9205 if (game.all_players_gone)
9208 Feld[x][y] = EL_SP_EXIT_OPENING;
9210 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9213 static void CloseAllOpenTimegates(void)
9217 SCAN_PLAYFIELD(x, y)
9219 int element = Feld[x][y];
9221 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9223 Feld[x][y] = EL_TIMEGATE_CLOSING;
9225 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9230 static void DrawTwinkleOnField(int x, int y)
9232 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9235 if (Feld[x][y] == EL_BD_DIAMOND)
9238 if (MovDelay[x][y] == 0) // next animation frame
9239 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9241 if (MovDelay[x][y] != 0) // wait some time before next frame
9245 DrawLevelElementAnimation(x, y, Feld[x][y]);
9247 if (MovDelay[x][y] != 0)
9249 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9250 10 - MovDelay[x][y]);
9252 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9257 static void MauerWaechst(int x, int y)
9261 if (!MovDelay[x][y]) // next animation frame
9262 MovDelay[x][y] = 3 * delay;
9264 if (MovDelay[x][y]) // wait some time before next frame
9268 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9270 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9271 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9273 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9276 if (!MovDelay[x][y])
9278 if (MovDir[x][y] == MV_LEFT)
9280 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9281 TEST_DrawLevelField(x - 1, y);
9283 else if (MovDir[x][y] == MV_RIGHT)
9285 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9286 TEST_DrawLevelField(x + 1, y);
9288 else if (MovDir[x][y] == MV_UP)
9290 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9291 TEST_DrawLevelField(x, y - 1);
9295 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9296 TEST_DrawLevelField(x, y + 1);
9299 Feld[x][y] = Store[x][y];
9301 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9302 TEST_DrawLevelField(x, y);
9307 static void MauerAbleger(int ax, int ay)
9309 int element = Feld[ax][ay];
9310 int graphic = el2img(element);
9311 boolean oben_frei = FALSE, unten_frei = FALSE;
9312 boolean links_frei = FALSE, rechts_frei = FALSE;
9313 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9314 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9315 boolean new_wall = FALSE;
9317 if (IS_ANIMATED(graphic))
9318 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9320 if (!MovDelay[ax][ay]) // start building new wall
9321 MovDelay[ax][ay] = 6;
9323 if (MovDelay[ax][ay]) // wait some time before building new wall
9326 if (MovDelay[ax][ay])
9330 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9332 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9334 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9336 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9339 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9340 element == EL_EXPANDABLE_WALL_ANY)
9344 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9345 Store[ax][ay-1] = element;
9346 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9347 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9348 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9349 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9354 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9355 Store[ax][ay+1] = element;
9356 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9357 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9358 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9359 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9364 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9365 element == EL_EXPANDABLE_WALL_ANY ||
9366 element == EL_EXPANDABLE_WALL ||
9367 element == EL_BD_EXPANDABLE_WALL)
9371 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9372 Store[ax-1][ay] = element;
9373 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9374 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9375 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9376 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9382 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9383 Store[ax+1][ay] = element;
9384 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9385 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9386 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9387 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9392 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9393 TEST_DrawLevelField(ax, ay);
9395 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9397 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9398 unten_massiv = TRUE;
9399 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9400 links_massiv = TRUE;
9401 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9402 rechts_massiv = TRUE;
9404 if (((oben_massiv && unten_massiv) ||
9405 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9406 element == EL_EXPANDABLE_WALL) &&
9407 ((links_massiv && rechts_massiv) ||
9408 element == EL_EXPANDABLE_WALL_VERTICAL))
9409 Feld[ax][ay] = EL_WALL;
9412 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9415 static void MauerAblegerStahl(int ax, int ay)
9417 int element = Feld[ax][ay];
9418 int graphic = el2img(element);
9419 boolean oben_frei = FALSE, unten_frei = FALSE;
9420 boolean links_frei = FALSE, rechts_frei = FALSE;
9421 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9422 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9423 boolean new_wall = FALSE;
9425 if (IS_ANIMATED(graphic))
9426 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9428 if (!MovDelay[ax][ay]) // start building new wall
9429 MovDelay[ax][ay] = 6;
9431 if (MovDelay[ax][ay]) // wait some time before building new wall
9434 if (MovDelay[ax][ay])
9438 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9440 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9442 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9444 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9447 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9448 element == EL_EXPANDABLE_STEELWALL_ANY)
9452 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9453 Store[ax][ay-1] = element;
9454 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9455 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9456 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9457 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9462 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9463 Store[ax][ay+1] = element;
9464 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9465 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9466 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9467 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9472 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9473 element == EL_EXPANDABLE_STEELWALL_ANY)
9477 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9478 Store[ax-1][ay] = element;
9479 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9480 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9481 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9482 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9488 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9489 Store[ax+1][ay] = element;
9490 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9491 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9492 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9493 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9498 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9500 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9501 unten_massiv = TRUE;
9502 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9503 links_massiv = TRUE;
9504 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9505 rechts_massiv = TRUE;
9507 if (((oben_massiv && unten_massiv) ||
9508 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9509 ((links_massiv && rechts_massiv) ||
9510 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9511 Feld[ax][ay] = EL_STEELWALL;
9514 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9517 static void CheckForDragon(int x, int y)
9520 boolean dragon_found = FALSE;
9521 static int xy[4][2] =
9529 for (i = 0; i < NUM_DIRECTIONS; i++)
9531 for (j = 0; j < 4; j++)
9533 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9535 if (IN_LEV_FIELD(xx, yy) &&
9536 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9538 if (Feld[xx][yy] == EL_DRAGON)
9539 dragon_found = TRUE;
9548 for (i = 0; i < NUM_DIRECTIONS; i++)
9550 for (j = 0; j < 3; j++)
9552 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9554 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9556 Feld[xx][yy] = EL_EMPTY;
9557 TEST_DrawLevelField(xx, yy);
9566 static void InitBuggyBase(int x, int y)
9568 int element = Feld[x][y];
9569 int activating_delay = FRAMES_PER_SECOND / 4;
9572 (element == EL_SP_BUGGY_BASE ?
9573 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9574 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9576 element == EL_SP_BUGGY_BASE_ACTIVE ?
9577 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9580 static void WarnBuggyBase(int x, int y)
9583 static int xy[4][2] =
9591 for (i = 0; i < NUM_DIRECTIONS; i++)
9593 int xx = x + xy[i][0];
9594 int yy = y + xy[i][1];
9596 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9598 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9605 static void InitTrap(int x, int y)
9607 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9610 static void ActivateTrap(int x, int y)
9612 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9615 static void ChangeActiveTrap(int x, int y)
9617 int graphic = IMG_TRAP_ACTIVE;
9619 // if new animation frame was drawn, correct crumbled sand border
9620 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9621 TEST_DrawLevelFieldCrumbled(x, y);
9624 static int getSpecialActionElement(int element, int number, int base_element)
9626 return (element != EL_EMPTY ? element :
9627 number != -1 ? base_element + number - 1 :
9631 static int getModifiedActionNumber(int value_old, int operator, int operand,
9632 int value_min, int value_max)
9634 int value_new = (operator == CA_MODE_SET ? operand :
9635 operator == CA_MODE_ADD ? value_old + operand :
9636 operator == CA_MODE_SUBTRACT ? value_old - operand :
9637 operator == CA_MODE_MULTIPLY ? value_old * operand :
9638 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9639 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9642 return (value_new < value_min ? value_min :
9643 value_new > value_max ? value_max :
9647 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9649 struct ElementInfo *ei = &element_info[element];
9650 struct ElementChangeInfo *change = &ei->change_page[page];
9651 int target_element = change->target_element;
9652 int action_type = change->action_type;
9653 int action_mode = change->action_mode;
9654 int action_arg = change->action_arg;
9655 int action_element = change->action_element;
9658 if (!change->has_action)
9661 // ---------- determine action paramater values -----------------------------
9663 int level_time_value =
9664 (level.time > 0 ? TimeLeft :
9667 int action_arg_element_raw =
9668 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9669 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9670 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9671 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9672 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9673 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9674 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9676 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9678 int action_arg_direction =
9679 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9680 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9681 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9682 change->actual_trigger_side :
9683 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9684 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9687 int action_arg_number_min =
9688 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9691 int action_arg_number_max =
9692 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9693 action_type == CA_SET_LEVEL_GEMS ? 999 :
9694 action_type == CA_SET_LEVEL_TIME ? 9999 :
9695 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9696 action_type == CA_SET_CE_VALUE ? 9999 :
9697 action_type == CA_SET_CE_SCORE ? 9999 :
9700 int action_arg_number_reset =
9701 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9702 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9703 action_type == CA_SET_LEVEL_TIME ? level.time :
9704 action_type == CA_SET_LEVEL_SCORE ? 0 :
9705 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9706 action_type == CA_SET_CE_SCORE ? 0 :
9709 int action_arg_number =
9710 (action_arg <= CA_ARG_MAX ? action_arg :
9711 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9712 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9713 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9714 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9715 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9716 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9717 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9718 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9719 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9720 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9721 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9722 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9723 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9724 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9725 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9726 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9727 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9728 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9729 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9730 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9731 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9734 int action_arg_number_old =
9735 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9736 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9737 action_type == CA_SET_LEVEL_SCORE ? game.score :
9738 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9739 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9742 int action_arg_number_new =
9743 getModifiedActionNumber(action_arg_number_old,
9744 action_mode, action_arg_number,
9745 action_arg_number_min, action_arg_number_max);
9747 int trigger_player_bits =
9748 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9749 change->actual_trigger_player_bits : change->trigger_player);
9751 int action_arg_player_bits =
9752 (action_arg >= CA_ARG_PLAYER_1 &&
9753 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9754 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9755 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9758 // ---------- execute action -----------------------------------------------
9760 switch (action_type)
9767 // ---------- level actions ----------------------------------------------
9769 case CA_RESTART_LEVEL:
9771 game.restart_level = TRUE;
9776 case CA_SHOW_ENVELOPE:
9778 int element = getSpecialActionElement(action_arg_element,
9779 action_arg_number, EL_ENVELOPE_1);
9781 if (IS_ENVELOPE(element))
9782 local_player->show_envelope = element;
9787 case CA_SET_LEVEL_TIME:
9789 if (level.time > 0) // only modify limited time value
9791 TimeLeft = action_arg_number_new;
9793 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9795 DisplayGameControlValues();
9797 if (!TimeLeft && setup.time_limit)
9798 for (i = 0; i < MAX_PLAYERS; i++)
9799 KillPlayer(&stored_player[i]);
9805 case CA_SET_LEVEL_SCORE:
9807 game.score = action_arg_number_new;
9809 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9811 DisplayGameControlValues();
9816 case CA_SET_LEVEL_GEMS:
9818 game.gems_still_needed = action_arg_number_new;
9820 game.snapshot.collected_item = TRUE;
9822 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9824 DisplayGameControlValues();
9829 case CA_SET_LEVEL_WIND:
9831 game.wind_direction = action_arg_direction;
9836 case CA_SET_LEVEL_RANDOM_SEED:
9838 // ensure that setting a new random seed while playing is predictable
9839 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9844 // ---------- player actions ---------------------------------------------
9846 case CA_MOVE_PLAYER:
9848 // automatically move to the next field in specified direction
9849 for (i = 0; i < MAX_PLAYERS; i++)
9850 if (trigger_player_bits & (1 << i))
9851 stored_player[i].programmed_action = action_arg_direction;
9856 case CA_EXIT_PLAYER:
9858 for (i = 0; i < MAX_PLAYERS; i++)
9859 if (action_arg_player_bits & (1 << i))
9860 ExitPlayer(&stored_player[i]);
9862 if (game.players_still_needed == 0)
9868 case CA_KILL_PLAYER:
9870 for (i = 0; i < MAX_PLAYERS; i++)
9871 if (action_arg_player_bits & (1 << i))
9872 KillPlayer(&stored_player[i]);
9877 case CA_SET_PLAYER_KEYS:
9879 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
9880 int element = getSpecialActionElement(action_arg_element,
9881 action_arg_number, EL_KEY_1);
9883 if (IS_KEY(element))
9885 for (i = 0; i < MAX_PLAYERS; i++)
9887 if (trigger_player_bits & (1 << i))
9889 stored_player[i].key[KEY_NR(element)] = key_state;
9891 DrawGameDoorValues();
9899 case CA_SET_PLAYER_SPEED:
9901 for (i = 0; i < MAX_PLAYERS; i++)
9903 if (trigger_player_bits & (1 << i))
9905 int move_stepsize = TILEX / stored_player[i].move_delay_value;
9907 if (action_arg == CA_ARG_SPEED_FASTER &&
9908 stored_player[i].cannot_move)
9910 action_arg_number = STEPSIZE_VERY_SLOW;
9912 else if (action_arg == CA_ARG_SPEED_SLOWER ||
9913 action_arg == CA_ARG_SPEED_FASTER)
9915 action_arg_number = 2;
9916 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
9919 else if (action_arg == CA_ARG_NUMBER_RESET)
9921 action_arg_number = level.initial_player_stepsize[i];
9925 getModifiedActionNumber(move_stepsize,
9928 action_arg_number_min,
9929 action_arg_number_max);
9931 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
9938 case CA_SET_PLAYER_SHIELD:
9940 for (i = 0; i < MAX_PLAYERS; i++)
9942 if (trigger_player_bits & (1 << i))
9944 if (action_arg == CA_ARG_SHIELD_OFF)
9946 stored_player[i].shield_normal_time_left = 0;
9947 stored_player[i].shield_deadly_time_left = 0;
9949 else if (action_arg == CA_ARG_SHIELD_NORMAL)
9951 stored_player[i].shield_normal_time_left = 999999;
9953 else if (action_arg == CA_ARG_SHIELD_DEADLY)
9955 stored_player[i].shield_normal_time_left = 999999;
9956 stored_player[i].shield_deadly_time_left = 999999;
9964 case CA_SET_PLAYER_GRAVITY:
9966 for (i = 0; i < MAX_PLAYERS; i++)
9968 if (trigger_player_bits & (1 << i))
9970 stored_player[i].gravity =
9971 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
9972 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
9973 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
9974 stored_player[i].gravity);
9981 case CA_SET_PLAYER_ARTWORK:
9983 for (i = 0; i < MAX_PLAYERS; i++)
9985 if (trigger_player_bits & (1 << i))
9987 int artwork_element = action_arg_element;
9989 if (action_arg == CA_ARG_ELEMENT_RESET)
9991 (level.use_artwork_element[i] ? level.artwork_element[i] :
9992 stored_player[i].element_nr);
9994 if (stored_player[i].artwork_element != artwork_element)
9995 stored_player[i].Frame = 0;
9997 stored_player[i].artwork_element = artwork_element;
9999 SetPlayerWaiting(&stored_player[i], FALSE);
10001 // set number of special actions for bored and sleeping animation
10002 stored_player[i].num_special_action_bored =
10003 get_num_special_action(artwork_element,
10004 ACTION_BORING_1, ACTION_BORING_LAST);
10005 stored_player[i].num_special_action_sleeping =
10006 get_num_special_action(artwork_element,
10007 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10014 case CA_SET_PLAYER_INVENTORY:
10016 for (i = 0; i < MAX_PLAYERS; i++)
10018 struct PlayerInfo *player = &stored_player[i];
10021 if (trigger_player_bits & (1 << i))
10023 int inventory_element = action_arg_element;
10025 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10026 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10027 action_arg == CA_ARG_ELEMENT_ACTION)
10029 int element = inventory_element;
10030 int collect_count = element_info[element].collect_count_initial;
10032 if (!IS_CUSTOM_ELEMENT(element))
10035 if (collect_count == 0)
10036 player->inventory_infinite_element = element;
10038 for (k = 0; k < collect_count; k++)
10039 if (player->inventory_size < MAX_INVENTORY_SIZE)
10040 player->inventory_element[player->inventory_size++] =
10043 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10044 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10045 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10047 if (player->inventory_infinite_element != EL_UNDEFINED &&
10048 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10049 action_arg_element_raw))
10050 player->inventory_infinite_element = EL_UNDEFINED;
10052 for (k = 0, j = 0; j < player->inventory_size; j++)
10054 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10055 action_arg_element_raw))
10056 player->inventory_element[k++] = player->inventory_element[j];
10059 player->inventory_size = k;
10061 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10063 if (player->inventory_size > 0)
10065 for (j = 0; j < player->inventory_size - 1; j++)
10066 player->inventory_element[j] = player->inventory_element[j + 1];
10068 player->inventory_size--;
10071 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10073 if (player->inventory_size > 0)
10074 player->inventory_size--;
10076 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10078 player->inventory_infinite_element = EL_UNDEFINED;
10079 player->inventory_size = 0;
10081 else if (action_arg == CA_ARG_INVENTORY_RESET)
10083 player->inventory_infinite_element = EL_UNDEFINED;
10084 player->inventory_size = 0;
10086 if (level.use_initial_inventory[i])
10088 for (j = 0; j < level.initial_inventory_size[i]; j++)
10090 int element = level.initial_inventory_content[i][j];
10091 int collect_count = element_info[element].collect_count_initial;
10093 if (!IS_CUSTOM_ELEMENT(element))
10096 if (collect_count == 0)
10097 player->inventory_infinite_element = element;
10099 for (k = 0; k < collect_count; k++)
10100 if (player->inventory_size < MAX_INVENTORY_SIZE)
10101 player->inventory_element[player->inventory_size++] =
10112 // ---------- CE actions -------------------------------------------------
10114 case CA_SET_CE_VALUE:
10116 int last_ce_value = CustomValue[x][y];
10118 CustomValue[x][y] = action_arg_number_new;
10120 if (CustomValue[x][y] != last_ce_value)
10122 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10123 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10125 if (CustomValue[x][y] == 0)
10127 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10128 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10135 case CA_SET_CE_SCORE:
10137 int last_ce_score = ei->collect_score;
10139 ei->collect_score = action_arg_number_new;
10141 if (ei->collect_score != last_ce_score)
10143 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10144 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10146 if (ei->collect_score == 0)
10150 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10151 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10154 This is a very special case that seems to be a mixture between
10155 CheckElementChange() and CheckTriggeredElementChange(): while
10156 the first one only affects single elements that are triggered
10157 directly, the second one affects multiple elements in the playfield
10158 that are triggered indirectly by another element. This is a third
10159 case: Changing the CE score always affects multiple identical CEs,
10160 so every affected CE must be checked, not only the single CE for
10161 which the CE score was changed in the first place (as every instance
10162 of that CE shares the same CE score, and therefore also can change)!
10164 SCAN_PLAYFIELD(xx, yy)
10166 if (Feld[xx][yy] == element)
10167 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10168 CE_SCORE_GETS_ZERO);
10176 case CA_SET_CE_ARTWORK:
10178 int artwork_element = action_arg_element;
10179 boolean reset_frame = FALSE;
10182 if (action_arg == CA_ARG_ELEMENT_RESET)
10183 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10186 if (ei->gfx_element != artwork_element)
10187 reset_frame = TRUE;
10189 ei->gfx_element = artwork_element;
10191 SCAN_PLAYFIELD(xx, yy)
10193 if (Feld[xx][yy] == element)
10197 ResetGfxAnimation(xx, yy);
10198 ResetRandomAnimationValue(xx, yy);
10201 TEST_DrawLevelField(xx, yy);
10208 // ---------- engine actions ---------------------------------------------
10210 case CA_SET_ENGINE_SCAN_MODE:
10212 InitPlayfieldScanMode(action_arg);
10222 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10224 int old_element = Feld[x][y];
10225 int new_element = GetElementFromGroupElement(element);
10226 int previous_move_direction = MovDir[x][y];
10227 int last_ce_value = CustomValue[x][y];
10228 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10229 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10230 boolean add_player_onto_element = (new_element_is_player &&
10231 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10232 IS_WALKABLE(old_element));
10234 if (!add_player_onto_element)
10236 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10237 RemoveMovingField(x, y);
10241 Feld[x][y] = new_element;
10243 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10244 MovDir[x][y] = previous_move_direction;
10246 if (element_info[new_element].use_last_ce_value)
10247 CustomValue[x][y] = last_ce_value;
10249 InitField_WithBug1(x, y, FALSE);
10251 new_element = Feld[x][y]; // element may have changed
10253 ResetGfxAnimation(x, y);
10254 ResetRandomAnimationValue(x, y);
10256 TEST_DrawLevelField(x, y);
10258 if (GFX_CRUMBLED(new_element))
10259 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10262 // check if element under the player changes from accessible to unaccessible
10263 // (needed for special case of dropping element which then changes)
10264 // (must be checked after creating new element for walkable group elements)
10265 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10266 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10273 // "ChangeCount" not set yet to allow "entered by player" change one time
10274 if (new_element_is_player)
10275 RelocatePlayer(x, y, new_element);
10278 ChangeCount[x][y]++; // count number of changes in the same frame
10280 TestIfBadThingTouchesPlayer(x, y);
10281 TestIfPlayerTouchesCustomElement(x, y);
10282 TestIfElementTouchesCustomElement(x, y);
10285 static void CreateField(int x, int y, int element)
10287 CreateFieldExt(x, y, element, FALSE);
10290 static void CreateElementFromChange(int x, int y, int element)
10292 element = GET_VALID_RUNTIME_ELEMENT(element);
10294 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10296 int old_element = Feld[x][y];
10298 // prevent changed element from moving in same engine frame
10299 // unless both old and new element can either fall or move
10300 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10301 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10305 CreateFieldExt(x, y, element, TRUE);
10308 static boolean ChangeElement(int x, int y, int element, int page)
10310 struct ElementInfo *ei = &element_info[element];
10311 struct ElementChangeInfo *change = &ei->change_page[page];
10312 int ce_value = CustomValue[x][y];
10313 int ce_score = ei->collect_score;
10314 int target_element;
10315 int old_element = Feld[x][y];
10317 // always use default change event to prevent running into a loop
10318 if (ChangeEvent[x][y] == -1)
10319 ChangeEvent[x][y] = CE_DELAY;
10321 if (ChangeEvent[x][y] == CE_DELAY)
10323 // reset actual trigger element, trigger player and action element
10324 change->actual_trigger_element = EL_EMPTY;
10325 change->actual_trigger_player = EL_EMPTY;
10326 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10327 change->actual_trigger_side = CH_SIDE_NONE;
10328 change->actual_trigger_ce_value = 0;
10329 change->actual_trigger_ce_score = 0;
10332 // do not change elements more than a specified maximum number of changes
10333 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10336 ChangeCount[x][y]++; // count number of changes in the same frame
10338 if (change->explode)
10345 if (change->use_target_content)
10347 boolean complete_replace = TRUE;
10348 boolean can_replace[3][3];
10351 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10354 boolean is_walkable;
10355 boolean is_diggable;
10356 boolean is_collectible;
10357 boolean is_removable;
10358 boolean is_destructible;
10359 int ex = x + xx - 1;
10360 int ey = y + yy - 1;
10361 int content_element = change->target_content.e[xx][yy];
10364 can_replace[xx][yy] = TRUE;
10366 if (ex == x && ey == y) // do not check changing element itself
10369 if (content_element == EL_EMPTY_SPACE)
10371 can_replace[xx][yy] = FALSE; // do not replace border with space
10376 if (!IN_LEV_FIELD(ex, ey))
10378 can_replace[xx][yy] = FALSE;
10379 complete_replace = FALSE;
10386 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10387 e = MovingOrBlocked2Element(ex, ey);
10389 is_empty = (IS_FREE(ex, ey) ||
10390 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10392 is_walkable = (is_empty || IS_WALKABLE(e));
10393 is_diggable = (is_empty || IS_DIGGABLE(e));
10394 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10395 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10396 is_removable = (is_diggable || is_collectible);
10398 can_replace[xx][yy] =
10399 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10400 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10401 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10402 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10403 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10404 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10405 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10407 if (!can_replace[xx][yy])
10408 complete_replace = FALSE;
10411 if (!change->only_if_complete || complete_replace)
10413 boolean something_has_changed = FALSE;
10415 if (change->only_if_complete && change->use_random_replace &&
10416 RND(100) < change->random_percentage)
10419 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10421 int ex = x + xx - 1;
10422 int ey = y + yy - 1;
10423 int content_element;
10425 if (can_replace[xx][yy] && (!change->use_random_replace ||
10426 RND(100) < change->random_percentage))
10428 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10429 RemoveMovingField(ex, ey);
10431 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10433 content_element = change->target_content.e[xx][yy];
10434 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10435 ce_value, ce_score);
10437 CreateElementFromChange(ex, ey, target_element);
10439 something_has_changed = TRUE;
10441 // for symmetry reasons, freeze newly created border elements
10442 if (ex != x || ey != y)
10443 Stop[ex][ey] = TRUE; // no more moving in this frame
10447 if (something_has_changed)
10449 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10450 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10456 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10457 ce_value, ce_score);
10459 if (element == EL_DIAGONAL_GROWING ||
10460 element == EL_DIAGONAL_SHRINKING)
10462 target_element = Store[x][y];
10464 Store[x][y] = EL_EMPTY;
10467 CreateElementFromChange(x, y, target_element);
10469 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10470 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10473 // this uses direct change before indirect change
10474 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10479 static void HandleElementChange(int x, int y, int page)
10481 int element = MovingOrBlocked2Element(x, y);
10482 struct ElementInfo *ei = &element_info[element];
10483 struct ElementChangeInfo *change = &ei->change_page[page];
10484 boolean handle_action_before_change = FALSE;
10487 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10488 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10491 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10492 x, y, element, element_info[element].token_name);
10493 printf("HandleElementChange(): This should never happen!\n");
10498 // this can happen with classic bombs on walkable, changing elements
10499 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10504 if (ChangeDelay[x][y] == 0) // initialize element change
10506 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10508 if (change->can_change)
10510 // !!! not clear why graphic animation should be reset at all here !!!
10511 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10512 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10515 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10517 When using an animation frame delay of 1 (this only happens with
10518 "sp_zonk.moving.left/right" in the classic graphics), the default
10519 (non-moving) animation shows wrong animation frames (while the
10520 moving animation, like "sp_zonk.moving.left/right", is correct,
10521 so this graphical bug never shows up with the classic graphics).
10522 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10523 be drawn instead of the correct frames 0,1,2,3. This is caused by
10524 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10525 an element change: First when the change delay ("ChangeDelay[][]")
10526 counter has reached zero after decrementing, then a second time in
10527 the next frame (after "GfxFrame[][]" was already incremented) when
10528 "ChangeDelay[][]" is reset to the initial delay value again.
10530 This causes frame 0 to be drawn twice, while the last frame won't
10531 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10533 As some animations may already be cleverly designed around this bug
10534 (at least the "Snake Bite" snake tail animation does this), it cannot
10535 simply be fixed here without breaking such existing animations.
10536 Unfortunately, it cannot easily be detected if a graphics set was
10537 designed "before" or "after" the bug was fixed. As a workaround,
10538 a new graphics set option "game.graphics_engine_version" was added
10539 to be able to specify the game's major release version for which the
10540 graphics set was designed, which can then be used to decide if the
10541 bugfix should be used (version 4 and above) or not (version 3 or
10542 below, or if no version was specified at all, as with old sets).
10544 (The wrong/fixed animation frames can be tested with the test level set
10545 "test_gfxframe" and level "000", which contains a specially prepared
10546 custom element at level position (x/y) == (11/9) which uses the zonk
10547 animation mentioned above. Using "game.graphics_engine_version: 4"
10548 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10549 This can also be seen from the debug output for this test element.)
10552 // when a custom element is about to change (for example by change delay),
10553 // do not reset graphic animation when the custom element is moving
10554 if (game.graphics_engine_version < 4 &&
10557 ResetGfxAnimation(x, y);
10558 ResetRandomAnimationValue(x, y);
10561 if (change->pre_change_function)
10562 change->pre_change_function(x, y);
10566 ChangeDelay[x][y]--;
10568 if (ChangeDelay[x][y] != 0) // continue element change
10570 if (change->can_change)
10572 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10574 if (IS_ANIMATED(graphic))
10575 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10577 if (change->change_function)
10578 change->change_function(x, y);
10581 else // finish element change
10583 if (ChangePage[x][y] != -1) // remember page from delayed change
10585 page = ChangePage[x][y];
10586 ChangePage[x][y] = -1;
10588 change = &ei->change_page[page];
10591 if (IS_MOVING(x, y)) // never change a running system ;-)
10593 ChangeDelay[x][y] = 1; // try change after next move step
10594 ChangePage[x][y] = page; // remember page to use for change
10599 // special case: set new level random seed before changing element
10600 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10601 handle_action_before_change = TRUE;
10603 if (change->has_action && handle_action_before_change)
10604 ExecuteCustomElementAction(x, y, element, page);
10606 if (change->can_change)
10608 if (ChangeElement(x, y, element, page))
10610 if (change->post_change_function)
10611 change->post_change_function(x, y);
10615 if (change->has_action && !handle_action_before_change)
10616 ExecuteCustomElementAction(x, y, element, page);
10620 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10621 int trigger_element,
10623 int trigger_player,
10627 boolean change_done_any = FALSE;
10628 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10631 if (!(trigger_events[trigger_element][trigger_event]))
10634 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10636 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10638 int element = EL_CUSTOM_START + i;
10639 boolean change_done = FALSE;
10642 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10643 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10646 for (p = 0; p < element_info[element].num_change_pages; p++)
10648 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10650 if (change->can_change_or_has_action &&
10651 change->has_event[trigger_event] &&
10652 change->trigger_side & trigger_side &&
10653 change->trigger_player & trigger_player &&
10654 change->trigger_page & trigger_page_bits &&
10655 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10657 change->actual_trigger_element = trigger_element;
10658 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10659 change->actual_trigger_player_bits = trigger_player;
10660 change->actual_trigger_side = trigger_side;
10661 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10662 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10664 if ((change->can_change && !change_done) || change->has_action)
10668 SCAN_PLAYFIELD(x, y)
10670 if (Feld[x][y] == element)
10672 if (change->can_change && !change_done)
10674 // if element already changed in this frame, not only prevent
10675 // another element change (checked in ChangeElement()), but
10676 // also prevent additional element actions for this element
10678 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10679 !level.use_action_after_change_bug)
10682 ChangeDelay[x][y] = 1;
10683 ChangeEvent[x][y] = trigger_event;
10685 HandleElementChange(x, y, p);
10687 else if (change->has_action)
10689 // if element already changed in this frame, not only prevent
10690 // another element change (checked in ChangeElement()), but
10691 // also prevent additional element actions for this element
10693 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10694 !level.use_action_after_change_bug)
10697 ExecuteCustomElementAction(x, y, element, p);
10698 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10703 if (change->can_change)
10705 change_done = TRUE;
10706 change_done_any = TRUE;
10713 RECURSION_LOOP_DETECTION_END();
10715 return change_done_any;
10718 static boolean CheckElementChangeExt(int x, int y,
10720 int trigger_element,
10722 int trigger_player,
10725 boolean change_done = FALSE;
10728 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10729 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10732 if (Feld[x][y] == EL_BLOCKED)
10734 Blocked2Moving(x, y, &x, &y);
10735 element = Feld[x][y];
10738 // check if element has already changed or is about to change after moving
10739 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10740 Feld[x][y] != element) ||
10742 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10743 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10744 ChangePage[x][y] != -1)))
10747 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10749 for (p = 0; p < element_info[element].num_change_pages; p++)
10751 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10753 /* check trigger element for all events where the element that is checked
10754 for changing interacts with a directly adjacent element -- this is
10755 different to element changes that affect other elements to change on the
10756 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10757 boolean check_trigger_element =
10758 (trigger_event == CE_TOUCHING_X ||
10759 trigger_event == CE_HITTING_X ||
10760 trigger_event == CE_HIT_BY_X ||
10761 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10763 if (change->can_change_or_has_action &&
10764 change->has_event[trigger_event] &&
10765 change->trigger_side & trigger_side &&
10766 change->trigger_player & trigger_player &&
10767 (!check_trigger_element ||
10768 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10770 change->actual_trigger_element = trigger_element;
10771 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10772 change->actual_trigger_player_bits = trigger_player;
10773 change->actual_trigger_side = trigger_side;
10774 change->actual_trigger_ce_value = CustomValue[x][y];
10775 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10777 // special case: trigger element not at (x,y) position for some events
10778 if (check_trigger_element)
10790 { 0, 0 }, { 0, 0 }, { 0, 0 },
10794 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10795 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10797 change->actual_trigger_ce_value = CustomValue[xx][yy];
10798 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10801 if (change->can_change && !change_done)
10803 ChangeDelay[x][y] = 1;
10804 ChangeEvent[x][y] = trigger_event;
10806 HandleElementChange(x, y, p);
10808 change_done = TRUE;
10810 else if (change->has_action)
10812 ExecuteCustomElementAction(x, y, element, p);
10813 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10818 RECURSION_LOOP_DETECTION_END();
10820 return change_done;
10823 static void PlayPlayerSound(struct PlayerInfo *player)
10825 int jx = player->jx, jy = player->jy;
10826 int sound_element = player->artwork_element;
10827 int last_action = player->last_action_waiting;
10828 int action = player->action_waiting;
10830 if (player->is_waiting)
10832 if (action != last_action)
10833 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10835 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10839 if (action != last_action)
10840 StopSound(element_info[sound_element].sound[last_action]);
10842 if (last_action == ACTION_SLEEPING)
10843 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
10847 static void PlayAllPlayersSound(void)
10851 for (i = 0; i < MAX_PLAYERS; i++)
10852 if (stored_player[i].active)
10853 PlayPlayerSound(&stored_player[i]);
10856 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
10858 boolean last_waiting = player->is_waiting;
10859 int move_dir = player->MovDir;
10861 player->dir_waiting = move_dir;
10862 player->last_action_waiting = player->action_waiting;
10866 if (!last_waiting) // not waiting -> waiting
10868 player->is_waiting = TRUE;
10870 player->frame_counter_bored =
10872 game.player_boring_delay_fixed +
10873 GetSimpleRandom(game.player_boring_delay_random);
10874 player->frame_counter_sleeping =
10876 game.player_sleeping_delay_fixed +
10877 GetSimpleRandom(game.player_sleeping_delay_random);
10879 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
10882 if (game.player_sleeping_delay_fixed +
10883 game.player_sleeping_delay_random > 0 &&
10884 player->anim_delay_counter == 0 &&
10885 player->post_delay_counter == 0 &&
10886 FrameCounter >= player->frame_counter_sleeping)
10887 player->is_sleeping = TRUE;
10888 else if (game.player_boring_delay_fixed +
10889 game.player_boring_delay_random > 0 &&
10890 FrameCounter >= player->frame_counter_bored)
10891 player->is_bored = TRUE;
10893 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
10894 player->is_bored ? ACTION_BORING :
10897 if (player->is_sleeping && player->use_murphy)
10899 // special case for sleeping Murphy when leaning against non-free tile
10901 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
10902 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
10903 !IS_MOVING(player->jx - 1, player->jy)))
10904 move_dir = MV_LEFT;
10905 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
10906 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
10907 !IS_MOVING(player->jx + 1, player->jy)))
10908 move_dir = MV_RIGHT;
10910 player->is_sleeping = FALSE;
10912 player->dir_waiting = move_dir;
10915 if (player->is_sleeping)
10917 if (player->num_special_action_sleeping > 0)
10919 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10921 int last_special_action = player->special_action_sleeping;
10922 int num_special_action = player->num_special_action_sleeping;
10923 int special_action =
10924 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
10925 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
10926 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
10927 last_special_action + 1 : ACTION_SLEEPING);
10928 int special_graphic =
10929 el_act_dir2img(player->artwork_element, special_action, move_dir);
10931 player->anim_delay_counter =
10932 graphic_info[special_graphic].anim_delay_fixed +
10933 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10934 player->post_delay_counter =
10935 graphic_info[special_graphic].post_delay_fixed +
10936 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10938 player->special_action_sleeping = special_action;
10941 if (player->anim_delay_counter > 0)
10943 player->action_waiting = player->special_action_sleeping;
10944 player->anim_delay_counter--;
10946 else if (player->post_delay_counter > 0)
10948 player->post_delay_counter--;
10952 else if (player->is_bored)
10954 if (player->num_special_action_bored > 0)
10956 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10958 int special_action =
10959 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
10960 int special_graphic =
10961 el_act_dir2img(player->artwork_element, special_action, move_dir);
10963 player->anim_delay_counter =
10964 graphic_info[special_graphic].anim_delay_fixed +
10965 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10966 player->post_delay_counter =
10967 graphic_info[special_graphic].post_delay_fixed +
10968 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10970 player->special_action_bored = special_action;
10973 if (player->anim_delay_counter > 0)
10975 player->action_waiting = player->special_action_bored;
10976 player->anim_delay_counter--;
10978 else if (player->post_delay_counter > 0)
10980 player->post_delay_counter--;
10985 else if (last_waiting) // waiting -> not waiting
10987 player->is_waiting = FALSE;
10988 player->is_bored = FALSE;
10989 player->is_sleeping = FALSE;
10991 player->frame_counter_bored = -1;
10992 player->frame_counter_sleeping = -1;
10994 player->anim_delay_counter = 0;
10995 player->post_delay_counter = 0;
10997 player->dir_waiting = player->MovDir;
10998 player->action_waiting = ACTION_DEFAULT;
11000 player->special_action_bored = ACTION_DEFAULT;
11001 player->special_action_sleeping = ACTION_DEFAULT;
11005 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11007 if ((!player->is_moving && player->was_moving) ||
11008 (player->MovPos == 0 && player->was_moving) ||
11009 (player->is_snapping && !player->was_snapping) ||
11010 (player->is_dropping && !player->was_dropping))
11012 if (!CheckSaveEngineSnapshotToList())
11015 player->was_moving = FALSE;
11016 player->was_snapping = TRUE;
11017 player->was_dropping = TRUE;
11021 if (player->is_moving)
11022 player->was_moving = TRUE;
11024 if (!player->is_snapping)
11025 player->was_snapping = FALSE;
11027 if (!player->is_dropping)
11028 player->was_dropping = FALSE;
11032 static void CheckSingleStepMode(struct PlayerInfo *player)
11034 if (tape.single_step && tape.recording && !tape.pausing)
11036 /* as it is called "single step mode", just return to pause mode when the
11037 player stopped moving after one tile (or never starts moving at all) */
11038 if (!player->is_moving &&
11039 !player->is_pushing &&
11040 !player->is_dropping_pressed)
11042 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11043 SnapField(player, 0, 0); // stop snapping
11047 CheckSaveEngineSnapshot(player);
11050 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11052 int left = player_action & JOY_LEFT;
11053 int right = player_action & JOY_RIGHT;
11054 int up = player_action & JOY_UP;
11055 int down = player_action & JOY_DOWN;
11056 int button1 = player_action & JOY_BUTTON_1;
11057 int button2 = player_action & JOY_BUTTON_2;
11058 int dx = (left ? -1 : right ? 1 : 0);
11059 int dy = (up ? -1 : down ? 1 : 0);
11061 if (!player->active || tape.pausing)
11067 SnapField(player, dx, dy);
11071 DropElement(player);
11073 MovePlayer(player, dx, dy);
11076 CheckSingleStepMode(player);
11078 SetPlayerWaiting(player, FALSE);
11080 return player_action;
11084 // no actions for this player (no input at player's configured device)
11086 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11087 SnapField(player, 0, 0);
11088 CheckGravityMovementWhenNotMoving(player);
11090 if (player->MovPos == 0)
11091 SetPlayerWaiting(player, TRUE);
11093 if (player->MovPos == 0) // needed for tape.playing
11094 player->is_moving = FALSE;
11096 player->is_dropping = FALSE;
11097 player->is_dropping_pressed = FALSE;
11098 player->drop_pressed_delay = 0;
11100 CheckSingleStepMode(player);
11106 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11109 if (!tape.use_mouse)
11112 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11113 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11114 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11117 static void SetTapeActionFromMouseAction(byte *tape_action,
11118 struct MouseActionInfo *mouse_action)
11120 if (!tape.use_mouse)
11123 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11124 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11125 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11128 static void CheckLevelSolved(void)
11130 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11132 if (game_em.level_solved &&
11133 !game_em.game_over) // game won
11137 game_em.game_over = TRUE;
11139 game.all_players_gone = TRUE;
11142 if (game_em.game_over) // game lost
11143 game.all_players_gone = TRUE;
11145 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11147 if (game_sp.level_solved &&
11148 !game_sp.game_over) // game won
11152 game_sp.game_over = TRUE;
11154 game.all_players_gone = TRUE;
11157 if (game_sp.game_over) // game lost
11158 game.all_players_gone = TRUE;
11160 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11162 if (game_mm.level_solved &&
11163 !game_mm.game_over) // game won
11167 game_mm.game_over = TRUE;
11169 game.all_players_gone = TRUE;
11172 if (game_mm.game_over) // game lost
11173 game.all_players_gone = TRUE;
11177 static void CheckLevelTime(void)
11181 if (TimeFrames >= FRAMES_PER_SECOND)
11186 for (i = 0; i < MAX_PLAYERS; i++)
11188 struct PlayerInfo *player = &stored_player[i];
11190 if (SHIELD_ON(player))
11192 player->shield_normal_time_left--;
11194 if (player->shield_deadly_time_left > 0)
11195 player->shield_deadly_time_left--;
11199 if (!game.LevelSolved && !level.use_step_counter)
11207 if (TimeLeft <= 10 && setup.time_limit)
11208 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11210 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11211 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11213 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11215 if (!TimeLeft && setup.time_limit)
11217 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11218 level.native_em_level->lev->killed_out_of_time = TRUE;
11220 for (i = 0; i < MAX_PLAYERS; i++)
11221 KillPlayer(&stored_player[i]);
11224 else if (game.no_time_limit && !game.all_players_gone)
11226 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11229 level.native_em_level->lev->time =
11230 (game.no_time_limit ? TimePlayed : TimeLeft);
11233 if (tape.recording || tape.playing)
11234 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11237 if (tape.recording || tape.playing)
11238 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11240 UpdateAndDisplayGameControlValues();
11243 void AdvanceFrameAndPlayerCounters(int player_nr)
11247 // advance frame counters (global frame counter and time frame counter)
11251 // advance player counters (counters for move delay, move animation etc.)
11252 for (i = 0; i < MAX_PLAYERS; i++)
11254 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11255 int move_delay_value = stored_player[i].move_delay_value;
11256 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11258 if (!advance_player_counters) // not all players may be affected
11261 if (move_frames == 0) // less than one move per game frame
11263 int stepsize = TILEX / move_delay_value;
11264 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11265 int count = (stored_player[i].is_moving ?
11266 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11268 if (count % delay == 0)
11272 stored_player[i].Frame += move_frames;
11274 if (stored_player[i].MovPos != 0)
11275 stored_player[i].StepFrame += move_frames;
11277 if (stored_player[i].move_delay > 0)
11278 stored_player[i].move_delay--;
11280 // due to bugs in previous versions, counter must count up, not down
11281 if (stored_player[i].push_delay != -1)
11282 stored_player[i].push_delay++;
11284 if (stored_player[i].drop_delay > 0)
11285 stored_player[i].drop_delay--;
11287 if (stored_player[i].is_dropping_pressed)
11288 stored_player[i].drop_pressed_delay++;
11292 void StartGameActions(boolean init_network_game, boolean record_tape,
11295 unsigned int new_random_seed = InitRND(random_seed);
11298 TapeStartRecording(new_random_seed);
11300 if (init_network_game)
11302 SendToServer_LevelFile();
11303 SendToServer_StartPlaying();
11311 static void GameActionsExt(void)
11314 static unsigned int game_frame_delay = 0;
11316 unsigned int game_frame_delay_value;
11317 byte *recorded_player_action;
11318 byte summarized_player_action = 0;
11319 byte tape_action[MAX_PLAYERS];
11322 // detect endless loops, caused by custom element programming
11323 if (recursion_loop_detected && recursion_loop_depth == 0)
11325 char *message = getStringCat3("Internal Error! Element ",
11326 EL_NAME(recursion_loop_element),
11327 " caused endless loop! Quit the game?");
11329 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11330 EL_NAME(recursion_loop_element));
11332 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11334 recursion_loop_detected = FALSE; // if game should be continued
11341 if (game.restart_level)
11342 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11344 CheckLevelSolved();
11346 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11349 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11352 if (game_status != GAME_MODE_PLAYING) // status might have changed
11355 game_frame_delay_value =
11356 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11358 if (tape.playing && tape.warp_forward && !tape.pausing)
11359 game_frame_delay_value = 0;
11361 SetVideoFrameDelay(game_frame_delay_value);
11365 // ---------- main game synchronization point ----------
11367 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11369 printf("::: skip == %d\n", skip);
11372 // ---------- main game synchronization point ----------
11374 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11378 if (network_playing && !network_player_action_received)
11380 // try to get network player actions in time
11382 // last chance to get network player actions without main loop delay
11383 HandleNetworking();
11385 // game was quit by network peer
11386 if (game_status != GAME_MODE_PLAYING)
11389 // check if network player actions still missing and game still running
11390 if (!network_player_action_received && !checkGameEnded())
11391 return; // failed to get network player actions in time
11393 // do not yet reset "network_player_action_received" (for tape.pausing)
11399 // at this point we know that we really continue executing the game
11401 network_player_action_received = FALSE;
11403 // when playing tape, read previously recorded player input from tape data
11404 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11406 local_player->effective_mouse_action = local_player->mouse_action;
11408 if (recorded_player_action != NULL)
11409 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11410 recorded_player_action);
11412 // TapePlayAction() may return NULL when toggling to "pause before death"
11416 if (tape.set_centered_player)
11418 game.centered_player_nr_next = tape.centered_player_nr_next;
11419 game.set_centered_player = TRUE;
11422 for (i = 0; i < MAX_PLAYERS; i++)
11424 summarized_player_action |= stored_player[i].action;
11426 if (!network_playing && (game.team_mode || tape.playing))
11427 stored_player[i].effective_action = stored_player[i].action;
11430 if (network_playing && !checkGameEnded())
11431 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 (!network.enabled && !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);
11544 CheckLevelSolved();
11547 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11549 if (global.show_frames_per_second)
11551 static unsigned int fps_counter = 0;
11552 static int fps_frames = 0;
11553 unsigned int fps_delay_ms = Counter() - fps_counter;
11557 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11559 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11562 fps_counter = Counter();
11564 // always draw FPS to screen after FPS value was updated
11565 redraw_mask |= REDRAW_FPS;
11568 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11569 if (GetDrawDeactivationMask() == REDRAW_NONE)
11570 redraw_mask |= REDRAW_FPS;
11574 static void GameActions_CheckSaveEngineSnapshot(void)
11576 if (!game.snapshot.save_snapshot)
11579 // clear flag for saving snapshot _before_ saving snapshot
11580 game.snapshot.save_snapshot = FALSE;
11582 SaveEngineSnapshotToList();
11585 void GameActions(void)
11589 GameActions_CheckSaveEngineSnapshot();
11592 void GameActions_EM_Main(void)
11594 byte effective_action[MAX_PLAYERS];
11595 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11598 for (i = 0; i < MAX_PLAYERS; i++)
11599 effective_action[i] = stored_player[i].effective_action;
11601 GameActions_EM(effective_action, warp_mode);
11604 void GameActions_SP_Main(void)
11606 byte effective_action[MAX_PLAYERS];
11607 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11610 for (i = 0; i < MAX_PLAYERS; i++)
11611 effective_action[i] = stored_player[i].effective_action;
11613 GameActions_SP(effective_action, warp_mode);
11615 for (i = 0; i < MAX_PLAYERS; i++)
11617 if (stored_player[i].force_dropping)
11618 stored_player[i].action |= KEY_BUTTON_DROP;
11620 stored_player[i].force_dropping = FALSE;
11624 void GameActions_MM_Main(void)
11626 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11628 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11631 void GameActions_RND_Main(void)
11636 void GameActions_RND(void)
11638 int magic_wall_x = 0, magic_wall_y = 0;
11639 int i, x, y, element, graphic, last_gfx_frame;
11641 InitPlayfieldScanModeVars();
11643 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11645 SCAN_PLAYFIELD(x, y)
11647 ChangeCount[x][y] = 0;
11648 ChangeEvent[x][y] = -1;
11652 if (game.set_centered_player)
11654 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11656 // switching to "all players" only possible if all players fit to screen
11657 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11659 game.centered_player_nr_next = game.centered_player_nr;
11660 game.set_centered_player = FALSE;
11663 // do not switch focus to non-existing (or non-active) player
11664 if (game.centered_player_nr_next >= 0 &&
11665 !stored_player[game.centered_player_nr_next].active)
11667 game.centered_player_nr_next = game.centered_player_nr;
11668 game.set_centered_player = FALSE;
11672 if (game.set_centered_player &&
11673 ScreenMovPos == 0) // screen currently aligned at tile position
11677 if (game.centered_player_nr_next == -1)
11679 setScreenCenteredToAllPlayers(&sx, &sy);
11683 sx = stored_player[game.centered_player_nr_next].jx;
11684 sy = stored_player[game.centered_player_nr_next].jy;
11687 game.centered_player_nr = game.centered_player_nr_next;
11688 game.set_centered_player = FALSE;
11690 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11691 DrawGameDoorValues();
11694 for (i = 0; i < MAX_PLAYERS; i++)
11696 int actual_player_action = stored_player[i].effective_action;
11699 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11700 - rnd_equinox_tetrachloride 048
11701 - rnd_equinox_tetrachloride_ii 096
11702 - rnd_emanuel_schmieg 002
11703 - doctor_sloan_ww 001, 020
11705 if (stored_player[i].MovPos == 0)
11706 CheckGravityMovement(&stored_player[i]);
11709 // overwrite programmed action with tape action
11710 if (stored_player[i].programmed_action)
11711 actual_player_action = stored_player[i].programmed_action;
11713 PlayerActions(&stored_player[i], actual_player_action);
11715 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11718 ScrollScreen(NULL, SCROLL_GO_ON);
11720 /* for backwards compatibility, the following code emulates a fixed bug that
11721 occured when pushing elements (causing elements that just made their last
11722 pushing step to already (if possible) make their first falling step in the
11723 same game frame, which is bad); this code is also needed to use the famous
11724 "spring push bug" which is used in older levels and might be wanted to be
11725 used also in newer levels, but in this case the buggy pushing code is only
11726 affecting the "spring" element and no other elements */
11728 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11730 for (i = 0; i < MAX_PLAYERS; i++)
11732 struct PlayerInfo *player = &stored_player[i];
11733 int x = player->jx;
11734 int y = player->jy;
11736 if (player->active && player->is_pushing && player->is_moving &&
11738 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11739 Feld[x][y] == EL_SPRING))
11741 ContinueMoving(x, y);
11743 // continue moving after pushing (this is actually a bug)
11744 if (!IS_MOVING(x, y))
11745 Stop[x][y] = FALSE;
11750 SCAN_PLAYFIELD(x, y)
11752 Last[x][y] = Feld[x][y];
11754 ChangeCount[x][y] = 0;
11755 ChangeEvent[x][y] = -1;
11757 // this must be handled before main playfield loop
11758 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11761 if (MovDelay[x][y] <= 0)
11765 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11768 if (MovDelay[x][y] <= 0)
11771 TEST_DrawLevelField(x, y);
11773 TestIfElementTouchesCustomElement(x, y); // for empty space
11778 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11780 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11781 printf("GameActions(): This should never happen!\n");
11783 ChangePage[x][y] = -1;
11787 Stop[x][y] = FALSE;
11788 if (WasJustMoving[x][y] > 0)
11789 WasJustMoving[x][y]--;
11790 if (WasJustFalling[x][y] > 0)
11791 WasJustFalling[x][y]--;
11792 if (CheckCollision[x][y] > 0)
11793 CheckCollision[x][y]--;
11794 if (CheckImpact[x][y] > 0)
11795 CheckImpact[x][y]--;
11799 /* reset finished pushing action (not done in ContinueMoving() to allow
11800 continuous pushing animation for elements with zero push delay) */
11801 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11803 ResetGfxAnimation(x, y);
11804 TEST_DrawLevelField(x, y);
11808 if (IS_BLOCKED(x, y))
11812 Blocked2Moving(x, y, &oldx, &oldy);
11813 if (!IS_MOVING(oldx, oldy))
11815 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11816 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11817 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11818 printf("GameActions(): This should never happen!\n");
11824 SCAN_PLAYFIELD(x, y)
11826 element = Feld[x][y];
11827 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11828 last_gfx_frame = GfxFrame[x][y];
11830 ResetGfxFrame(x, y);
11832 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
11833 DrawLevelGraphicAnimation(x, y, graphic);
11835 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
11836 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
11837 ResetRandomAnimationValue(x, y);
11839 SetRandomAnimationValue(x, y);
11841 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
11843 if (IS_INACTIVE(element))
11845 if (IS_ANIMATED(graphic))
11846 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11851 // this may take place after moving, so 'element' may have changed
11852 if (IS_CHANGING(x, y) &&
11853 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
11855 int page = element_info[element].event_page_nr[CE_DELAY];
11857 HandleElementChange(x, y, page);
11859 element = Feld[x][y];
11860 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11863 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
11867 element = Feld[x][y];
11868 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11870 if (IS_ANIMATED(graphic) &&
11871 !IS_MOVING(x, y) &&
11873 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11875 if (IS_GEM(element) || element == EL_SP_INFOTRON)
11876 TEST_DrawTwinkleOnField(x, y);
11878 else if (element == EL_ACID)
11881 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11883 else if ((element == EL_EXIT_OPEN ||
11884 element == EL_EM_EXIT_OPEN ||
11885 element == EL_SP_EXIT_OPEN ||
11886 element == EL_STEEL_EXIT_OPEN ||
11887 element == EL_EM_STEEL_EXIT_OPEN ||
11888 element == EL_SP_TERMINAL ||
11889 element == EL_SP_TERMINAL_ACTIVE ||
11890 element == EL_EXTRA_TIME ||
11891 element == EL_SHIELD_NORMAL ||
11892 element == EL_SHIELD_DEADLY) &&
11893 IS_ANIMATED(graphic))
11894 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11895 else if (IS_MOVING(x, y))
11896 ContinueMoving(x, y);
11897 else if (IS_ACTIVE_BOMB(element))
11898 CheckDynamite(x, y);
11899 else if (element == EL_AMOEBA_GROWING)
11900 AmoebeWaechst(x, y);
11901 else if (element == EL_AMOEBA_SHRINKING)
11902 AmoebaDisappearing(x, y);
11904 #if !USE_NEW_AMOEBA_CODE
11905 else if (IS_AMOEBALIVE(element))
11906 AmoebeAbleger(x, y);
11909 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
11911 else if (element == EL_EXIT_CLOSED)
11913 else if (element == EL_EM_EXIT_CLOSED)
11915 else if (element == EL_STEEL_EXIT_CLOSED)
11916 CheckExitSteel(x, y);
11917 else if (element == EL_EM_STEEL_EXIT_CLOSED)
11918 CheckExitSteelEM(x, y);
11919 else if (element == EL_SP_EXIT_CLOSED)
11921 else if (element == EL_EXPANDABLE_WALL_GROWING ||
11922 element == EL_EXPANDABLE_STEELWALL_GROWING)
11923 MauerWaechst(x, y);
11924 else if (element == EL_EXPANDABLE_WALL ||
11925 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
11926 element == EL_EXPANDABLE_WALL_VERTICAL ||
11927 element == EL_EXPANDABLE_WALL_ANY ||
11928 element == EL_BD_EXPANDABLE_WALL)
11929 MauerAbleger(x, y);
11930 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
11931 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
11932 element == EL_EXPANDABLE_STEELWALL_ANY)
11933 MauerAblegerStahl(x, y);
11934 else if (element == EL_FLAMES)
11935 CheckForDragon(x, y);
11936 else if (element == EL_EXPLOSION)
11937 ; // drawing of correct explosion animation is handled separately
11938 else if (element == EL_ELEMENT_SNAPPING ||
11939 element == EL_DIAGONAL_SHRINKING ||
11940 element == EL_DIAGONAL_GROWING)
11942 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
11944 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11946 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
11947 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11949 if (IS_BELT_ACTIVE(element))
11950 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
11952 if (game.magic_wall_active)
11954 int jx = local_player->jx, jy = local_player->jy;
11956 // play the element sound at the position nearest to the player
11957 if ((element == EL_MAGIC_WALL_FULL ||
11958 element == EL_MAGIC_WALL_ACTIVE ||
11959 element == EL_MAGIC_WALL_EMPTYING ||
11960 element == EL_BD_MAGIC_WALL_FULL ||
11961 element == EL_BD_MAGIC_WALL_ACTIVE ||
11962 element == EL_BD_MAGIC_WALL_EMPTYING ||
11963 element == EL_DC_MAGIC_WALL_FULL ||
11964 element == EL_DC_MAGIC_WALL_ACTIVE ||
11965 element == EL_DC_MAGIC_WALL_EMPTYING) &&
11966 ABS(x-jx) + ABS(y-jy) < ABS(magic_wall_x-jx) + ABS(magic_wall_y-jy))
11974 #if USE_NEW_AMOEBA_CODE
11975 // new experimental amoeba growth stuff
11976 if (!(FrameCounter % 8))
11978 static unsigned int random = 1684108901;
11980 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
11982 x = RND(lev_fieldx);
11983 y = RND(lev_fieldy);
11984 element = Feld[x][y];
11986 if (!IS_PLAYER(x,y) &&
11987 (element == EL_EMPTY ||
11988 CAN_GROW_INTO(element) ||
11989 element == EL_QUICKSAND_EMPTY ||
11990 element == EL_QUICKSAND_FAST_EMPTY ||
11991 element == EL_ACID_SPLASH_LEFT ||
11992 element == EL_ACID_SPLASH_RIGHT))
11994 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
11995 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
11996 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
11997 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
11998 Feld[x][y] = EL_AMOEBA_DROP;
12001 random = random * 129 + 1;
12006 game.explosions_delayed = FALSE;
12008 SCAN_PLAYFIELD(x, y)
12010 element = Feld[x][y];
12012 if (ExplodeField[x][y])
12013 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12014 else if (element == EL_EXPLOSION)
12015 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12017 ExplodeField[x][y] = EX_TYPE_NONE;
12020 game.explosions_delayed = TRUE;
12022 if (game.magic_wall_active)
12024 if (!(game.magic_wall_time_left % 4))
12026 int element = Feld[magic_wall_x][magic_wall_y];
12028 if (element == EL_BD_MAGIC_WALL_FULL ||
12029 element == EL_BD_MAGIC_WALL_ACTIVE ||
12030 element == EL_BD_MAGIC_WALL_EMPTYING)
12031 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12032 else if (element == EL_DC_MAGIC_WALL_FULL ||
12033 element == EL_DC_MAGIC_WALL_ACTIVE ||
12034 element == EL_DC_MAGIC_WALL_EMPTYING)
12035 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12037 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12040 if (game.magic_wall_time_left > 0)
12042 game.magic_wall_time_left--;
12044 if (!game.magic_wall_time_left)
12046 SCAN_PLAYFIELD(x, y)
12048 element = Feld[x][y];
12050 if (element == EL_MAGIC_WALL_ACTIVE ||
12051 element == EL_MAGIC_WALL_FULL)
12053 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12054 TEST_DrawLevelField(x, y);
12056 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12057 element == EL_BD_MAGIC_WALL_FULL)
12059 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12060 TEST_DrawLevelField(x, y);
12062 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12063 element == EL_DC_MAGIC_WALL_FULL)
12065 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12066 TEST_DrawLevelField(x, y);
12070 game.magic_wall_active = FALSE;
12075 if (game.light_time_left > 0)
12077 game.light_time_left--;
12079 if (game.light_time_left == 0)
12080 RedrawAllLightSwitchesAndInvisibleElements();
12083 if (game.timegate_time_left > 0)
12085 game.timegate_time_left--;
12087 if (game.timegate_time_left == 0)
12088 CloseAllOpenTimegates();
12091 if (game.lenses_time_left > 0)
12093 game.lenses_time_left--;
12095 if (game.lenses_time_left == 0)
12096 RedrawAllInvisibleElementsForLenses();
12099 if (game.magnify_time_left > 0)
12101 game.magnify_time_left--;
12103 if (game.magnify_time_left == 0)
12104 RedrawAllInvisibleElementsForMagnifier();
12107 for (i = 0; i < MAX_PLAYERS; i++)
12109 struct PlayerInfo *player = &stored_player[i];
12111 if (SHIELD_ON(player))
12113 if (player->shield_deadly_time_left)
12114 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12115 else if (player->shield_normal_time_left)
12116 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12120 #if USE_DELAYED_GFX_REDRAW
12121 SCAN_PLAYFIELD(x, y)
12123 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12125 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12126 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12128 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12129 DrawLevelField(x, y);
12131 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12132 DrawLevelFieldCrumbled(x, y);
12134 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12135 DrawLevelFieldCrumbledNeighbours(x, y);
12137 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12138 DrawTwinkleOnField(x, y);
12141 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12146 PlayAllPlayersSound();
12148 if (local_player->show_envelope != 0 && local_player->MovPos == 0)
12150 ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
12152 local_player->show_envelope = 0;
12155 // use random number generator in every frame to make it less predictable
12156 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12160 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12162 int min_x = x, min_y = y, max_x = x, max_y = y;
12165 for (i = 0; i < MAX_PLAYERS; i++)
12167 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12169 if (!stored_player[i].active || &stored_player[i] == player)
12172 min_x = MIN(min_x, jx);
12173 min_y = MIN(min_y, jy);
12174 max_x = MAX(max_x, jx);
12175 max_y = MAX(max_y, jy);
12178 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12181 static boolean AllPlayersInVisibleScreen(void)
12185 for (i = 0; i < MAX_PLAYERS; i++)
12187 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12189 if (!stored_player[i].active)
12192 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12199 void ScrollLevel(int dx, int dy)
12201 int scroll_offset = 2 * TILEX_VAR;
12204 BlitBitmap(drawto_field, drawto_field,
12205 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12206 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12207 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12208 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12209 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12210 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12214 x = (dx == 1 ? BX1 : BX2);
12215 for (y = BY1; y <= BY2; y++)
12216 DrawScreenField(x, y);
12221 y = (dy == 1 ? BY1 : BY2);
12222 for (x = BX1; x <= BX2; x++)
12223 DrawScreenField(x, y);
12226 redraw_mask |= REDRAW_FIELD;
12229 static boolean canFallDown(struct PlayerInfo *player)
12231 int jx = player->jx, jy = player->jy;
12233 return (IN_LEV_FIELD(jx, jy + 1) &&
12234 (IS_FREE(jx, jy + 1) ||
12235 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12236 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12237 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12240 static boolean canPassField(int x, int y, int move_dir)
12242 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12243 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12244 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12245 int nextx = x + dx;
12246 int nexty = y + dy;
12247 int element = Feld[x][y];
12249 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12250 !CAN_MOVE(element) &&
12251 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12252 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12253 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12256 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12258 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12259 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12260 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12264 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12265 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12266 (IS_DIGGABLE(Feld[newx][newy]) ||
12267 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12268 canPassField(newx, newy, move_dir)));
12271 static void CheckGravityMovement(struct PlayerInfo *player)
12273 if (player->gravity && !player->programmed_action)
12275 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12276 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12277 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12278 int jx = player->jx, jy = player->jy;
12279 boolean player_is_moving_to_valid_field =
12280 (!player_is_snapping &&
12281 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12282 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12283 boolean player_can_fall_down = canFallDown(player);
12285 if (player_can_fall_down &&
12286 !player_is_moving_to_valid_field)
12287 player->programmed_action = MV_DOWN;
12291 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12293 return CheckGravityMovement(player);
12295 if (player->gravity && !player->programmed_action)
12297 int jx = player->jx, jy = player->jy;
12298 boolean field_under_player_is_free =
12299 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12300 boolean player_is_standing_on_valid_field =
12301 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12302 (IS_WALKABLE(Feld[jx][jy]) &&
12303 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12305 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12306 player->programmed_action = MV_DOWN;
12311 MovePlayerOneStep()
12312 -----------------------------------------------------------------------------
12313 dx, dy: direction (non-diagonal) to try to move the player to
12314 real_dx, real_dy: direction as read from input device (can be diagonal)
12317 boolean MovePlayerOneStep(struct PlayerInfo *player,
12318 int dx, int dy, int real_dx, int real_dy)
12320 int jx = player->jx, jy = player->jy;
12321 int new_jx = jx + dx, new_jy = jy + dy;
12323 boolean player_can_move = !player->cannot_move;
12325 if (!player->active || (!dx && !dy))
12326 return MP_NO_ACTION;
12328 player->MovDir = (dx < 0 ? MV_LEFT :
12329 dx > 0 ? MV_RIGHT :
12331 dy > 0 ? MV_DOWN : MV_NONE);
12333 if (!IN_LEV_FIELD(new_jx, new_jy))
12334 return MP_NO_ACTION;
12336 if (!player_can_move)
12338 if (player->MovPos == 0)
12340 player->is_moving = FALSE;
12341 player->is_digging = FALSE;
12342 player->is_collecting = FALSE;
12343 player->is_snapping = FALSE;
12344 player->is_pushing = FALSE;
12348 if (!network.enabled && game.centered_player_nr == -1 &&
12349 !AllPlayersInSight(player, new_jx, new_jy))
12350 return MP_NO_ACTION;
12352 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12353 if (can_move != MP_MOVING)
12356 // check if DigField() has caused relocation of the player
12357 if (player->jx != jx || player->jy != jy)
12358 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12360 StorePlayer[jx][jy] = 0;
12361 player->last_jx = jx;
12362 player->last_jy = jy;
12363 player->jx = new_jx;
12364 player->jy = new_jy;
12365 StorePlayer[new_jx][new_jy] = player->element_nr;
12367 if (player->move_delay_value_next != -1)
12369 player->move_delay_value = player->move_delay_value_next;
12370 player->move_delay_value_next = -1;
12374 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12376 player->step_counter++;
12378 PlayerVisit[jx][jy] = FrameCounter;
12380 player->is_moving = TRUE;
12383 // should better be called in MovePlayer(), but this breaks some tapes
12384 ScrollPlayer(player, SCROLL_INIT);
12390 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12392 int jx = player->jx, jy = player->jy;
12393 int old_jx = jx, old_jy = jy;
12394 int moved = MP_NO_ACTION;
12396 if (!player->active)
12401 if (player->MovPos == 0)
12403 player->is_moving = FALSE;
12404 player->is_digging = FALSE;
12405 player->is_collecting = FALSE;
12406 player->is_snapping = FALSE;
12407 player->is_pushing = FALSE;
12413 if (player->move_delay > 0)
12416 player->move_delay = -1; // set to "uninitialized" value
12418 // store if player is automatically moved to next field
12419 player->is_auto_moving = (player->programmed_action != MV_NONE);
12421 // remove the last programmed player action
12422 player->programmed_action = 0;
12424 if (player->MovPos)
12426 // should only happen if pre-1.2 tape recordings are played
12427 // this is only for backward compatibility
12429 int original_move_delay_value = player->move_delay_value;
12432 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12436 // scroll remaining steps with finest movement resolution
12437 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12439 while (player->MovPos)
12441 ScrollPlayer(player, SCROLL_GO_ON);
12442 ScrollScreen(NULL, SCROLL_GO_ON);
12444 AdvanceFrameAndPlayerCounters(player->index_nr);
12447 BackToFront_WithFrameDelay(0);
12450 player->move_delay_value = original_move_delay_value;
12453 player->is_active = FALSE;
12455 if (player->last_move_dir & MV_HORIZONTAL)
12457 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12458 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12462 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12463 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12466 if (!moved && !player->is_active)
12468 player->is_moving = FALSE;
12469 player->is_digging = FALSE;
12470 player->is_collecting = FALSE;
12471 player->is_snapping = FALSE;
12472 player->is_pushing = FALSE;
12478 if (moved & MP_MOVING && !ScreenMovPos &&
12479 (player->index_nr == game.centered_player_nr ||
12480 game.centered_player_nr == -1))
12482 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12483 int offset = game.scroll_delay_value;
12485 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12487 // actual player has left the screen -- scroll in that direction
12488 if (jx != old_jx) // player has moved horizontally
12489 scroll_x += (jx - old_jx);
12490 else // player has moved vertically
12491 scroll_y += (jy - old_jy);
12495 if (jx != old_jx) // player has moved horizontally
12497 if ((player->MovDir == MV_LEFT && scroll_x > jx - MIDPOSX + offset) ||
12498 (player->MovDir == MV_RIGHT && scroll_x < jx - MIDPOSX - offset))
12499 scroll_x = jx-MIDPOSX + (scroll_x < jx-MIDPOSX ? -offset : +offset);
12501 // don't scroll over playfield boundaries
12502 if (scroll_x < SBX_Left || scroll_x > SBX_Right)
12503 scroll_x = (scroll_x < SBX_Left ? SBX_Left : SBX_Right);
12505 // don't scroll more than one field at a time
12506 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12508 // don't scroll against the player's moving direction
12509 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12510 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12511 scroll_x = old_scroll_x;
12513 else // player has moved vertically
12515 if ((player->MovDir == MV_UP && scroll_y > jy - MIDPOSY + offset) ||
12516 (player->MovDir == MV_DOWN && scroll_y < jy - MIDPOSY - offset))
12517 scroll_y = jy-MIDPOSY + (scroll_y < jy-MIDPOSY ? -offset : +offset);
12519 // don't scroll over playfield boundaries
12520 if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
12521 scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
12523 // don't scroll more than one field at a time
12524 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12526 // don't scroll against the player's moving direction
12527 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12528 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12529 scroll_y = old_scroll_y;
12533 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12535 if (!network.enabled && game.centered_player_nr == -1 &&
12536 !AllPlayersInVisibleScreen())
12538 scroll_x = old_scroll_x;
12539 scroll_y = old_scroll_y;
12543 ScrollScreen(player, SCROLL_INIT);
12544 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12549 player->StepFrame = 0;
12551 if (moved & MP_MOVING)
12553 if (old_jx != jx && old_jy == jy)
12554 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12555 else if (old_jx == jx && old_jy != jy)
12556 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12558 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12560 player->last_move_dir = player->MovDir;
12561 player->is_moving = TRUE;
12562 player->is_snapping = FALSE;
12563 player->is_switching = FALSE;
12564 player->is_dropping = FALSE;
12565 player->is_dropping_pressed = FALSE;
12566 player->drop_pressed_delay = 0;
12569 // should better be called here than above, but this breaks some tapes
12570 ScrollPlayer(player, SCROLL_INIT);
12575 CheckGravityMovementWhenNotMoving(player);
12577 player->is_moving = FALSE;
12579 /* at this point, the player is allowed to move, but cannot move right now
12580 (e.g. because of something blocking the way) -- ensure that the player
12581 is also allowed to move in the next frame (in old versions before 3.1.1,
12582 the player was forced to wait again for eight frames before next try) */
12584 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12585 player->move_delay = 0; // allow direct movement in the next frame
12588 if (player->move_delay == -1) // not yet initialized by DigField()
12589 player->move_delay = player->move_delay_value;
12591 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12593 TestIfPlayerTouchesBadThing(jx, jy);
12594 TestIfPlayerTouchesCustomElement(jx, jy);
12597 if (!player->active)
12598 RemovePlayer(player);
12603 void ScrollPlayer(struct PlayerInfo *player, int mode)
12605 int jx = player->jx, jy = player->jy;
12606 int last_jx = player->last_jx, last_jy = player->last_jy;
12607 int move_stepsize = TILEX / player->move_delay_value;
12609 if (!player->active)
12612 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12615 if (mode == SCROLL_INIT)
12617 player->actual_frame_counter = FrameCounter;
12618 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12620 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12621 Feld[last_jx][last_jy] == EL_EMPTY)
12623 int last_field_block_delay = 0; // start with no blocking at all
12624 int block_delay_adjustment = player->block_delay_adjustment;
12626 // if player blocks last field, add delay for exactly one move
12627 if (player->block_last_field)
12629 last_field_block_delay += player->move_delay_value;
12631 // when blocking enabled, prevent moving up despite gravity
12632 if (player->gravity && player->MovDir == MV_UP)
12633 block_delay_adjustment = -1;
12636 // add block delay adjustment (also possible when not blocking)
12637 last_field_block_delay += block_delay_adjustment;
12639 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12640 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12643 if (player->MovPos != 0) // player has not yet reached destination
12646 else if (!FrameReached(&player->actual_frame_counter, 1))
12649 if (player->MovPos != 0)
12651 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12652 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12654 // before DrawPlayer() to draw correct player graphic for this case
12655 if (player->MovPos == 0)
12656 CheckGravityMovement(player);
12659 if (player->MovPos == 0) // player reached destination field
12661 if (player->move_delay_reset_counter > 0)
12663 player->move_delay_reset_counter--;
12665 if (player->move_delay_reset_counter == 0)
12667 // continue with normal speed after quickly moving through gate
12668 HALVE_PLAYER_SPEED(player);
12670 // be able to make the next move without delay
12671 player->move_delay = 0;
12675 player->last_jx = jx;
12676 player->last_jy = jy;
12678 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12679 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12680 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12681 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12682 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12683 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12684 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12685 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12687 ExitPlayer(player);
12689 if (game.players_still_needed == 0 &&
12690 (game.friends_still_needed == 0 ||
12691 IS_SP_ELEMENT(Feld[jx][jy])))
12695 // this breaks one level: "machine", level 000
12697 int move_direction = player->MovDir;
12698 int enter_side = MV_DIR_OPPOSITE(move_direction);
12699 int leave_side = move_direction;
12700 int old_jx = last_jx;
12701 int old_jy = last_jy;
12702 int old_element = Feld[old_jx][old_jy];
12703 int new_element = Feld[jx][jy];
12705 if (IS_CUSTOM_ELEMENT(old_element))
12706 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12708 player->index_bit, leave_side);
12710 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12711 CE_PLAYER_LEAVES_X,
12712 player->index_bit, leave_side);
12714 if (IS_CUSTOM_ELEMENT(new_element))
12715 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12716 player->index_bit, enter_side);
12718 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12719 CE_PLAYER_ENTERS_X,
12720 player->index_bit, enter_side);
12722 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12723 CE_MOVE_OF_X, move_direction);
12726 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12728 TestIfPlayerTouchesBadThing(jx, jy);
12729 TestIfPlayerTouchesCustomElement(jx, jy);
12731 /* needed because pushed element has not yet reached its destination,
12732 so it would trigger a change event at its previous field location */
12733 if (!player->is_pushing)
12734 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12736 if (!player->active)
12737 RemovePlayer(player);
12740 if (!game.LevelSolved && level.use_step_counter)
12750 if (TimeLeft <= 10 && setup.time_limit)
12751 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12753 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12755 DisplayGameControlValues();
12757 if (!TimeLeft && setup.time_limit)
12758 for (i = 0; i < MAX_PLAYERS; i++)
12759 KillPlayer(&stored_player[i]);
12761 else if (game.no_time_limit && !game.all_players_gone)
12763 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12765 DisplayGameControlValues();
12769 if (tape.single_step && tape.recording && !tape.pausing &&
12770 !player->programmed_action)
12771 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12773 if (!player->programmed_action)
12774 CheckSaveEngineSnapshot(player);
12778 void ScrollScreen(struct PlayerInfo *player, int mode)
12780 static unsigned int screen_frame_counter = 0;
12782 if (mode == SCROLL_INIT)
12784 // set scrolling step size according to actual player's moving speed
12785 ScrollStepSize = TILEX / player->move_delay_value;
12787 screen_frame_counter = FrameCounter;
12788 ScreenMovDir = player->MovDir;
12789 ScreenMovPos = player->MovPos;
12790 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12793 else if (!FrameReached(&screen_frame_counter, 1))
12798 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
12799 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12800 redraw_mask |= REDRAW_FIELD;
12803 ScreenMovDir = MV_NONE;
12806 void TestIfPlayerTouchesCustomElement(int x, int y)
12808 static int xy[4][2] =
12815 static int trigger_sides[4][2] =
12817 // center side border side
12818 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12819 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12820 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12821 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12823 static int touch_dir[4] =
12825 MV_LEFT | MV_RIGHT,
12830 int center_element = Feld[x][y]; // should always be non-moving!
12833 for (i = 0; i < NUM_DIRECTIONS; i++)
12835 int xx = x + xy[i][0];
12836 int yy = y + xy[i][1];
12837 int center_side = trigger_sides[i][0];
12838 int border_side = trigger_sides[i][1];
12839 int border_element;
12841 if (!IN_LEV_FIELD(xx, yy))
12844 if (IS_PLAYER(x, y)) // player found at center element
12846 struct PlayerInfo *player = PLAYERINFO(x, y);
12848 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12849 border_element = Feld[xx][yy]; // may be moving!
12850 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12851 border_element = Feld[xx][yy];
12852 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
12853 border_element = MovingOrBlocked2Element(xx, yy);
12855 continue; // center and border element do not touch
12857 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
12858 player->index_bit, border_side);
12859 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
12860 CE_PLAYER_TOUCHES_X,
12861 player->index_bit, border_side);
12864 /* use player element that is initially defined in the level playfield,
12865 not the player element that corresponds to the runtime player number
12866 (example: a level that contains EL_PLAYER_3 as the only player would
12867 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12868 int player_element = PLAYERINFO(x, y)->initial_element;
12870 CheckElementChangeBySide(xx, yy, border_element, player_element,
12871 CE_TOUCHING_X, border_side);
12874 else if (IS_PLAYER(xx, yy)) // player found at border element
12876 struct PlayerInfo *player = PLAYERINFO(xx, yy);
12878 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12880 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
12881 continue; // center and border element do not touch
12884 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
12885 player->index_bit, center_side);
12886 CheckTriggeredElementChangeByPlayer(x, y, center_element,
12887 CE_PLAYER_TOUCHES_X,
12888 player->index_bit, center_side);
12891 /* use player element that is initially defined in the level playfield,
12892 not the player element that corresponds to the runtime player number
12893 (example: a level that contains EL_PLAYER_3 as the only player would
12894 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12895 int player_element = PLAYERINFO(xx, yy)->initial_element;
12897 CheckElementChangeBySide(x, y, center_element, player_element,
12898 CE_TOUCHING_X, center_side);
12906 void TestIfElementTouchesCustomElement(int x, int y)
12908 static int xy[4][2] =
12915 static int trigger_sides[4][2] =
12917 // center side border side
12918 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12919 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12920 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12921 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12923 static int touch_dir[4] =
12925 MV_LEFT | MV_RIGHT,
12930 boolean change_center_element = FALSE;
12931 int center_element = Feld[x][y]; // should always be non-moving!
12932 int border_element_old[NUM_DIRECTIONS];
12935 for (i = 0; i < NUM_DIRECTIONS; i++)
12937 int xx = x + xy[i][0];
12938 int yy = y + xy[i][1];
12939 int border_element;
12941 border_element_old[i] = -1;
12943 if (!IN_LEV_FIELD(xx, yy))
12946 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12947 border_element = Feld[xx][yy]; // may be moving!
12948 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12949 border_element = Feld[xx][yy];
12950 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
12951 border_element = MovingOrBlocked2Element(xx, yy);
12953 continue; // center and border element do not touch
12955 border_element_old[i] = border_element;
12958 for (i = 0; i < NUM_DIRECTIONS; i++)
12960 int xx = x + xy[i][0];
12961 int yy = y + xy[i][1];
12962 int center_side = trigger_sides[i][0];
12963 int border_element = border_element_old[i];
12965 if (border_element == -1)
12968 // check for change of border element
12969 CheckElementChangeBySide(xx, yy, border_element, center_element,
12970 CE_TOUCHING_X, center_side);
12972 // (center element cannot be player, so we dont have to check this here)
12975 for (i = 0; i < NUM_DIRECTIONS; i++)
12977 int xx = x + xy[i][0];
12978 int yy = y + xy[i][1];
12979 int border_side = trigger_sides[i][1];
12980 int border_element = border_element_old[i];
12982 if (border_element == -1)
12985 // check for change of center element (but change it only once)
12986 if (!change_center_element)
12987 change_center_element =
12988 CheckElementChangeBySide(x, y, center_element, border_element,
12989 CE_TOUCHING_X, border_side);
12991 if (IS_PLAYER(xx, yy))
12993 /* use player element that is initially defined in the level playfield,
12994 not the player element that corresponds to the runtime player number
12995 (example: a level that contains EL_PLAYER_3 as the only player would
12996 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12997 int player_element = PLAYERINFO(xx, yy)->initial_element;
12999 CheckElementChangeBySide(x, y, center_element, player_element,
13000 CE_TOUCHING_X, border_side);
13005 void TestIfElementHitsCustomElement(int x, int y, int direction)
13007 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13008 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13009 int hitx = x + dx, hity = y + dy;
13010 int hitting_element = Feld[x][y];
13011 int touched_element;
13013 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13016 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13017 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13019 if (IN_LEV_FIELD(hitx, hity))
13021 int opposite_direction = MV_DIR_OPPOSITE(direction);
13022 int hitting_side = direction;
13023 int touched_side = opposite_direction;
13024 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13025 MovDir[hitx][hity] != direction ||
13026 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13032 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13033 CE_HITTING_X, touched_side);
13035 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13036 CE_HIT_BY_X, hitting_side);
13038 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13039 CE_HIT_BY_SOMETHING, opposite_direction);
13041 if (IS_PLAYER(hitx, hity))
13043 /* use player element that is initially defined in the level playfield,
13044 not the player element that corresponds to the runtime player number
13045 (example: a level that contains EL_PLAYER_3 as the only player would
13046 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13047 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13049 CheckElementChangeBySide(x, y, hitting_element, player_element,
13050 CE_HITTING_X, touched_side);
13055 // "hitting something" is also true when hitting the playfield border
13056 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13057 CE_HITTING_SOMETHING, direction);
13060 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13062 int i, kill_x = -1, kill_y = -1;
13064 int bad_element = -1;
13065 static int test_xy[4][2] =
13072 static int test_dir[4] =
13080 for (i = 0; i < NUM_DIRECTIONS; i++)
13082 int test_x, test_y, test_move_dir, test_element;
13084 test_x = good_x + test_xy[i][0];
13085 test_y = good_y + test_xy[i][1];
13087 if (!IN_LEV_FIELD(test_x, test_y))
13091 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13093 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13095 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13096 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13098 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13099 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13103 bad_element = test_element;
13109 if (kill_x != -1 || kill_y != -1)
13111 if (IS_PLAYER(good_x, good_y))
13113 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13115 if (player->shield_deadly_time_left > 0 &&
13116 !IS_INDESTRUCTIBLE(bad_element))
13117 Bang(kill_x, kill_y);
13118 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13119 KillPlayer(player);
13122 Bang(good_x, good_y);
13126 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13128 int i, kill_x = -1, kill_y = -1;
13129 int bad_element = Feld[bad_x][bad_y];
13130 static int test_xy[4][2] =
13137 static int touch_dir[4] =
13139 MV_LEFT | MV_RIGHT,
13144 static int test_dir[4] =
13152 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13155 for (i = 0; i < NUM_DIRECTIONS; i++)
13157 int test_x, test_y, test_move_dir, test_element;
13159 test_x = bad_x + test_xy[i][0];
13160 test_y = bad_y + test_xy[i][1];
13162 if (!IN_LEV_FIELD(test_x, test_y))
13166 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13168 test_element = Feld[test_x][test_y];
13170 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13171 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13173 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13174 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13176 // good thing is player or penguin that does not move away
13177 if (IS_PLAYER(test_x, test_y))
13179 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13181 if (bad_element == EL_ROBOT && player->is_moving)
13182 continue; // robot does not kill player if he is moving
13184 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13186 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13187 continue; // center and border element do not touch
13195 else if (test_element == EL_PENGUIN)
13205 if (kill_x != -1 || kill_y != -1)
13207 if (IS_PLAYER(kill_x, kill_y))
13209 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13211 if (player->shield_deadly_time_left > 0 &&
13212 !IS_INDESTRUCTIBLE(bad_element))
13213 Bang(bad_x, bad_y);
13214 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13215 KillPlayer(player);
13218 Bang(kill_x, kill_y);
13222 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13224 int bad_element = Feld[bad_x][bad_y];
13225 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13226 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13227 int test_x = bad_x + dx, test_y = bad_y + dy;
13228 int test_move_dir, test_element;
13229 int kill_x = -1, kill_y = -1;
13231 if (!IN_LEV_FIELD(test_x, test_y))
13235 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13237 test_element = Feld[test_x][test_y];
13239 if (test_move_dir != bad_move_dir)
13241 // good thing can be player or penguin that does not move away
13242 if (IS_PLAYER(test_x, test_y))
13244 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13246 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13247 player as being hit when he is moving towards the bad thing, because
13248 the "get hit by" condition would be lost after the player stops) */
13249 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13250 return; // player moves away from bad thing
13255 else if (test_element == EL_PENGUIN)
13262 if (kill_x != -1 || kill_y != -1)
13264 if (IS_PLAYER(kill_x, kill_y))
13266 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13268 if (player->shield_deadly_time_left > 0 &&
13269 !IS_INDESTRUCTIBLE(bad_element))
13270 Bang(bad_x, bad_y);
13271 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13272 KillPlayer(player);
13275 Bang(kill_x, kill_y);
13279 void TestIfPlayerTouchesBadThing(int x, int y)
13281 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13284 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13286 TestIfGoodThingHitsBadThing(x, y, move_dir);
13289 void TestIfBadThingTouchesPlayer(int x, int y)
13291 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13294 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13296 TestIfBadThingHitsGoodThing(x, y, move_dir);
13299 void TestIfFriendTouchesBadThing(int x, int y)
13301 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13304 void TestIfBadThingTouchesFriend(int x, int y)
13306 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13309 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13311 int i, kill_x = bad_x, kill_y = bad_y;
13312 static int xy[4][2] =
13320 for (i = 0; i < NUM_DIRECTIONS; i++)
13324 x = bad_x + xy[i][0];
13325 y = bad_y + xy[i][1];
13326 if (!IN_LEV_FIELD(x, y))
13329 element = Feld[x][y];
13330 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13331 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13339 if (kill_x != bad_x || kill_y != bad_y)
13340 Bang(bad_x, bad_y);
13343 void KillPlayer(struct PlayerInfo *player)
13345 int jx = player->jx, jy = player->jy;
13347 if (!player->active)
13351 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13352 player->killed, player->active, player->reanimated);
13355 /* the following code was introduced to prevent an infinite loop when calling
13357 -> CheckTriggeredElementChangeExt()
13358 -> ExecuteCustomElementAction()
13360 -> (infinitely repeating the above sequence of function calls)
13361 which occurs when killing the player while having a CE with the setting
13362 "kill player X when explosion of <player X>"; the solution using a new
13363 field "player->killed" was chosen for backwards compatibility, although
13364 clever use of the fields "player->active" etc. would probably also work */
13366 if (player->killed)
13370 player->killed = TRUE;
13372 // remove accessible field at the player's position
13373 Feld[jx][jy] = EL_EMPTY;
13375 // deactivate shield (else Bang()/Explode() would not work right)
13376 player->shield_normal_time_left = 0;
13377 player->shield_deadly_time_left = 0;
13380 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13381 player->killed, player->active, player->reanimated);
13387 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13388 player->killed, player->active, player->reanimated);
13391 if (player->reanimated) // killed player may have been reanimated
13392 player->killed = player->reanimated = FALSE;
13394 BuryPlayer(player);
13397 static void KillPlayerUnlessEnemyProtected(int x, int y)
13399 if (!PLAYER_ENEMY_PROTECTED(x, y))
13400 KillPlayer(PLAYERINFO(x, y));
13403 static void KillPlayerUnlessExplosionProtected(int x, int y)
13405 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13406 KillPlayer(PLAYERINFO(x, y));
13409 void BuryPlayer(struct PlayerInfo *player)
13411 int jx = player->jx, jy = player->jy;
13413 if (!player->active)
13416 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13417 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13419 RemovePlayer(player);
13421 player->buried = TRUE;
13423 if (game.all_players_gone)
13424 game.GameOver = TRUE;
13427 void RemovePlayer(struct PlayerInfo *player)
13429 int jx = player->jx, jy = player->jy;
13430 int i, found = FALSE;
13432 player->present = FALSE;
13433 player->active = FALSE;
13435 if (!ExplodeField[jx][jy])
13436 StorePlayer[jx][jy] = 0;
13438 if (player->is_moving)
13439 TEST_DrawLevelField(player->last_jx, player->last_jy);
13441 for (i = 0; i < MAX_PLAYERS; i++)
13442 if (stored_player[i].active)
13447 game.all_players_gone = TRUE;
13448 game.GameOver = TRUE;
13451 game.exit_x = ZX = jx;
13452 game.exit_y = ZY = jy;
13455 void ExitPlayer(struct PlayerInfo *player)
13457 DrawPlayer(player); // needed here only to cleanup last field
13458 RemovePlayer(player);
13460 if (game.players_still_needed > 0)
13461 game.players_still_needed--;
13464 static void setFieldForSnapping(int x, int y, int element, int direction)
13466 struct ElementInfo *ei = &element_info[element];
13467 int direction_bit = MV_DIR_TO_BIT(direction);
13468 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13469 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13470 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13472 Feld[x][y] = EL_ELEMENT_SNAPPING;
13473 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13475 ResetGfxAnimation(x, y);
13477 GfxElement[x][y] = element;
13478 GfxAction[x][y] = action;
13479 GfxDir[x][y] = direction;
13480 GfxFrame[x][y] = -1;
13484 =============================================================================
13485 checkDiagonalPushing()
13486 -----------------------------------------------------------------------------
13487 check if diagonal input device direction results in pushing of object
13488 (by checking if the alternative direction is walkable, diggable, ...)
13489 =============================================================================
13492 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13493 int x, int y, int real_dx, int real_dy)
13495 int jx, jy, dx, dy, xx, yy;
13497 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13500 // diagonal direction: check alternative direction
13505 xx = jx + (dx == 0 ? real_dx : 0);
13506 yy = jy + (dy == 0 ? real_dy : 0);
13508 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13512 =============================================================================
13514 -----------------------------------------------------------------------------
13515 x, y: field next to player (non-diagonal) to try to dig to
13516 real_dx, real_dy: direction as read from input device (can be diagonal)
13517 =============================================================================
13520 static int DigField(struct PlayerInfo *player,
13521 int oldx, int oldy, int x, int y,
13522 int real_dx, int real_dy, int mode)
13524 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13525 boolean player_was_pushing = player->is_pushing;
13526 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13527 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13528 int jx = oldx, jy = oldy;
13529 int dx = x - jx, dy = y - jy;
13530 int nextx = x + dx, nexty = y + dy;
13531 int move_direction = (dx == -1 ? MV_LEFT :
13532 dx == +1 ? MV_RIGHT :
13534 dy == +1 ? MV_DOWN : MV_NONE);
13535 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13536 int dig_side = MV_DIR_OPPOSITE(move_direction);
13537 int old_element = Feld[jx][jy];
13538 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13541 if (is_player) // function can also be called by EL_PENGUIN
13543 if (player->MovPos == 0)
13545 player->is_digging = FALSE;
13546 player->is_collecting = FALSE;
13549 if (player->MovPos == 0) // last pushing move finished
13550 player->is_pushing = FALSE;
13552 if (mode == DF_NO_PUSH) // player just stopped pushing
13554 player->is_switching = FALSE;
13555 player->push_delay = -1;
13557 return MP_NO_ACTION;
13561 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13562 old_element = Back[jx][jy];
13564 // in case of element dropped at player position, check background
13565 else if (Back[jx][jy] != EL_EMPTY &&
13566 game.engine_version >= VERSION_IDENT(2,2,0,0))
13567 old_element = Back[jx][jy];
13569 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13570 return MP_NO_ACTION; // field has no opening in this direction
13572 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13573 return MP_NO_ACTION; // field has no opening in this direction
13575 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13579 Feld[jx][jy] = player->artwork_element;
13580 InitMovingField(jx, jy, MV_DOWN);
13581 Store[jx][jy] = EL_ACID;
13582 ContinueMoving(jx, jy);
13583 BuryPlayer(player);
13585 return MP_DONT_RUN_INTO;
13588 if (player_can_move && DONT_RUN_INTO(element))
13590 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13592 return MP_DONT_RUN_INTO;
13595 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13596 return MP_NO_ACTION;
13598 collect_count = element_info[element].collect_count_initial;
13600 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13601 return MP_NO_ACTION;
13603 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13604 player_can_move = player_can_move_or_snap;
13606 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13607 game.engine_version >= VERSION_IDENT(2,2,0,0))
13609 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13610 player->index_bit, dig_side);
13611 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13612 player->index_bit, dig_side);
13614 if (element == EL_DC_LANDMINE)
13617 if (Feld[x][y] != element) // field changed by snapping
13620 return MP_NO_ACTION;
13623 if (player->gravity && is_player && !player->is_auto_moving &&
13624 canFallDown(player) && move_direction != MV_DOWN &&
13625 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13626 return MP_NO_ACTION; // player cannot walk here due to gravity
13628 if (player_can_move &&
13629 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13631 int sound_element = SND_ELEMENT(element);
13632 int sound_action = ACTION_WALKING;
13634 if (IS_RND_GATE(element))
13636 if (!player->key[RND_GATE_NR(element)])
13637 return MP_NO_ACTION;
13639 else if (IS_RND_GATE_GRAY(element))
13641 if (!player->key[RND_GATE_GRAY_NR(element)])
13642 return MP_NO_ACTION;
13644 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13646 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13647 return MP_NO_ACTION;
13649 else if (element == EL_EXIT_OPEN ||
13650 element == EL_EM_EXIT_OPEN ||
13651 element == EL_EM_EXIT_OPENING ||
13652 element == EL_STEEL_EXIT_OPEN ||
13653 element == EL_EM_STEEL_EXIT_OPEN ||
13654 element == EL_EM_STEEL_EXIT_OPENING ||
13655 element == EL_SP_EXIT_OPEN ||
13656 element == EL_SP_EXIT_OPENING)
13658 sound_action = ACTION_PASSING; // player is passing exit
13660 else if (element == EL_EMPTY)
13662 sound_action = ACTION_MOVING; // nothing to walk on
13665 // play sound from background or player, whatever is available
13666 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13667 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13669 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13671 else if (player_can_move &&
13672 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13674 if (!ACCESS_FROM(element, opposite_direction))
13675 return MP_NO_ACTION; // field not accessible from this direction
13677 if (CAN_MOVE(element)) // only fixed elements can be passed!
13678 return MP_NO_ACTION;
13680 if (IS_EM_GATE(element))
13682 if (!player->key[EM_GATE_NR(element)])
13683 return MP_NO_ACTION;
13685 else if (IS_EM_GATE_GRAY(element))
13687 if (!player->key[EM_GATE_GRAY_NR(element)])
13688 return MP_NO_ACTION;
13690 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13692 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13693 return MP_NO_ACTION;
13695 else if (IS_EMC_GATE(element))
13697 if (!player->key[EMC_GATE_NR(element)])
13698 return MP_NO_ACTION;
13700 else if (IS_EMC_GATE_GRAY(element))
13702 if (!player->key[EMC_GATE_GRAY_NR(element)])
13703 return MP_NO_ACTION;
13705 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13707 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13708 return MP_NO_ACTION;
13710 else if (element == EL_DC_GATE_WHITE ||
13711 element == EL_DC_GATE_WHITE_GRAY ||
13712 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13714 if (player->num_white_keys == 0)
13715 return MP_NO_ACTION;
13717 player->num_white_keys--;
13719 else if (IS_SP_PORT(element))
13721 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13722 element == EL_SP_GRAVITY_PORT_RIGHT ||
13723 element == EL_SP_GRAVITY_PORT_UP ||
13724 element == EL_SP_GRAVITY_PORT_DOWN)
13725 player->gravity = !player->gravity;
13726 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13727 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13728 element == EL_SP_GRAVITY_ON_PORT_UP ||
13729 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13730 player->gravity = TRUE;
13731 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13732 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13733 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13734 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13735 player->gravity = FALSE;
13738 // automatically move to the next field with double speed
13739 player->programmed_action = move_direction;
13741 if (player->move_delay_reset_counter == 0)
13743 player->move_delay_reset_counter = 2; // two double speed steps
13745 DOUBLE_PLAYER_SPEED(player);
13748 PlayLevelSoundAction(x, y, ACTION_PASSING);
13750 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13754 if (mode != DF_SNAP)
13756 GfxElement[x][y] = GFX_ELEMENT(element);
13757 player->is_digging = TRUE;
13760 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13762 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13763 player->index_bit, dig_side);
13765 if (mode == DF_SNAP)
13767 if (level.block_snap_field)
13768 setFieldForSnapping(x, y, element, move_direction);
13770 TestIfElementTouchesCustomElement(x, y); // for empty space
13772 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13773 player->index_bit, dig_side);
13776 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13780 if (is_player && mode != DF_SNAP)
13782 GfxElement[x][y] = element;
13783 player->is_collecting = TRUE;
13786 if (element == EL_SPEED_PILL)
13788 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13790 else if (element == EL_EXTRA_TIME && level.time > 0)
13792 TimeLeft += level.extra_time;
13794 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13796 DisplayGameControlValues();
13798 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
13800 player->shield_normal_time_left += level.shield_normal_time;
13801 if (element == EL_SHIELD_DEADLY)
13802 player->shield_deadly_time_left += level.shield_deadly_time;
13804 else if (element == EL_DYNAMITE ||
13805 element == EL_EM_DYNAMITE ||
13806 element == EL_SP_DISK_RED)
13808 if (player->inventory_size < MAX_INVENTORY_SIZE)
13809 player->inventory_element[player->inventory_size++] = element;
13811 DrawGameDoorValues();
13813 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
13815 player->dynabomb_count++;
13816 player->dynabombs_left++;
13818 else if (element == EL_DYNABOMB_INCREASE_SIZE)
13820 player->dynabomb_size++;
13822 else if (element == EL_DYNABOMB_INCREASE_POWER)
13824 player->dynabomb_xl = TRUE;
13826 else if (IS_KEY(element))
13828 player->key[KEY_NR(element)] = TRUE;
13830 DrawGameDoorValues();
13832 else if (element == EL_DC_KEY_WHITE)
13834 player->num_white_keys++;
13836 // display white keys?
13837 // DrawGameDoorValues();
13839 else if (IS_ENVELOPE(element))
13841 player->show_envelope = element;
13843 else if (element == EL_EMC_LENSES)
13845 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
13847 RedrawAllInvisibleElementsForLenses();
13849 else if (element == EL_EMC_MAGNIFIER)
13851 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
13853 RedrawAllInvisibleElementsForMagnifier();
13855 else if (IS_DROPPABLE(element) ||
13856 IS_THROWABLE(element)) // can be collected and dropped
13860 if (collect_count == 0)
13861 player->inventory_infinite_element = element;
13863 for (i = 0; i < collect_count; i++)
13864 if (player->inventory_size < MAX_INVENTORY_SIZE)
13865 player->inventory_element[player->inventory_size++] = element;
13867 DrawGameDoorValues();
13869 else if (collect_count > 0)
13871 game.gems_still_needed -= collect_count;
13872 if (game.gems_still_needed < 0)
13873 game.gems_still_needed = 0;
13875 game.snapshot.collected_item = TRUE;
13877 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
13879 DisplayGameControlValues();
13882 RaiseScoreElement(element);
13883 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
13886 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
13887 player->index_bit, dig_side);
13889 if (mode == DF_SNAP)
13891 if (level.block_snap_field)
13892 setFieldForSnapping(x, y, element, move_direction);
13894 TestIfElementTouchesCustomElement(x, y); // for empty space
13896 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13897 player->index_bit, dig_side);
13900 else if (player_can_move_or_snap && IS_PUSHABLE(element))
13902 if (mode == DF_SNAP && element != EL_BD_ROCK)
13903 return MP_NO_ACTION;
13905 if (CAN_FALL(element) && dy)
13906 return MP_NO_ACTION;
13908 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
13909 !(element == EL_SPRING && level.use_spring_bug))
13910 return MP_NO_ACTION;
13912 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
13913 ((move_direction & MV_VERTICAL &&
13914 ((element_info[element].move_pattern & MV_LEFT &&
13915 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
13916 (element_info[element].move_pattern & MV_RIGHT &&
13917 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
13918 (move_direction & MV_HORIZONTAL &&
13919 ((element_info[element].move_pattern & MV_UP &&
13920 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
13921 (element_info[element].move_pattern & MV_DOWN &&
13922 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
13923 return MP_NO_ACTION;
13925 // do not push elements already moving away faster than player
13926 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
13927 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
13928 return MP_NO_ACTION;
13930 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
13932 if (player->push_delay_value == -1 || !player_was_pushing)
13933 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13935 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13937 if (player->push_delay_value == -1)
13938 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13940 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
13942 if (!player->is_pushing)
13943 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13946 player->is_pushing = TRUE;
13947 player->is_active = TRUE;
13949 if (!(IN_LEV_FIELD(nextx, nexty) &&
13950 (IS_FREE(nextx, nexty) ||
13951 (IS_SB_ELEMENT(element) &&
13952 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
13953 (IS_CUSTOM_ELEMENT(element) &&
13954 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
13955 return MP_NO_ACTION;
13957 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
13958 return MP_NO_ACTION;
13960 if (player->push_delay == -1) // new pushing; restart delay
13961 player->push_delay = 0;
13963 if (player->push_delay < player->push_delay_value &&
13964 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
13965 element != EL_SPRING && element != EL_BALLOON)
13967 // make sure that there is no move delay before next try to push
13968 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13969 player->move_delay = 0;
13971 return MP_NO_ACTION;
13974 if (IS_CUSTOM_ELEMENT(element) &&
13975 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
13977 if (!DigFieldByCE(nextx, nexty, element))
13978 return MP_NO_ACTION;
13981 if (IS_SB_ELEMENT(element))
13983 boolean sokoban_task_solved = FALSE;
13985 if (element == EL_SOKOBAN_FIELD_FULL)
13987 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
13989 IncrementSokobanFieldsNeeded();
13990 IncrementSokobanObjectsNeeded();
13993 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
13995 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
13997 DecrementSokobanFieldsNeeded();
13998 DecrementSokobanObjectsNeeded();
14000 // sokoban object was pushed from empty field to sokoban field
14001 if (Back[x][y] == EL_EMPTY)
14002 sokoban_task_solved = TRUE;
14005 Feld[x][y] = EL_SOKOBAN_OBJECT;
14007 if (Back[x][y] == Back[nextx][nexty])
14008 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14009 else if (Back[x][y] != 0)
14010 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14013 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14016 if (sokoban_task_solved &&
14017 game.sokoban_fields_still_needed == 0 &&
14018 game.sokoban_objects_still_needed == 0 &&
14019 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14021 game.players_still_needed = 0;
14025 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14029 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14031 InitMovingField(x, y, move_direction);
14032 GfxAction[x][y] = ACTION_PUSHING;
14034 if (mode == DF_SNAP)
14035 ContinueMoving(x, y);
14037 MovPos[x][y] = (dx != 0 ? dx : dy);
14039 Pushed[x][y] = TRUE;
14040 Pushed[nextx][nexty] = TRUE;
14042 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14043 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14045 player->push_delay_value = -1; // get new value later
14047 // check for element change _after_ element has been pushed
14048 if (game.use_change_when_pushing_bug)
14050 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14051 player->index_bit, dig_side);
14052 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14053 player->index_bit, dig_side);
14056 else if (IS_SWITCHABLE(element))
14058 if (PLAYER_SWITCHING(player, x, y))
14060 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14061 player->index_bit, dig_side);
14066 player->is_switching = TRUE;
14067 player->switch_x = x;
14068 player->switch_y = y;
14070 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14072 if (element == EL_ROBOT_WHEEL)
14074 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14078 game.robot_wheel_active = TRUE;
14080 TEST_DrawLevelField(x, y);
14082 else if (element == EL_SP_TERMINAL)
14086 SCAN_PLAYFIELD(xx, yy)
14088 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14092 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14094 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14096 ResetGfxAnimation(xx, yy);
14097 TEST_DrawLevelField(xx, yy);
14101 else if (IS_BELT_SWITCH(element))
14103 ToggleBeltSwitch(x, y);
14105 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14106 element == EL_SWITCHGATE_SWITCH_DOWN ||
14107 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14108 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14110 ToggleSwitchgateSwitch(x, y);
14112 else if (element == EL_LIGHT_SWITCH ||
14113 element == EL_LIGHT_SWITCH_ACTIVE)
14115 ToggleLightSwitch(x, y);
14117 else if (element == EL_TIMEGATE_SWITCH ||
14118 element == EL_DC_TIMEGATE_SWITCH)
14120 ActivateTimegateSwitch(x, y);
14122 else if (element == EL_BALLOON_SWITCH_LEFT ||
14123 element == EL_BALLOON_SWITCH_RIGHT ||
14124 element == EL_BALLOON_SWITCH_UP ||
14125 element == EL_BALLOON_SWITCH_DOWN ||
14126 element == EL_BALLOON_SWITCH_NONE ||
14127 element == EL_BALLOON_SWITCH_ANY)
14129 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14130 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14131 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14132 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14133 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14136 else if (element == EL_LAMP)
14138 Feld[x][y] = EL_LAMP_ACTIVE;
14139 game.lights_still_needed--;
14141 ResetGfxAnimation(x, y);
14142 TEST_DrawLevelField(x, y);
14144 else if (element == EL_TIME_ORB_FULL)
14146 Feld[x][y] = EL_TIME_ORB_EMPTY;
14148 if (level.time > 0 || level.use_time_orb_bug)
14150 TimeLeft += level.time_orb_time;
14151 game.no_time_limit = FALSE;
14153 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14155 DisplayGameControlValues();
14158 ResetGfxAnimation(x, y);
14159 TEST_DrawLevelField(x, y);
14161 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14162 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14166 game.ball_state = !game.ball_state;
14168 SCAN_PLAYFIELD(xx, yy)
14170 int e = Feld[xx][yy];
14172 if (game.ball_state)
14174 if (e == EL_EMC_MAGIC_BALL)
14175 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14176 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14177 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14181 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14182 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14183 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14184 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14189 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14190 player->index_bit, dig_side);
14192 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14193 player->index_bit, dig_side);
14195 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14196 player->index_bit, dig_side);
14202 if (!PLAYER_SWITCHING(player, x, y))
14204 player->is_switching = TRUE;
14205 player->switch_x = x;
14206 player->switch_y = y;
14208 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14209 player->index_bit, dig_side);
14210 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14211 player->index_bit, dig_side);
14213 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14214 player->index_bit, dig_side);
14215 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14216 player->index_bit, dig_side);
14219 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14220 player->index_bit, dig_side);
14221 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14222 player->index_bit, dig_side);
14224 return MP_NO_ACTION;
14227 player->push_delay = -1;
14229 if (is_player) // function can also be called by EL_PENGUIN
14231 if (Feld[x][y] != element) // really digged/collected something
14233 player->is_collecting = !player->is_digging;
14234 player->is_active = TRUE;
14241 static boolean DigFieldByCE(int x, int y, int digging_element)
14243 int element = Feld[x][y];
14245 if (!IS_FREE(x, y))
14247 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14248 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14251 // no element can dig solid indestructible elements
14252 if (IS_INDESTRUCTIBLE(element) &&
14253 !IS_DIGGABLE(element) &&
14254 !IS_COLLECTIBLE(element))
14257 if (AmoebaNr[x][y] &&
14258 (element == EL_AMOEBA_FULL ||
14259 element == EL_BD_AMOEBA ||
14260 element == EL_AMOEBA_GROWING))
14262 AmoebaCnt[AmoebaNr[x][y]]--;
14263 AmoebaCnt2[AmoebaNr[x][y]]--;
14266 if (IS_MOVING(x, y))
14267 RemoveMovingField(x, y);
14271 TEST_DrawLevelField(x, y);
14274 // if digged element was about to explode, prevent the explosion
14275 ExplodeField[x][y] = EX_TYPE_NONE;
14277 PlayLevelSoundAction(x, y, action);
14280 Store[x][y] = EL_EMPTY;
14282 // this makes it possible to leave the removed element again
14283 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14284 Store[x][y] = element;
14289 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14291 int jx = player->jx, jy = player->jy;
14292 int x = jx + dx, y = jy + dy;
14293 int snap_direction = (dx == -1 ? MV_LEFT :
14294 dx == +1 ? MV_RIGHT :
14296 dy == +1 ? MV_DOWN : MV_NONE);
14297 boolean can_continue_snapping = (level.continuous_snapping &&
14298 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14300 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14303 if (!player->active || !IN_LEV_FIELD(x, y))
14311 if (player->MovPos == 0)
14312 player->is_pushing = FALSE;
14314 player->is_snapping = FALSE;
14316 if (player->MovPos == 0)
14318 player->is_moving = FALSE;
14319 player->is_digging = FALSE;
14320 player->is_collecting = FALSE;
14326 // prevent snapping with already pressed snap key when not allowed
14327 if (player->is_snapping && !can_continue_snapping)
14330 player->MovDir = snap_direction;
14332 if (player->MovPos == 0)
14334 player->is_moving = FALSE;
14335 player->is_digging = FALSE;
14336 player->is_collecting = FALSE;
14339 player->is_dropping = FALSE;
14340 player->is_dropping_pressed = FALSE;
14341 player->drop_pressed_delay = 0;
14343 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14346 player->is_snapping = TRUE;
14347 player->is_active = TRUE;
14349 if (player->MovPos == 0)
14351 player->is_moving = FALSE;
14352 player->is_digging = FALSE;
14353 player->is_collecting = FALSE;
14356 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14357 TEST_DrawLevelField(player->last_jx, player->last_jy);
14359 TEST_DrawLevelField(x, y);
14364 static boolean DropElement(struct PlayerInfo *player)
14366 int old_element, new_element;
14367 int dropx = player->jx, dropy = player->jy;
14368 int drop_direction = player->MovDir;
14369 int drop_side = drop_direction;
14370 int drop_element = get_next_dropped_element(player);
14372 /* do not drop an element on top of another element; when holding drop key
14373 pressed without moving, dropped element must move away before the next
14374 element can be dropped (this is especially important if the next element
14375 is dynamite, which can be placed on background for historical reasons) */
14376 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14379 if (IS_THROWABLE(drop_element))
14381 dropx += GET_DX_FROM_DIR(drop_direction);
14382 dropy += GET_DY_FROM_DIR(drop_direction);
14384 if (!IN_LEV_FIELD(dropx, dropy))
14388 old_element = Feld[dropx][dropy]; // old element at dropping position
14389 new_element = drop_element; // default: no change when dropping
14391 // check if player is active, not moving and ready to drop
14392 if (!player->active || player->MovPos || player->drop_delay > 0)
14395 // check if player has anything that can be dropped
14396 if (new_element == EL_UNDEFINED)
14399 // only set if player has anything that can be dropped
14400 player->is_dropping_pressed = TRUE;
14402 // check if drop key was pressed long enough for EM style dynamite
14403 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14406 // check if anything can be dropped at the current position
14407 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14410 // collected custom elements can only be dropped on empty fields
14411 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14414 if (old_element != EL_EMPTY)
14415 Back[dropx][dropy] = old_element; // store old element on this field
14417 ResetGfxAnimation(dropx, dropy);
14418 ResetRandomAnimationValue(dropx, dropy);
14420 if (player->inventory_size > 0 ||
14421 player->inventory_infinite_element != EL_UNDEFINED)
14423 if (player->inventory_size > 0)
14425 player->inventory_size--;
14427 DrawGameDoorValues();
14429 if (new_element == EL_DYNAMITE)
14430 new_element = EL_DYNAMITE_ACTIVE;
14431 else if (new_element == EL_EM_DYNAMITE)
14432 new_element = EL_EM_DYNAMITE_ACTIVE;
14433 else if (new_element == EL_SP_DISK_RED)
14434 new_element = EL_SP_DISK_RED_ACTIVE;
14437 Feld[dropx][dropy] = new_element;
14439 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14440 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14441 el2img(Feld[dropx][dropy]), 0);
14443 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14445 // needed if previous element just changed to "empty" in the last frame
14446 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14448 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14449 player->index_bit, drop_side);
14450 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14452 player->index_bit, drop_side);
14454 TestIfElementTouchesCustomElement(dropx, dropy);
14456 else // player is dropping a dyna bomb
14458 player->dynabombs_left--;
14460 Feld[dropx][dropy] = new_element;
14462 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14463 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14464 el2img(Feld[dropx][dropy]), 0);
14466 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14469 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14470 InitField_WithBug1(dropx, dropy, FALSE);
14472 new_element = Feld[dropx][dropy]; // element might have changed
14474 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14475 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14477 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14478 MovDir[dropx][dropy] = drop_direction;
14480 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14482 // do not cause impact style collision by dropping elements that can fall
14483 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14486 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14487 player->is_dropping = TRUE;
14489 player->drop_pressed_delay = 0;
14490 player->is_dropping_pressed = FALSE;
14492 player->drop_x = dropx;
14493 player->drop_y = dropy;
14498 // ----------------------------------------------------------------------------
14499 // game sound playing functions
14500 // ----------------------------------------------------------------------------
14502 static int *loop_sound_frame = NULL;
14503 static int *loop_sound_volume = NULL;
14505 void InitPlayLevelSound(void)
14507 int num_sounds = getSoundListSize();
14509 checked_free(loop_sound_frame);
14510 checked_free(loop_sound_volume);
14512 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14513 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14516 static void PlayLevelSound(int x, int y, int nr)
14518 int sx = SCREENX(x), sy = SCREENY(y);
14519 int volume, stereo_position;
14520 int max_distance = 8;
14521 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14523 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14524 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14527 if (!IN_LEV_FIELD(x, y) ||
14528 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14529 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14532 volume = SOUND_MAX_VOLUME;
14534 if (!IN_SCR_FIELD(sx, sy))
14536 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14537 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14539 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14542 stereo_position = (SOUND_MAX_LEFT +
14543 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14544 (SCR_FIELDX + 2 * max_distance));
14546 if (IS_LOOP_SOUND(nr))
14548 /* This assures that quieter loop sounds do not overwrite louder ones,
14549 while restarting sound volume comparison with each new game frame. */
14551 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14554 loop_sound_volume[nr] = volume;
14555 loop_sound_frame[nr] = FrameCounter;
14558 PlaySoundExt(nr, volume, stereo_position, type);
14561 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14563 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14564 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14565 y < LEVELY(BY1) ? LEVELY(BY1) :
14566 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14570 static void PlayLevelSoundAction(int x, int y, int action)
14572 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14575 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14577 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14579 if (sound_effect != SND_UNDEFINED)
14580 PlayLevelSound(x, y, sound_effect);
14583 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14586 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14588 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14589 PlayLevelSound(x, y, sound_effect);
14592 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14594 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14596 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14597 PlayLevelSound(x, y, sound_effect);
14600 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14602 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14604 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14605 StopSound(sound_effect);
14608 static int getLevelMusicNr(void)
14610 if (levelset.music[level_nr] != MUS_UNDEFINED)
14611 return levelset.music[level_nr]; // from config file
14613 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14616 static void FadeLevelSounds(void)
14621 static void FadeLevelMusic(void)
14623 int music_nr = getLevelMusicNr();
14624 char *curr_music = getCurrentlyPlayingMusicFilename();
14625 char *next_music = getMusicInfoEntryFilename(music_nr);
14627 if (!strEqual(curr_music, next_music))
14631 void FadeLevelSoundsAndMusic(void)
14637 static void PlayLevelMusic(void)
14639 int music_nr = getLevelMusicNr();
14640 char *curr_music = getCurrentlyPlayingMusicFilename();
14641 char *next_music = getMusicInfoEntryFilename(music_nr);
14643 if (!strEqual(curr_music, next_music))
14644 PlayMusicLoop(music_nr);
14647 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14649 int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0);
14650 int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
14651 int x = xx - 1 - offset;
14652 int y = yy - 1 - offset;
14657 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14661 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14665 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14669 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14673 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14677 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14681 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14684 case SAMPLE_android_clone:
14685 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14688 case SAMPLE_android_move:
14689 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14692 case SAMPLE_spring:
14693 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14697 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14701 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14704 case SAMPLE_eater_eat:
14705 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14709 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14712 case SAMPLE_collect:
14713 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14716 case SAMPLE_diamond:
14717 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14720 case SAMPLE_squash:
14721 // !!! CHECK THIS !!!
14723 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14725 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14729 case SAMPLE_wonderfall:
14730 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14734 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14738 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14742 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14746 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14750 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14754 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14757 case SAMPLE_wonder:
14758 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14762 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14765 case SAMPLE_exit_open:
14766 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14769 case SAMPLE_exit_leave:
14770 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14773 case SAMPLE_dynamite:
14774 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14778 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14782 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14786 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14790 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14794 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
14798 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
14802 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
14807 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
14809 int element = map_element_SP_to_RND(element_sp);
14810 int action = map_action_SP_to_RND(action_sp);
14811 int offset = (setup.sp_show_border_elements ? 0 : 1);
14812 int x = xx - offset;
14813 int y = yy - offset;
14815 PlayLevelSoundElementAction(x, y, element, action);
14818 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
14820 int element = map_element_MM_to_RND(element_mm);
14821 int action = map_action_MM_to_RND(action_mm);
14823 int x = xx - offset;
14824 int y = yy - offset;
14826 if (!IS_MM_ELEMENT(element))
14827 element = EL_MM_DEFAULT;
14829 PlayLevelSoundElementAction(x, y, element, action);
14832 void PlaySound_MM(int sound_mm)
14834 int sound = map_sound_MM_to_RND(sound_mm);
14836 if (sound == SND_UNDEFINED)
14842 void PlaySoundLoop_MM(int sound_mm)
14844 int sound = map_sound_MM_to_RND(sound_mm);
14846 if (sound == SND_UNDEFINED)
14849 PlaySoundLoop(sound);
14852 void StopSound_MM(int sound_mm)
14854 int sound = map_sound_MM_to_RND(sound_mm);
14856 if (sound == SND_UNDEFINED)
14862 void RaiseScore(int value)
14864 game.score += value;
14866 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
14868 DisplayGameControlValues();
14871 void RaiseScoreElement(int element)
14876 case EL_BD_DIAMOND:
14877 case EL_EMERALD_YELLOW:
14878 case EL_EMERALD_RED:
14879 case EL_EMERALD_PURPLE:
14880 case EL_SP_INFOTRON:
14881 RaiseScore(level.score[SC_EMERALD]);
14884 RaiseScore(level.score[SC_DIAMOND]);
14887 RaiseScore(level.score[SC_CRYSTAL]);
14890 RaiseScore(level.score[SC_PEARL]);
14893 case EL_BD_BUTTERFLY:
14894 case EL_SP_ELECTRON:
14895 RaiseScore(level.score[SC_BUG]);
14898 case EL_BD_FIREFLY:
14899 case EL_SP_SNIKSNAK:
14900 RaiseScore(level.score[SC_SPACESHIP]);
14903 case EL_DARK_YAMYAM:
14904 RaiseScore(level.score[SC_YAMYAM]);
14907 RaiseScore(level.score[SC_ROBOT]);
14910 RaiseScore(level.score[SC_PACMAN]);
14913 RaiseScore(level.score[SC_NUT]);
14916 case EL_EM_DYNAMITE:
14917 case EL_SP_DISK_RED:
14918 case EL_DYNABOMB_INCREASE_NUMBER:
14919 case EL_DYNABOMB_INCREASE_SIZE:
14920 case EL_DYNABOMB_INCREASE_POWER:
14921 RaiseScore(level.score[SC_DYNAMITE]);
14923 case EL_SHIELD_NORMAL:
14924 case EL_SHIELD_DEADLY:
14925 RaiseScore(level.score[SC_SHIELD]);
14927 case EL_EXTRA_TIME:
14928 RaiseScore(level.extra_time_score);
14942 case EL_DC_KEY_WHITE:
14943 RaiseScore(level.score[SC_KEY]);
14946 RaiseScore(element_info[element].collect_score);
14951 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
14953 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
14955 // closing door required in case of envelope style request dialogs
14957 CloseDoor(DOOR_CLOSE_1);
14959 if (network.enabled)
14960 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
14964 FadeSkipNextFadeIn();
14966 SetGameStatus(GAME_MODE_MAIN);
14971 else // continue playing the game
14973 if (tape.playing && tape.deactivate_display)
14974 TapeDeactivateDisplayOff(TRUE);
14976 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
14978 if (tape.playing && tape.deactivate_display)
14979 TapeDeactivateDisplayOn();
14983 void RequestQuitGame(boolean ask_if_really_quit)
14985 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
14986 boolean skip_request = game.all_players_gone || quick_quit;
14988 RequestQuitGameExt(skip_request, quick_quit,
14989 "Do you really want to quit the game?");
14992 void RequestRestartGame(char *message)
14994 game.restart_game_message = NULL;
14996 boolean has_started_game = hasStartedNetworkGame();
14997 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
14999 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15001 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15005 SetGameStatus(GAME_MODE_MAIN);
15011 void CheckGameOver(void)
15013 static boolean last_game_over = FALSE;
15014 static int game_over_delay = 0;
15015 int game_over_delay_value = 50;
15016 boolean game_over = checkGameFailed();
15018 // do not handle game over if request dialog is already active
15019 if (game.request_active)
15024 last_game_over = FALSE;
15025 game_over_delay = game_over_delay_value;
15030 if (game_over_delay > 0)
15037 if (last_game_over != game_over)
15038 game.restart_game_message = (hasStartedNetworkGame() ?
15039 "Game over! Play it again?" :
15042 last_game_over = game_over;
15045 boolean checkGameSolved(void)
15047 // set for all game engines if level was solved
15048 return game.LevelSolved_GameEnd;
15051 boolean checkGameFailed(void)
15053 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15054 return (game_em.game_over && !game_em.level_solved);
15055 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15056 return (game_sp.game_over && !game_sp.level_solved);
15057 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15058 return (game_mm.game_over && !game_mm.level_solved);
15059 else // GAME_ENGINE_TYPE_RND
15060 return (game.GameOver && !game.LevelSolved);
15063 boolean checkGameEnded(void)
15065 return (checkGameSolved() || checkGameFailed());
15069 // ----------------------------------------------------------------------------
15070 // random generator functions
15071 // ----------------------------------------------------------------------------
15073 unsigned int InitEngineRandom_RND(int seed)
15075 game.num_random_calls = 0;
15077 return InitEngineRandom(seed);
15080 unsigned int RND(int max)
15084 game.num_random_calls++;
15086 return GetEngineRandom(max);
15093 // ----------------------------------------------------------------------------
15094 // game engine snapshot handling functions
15095 // ----------------------------------------------------------------------------
15097 struct EngineSnapshotInfo
15099 // runtime values for custom element collect score
15100 int collect_score[NUM_CUSTOM_ELEMENTS];
15102 // runtime values for group element choice position
15103 int choice_pos[NUM_GROUP_ELEMENTS];
15105 // runtime values for belt position animations
15106 int belt_graphic[4][NUM_BELT_PARTS];
15107 int belt_anim_mode[4][NUM_BELT_PARTS];
15110 static struct EngineSnapshotInfo engine_snapshot_rnd;
15111 static char *snapshot_level_identifier = NULL;
15112 static int snapshot_level_nr = -1;
15114 static void SaveEngineSnapshotValues_RND(void)
15116 static int belt_base_active_element[4] =
15118 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15119 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15120 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15121 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15125 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15127 int element = EL_CUSTOM_START + i;
15129 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15132 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15134 int element = EL_GROUP_START + i;
15136 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15139 for (i = 0; i < 4; i++)
15141 for (j = 0; j < NUM_BELT_PARTS; j++)
15143 int element = belt_base_active_element[i] + j;
15144 int graphic = el2img(element);
15145 int anim_mode = graphic_info[graphic].anim_mode;
15147 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15148 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15153 static void LoadEngineSnapshotValues_RND(void)
15155 unsigned int num_random_calls = game.num_random_calls;
15158 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15160 int element = EL_CUSTOM_START + i;
15162 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15165 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15167 int element = EL_GROUP_START + i;
15169 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15172 for (i = 0; i < 4; i++)
15174 for (j = 0; j < NUM_BELT_PARTS; j++)
15176 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15177 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15179 graphic_info[graphic].anim_mode = anim_mode;
15183 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15185 InitRND(tape.random_seed);
15186 for (i = 0; i < num_random_calls; i++)
15190 if (game.num_random_calls != num_random_calls)
15192 Error(ERR_INFO, "number of random calls out of sync");
15193 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15194 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15195 Error(ERR_EXIT, "this should not happen -- please debug");
15199 void FreeEngineSnapshotSingle(void)
15201 FreeSnapshotSingle();
15203 setString(&snapshot_level_identifier, NULL);
15204 snapshot_level_nr = -1;
15207 void FreeEngineSnapshotList(void)
15209 FreeSnapshotList();
15212 static ListNode *SaveEngineSnapshotBuffers(void)
15214 ListNode *buffers = NULL;
15216 // copy some special values to a structure better suited for the snapshot
15218 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15219 SaveEngineSnapshotValues_RND();
15220 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15221 SaveEngineSnapshotValues_EM();
15222 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15223 SaveEngineSnapshotValues_SP(&buffers);
15224 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15225 SaveEngineSnapshotValues_MM(&buffers);
15227 // save values stored in special snapshot structure
15229 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15230 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15231 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15232 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15233 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15234 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15235 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15236 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15238 // save further RND engine values
15240 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15241 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15242 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15244 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZX));
15245 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZY));
15247 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15248 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15249 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15250 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15251 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15253 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15254 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15255 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15257 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15259 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15260 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15262 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15263 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15264 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15265 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15266 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15267 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15268 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15269 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15270 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15271 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15272 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15273 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15274 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15275 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15276 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15277 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15278 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15279 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15281 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15282 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15284 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15285 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15286 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15288 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15289 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15291 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15292 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15293 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15294 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15295 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15297 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15298 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15301 ListNode *node = engine_snapshot_list_rnd;
15304 while (node != NULL)
15306 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15311 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15317 void SaveEngineSnapshotSingle(void)
15319 ListNode *buffers = SaveEngineSnapshotBuffers();
15321 // finally save all snapshot buffers to single snapshot
15322 SaveSnapshotSingle(buffers);
15324 // save level identification information
15325 setString(&snapshot_level_identifier, leveldir_current->identifier);
15326 snapshot_level_nr = level_nr;
15329 boolean CheckSaveEngineSnapshotToList(void)
15331 boolean save_snapshot =
15332 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15333 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15334 game.snapshot.changed_action) ||
15335 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15336 game.snapshot.collected_item));
15338 game.snapshot.changed_action = FALSE;
15339 game.snapshot.collected_item = FALSE;
15340 game.snapshot.save_snapshot = save_snapshot;
15342 return save_snapshot;
15345 void SaveEngineSnapshotToList(void)
15347 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15351 ListNode *buffers = SaveEngineSnapshotBuffers();
15353 // finally save all snapshot buffers to snapshot list
15354 SaveSnapshotToList(buffers);
15357 void SaveEngineSnapshotToListInitial(void)
15359 FreeEngineSnapshotList();
15361 SaveEngineSnapshotToList();
15364 static void LoadEngineSnapshotValues(void)
15366 // restore special values from snapshot structure
15368 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15369 LoadEngineSnapshotValues_RND();
15370 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15371 LoadEngineSnapshotValues_EM();
15372 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15373 LoadEngineSnapshotValues_SP();
15374 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15375 LoadEngineSnapshotValues_MM();
15378 void LoadEngineSnapshotSingle(void)
15380 LoadSnapshotSingle();
15382 LoadEngineSnapshotValues();
15385 static void LoadEngineSnapshot_Undo(int steps)
15387 LoadSnapshotFromList_Older(steps);
15389 LoadEngineSnapshotValues();
15392 static void LoadEngineSnapshot_Redo(int steps)
15394 LoadSnapshotFromList_Newer(steps);
15396 LoadEngineSnapshotValues();
15399 boolean CheckEngineSnapshotSingle(void)
15401 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15402 snapshot_level_nr == level_nr);
15405 boolean CheckEngineSnapshotList(void)
15407 return CheckSnapshotList();
15411 // ---------- new game button stuff -------------------------------------------
15418 boolean *setup_value;
15419 boolean allowed_on_tape;
15421 } gamebutton_info[NUM_GAME_BUTTONS] =
15424 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15425 GAME_CTRL_ID_STOP, NULL,
15429 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15430 GAME_CTRL_ID_PAUSE, NULL,
15434 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15435 GAME_CTRL_ID_PLAY, NULL,
15439 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15440 GAME_CTRL_ID_UNDO, NULL,
15444 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15445 GAME_CTRL_ID_REDO, NULL,
15449 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15450 GAME_CTRL_ID_SAVE, NULL,
15454 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15455 GAME_CTRL_ID_PAUSE2, NULL,
15459 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15460 GAME_CTRL_ID_LOAD, NULL,
15464 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15465 GAME_CTRL_ID_PANEL_STOP, NULL,
15469 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15470 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15471 FALSE, "pause game"
15474 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15475 GAME_CTRL_ID_PANEL_PLAY, NULL,
15479 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15480 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15481 TRUE, "background music on/off"
15484 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15485 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15486 TRUE, "sound loops on/off"
15489 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15490 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15491 TRUE, "normal sounds on/off"
15494 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15495 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15496 FALSE, "background music on/off"
15499 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15500 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15501 FALSE, "sound loops on/off"
15504 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15505 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15506 FALSE, "normal sounds on/off"
15510 void CreateGameButtons(void)
15514 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15516 int graphic = gamebutton_info[i].graphic;
15517 struct GraphicInfo *gfx = &graphic_info[graphic];
15518 struct XY *pos = gamebutton_info[i].pos;
15519 struct GadgetInfo *gi;
15522 unsigned int event_mask;
15523 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15524 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15525 int base_x = (on_tape ? VX : DX);
15526 int base_y = (on_tape ? VY : DY);
15527 int gd_x = gfx->src_x;
15528 int gd_y = gfx->src_y;
15529 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15530 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15531 int gd_xa = gfx->src_x + gfx->active_xoffset;
15532 int gd_ya = gfx->src_y + gfx->active_yoffset;
15533 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15534 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15537 if (gfx->bitmap == NULL)
15539 game_gadget[id] = NULL;
15544 if (id == GAME_CTRL_ID_STOP ||
15545 id == GAME_CTRL_ID_PANEL_STOP ||
15546 id == GAME_CTRL_ID_PLAY ||
15547 id == GAME_CTRL_ID_PANEL_PLAY ||
15548 id == GAME_CTRL_ID_SAVE ||
15549 id == GAME_CTRL_ID_LOAD)
15551 button_type = GD_TYPE_NORMAL_BUTTON;
15553 event_mask = GD_EVENT_RELEASED;
15555 else if (id == GAME_CTRL_ID_UNDO ||
15556 id == GAME_CTRL_ID_REDO)
15558 button_type = GD_TYPE_NORMAL_BUTTON;
15560 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15564 button_type = GD_TYPE_CHECK_BUTTON;
15565 checked = (gamebutton_info[i].setup_value != NULL ?
15566 *gamebutton_info[i].setup_value : FALSE);
15567 event_mask = GD_EVENT_PRESSED;
15570 gi = CreateGadget(GDI_CUSTOM_ID, id,
15571 GDI_IMAGE_ID, graphic,
15572 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15573 GDI_X, base_x + GDI_ACTIVE_POS(pos->x),
15574 GDI_Y, base_y + GDI_ACTIVE_POS(pos->y),
15575 GDI_WIDTH, gfx->width,
15576 GDI_HEIGHT, gfx->height,
15577 GDI_TYPE, button_type,
15578 GDI_STATE, GD_BUTTON_UNPRESSED,
15579 GDI_CHECKED, checked,
15580 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15581 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15582 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15583 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15584 GDI_DIRECT_DRAW, FALSE,
15585 GDI_EVENT_MASK, event_mask,
15586 GDI_CALLBACK_ACTION, HandleGameButtons,
15590 Error(ERR_EXIT, "cannot create gadget");
15592 game_gadget[id] = gi;
15596 void FreeGameButtons(void)
15600 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15601 FreeGadget(game_gadget[i]);
15604 static void UnmapGameButtonsAtSamePosition(int id)
15608 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15610 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15611 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15612 UnmapGadget(game_gadget[i]);
15615 static void UnmapGameButtonsAtSamePosition_All(void)
15617 if (setup.show_snapshot_buttons)
15619 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15620 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15621 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15625 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15626 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15627 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15629 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15630 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15631 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15635 static void MapGameButtonsAtSamePosition(int id)
15639 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15641 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15642 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15643 MapGadget(game_gadget[i]);
15645 UnmapGameButtonsAtSamePosition_All();
15648 void MapUndoRedoButtons(void)
15650 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15651 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15653 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15654 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15656 ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, TRUE, GDI_END);
15659 void UnmapUndoRedoButtons(void)
15661 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15662 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15664 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15665 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15667 ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, FALSE, GDI_END);
15670 static void MapGameButtonsExt(boolean on_tape)
15674 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15675 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15676 i != GAME_CTRL_ID_UNDO &&
15677 i != GAME_CTRL_ID_REDO)
15678 MapGadget(game_gadget[i]);
15680 UnmapGameButtonsAtSamePosition_All();
15682 RedrawGameButtons();
15685 static void UnmapGameButtonsExt(boolean on_tape)
15689 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15690 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15691 UnmapGadget(game_gadget[i]);
15694 static void RedrawGameButtonsExt(boolean on_tape)
15698 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15699 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15700 RedrawGadget(game_gadget[i]);
15702 // RedrawGadget() may have set REDRAW_ALL if buttons are defined off-area
15703 redraw_mask &= ~REDRAW_ALL;
15706 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15711 gi->checked = state;
15714 static void RedrawSoundButtonGadget(int id)
15716 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15717 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15718 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15719 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15720 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15721 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15724 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15725 RedrawGadget(game_gadget[id2]);
15728 void MapGameButtons(void)
15730 MapGameButtonsExt(FALSE);
15733 void UnmapGameButtons(void)
15735 UnmapGameButtonsExt(FALSE);
15738 void RedrawGameButtons(void)
15740 RedrawGameButtonsExt(FALSE);
15743 void MapGameButtonsOnTape(void)
15745 MapGameButtonsExt(TRUE);
15748 void UnmapGameButtonsOnTape(void)
15750 UnmapGameButtonsExt(TRUE);
15753 void RedrawGameButtonsOnTape(void)
15755 RedrawGameButtonsExt(TRUE);
15758 static void GameUndoRedoExt(void)
15760 ClearPlayerAction();
15762 tape.pausing = TRUE;
15765 UpdateAndDisplayGameControlValues();
15767 DrawCompleteVideoDisplay();
15768 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
15769 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
15770 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
15775 static void GameUndo(int steps)
15777 if (!CheckEngineSnapshotList())
15780 LoadEngineSnapshot_Undo(steps);
15785 static void GameRedo(int steps)
15787 if (!CheckEngineSnapshotList())
15790 LoadEngineSnapshot_Redo(steps);
15795 static void HandleGameButtonsExt(int id, int button)
15797 static boolean game_undo_executed = FALSE;
15798 int steps = BUTTON_STEPSIZE(button);
15799 boolean handle_game_buttons =
15800 (game_status == GAME_MODE_PLAYING ||
15801 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
15803 if (!handle_game_buttons)
15808 case GAME_CTRL_ID_STOP:
15809 case GAME_CTRL_ID_PANEL_STOP:
15810 if (game_status == GAME_MODE_MAIN)
15816 RequestQuitGame(TRUE);
15820 case GAME_CTRL_ID_PAUSE:
15821 case GAME_CTRL_ID_PAUSE2:
15822 case GAME_CTRL_ID_PANEL_PAUSE:
15823 if (network.enabled && game_status == GAME_MODE_PLAYING)
15826 SendToServer_ContinuePlaying();
15828 SendToServer_PausePlaying();
15831 TapeTogglePause(TAPE_TOGGLE_MANUAL);
15833 game_undo_executed = FALSE;
15837 case GAME_CTRL_ID_PLAY:
15838 case GAME_CTRL_ID_PANEL_PLAY:
15839 if (game_status == GAME_MODE_MAIN)
15841 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15843 else if (tape.pausing)
15845 if (network.enabled)
15846 SendToServer_ContinuePlaying();
15848 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
15852 case GAME_CTRL_ID_UNDO:
15853 // Important: When using "save snapshot when collecting an item" mode,
15854 // load last (current) snapshot for first "undo" after pressing "pause"
15855 // (else the last-but-one snapshot would be loaded, because the snapshot
15856 // pointer already points to the last snapshot when pressing "pause",
15857 // which is fine for "every step/move" mode, but not for "every collect")
15858 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15859 !game_undo_executed)
15862 game_undo_executed = TRUE;
15867 case GAME_CTRL_ID_REDO:
15871 case GAME_CTRL_ID_SAVE:
15875 case GAME_CTRL_ID_LOAD:
15879 case SOUND_CTRL_ID_MUSIC:
15880 case SOUND_CTRL_ID_PANEL_MUSIC:
15881 if (setup.sound_music)
15883 setup.sound_music = FALSE;
15887 else if (audio.music_available)
15889 setup.sound = setup.sound_music = TRUE;
15891 SetAudioMode(setup.sound);
15893 if (game_status == GAME_MODE_PLAYING)
15897 RedrawSoundButtonGadget(id);
15901 case SOUND_CTRL_ID_LOOPS:
15902 case SOUND_CTRL_ID_PANEL_LOOPS:
15903 if (setup.sound_loops)
15904 setup.sound_loops = FALSE;
15905 else if (audio.loops_available)
15907 setup.sound = setup.sound_loops = TRUE;
15909 SetAudioMode(setup.sound);
15912 RedrawSoundButtonGadget(id);
15916 case SOUND_CTRL_ID_SIMPLE:
15917 case SOUND_CTRL_ID_PANEL_SIMPLE:
15918 if (setup.sound_simple)
15919 setup.sound_simple = FALSE;
15920 else if (audio.sound_available)
15922 setup.sound = setup.sound_simple = TRUE;
15924 SetAudioMode(setup.sound);
15927 RedrawSoundButtonGadget(id);
15936 static void HandleGameButtons(struct GadgetInfo *gi)
15938 HandleGameButtonsExt(gi->custom_id, gi->event.button);
15941 void HandleSoundButtonKeys(Key key)
15943 if (key == setup.shortcut.sound_simple)
15944 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
15945 else if (key == setup.shortcut.sound_loops)
15946 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
15947 else if (key == setup.shortcut.sound_music)
15948 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);