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 InitPlayerField(int x, int y, int element, boolean init_game)
1688 if (element == EL_SP_MURPHY)
1692 if (stored_player[0].present)
1694 Feld[x][y] = EL_SP_MURPHY_CLONE;
1700 stored_player[0].initial_element = element;
1701 stored_player[0].use_murphy = TRUE;
1703 if (!level.use_artwork_element[0])
1704 stored_player[0].artwork_element = EL_SP_MURPHY;
1707 Feld[x][y] = EL_PLAYER_1;
1713 struct PlayerInfo *player = &stored_player[Feld[x][y] - EL_PLAYER_1];
1714 int jx = player->jx, jy = player->jy;
1716 player->present = TRUE;
1718 player->block_last_field = (element == EL_SP_MURPHY ?
1719 level.sp_block_last_field :
1720 level.block_last_field);
1722 // ---------- initialize player's last field block delay ------------------
1724 // always start with reliable default value (no adjustment needed)
1725 player->block_delay_adjustment = 0;
1727 // special case 1: in Supaplex, Murphy blocks last field one more frame
1728 if (player->block_last_field && element == EL_SP_MURPHY)
1729 player->block_delay_adjustment = 1;
1731 // special case 2: in game engines before 3.1.1, blocking was different
1732 if (game.use_block_last_field_bug)
1733 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1735 if (!network.enabled || player->connected_network)
1737 player->active = TRUE;
1739 // remove potentially duplicate players
1740 if (StorePlayer[jx][jy] == Feld[x][y])
1741 StorePlayer[jx][jy] = 0;
1743 StorePlayer[x][y] = Feld[x][y];
1745 #if DEBUG_INIT_PLAYER
1748 printf("- player element %d activated", player->element_nr);
1749 printf(" (local player is %d and currently %s)\n",
1750 local_player->element_nr,
1751 local_player->active ? "active" : "not active");
1756 Feld[x][y] = EL_EMPTY;
1758 player->jx = player->last_jx = x;
1759 player->jy = player->last_jy = y;
1764 int player_nr = GET_PLAYER_NR(element);
1765 struct PlayerInfo *player = &stored_player[player_nr];
1767 if (player->active && player->killed)
1768 player->reanimated = TRUE; // if player was just killed, reanimate him
1772 static void InitField(int x, int y, boolean init_game)
1774 int element = Feld[x][y];
1783 InitPlayerField(x, y, element, init_game);
1786 case EL_SOKOBAN_FIELD_PLAYER:
1787 element = Feld[x][y] = EL_PLAYER_1;
1788 InitField(x, y, init_game);
1790 element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1791 InitField(x, y, init_game);
1794 case EL_SOKOBAN_FIELD_EMPTY:
1795 local_player->sokoban_fields_still_needed++;
1798 case EL_SOKOBAN_OBJECT:
1799 local_player->sokoban_objects_still_needed++;
1803 if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID)
1804 Feld[x][y] = EL_ACID_POOL_TOPLEFT;
1805 else if (x > 0 && Feld[x-1][y] == EL_ACID)
1806 Feld[x][y] = EL_ACID_POOL_TOPRIGHT;
1807 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPLEFT)
1808 Feld[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1809 else if (y > 0 && Feld[x][y-1] == EL_ACID)
1810 Feld[x][y] = EL_ACID_POOL_BOTTOM;
1811 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1812 Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1821 case EL_SPACESHIP_RIGHT:
1822 case EL_SPACESHIP_UP:
1823 case EL_SPACESHIP_LEFT:
1824 case EL_SPACESHIP_DOWN:
1825 case EL_BD_BUTTERFLY:
1826 case EL_BD_BUTTERFLY_RIGHT:
1827 case EL_BD_BUTTERFLY_UP:
1828 case EL_BD_BUTTERFLY_LEFT:
1829 case EL_BD_BUTTERFLY_DOWN:
1831 case EL_BD_FIREFLY_RIGHT:
1832 case EL_BD_FIREFLY_UP:
1833 case EL_BD_FIREFLY_LEFT:
1834 case EL_BD_FIREFLY_DOWN:
1835 case EL_PACMAN_RIGHT:
1837 case EL_PACMAN_LEFT:
1838 case EL_PACMAN_DOWN:
1840 case EL_YAMYAM_LEFT:
1841 case EL_YAMYAM_RIGHT:
1843 case EL_YAMYAM_DOWN:
1844 case EL_DARK_YAMYAM:
1847 case EL_SP_SNIKSNAK:
1848 case EL_SP_ELECTRON:
1857 case EL_AMOEBA_FULL:
1862 case EL_AMOEBA_DROP:
1863 if (y == lev_fieldy - 1)
1865 Feld[x][y] = EL_AMOEBA_GROWING;
1866 Store[x][y] = EL_AMOEBA_WET;
1870 case EL_DYNAMITE_ACTIVE:
1871 case EL_SP_DISK_RED_ACTIVE:
1872 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1873 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1874 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1875 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1876 MovDelay[x][y] = 96;
1879 case EL_EM_DYNAMITE_ACTIVE:
1880 MovDelay[x][y] = 32;
1884 local_player->lights_still_needed++;
1888 local_player->friends_still_needed++;
1893 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1896 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1897 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1898 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1899 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1900 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1901 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1902 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1903 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1904 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1905 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1906 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1907 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1910 int belt_nr = getBeltNrFromBeltSwitchElement(Feld[x][y]);
1911 int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]);
1912 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[x][y]);
1914 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1916 game.belt_dir[belt_nr] = belt_dir;
1917 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1919 else // more than one switch -- set it like the first switch
1921 Feld[x][y] = Feld[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1926 case EL_LIGHT_SWITCH_ACTIVE:
1928 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1931 case EL_INVISIBLE_STEELWALL:
1932 case EL_INVISIBLE_WALL:
1933 case EL_INVISIBLE_SAND:
1934 if (game.light_time_left > 0 ||
1935 game.lenses_time_left > 0)
1936 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
1939 case EL_EMC_MAGIC_BALL:
1940 if (game.ball_state)
1941 Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1944 case EL_EMC_MAGIC_BALL_SWITCH:
1945 if (game.ball_state)
1946 Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1949 case EL_TRIGGER_PLAYER:
1950 case EL_TRIGGER_ELEMENT:
1951 case EL_TRIGGER_CE_VALUE:
1952 case EL_TRIGGER_CE_SCORE:
1954 case EL_ANY_ELEMENT:
1955 case EL_CURRENT_CE_VALUE:
1956 case EL_CURRENT_CE_SCORE:
1973 // reference elements should not be used on the playfield
1974 Feld[x][y] = EL_EMPTY;
1978 if (IS_CUSTOM_ELEMENT(element))
1980 if (CAN_MOVE(element))
1983 if (!element_info[element].use_last_ce_value || init_game)
1984 CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
1986 else if (IS_GROUP_ELEMENT(element))
1988 Feld[x][y] = GetElementFromGroupElement(element);
1990 InitField(x, y, init_game);
1997 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2000 static void InitField_WithBug1(int x, int y, boolean init_game)
2002 InitField(x, y, init_game);
2004 // not needed to call InitMovDir() -- already done by InitField()!
2005 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2006 CAN_MOVE(Feld[x][y]))
2010 static void InitField_WithBug2(int x, int y, boolean init_game)
2012 int old_element = Feld[x][y];
2014 InitField(x, y, init_game);
2016 // not needed to call InitMovDir() -- already done by InitField()!
2017 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2018 CAN_MOVE(old_element) &&
2019 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2022 /* this case is in fact a combination of not less than three bugs:
2023 first, it calls InitMovDir() for elements that can move, although this is
2024 already done by InitField(); then, it checks the element that was at this
2025 field _before_ the call to InitField() (which can change it); lastly, it
2026 was not called for "mole with direction" elements, which were treated as
2027 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2031 static int get_key_element_from_nr(int key_nr)
2033 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2034 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2035 EL_EM_KEY_1 : EL_KEY_1);
2037 return key_base_element + key_nr;
2040 static int get_next_dropped_element(struct PlayerInfo *player)
2042 return (player->inventory_size > 0 ?
2043 player->inventory_element[player->inventory_size - 1] :
2044 player->inventory_infinite_element != EL_UNDEFINED ?
2045 player->inventory_infinite_element :
2046 player->dynabombs_left > 0 ?
2047 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2051 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2053 // pos >= 0: get element from bottom of the stack;
2054 // pos < 0: get element from top of the stack
2058 int min_inventory_size = -pos;
2059 int inventory_pos = player->inventory_size - min_inventory_size;
2060 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2062 return (player->inventory_size >= min_inventory_size ?
2063 player->inventory_element[inventory_pos] :
2064 player->inventory_infinite_element != EL_UNDEFINED ?
2065 player->inventory_infinite_element :
2066 player->dynabombs_left >= min_dynabombs_left ?
2067 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2072 int min_dynabombs_left = pos + 1;
2073 int min_inventory_size = pos + 1 - player->dynabombs_left;
2074 int inventory_pos = pos - player->dynabombs_left;
2076 return (player->inventory_infinite_element != EL_UNDEFINED ?
2077 player->inventory_infinite_element :
2078 player->dynabombs_left >= min_dynabombs_left ?
2079 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2080 player->inventory_size >= min_inventory_size ?
2081 player->inventory_element[inventory_pos] :
2086 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2088 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2089 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2092 if (gpo1->sort_priority != gpo2->sort_priority)
2093 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2095 compare_result = gpo1->nr - gpo2->nr;
2097 return compare_result;
2100 int getPlayerInventorySize(int player_nr)
2102 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2103 return level.native_em_level->ply[player_nr]->dynamite;
2104 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2105 return level.native_sp_level->game_sp->red_disk_count;
2107 return stored_player[player_nr].inventory_size;
2110 static void InitGameControlValues(void)
2114 for (i = 0; game_panel_controls[i].nr != -1; i++)
2116 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2117 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2118 struct TextPosInfo *pos = gpc->pos;
2120 int type = gpc->type;
2124 Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
2125 Error(ERR_EXIT, "this should not happen -- please debug");
2128 // force update of game controls after initialization
2129 gpc->value = gpc->last_value = -1;
2130 gpc->frame = gpc->last_frame = -1;
2131 gpc->gfx_frame = -1;
2133 // determine panel value width for later calculation of alignment
2134 if (type == TYPE_INTEGER || type == TYPE_STRING)
2136 pos->width = pos->size * getFontWidth(pos->font);
2137 pos->height = getFontHeight(pos->font);
2139 else if (type == TYPE_ELEMENT)
2141 pos->width = pos->size;
2142 pos->height = pos->size;
2145 // fill structure for game panel draw order
2147 gpo->sort_priority = pos->sort_priority;
2150 // sort game panel controls according to sort_priority and control number
2151 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2152 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2155 static void UpdatePlayfieldElementCount(void)
2157 boolean use_element_count = FALSE;
2160 // first check if it is needed at all to calculate playfield element count
2161 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2162 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2163 use_element_count = TRUE;
2165 if (!use_element_count)
2168 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2169 element_info[i].element_count = 0;
2171 SCAN_PLAYFIELD(x, y)
2173 element_info[Feld[x][y]].element_count++;
2176 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2177 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2178 if (IS_IN_GROUP(j, i))
2179 element_info[EL_GROUP_START + i].element_count +=
2180 element_info[j].element_count;
2183 static void UpdateGameControlValues(void)
2186 int time = (local_player->LevelSolved ?
2187 local_player->LevelSolved_CountingTime :
2188 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2189 level.native_em_level->lev->time :
2190 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2191 level.native_sp_level->game_sp->time_played :
2192 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2193 game_mm.energy_left :
2194 game.no_time_limit ? TimePlayed : TimeLeft);
2195 int score = (local_player->LevelSolved ?
2196 local_player->LevelSolved_CountingScore :
2197 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2198 level.native_em_level->lev->score :
2199 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2200 level.native_sp_level->game_sp->score :
2201 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2203 local_player->score);
2204 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2205 level.native_em_level->lev->required :
2206 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2207 level.native_sp_level->game_sp->infotrons_still_needed :
2208 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2209 game_mm.kettles_still_needed :
2210 local_player->gems_still_needed);
2211 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2212 level.native_em_level->lev->required > 0 :
2213 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2214 level.native_sp_level->game_sp->infotrons_still_needed > 0 :
2215 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2216 game_mm.kettles_still_needed > 0 ||
2217 game_mm.lights_still_needed > 0 :
2218 local_player->gems_still_needed > 0 ||
2219 local_player->sokoban_fields_still_needed > 0 ||
2220 local_player->sokoban_objects_still_needed > 0 ||
2221 local_player->lights_still_needed > 0);
2222 int health = (local_player->LevelSolved ?
2223 local_player->LevelSolved_CountingHealth :
2224 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2225 MM_HEALTH(game_mm.laser_overload_value) :
2226 local_player->health);
2228 UpdatePlayfieldElementCount();
2230 // update game panel control values
2232 // used instead of "level_nr" (for network games)
2233 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2234 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2236 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2237 for (i = 0; i < MAX_NUM_KEYS; i++)
2238 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2239 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2240 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2242 if (game.centered_player_nr == -1)
2244 for (i = 0; i < MAX_PLAYERS; i++)
2246 // only one player in Supaplex game engine
2247 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2250 for (k = 0; k < MAX_NUM_KEYS; k++)
2252 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2254 if (level.native_em_level->ply[i]->keys & (1 << k))
2255 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2256 get_key_element_from_nr(k);
2258 else if (stored_player[i].key[k])
2259 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2260 get_key_element_from_nr(k);
2263 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2264 getPlayerInventorySize(i);
2266 if (stored_player[i].num_white_keys > 0)
2267 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2270 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2271 stored_player[i].num_white_keys;
2276 int player_nr = game.centered_player_nr;
2278 for (k = 0; k < MAX_NUM_KEYS; k++)
2280 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2282 if (level.native_em_level->ply[player_nr]->keys & (1 << k))
2283 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2284 get_key_element_from_nr(k);
2286 else if (stored_player[player_nr].key[k])
2287 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2288 get_key_element_from_nr(k);
2291 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2292 getPlayerInventorySize(player_nr);
2294 if (stored_player[player_nr].num_white_keys > 0)
2295 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2297 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2298 stored_player[player_nr].num_white_keys;
2301 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2303 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2304 get_inventory_element_from_pos(local_player, i);
2305 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2306 get_inventory_element_from_pos(local_player, -i - 1);
2309 game_panel_controls[GAME_PANEL_SCORE].value = score;
2310 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2312 game_panel_controls[GAME_PANEL_TIME].value = time;
2314 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2315 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2316 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2318 if (level.time == 0)
2319 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2321 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2323 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2324 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2326 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2328 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2329 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2331 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2332 local_player->shield_normal_time_left;
2333 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2334 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2336 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2337 local_player->shield_deadly_time_left;
2339 game_panel_controls[GAME_PANEL_EXIT].value =
2340 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2342 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2343 (game.ball_state ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2344 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2345 (game.ball_state ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2346 EL_EMC_MAGIC_BALL_SWITCH);
2348 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2349 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2350 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2351 game.light_time_left;
2353 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2354 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2355 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2356 game.timegate_time_left;
2358 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2359 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2361 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2362 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2363 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2364 game.lenses_time_left;
2366 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2367 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2368 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2369 game.magnify_time_left;
2371 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2372 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2373 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2374 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2375 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2376 EL_BALLOON_SWITCH_NONE);
2378 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2379 local_player->dynabomb_count;
2380 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2381 local_player->dynabomb_size;
2382 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2383 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2385 game_panel_controls[GAME_PANEL_PENGUINS].value =
2386 local_player->friends_still_needed;
2388 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2389 local_player->sokoban_objects_still_needed;
2390 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2391 local_player->sokoban_fields_still_needed;
2393 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2394 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2396 for (i = 0; i < NUM_BELTS; i++)
2398 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2399 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2400 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2401 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2402 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2405 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2406 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2407 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2408 game.magic_wall_time_left;
2410 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2411 local_player->gravity;
2413 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2414 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2416 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2417 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2418 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2419 game.panel.element[i].id : EL_UNDEFINED);
2421 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2422 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2423 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2424 element_info[game.panel.element_count[i].id].element_count : 0);
2426 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2427 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2428 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2429 element_info[game.panel.ce_score[i].id].collect_score : 0);
2431 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2432 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2433 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2434 element_info[game.panel.ce_score_element[i].id].collect_score :
2437 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2438 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2439 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2441 // update game panel control frames
2443 for (i = 0; game_panel_controls[i].nr != -1; i++)
2445 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2447 if (gpc->type == TYPE_ELEMENT)
2449 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2451 int last_anim_random_frame = gfx.anim_random_frame;
2452 int element = gpc->value;
2453 int graphic = el2panelimg(element);
2455 if (gpc->value != gpc->last_value)
2458 gpc->gfx_random = INIT_GFX_RANDOM();
2464 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2465 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2466 gpc->gfx_random = INIT_GFX_RANDOM();
2469 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2470 gfx.anim_random_frame = gpc->gfx_random;
2472 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2473 gpc->gfx_frame = element_info[element].collect_score;
2475 gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
2478 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2479 gfx.anim_random_frame = last_anim_random_frame;
2482 else if (gpc->type == TYPE_GRAPHIC)
2484 if (gpc->graphic != IMG_UNDEFINED)
2486 int last_anim_random_frame = gfx.anim_random_frame;
2487 int graphic = gpc->graphic;
2489 if (gpc->value != gpc->last_value)
2492 gpc->gfx_random = INIT_GFX_RANDOM();
2498 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2499 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2500 gpc->gfx_random = INIT_GFX_RANDOM();
2503 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2504 gfx.anim_random_frame = gpc->gfx_random;
2506 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2508 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2509 gfx.anim_random_frame = last_anim_random_frame;
2515 static void DisplayGameControlValues(void)
2517 boolean redraw_panel = FALSE;
2520 for (i = 0; game_panel_controls[i].nr != -1; i++)
2522 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2524 if (PANEL_DEACTIVATED(gpc->pos))
2527 if (gpc->value == gpc->last_value &&
2528 gpc->frame == gpc->last_frame)
2531 redraw_panel = TRUE;
2537 // copy default game door content to main double buffer
2539 // !!! CHECK AGAIN !!!
2540 SetPanelBackground();
2541 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2542 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2544 // redraw game control buttons
2545 RedrawGameButtons();
2547 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2549 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2551 int nr = game_panel_order[i].nr;
2552 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2553 struct TextPosInfo *pos = gpc->pos;
2554 int type = gpc->type;
2555 int value = gpc->value;
2556 int frame = gpc->frame;
2557 int size = pos->size;
2558 int font = pos->font;
2559 boolean draw_masked = pos->draw_masked;
2560 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2562 if (PANEL_DEACTIVATED(pos))
2565 gpc->last_value = value;
2566 gpc->last_frame = frame;
2568 if (type == TYPE_INTEGER)
2570 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2571 nr == GAME_PANEL_TIME)
2573 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2575 if (use_dynamic_size) // use dynamic number of digits
2577 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2578 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2579 int size2 = size1 + 1;
2580 int font1 = pos->font;
2581 int font2 = pos->font_alt;
2583 size = (value < value_change ? size1 : size2);
2584 font = (value < value_change ? font1 : font2);
2588 // correct text size if "digits" is zero or less
2590 size = strlen(int2str(value, size));
2592 // dynamically correct text alignment
2593 pos->width = size * getFontWidth(font);
2595 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2596 int2str(value, size), font, mask_mode);
2598 else if (type == TYPE_ELEMENT)
2600 int element, graphic;
2604 int dst_x = PANEL_XPOS(pos);
2605 int dst_y = PANEL_YPOS(pos);
2607 if (value != EL_UNDEFINED && value != EL_EMPTY)
2610 graphic = el2panelimg(value);
2612 // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
2614 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2617 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2620 width = graphic_info[graphic].width * size / TILESIZE;
2621 height = graphic_info[graphic].height * size / TILESIZE;
2624 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2627 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2631 else if (type == TYPE_GRAPHIC)
2633 int graphic = gpc->graphic;
2634 int graphic_active = gpc->graphic_active;
2638 int dst_x = PANEL_XPOS(pos);
2639 int dst_y = PANEL_YPOS(pos);
2640 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2641 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2643 if (graphic != IMG_UNDEFINED && !skip)
2645 if (pos->style == STYLE_REVERSE)
2646 value = 100 - value;
2648 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2650 if (pos->direction & MV_HORIZONTAL)
2652 width = graphic_info[graphic_active].width * value / 100;
2653 height = graphic_info[graphic_active].height;
2655 if (pos->direction == MV_LEFT)
2657 src_x += graphic_info[graphic_active].width - width;
2658 dst_x += graphic_info[graphic_active].width - width;
2663 width = graphic_info[graphic_active].width;
2664 height = graphic_info[graphic_active].height * value / 100;
2666 if (pos->direction == MV_UP)
2668 src_y += graphic_info[graphic_active].height - height;
2669 dst_y += graphic_info[graphic_active].height - height;
2674 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2677 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2680 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2682 if (pos->direction & MV_HORIZONTAL)
2684 if (pos->direction == MV_RIGHT)
2691 dst_x = PANEL_XPOS(pos);
2694 width = graphic_info[graphic].width - width;
2698 if (pos->direction == MV_DOWN)
2705 dst_y = PANEL_YPOS(pos);
2708 height = graphic_info[graphic].height - height;
2712 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2715 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2719 else if (type == TYPE_STRING)
2721 boolean active = (value != 0);
2722 char *state_normal = "off";
2723 char *state_active = "on";
2724 char *state = (active ? state_active : state_normal);
2725 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2726 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2727 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2728 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2730 if (nr == GAME_PANEL_GRAVITY_STATE)
2732 int font1 = pos->font; // (used for normal state)
2733 int font2 = pos->font_alt; // (used for active state)
2735 font = (active ? font2 : font1);
2744 // don't truncate output if "chars" is zero or less
2747 // dynamically correct text alignment
2748 pos->width = size * getFontWidth(font);
2751 s_cut = getStringCopyN(s, size);
2753 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2754 s_cut, font, mask_mode);
2760 redraw_mask |= REDRAW_DOOR_1;
2763 SetGameStatus(GAME_MODE_PLAYING);
2766 void UpdateAndDisplayGameControlValues(void)
2768 if (tape.deactivate_display)
2771 UpdateGameControlValues();
2772 DisplayGameControlValues();
2776 static void UpdateGameDoorValues(void)
2778 UpdateGameControlValues();
2782 void DrawGameDoorValues(void)
2784 DisplayGameControlValues();
2788 // ============================================================================
2790 // ----------------------------------------------------------------------------
2791 // initialize game engine due to level / tape version number
2792 // ============================================================================
2794 static void InitGameEngine(void)
2796 int i, j, k, l, x, y;
2798 // set game engine from tape file when re-playing, else from level file
2799 game.engine_version = (tape.playing ? tape.engine_version :
2800 level.game_version);
2802 // set single or multi-player game mode (needed for re-playing tapes)
2803 game.team_mode = setup.team_mode;
2807 int num_players = 0;
2809 for (i = 0; i < MAX_PLAYERS; i++)
2810 if (tape.player_participates[i])
2813 // multi-player tapes contain input data for more than one player
2814 game.team_mode = (num_players > 1);
2817 // --------------------------------------------------------------------------
2818 // set flags for bugs and changes according to active game engine version
2819 // --------------------------------------------------------------------------
2822 Summary of bugfix/change:
2823 Fixed handling for custom elements that change when pushed by the player.
2825 Fixed/changed in version:
2829 Before 3.1.0, custom elements that "change when pushing" changed directly
2830 after the player started pushing them (until then handled in "DigField()").
2831 Since 3.1.0, these custom elements are not changed until the "pushing"
2832 move of the element is finished (now handled in "ContinueMoving()").
2834 Affected levels/tapes:
2835 The first condition is generally needed for all levels/tapes before version
2836 3.1.0, which might use the old behaviour before it was changed; known tapes
2837 that are affected are some tapes from the level set "Walpurgis Gardens" by
2839 The second condition is an exception from the above case and is needed for
2840 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2841 above (including some development versions of 3.1.0), but before it was
2842 known that this change would break tapes like the above and was fixed in
2843 3.1.1, so that the changed behaviour was active although the engine version
2844 while recording maybe was before 3.1.0. There is at least one tape that is
2845 affected by this exception, which is the tape for the one-level set "Bug
2846 Machine" by Juergen Bonhagen.
2849 game.use_change_when_pushing_bug =
2850 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2852 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2853 tape.game_version < VERSION_IDENT(3,1,1,0)));
2856 Summary of bugfix/change:
2857 Fixed handling for blocking the field the player leaves when moving.
2859 Fixed/changed in version:
2863 Before 3.1.1, when "block last field when moving" was enabled, the field
2864 the player is leaving when moving was blocked for the time of the move,
2865 and was directly unblocked afterwards. This resulted in the last field
2866 being blocked for exactly one less than the number of frames of one player
2867 move. Additionally, even when blocking was disabled, the last field was
2868 blocked for exactly one frame.
2869 Since 3.1.1, due to changes in player movement handling, the last field
2870 is not blocked at all when blocking is disabled. When blocking is enabled,
2871 the last field is blocked for exactly the number of frames of one player
2872 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2873 last field is blocked for exactly one more than the number of frames of
2876 Affected levels/tapes:
2877 (!!! yet to be determined -- probably many !!!)
2880 game.use_block_last_field_bug =
2881 (game.engine_version < VERSION_IDENT(3,1,1,0));
2883 game_em.use_single_button =
2884 (game.engine_version > VERSION_IDENT(4,0,0,2));
2886 game_em.use_snap_key_bug =
2887 (game.engine_version < VERSION_IDENT(4,0,1,0));
2889 // --------------------------------------------------------------------------
2891 // set maximal allowed number of custom element changes per game frame
2892 game.max_num_changes_per_frame = 1;
2894 // default scan direction: scan playfield from top/left to bottom/right
2895 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
2897 // dynamically adjust element properties according to game engine version
2898 InitElementPropertiesEngine(game.engine_version);
2901 printf("level %d: level version == %06d\n", level_nr, level.game_version);
2902 printf(" tape version == %06d [%s] [file: %06d]\n",
2903 tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
2905 printf(" => game.engine_version == %06d\n", game.engine_version);
2908 // ---------- initialize player's initial move delay ------------------------
2910 // dynamically adjust player properties according to level information
2911 for (i = 0; i < MAX_PLAYERS; i++)
2912 game.initial_move_delay_value[i] =
2913 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
2915 // dynamically adjust player properties according to game engine version
2916 for (i = 0; i < MAX_PLAYERS; i++)
2917 game.initial_move_delay[i] =
2918 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
2919 game.initial_move_delay_value[i] : 0);
2921 // ---------- initialize player's initial push delay ------------------------
2923 // dynamically adjust player properties according to game engine version
2924 game.initial_push_delay_value =
2925 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
2927 // ---------- initialize changing elements ----------------------------------
2929 // initialize changing elements information
2930 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2932 struct ElementInfo *ei = &element_info[i];
2934 // this pointer might have been changed in the level editor
2935 ei->change = &ei->change_page[0];
2937 if (!IS_CUSTOM_ELEMENT(i))
2939 ei->change->target_element = EL_EMPTY_SPACE;
2940 ei->change->delay_fixed = 0;
2941 ei->change->delay_random = 0;
2942 ei->change->delay_frames = 1;
2945 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
2947 ei->has_change_event[j] = FALSE;
2949 ei->event_page_nr[j] = 0;
2950 ei->event_page[j] = &ei->change_page[0];
2954 // add changing elements from pre-defined list
2955 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
2957 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
2958 struct ElementInfo *ei = &element_info[ch_delay->element];
2960 ei->change->target_element = ch_delay->target_element;
2961 ei->change->delay_fixed = ch_delay->change_delay;
2963 ei->change->pre_change_function = ch_delay->pre_change_function;
2964 ei->change->change_function = ch_delay->change_function;
2965 ei->change->post_change_function = ch_delay->post_change_function;
2967 ei->change->can_change = TRUE;
2968 ei->change->can_change_or_has_action = TRUE;
2970 ei->has_change_event[CE_DELAY] = TRUE;
2972 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
2973 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
2976 // ---------- initialize internal run-time variables ------------------------
2978 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
2980 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
2982 for (j = 0; j < ei->num_change_pages; j++)
2984 ei->change_page[j].can_change_or_has_action =
2985 (ei->change_page[j].can_change |
2986 ei->change_page[j].has_action);
2990 // add change events from custom element configuration
2991 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
2993 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
2995 for (j = 0; j < ei->num_change_pages; j++)
2997 if (!ei->change_page[j].can_change_or_has_action)
3000 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3002 // only add event page for the first page found with this event
3003 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3005 ei->has_change_event[k] = TRUE;
3007 ei->event_page_nr[k] = j;
3008 ei->event_page[k] = &ei->change_page[j];
3014 // ---------- initialize reference elements in change conditions ------------
3016 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3018 int element = EL_CUSTOM_START + i;
3019 struct ElementInfo *ei = &element_info[element];
3021 for (j = 0; j < ei->num_change_pages; j++)
3023 int trigger_element = ei->change_page[j].initial_trigger_element;
3025 if (trigger_element >= EL_PREV_CE_8 &&
3026 trigger_element <= EL_NEXT_CE_8)
3027 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3029 ei->change_page[j].trigger_element = trigger_element;
3033 // ---------- initialize run-time trigger player and element ----------------
3035 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3037 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3039 for (j = 0; j < ei->num_change_pages; j++)
3041 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3042 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3043 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3044 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3045 ei->change_page[j].actual_trigger_ce_value = 0;
3046 ei->change_page[j].actual_trigger_ce_score = 0;
3050 // ---------- initialize trigger events -------------------------------------
3052 // initialize trigger events information
3053 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3054 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3055 trigger_events[i][j] = FALSE;
3057 // add trigger events from element change event properties
3058 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3060 struct ElementInfo *ei = &element_info[i];
3062 for (j = 0; j < ei->num_change_pages; j++)
3064 if (!ei->change_page[j].can_change_or_has_action)
3067 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3069 int trigger_element = ei->change_page[j].trigger_element;
3071 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3073 if (ei->change_page[j].has_event[k])
3075 if (IS_GROUP_ELEMENT(trigger_element))
3077 struct ElementGroupInfo *group =
3078 element_info[trigger_element].group;
3080 for (l = 0; l < group->num_elements_resolved; l++)
3081 trigger_events[group->element_resolved[l]][k] = TRUE;
3083 else if (trigger_element == EL_ANY_ELEMENT)
3084 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3085 trigger_events[l][k] = TRUE;
3087 trigger_events[trigger_element][k] = TRUE;
3094 // ---------- initialize push delay -----------------------------------------
3096 // initialize push delay values to default
3097 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3099 if (!IS_CUSTOM_ELEMENT(i))
3101 // set default push delay values (corrected since version 3.0.7-1)
3102 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3104 element_info[i].push_delay_fixed = 2;
3105 element_info[i].push_delay_random = 8;
3109 element_info[i].push_delay_fixed = 8;
3110 element_info[i].push_delay_random = 8;
3115 // set push delay value for certain elements from pre-defined list
3116 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3118 int e = push_delay_list[i].element;
3120 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3121 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3124 // set push delay value for Supaplex elements for newer engine versions
3125 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3127 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3129 if (IS_SP_ELEMENT(i))
3131 // set SP push delay to just enough to push under a falling zonk
3132 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3134 element_info[i].push_delay_fixed = delay;
3135 element_info[i].push_delay_random = 0;
3140 // ---------- initialize move stepsize --------------------------------------
3142 // initialize move stepsize values to default
3143 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3144 if (!IS_CUSTOM_ELEMENT(i))
3145 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3147 // set move stepsize value for certain elements from pre-defined list
3148 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3150 int e = move_stepsize_list[i].element;
3152 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3155 // ---------- initialize collect score --------------------------------------
3157 // initialize collect score values for custom elements from initial value
3158 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3159 if (IS_CUSTOM_ELEMENT(i))
3160 element_info[i].collect_score = element_info[i].collect_score_initial;
3162 // ---------- initialize collect count --------------------------------------
3164 // initialize collect count values for non-custom elements
3165 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3166 if (!IS_CUSTOM_ELEMENT(i))
3167 element_info[i].collect_count_initial = 0;
3169 // add collect count values for all elements from pre-defined list
3170 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3171 element_info[collect_count_list[i].element].collect_count_initial =
3172 collect_count_list[i].count;
3174 // ---------- initialize access direction -----------------------------------
3176 // initialize access direction values to default (access from every side)
3177 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3178 if (!IS_CUSTOM_ELEMENT(i))
3179 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3181 // set access direction value for certain elements from pre-defined list
3182 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3183 element_info[access_direction_list[i].element].access_direction =
3184 access_direction_list[i].direction;
3186 // ---------- initialize explosion content ----------------------------------
3187 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3189 if (IS_CUSTOM_ELEMENT(i))
3192 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3194 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3196 element_info[i].content.e[x][y] =
3197 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3198 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3199 i == EL_PLAYER_3 ? EL_EMERALD :
3200 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3201 i == EL_MOLE ? EL_EMERALD_RED :
3202 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3203 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3204 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3205 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3206 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3207 i == EL_WALL_EMERALD ? EL_EMERALD :
3208 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3209 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3210 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3211 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3212 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3213 i == EL_WALL_PEARL ? EL_PEARL :
3214 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3219 // ---------- initialize recursion detection --------------------------------
3220 recursion_loop_depth = 0;
3221 recursion_loop_detected = FALSE;
3222 recursion_loop_element = EL_UNDEFINED;
3224 // ---------- initialize graphics engine ------------------------------------
3225 game.scroll_delay_value =
3226 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3227 setup.scroll_delay ? setup.scroll_delay_value : 0);
3228 game.scroll_delay_value =
3229 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3231 // ---------- initialize game engine snapshots ------------------------------
3232 for (i = 0; i < MAX_PLAYERS; i++)
3233 game.snapshot.last_action[i] = 0;
3234 game.snapshot.changed_action = FALSE;
3235 game.snapshot.collected_item = FALSE;
3236 game.snapshot.mode =
3237 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3238 SNAPSHOT_MODE_EVERY_STEP :
3239 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3240 SNAPSHOT_MODE_EVERY_MOVE :
3241 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3242 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3243 game.snapshot.save_snapshot = FALSE;
3245 // ---------- initialize level time for Supaplex engine ---------------------
3246 // Supaplex levels with time limit currently unsupported -- should be added
3247 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3251 static int get_num_special_action(int element, int action_first,
3254 int num_special_action = 0;
3257 for (i = action_first; i <= action_last; i++)
3259 boolean found = FALSE;
3261 for (j = 0; j < NUM_DIRECTIONS; j++)
3262 if (el_act_dir2img(element, i, j) !=
3263 el_act_dir2img(element, ACTION_DEFAULT, j))
3267 num_special_action++;
3272 return num_special_action;
3276 // ============================================================================
3278 // ----------------------------------------------------------------------------
3279 // initialize and start new game
3280 // ============================================================================
3282 #if DEBUG_INIT_PLAYER
3283 static void DebugPrintPlayerStatus(char *message)
3290 printf("%s:\n", message);
3292 for (i = 0; i < MAX_PLAYERS; i++)
3294 struct PlayerInfo *player = &stored_player[i];
3296 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3300 player->connected_locally,
3301 player->connected_network,
3304 if (local_player == player)
3305 printf(" (local player)");
3314 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3315 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3316 int fade_mask = REDRAW_FIELD;
3318 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3319 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3320 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3321 int initial_move_dir = MV_DOWN;
3324 // required here to update video display before fading (FIX THIS)
3325 DrawMaskedBorder(REDRAW_DOOR_2);
3327 if (!game.restart_level)
3328 CloseDoor(DOOR_CLOSE_1);
3330 SetGameStatus(GAME_MODE_PLAYING);
3332 if (level_editor_test_game)
3333 FadeSkipNextFadeIn();
3335 FadeSetEnterScreen();
3337 if (CheckIfGlobalBorderOrPlayfieldViewportHasChanged())
3338 fade_mask = REDRAW_ALL;
3340 FadeLevelSoundsAndMusic();
3342 ExpireSoundLoops(TRUE);
3346 // needed if different viewport properties defined for playing
3347 ChangeViewportPropertiesIfNeeded();
3351 DrawCompleteVideoDisplay();
3353 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3356 InitGameControlValues();
3358 // don't play tapes over network
3359 network_playing = (network.enabled && !tape.playing);
3361 for (i = 0; i < MAX_PLAYERS; i++)
3363 struct PlayerInfo *player = &stored_player[i];
3365 player->index_nr = i;
3366 player->index_bit = (1 << i);
3367 player->element_nr = EL_PLAYER_1 + i;
3369 player->present = FALSE;
3370 player->active = FALSE;
3371 player->mapped = FALSE;
3373 player->killed = FALSE;
3374 player->reanimated = FALSE;
3377 player->effective_action = 0;
3378 player->programmed_action = 0;
3380 player->mouse_action.lx = 0;
3381 player->mouse_action.ly = 0;
3382 player->mouse_action.button = 0;
3383 player->mouse_action.button_hint = 0;
3385 player->effective_mouse_action.lx = 0;
3386 player->effective_mouse_action.ly = 0;
3387 player->effective_mouse_action.button = 0;
3388 player->effective_mouse_action.button_hint = 0;
3391 player->score_final = 0;
3393 player->health = MAX_HEALTH;
3394 player->health_final = MAX_HEALTH;
3396 player->gems_still_needed = level.gems_needed;
3397 player->sokoban_fields_still_needed = 0;
3398 player->sokoban_objects_still_needed = 0;
3399 player->lights_still_needed = 0;
3400 player->players_still_needed = 0;
3401 player->friends_still_needed = 0;
3403 for (j = 0; j < MAX_NUM_KEYS; j++)
3404 player->key[j] = FALSE;
3406 player->num_white_keys = 0;
3408 player->dynabomb_count = 0;
3409 player->dynabomb_size = 1;
3410 player->dynabombs_left = 0;
3411 player->dynabomb_xl = FALSE;
3413 player->MovDir = initial_move_dir;
3416 player->GfxDir = initial_move_dir;
3417 player->GfxAction = ACTION_DEFAULT;
3419 player->StepFrame = 0;
3421 player->initial_element = player->element_nr;
3422 player->artwork_element =
3423 (level.use_artwork_element[i] ? level.artwork_element[i] :
3424 player->element_nr);
3425 player->use_murphy = FALSE;
3427 player->block_last_field = FALSE; // initialized in InitPlayerField()
3428 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3430 player->gravity = level.initial_player_gravity[i];
3432 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3434 player->actual_frame_counter = 0;
3436 player->step_counter = 0;
3438 player->last_move_dir = initial_move_dir;
3440 player->is_active = FALSE;
3442 player->is_waiting = FALSE;
3443 player->is_moving = FALSE;
3444 player->is_auto_moving = FALSE;
3445 player->is_digging = FALSE;
3446 player->is_snapping = FALSE;
3447 player->is_collecting = FALSE;
3448 player->is_pushing = FALSE;
3449 player->is_switching = FALSE;
3450 player->is_dropping = FALSE;
3451 player->is_dropping_pressed = FALSE;
3453 player->is_bored = FALSE;
3454 player->is_sleeping = FALSE;
3456 player->was_waiting = TRUE;
3457 player->was_moving = FALSE;
3458 player->was_snapping = FALSE;
3459 player->was_dropping = FALSE;
3461 player->force_dropping = FALSE;
3463 player->frame_counter_bored = -1;
3464 player->frame_counter_sleeping = -1;
3466 player->anim_delay_counter = 0;
3467 player->post_delay_counter = 0;
3469 player->dir_waiting = initial_move_dir;
3470 player->action_waiting = ACTION_DEFAULT;
3471 player->last_action_waiting = ACTION_DEFAULT;
3472 player->special_action_bored = ACTION_DEFAULT;
3473 player->special_action_sleeping = ACTION_DEFAULT;
3475 player->switch_x = -1;
3476 player->switch_y = -1;
3478 player->drop_x = -1;
3479 player->drop_y = -1;
3481 player->show_envelope = 0;
3483 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3485 player->push_delay = -1; // initialized when pushing starts
3486 player->push_delay_value = game.initial_push_delay_value;
3488 player->drop_delay = 0;
3489 player->drop_pressed_delay = 0;
3491 player->last_jx = -1;
3492 player->last_jy = -1;
3496 player->shield_normal_time_left = 0;
3497 player->shield_deadly_time_left = 0;
3499 player->inventory_infinite_element = EL_UNDEFINED;
3500 player->inventory_size = 0;
3502 if (level.use_initial_inventory[i])
3504 for (j = 0; j < level.initial_inventory_size[i]; j++)
3506 int element = level.initial_inventory_content[i][j];
3507 int collect_count = element_info[element].collect_count_initial;
3510 if (!IS_CUSTOM_ELEMENT(element))
3513 if (collect_count == 0)
3514 player->inventory_infinite_element = element;
3516 for (k = 0; k < collect_count; k++)
3517 if (player->inventory_size < MAX_INVENTORY_SIZE)
3518 player->inventory_element[player->inventory_size++] = element;
3522 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3523 SnapField(player, 0, 0);
3525 player->LevelSolved = FALSE;
3526 player->GameOver = FALSE;
3528 player->LevelSolved_GameWon = FALSE;
3529 player->LevelSolved_GameEnd = FALSE;
3530 player->LevelSolved_SaveTape = FALSE;
3531 player->LevelSolved_SaveScore = FALSE;
3533 player->LevelSolved_CountingTime = 0;
3534 player->LevelSolved_CountingScore = 0;
3535 player->LevelSolved_CountingHealth = 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);
3552 TimeLeft = level.time;
3555 ScreenMovDir = MV_NONE;
3559 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3561 AllPlayersGone = FALSE;
3563 game.panel.active = TRUE;
3565 game.no_time_limit = (level.time == 0);
3567 game.yamyam_content_nr = 0;
3568 game.robot_wheel_active = FALSE;
3569 game.magic_wall_active = FALSE;
3570 game.magic_wall_time_left = 0;
3571 game.light_time_left = 0;
3572 game.timegate_time_left = 0;
3573 game.switchgate_pos = 0;
3574 game.wind_direction = level.wind_direction_initial;
3576 game.lenses_time_left = 0;
3577 game.magnify_time_left = 0;
3579 game.ball_state = level.ball_state_initial;
3580 game.ball_content_nr = 0;
3582 game.explosions_delayed = TRUE;
3584 game.envelope_active = FALSE;
3586 for (i = 0; i < NUM_BELTS; i++)
3588 game.belt_dir[i] = MV_NONE;
3589 game.belt_dir_nr[i] = 3; // not moving, next moving left
3592 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3593 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3595 #if DEBUG_INIT_PLAYER
3596 DebugPrintPlayerStatus("Player status at level initialization");
3599 SCAN_PLAYFIELD(x, y)
3601 Feld[x][y] = Last[x][y] = level.field[x][y];
3602 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3603 ChangeDelay[x][y] = 0;
3604 ChangePage[x][y] = -1;
3605 CustomValue[x][y] = 0; // initialized in InitField()
3606 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3608 WasJustMoving[x][y] = 0;
3609 WasJustFalling[x][y] = 0;
3610 CheckCollision[x][y] = 0;
3611 CheckImpact[x][y] = 0;
3613 Pushed[x][y] = FALSE;
3615 ChangeCount[x][y] = 0;
3616 ChangeEvent[x][y] = -1;
3618 ExplodePhase[x][y] = 0;
3619 ExplodeDelay[x][y] = 0;
3620 ExplodeField[x][y] = EX_TYPE_NONE;
3622 RunnerVisit[x][y] = 0;
3623 PlayerVisit[x][y] = 0;
3626 GfxRandom[x][y] = INIT_GFX_RANDOM();
3627 GfxElement[x][y] = EL_UNDEFINED;
3628 GfxAction[x][y] = ACTION_DEFAULT;
3629 GfxDir[x][y] = MV_NONE;
3630 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3633 SCAN_PLAYFIELD(x, y)
3635 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3637 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3639 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3642 InitField(x, y, TRUE);
3644 ResetGfxAnimation(x, y);
3649 for (i = 0; i < MAX_PLAYERS; i++)
3651 struct PlayerInfo *player = &stored_player[i];
3653 // set number of special actions for bored and sleeping animation
3654 player->num_special_action_bored =
3655 get_num_special_action(player->artwork_element,
3656 ACTION_BORING_1, ACTION_BORING_LAST);
3657 player->num_special_action_sleeping =
3658 get_num_special_action(player->artwork_element,
3659 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3662 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3663 emulate_sb ? EMU_SOKOBAN :
3664 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3666 // initialize type of slippery elements
3667 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3669 if (!IS_CUSTOM_ELEMENT(i))
3671 // default: elements slip down either to the left or right randomly
3672 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3674 // SP style elements prefer to slip down on the left side
3675 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3676 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3678 // BD style elements prefer to slip down on the left side
3679 if (game.emulation == EMU_BOULDERDASH)
3680 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3684 // initialize explosion and ignition delay
3685 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3687 if (!IS_CUSTOM_ELEMENT(i))
3690 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3691 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3692 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3693 int last_phase = (num_phase + 1) * delay;
3694 int half_phase = (num_phase / 2) * delay;
3696 element_info[i].explosion_delay = last_phase - 1;
3697 element_info[i].ignition_delay = half_phase;
3699 if (i == EL_BLACK_ORB)
3700 element_info[i].ignition_delay = 1;
3704 // correct non-moving belts to start moving left
3705 for (i = 0; i < NUM_BELTS; i++)
3706 if (game.belt_dir[i] == MV_NONE)
3707 game.belt_dir_nr[i] = 3; // not moving, next moving left
3709 #if USE_NEW_PLAYER_ASSIGNMENTS
3710 for (i = 0; i < MAX_PLAYERS; i++)
3712 stored_player[i].connected = FALSE;
3714 // in network game mode, the local player might not be the first player
3715 if (stored_player[i].connected_locally)
3716 local_player = &stored_player[i];
3719 if (!network.enabled)
3720 local_player->connected = TRUE;
3724 for (i = 0; i < MAX_PLAYERS; i++)
3725 stored_player[i].connected = tape.player_participates[i];
3727 else if (network.enabled)
3729 // add team mode players connected over the network (needed for correct
3730 // assignment of player figures from level to locally playing players)
3732 for (i = 0; i < MAX_PLAYERS; i++)
3733 if (stored_player[i].connected_network)
3734 stored_player[i].connected = TRUE;
3736 else if (game.team_mode)
3738 // try to guess locally connected team mode players (needed for correct
3739 // assignment of player figures from level to locally playing players)
3741 for (i = 0; i < MAX_PLAYERS; i++)
3742 if (setup.input[i].use_joystick ||
3743 setup.input[i].key.left != KSYM_UNDEFINED)
3744 stored_player[i].connected = TRUE;
3747 #if DEBUG_INIT_PLAYER
3748 DebugPrintPlayerStatus("Player status after level initialization");
3751 #if DEBUG_INIT_PLAYER
3753 printf("Reassigning players ...\n");
3756 // check if any connected player was not found in playfield
3757 for (i = 0; i < MAX_PLAYERS; i++)
3759 struct PlayerInfo *player = &stored_player[i];
3761 if (player->connected && !player->present)
3763 struct PlayerInfo *field_player = NULL;
3765 #if DEBUG_INIT_PLAYER
3767 printf("- looking for field player for player %d ...\n", i + 1);
3770 // assign first free player found that is present in the playfield
3772 // first try: look for unmapped playfield player that is not connected
3773 for (j = 0; j < MAX_PLAYERS; j++)
3774 if (field_player == NULL &&
3775 stored_player[j].present &&
3776 !stored_player[j].mapped &&
3777 !stored_player[j].connected)
3778 field_player = &stored_player[j];
3780 // second try: look for *any* unmapped playfield player
3781 for (j = 0; j < MAX_PLAYERS; j++)
3782 if (field_player == NULL &&
3783 stored_player[j].present &&
3784 !stored_player[j].mapped)
3785 field_player = &stored_player[j];
3787 if (field_player != NULL)
3789 int jx = field_player->jx, jy = field_player->jy;
3791 #if DEBUG_INIT_PLAYER
3793 printf("- found player %d\n", field_player->index_nr + 1);
3796 player->present = FALSE;
3797 player->active = FALSE;
3799 field_player->present = TRUE;
3800 field_player->active = TRUE;
3803 player->initial_element = field_player->initial_element;
3804 player->artwork_element = field_player->artwork_element;
3806 player->block_last_field = field_player->block_last_field;
3807 player->block_delay_adjustment = field_player->block_delay_adjustment;
3810 StorePlayer[jx][jy] = field_player->element_nr;
3812 field_player->jx = field_player->last_jx = jx;
3813 field_player->jy = field_player->last_jy = jy;
3815 if (local_player == player)
3816 local_player = field_player;
3818 map_player_action[field_player->index_nr] = i;
3820 field_player->mapped = TRUE;
3822 #if DEBUG_INIT_PLAYER
3824 printf("- map_player_action[%d] == %d\n",
3825 field_player->index_nr + 1, i + 1);
3830 if (player->connected && player->present)
3831 player->mapped = TRUE;
3834 #if DEBUG_INIT_PLAYER
3835 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
3840 // check if any connected player was not found in playfield
3841 for (i = 0; i < MAX_PLAYERS; i++)
3843 struct PlayerInfo *player = &stored_player[i];
3845 if (player->connected && !player->present)
3847 for (j = 0; j < MAX_PLAYERS; j++)
3849 struct PlayerInfo *field_player = &stored_player[j];
3850 int jx = field_player->jx, jy = field_player->jy;
3852 // assign first free player found that is present in the playfield
3853 if (field_player->present && !field_player->connected)
3855 player->present = TRUE;
3856 player->active = TRUE;
3858 field_player->present = FALSE;
3859 field_player->active = FALSE;
3861 player->initial_element = field_player->initial_element;
3862 player->artwork_element = field_player->artwork_element;
3864 player->block_last_field = field_player->block_last_field;
3865 player->block_delay_adjustment = field_player->block_delay_adjustment;
3867 StorePlayer[jx][jy] = player->element_nr;
3869 player->jx = player->last_jx = jx;
3870 player->jy = player->last_jy = jy;
3880 printf("::: local_player->present == %d\n", local_player->present);
3883 // set focus to local player for network games, else to all players
3884 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
3885 game.centered_player_nr_next = game.centered_player_nr;
3886 game.set_centered_player = FALSE;
3888 if (network_playing && tape.recording)
3890 // store client dependent player focus when recording network games
3891 tape.centered_player_nr_next = game.centered_player_nr_next;
3892 tape.set_centered_player = TRUE;
3897 // when playing a tape, eliminate all players who do not participate
3899 #if USE_NEW_PLAYER_ASSIGNMENTS
3901 if (!game.team_mode)
3903 for (i = 0; i < MAX_PLAYERS; i++)
3905 if (stored_player[i].active &&
3906 !tape.player_participates[map_player_action[i]])
3908 struct PlayerInfo *player = &stored_player[i];
3909 int jx = player->jx, jy = player->jy;
3911 #if DEBUG_INIT_PLAYER
3913 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
3916 player->active = FALSE;
3917 StorePlayer[jx][jy] = 0;
3918 Feld[jx][jy] = EL_EMPTY;
3925 for (i = 0; i < MAX_PLAYERS; i++)
3927 if (stored_player[i].active &&
3928 !tape.player_participates[i])
3930 struct PlayerInfo *player = &stored_player[i];
3931 int jx = player->jx, jy = player->jy;
3933 player->active = FALSE;
3934 StorePlayer[jx][jy] = 0;
3935 Feld[jx][jy] = EL_EMPTY;
3940 else if (!network.enabled && !game.team_mode) // && !tape.playing
3942 // when in single player mode, eliminate all but the local player
3944 for (i = 0; i < MAX_PLAYERS; i++)
3946 struct PlayerInfo *player = &stored_player[i];
3948 if (player->active && player != local_player)
3950 int jx = player->jx, jy = player->jy;
3952 player->active = FALSE;
3953 player->present = FALSE;
3955 StorePlayer[jx][jy] = 0;
3956 Feld[jx][jy] = EL_EMPTY;
3961 for (i = 0; i < MAX_PLAYERS; i++)
3962 if (stored_player[i].active)
3963 local_player->players_still_needed++;
3965 if (level.solved_by_one_player)
3966 local_player->players_still_needed = 1;
3968 // when recording the game, store which players take part in the game
3971 #if USE_NEW_PLAYER_ASSIGNMENTS
3972 for (i = 0; i < MAX_PLAYERS; i++)
3973 if (stored_player[i].connected)
3974 tape.player_participates[i] = TRUE;
3976 for (i = 0; i < MAX_PLAYERS; i++)
3977 if (stored_player[i].active)
3978 tape.player_participates[i] = TRUE;
3982 #if DEBUG_INIT_PLAYER
3983 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
3986 if (BorderElement == EL_EMPTY)
3989 SBX_Right = lev_fieldx - SCR_FIELDX;
3991 SBY_Lower = lev_fieldy - SCR_FIELDY;
3996 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
3998 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4001 if (full_lev_fieldx <= SCR_FIELDX)
4002 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4003 if (full_lev_fieldy <= SCR_FIELDY)
4004 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4006 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4008 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4011 // if local player not found, look for custom element that might create
4012 // the player (make some assumptions about the right custom element)
4013 if (!local_player->present)
4015 int start_x = 0, start_y = 0;
4016 int found_rating = 0;
4017 int found_element = EL_UNDEFINED;
4018 int player_nr = local_player->index_nr;
4020 SCAN_PLAYFIELD(x, y)
4022 int element = Feld[x][y];
4027 if (level.use_start_element[player_nr] &&
4028 level.start_element[player_nr] == element &&
4035 found_element = element;
4038 if (!IS_CUSTOM_ELEMENT(element))
4041 if (CAN_CHANGE(element))
4043 for (i = 0; i < element_info[element].num_change_pages; i++)
4045 // check for player created from custom element as single target
4046 content = element_info[element].change_page[i].target_element;
4047 is_player = ELEM_IS_PLAYER(content);
4049 if (is_player && (found_rating < 3 ||
4050 (found_rating == 3 && element < found_element)))
4056 found_element = element;
4061 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4063 // check for player created from custom element as explosion content
4064 content = element_info[element].content.e[xx][yy];
4065 is_player = ELEM_IS_PLAYER(content);
4067 if (is_player && (found_rating < 2 ||
4068 (found_rating == 2 && element < found_element)))
4070 start_x = x + xx - 1;
4071 start_y = y + yy - 1;
4074 found_element = element;
4077 if (!CAN_CHANGE(element))
4080 for (i = 0; i < element_info[element].num_change_pages; i++)
4082 // check for player created from custom element as extended target
4084 element_info[element].change_page[i].target_content.e[xx][yy];
4086 is_player = ELEM_IS_PLAYER(content);
4088 if (is_player && (found_rating < 1 ||
4089 (found_rating == 1 && element < found_element)))
4091 start_x = x + xx - 1;
4092 start_y = y + yy - 1;
4095 found_element = element;
4101 scroll_x = SCROLL_POSITION_X(start_x);
4102 scroll_y = SCROLL_POSITION_Y(start_y);
4106 scroll_x = SCROLL_POSITION_X(local_player->jx);
4107 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4110 // !!! FIX THIS (START) !!!
4111 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4113 InitGameEngine_EM();
4115 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4117 InitGameEngine_SP();
4119 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4121 InitGameEngine_MM();
4125 DrawLevel(REDRAW_FIELD);
4128 // after drawing the level, correct some elements
4129 if (game.timegate_time_left == 0)
4130 CloseAllOpenTimegates();
4133 // blit playfield from scroll buffer to normal back buffer for fading in
4134 BlitScreenToBitmap(backbuffer);
4135 // !!! FIX THIS (END) !!!
4137 DrawMaskedBorder(fade_mask);
4142 // full screen redraw is required at this point in the following cases:
4143 // - special editor door undrawn when game was started from level editor
4144 // - drawing area (playfield) was changed and has to be removed completely
4145 redraw_mask = REDRAW_ALL;
4149 if (!game.restart_level)
4151 // copy default game door content to main double buffer
4153 // !!! CHECK AGAIN !!!
4154 SetPanelBackground();
4155 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4156 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4159 SetPanelBackground();
4160 SetDrawBackgroundMask(REDRAW_DOOR_1);
4162 UpdateAndDisplayGameControlValues();
4164 if (!game.restart_level)
4170 CreateGameButtons();
4175 // copy actual game door content to door double buffer for OpenDoor()
4176 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4178 OpenDoor(DOOR_OPEN_ALL);
4180 KeyboardAutoRepeatOffUnlessAutoplay();
4182 #if DEBUG_INIT_PLAYER
4183 DebugPrintPlayerStatus("Player status (final)");
4192 if (!game.restart_level && !tape.playing)
4194 LevelStats_incPlayed(level_nr);
4196 SaveLevelSetup_SeriesInfo();
4199 game.restart_level = FALSE;
4200 game.restart_game_message = NULL;
4201 game.request_active = FALSE;
4203 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4204 InitGameActions_MM();
4206 SaveEngineSnapshotToListInitial();
4208 if (!game.restart_level)
4210 PlaySound(SND_GAME_STARTING);
4212 if (setup.sound_music)
4217 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4218 int actual_player_x, int actual_player_y)
4220 // this is used for non-R'n'D game engines to update certain engine values
4222 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4224 actual_player_x = correctLevelPosX_EM(actual_player_x);
4225 actual_player_y = correctLevelPosY_EM(actual_player_y);
4228 // needed to determine if sounds are played within the visible screen area
4229 scroll_x = actual_scroll_x;
4230 scroll_y = actual_scroll_y;
4232 // needed to get player position for "follow finger" playing input method
4233 local_player->jx = actual_player_x;
4234 local_player->jy = actual_player_y;
4237 void InitMovDir(int x, int y)
4239 int i, element = Feld[x][y];
4240 static int xy[4][2] =
4247 static int direction[3][4] =
4249 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4250 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4251 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4260 Feld[x][y] = EL_BUG;
4261 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4264 case EL_SPACESHIP_RIGHT:
4265 case EL_SPACESHIP_UP:
4266 case EL_SPACESHIP_LEFT:
4267 case EL_SPACESHIP_DOWN:
4268 Feld[x][y] = EL_SPACESHIP;
4269 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4272 case EL_BD_BUTTERFLY_RIGHT:
4273 case EL_BD_BUTTERFLY_UP:
4274 case EL_BD_BUTTERFLY_LEFT:
4275 case EL_BD_BUTTERFLY_DOWN:
4276 Feld[x][y] = EL_BD_BUTTERFLY;
4277 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4280 case EL_BD_FIREFLY_RIGHT:
4281 case EL_BD_FIREFLY_UP:
4282 case EL_BD_FIREFLY_LEFT:
4283 case EL_BD_FIREFLY_DOWN:
4284 Feld[x][y] = EL_BD_FIREFLY;
4285 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4288 case EL_PACMAN_RIGHT:
4290 case EL_PACMAN_LEFT:
4291 case EL_PACMAN_DOWN:
4292 Feld[x][y] = EL_PACMAN;
4293 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4296 case EL_YAMYAM_LEFT:
4297 case EL_YAMYAM_RIGHT:
4299 case EL_YAMYAM_DOWN:
4300 Feld[x][y] = EL_YAMYAM;
4301 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4304 case EL_SP_SNIKSNAK:
4305 MovDir[x][y] = MV_UP;
4308 case EL_SP_ELECTRON:
4309 MovDir[x][y] = MV_LEFT;
4316 Feld[x][y] = EL_MOLE;
4317 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4321 if (IS_CUSTOM_ELEMENT(element))
4323 struct ElementInfo *ei = &element_info[element];
4324 int move_direction_initial = ei->move_direction_initial;
4325 int move_pattern = ei->move_pattern;
4327 if (move_direction_initial == MV_START_PREVIOUS)
4329 if (MovDir[x][y] != MV_NONE)
4332 move_direction_initial = MV_START_AUTOMATIC;
4335 if (move_direction_initial == MV_START_RANDOM)
4336 MovDir[x][y] = 1 << RND(4);
4337 else if (move_direction_initial & MV_ANY_DIRECTION)
4338 MovDir[x][y] = move_direction_initial;
4339 else if (move_pattern == MV_ALL_DIRECTIONS ||
4340 move_pattern == MV_TURNING_LEFT ||
4341 move_pattern == MV_TURNING_RIGHT ||
4342 move_pattern == MV_TURNING_LEFT_RIGHT ||
4343 move_pattern == MV_TURNING_RIGHT_LEFT ||
4344 move_pattern == MV_TURNING_RANDOM)
4345 MovDir[x][y] = 1 << RND(4);
4346 else if (move_pattern == MV_HORIZONTAL)
4347 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4348 else if (move_pattern == MV_VERTICAL)
4349 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4350 else if (move_pattern & MV_ANY_DIRECTION)
4351 MovDir[x][y] = element_info[element].move_pattern;
4352 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4353 move_pattern == MV_ALONG_RIGHT_SIDE)
4355 // use random direction as default start direction
4356 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4357 MovDir[x][y] = 1 << RND(4);
4359 for (i = 0; i < NUM_DIRECTIONS; i++)
4361 int x1 = x + xy[i][0];
4362 int y1 = y + xy[i][1];
4364 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4366 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4367 MovDir[x][y] = direction[0][i];
4369 MovDir[x][y] = direction[1][i];
4378 MovDir[x][y] = 1 << RND(4);
4380 if (element != EL_BUG &&
4381 element != EL_SPACESHIP &&
4382 element != EL_BD_BUTTERFLY &&
4383 element != EL_BD_FIREFLY)
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 (element == EL_BUG || element == EL_BD_BUTTERFLY)
4395 MovDir[x][y] = direction[0][i];
4398 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4399 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4401 MovDir[x][y] = direction[1][i];
4410 GfxDir[x][y] = MovDir[x][y];
4413 void InitAmoebaNr(int x, int y)
4416 int group_nr = AmoebeNachbarNr(x, y);
4420 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4422 if (AmoebaCnt[i] == 0)
4430 AmoebaNr[x][y] = group_nr;
4431 AmoebaCnt[group_nr]++;
4432 AmoebaCnt2[group_nr]++;
4435 static void PlayerWins(struct PlayerInfo *player)
4437 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4438 local_player->players_still_needed > 0)
4441 player->LevelSolved = TRUE;
4442 player->GameOver = TRUE;
4444 player->score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4445 level.native_em_level->lev->score :
4446 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4449 player->health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4450 MM_HEALTH(game_mm.laser_overload_value) :
4453 player->LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed :
4455 player->LevelSolved_CountingScore = player->score_final;
4456 player->LevelSolved_CountingHealth = player->health_final;
4461 static int time_count_steps;
4462 static int time, time_final;
4463 static int score, score_final;
4464 static int health, health_final;
4465 static int game_over_delay_1 = 0;
4466 static int game_over_delay_2 = 0;
4467 static int game_over_delay_3 = 0;
4468 int game_over_delay_value_1 = 50;
4469 int game_over_delay_value_2 = 25;
4470 int game_over_delay_value_3 = 50;
4472 if (!local_player->LevelSolved_GameWon)
4476 // do not start end game actions before the player stops moving (to exit)
4477 if (local_player->MovPos)
4480 local_player->LevelSolved_GameWon = TRUE;
4481 local_player->LevelSolved_SaveTape = tape.recording;
4482 local_player->LevelSolved_SaveScore = !tape.playing;
4486 LevelStats_incSolved(level_nr);
4488 SaveLevelSetup_SeriesInfo();
4491 if (tape.auto_play) // tape might already be stopped here
4492 tape.auto_play_level_solved = TRUE;
4496 game_over_delay_1 = 0;
4497 game_over_delay_2 = 0;
4498 game_over_delay_3 = game_over_delay_value_3;
4500 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4501 score = score_final = local_player->score_final;
4502 health = health_final = local_player->health_final;
4504 if (level.score[SC_TIME_BONUS] > 0)
4509 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4511 else if (game.no_time_limit && TimePlayed < 999)
4514 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4517 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4519 game_over_delay_1 = game_over_delay_value_1;
4521 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4524 score_final += health * level.score[SC_TIME_BONUS];
4526 game_over_delay_2 = game_over_delay_value_2;
4529 local_player->score_final = score_final;
4530 local_player->health_final = health_final;
4533 if (level_editor_test_game)
4536 score = score_final;
4538 local_player->LevelSolved_CountingTime = time;
4539 local_player->LevelSolved_CountingScore = score;
4541 game_panel_controls[GAME_PANEL_TIME].value = time;
4542 game_panel_controls[GAME_PANEL_SCORE].value = score;
4544 DisplayGameControlValues();
4547 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4549 if (ExitX >= 0 && ExitY >= 0) // local player has left the level
4551 // close exit door after last player
4552 if ((AllPlayersGone &&
4553 (Feld[ExitX][ExitY] == EL_EXIT_OPEN ||
4554 Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN ||
4555 Feld[ExitX][ExitY] == EL_STEEL_EXIT_OPEN)) ||
4556 Feld[ExitX][ExitY] == EL_EM_EXIT_OPEN ||
4557 Feld[ExitX][ExitY] == EL_EM_STEEL_EXIT_OPEN)
4559 int element = Feld[ExitX][ExitY];
4561 Feld[ExitX][ExitY] =
4562 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4563 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4564 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4565 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4566 EL_EM_STEEL_EXIT_CLOSING);
4568 PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING);
4571 // player disappears
4572 DrawLevelField(ExitX, ExitY);
4575 for (i = 0; i < MAX_PLAYERS; i++)
4577 struct PlayerInfo *player = &stored_player[i];
4579 if (player->present)
4581 RemovePlayer(player);
4583 // player disappears
4584 DrawLevelField(player->jx, player->jy);
4589 PlaySound(SND_GAME_WINNING);
4592 if (game_over_delay_1 > 0)
4594 game_over_delay_1--;
4599 if (time != time_final)
4601 int time_to_go = ABS(time_final - time);
4602 int time_count_dir = (time < time_final ? +1 : -1);
4604 if (time_to_go < time_count_steps)
4605 time_count_steps = 1;
4607 time += time_count_steps * time_count_dir;
4608 score += time_count_steps * level.score[SC_TIME_BONUS];
4610 local_player->LevelSolved_CountingTime = time;
4611 local_player->LevelSolved_CountingScore = score;
4613 game_panel_controls[GAME_PANEL_TIME].value = time;
4614 game_panel_controls[GAME_PANEL_SCORE].value = score;
4616 DisplayGameControlValues();
4618 if (time == time_final)
4619 StopSound(SND_GAME_LEVELTIME_BONUS);
4620 else if (setup.sound_loops)
4621 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4623 PlaySound(SND_GAME_LEVELTIME_BONUS);
4628 if (game_over_delay_2 > 0)
4630 game_over_delay_2--;
4635 if (health != health_final)
4637 int health_count_dir = (health < health_final ? +1 : -1);
4639 health += health_count_dir;
4640 score += level.score[SC_TIME_BONUS];
4642 local_player->LevelSolved_CountingHealth = health;
4643 local_player->LevelSolved_CountingScore = score;
4645 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4646 game_panel_controls[GAME_PANEL_SCORE].value = score;
4648 DisplayGameControlValues();
4650 if (health == health_final)
4651 StopSound(SND_GAME_LEVELTIME_BONUS);
4652 else if (setup.sound_loops)
4653 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4655 PlaySound(SND_GAME_LEVELTIME_BONUS);
4660 game.panel.active = FALSE;
4662 if (game_over_delay_3 > 0)
4664 game_over_delay_3--;
4674 // used instead of "level_nr" (needed for network games)
4675 int last_level_nr = levelset.level_nr;
4678 local_player->LevelSolved_GameEnd = TRUE;
4680 if (local_player->LevelSolved_SaveTape)
4682 // make sure that request dialog to save tape does not open door again
4683 if (!global.use_envelope_request)
4684 CloseDoor(DOOR_CLOSE_1);
4686 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4689 // if no tape is to be saved, close both doors simultaneously
4690 CloseDoor(DOOR_CLOSE_ALL);
4692 if (level_editor_test_game)
4694 SetGameStatus(GAME_MODE_MAIN);
4701 if (!local_player->LevelSolved_SaveScore)
4703 SetGameStatus(GAME_MODE_MAIN);
4710 if (level_nr == leveldir_current->handicap_level)
4712 leveldir_current->handicap_level++;
4714 SaveLevelSetup_SeriesInfo();
4717 if (setup.increment_levels &&
4718 level_nr < leveldir_current->last_level &&
4721 level_nr++; // advance to next level
4722 TapeErase(); // start with empty tape
4724 if (setup.auto_play_next_level)
4726 LoadLevel(level_nr);
4728 SaveLevelSetup_SeriesInfo();
4732 hi_pos = NewHiScore(last_level_nr);
4734 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4736 SetGameStatus(GAME_MODE_SCORES);
4738 DrawHallOfFame(last_level_nr, hi_pos);
4740 else if (setup.auto_play_next_level && setup.increment_levels &&
4741 last_level_nr < leveldir_current->last_level &&
4744 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4748 SetGameStatus(GAME_MODE_MAIN);
4754 int NewHiScore(int level_nr)
4758 boolean one_score_entry_per_name = !program.many_scores_per_name;
4760 LoadScore(level_nr);
4762 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4763 local_player->score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4766 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4768 if (local_player->score_final > highscore[k].Score)
4770 // player has made it to the hall of fame
4772 if (k < MAX_SCORE_ENTRIES - 1)
4774 int m = MAX_SCORE_ENTRIES - 1;
4776 if (one_score_entry_per_name)
4778 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4779 if (strEqual(setup.player_name, highscore[l].Name))
4782 if (m == k) // player's new highscore overwrites his old one
4786 for (l = m; l > k; l--)
4788 strcpy(highscore[l].Name, highscore[l - 1].Name);
4789 highscore[l].Score = highscore[l - 1].Score;
4795 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4796 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4797 highscore[k].Score = local_player->score_final;
4802 else if (one_score_entry_per_name &&
4803 !strncmp(setup.player_name, highscore[k].Name,
4804 MAX_PLAYER_NAME_LEN))
4805 break; // player already there with a higher score
4809 SaveScore(level_nr);
4814 static int getElementMoveStepsizeExt(int x, int y, int direction)
4816 int element = Feld[x][y];
4817 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4818 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4819 int horiz_move = (dx != 0);
4820 int sign = (horiz_move ? dx : dy);
4821 int step = sign * element_info[element].move_stepsize;
4823 // special values for move stepsize for spring and things on conveyor belt
4826 if (CAN_FALL(element) &&
4827 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
4828 step = sign * MOVE_STEPSIZE_NORMAL / 2;
4829 else if (element == EL_SPRING)
4830 step = sign * MOVE_STEPSIZE_NORMAL * 2;
4836 static int getElementMoveStepsize(int x, int y)
4838 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
4841 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
4843 if (player->GfxAction != action || player->GfxDir != dir)
4845 player->GfxAction = action;
4846 player->GfxDir = dir;
4848 player->StepFrame = 0;
4852 static void ResetGfxFrame(int x, int y)
4854 // profiling showed that "autotest" spends 10~20% of its time in this function
4855 if (DrawingDeactivatedField())
4858 int element = Feld[x][y];
4859 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
4861 if (graphic_info[graphic].anim_global_sync)
4862 GfxFrame[x][y] = FrameCounter;
4863 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
4864 GfxFrame[x][y] = CustomValue[x][y];
4865 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
4866 GfxFrame[x][y] = element_info[element].collect_score;
4867 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
4868 GfxFrame[x][y] = ChangeDelay[x][y];
4871 static void ResetGfxAnimation(int x, int y)
4873 GfxAction[x][y] = ACTION_DEFAULT;
4874 GfxDir[x][y] = MovDir[x][y];
4877 ResetGfxFrame(x, y);
4880 static void ResetRandomAnimationValue(int x, int y)
4882 GfxRandom[x][y] = INIT_GFX_RANDOM();
4885 static void InitMovingField(int x, int y, int direction)
4887 int element = Feld[x][y];
4888 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4889 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4892 boolean is_moving_before, is_moving_after;
4894 // check if element was/is moving or being moved before/after mode change
4895 is_moving_before = (WasJustMoving[x][y] != 0);
4896 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
4898 // reset animation only for moving elements which change direction of moving
4899 // or which just started or stopped moving
4900 // (else CEs with property "can move" / "not moving" are reset each frame)
4901 if (is_moving_before != is_moving_after ||
4902 direction != MovDir[x][y])
4903 ResetGfxAnimation(x, y);
4905 MovDir[x][y] = direction;
4906 GfxDir[x][y] = direction;
4908 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
4909 direction == MV_DOWN && CAN_FALL(element) ?
4910 ACTION_FALLING : ACTION_MOVING);
4912 // this is needed for CEs with property "can move" / "not moving"
4914 if (is_moving_after)
4916 if (Feld[newx][newy] == EL_EMPTY)
4917 Feld[newx][newy] = EL_BLOCKED;
4919 MovDir[newx][newy] = MovDir[x][y];
4921 CustomValue[newx][newy] = CustomValue[x][y];
4923 GfxFrame[newx][newy] = GfxFrame[x][y];
4924 GfxRandom[newx][newy] = GfxRandom[x][y];
4925 GfxAction[newx][newy] = GfxAction[x][y];
4926 GfxDir[newx][newy] = GfxDir[x][y];
4930 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
4932 int direction = MovDir[x][y];
4933 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
4934 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
4940 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
4942 int oldx = x, oldy = y;
4943 int direction = MovDir[x][y];
4945 if (direction == MV_LEFT)
4947 else if (direction == MV_RIGHT)
4949 else if (direction == MV_UP)
4951 else if (direction == MV_DOWN)
4954 *comes_from_x = oldx;
4955 *comes_from_y = oldy;
4958 static int MovingOrBlocked2Element(int x, int y)
4960 int element = Feld[x][y];
4962 if (element == EL_BLOCKED)
4966 Blocked2Moving(x, y, &oldx, &oldy);
4967 return Feld[oldx][oldy];
4973 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
4975 // like MovingOrBlocked2Element(), but if element is moving
4976 // and (x,y) is the field the moving element is just leaving,
4977 // return EL_BLOCKED instead of the element value
4978 int element = Feld[x][y];
4980 if (IS_MOVING(x, y))
4982 if (element == EL_BLOCKED)
4986 Blocked2Moving(x, y, &oldx, &oldy);
4987 return Feld[oldx][oldy];
4996 static void RemoveField(int x, int y)
4998 Feld[x][y] = EL_EMPTY;
5004 CustomValue[x][y] = 0;
5007 ChangeDelay[x][y] = 0;
5008 ChangePage[x][y] = -1;
5009 Pushed[x][y] = FALSE;
5011 GfxElement[x][y] = EL_UNDEFINED;
5012 GfxAction[x][y] = ACTION_DEFAULT;
5013 GfxDir[x][y] = MV_NONE;
5016 static void RemoveMovingField(int x, int y)
5018 int oldx = x, oldy = y, newx = x, newy = y;
5019 int element = Feld[x][y];
5020 int next_element = EL_UNDEFINED;
5022 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5025 if (IS_MOVING(x, y))
5027 Moving2Blocked(x, y, &newx, &newy);
5029 if (Feld[newx][newy] != EL_BLOCKED)
5031 // element is moving, but target field is not free (blocked), but
5032 // already occupied by something different (example: acid pool);
5033 // in this case, only remove the moving field, but not the target
5035 RemoveField(oldx, oldy);
5037 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5039 TEST_DrawLevelField(oldx, oldy);
5044 else if (element == EL_BLOCKED)
5046 Blocked2Moving(x, y, &oldx, &oldy);
5047 if (!IS_MOVING(oldx, oldy))
5051 if (element == EL_BLOCKED &&
5052 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5053 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5054 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5055 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5056 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5057 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5058 next_element = get_next_element(Feld[oldx][oldy]);
5060 RemoveField(oldx, oldy);
5061 RemoveField(newx, newy);
5063 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5065 if (next_element != EL_UNDEFINED)
5066 Feld[oldx][oldy] = next_element;
5068 TEST_DrawLevelField(oldx, oldy);
5069 TEST_DrawLevelField(newx, newy);
5072 void DrawDynamite(int x, int y)
5074 int sx = SCREENX(x), sy = SCREENY(y);
5075 int graphic = el2img(Feld[x][y]);
5078 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5081 if (IS_WALKABLE_INSIDE(Back[x][y]))
5085 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5086 else if (Store[x][y])
5087 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5089 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5091 if (Back[x][y] || Store[x][y])
5092 DrawGraphicThruMask(sx, sy, graphic, frame);
5094 DrawGraphic(sx, sy, graphic, frame);
5097 static void CheckDynamite(int x, int y)
5099 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5103 if (MovDelay[x][y] != 0)
5106 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5112 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5117 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5119 boolean num_checked_players = 0;
5122 for (i = 0; i < MAX_PLAYERS; i++)
5124 if (stored_player[i].active)
5126 int sx = stored_player[i].jx;
5127 int sy = stored_player[i].jy;
5129 if (num_checked_players == 0)
5136 *sx1 = MIN(*sx1, sx);
5137 *sy1 = MIN(*sy1, sy);
5138 *sx2 = MAX(*sx2, sx);
5139 *sy2 = MAX(*sy2, sy);
5142 num_checked_players++;
5147 static boolean checkIfAllPlayersFitToScreen_RND(void)
5149 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5151 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5153 return (sx2 - sx1 < SCR_FIELDX &&
5154 sy2 - sy1 < SCR_FIELDY);
5157 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5159 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5161 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5163 *sx = (sx1 + sx2) / 2;
5164 *sy = (sy1 + sy2) / 2;
5167 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5168 boolean center_screen, boolean quick_relocation)
5170 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5171 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5172 boolean no_delay = (tape.warp_forward);
5173 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5174 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5175 int new_scroll_x, new_scroll_y;
5177 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5179 // case 1: quick relocation inside visible screen (without scrolling)
5186 if (!level.shifted_relocation || center_screen)
5188 // relocation _with_ centering of screen
5190 new_scroll_x = SCROLL_POSITION_X(x);
5191 new_scroll_y = SCROLL_POSITION_Y(y);
5195 // relocation _without_ centering of screen
5197 int center_scroll_x = SCROLL_POSITION_X(old_x);
5198 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5199 int offset_x = x + (scroll_x - center_scroll_x);
5200 int offset_y = y + (scroll_y - center_scroll_y);
5202 // for new screen position, apply previous offset to center position
5203 new_scroll_x = SCROLL_POSITION_X(offset_x);
5204 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5207 if (quick_relocation)
5209 // case 2: quick relocation (redraw without visible scrolling)
5211 scroll_x = new_scroll_x;
5212 scroll_y = new_scroll_y;
5219 // case 3: visible relocation (with scrolling to new position)
5221 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5223 SetVideoFrameDelay(wait_delay_value);
5225 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5228 int fx = FX, fy = FY;
5230 dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5231 dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5233 if (dx == 0 && dy == 0) // no scrolling needed at all
5239 fx += dx * TILEX / 2;
5240 fy += dy * TILEY / 2;
5242 ScrollLevel(dx, dy);
5245 // scroll in two steps of half tile size to make things smoother
5246 BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
5248 // scroll second step to align at full tile size
5249 BlitScreenToBitmap(window);
5255 SetVideoFrameDelay(frame_delay_value_old);
5258 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5260 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5261 int player_nr = GET_PLAYER_NR(el_player);
5262 struct PlayerInfo *player = &stored_player[player_nr];
5263 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5264 boolean no_delay = (tape.warp_forward);
5265 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5266 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5267 int old_jx = player->jx;
5268 int old_jy = player->jy;
5269 int old_element = Feld[old_jx][old_jy];
5270 int element = Feld[jx][jy];
5271 boolean player_relocated = (old_jx != jx || old_jy != jy);
5273 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5274 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5275 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5276 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5277 int leave_side_horiz = move_dir_horiz;
5278 int leave_side_vert = move_dir_vert;
5279 int enter_side = enter_side_horiz | enter_side_vert;
5280 int leave_side = leave_side_horiz | leave_side_vert;
5282 if (player->GameOver) // do not reanimate dead player
5285 if (!player_relocated) // no need to relocate the player
5288 if (IS_PLAYER(jx, jy)) // player already placed at new position
5290 RemoveField(jx, jy); // temporarily remove newly placed player
5291 DrawLevelField(jx, jy);
5294 if (player->present)
5296 while (player->MovPos)
5298 ScrollPlayer(player, SCROLL_GO_ON);
5299 ScrollScreen(NULL, SCROLL_GO_ON);
5301 AdvanceFrameAndPlayerCounters(player->index_nr);
5305 BackToFront_WithFrameDelay(wait_delay_value);
5308 DrawPlayer(player); // needed here only to cleanup last field
5309 DrawLevelField(player->jx, player->jy); // remove player graphic
5311 player->is_moving = FALSE;
5314 if (IS_CUSTOM_ELEMENT(old_element))
5315 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5317 player->index_bit, leave_side);
5319 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5321 player->index_bit, leave_side);
5323 Feld[jx][jy] = el_player;
5324 InitPlayerField(jx, jy, el_player, TRUE);
5326 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5327 possible that the relocation target field did not contain a player element,
5328 but a walkable element, to which the new player was relocated -- in this
5329 case, restore that (already initialized!) element on the player field */
5330 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5332 Feld[jx][jy] = element; // restore previously existing element
5335 // only visually relocate centered player
5336 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5337 FALSE, level.instant_relocation);
5339 TestIfPlayerTouchesBadThing(jx, jy);
5340 TestIfPlayerTouchesCustomElement(jx, jy);
5342 if (IS_CUSTOM_ELEMENT(element))
5343 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5344 player->index_bit, enter_side);
5346 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5347 player->index_bit, enter_side);
5349 if (player->is_switching)
5351 /* ensure that relocation while still switching an element does not cause
5352 a new element to be treated as also switched directly after relocation
5353 (this is important for teleporter switches that teleport the player to
5354 a place where another teleporter switch is in the same direction, which
5355 would then incorrectly be treated as immediately switched before the
5356 direction key that caused the switch was released) */
5358 player->switch_x += jx - old_jx;
5359 player->switch_y += jy - old_jy;
5363 static void Explode(int ex, int ey, int phase, int mode)
5369 // !!! eliminate this variable !!!
5370 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5372 if (game.explosions_delayed)
5374 ExplodeField[ex][ey] = mode;
5378 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5380 int center_element = Feld[ex][ey];
5381 int artwork_element, explosion_element; // set these values later
5383 // remove things displayed in background while burning dynamite
5384 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5387 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5389 // put moving element to center field (and let it explode there)
5390 center_element = MovingOrBlocked2Element(ex, ey);
5391 RemoveMovingField(ex, ey);
5392 Feld[ex][ey] = center_element;
5395 // now "center_element" is finally determined -- set related values now
5396 artwork_element = center_element; // for custom player artwork
5397 explosion_element = center_element; // for custom player artwork
5399 if (IS_PLAYER(ex, ey))
5401 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5403 artwork_element = stored_player[player_nr].artwork_element;
5405 if (level.use_explosion_element[player_nr])
5407 explosion_element = level.explosion_element[player_nr];
5408 artwork_element = explosion_element;
5412 if (mode == EX_TYPE_NORMAL ||
5413 mode == EX_TYPE_CENTER ||
5414 mode == EX_TYPE_CROSS)
5415 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5417 last_phase = element_info[explosion_element].explosion_delay + 1;
5419 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5421 int xx = x - ex + 1;
5422 int yy = y - ey + 1;
5425 if (!IN_LEV_FIELD(x, y) ||
5426 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5427 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5430 element = Feld[x][y];
5432 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5434 element = MovingOrBlocked2Element(x, y);
5436 if (!IS_EXPLOSION_PROOF(element))
5437 RemoveMovingField(x, y);
5440 // indestructible elements can only explode in center (but not flames)
5441 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5442 mode == EX_TYPE_BORDER)) ||
5443 element == EL_FLAMES)
5446 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5447 behaviour, for example when touching a yamyam that explodes to rocks
5448 with active deadly shield, a rock is created under the player !!! */
5449 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5451 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5452 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5453 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5455 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5458 if (IS_ACTIVE_BOMB(element))
5460 // re-activate things under the bomb like gate or penguin
5461 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5468 // save walkable background elements while explosion on same tile
5469 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5470 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5471 Back[x][y] = element;
5473 // ignite explodable elements reached by other explosion
5474 if (element == EL_EXPLOSION)
5475 element = Store2[x][y];
5477 if (AmoebaNr[x][y] &&
5478 (element == EL_AMOEBA_FULL ||
5479 element == EL_BD_AMOEBA ||
5480 element == EL_AMOEBA_GROWING))
5482 AmoebaCnt[AmoebaNr[x][y]]--;
5483 AmoebaCnt2[AmoebaNr[x][y]]--;
5488 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5490 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5492 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5494 if (PLAYERINFO(ex, ey)->use_murphy)
5495 Store[x][y] = EL_EMPTY;
5498 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5499 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5500 else if (ELEM_IS_PLAYER(center_element))
5501 Store[x][y] = EL_EMPTY;
5502 else if (center_element == EL_YAMYAM)
5503 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5504 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5505 Store[x][y] = element_info[center_element].content.e[xx][yy];
5507 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5508 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5509 // otherwise) -- FIX THIS !!!
5510 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5511 Store[x][y] = element_info[element].content.e[1][1];
5513 else if (!CAN_EXPLODE(element))
5514 Store[x][y] = element_info[element].content.e[1][1];
5517 Store[x][y] = EL_EMPTY;
5519 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5520 center_element == EL_AMOEBA_TO_DIAMOND)
5521 Store2[x][y] = element;
5523 Feld[x][y] = EL_EXPLOSION;
5524 GfxElement[x][y] = artwork_element;
5526 ExplodePhase[x][y] = 1;
5527 ExplodeDelay[x][y] = last_phase;
5532 if (center_element == EL_YAMYAM)
5533 game.yamyam_content_nr =
5534 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5546 GfxFrame[x][y] = 0; // restart explosion animation
5548 last_phase = ExplodeDelay[x][y];
5550 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5552 // this can happen if the player leaves an explosion just in time
5553 if (GfxElement[x][y] == EL_UNDEFINED)
5554 GfxElement[x][y] = EL_EMPTY;
5556 border_element = Store2[x][y];
5557 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5558 border_element = StorePlayer[x][y];
5560 if (phase == element_info[border_element].ignition_delay ||
5561 phase == last_phase)
5563 boolean border_explosion = FALSE;
5565 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5566 !PLAYER_EXPLOSION_PROTECTED(x, y))
5568 KillPlayerUnlessExplosionProtected(x, y);
5569 border_explosion = TRUE;
5571 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5573 Feld[x][y] = Store2[x][y];
5576 border_explosion = TRUE;
5578 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5580 AmoebeUmwandeln(x, y);
5582 border_explosion = TRUE;
5585 // if an element just explodes due to another explosion (chain-reaction),
5586 // do not immediately end the new explosion when it was the last frame of
5587 // the explosion (as it would be done in the following "if"-statement!)
5588 if (border_explosion && phase == last_phase)
5592 if (phase == last_phase)
5596 element = Feld[x][y] = Store[x][y];
5597 Store[x][y] = Store2[x][y] = 0;
5598 GfxElement[x][y] = EL_UNDEFINED;
5600 // player can escape from explosions and might therefore be still alive
5601 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5602 element <= EL_PLAYER_IS_EXPLODING_4)
5604 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5605 int explosion_element = EL_PLAYER_1 + player_nr;
5606 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5607 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5609 if (level.use_explosion_element[player_nr])
5610 explosion_element = level.explosion_element[player_nr];
5612 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5613 element_info[explosion_element].content.e[xx][yy]);
5616 // restore probably existing indestructible background element
5617 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5618 element = Feld[x][y] = Back[x][y];
5621 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5622 GfxDir[x][y] = MV_NONE;
5623 ChangeDelay[x][y] = 0;
5624 ChangePage[x][y] = -1;
5626 CustomValue[x][y] = 0;
5628 InitField_WithBug2(x, y, FALSE);
5630 TEST_DrawLevelField(x, y);
5632 TestIfElementTouchesCustomElement(x, y);
5634 if (GFX_CRUMBLED(element))
5635 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5637 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5638 StorePlayer[x][y] = 0;
5640 if (ELEM_IS_PLAYER(element))
5641 RelocatePlayer(x, y, element);
5643 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5645 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5646 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5649 TEST_DrawLevelFieldCrumbled(x, y);
5651 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5653 DrawLevelElement(x, y, Back[x][y]);
5654 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5656 else if (IS_WALKABLE_UNDER(Back[x][y]))
5658 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5659 DrawLevelElementThruMask(x, y, Back[x][y]);
5661 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5662 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5666 static void DynaExplode(int ex, int ey)
5669 int dynabomb_element = Feld[ex][ey];
5670 int dynabomb_size = 1;
5671 boolean dynabomb_xl = FALSE;
5672 struct PlayerInfo *player;
5673 static int xy[4][2] =
5681 if (IS_ACTIVE_BOMB(dynabomb_element))
5683 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5684 dynabomb_size = player->dynabomb_size;
5685 dynabomb_xl = player->dynabomb_xl;
5686 player->dynabombs_left++;
5689 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5691 for (i = 0; i < NUM_DIRECTIONS; i++)
5693 for (j = 1; j <= dynabomb_size; j++)
5695 int x = ex + j * xy[i][0];
5696 int y = ey + j * xy[i][1];
5699 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5702 element = Feld[x][y];
5704 // do not restart explosions of fields with active bombs
5705 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5708 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5710 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5711 !IS_DIGGABLE(element) && !dynabomb_xl)
5717 void Bang(int x, int y)
5719 int element = MovingOrBlocked2Element(x, y);
5720 int explosion_type = EX_TYPE_NORMAL;
5722 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5724 struct PlayerInfo *player = PLAYERINFO(x, y);
5726 element = Feld[x][y] = player->initial_element;
5728 if (level.use_explosion_element[player->index_nr])
5730 int explosion_element = level.explosion_element[player->index_nr];
5732 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5733 explosion_type = EX_TYPE_CROSS;
5734 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5735 explosion_type = EX_TYPE_CENTER;
5743 case EL_BD_BUTTERFLY:
5746 case EL_DARK_YAMYAM:
5750 RaiseScoreElement(element);
5753 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5754 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5755 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5756 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5757 case EL_DYNABOMB_INCREASE_NUMBER:
5758 case EL_DYNABOMB_INCREASE_SIZE:
5759 case EL_DYNABOMB_INCREASE_POWER:
5760 explosion_type = EX_TYPE_DYNA;
5763 case EL_DC_LANDMINE:
5764 explosion_type = EX_TYPE_CENTER;
5769 case EL_LAMP_ACTIVE:
5770 case EL_AMOEBA_TO_DIAMOND:
5771 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5772 explosion_type = EX_TYPE_CENTER;
5776 if (element_info[element].explosion_type == EXPLODES_CROSS)
5777 explosion_type = EX_TYPE_CROSS;
5778 else if (element_info[element].explosion_type == EXPLODES_1X1)
5779 explosion_type = EX_TYPE_CENTER;
5783 if (explosion_type == EX_TYPE_DYNA)
5786 Explode(x, y, EX_PHASE_START, explosion_type);
5788 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5791 static void SplashAcid(int x, int y)
5793 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5794 (!IN_LEV_FIELD(x - 1, y - 2) ||
5795 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5796 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5798 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5799 (!IN_LEV_FIELD(x + 1, y - 2) ||
5800 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5801 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5803 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5806 static void InitBeltMovement(void)
5808 static int belt_base_element[4] =
5810 EL_CONVEYOR_BELT_1_LEFT,
5811 EL_CONVEYOR_BELT_2_LEFT,
5812 EL_CONVEYOR_BELT_3_LEFT,
5813 EL_CONVEYOR_BELT_4_LEFT
5815 static int belt_base_active_element[4] =
5817 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5818 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5819 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5820 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5825 // set frame order for belt animation graphic according to belt direction
5826 for (i = 0; i < NUM_BELTS; i++)
5830 for (j = 0; j < NUM_BELT_PARTS; j++)
5832 int element = belt_base_active_element[belt_nr] + j;
5833 int graphic_1 = el2img(element);
5834 int graphic_2 = el2panelimg(element);
5836 if (game.belt_dir[i] == MV_LEFT)
5838 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5839 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5843 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5844 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5849 SCAN_PLAYFIELD(x, y)
5851 int element = Feld[x][y];
5853 for (i = 0; i < NUM_BELTS; i++)
5855 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
5857 int e_belt_nr = getBeltNrFromBeltElement(element);
5860 if (e_belt_nr == belt_nr)
5862 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
5864 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
5871 static void ToggleBeltSwitch(int x, int y)
5873 static int belt_base_element[4] =
5875 EL_CONVEYOR_BELT_1_LEFT,
5876 EL_CONVEYOR_BELT_2_LEFT,
5877 EL_CONVEYOR_BELT_3_LEFT,
5878 EL_CONVEYOR_BELT_4_LEFT
5880 static int belt_base_active_element[4] =
5882 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5883 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5884 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5885 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5887 static int belt_base_switch_element[4] =
5889 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
5890 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
5891 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
5892 EL_CONVEYOR_BELT_4_SWITCH_LEFT
5894 static int belt_move_dir[4] =
5902 int element = Feld[x][y];
5903 int belt_nr = getBeltNrFromBeltSwitchElement(element);
5904 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
5905 int belt_dir = belt_move_dir[belt_dir_nr];
5908 if (!IS_BELT_SWITCH(element))
5911 game.belt_dir_nr[belt_nr] = belt_dir_nr;
5912 game.belt_dir[belt_nr] = belt_dir;
5914 if (belt_dir_nr == 3)
5917 // set frame order for belt animation graphic according to belt direction
5918 for (i = 0; i < NUM_BELT_PARTS; i++)
5920 int element = belt_base_active_element[belt_nr] + i;
5921 int graphic_1 = el2img(element);
5922 int graphic_2 = el2panelimg(element);
5924 if (belt_dir == MV_LEFT)
5926 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5927 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5931 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5932 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5936 SCAN_PLAYFIELD(xx, yy)
5938 int element = Feld[xx][yy];
5940 if (IS_BELT_SWITCH(element))
5942 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
5944 if (e_belt_nr == belt_nr)
5946 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
5947 TEST_DrawLevelField(xx, yy);
5950 else if (IS_BELT(element) && belt_dir != MV_NONE)
5952 int e_belt_nr = getBeltNrFromBeltElement(element);
5954 if (e_belt_nr == belt_nr)
5956 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
5958 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
5959 TEST_DrawLevelField(xx, yy);
5962 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
5964 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
5966 if (e_belt_nr == belt_nr)
5968 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
5970 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
5971 TEST_DrawLevelField(xx, yy);
5977 static void ToggleSwitchgateSwitch(int x, int y)
5981 game.switchgate_pos = !game.switchgate_pos;
5983 SCAN_PLAYFIELD(xx, yy)
5985 int element = Feld[xx][yy];
5987 if (element == EL_SWITCHGATE_SWITCH_UP)
5989 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
5990 TEST_DrawLevelField(xx, yy);
5992 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
5994 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
5995 TEST_DrawLevelField(xx, yy);
5997 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
5999 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6000 TEST_DrawLevelField(xx, yy);
6002 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6004 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6005 TEST_DrawLevelField(xx, yy);
6007 else if (element == EL_SWITCHGATE_OPEN ||
6008 element == EL_SWITCHGATE_OPENING)
6010 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6012 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6014 else if (element == EL_SWITCHGATE_CLOSED ||
6015 element == EL_SWITCHGATE_CLOSING)
6017 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6019 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6024 static int getInvisibleActiveFromInvisibleElement(int element)
6026 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6027 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6028 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6032 static int getInvisibleFromInvisibleActiveElement(int element)
6034 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6035 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6036 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6040 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6044 SCAN_PLAYFIELD(x, y)
6046 int element = Feld[x][y];
6048 if (element == EL_LIGHT_SWITCH &&
6049 game.light_time_left > 0)
6051 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6052 TEST_DrawLevelField(x, y);
6054 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6055 game.light_time_left == 0)
6057 Feld[x][y] = EL_LIGHT_SWITCH;
6058 TEST_DrawLevelField(x, y);
6060 else if (element == EL_EMC_DRIPPER &&
6061 game.light_time_left > 0)
6063 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6064 TEST_DrawLevelField(x, y);
6066 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6067 game.light_time_left == 0)
6069 Feld[x][y] = EL_EMC_DRIPPER;
6070 TEST_DrawLevelField(x, y);
6072 else if (element == EL_INVISIBLE_STEELWALL ||
6073 element == EL_INVISIBLE_WALL ||
6074 element == EL_INVISIBLE_SAND)
6076 if (game.light_time_left > 0)
6077 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6079 TEST_DrawLevelField(x, y);
6081 // uncrumble neighbour fields, if needed
6082 if (element == EL_INVISIBLE_SAND)
6083 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6085 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6086 element == EL_INVISIBLE_WALL_ACTIVE ||
6087 element == EL_INVISIBLE_SAND_ACTIVE)
6089 if (game.light_time_left == 0)
6090 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6092 TEST_DrawLevelField(x, y);
6094 // re-crumble neighbour fields, if needed
6095 if (element == EL_INVISIBLE_SAND)
6096 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6101 static void RedrawAllInvisibleElementsForLenses(void)
6105 SCAN_PLAYFIELD(x, y)
6107 int element = Feld[x][y];
6109 if (element == EL_EMC_DRIPPER &&
6110 game.lenses_time_left > 0)
6112 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6113 TEST_DrawLevelField(x, y);
6115 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6116 game.lenses_time_left == 0)
6118 Feld[x][y] = EL_EMC_DRIPPER;
6119 TEST_DrawLevelField(x, y);
6121 else if (element == EL_INVISIBLE_STEELWALL ||
6122 element == EL_INVISIBLE_WALL ||
6123 element == EL_INVISIBLE_SAND)
6125 if (game.lenses_time_left > 0)
6126 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6128 TEST_DrawLevelField(x, y);
6130 // uncrumble neighbour fields, if needed
6131 if (element == EL_INVISIBLE_SAND)
6132 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6134 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6135 element == EL_INVISIBLE_WALL_ACTIVE ||
6136 element == EL_INVISIBLE_SAND_ACTIVE)
6138 if (game.lenses_time_left == 0)
6139 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6141 TEST_DrawLevelField(x, y);
6143 // re-crumble neighbour fields, if needed
6144 if (element == EL_INVISIBLE_SAND)
6145 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6150 static void RedrawAllInvisibleElementsForMagnifier(void)
6154 SCAN_PLAYFIELD(x, y)
6156 int element = Feld[x][y];
6158 if (element == EL_EMC_FAKE_GRASS &&
6159 game.magnify_time_left > 0)
6161 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6162 TEST_DrawLevelField(x, y);
6164 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6165 game.magnify_time_left == 0)
6167 Feld[x][y] = EL_EMC_FAKE_GRASS;
6168 TEST_DrawLevelField(x, y);
6170 else if (IS_GATE_GRAY(element) &&
6171 game.magnify_time_left > 0)
6173 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6174 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6175 IS_EM_GATE_GRAY(element) ?
6176 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6177 IS_EMC_GATE_GRAY(element) ?
6178 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6179 IS_DC_GATE_GRAY(element) ?
6180 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6182 TEST_DrawLevelField(x, y);
6184 else if (IS_GATE_GRAY_ACTIVE(element) &&
6185 game.magnify_time_left == 0)
6187 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6188 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6189 IS_EM_GATE_GRAY_ACTIVE(element) ?
6190 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6191 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6192 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6193 IS_DC_GATE_GRAY_ACTIVE(element) ?
6194 EL_DC_GATE_WHITE_GRAY :
6196 TEST_DrawLevelField(x, y);
6201 static void ToggleLightSwitch(int x, int y)
6203 int element = Feld[x][y];
6205 game.light_time_left =
6206 (element == EL_LIGHT_SWITCH ?
6207 level.time_light * FRAMES_PER_SECOND : 0);
6209 RedrawAllLightSwitchesAndInvisibleElements();
6212 static void ActivateTimegateSwitch(int x, int y)
6216 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6218 SCAN_PLAYFIELD(xx, yy)
6220 int element = Feld[xx][yy];
6222 if (element == EL_TIMEGATE_CLOSED ||
6223 element == EL_TIMEGATE_CLOSING)
6225 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6226 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6230 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6232 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6233 TEST_DrawLevelField(xx, yy);
6239 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6240 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6243 static void Impact(int x, int y)
6245 boolean last_line = (y == lev_fieldy - 1);
6246 boolean object_hit = FALSE;
6247 boolean impact = (last_line || object_hit);
6248 int element = Feld[x][y];
6249 int smashed = EL_STEELWALL;
6251 if (!last_line) // check if element below was hit
6253 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6256 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6257 MovDir[x][y + 1] != MV_DOWN ||
6258 MovPos[x][y + 1] <= TILEY / 2));
6260 // do not smash moving elements that left the smashed field in time
6261 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6262 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6265 #if USE_QUICKSAND_IMPACT_BUGFIX
6266 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6268 RemoveMovingField(x, y + 1);
6269 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6270 Feld[x][y + 2] = EL_ROCK;
6271 TEST_DrawLevelField(x, y + 2);
6276 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6278 RemoveMovingField(x, y + 1);
6279 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6280 Feld[x][y + 2] = EL_ROCK;
6281 TEST_DrawLevelField(x, y + 2);
6288 smashed = MovingOrBlocked2Element(x, y + 1);
6290 impact = (last_line || object_hit);
6293 if (!last_line && smashed == EL_ACID) // element falls into acid
6295 SplashAcid(x, y + 1);
6299 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6300 // only reset graphic animation if graphic really changes after impact
6302 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6304 ResetGfxAnimation(x, y);
6305 TEST_DrawLevelField(x, y);
6308 if (impact && CAN_EXPLODE_IMPACT(element))
6313 else if (impact && element == EL_PEARL &&
6314 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6316 ResetGfxAnimation(x, y);
6318 Feld[x][y] = EL_PEARL_BREAKING;
6319 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6322 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6324 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6329 if (impact && element == EL_AMOEBA_DROP)
6331 if (object_hit && IS_PLAYER(x, y + 1))
6332 KillPlayerUnlessEnemyProtected(x, y + 1);
6333 else if (object_hit && smashed == EL_PENGUIN)
6337 Feld[x][y] = EL_AMOEBA_GROWING;
6338 Store[x][y] = EL_AMOEBA_WET;
6340 ResetRandomAnimationValue(x, y);
6345 if (object_hit) // check which object was hit
6347 if ((CAN_PASS_MAGIC_WALL(element) &&
6348 (smashed == EL_MAGIC_WALL ||
6349 smashed == EL_BD_MAGIC_WALL)) ||
6350 (CAN_PASS_DC_MAGIC_WALL(element) &&
6351 smashed == EL_DC_MAGIC_WALL))
6354 int activated_magic_wall =
6355 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6356 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6357 EL_DC_MAGIC_WALL_ACTIVE);
6359 // activate magic wall / mill
6360 SCAN_PLAYFIELD(xx, yy)
6362 if (Feld[xx][yy] == smashed)
6363 Feld[xx][yy] = activated_magic_wall;
6366 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6367 game.magic_wall_active = TRUE;
6369 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6370 SND_MAGIC_WALL_ACTIVATING :
6371 smashed == EL_BD_MAGIC_WALL ?
6372 SND_BD_MAGIC_WALL_ACTIVATING :
6373 SND_DC_MAGIC_WALL_ACTIVATING));
6376 if (IS_PLAYER(x, y + 1))
6378 if (CAN_SMASH_PLAYER(element))
6380 KillPlayerUnlessEnemyProtected(x, y + 1);
6384 else if (smashed == EL_PENGUIN)
6386 if (CAN_SMASH_PLAYER(element))
6392 else if (element == EL_BD_DIAMOND)
6394 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6400 else if (((element == EL_SP_INFOTRON ||
6401 element == EL_SP_ZONK) &&
6402 (smashed == EL_SP_SNIKSNAK ||
6403 smashed == EL_SP_ELECTRON ||
6404 smashed == EL_SP_DISK_ORANGE)) ||
6405 (element == EL_SP_INFOTRON &&
6406 smashed == EL_SP_DISK_YELLOW))
6411 else if (CAN_SMASH_EVERYTHING(element))
6413 if (IS_CLASSIC_ENEMY(smashed) ||
6414 CAN_EXPLODE_SMASHED(smashed))
6419 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6421 if (smashed == EL_LAMP ||
6422 smashed == EL_LAMP_ACTIVE)
6427 else if (smashed == EL_NUT)
6429 Feld[x][y + 1] = EL_NUT_BREAKING;
6430 PlayLevelSound(x, y, SND_NUT_BREAKING);
6431 RaiseScoreElement(EL_NUT);
6434 else if (smashed == EL_PEARL)
6436 ResetGfxAnimation(x, y);
6438 Feld[x][y + 1] = EL_PEARL_BREAKING;
6439 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6442 else if (smashed == EL_DIAMOND)
6444 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6445 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6448 else if (IS_BELT_SWITCH(smashed))
6450 ToggleBeltSwitch(x, y + 1);
6452 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6453 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6454 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6455 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6457 ToggleSwitchgateSwitch(x, y + 1);
6459 else if (smashed == EL_LIGHT_SWITCH ||
6460 smashed == EL_LIGHT_SWITCH_ACTIVE)
6462 ToggleLightSwitch(x, y + 1);
6466 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6468 CheckElementChangeBySide(x, y + 1, smashed, element,
6469 CE_SWITCHED, CH_SIDE_TOP);
6470 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6476 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6481 // play sound of magic wall / mill
6483 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6484 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6485 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6487 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6488 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6489 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6490 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6491 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6492 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6497 // play sound of object that hits the ground
6498 if (last_line || object_hit)
6499 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6502 static void TurnRoundExt(int x, int y)
6514 { 0, 0 }, { 0, 0 }, { 0, 0 },
6519 int left, right, back;
6523 { MV_DOWN, MV_UP, MV_RIGHT },
6524 { MV_UP, MV_DOWN, MV_LEFT },
6526 { MV_LEFT, MV_RIGHT, MV_DOWN },
6530 { MV_RIGHT, MV_LEFT, MV_UP }
6533 int element = Feld[x][y];
6534 int move_pattern = element_info[element].move_pattern;
6536 int old_move_dir = MovDir[x][y];
6537 int left_dir = turn[old_move_dir].left;
6538 int right_dir = turn[old_move_dir].right;
6539 int back_dir = turn[old_move_dir].back;
6541 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6542 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6543 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6544 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6546 int left_x = x + left_dx, left_y = y + left_dy;
6547 int right_x = x + right_dx, right_y = y + right_dy;
6548 int move_x = x + move_dx, move_y = y + move_dy;
6552 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6554 TestIfBadThingTouchesOtherBadThing(x, y);
6556 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6557 MovDir[x][y] = right_dir;
6558 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6559 MovDir[x][y] = left_dir;
6561 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6563 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6566 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6568 TestIfBadThingTouchesOtherBadThing(x, y);
6570 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6571 MovDir[x][y] = left_dir;
6572 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6573 MovDir[x][y] = right_dir;
6575 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6577 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6580 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6582 TestIfBadThingTouchesOtherBadThing(x, y);
6584 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6585 MovDir[x][y] = left_dir;
6586 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6587 MovDir[x][y] = right_dir;
6589 if (MovDir[x][y] != old_move_dir)
6592 else if (element == EL_YAMYAM)
6594 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6595 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6597 if (can_turn_left && can_turn_right)
6598 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6599 else if (can_turn_left)
6600 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6601 else if (can_turn_right)
6602 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6604 MovDir[x][y] = back_dir;
6606 MovDelay[x][y] = 16 + 16 * RND(3);
6608 else if (element == EL_DARK_YAMYAM)
6610 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6612 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6615 if (can_turn_left && can_turn_right)
6616 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6617 else if (can_turn_left)
6618 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6619 else if (can_turn_right)
6620 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6622 MovDir[x][y] = back_dir;
6624 MovDelay[x][y] = 16 + 16 * RND(3);
6626 else if (element == EL_PACMAN)
6628 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6629 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6631 if (can_turn_left && can_turn_right)
6632 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6633 else if (can_turn_left)
6634 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6635 else if (can_turn_right)
6636 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6638 MovDir[x][y] = back_dir;
6640 MovDelay[x][y] = 6 + RND(40);
6642 else if (element == EL_PIG)
6644 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6645 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6646 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6647 boolean should_turn_left, should_turn_right, should_move_on;
6649 int rnd = RND(rnd_value);
6651 should_turn_left = (can_turn_left &&
6653 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6654 y + back_dy + left_dy)));
6655 should_turn_right = (can_turn_right &&
6657 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6658 y + back_dy + right_dy)));
6659 should_move_on = (can_move_on &&
6662 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6663 y + move_dy + left_dy) ||
6664 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6665 y + move_dy + right_dy)));
6667 if (should_turn_left || should_turn_right || should_move_on)
6669 if (should_turn_left && should_turn_right && should_move_on)
6670 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6671 rnd < 2 * rnd_value / 3 ? right_dir :
6673 else if (should_turn_left && should_turn_right)
6674 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6675 else if (should_turn_left && should_move_on)
6676 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6677 else if (should_turn_right && should_move_on)
6678 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6679 else if (should_turn_left)
6680 MovDir[x][y] = left_dir;
6681 else if (should_turn_right)
6682 MovDir[x][y] = right_dir;
6683 else if (should_move_on)
6684 MovDir[x][y] = old_move_dir;
6686 else if (can_move_on && rnd > rnd_value / 8)
6687 MovDir[x][y] = old_move_dir;
6688 else if (can_turn_left && can_turn_right)
6689 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6690 else if (can_turn_left && rnd > rnd_value / 8)
6691 MovDir[x][y] = left_dir;
6692 else if (can_turn_right && rnd > rnd_value/8)
6693 MovDir[x][y] = right_dir;
6695 MovDir[x][y] = back_dir;
6697 xx = x + move_xy[MovDir[x][y]].dx;
6698 yy = y + move_xy[MovDir[x][y]].dy;
6700 if (!IN_LEV_FIELD(xx, yy) ||
6701 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6702 MovDir[x][y] = old_move_dir;
6706 else if (element == EL_DRAGON)
6708 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6709 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6710 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6712 int rnd = RND(rnd_value);
6714 if (can_move_on && rnd > rnd_value / 8)
6715 MovDir[x][y] = old_move_dir;
6716 else if (can_turn_left && can_turn_right)
6717 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6718 else if (can_turn_left && rnd > rnd_value / 8)
6719 MovDir[x][y] = left_dir;
6720 else if (can_turn_right && rnd > rnd_value / 8)
6721 MovDir[x][y] = right_dir;
6723 MovDir[x][y] = back_dir;
6725 xx = x + move_xy[MovDir[x][y]].dx;
6726 yy = y + move_xy[MovDir[x][y]].dy;
6728 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6729 MovDir[x][y] = old_move_dir;
6733 else if (element == EL_MOLE)
6735 boolean can_move_on =
6736 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6737 IS_AMOEBOID(Feld[move_x][move_y]) ||
6738 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6741 boolean can_turn_left =
6742 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6743 IS_AMOEBOID(Feld[left_x][left_y])));
6745 boolean can_turn_right =
6746 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6747 IS_AMOEBOID(Feld[right_x][right_y])));
6749 if (can_turn_left && can_turn_right)
6750 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6751 else if (can_turn_left)
6752 MovDir[x][y] = left_dir;
6754 MovDir[x][y] = right_dir;
6757 if (MovDir[x][y] != old_move_dir)
6760 else if (element == EL_BALLOON)
6762 MovDir[x][y] = game.wind_direction;
6765 else if (element == EL_SPRING)
6767 if (MovDir[x][y] & MV_HORIZONTAL)
6769 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6770 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6772 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6773 ResetGfxAnimation(move_x, move_y);
6774 TEST_DrawLevelField(move_x, move_y);
6776 MovDir[x][y] = back_dir;
6778 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6779 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6780 MovDir[x][y] = MV_NONE;
6785 else if (element == EL_ROBOT ||
6786 element == EL_SATELLITE ||
6787 element == EL_PENGUIN ||
6788 element == EL_EMC_ANDROID)
6790 int attr_x = -1, attr_y = -1;
6801 for (i = 0; i < MAX_PLAYERS; i++)
6803 struct PlayerInfo *player = &stored_player[i];
6804 int jx = player->jx, jy = player->jy;
6806 if (!player->active)
6810 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6818 if (element == EL_ROBOT && ZX >= 0 && ZY >= 0 &&
6819 (Feld[ZX][ZY] == EL_ROBOT_WHEEL_ACTIVE ||
6820 game.engine_version < VERSION_IDENT(3,1,0,0)))
6826 if (element == EL_PENGUIN)
6829 static int xy[4][2] =
6837 for (i = 0; i < NUM_DIRECTIONS; i++)
6839 int ex = x + xy[i][0];
6840 int ey = y + xy[i][1];
6842 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
6843 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
6844 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
6845 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
6854 MovDir[x][y] = MV_NONE;
6856 MovDir[x][y] |= (AllPlayersGone ? MV_RIGHT : MV_LEFT);
6857 else if (attr_x > x)
6858 MovDir[x][y] |= (AllPlayersGone ? MV_LEFT : MV_RIGHT);
6860 MovDir[x][y] |= (AllPlayersGone ? MV_DOWN : MV_UP);
6861 else if (attr_y > y)
6862 MovDir[x][y] |= (AllPlayersGone ? MV_UP : MV_DOWN);
6864 if (element == EL_ROBOT)
6868 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6869 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
6870 Moving2Blocked(x, y, &newx, &newy);
6872 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
6873 MovDelay[x][y] = 8 + 8 * !RND(3);
6875 MovDelay[x][y] = 16;
6877 else if (element == EL_PENGUIN)
6883 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6885 boolean first_horiz = RND(2);
6886 int new_move_dir = MovDir[x][y];
6889 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6890 Moving2Blocked(x, y, &newx, &newy);
6892 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6896 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6897 Moving2Blocked(x, y, &newx, &newy);
6899 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6902 MovDir[x][y] = old_move_dir;
6906 else if (element == EL_SATELLITE)
6912 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6914 boolean first_horiz = RND(2);
6915 int new_move_dir = MovDir[x][y];
6918 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6919 Moving2Blocked(x, y, &newx, &newy);
6921 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6925 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6926 Moving2Blocked(x, y, &newx, &newy);
6928 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6931 MovDir[x][y] = old_move_dir;
6935 else if (element == EL_EMC_ANDROID)
6937 static int check_pos[16] =
6939 -1, // 0 => (invalid)
6942 -1, // 3 => (invalid)
6944 0, // 5 => MV_LEFT | MV_UP
6945 2, // 6 => MV_RIGHT | MV_UP
6946 -1, // 7 => (invalid)
6948 6, // 9 => MV_LEFT | MV_DOWN
6949 4, // 10 => MV_RIGHT | MV_DOWN
6950 -1, // 11 => (invalid)
6951 -1, // 12 => (invalid)
6952 -1, // 13 => (invalid)
6953 -1, // 14 => (invalid)
6954 -1, // 15 => (invalid)
6962 { -1, -1, MV_LEFT | MV_UP },
6964 { +1, -1, MV_RIGHT | MV_UP },
6965 { +1, 0, MV_RIGHT },
6966 { +1, +1, MV_RIGHT | MV_DOWN },
6968 { -1, +1, MV_LEFT | MV_DOWN },
6971 int start_pos, check_order;
6972 boolean can_clone = FALSE;
6975 // check if there is any free field around current position
6976 for (i = 0; i < 8; i++)
6978 int newx = x + check_xy[i].dx;
6979 int newy = y + check_xy[i].dy;
6981 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
6989 if (can_clone) // randomly find an element to clone
6993 start_pos = check_pos[RND(8)];
6994 check_order = (RND(2) ? -1 : +1);
6996 for (i = 0; i < 8; i++)
6998 int pos_raw = start_pos + i * check_order;
6999 int pos = (pos_raw + 8) % 8;
7000 int newx = x + check_xy[pos].dx;
7001 int newy = y + check_xy[pos].dy;
7003 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7005 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7006 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7008 Store[x][y] = Feld[newx][newy];
7017 if (can_clone) // randomly find a direction to move
7021 start_pos = check_pos[RND(8)];
7022 check_order = (RND(2) ? -1 : +1);
7024 for (i = 0; i < 8; i++)
7026 int pos_raw = start_pos + i * check_order;
7027 int pos = (pos_raw + 8) % 8;
7028 int newx = x + check_xy[pos].dx;
7029 int newy = y + check_xy[pos].dy;
7030 int new_move_dir = check_xy[pos].dir;
7032 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7034 MovDir[x][y] = new_move_dir;
7035 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7044 if (can_clone) // cloning and moving successful
7047 // cannot clone -- try to move towards player
7049 start_pos = check_pos[MovDir[x][y] & 0x0f];
7050 check_order = (RND(2) ? -1 : +1);
7052 for (i = 0; i < 3; i++)
7054 // first check start_pos, then previous/next or (next/previous) pos
7055 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7056 int pos = (pos_raw + 8) % 8;
7057 int newx = x + check_xy[pos].dx;
7058 int newy = y + check_xy[pos].dy;
7059 int new_move_dir = check_xy[pos].dir;
7061 if (IS_PLAYER(newx, newy))
7064 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7066 MovDir[x][y] = new_move_dir;
7067 MovDelay[x][y] = level.android_move_time * 8 + 1;
7074 else if (move_pattern == MV_TURNING_LEFT ||
7075 move_pattern == MV_TURNING_RIGHT ||
7076 move_pattern == MV_TURNING_LEFT_RIGHT ||
7077 move_pattern == MV_TURNING_RIGHT_LEFT ||
7078 move_pattern == MV_TURNING_RANDOM ||
7079 move_pattern == MV_ALL_DIRECTIONS)
7081 boolean can_turn_left =
7082 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7083 boolean can_turn_right =
7084 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7086 if (element_info[element].move_stepsize == 0) // "not moving"
7089 if (move_pattern == MV_TURNING_LEFT)
7090 MovDir[x][y] = left_dir;
7091 else if (move_pattern == MV_TURNING_RIGHT)
7092 MovDir[x][y] = right_dir;
7093 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7094 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7095 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7096 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7097 else if (move_pattern == MV_TURNING_RANDOM)
7098 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7099 can_turn_right && !can_turn_left ? right_dir :
7100 RND(2) ? left_dir : right_dir);
7101 else if (can_turn_left && can_turn_right)
7102 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7103 else if (can_turn_left)
7104 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7105 else if (can_turn_right)
7106 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7108 MovDir[x][y] = back_dir;
7110 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7112 else if (move_pattern == MV_HORIZONTAL ||
7113 move_pattern == MV_VERTICAL)
7115 if (move_pattern & old_move_dir)
7116 MovDir[x][y] = back_dir;
7117 else if (move_pattern == MV_HORIZONTAL)
7118 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7119 else if (move_pattern == MV_VERTICAL)
7120 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7122 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7124 else if (move_pattern & MV_ANY_DIRECTION)
7126 MovDir[x][y] = move_pattern;
7127 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7129 else if (move_pattern & MV_WIND_DIRECTION)
7131 MovDir[x][y] = game.wind_direction;
7132 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7134 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7136 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7137 MovDir[x][y] = left_dir;
7138 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7139 MovDir[x][y] = right_dir;
7141 if (MovDir[x][y] != old_move_dir)
7142 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7144 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7146 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7147 MovDir[x][y] = right_dir;
7148 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7149 MovDir[x][y] = left_dir;
7151 if (MovDir[x][y] != old_move_dir)
7152 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7154 else if (move_pattern == MV_TOWARDS_PLAYER ||
7155 move_pattern == MV_AWAY_FROM_PLAYER)
7157 int attr_x = -1, attr_y = -1;
7159 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7170 for (i = 0; i < MAX_PLAYERS; i++)
7172 struct PlayerInfo *player = &stored_player[i];
7173 int jx = player->jx, jy = player->jy;
7175 if (!player->active)
7179 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7187 MovDir[x][y] = MV_NONE;
7189 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7190 else if (attr_x > x)
7191 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7193 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7194 else if (attr_y > y)
7195 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7197 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7199 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7201 boolean first_horiz = RND(2);
7202 int new_move_dir = MovDir[x][y];
7204 if (element_info[element].move_stepsize == 0) // "not moving"
7206 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7207 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7213 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7214 Moving2Blocked(x, y, &newx, &newy);
7216 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7220 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7221 Moving2Blocked(x, y, &newx, &newy);
7223 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7226 MovDir[x][y] = old_move_dir;
7229 else if (move_pattern == MV_WHEN_PUSHED ||
7230 move_pattern == MV_WHEN_DROPPED)
7232 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7233 MovDir[x][y] = MV_NONE;
7237 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7239 static int test_xy[7][2] =
7249 static int test_dir[7] =
7259 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7260 int move_preference = -1000000; // start with very low preference
7261 int new_move_dir = MV_NONE;
7262 int start_test = RND(4);
7265 for (i = 0; i < NUM_DIRECTIONS; i++)
7267 int move_dir = test_dir[start_test + i];
7268 int move_dir_preference;
7270 xx = x + test_xy[start_test + i][0];
7271 yy = y + test_xy[start_test + i][1];
7273 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7274 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7276 new_move_dir = move_dir;
7281 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7284 move_dir_preference = -1 * RunnerVisit[xx][yy];
7285 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7286 move_dir_preference = PlayerVisit[xx][yy];
7288 if (move_dir_preference > move_preference)
7290 // prefer field that has not been visited for the longest time
7291 move_preference = move_dir_preference;
7292 new_move_dir = move_dir;
7294 else if (move_dir_preference == move_preference &&
7295 move_dir == old_move_dir)
7297 // prefer last direction when all directions are preferred equally
7298 move_preference = move_dir_preference;
7299 new_move_dir = move_dir;
7303 MovDir[x][y] = new_move_dir;
7304 if (old_move_dir != new_move_dir)
7305 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7309 static void TurnRound(int x, int y)
7311 int direction = MovDir[x][y];
7315 GfxDir[x][y] = MovDir[x][y];
7317 if (direction != MovDir[x][y])
7321 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7323 ResetGfxFrame(x, y);
7326 static boolean JustBeingPushed(int x, int y)
7330 for (i = 0; i < MAX_PLAYERS; i++)
7332 struct PlayerInfo *player = &stored_player[i];
7334 if (player->active && player->is_pushing && player->MovPos)
7336 int next_jx = player->jx + (player->jx - player->last_jx);
7337 int next_jy = player->jy + (player->jy - player->last_jy);
7339 if (x == next_jx && y == next_jy)
7347 static void StartMoving(int x, int y)
7349 boolean started_moving = FALSE; // some elements can fall _and_ move
7350 int element = Feld[x][y];
7355 if (MovDelay[x][y] == 0)
7356 GfxAction[x][y] = ACTION_DEFAULT;
7358 if (CAN_FALL(element) && y < lev_fieldy - 1)
7360 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7361 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7362 if (JustBeingPushed(x, y))
7365 if (element == EL_QUICKSAND_FULL)
7367 if (IS_FREE(x, y + 1))
7369 InitMovingField(x, y, MV_DOWN);
7370 started_moving = TRUE;
7372 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7373 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7374 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7375 Store[x][y] = EL_ROCK;
7377 Store[x][y] = EL_ROCK;
7380 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7382 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7384 if (!MovDelay[x][y])
7386 MovDelay[x][y] = TILEY + 1;
7388 ResetGfxAnimation(x, y);
7389 ResetGfxAnimation(x, y + 1);
7394 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7395 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7402 Feld[x][y] = EL_QUICKSAND_EMPTY;
7403 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7404 Store[x][y + 1] = Store[x][y];
7407 PlayLevelSoundAction(x, y, ACTION_FILLING);
7409 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7411 if (!MovDelay[x][y])
7413 MovDelay[x][y] = TILEY + 1;
7415 ResetGfxAnimation(x, y);
7416 ResetGfxAnimation(x, y + 1);
7421 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7422 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7429 Feld[x][y] = EL_QUICKSAND_EMPTY;
7430 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7431 Store[x][y + 1] = Store[x][y];
7434 PlayLevelSoundAction(x, y, ACTION_FILLING);
7437 else if (element == EL_QUICKSAND_FAST_FULL)
7439 if (IS_FREE(x, y + 1))
7441 InitMovingField(x, y, MV_DOWN);
7442 started_moving = TRUE;
7444 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7445 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7446 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7447 Store[x][y] = EL_ROCK;
7449 Store[x][y] = EL_ROCK;
7452 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7454 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7456 if (!MovDelay[x][y])
7458 MovDelay[x][y] = TILEY + 1;
7460 ResetGfxAnimation(x, y);
7461 ResetGfxAnimation(x, y + 1);
7466 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7467 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7474 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7475 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7476 Store[x][y + 1] = Store[x][y];
7479 PlayLevelSoundAction(x, y, ACTION_FILLING);
7481 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7483 if (!MovDelay[x][y])
7485 MovDelay[x][y] = TILEY + 1;
7487 ResetGfxAnimation(x, y);
7488 ResetGfxAnimation(x, y + 1);
7493 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7494 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7501 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7502 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7503 Store[x][y + 1] = Store[x][y];
7506 PlayLevelSoundAction(x, y, ACTION_FILLING);
7509 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7510 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7512 InitMovingField(x, y, MV_DOWN);
7513 started_moving = TRUE;
7515 Feld[x][y] = EL_QUICKSAND_FILLING;
7516 Store[x][y] = element;
7518 PlayLevelSoundAction(x, y, ACTION_FILLING);
7520 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7521 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7523 InitMovingField(x, y, MV_DOWN);
7524 started_moving = TRUE;
7526 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7527 Store[x][y] = element;
7529 PlayLevelSoundAction(x, y, ACTION_FILLING);
7531 else if (element == EL_MAGIC_WALL_FULL)
7533 if (IS_FREE(x, y + 1))
7535 InitMovingField(x, y, MV_DOWN);
7536 started_moving = TRUE;
7538 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7539 Store[x][y] = EL_CHANGED(Store[x][y]);
7541 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7543 if (!MovDelay[x][y])
7544 MovDelay[x][y] = TILEY / 4 + 1;
7553 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7554 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7555 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7559 else if (element == EL_BD_MAGIC_WALL_FULL)
7561 if (IS_FREE(x, y + 1))
7563 InitMovingField(x, y, MV_DOWN);
7564 started_moving = TRUE;
7566 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7567 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7569 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7571 if (!MovDelay[x][y])
7572 MovDelay[x][y] = TILEY / 4 + 1;
7581 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7582 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7583 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7587 else if (element == EL_DC_MAGIC_WALL_FULL)
7589 if (IS_FREE(x, y + 1))
7591 InitMovingField(x, y, MV_DOWN);
7592 started_moving = TRUE;
7594 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7595 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7597 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7599 if (!MovDelay[x][y])
7600 MovDelay[x][y] = TILEY / 4 + 1;
7609 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7610 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7611 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7615 else if ((CAN_PASS_MAGIC_WALL(element) &&
7616 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7617 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7618 (CAN_PASS_DC_MAGIC_WALL(element) &&
7619 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7622 InitMovingField(x, y, MV_DOWN);
7623 started_moving = TRUE;
7626 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7627 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7628 EL_DC_MAGIC_WALL_FILLING);
7629 Store[x][y] = element;
7631 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7633 SplashAcid(x, y + 1);
7635 InitMovingField(x, y, MV_DOWN);
7636 started_moving = TRUE;
7638 Store[x][y] = EL_ACID;
7641 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7642 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7643 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7644 CAN_FALL(element) && WasJustFalling[x][y] &&
7645 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7647 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7648 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7649 (Feld[x][y + 1] == EL_BLOCKED)))
7651 /* this is needed for a special case not covered by calling "Impact()"
7652 from "ContinueMoving()": if an element moves to a tile directly below
7653 another element which was just falling on that tile (which was empty
7654 in the previous frame), the falling element above would just stop
7655 instead of smashing the element below (in previous version, the above
7656 element was just checked for "moving" instead of "falling", resulting
7657 in incorrect smashes caused by horizontal movement of the above
7658 element; also, the case of the player being the element to smash was
7659 simply not covered here... :-/ ) */
7661 CheckCollision[x][y] = 0;
7662 CheckImpact[x][y] = 0;
7666 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7668 if (MovDir[x][y] == MV_NONE)
7670 InitMovingField(x, y, MV_DOWN);
7671 started_moving = TRUE;
7674 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7676 if (WasJustFalling[x][y]) // prevent animation from being restarted
7677 MovDir[x][y] = MV_DOWN;
7679 InitMovingField(x, y, MV_DOWN);
7680 started_moving = TRUE;
7682 else if (element == EL_AMOEBA_DROP)
7684 Feld[x][y] = EL_AMOEBA_GROWING;
7685 Store[x][y] = EL_AMOEBA_WET;
7687 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7688 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7689 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7690 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7692 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7693 (IS_FREE(x - 1, y + 1) ||
7694 Feld[x - 1][y + 1] == EL_ACID));
7695 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7696 (IS_FREE(x + 1, y + 1) ||
7697 Feld[x + 1][y + 1] == EL_ACID));
7698 boolean can_fall_any = (can_fall_left || can_fall_right);
7699 boolean can_fall_both = (can_fall_left && can_fall_right);
7700 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7702 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7704 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7705 can_fall_right = FALSE;
7706 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7707 can_fall_left = FALSE;
7708 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7709 can_fall_right = FALSE;
7710 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7711 can_fall_left = FALSE;
7713 can_fall_any = (can_fall_left || can_fall_right);
7714 can_fall_both = FALSE;
7719 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7720 can_fall_right = FALSE; // slip down on left side
7722 can_fall_left = !(can_fall_right = RND(2));
7724 can_fall_both = FALSE;
7729 // if not determined otherwise, prefer left side for slipping down
7730 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7731 started_moving = TRUE;
7734 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7736 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7737 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7738 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7739 int belt_dir = game.belt_dir[belt_nr];
7741 if ((belt_dir == MV_LEFT && left_is_free) ||
7742 (belt_dir == MV_RIGHT && right_is_free))
7744 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7746 InitMovingField(x, y, belt_dir);
7747 started_moving = TRUE;
7749 Pushed[x][y] = TRUE;
7750 Pushed[nextx][y] = TRUE;
7752 GfxAction[x][y] = ACTION_DEFAULT;
7756 MovDir[x][y] = 0; // if element was moving, stop it
7761 // not "else if" because of elements that can fall and move (EL_SPRING)
7762 if (CAN_MOVE(element) && !started_moving)
7764 int move_pattern = element_info[element].move_pattern;
7767 Moving2Blocked(x, y, &newx, &newy);
7769 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7772 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7773 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7775 WasJustMoving[x][y] = 0;
7776 CheckCollision[x][y] = 0;
7778 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7780 if (Feld[x][y] != element) // element has changed
7784 if (!MovDelay[x][y]) // start new movement phase
7786 // all objects that can change their move direction after each step
7787 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7789 if (element != EL_YAMYAM &&
7790 element != EL_DARK_YAMYAM &&
7791 element != EL_PACMAN &&
7792 !(move_pattern & MV_ANY_DIRECTION) &&
7793 move_pattern != MV_TURNING_LEFT &&
7794 move_pattern != MV_TURNING_RIGHT &&
7795 move_pattern != MV_TURNING_LEFT_RIGHT &&
7796 move_pattern != MV_TURNING_RIGHT_LEFT &&
7797 move_pattern != MV_TURNING_RANDOM)
7801 if (MovDelay[x][y] && (element == EL_BUG ||
7802 element == EL_SPACESHIP ||
7803 element == EL_SP_SNIKSNAK ||
7804 element == EL_SP_ELECTRON ||
7805 element == EL_MOLE))
7806 TEST_DrawLevelField(x, y);
7810 if (MovDelay[x][y]) // wait some time before next movement
7814 if (element == EL_ROBOT ||
7815 element == EL_YAMYAM ||
7816 element == EL_DARK_YAMYAM)
7818 DrawLevelElementAnimationIfNeeded(x, y, element);
7819 PlayLevelSoundAction(x, y, ACTION_WAITING);
7821 else if (element == EL_SP_ELECTRON)
7822 DrawLevelElementAnimationIfNeeded(x, y, element);
7823 else if (element == EL_DRAGON)
7826 int dir = MovDir[x][y];
7827 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
7828 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
7829 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
7830 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
7831 dir == MV_UP ? IMG_FLAMES_1_UP :
7832 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
7833 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
7835 GfxAction[x][y] = ACTION_ATTACKING;
7837 if (IS_PLAYER(x, y))
7838 DrawPlayerField(x, y);
7840 TEST_DrawLevelField(x, y);
7842 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
7844 for (i = 1; i <= 3; i++)
7846 int xx = x + i * dx;
7847 int yy = y + i * dy;
7848 int sx = SCREENX(xx);
7849 int sy = SCREENY(yy);
7850 int flame_graphic = graphic + (i - 1);
7852 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
7857 int flamed = MovingOrBlocked2Element(xx, yy);
7859 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
7862 RemoveMovingField(xx, yy);
7864 ChangeDelay[xx][yy] = 0;
7866 Feld[xx][yy] = EL_FLAMES;
7868 if (IN_SCR_FIELD(sx, sy))
7870 TEST_DrawLevelFieldCrumbled(xx, yy);
7871 DrawGraphic(sx, sy, flame_graphic, frame);
7876 if (Feld[xx][yy] == EL_FLAMES)
7877 Feld[xx][yy] = EL_EMPTY;
7878 TEST_DrawLevelField(xx, yy);
7883 if (MovDelay[x][y]) // element still has to wait some time
7885 PlayLevelSoundAction(x, y, ACTION_WAITING);
7891 // now make next step
7893 Moving2Blocked(x, y, &newx, &newy); // get next screen position
7895 if (DONT_COLLIDE_WITH(element) &&
7896 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
7897 !PLAYER_ENEMY_PROTECTED(newx, newy))
7899 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
7904 else if (CAN_MOVE_INTO_ACID(element) &&
7905 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
7906 !IS_MV_DIAGONAL(MovDir[x][y]) &&
7907 (MovDir[x][y] == MV_DOWN ||
7908 game.engine_version >= VERSION_IDENT(3,1,0,0)))
7910 SplashAcid(newx, newy);
7911 Store[x][y] = EL_ACID;
7913 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
7915 if (Feld[newx][newy] == EL_EXIT_OPEN ||
7916 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
7917 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
7918 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
7921 TEST_DrawLevelField(x, y);
7923 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
7924 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
7925 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
7927 local_player->friends_still_needed--;
7928 if (!local_player->friends_still_needed &&
7929 !local_player->GameOver && AllPlayersGone)
7930 PlayerWins(local_player);
7934 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
7936 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
7937 TEST_DrawLevelField(newx, newy);
7939 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
7941 else if (!IS_FREE(newx, newy))
7943 GfxAction[x][y] = ACTION_WAITING;
7945 if (IS_PLAYER(x, y))
7946 DrawPlayerField(x, y);
7948 TEST_DrawLevelField(x, y);
7953 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
7955 if (IS_FOOD_PIG(Feld[newx][newy]))
7957 if (IS_MOVING(newx, newy))
7958 RemoveMovingField(newx, newy);
7961 Feld[newx][newy] = EL_EMPTY;
7962 TEST_DrawLevelField(newx, newy);
7965 PlayLevelSound(x, y, SND_PIG_DIGGING);
7967 else if (!IS_FREE(newx, newy))
7969 if (IS_PLAYER(x, y))
7970 DrawPlayerField(x, y);
7972 TEST_DrawLevelField(x, y);
7977 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
7979 if (Store[x][y] != EL_EMPTY)
7981 boolean can_clone = FALSE;
7984 // check if element to clone is still there
7985 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
7987 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
7995 // cannot clone or target field not free anymore -- do not clone
7996 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7997 Store[x][y] = EL_EMPTY;
8000 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8002 if (IS_MV_DIAGONAL(MovDir[x][y]))
8004 int diagonal_move_dir = MovDir[x][y];
8005 int stored = Store[x][y];
8006 int change_delay = 8;
8009 // android is moving diagonally
8011 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8013 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8014 GfxElement[x][y] = EL_EMC_ANDROID;
8015 GfxAction[x][y] = ACTION_SHRINKING;
8016 GfxDir[x][y] = diagonal_move_dir;
8017 ChangeDelay[x][y] = change_delay;
8019 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8022 DrawLevelGraphicAnimation(x, y, graphic);
8023 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8025 if (Feld[newx][newy] == EL_ACID)
8027 SplashAcid(newx, newy);
8032 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8034 Store[newx][newy] = EL_EMC_ANDROID;
8035 GfxElement[newx][newy] = EL_EMC_ANDROID;
8036 GfxAction[newx][newy] = ACTION_GROWING;
8037 GfxDir[newx][newy] = diagonal_move_dir;
8038 ChangeDelay[newx][newy] = change_delay;
8040 graphic = el_act_dir2img(GfxElement[newx][newy],
8041 GfxAction[newx][newy], GfxDir[newx][newy]);
8043 DrawLevelGraphicAnimation(newx, newy, graphic);
8044 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8050 Feld[newx][newy] = EL_EMPTY;
8051 TEST_DrawLevelField(newx, newy);
8053 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8056 else if (!IS_FREE(newx, newy))
8061 else if (IS_CUSTOM_ELEMENT(element) &&
8062 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8064 if (!DigFieldByCE(newx, newy, element))
8067 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8069 RunnerVisit[x][y] = FrameCounter;
8070 PlayerVisit[x][y] /= 8; // expire player visit path
8073 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8075 if (!IS_FREE(newx, newy))
8077 if (IS_PLAYER(x, y))
8078 DrawPlayerField(x, y);
8080 TEST_DrawLevelField(x, y);
8086 boolean wanna_flame = !RND(10);
8087 int dx = newx - x, dy = newy - y;
8088 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8089 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8090 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8091 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8092 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8093 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8096 IS_CLASSIC_ENEMY(element1) ||
8097 IS_CLASSIC_ENEMY(element2)) &&
8098 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8099 element1 != EL_FLAMES && element2 != EL_FLAMES)
8101 ResetGfxAnimation(x, y);
8102 GfxAction[x][y] = ACTION_ATTACKING;
8104 if (IS_PLAYER(x, y))
8105 DrawPlayerField(x, y);
8107 TEST_DrawLevelField(x, y);
8109 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8111 MovDelay[x][y] = 50;
8113 Feld[newx][newy] = EL_FLAMES;
8114 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8115 Feld[newx1][newy1] = EL_FLAMES;
8116 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8117 Feld[newx2][newy2] = EL_FLAMES;
8123 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8124 Feld[newx][newy] == EL_DIAMOND)
8126 if (IS_MOVING(newx, newy))
8127 RemoveMovingField(newx, newy);
8130 Feld[newx][newy] = EL_EMPTY;
8131 TEST_DrawLevelField(newx, newy);
8134 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8136 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8137 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8139 if (AmoebaNr[newx][newy])
8141 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8142 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8143 Feld[newx][newy] == EL_BD_AMOEBA)
8144 AmoebaCnt[AmoebaNr[newx][newy]]--;
8147 if (IS_MOVING(newx, newy))
8149 RemoveMovingField(newx, newy);
8153 Feld[newx][newy] = EL_EMPTY;
8154 TEST_DrawLevelField(newx, newy);
8157 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8159 else if ((element == EL_PACMAN || element == EL_MOLE)
8160 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8162 if (AmoebaNr[newx][newy])
8164 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8165 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8166 Feld[newx][newy] == EL_BD_AMOEBA)
8167 AmoebaCnt[AmoebaNr[newx][newy]]--;
8170 if (element == EL_MOLE)
8172 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8173 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8175 ResetGfxAnimation(x, y);
8176 GfxAction[x][y] = ACTION_DIGGING;
8177 TEST_DrawLevelField(x, y);
8179 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8181 return; // wait for shrinking amoeba
8183 else // element == EL_PACMAN
8185 Feld[newx][newy] = EL_EMPTY;
8186 TEST_DrawLevelField(newx, newy);
8187 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8190 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8191 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8192 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8194 // wait for shrinking amoeba to completely disappear
8197 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8199 // object was running against a wall
8203 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8204 DrawLevelElementAnimation(x, y, element);
8206 if (DONT_TOUCH(element))
8207 TestIfBadThingTouchesPlayer(x, y);
8212 InitMovingField(x, y, MovDir[x][y]);
8214 PlayLevelSoundAction(x, y, ACTION_MOVING);
8218 ContinueMoving(x, y);
8221 void ContinueMoving(int x, int y)
8223 int element = Feld[x][y];
8224 struct ElementInfo *ei = &element_info[element];
8225 int direction = MovDir[x][y];
8226 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8227 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8228 int newx = x + dx, newy = y + dy;
8229 int stored = Store[x][y];
8230 int stored_new = Store[newx][newy];
8231 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8232 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8233 boolean last_line = (newy == lev_fieldy - 1);
8235 MovPos[x][y] += getElementMoveStepsize(x, y);
8237 if (pushed_by_player) // special case: moving object pushed by player
8238 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8240 if (ABS(MovPos[x][y]) < TILEX)
8242 TEST_DrawLevelField(x, y);
8244 return; // element is still moving
8247 // element reached destination field
8249 Feld[x][y] = EL_EMPTY;
8250 Feld[newx][newy] = element;
8251 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8253 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8255 element = Feld[newx][newy] = EL_ACID;
8257 else if (element == EL_MOLE)
8259 Feld[x][y] = EL_SAND;
8261 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8263 else if (element == EL_QUICKSAND_FILLING)
8265 element = Feld[newx][newy] = get_next_element(element);
8266 Store[newx][newy] = Store[x][y];
8268 else if (element == EL_QUICKSAND_EMPTYING)
8270 Feld[x][y] = get_next_element(element);
8271 element = Feld[newx][newy] = Store[x][y];
8273 else if (element == EL_QUICKSAND_FAST_FILLING)
8275 element = Feld[newx][newy] = get_next_element(element);
8276 Store[newx][newy] = Store[x][y];
8278 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8280 Feld[x][y] = get_next_element(element);
8281 element = Feld[newx][newy] = Store[x][y];
8283 else if (element == EL_MAGIC_WALL_FILLING)
8285 element = Feld[newx][newy] = get_next_element(element);
8286 if (!game.magic_wall_active)
8287 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8288 Store[newx][newy] = Store[x][y];
8290 else if (element == EL_MAGIC_WALL_EMPTYING)
8292 Feld[x][y] = get_next_element(element);
8293 if (!game.magic_wall_active)
8294 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8295 element = Feld[newx][newy] = Store[x][y];
8297 InitField(newx, newy, FALSE);
8299 else if (element == EL_BD_MAGIC_WALL_FILLING)
8301 element = Feld[newx][newy] = get_next_element(element);
8302 if (!game.magic_wall_active)
8303 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8304 Store[newx][newy] = Store[x][y];
8306 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8308 Feld[x][y] = get_next_element(element);
8309 if (!game.magic_wall_active)
8310 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8311 element = Feld[newx][newy] = Store[x][y];
8313 InitField(newx, newy, FALSE);
8315 else if (element == EL_DC_MAGIC_WALL_FILLING)
8317 element = Feld[newx][newy] = get_next_element(element);
8318 if (!game.magic_wall_active)
8319 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8320 Store[newx][newy] = Store[x][y];
8322 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8324 Feld[x][y] = get_next_element(element);
8325 if (!game.magic_wall_active)
8326 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8327 element = Feld[newx][newy] = Store[x][y];
8329 InitField(newx, newy, FALSE);
8331 else if (element == EL_AMOEBA_DROPPING)
8333 Feld[x][y] = get_next_element(element);
8334 element = Feld[newx][newy] = Store[x][y];
8336 else if (element == EL_SOKOBAN_OBJECT)
8339 Feld[x][y] = Back[x][y];
8341 if (Back[newx][newy])
8342 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8344 Back[x][y] = Back[newx][newy] = 0;
8347 Store[x][y] = EL_EMPTY;
8352 MovDelay[newx][newy] = 0;
8354 if (CAN_CHANGE_OR_HAS_ACTION(element))
8356 // copy element change control values to new field
8357 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8358 ChangePage[newx][newy] = ChangePage[x][y];
8359 ChangeCount[newx][newy] = ChangeCount[x][y];
8360 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8363 CustomValue[newx][newy] = CustomValue[x][y];
8365 ChangeDelay[x][y] = 0;
8366 ChangePage[x][y] = -1;
8367 ChangeCount[x][y] = 0;
8368 ChangeEvent[x][y] = -1;
8370 CustomValue[x][y] = 0;
8372 // copy animation control values to new field
8373 GfxFrame[newx][newy] = GfxFrame[x][y];
8374 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8375 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8376 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8378 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8380 // some elements can leave other elements behind after moving
8381 if (ei->move_leave_element != EL_EMPTY &&
8382 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8383 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8385 int move_leave_element = ei->move_leave_element;
8387 // this makes it possible to leave the removed element again
8388 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8389 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8391 Feld[x][y] = move_leave_element;
8393 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8394 MovDir[x][y] = direction;
8396 InitField(x, y, FALSE);
8398 if (GFX_CRUMBLED(Feld[x][y]))
8399 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8401 if (ELEM_IS_PLAYER(move_leave_element))
8402 RelocatePlayer(x, y, move_leave_element);
8405 // do this after checking for left-behind element
8406 ResetGfxAnimation(x, y); // reset animation values for old field
8408 if (!CAN_MOVE(element) ||
8409 (CAN_FALL(element) && direction == MV_DOWN &&
8410 (element == EL_SPRING ||
8411 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8412 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8413 GfxDir[x][y] = MovDir[newx][newy] = 0;
8415 TEST_DrawLevelField(x, y);
8416 TEST_DrawLevelField(newx, newy);
8418 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8420 // prevent pushed element from moving on in pushed direction
8421 if (pushed_by_player && CAN_MOVE(element) &&
8422 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8423 !(element_info[element].move_pattern & direction))
8424 TurnRound(newx, newy);
8426 // prevent elements on conveyor belt from moving on in last direction
8427 if (pushed_by_conveyor && CAN_FALL(element) &&
8428 direction & MV_HORIZONTAL)
8429 MovDir[newx][newy] = 0;
8431 if (!pushed_by_player)
8433 int nextx = newx + dx, nexty = newy + dy;
8434 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8436 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8438 if (CAN_FALL(element) && direction == MV_DOWN)
8439 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8441 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8442 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8444 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8445 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8448 if (DONT_TOUCH(element)) // object may be nasty to player or others
8450 TestIfBadThingTouchesPlayer(newx, newy);
8451 TestIfBadThingTouchesFriend(newx, newy);
8453 if (!IS_CUSTOM_ELEMENT(element))
8454 TestIfBadThingTouchesOtherBadThing(newx, newy);
8456 else if (element == EL_PENGUIN)
8457 TestIfFriendTouchesBadThing(newx, newy);
8459 if (DONT_GET_HIT_BY(element))
8461 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8464 // give the player one last chance (one more frame) to move away
8465 if (CAN_FALL(element) && direction == MV_DOWN &&
8466 (last_line || (!IS_FREE(x, newy + 1) &&
8467 (!IS_PLAYER(x, newy + 1) ||
8468 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8471 if (pushed_by_player && !game.use_change_when_pushing_bug)
8473 int push_side = MV_DIR_OPPOSITE(direction);
8474 struct PlayerInfo *player = PLAYERINFO(x, y);
8476 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8477 player->index_bit, push_side);
8478 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8479 player->index_bit, push_side);
8482 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8483 MovDelay[newx][newy] = 1;
8485 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8487 TestIfElementTouchesCustomElement(x, y); // empty or new element
8488 TestIfElementHitsCustomElement(newx, newy, direction);
8489 TestIfPlayerTouchesCustomElement(newx, newy);
8490 TestIfElementTouchesCustomElement(newx, newy);
8492 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8493 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8494 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8495 MV_DIR_OPPOSITE(direction));
8498 int AmoebeNachbarNr(int ax, int ay)
8501 int element = Feld[ax][ay];
8503 static int xy[4][2] =
8511 for (i = 0; i < NUM_DIRECTIONS; i++)
8513 int x = ax + xy[i][0];
8514 int y = ay + xy[i][1];
8516 if (!IN_LEV_FIELD(x, y))
8519 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8520 group_nr = AmoebaNr[x][y];
8526 static void AmoebenVereinigen(int ax, int ay)
8528 int i, x, y, xx, yy;
8529 int new_group_nr = AmoebaNr[ax][ay];
8530 static int xy[4][2] =
8538 if (new_group_nr == 0)
8541 for (i = 0; i < NUM_DIRECTIONS; i++)
8546 if (!IN_LEV_FIELD(x, y))
8549 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8550 Feld[x][y] == EL_BD_AMOEBA ||
8551 Feld[x][y] == EL_AMOEBA_DEAD) &&
8552 AmoebaNr[x][y] != new_group_nr)
8554 int old_group_nr = AmoebaNr[x][y];
8556 if (old_group_nr == 0)
8559 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8560 AmoebaCnt[old_group_nr] = 0;
8561 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8562 AmoebaCnt2[old_group_nr] = 0;
8564 SCAN_PLAYFIELD(xx, yy)
8566 if (AmoebaNr[xx][yy] == old_group_nr)
8567 AmoebaNr[xx][yy] = new_group_nr;
8573 void AmoebeUmwandeln(int ax, int ay)
8577 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8579 int group_nr = AmoebaNr[ax][ay];
8584 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8585 printf("AmoebeUmwandeln(): This should never happen!\n");
8590 SCAN_PLAYFIELD(x, y)
8592 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8595 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8599 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8600 SND_AMOEBA_TURNING_TO_GEM :
8601 SND_AMOEBA_TURNING_TO_ROCK));
8606 static int xy[4][2] =
8614 for (i = 0; i < NUM_DIRECTIONS; i++)
8619 if (!IN_LEV_FIELD(x, y))
8622 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8624 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8625 SND_AMOEBA_TURNING_TO_GEM :
8626 SND_AMOEBA_TURNING_TO_ROCK));
8633 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8636 int group_nr = AmoebaNr[ax][ay];
8637 boolean done = FALSE;
8642 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8643 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8648 SCAN_PLAYFIELD(x, y)
8650 if (AmoebaNr[x][y] == group_nr &&
8651 (Feld[x][y] == EL_AMOEBA_DEAD ||
8652 Feld[x][y] == EL_BD_AMOEBA ||
8653 Feld[x][y] == EL_AMOEBA_GROWING))
8656 Feld[x][y] = new_element;
8657 InitField(x, y, FALSE);
8658 TEST_DrawLevelField(x, y);
8664 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8665 SND_BD_AMOEBA_TURNING_TO_ROCK :
8666 SND_BD_AMOEBA_TURNING_TO_GEM));
8669 static void AmoebeWaechst(int x, int y)
8671 static unsigned int sound_delay = 0;
8672 static unsigned int sound_delay_value = 0;
8674 if (!MovDelay[x][y]) // start new growing cycle
8678 if (DelayReached(&sound_delay, sound_delay_value))
8680 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8681 sound_delay_value = 30;
8685 if (MovDelay[x][y]) // wait some time before growing bigger
8688 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8690 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8691 6 - MovDelay[x][y]);
8693 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8696 if (!MovDelay[x][y])
8698 Feld[x][y] = Store[x][y];
8700 TEST_DrawLevelField(x, y);
8705 static void AmoebaDisappearing(int x, int y)
8707 static unsigned int sound_delay = 0;
8708 static unsigned int sound_delay_value = 0;
8710 if (!MovDelay[x][y]) // start new shrinking cycle
8714 if (DelayReached(&sound_delay, sound_delay_value))
8715 sound_delay_value = 30;
8718 if (MovDelay[x][y]) // wait some time before shrinking
8721 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8723 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8724 6 - MovDelay[x][y]);
8726 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8729 if (!MovDelay[x][y])
8731 Feld[x][y] = EL_EMPTY;
8732 TEST_DrawLevelField(x, y);
8734 // don't let mole enter this field in this cycle;
8735 // (give priority to objects falling to this field from above)
8741 static void AmoebeAbleger(int ax, int ay)
8744 int element = Feld[ax][ay];
8745 int graphic = el2img(element);
8746 int newax = ax, neway = ay;
8747 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8748 static int xy[4][2] =
8756 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8758 Feld[ax][ay] = EL_AMOEBA_DEAD;
8759 TEST_DrawLevelField(ax, ay);
8763 if (IS_ANIMATED(graphic))
8764 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8766 if (!MovDelay[ax][ay]) // start making new amoeba field
8767 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8769 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8772 if (MovDelay[ax][ay])
8776 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8779 int x = ax + xy[start][0];
8780 int y = ay + xy[start][1];
8782 if (!IN_LEV_FIELD(x, y))
8785 if (IS_FREE(x, y) ||
8786 CAN_GROW_INTO(Feld[x][y]) ||
8787 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8788 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8794 if (newax == ax && neway == ay)
8797 else // normal or "filled" (BD style) amoeba
8800 boolean waiting_for_player = FALSE;
8802 for (i = 0; i < NUM_DIRECTIONS; i++)
8804 int j = (start + i) % 4;
8805 int x = ax + xy[j][0];
8806 int y = ay + xy[j][1];
8808 if (!IN_LEV_FIELD(x, y))
8811 if (IS_FREE(x, y) ||
8812 CAN_GROW_INTO(Feld[x][y]) ||
8813 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8814 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8820 else if (IS_PLAYER(x, y))
8821 waiting_for_player = TRUE;
8824 if (newax == ax && neway == ay) // amoeba cannot grow
8826 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
8828 Feld[ax][ay] = EL_AMOEBA_DEAD;
8829 TEST_DrawLevelField(ax, ay);
8830 AmoebaCnt[AmoebaNr[ax][ay]]--;
8832 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
8834 if (element == EL_AMOEBA_FULL)
8835 AmoebeUmwandeln(ax, ay);
8836 else if (element == EL_BD_AMOEBA)
8837 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
8842 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
8844 // amoeba gets larger by growing in some direction
8846 int new_group_nr = AmoebaNr[ax][ay];
8849 if (new_group_nr == 0)
8851 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
8852 printf("AmoebeAbleger(): This should never happen!\n");
8857 AmoebaNr[newax][neway] = new_group_nr;
8858 AmoebaCnt[new_group_nr]++;
8859 AmoebaCnt2[new_group_nr]++;
8861 // if amoeba touches other amoeba(s) after growing, unify them
8862 AmoebenVereinigen(newax, neway);
8864 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
8866 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
8872 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
8873 (neway == lev_fieldy - 1 && newax != ax))
8875 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
8876 Store[newax][neway] = element;
8878 else if (neway == ay || element == EL_EMC_DRIPPER)
8880 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
8882 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
8886 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
8887 Feld[ax][ay] = EL_AMOEBA_DROPPING;
8888 Store[ax][ay] = EL_AMOEBA_DROP;
8889 ContinueMoving(ax, ay);
8893 TEST_DrawLevelField(newax, neway);
8896 static void Life(int ax, int ay)
8900 int element = Feld[ax][ay];
8901 int graphic = el2img(element);
8902 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
8904 boolean changed = FALSE;
8906 if (IS_ANIMATED(graphic))
8907 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8912 if (!MovDelay[ax][ay]) // start new "game of life" cycle
8913 MovDelay[ax][ay] = life_time;
8915 if (MovDelay[ax][ay]) // wait some time before next cycle
8918 if (MovDelay[ax][ay])
8922 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
8924 int xx = ax+x1, yy = ay+y1;
8925 int old_element = Feld[xx][yy];
8926 int num_neighbours = 0;
8928 if (!IN_LEV_FIELD(xx, yy))
8931 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
8933 int x = xx+x2, y = yy+y2;
8935 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
8938 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
8939 boolean is_neighbour = FALSE;
8941 if (level.use_life_bugs)
8943 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
8944 (IS_FREE(x, y) && Stop[x][y]));
8947 (Last[x][y] == element || is_player_cell);
8953 boolean is_free = FALSE;
8955 if (level.use_life_bugs)
8956 is_free = (IS_FREE(xx, yy));
8958 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
8960 if (xx == ax && yy == ay) // field in the middle
8962 if (num_neighbours < life_parameter[0] ||
8963 num_neighbours > life_parameter[1])
8965 Feld[xx][yy] = EL_EMPTY;
8966 if (Feld[xx][yy] != old_element)
8967 TEST_DrawLevelField(xx, yy);
8968 Stop[xx][yy] = TRUE;
8972 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
8973 { // free border field
8974 if (num_neighbours >= life_parameter[2] &&
8975 num_neighbours <= life_parameter[3])
8977 Feld[xx][yy] = element;
8978 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
8979 if (Feld[xx][yy] != old_element)
8980 TEST_DrawLevelField(xx, yy);
8981 Stop[xx][yy] = TRUE;
8988 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
8989 SND_GAME_OF_LIFE_GROWING);
8992 static void InitRobotWheel(int x, int y)
8994 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
8997 static void RunRobotWheel(int x, int y)
8999 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9002 static void StopRobotWheel(int x, int y)
9004 if (ZX == x && ZY == y)
9008 game.robot_wheel_active = FALSE;
9012 static void InitTimegateWheel(int x, int y)
9014 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9017 static void RunTimegateWheel(int x, int y)
9019 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9022 static void InitMagicBallDelay(int x, int y)
9024 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9027 static void ActivateMagicBall(int bx, int by)
9031 if (level.ball_random)
9033 int pos_border = RND(8); // select one of the eight border elements
9034 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9035 int xx = pos_content % 3;
9036 int yy = pos_content / 3;
9041 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9042 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9046 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9048 int xx = x - bx + 1;
9049 int yy = y - by + 1;
9051 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9052 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9056 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9059 static void CheckExit(int x, int y)
9061 if (local_player->gems_still_needed > 0 ||
9062 local_player->sokoban_fields_still_needed > 0 ||
9063 local_player->sokoban_objects_still_needed > 0 ||
9064 local_player->lights_still_needed > 0)
9066 int element = Feld[x][y];
9067 int graphic = el2img(element);
9069 if (IS_ANIMATED(graphic))
9070 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9075 if (AllPlayersGone) // do not re-open exit door closed after last player
9078 Feld[x][y] = EL_EXIT_OPENING;
9080 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9083 static void CheckExitEM(int x, int y)
9085 if (local_player->gems_still_needed > 0 ||
9086 local_player->sokoban_fields_still_needed > 0 ||
9087 local_player->sokoban_objects_still_needed > 0 ||
9088 local_player->lights_still_needed > 0)
9090 int element = Feld[x][y];
9091 int graphic = el2img(element);
9093 if (IS_ANIMATED(graphic))
9094 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9099 if (AllPlayersGone) // do not re-open exit door closed after last player
9102 Feld[x][y] = EL_EM_EXIT_OPENING;
9104 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9107 static void CheckExitSteel(int x, int y)
9109 if (local_player->gems_still_needed > 0 ||
9110 local_player->sokoban_fields_still_needed > 0 ||
9111 local_player->sokoban_objects_still_needed > 0 ||
9112 local_player->lights_still_needed > 0)
9114 int element = Feld[x][y];
9115 int graphic = el2img(element);
9117 if (IS_ANIMATED(graphic))
9118 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9123 if (AllPlayersGone) // do not re-open exit door closed after last player
9126 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9128 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9131 static void CheckExitSteelEM(int x, int y)
9133 if (local_player->gems_still_needed > 0 ||
9134 local_player->sokoban_fields_still_needed > 0 ||
9135 local_player->sokoban_objects_still_needed > 0 ||
9136 local_player->lights_still_needed > 0)
9138 int element = Feld[x][y];
9139 int graphic = el2img(element);
9141 if (IS_ANIMATED(graphic))
9142 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9147 if (AllPlayersGone) // do not re-open exit door closed after last player
9150 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9152 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9155 static void CheckExitSP(int x, int y)
9157 if (local_player->gems_still_needed > 0)
9159 int element = Feld[x][y];
9160 int graphic = el2img(element);
9162 if (IS_ANIMATED(graphic))
9163 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9168 if (AllPlayersGone) // do not re-open exit door closed after last player
9171 Feld[x][y] = EL_SP_EXIT_OPENING;
9173 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9176 static void CloseAllOpenTimegates(void)
9180 SCAN_PLAYFIELD(x, y)
9182 int element = Feld[x][y];
9184 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9186 Feld[x][y] = EL_TIMEGATE_CLOSING;
9188 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9193 static void DrawTwinkleOnField(int x, int y)
9195 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9198 if (Feld[x][y] == EL_BD_DIAMOND)
9201 if (MovDelay[x][y] == 0) // next animation frame
9202 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9204 if (MovDelay[x][y] != 0) // wait some time before next frame
9208 DrawLevelElementAnimation(x, y, Feld[x][y]);
9210 if (MovDelay[x][y] != 0)
9212 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9213 10 - MovDelay[x][y]);
9215 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9220 static void MauerWaechst(int x, int y)
9224 if (!MovDelay[x][y]) // next animation frame
9225 MovDelay[x][y] = 3 * delay;
9227 if (MovDelay[x][y]) // wait some time before next frame
9231 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9233 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9234 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9236 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9239 if (!MovDelay[x][y])
9241 if (MovDir[x][y] == MV_LEFT)
9243 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9244 TEST_DrawLevelField(x - 1, y);
9246 else if (MovDir[x][y] == MV_RIGHT)
9248 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9249 TEST_DrawLevelField(x + 1, y);
9251 else if (MovDir[x][y] == MV_UP)
9253 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9254 TEST_DrawLevelField(x, y - 1);
9258 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9259 TEST_DrawLevelField(x, y + 1);
9262 Feld[x][y] = Store[x][y];
9264 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9265 TEST_DrawLevelField(x, y);
9270 static void MauerAbleger(int ax, int ay)
9272 int element = Feld[ax][ay];
9273 int graphic = el2img(element);
9274 boolean oben_frei = FALSE, unten_frei = FALSE;
9275 boolean links_frei = FALSE, rechts_frei = FALSE;
9276 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9277 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9278 boolean new_wall = FALSE;
9280 if (IS_ANIMATED(graphic))
9281 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9283 if (!MovDelay[ax][ay]) // start building new wall
9284 MovDelay[ax][ay] = 6;
9286 if (MovDelay[ax][ay]) // wait some time before building new wall
9289 if (MovDelay[ax][ay])
9293 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9295 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9297 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9299 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9302 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9303 element == EL_EXPANDABLE_WALL_ANY)
9307 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9308 Store[ax][ay-1] = element;
9309 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9310 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9311 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9312 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9317 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9318 Store[ax][ay+1] = element;
9319 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9320 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9321 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9322 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9327 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9328 element == EL_EXPANDABLE_WALL_ANY ||
9329 element == EL_EXPANDABLE_WALL ||
9330 element == EL_BD_EXPANDABLE_WALL)
9334 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9335 Store[ax-1][ay] = element;
9336 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9337 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9338 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9339 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9345 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9346 Store[ax+1][ay] = element;
9347 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9348 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9349 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9350 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9355 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9356 TEST_DrawLevelField(ax, ay);
9358 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9360 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9361 unten_massiv = TRUE;
9362 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9363 links_massiv = TRUE;
9364 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9365 rechts_massiv = TRUE;
9367 if (((oben_massiv && unten_massiv) ||
9368 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9369 element == EL_EXPANDABLE_WALL) &&
9370 ((links_massiv && rechts_massiv) ||
9371 element == EL_EXPANDABLE_WALL_VERTICAL))
9372 Feld[ax][ay] = EL_WALL;
9375 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9378 static void MauerAblegerStahl(int ax, int ay)
9380 int element = Feld[ax][ay];
9381 int graphic = el2img(element);
9382 boolean oben_frei = FALSE, unten_frei = FALSE;
9383 boolean links_frei = FALSE, rechts_frei = FALSE;
9384 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9385 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9386 boolean new_wall = FALSE;
9388 if (IS_ANIMATED(graphic))
9389 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9391 if (!MovDelay[ax][ay]) // start building new wall
9392 MovDelay[ax][ay] = 6;
9394 if (MovDelay[ax][ay]) // wait some time before building new wall
9397 if (MovDelay[ax][ay])
9401 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9403 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9405 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9407 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9410 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9411 element == EL_EXPANDABLE_STEELWALL_ANY)
9415 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9416 Store[ax][ay-1] = element;
9417 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9418 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9419 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9420 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9425 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9426 Store[ax][ay+1] = element;
9427 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9428 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9429 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9430 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9435 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9436 element == EL_EXPANDABLE_STEELWALL_ANY)
9440 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9441 Store[ax-1][ay] = element;
9442 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9443 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9444 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9445 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9451 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9452 Store[ax+1][ay] = element;
9453 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9454 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9455 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9456 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9461 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9463 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9464 unten_massiv = TRUE;
9465 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9466 links_massiv = TRUE;
9467 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9468 rechts_massiv = TRUE;
9470 if (((oben_massiv && unten_massiv) ||
9471 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9472 ((links_massiv && rechts_massiv) ||
9473 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9474 Feld[ax][ay] = EL_STEELWALL;
9477 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9480 static void CheckForDragon(int x, int y)
9483 boolean dragon_found = FALSE;
9484 static int xy[4][2] =
9492 for (i = 0; i < NUM_DIRECTIONS; i++)
9494 for (j = 0; j < 4; j++)
9496 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9498 if (IN_LEV_FIELD(xx, yy) &&
9499 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9501 if (Feld[xx][yy] == EL_DRAGON)
9502 dragon_found = TRUE;
9511 for (i = 0; i < NUM_DIRECTIONS; i++)
9513 for (j = 0; j < 3; j++)
9515 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9517 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9519 Feld[xx][yy] = EL_EMPTY;
9520 TEST_DrawLevelField(xx, yy);
9529 static void InitBuggyBase(int x, int y)
9531 int element = Feld[x][y];
9532 int activating_delay = FRAMES_PER_SECOND / 4;
9535 (element == EL_SP_BUGGY_BASE ?
9536 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9537 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9539 element == EL_SP_BUGGY_BASE_ACTIVE ?
9540 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9543 static void WarnBuggyBase(int x, int y)
9546 static int xy[4][2] =
9554 for (i = 0; i < NUM_DIRECTIONS; i++)
9556 int xx = x + xy[i][0];
9557 int yy = y + xy[i][1];
9559 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9561 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9568 static void InitTrap(int x, int y)
9570 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9573 static void ActivateTrap(int x, int y)
9575 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9578 static void ChangeActiveTrap(int x, int y)
9580 int graphic = IMG_TRAP_ACTIVE;
9582 // if new animation frame was drawn, correct crumbled sand border
9583 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9584 TEST_DrawLevelFieldCrumbled(x, y);
9587 static int getSpecialActionElement(int element, int number, int base_element)
9589 return (element != EL_EMPTY ? element :
9590 number != -1 ? base_element + number - 1 :
9594 static int getModifiedActionNumber(int value_old, int operator, int operand,
9595 int value_min, int value_max)
9597 int value_new = (operator == CA_MODE_SET ? operand :
9598 operator == CA_MODE_ADD ? value_old + operand :
9599 operator == CA_MODE_SUBTRACT ? value_old - operand :
9600 operator == CA_MODE_MULTIPLY ? value_old * operand :
9601 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9602 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9605 return (value_new < value_min ? value_min :
9606 value_new > value_max ? value_max :
9610 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9612 struct ElementInfo *ei = &element_info[element];
9613 struct ElementChangeInfo *change = &ei->change_page[page];
9614 int target_element = change->target_element;
9615 int action_type = change->action_type;
9616 int action_mode = change->action_mode;
9617 int action_arg = change->action_arg;
9618 int action_element = change->action_element;
9621 if (!change->has_action)
9624 // ---------- determine action paramater values -----------------------------
9626 int level_time_value =
9627 (level.time > 0 ? TimeLeft :
9630 int action_arg_element_raw =
9631 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9632 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9633 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9634 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9635 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9636 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9637 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9639 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9641 int action_arg_direction =
9642 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9643 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9644 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9645 change->actual_trigger_side :
9646 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9647 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9650 int action_arg_number_min =
9651 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9654 int action_arg_number_max =
9655 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9656 action_type == CA_SET_LEVEL_GEMS ? 999 :
9657 action_type == CA_SET_LEVEL_TIME ? 9999 :
9658 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9659 action_type == CA_SET_CE_VALUE ? 9999 :
9660 action_type == CA_SET_CE_SCORE ? 9999 :
9663 int action_arg_number_reset =
9664 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9665 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9666 action_type == CA_SET_LEVEL_TIME ? level.time :
9667 action_type == CA_SET_LEVEL_SCORE ? 0 :
9668 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9669 action_type == CA_SET_CE_SCORE ? 0 :
9672 int action_arg_number =
9673 (action_arg <= CA_ARG_MAX ? action_arg :
9674 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9675 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9676 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9677 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9678 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9679 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9680 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9681 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9682 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9683 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9684 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? local_player->gems_still_needed :
9685 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->score :
9686 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9687 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9688 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9689 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9690 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9691 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9692 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9693 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9694 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9697 int action_arg_number_old =
9698 (action_type == CA_SET_LEVEL_GEMS ? local_player->gems_still_needed :
9699 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9700 action_type == CA_SET_LEVEL_SCORE ? local_player->score :
9701 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9702 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9705 int action_arg_number_new =
9706 getModifiedActionNumber(action_arg_number_old,
9707 action_mode, action_arg_number,
9708 action_arg_number_min, action_arg_number_max);
9710 int trigger_player_bits =
9711 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9712 change->actual_trigger_player_bits : change->trigger_player);
9714 int action_arg_player_bits =
9715 (action_arg >= CA_ARG_PLAYER_1 &&
9716 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9717 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9718 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9721 // ---------- execute action -----------------------------------------------
9723 switch (action_type)
9730 // ---------- level actions ----------------------------------------------
9732 case CA_RESTART_LEVEL:
9734 game.restart_level = TRUE;
9739 case CA_SHOW_ENVELOPE:
9741 int element = getSpecialActionElement(action_arg_element,
9742 action_arg_number, EL_ENVELOPE_1);
9744 if (IS_ENVELOPE(element))
9745 local_player->show_envelope = element;
9750 case CA_SET_LEVEL_TIME:
9752 if (level.time > 0) // only modify limited time value
9754 TimeLeft = action_arg_number_new;
9756 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9758 DisplayGameControlValues();
9760 if (!TimeLeft && setup.time_limit)
9761 for (i = 0; i < MAX_PLAYERS; i++)
9762 KillPlayer(&stored_player[i]);
9768 case CA_SET_LEVEL_SCORE:
9770 local_player->score = action_arg_number_new;
9772 game_panel_controls[GAME_PANEL_SCORE].value = local_player->score;
9774 DisplayGameControlValues();
9779 case CA_SET_LEVEL_GEMS:
9781 local_player->gems_still_needed = action_arg_number_new;
9783 game.snapshot.collected_item = TRUE;
9785 game_panel_controls[GAME_PANEL_GEMS].value =
9786 local_player->gems_still_needed;
9788 DisplayGameControlValues();
9793 case CA_SET_LEVEL_WIND:
9795 game.wind_direction = action_arg_direction;
9800 case CA_SET_LEVEL_RANDOM_SEED:
9802 // ensure that setting a new random seed while playing is predictable
9803 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9808 // ---------- player actions ---------------------------------------------
9810 case CA_MOVE_PLAYER:
9812 // automatically move to the next field in specified direction
9813 for (i = 0; i < MAX_PLAYERS; i++)
9814 if (trigger_player_bits & (1 << i))
9815 stored_player[i].programmed_action = action_arg_direction;
9820 case CA_EXIT_PLAYER:
9822 for (i = 0; i < MAX_PLAYERS; i++)
9823 if (action_arg_player_bits & (1 << i))
9824 ExitPlayer(&stored_player[i]);
9827 PlayerWins(local_player);
9832 case CA_KILL_PLAYER:
9834 for (i = 0; i < MAX_PLAYERS; i++)
9835 if (action_arg_player_bits & (1 << i))
9836 KillPlayer(&stored_player[i]);
9841 case CA_SET_PLAYER_KEYS:
9843 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
9844 int element = getSpecialActionElement(action_arg_element,
9845 action_arg_number, EL_KEY_1);
9847 if (IS_KEY(element))
9849 for (i = 0; i < MAX_PLAYERS; i++)
9851 if (trigger_player_bits & (1 << i))
9853 stored_player[i].key[KEY_NR(element)] = key_state;
9855 DrawGameDoorValues();
9863 case CA_SET_PLAYER_SPEED:
9865 for (i = 0; i < MAX_PLAYERS; i++)
9867 if (trigger_player_bits & (1 << i))
9869 int move_stepsize = TILEX / stored_player[i].move_delay_value;
9871 if (action_arg == CA_ARG_SPEED_FASTER &&
9872 stored_player[i].cannot_move)
9874 action_arg_number = STEPSIZE_VERY_SLOW;
9876 else if (action_arg == CA_ARG_SPEED_SLOWER ||
9877 action_arg == CA_ARG_SPEED_FASTER)
9879 action_arg_number = 2;
9880 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
9883 else if (action_arg == CA_ARG_NUMBER_RESET)
9885 action_arg_number = level.initial_player_stepsize[i];
9889 getModifiedActionNumber(move_stepsize,
9892 action_arg_number_min,
9893 action_arg_number_max);
9895 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
9902 case CA_SET_PLAYER_SHIELD:
9904 for (i = 0; i < MAX_PLAYERS; i++)
9906 if (trigger_player_bits & (1 << i))
9908 if (action_arg == CA_ARG_SHIELD_OFF)
9910 stored_player[i].shield_normal_time_left = 0;
9911 stored_player[i].shield_deadly_time_left = 0;
9913 else if (action_arg == CA_ARG_SHIELD_NORMAL)
9915 stored_player[i].shield_normal_time_left = 999999;
9917 else if (action_arg == CA_ARG_SHIELD_DEADLY)
9919 stored_player[i].shield_normal_time_left = 999999;
9920 stored_player[i].shield_deadly_time_left = 999999;
9928 case CA_SET_PLAYER_GRAVITY:
9930 for (i = 0; i < MAX_PLAYERS; i++)
9932 if (trigger_player_bits & (1 << i))
9934 stored_player[i].gravity =
9935 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
9936 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
9937 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
9938 stored_player[i].gravity);
9945 case CA_SET_PLAYER_ARTWORK:
9947 for (i = 0; i < MAX_PLAYERS; i++)
9949 if (trigger_player_bits & (1 << i))
9951 int artwork_element = action_arg_element;
9953 if (action_arg == CA_ARG_ELEMENT_RESET)
9955 (level.use_artwork_element[i] ? level.artwork_element[i] :
9956 stored_player[i].element_nr);
9958 if (stored_player[i].artwork_element != artwork_element)
9959 stored_player[i].Frame = 0;
9961 stored_player[i].artwork_element = artwork_element;
9963 SetPlayerWaiting(&stored_player[i], FALSE);
9965 // set number of special actions for bored and sleeping animation
9966 stored_player[i].num_special_action_bored =
9967 get_num_special_action(artwork_element,
9968 ACTION_BORING_1, ACTION_BORING_LAST);
9969 stored_player[i].num_special_action_sleeping =
9970 get_num_special_action(artwork_element,
9971 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
9978 case CA_SET_PLAYER_INVENTORY:
9980 for (i = 0; i < MAX_PLAYERS; i++)
9982 struct PlayerInfo *player = &stored_player[i];
9985 if (trigger_player_bits & (1 << i))
9987 int inventory_element = action_arg_element;
9989 if (action_arg == CA_ARG_ELEMENT_TARGET ||
9990 action_arg == CA_ARG_ELEMENT_TRIGGER ||
9991 action_arg == CA_ARG_ELEMENT_ACTION)
9993 int element = inventory_element;
9994 int collect_count = element_info[element].collect_count_initial;
9996 if (!IS_CUSTOM_ELEMENT(element))
9999 if (collect_count == 0)
10000 player->inventory_infinite_element = element;
10002 for (k = 0; k < collect_count; k++)
10003 if (player->inventory_size < MAX_INVENTORY_SIZE)
10004 player->inventory_element[player->inventory_size++] =
10007 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10008 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10009 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10011 if (player->inventory_infinite_element != EL_UNDEFINED &&
10012 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10013 action_arg_element_raw))
10014 player->inventory_infinite_element = EL_UNDEFINED;
10016 for (k = 0, j = 0; j < player->inventory_size; j++)
10018 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10019 action_arg_element_raw))
10020 player->inventory_element[k++] = player->inventory_element[j];
10023 player->inventory_size = k;
10025 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10027 if (player->inventory_size > 0)
10029 for (j = 0; j < player->inventory_size - 1; j++)
10030 player->inventory_element[j] = player->inventory_element[j + 1];
10032 player->inventory_size--;
10035 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10037 if (player->inventory_size > 0)
10038 player->inventory_size--;
10040 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10042 player->inventory_infinite_element = EL_UNDEFINED;
10043 player->inventory_size = 0;
10045 else if (action_arg == CA_ARG_INVENTORY_RESET)
10047 player->inventory_infinite_element = EL_UNDEFINED;
10048 player->inventory_size = 0;
10050 if (level.use_initial_inventory[i])
10052 for (j = 0; j < level.initial_inventory_size[i]; j++)
10054 int element = level.initial_inventory_content[i][j];
10055 int collect_count = element_info[element].collect_count_initial;
10057 if (!IS_CUSTOM_ELEMENT(element))
10060 if (collect_count == 0)
10061 player->inventory_infinite_element = element;
10063 for (k = 0; k < collect_count; k++)
10064 if (player->inventory_size < MAX_INVENTORY_SIZE)
10065 player->inventory_element[player->inventory_size++] =
10076 // ---------- CE actions -------------------------------------------------
10078 case CA_SET_CE_VALUE:
10080 int last_ce_value = CustomValue[x][y];
10082 CustomValue[x][y] = action_arg_number_new;
10084 if (CustomValue[x][y] != last_ce_value)
10086 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10087 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10089 if (CustomValue[x][y] == 0)
10091 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10092 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10099 case CA_SET_CE_SCORE:
10101 int last_ce_score = ei->collect_score;
10103 ei->collect_score = action_arg_number_new;
10105 if (ei->collect_score != last_ce_score)
10107 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10108 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10110 if (ei->collect_score == 0)
10114 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10115 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10118 This is a very special case that seems to be a mixture between
10119 CheckElementChange() and CheckTriggeredElementChange(): while
10120 the first one only affects single elements that are triggered
10121 directly, the second one affects multiple elements in the playfield
10122 that are triggered indirectly by another element. This is a third
10123 case: Changing the CE score always affects multiple identical CEs,
10124 so every affected CE must be checked, not only the single CE for
10125 which the CE score was changed in the first place (as every instance
10126 of that CE shares the same CE score, and therefore also can change)!
10128 SCAN_PLAYFIELD(xx, yy)
10130 if (Feld[xx][yy] == element)
10131 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10132 CE_SCORE_GETS_ZERO);
10140 case CA_SET_CE_ARTWORK:
10142 int artwork_element = action_arg_element;
10143 boolean reset_frame = FALSE;
10146 if (action_arg == CA_ARG_ELEMENT_RESET)
10147 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10150 if (ei->gfx_element != artwork_element)
10151 reset_frame = TRUE;
10153 ei->gfx_element = artwork_element;
10155 SCAN_PLAYFIELD(xx, yy)
10157 if (Feld[xx][yy] == element)
10161 ResetGfxAnimation(xx, yy);
10162 ResetRandomAnimationValue(xx, yy);
10165 TEST_DrawLevelField(xx, yy);
10172 // ---------- engine actions ---------------------------------------------
10174 case CA_SET_ENGINE_SCAN_MODE:
10176 InitPlayfieldScanMode(action_arg);
10186 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10188 int old_element = Feld[x][y];
10189 int new_element = GetElementFromGroupElement(element);
10190 int previous_move_direction = MovDir[x][y];
10191 int last_ce_value = CustomValue[x][y];
10192 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10193 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10194 boolean add_player_onto_element = (new_element_is_player &&
10195 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10196 IS_WALKABLE(old_element));
10198 if (!add_player_onto_element)
10200 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10201 RemoveMovingField(x, y);
10205 Feld[x][y] = new_element;
10207 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10208 MovDir[x][y] = previous_move_direction;
10210 if (element_info[new_element].use_last_ce_value)
10211 CustomValue[x][y] = last_ce_value;
10213 InitField_WithBug1(x, y, FALSE);
10215 new_element = Feld[x][y]; // element may have changed
10217 ResetGfxAnimation(x, y);
10218 ResetRandomAnimationValue(x, y);
10220 TEST_DrawLevelField(x, y);
10222 if (GFX_CRUMBLED(new_element))
10223 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10226 // check if element under the player changes from accessible to unaccessible
10227 // (needed for special case of dropping element which then changes)
10228 // (must be checked after creating new element for walkable group elements)
10229 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10230 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10237 // "ChangeCount" not set yet to allow "entered by player" change one time
10238 if (new_element_is_player)
10239 RelocatePlayer(x, y, new_element);
10242 ChangeCount[x][y]++; // count number of changes in the same frame
10244 TestIfBadThingTouchesPlayer(x, y);
10245 TestIfPlayerTouchesCustomElement(x, y);
10246 TestIfElementTouchesCustomElement(x, y);
10249 static void CreateField(int x, int y, int element)
10251 CreateFieldExt(x, y, element, FALSE);
10254 static void CreateElementFromChange(int x, int y, int element)
10256 element = GET_VALID_RUNTIME_ELEMENT(element);
10258 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10260 int old_element = Feld[x][y];
10262 // prevent changed element from moving in same engine frame
10263 // unless both old and new element can either fall or move
10264 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10265 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10269 CreateFieldExt(x, y, element, TRUE);
10272 static boolean ChangeElement(int x, int y, int element, int page)
10274 struct ElementInfo *ei = &element_info[element];
10275 struct ElementChangeInfo *change = &ei->change_page[page];
10276 int ce_value = CustomValue[x][y];
10277 int ce_score = ei->collect_score;
10278 int target_element;
10279 int old_element = Feld[x][y];
10281 // always use default change event to prevent running into a loop
10282 if (ChangeEvent[x][y] == -1)
10283 ChangeEvent[x][y] = CE_DELAY;
10285 if (ChangeEvent[x][y] == CE_DELAY)
10287 // reset actual trigger element, trigger player and action element
10288 change->actual_trigger_element = EL_EMPTY;
10289 change->actual_trigger_player = EL_EMPTY;
10290 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10291 change->actual_trigger_side = CH_SIDE_NONE;
10292 change->actual_trigger_ce_value = 0;
10293 change->actual_trigger_ce_score = 0;
10296 // do not change elements more than a specified maximum number of changes
10297 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10300 ChangeCount[x][y]++; // count number of changes in the same frame
10302 if (change->explode)
10309 if (change->use_target_content)
10311 boolean complete_replace = TRUE;
10312 boolean can_replace[3][3];
10315 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10318 boolean is_walkable;
10319 boolean is_diggable;
10320 boolean is_collectible;
10321 boolean is_removable;
10322 boolean is_destructible;
10323 int ex = x + xx - 1;
10324 int ey = y + yy - 1;
10325 int content_element = change->target_content.e[xx][yy];
10328 can_replace[xx][yy] = TRUE;
10330 if (ex == x && ey == y) // do not check changing element itself
10333 if (content_element == EL_EMPTY_SPACE)
10335 can_replace[xx][yy] = FALSE; // do not replace border with space
10340 if (!IN_LEV_FIELD(ex, ey))
10342 can_replace[xx][yy] = FALSE;
10343 complete_replace = FALSE;
10350 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10351 e = MovingOrBlocked2Element(ex, ey);
10353 is_empty = (IS_FREE(ex, ey) ||
10354 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10356 is_walkable = (is_empty || IS_WALKABLE(e));
10357 is_diggable = (is_empty || IS_DIGGABLE(e));
10358 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10359 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10360 is_removable = (is_diggable || is_collectible);
10362 can_replace[xx][yy] =
10363 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10364 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10365 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10366 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10367 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10368 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10369 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10371 if (!can_replace[xx][yy])
10372 complete_replace = FALSE;
10375 if (!change->only_if_complete || complete_replace)
10377 boolean something_has_changed = FALSE;
10379 if (change->only_if_complete && change->use_random_replace &&
10380 RND(100) < change->random_percentage)
10383 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10385 int ex = x + xx - 1;
10386 int ey = y + yy - 1;
10387 int content_element;
10389 if (can_replace[xx][yy] && (!change->use_random_replace ||
10390 RND(100) < change->random_percentage))
10392 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10393 RemoveMovingField(ex, ey);
10395 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10397 content_element = change->target_content.e[xx][yy];
10398 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10399 ce_value, ce_score);
10401 CreateElementFromChange(ex, ey, target_element);
10403 something_has_changed = TRUE;
10405 // for symmetry reasons, freeze newly created border elements
10406 if (ex != x || ey != y)
10407 Stop[ex][ey] = TRUE; // no more moving in this frame
10411 if (something_has_changed)
10413 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10414 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10420 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10421 ce_value, ce_score);
10423 if (element == EL_DIAGONAL_GROWING ||
10424 element == EL_DIAGONAL_SHRINKING)
10426 target_element = Store[x][y];
10428 Store[x][y] = EL_EMPTY;
10431 CreateElementFromChange(x, y, target_element);
10433 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10434 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10437 // this uses direct change before indirect change
10438 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10443 static void HandleElementChange(int x, int y, int page)
10445 int element = MovingOrBlocked2Element(x, y);
10446 struct ElementInfo *ei = &element_info[element];
10447 struct ElementChangeInfo *change = &ei->change_page[page];
10448 boolean handle_action_before_change = FALSE;
10451 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10452 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10455 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10456 x, y, element, element_info[element].token_name);
10457 printf("HandleElementChange(): This should never happen!\n");
10462 // this can happen with classic bombs on walkable, changing elements
10463 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10468 if (ChangeDelay[x][y] == 0) // initialize element change
10470 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10472 if (change->can_change)
10474 // !!! not clear why graphic animation should be reset at all here !!!
10475 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10476 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10479 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10481 When using an animation frame delay of 1 (this only happens with
10482 "sp_zonk.moving.left/right" in the classic graphics), the default
10483 (non-moving) animation shows wrong animation frames (while the
10484 moving animation, like "sp_zonk.moving.left/right", is correct,
10485 so this graphical bug never shows up with the classic graphics).
10486 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10487 be drawn instead of the correct frames 0,1,2,3. This is caused by
10488 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10489 an element change: First when the change delay ("ChangeDelay[][]")
10490 counter has reached zero after decrementing, then a second time in
10491 the next frame (after "GfxFrame[][]" was already incremented) when
10492 "ChangeDelay[][]" is reset to the initial delay value again.
10494 This causes frame 0 to be drawn twice, while the last frame won't
10495 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10497 As some animations may already be cleverly designed around this bug
10498 (at least the "Snake Bite" snake tail animation does this), it cannot
10499 simply be fixed here without breaking such existing animations.
10500 Unfortunately, it cannot easily be detected if a graphics set was
10501 designed "before" or "after" the bug was fixed. As a workaround,
10502 a new graphics set option "game.graphics_engine_version" was added
10503 to be able to specify the game's major release version for which the
10504 graphics set was designed, which can then be used to decide if the
10505 bugfix should be used (version 4 and above) or not (version 3 or
10506 below, or if no version was specified at all, as with old sets).
10508 (The wrong/fixed animation frames can be tested with the test level set
10509 "test_gfxframe" and level "000", which contains a specially prepared
10510 custom element at level position (x/y) == (11/9) which uses the zonk
10511 animation mentioned above. Using "game.graphics_engine_version: 4"
10512 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10513 This can also be seen from the debug output for this test element.)
10516 // when a custom element is about to change (for example by change delay),
10517 // do not reset graphic animation when the custom element is moving
10518 if (game.graphics_engine_version < 4 &&
10521 ResetGfxAnimation(x, y);
10522 ResetRandomAnimationValue(x, y);
10525 if (change->pre_change_function)
10526 change->pre_change_function(x, y);
10530 ChangeDelay[x][y]--;
10532 if (ChangeDelay[x][y] != 0) // continue element change
10534 if (change->can_change)
10536 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10538 if (IS_ANIMATED(graphic))
10539 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10541 if (change->change_function)
10542 change->change_function(x, y);
10545 else // finish element change
10547 if (ChangePage[x][y] != -1) // remember page from delayed change
10549 page = ChangePage[x][y];
10550 ChangePage[x][y] = -1;
10552 change = &ei->change_page[page];
10555 if (IS_MOVING(x, y)) // never change a running system ;-)
10557 ChangeDelay[x][y] = 1; // try change after next move step
10558 ChangePage[x][y] = page; // remember page to use for change
10563 // special case: set new level random seed before changing element
10564 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10565 handle_action_before_change = TRUE;
10567 if (change->has_action && handle_action_before_change)
10568 ExecuteCustomElementAction(x, y, element, page);
10570 if (change->can_change)
10572 if (ChangeElement(x, y, element, page))
10574 if (change->post_change_function)
10575 change->post_change_function(x, y);
10579 if (change->has_action && !handle_action_before_change)
10580 ExecuteCustomElementAction(x, y, element, page);
10584 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10585 int trigger_element,
10587 int trigger_player,
10591 boolean change_done_any = FALSE;
10592 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10595 if (!(trigger_events[trigger_element][trigger_event]))
10598 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10600 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10602 int element = EL_CUSTOM_START + i;
10603 boolean change_done = FALSE;
10606 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10607 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10610 for (p = 0; p < element_info[element].num_change_pages; p++)
10612 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10614 if (change->can_change_or_has_action &&
10615 change->has_event[trigger_event] &&
10616 change->trigger_side & trigger_side &&
10617 change->trigger_player & trigger_player &&
10618 change->trigger_page & trigger_page_bits &&
10619 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10621 change->actual_trigger_element = trigger_element;
10622 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10623 change->actual_trigger_player_bits = trigger_player;
10624 change->actual_trigger_side = trigger_side;
10625 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10626 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10628 if ((change->can_change && !change_done) || change->has_action)
10632 SCAN_PLAYFIELD(x, y)
10634 if (Feld[x][y] == element)
10636 if (change->can_change && !change_done)
10638 // if element already changed in this frame, not only prevent
10639 // another element change (checked in ChangeElement()), but
10640 // also prevent additional element actions for this element
10642 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10643 !level.use_action_after_change_bug)
10646 ChangeDelay[x][y] = 1;
10647 ChangeEvent[x][y] = trigger_event;
10649 HandleElementChange(x, y, p);
10651 else if (change->has_action)
10653 // if element already changed in this frame, not only prevent
10654 // another element change (checked in ChangeElement()), but
10655 // also prevent additional element actions for this element
10657 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10658 !level.use_action_after_change_bug)
10661 ExecuteCustomElementAction(x, y, element, p);
10662 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10667 if (change->can_change)
10669 change_done = TRUE;
10670 change_done_any = TRUE;
10677 RECURSION_LOOP_DETECTION_END();
10679 return change_done_any;
10682 static boolean CheckElementChangeExt(int x, int y,
10684 int trigger_element,
10686 int trigger_player,
10689 boolean change_done = FALSE;
10692 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10693 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10696 if (Feld[x][y] == EL_BLOCKED)
10698 Blocked2Moving(x, y, &x, &y);
10699 element = Feld[x][y];
10702 // check if element has already changed or is about to change after moving
10703 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10704 Feld[x][y] != element) ||
10706 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10707 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10708 ChangePage[x][y] != -1)))
10711 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10713 for (p = 0; p < element_info[element].num_change_pages; p++)
10715 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10717 /* check trigger element for all events where the element that is checked
10718 for changing interacts with a directly adjacent element -- this is
10719 different to element changes that affect other elements to change on the
10720 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10721 boolean check_trigger_element =
10722 (trigger_event == CE_TOUCHING_X ||
10723 trigger_event == CE_HITTING_X ||
10724 trigger_event == CE_HIT_BY_X ||
10725 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10727 if (change->can_change_or_has_action &&
10728 change->has_event[trigger_event] &&
10729 change->trigger_side & trigger_side &&
10730 change->trigger_player & trigger_player &&
10731 (!check_trigger_element ||
10732 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10734 change->actual_trigger_element = trigger_element;
10735 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10736 change->actual_trigger_player_bits = trigger_player;
10737 change->actual_trigger_side = trigger_side;
10738 change->actual_trigger_ce_value = CustomValue[x][y];
10739 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10741 // special case: trigger element not at (x,y) position for some events
10742 if (check_trigger_element)
10754 { 0, 0 }, { 0, 0 }, { 0, 0 },
10758 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10759 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10761 change->actual_trigger_ce_value = CustomValue[xx][yy];
10762 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10765 if (change->can_change && !change_done)
10767 ChangeDelay[x][y] = 1;
10768 ChangeEvent[x][y] = trigger_event;
10770 HandleElementChange(x, y, p);
10772 change_done = TRUE;
10774 else if (change->has_action)
10776 ExecuteCustomElementAction(x, y, element, p);
10777 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10782 RECURSION_LOOP_DETECTION_END();
10784 return change_done;
10787 static void PlayPlayerSound(struct PlayerInfo *player)
10789 int jx = player->jx, jy = player->jy;
10790 int sound_element = player->artwork_element;
10791 int last_action = player->last_action_waiting;
10792 int action = player->action_waiting;
10794 if (player->is_waiting)
10796 if (action != last_action)
10797 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10799 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10803 if (action != last_action)
10804 StopSound(element_info[sound_element].sound[last_action]);
10806 if (last_action == ACTION_SLEEPING)
10807 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
10811 static void PlayAllPlayersSound(void)
10815 for (i = 0; i < MAX_PLAYERS; i++)
10816 if (stored_player[i].active)
10817 PlayPlayerSound(&stored_player[i]);
10820 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
10822 boolean last_waiting = player->is_waiting;
10823 int move_dir = player->MovDir;
10825 player->dir_waiting = move_dir;
10826 player->last_action_waiting = player->action_waiting;
10830 if (!last_waiting) // not waiting -> waiting
10832 player->is_waiting = TRUE;
10834 player->frame_counter_bored =
10836 game.player_boring_delay_fixed +
10837 GetSimpleRandom(game.player_boring_delay_random);
10838 player->frame_counter_sleeping =
10840 game.player_sleeping_delay_fixed +
10841 GetSimpleRandom(game.player_sleeping_delay_random);
10843 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
10846 if (game.player_sleeping_delay_fixed +
10847 game.player_sleeping_delay_random > 0 &&
10848 player->anim_delay_counter == 0 &&
10849 player->post_delay_counter == 0 &&
10850 FrameCounter >= player->frame_counter_sleeping)
10851 player->is_sleeping = TRUE;
10852 else if (game.player_boring_delay_fixed +
10853 game.player_boring_delay_random > 0 &&
10854 FrameCounter >= player->frame_counter_bored)
10855 player->is_bored = TRUE;
10857 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
10858 player->is_bored ? ACTION_BORING :
10861 if (player->is_sleeping && player->use_murphy)
10863 // special case for sleeping Murphy when leaning against non-free tile
10865 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
10866 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
10867 !IS_MOVING(player->jx - 1, player->jy)))
10868 move_dir = MV_LEFT;
10869 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
10870 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
10871 !IS_MOVING(player->jx + 1, player->jy)))
10872 move_dir = MV_RIGHT;
10874 player->is_sleeping = FALSE;
10876 player->dir_waiting = move_dir;
10879 if (player->is_sleeping)
10881 if (player->num_special_action_sleeping > 0)
10883 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10885 int last_special_action = player->special_action_sleeping;
10886 int num_special_action = player->num_special_action_sleeping;
10887 int special_action =
10888 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
10889 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
10890 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
10891 last_special_action + 1 : ACTION_SLEEPING);
10892 int special_graphic =
10893 el_act_dir2img(player->artwork_element, special_action, move_dir);
10895 player->anim_delay_counter =
10896 graphic_info[special_graphic].anim_delay_fixed +
10897 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10898 player->post_delay_counter =
10899 graphic_info[special_graphic].post_delay_fixed +
10900 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10902 player->special_action_sleeping = special_action;
10905 if (player->anim_delay_counter > 0)
10907 player->action_waiting = player->special_action_sleeping;
10908 player->anim_delay_counter--;
10910 else if (player->post_delay_counter > 0)
10912 player->post_delay_counter--;
10916 else if (player->is_bored)
10918 if (player->num_special_action_bored > 0)
10920 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10922 int special_action =
10923 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
10924 int special_graphic =
10925 el_act_dir2img(player->artwork_element, special_action, move_dir);
10927 player->anim_delay_counter =
10928 graphic_info[special_graphic].anim_delay_fixed +
10929 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10930 player->post_delay_counter =
10931 graphic_info[special_graphic].post_delay_fixed +
10932 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10934 player->special_action_bored = special_action;
10937 if (player->anim_delay_counter > 0)
10939 player->action_waiting = player->special_action_bored;
10940 player->anim_delay_counter--;
10942 else if (player->post_delay_counter > 0)
10944 player->post_delay_counter--;
10949 else if (last_waiting) // waiting -> not waiting
10951 player->is_waiting = FALSE;
10952 player->is_bored = FALSE;
10953 player->is_sleeping = FALSE;
10955 player->frame_counter_bored = -1;
10956 player->frame_counter_sleeping = -1;
10958 player->anim_delay_counter = 0;
10959 player->post_delay_counter = 0;
10961 player->dir_waiting = player->MovDir;
10962 player->action_waiting = ACTION_DEFAULT;
10964 player->special_action_bored = ACTION_DEFAULT;
10965 player->special_action_sleeping = ACTION_DEFAULT;
10969 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
10971 if ((!player->is_moving && player->was_moving) ||
10972 (player->MovPos == 0 && player->was_moving) ||
10973 (player->is_snapping && !player->was_snapping) ||
10974 (player->is_dropping && !player->was_dropping))
10976 if (!CheckSaveEngineSnapshotToList())
10979 player->was_moving = FALSE;
10980 player->was_snapping = TRUE;
10981 player->was_dropping = TRUE;
10985 if (player->is_moving)
10986 player->was_moving = TRUE;
10988 if (!player->is_snapping)
10989 player->was_snapping = FALSE;
10991 if (!player->is_dropping)
10992 player->was_dropping = FALSE;
10996 static void CheckSingleStepMode(struct PlayerInfo *player)
10998 if (tape.single_step && tape.recording && !tape.pausing)
11000 /* as it is called "single step mode", just return to pause mode when the
11001 player stopped moving after one tile (or never starts moving at all) */
11002 if (!player->is_moving &&
11003 !player->is_pushing &&
11004 !player->is_dropping_pressed)
11006 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11007 SnapField(player, 0, 0); // stop snapping
11011 CheckSaveEngineSnapshot(player);
11014 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11016 int left = player_action & JOY_LEFT;
11017 int right = player_action & JOY_RIGHT;
11018 int up = player_action & JOY_UP;
11019 int down = player_action & JOY_DOWN;
11020 int button1 = player_action & JOY_BUTTON_1;
11021 int button2 = player_action & JOY_BUTTON_2;
11022 int dx = (left ? -1 : right ? 1 : 0);
11023 int dy = (up ? -1 : down ? 1 : 0);
11025 if (!player->active || tape.pausing)
11031 SnapField(player, dx, dy);
11035 DropElement(player);
11037 MovePlayer(player, dx, dy);
11040 CheckSingleStepMode(player);
11042 SetPlayerWaiting(player, FALSE);
11044 return player_action;
11048 // no actions for this player (no input at player's configured device)
11050 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11051 SnapField(player, 0, 0);
11052 CheckGravityMovementWhenNotMoving(player);
11054 if (player->MovPos == 0)
11055 SetPlayerWaiting(player, TRUE);
11057 if (player->MovPos == 0) // needed for tape.playing
11058 player->is_moving = FALSE;
11060 player->is_dropping = FALSE;
11061 player->is_dropping_pressed = FALSE;
11062 player->drop_pressed_delay = 0;
11064 CheckSingleStepMode(player);
11070 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11073 if (!tape.use_mouse)
11076 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11077 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11078 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11081 static void SetTapeActionFromMouseAction(byte *tape_action,
11082 struct MouseActionInfo *mouse_action)
11084 if (!tape.use_mouse)
11087 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11088 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11089 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11092 static void CheckLevelSolved(void)
11094 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11096 if (level.native_em_level->lev->home == 0) // all players at home
11098 PlayerWins(local_player);
11100 AllPlayersGone = TRUE;
11102 level.native_em_level->lev->home = -1;
11105 if (level.native_em_level->ply[0]->alive == 0 &&
11106 level.native_em_level->ply[1]->alive == 0 &&
11107 level.native_em_level->ply[2]->alive == 0 &&
11108 level.native_em_level->ply[3]->alive == 0) // all dead
11109 AllPlayersGone = TRUE;
11111 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11113 if (game_sp.LevelSolved &&
11114 !game_sp.GameOver) // game won
11116 PlayerWins(local_player);
11118 game_sp.GameOver = TRUE;
11120 AllPlayersGone = TRUE;
11123 if (game_sp.GameOver) // game lost
11124 AllPlayersGone = TRUE;
11126 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11128 if (game_mm.level_solved &&
11129 !game_mm.game_over) // game won
11131 PlayerWins(local_player);
11133 game_mm.game_over = TRUE;
11135 AllPlayersGone = TRUE;
11138 if (game_mm.game_over) // game lost
11139 AllPlayersGone = TRUE;
11143 static void CheckLevelTime(void)
11147 if (TimeFrames >= FRAMES_PER_SECOND)
11152 for (i = 0; i < MAX_PLAYERS; i++)
11154 struct PlayerInfo *player = &stored_player[i];
11156 if (SHIELD_ON(player))
11158 player->shield_normal_time_left--;
11160 if (player->shield_deadly_time_left > 0)
11161 player->shield_deadly_time_left--;
11165 if (!local_player->LevelSolved && !level.use_step_counter)
11173 if (TimeLeft <= 10 && setup.time_limit)
11174 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11176 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11177 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11179 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11181 if (!TimeLeft && setup.time_limit)
11183 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11184 level.native_em_level->lev->killed_out_of_time = TRUE;
11186 for (i = 0; i < MAX_PLAYERS; i++)
11187 KillPlayer(&stored_player[i]);
11190 else if (game.no_time_limit && !AllPlayersGone) // level w/o time limit
11192 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11195 level.native_em_level->lev->time =
11196 (game.no_time_limit ? TimePlayed : TimeLeft);
11199 if (tape.recording || tape.playing)
11200 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11203 if (tape.recording || tape.playing)
11204 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11206 UpdateAndDisplayGameControlValues();
11209 void AdvanceFrameAndPlayerCounters(int player_nr)
11213 // advance frame counters (global frame counter and time frame counter)
11217 // advance player counters (counters for move delay, move animation etc.)
11218 for (i = 0; i < MAX_PLAYERS; i++)
11220 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11221 int move_delay_value = stored_player[i].move_delay_value;
11222 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11224 if (!advance_player_counters) // not all players may be affected
11227 if (move_frames == 0) // less than one move per game frame
11229 int stepsize = TILEX / move_delay_value;
11230 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11231 int count = (stored_player[i].is_moving ?
11232 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11234 if (count % delay == 0)
11238 stored_player[i].Frame += move_frames;
11240 if (stored_player[i].MovPos != 0)
11241 stored_player[i].StepFrame += move_frames;
11243 if (stored_player[i].move_delay > 0)
11244 stored_player[i].move_delay--;
11246 // due to bugs in previous versions, counter must count up, not down
11247 if (stored_player[i].push_delay != -1)
11248 stored_player[i].push_delay++;
11250 if (stored_player[i].drop_delay > 0)
11251 stored_player[i].drop_delay--;
11253 if (stored_player[i].is_dropping_pressed)
11254 stored_player[i].drop_pressed_delay++;
11258 void StartGameActions(boolean init_network_game, boolean record_tape,
11261 unsigned int new_random_seed = InitRND(random_seed);
11264 TapeStartRecording(new_random_seed);
11266 if (init_network_game)
11268 SendToServer_LevelFile();
11269 SendToServer_StartPlaying();
11277 static void GameActionsExt(void)
11280 static unsigned int game_frame_delay = 0;
11282 unsigned int game_frame_delay_value;
11283 byte *recorded_player_action;
11284 byte summarized_player_action = 0;
11285 byte tape_action[MAX_PLAYERS];
11288 // detect endless loops, caused by custom element programming
11289 if (recursion_loop_detected && recursion_loop_depth == 0)
11291 char *message = getStringCat3("Internal Error! Element ",
11292 EL_NAME(recursion_loop_element),
11293 " caused endless loop! Quit the game?");
11295 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11296 EL_NAME(recursion_loop_element));
11298 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11300 recursion_loop_detected = FALSE; // if game should be continued
11307 if (game.restart_level)
11308 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11310 CheckLevelSolved();
11312 if (local_player->LevelSolved && !local_player->LevelSolved_GameEnd)
11315 if (AllPlayersGone && !TAPE_IS_STOPPED(tape))
11318 if (game_status != GAME_MODE_PLAYING) // status might have changed
11321 game_frame_delay_value =
11322 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11324 if (tape.playing && tape.warp_forward && !tape.pausing)
11325 game_frame_delay_value = 0;
11327 SetVideoFrameDelay(game_frame_delay_value);
11331 // ---------- main game synchronization point ----------
11333 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11335 printf("::: skip == %d\n", skip);
11338 // ---------- main game synchronization point ----------
11340 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11344 if (network_playing && !network_player_action_received)
11346 // try to get network player actions in time
11348 // last chance to get network player actions without main loop delay
11349 HandleNetworking();
11351 // game was quit by network peer
11352 if (game_status != GAME_MODE_PLAYING)
11355 // check if network player actions still missing and game still running
11356 if (!network_player_action_received && !checkGameEnded())
11357 return; // failed to get network player actions in time
11359 // do not yet reset "network_player_action_received" (for tape.pausing)
11365 // at this point we know that we really continue executing the game
11367 network_player_action_received = FALSE;
11369 // when playing tape, read previously recorded player input from tape data
11370 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11372 local_player->effective_mouse_action = local_player->mouse_action;
11374 if (recorded_player_action != NULL)
11375 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11376 recorded_player_action);
11378 // TapePlayAction() may return NULL when toggling to "pause before death"
11382 if (tape.set_centered_player)
11384 game.centered_player_nr_next = tape.centered_player_nr_next;
11385 game.set_centered_player = TRUE;
11388 for (i = 0; i < MAX_PLAYERS; i++)
11390 summarized_player_action |= stored_player[i].action;
11392 if (!network_playing && (game.team_mode || tape.playing))
11393 stored_player[i].effective_action = stored_player[i].action;
11396 if (network_playing && !checkGameEnded())
11397 SendToServer_MovePlayer(summarized_player_action);
11399 // summarize all actions at local players mapped input device position
11400 // (this allows using different input devices in single player mode)
11401 if (!network.enabled && !game.team_mode)
11402 stored_player[map_player_action[local_player->index_nr]].effective_action =
11403 summarized_player_action;
11405 if (tape.recording &&
11407 setup.input_on_focus &&
11408 game.centered_player_nr != -1)
11410 for (i = 0; i < MAX_PLAYERS; i++)
11411 stored_player[i].effective_action =
11412 (i == game.centered_player_nr ? summarized_player_action : 0);
11415 if (recorded_player_action != NULL)
11416 for (i = 0; i < MAX_PLAYERS; i++)
11417 stored_player[i].effective_action = recorded_player_action[i];
11419 for (i = 0; i < MAX_PLAYERS; i++)
11421 tape_action[i] = stored_player[i].effective_action;
11423 /* (this may happen in the RND game engine if a player was not present on
11424 the playfield on level start, but appeared later from a custom element */
11425 if (setup.team_mode &&
11428 !tape.player_participates[i])
11429 tape.player_participates[i] = TRUE;
11432 SetTapeActionFromMouseAction(tape_action,
11433 &local_player->effective_mouse_action);
11435 // only record actions from input devices, but not programmed actions
11436 if (tape.recording)
11437 TapeRecordAction(tape_action);
11439 #if USE_NEW_PLAYER_ASSIGNMENTS
11440 // !!! also map player actions in single player mode !!!
11441 // if (game.team_mode)
11444 byte mapped_action[MAX_PLAYERS];
11446 #if DEBUG_PLAYER_ACTIONS
11448 for (i = 0; i < MAX_PLAYERS; i++)
11449 printf(" %d, ", stored_player[i].effective_action);
11452 for (i = 0; i < MAX_PLAYERS; i++)
11453 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11455 for (i = 0; i < MAX_PLAYERS; i++)
11456 stored_player[i].effective_action = mapped_action[i];
11458 #if DEBUG_PLAYER_ACTIONS
11460 for (i = 0; i < MAX_PLAYERS; i++)
11461 printf(" %d, ", stored_player[i].effective_action);
11465 #if DEBUG_PLAYER_ACTIONS
11469 for (i = 0; i < MAX_PLAYERS; i++)
11470 printf(" %d, ", stored_player[i].effective_action);
11476 for (i = 0; i < MAX_PLAYERS; i++)
11478 // allow engine snapshot in case of changed movement attempt
11479 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11480 (stored_player[i].effective_action & KEY_MOTION))
11481 game.snapshot.changed_action = TRUE;
11483 // allow engine snapshot in case of snapping/dropping attempt
11484 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11485 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11486 game.snapshot.changed_action = TRUE;
11488 game.snapshot.last_action[i] = stored_player[i].effective_action;
11491 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11493 GameActions_EM_Main();
11495 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11497 GameActions_SP_Main();
11499 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11501 GameActions_MM_Main();
11505 GameActions_RND_Main();
11508 BlitScreenToBitmap(backbuffer);
11510 CheckLevelSolved();
11513 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11515 if (global.show_frames_per_second)
11517 static unsigned int fps_counter = 0;
11518 static int fps_frames = 0;
11519 unsigned int fps_delay_ms = Counter() - fps_counter;
11523 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11525 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11528 fps_counter = Counter();
11530 // always draw FPS to screen after FPS value was updated
11531 redraw_mask |= REDRAW_FPS;
11534 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11535 if (GetDrawDeactivationMask() == REDRAW_NONE)
11536 redraw_mask |= REDRAW_FPS;
11540 static void GameActions_CheckSaveEngineSnapshot(void)
11542 if (!game.snapshot.save_snapshot)
11545 // clear flag for saving snapshot _before_ saving snapshot
11546 game.snapshot.save_snapshot = FALSE;
11548 SaveEngineSnapshotToList();
11551 void GameActions(void)
11555 GameActions_CheckSaveEngineSnapshot();
11558 void GameActions_EM_Main(void)
11560 byte effective_action[MAX_PLAYERS];
11561 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11564 for (i = 0; i < MAX_PLAYERS; i++)
11565 effective_action[i] = stored_player[i].effective_action;
11567 GameActions_EM(effective_action, warp_mode);
11570 void GameActions_SP_Main(void)
11572 byte effective_action[MAX_PLAYERS];
11573 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11576 for (i = 0; i < MAX_PLAYERS; i++)
11577 effective_action[i] = stored_player[i].effective_action;
11579 GameActions_SP(effective_action, warp_mode);
11581 for (i = 0; i < MAX_PLAYERS; i++)
11583 if (stored_player[i].force_dropping)
11584 stored_player[i].action |= KEY_BUTTON_DROP;
11586 stored_player[i].force_dropping = FALSE;
11590 void GameActions_MM_Main(void)
11592 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11594 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11597 void GameActions_RND_Main(void)
11602 void GameActions_RND(void)
11604 int magic_wall_x = 0, magic_wall_y = 0;
11605 int i, x, y, element, graphic, last_gfx_frame;
11607 InitPlayfieldScanModeVars();
11609 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11611 SCAN_PLAYFIELD(x, y)
11613 ChangeCount[x][y] = 0;
11614 ChangeEvent[x][y] = -1;
11618 if (game.set_centered_player)
11620 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11622 // switching to "all players" only possible if all players fit to screen
11623 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11625 game.centered_player_nr_next = game.centered_player_nr;
11626 game.set_centered_player = FALSE;
11629 // do not switch focus to non-existing (or non-active) player
11630 if (game.centered_player_nr_next >= 0 &&
11631 !stored_player[game.centered_player_nr_next].active)
11633 game.centered_player_nr_next = game.centered_player_nr;
11634 game.set_centered_player = FALSE;
11638 if (game.set_centered_player &&
11639 ScreenMovPos == 0) // screen currently aligned at tile position
11643 if (game.centered_player_nr_next == -1)
11645 setScreenCenteredToAllPlayers(&sx, &sy);
11649 sx = stored_player[game.centered_player_nr_next].jx;
11650 sy = stored_player[game.centered_player_nr_next].jy;
11653 game.centered_player_nr = game.centered_player_nr_next;
11654 game.set_centered_player = FALSE;
11656 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11657 DrawGameDoorValues();
11660 for (i = 0; i < MAX_PLAYERS; i++)
11662 int actual_player_action = stored_player[i].effective_action;
11665 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11666 - rnd_equinox_tetrachloride 048
11667 - rnd_equinox_tetrachloride_ii 096
11668 - rnd_emanuel_schmieg 002
11669 - doctor_sloan_ww 001, 020
11671 if (stored_player[i].MovPos == 0)
11672 CheckGravityMovement(&stored_player[i]);
11675 // overwrite programmed action with tape action
11676 if (stored_player[i].programmed_action)
11677 actual_player_action = stored_player[i].programmed_action;
11679 PlayerActions(&stored_player[i], actual_player_action);
11681 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11684 ScrollScreen(NULL, SCROLL_GO_ON);
11686 /* for backwards compatibility, the following code emulates a fixed bug that
11687 occured when pushing elements (causing elements that just made their last
11688 pushing step to already (if possible) make their first falling step in the
11689 same game frame, which is bad); this code is also needed to use the famous
11690 "spring push bug" which is used in older levels and might be wanted to be
11691 used also in newer levels, but in this case the buggy pushing code is only
11692 affecting the "spring" element and no other elements */
11694 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11696 for (i = 0; i < MAX_PLAYERS; i++)
11698 struct PlayerInfo *player = &stored_player[i];
11699 int x = player->jx;
11700 int y = player->jy;
11702 if (player->active && player->is_pushing && player->is_moving &&
11704 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11705 Feld[x][y] == EL_SPRING))
11707 ContinueMoving(x, y);
11709 // continue moving after pushing (this is actually a bug)
11710 if (!IS_MOVING(x, y))
11711 Stop[x][y] = FALSE;
11716 SCAN_PLAYFIELD(x, y)
11718 Last[x][y] = Feld[x][y];
11720 ChangeCount[x][y] = 0;
11721 ChangeEvent[x][y] = -1;
11723 // this must be handled before main playfield loop
11724 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11727 if (MovDelay[x][y] <= 0)
11731 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11734 if (MovDelay[x][y] <= 0)
11737 TEST_DrawLevelField(x, y);
11739 TestIfElementTouchesCustomElement(x, y); // for empty space
11744 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11746 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11747 printf("GameActions(): This should never happen!\n");
11749 ChangePage[x][y] = -1;
11753 Stop[x][y] = FALSE;
11754 if (WasJustMoving[x][y] > 0)
11755 WasJustMoving[x][y]--;
11756 if (WasJustFalling[x][y] > 0)
11757 WasJustFalling[x][y]--;
11758 if (CheckCollision[x][y] > 0)
11759 CheckCollision[x][y]--;
11760 if (CheckImpact[x][y] > 0)
11761 CheckImpact[x][y]--;
11765 /* reset finished pushing action (not done in ContinueMoving() to allow
11766 continuous pushing animation for elements with zero push delay) */
11767 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11769 ResetGfxAnimation(x, y);
11770 TEST_DrawLevelField(x, y);
11774 if (IS_BLOCKED(x, y))
11778 Blocked2Moving(x, y, &oldx, &oldy);
11779 if (!IS_MOVING(oldx, oldy))
11781 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11782 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11783 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11784 printf("GameActions(): This should never happen!\n");
11790 SCAN_PLAYFIELD(x, y)
11792 element = Feld[x][y];
11793 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11794 last_gfx_frame = GfxFrame[x][y];
11796 ResetGfxFrame(x, y);
11798 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
11799 DrawLevelGraphicAnimation(x, y, graphic);
11801 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
11802 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
11803 ResetRandomAnimationValue(x, y);
11805 SetRandomAnimationValue(x, y);
11807 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
11809 if (IS_INACTIVE(element))
11811 if (IS_ANIMATED(graphic))
11812 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11817 // this may take place after moving, so 'element' may have changed
11818 if (IS_CHANGING(x, y) &&
11819 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
11821 int page = element_info[element].event_page_nr[CE_DELAY];
11823 HandleElementChange(x, y, page);
11825 element = Feld[x][y];
11826 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11829 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
11833 element = Feld[x][y];
11834 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11836 if (IS_ANIMATED(graphic) &&
11837 !IS_MOVING(x, y) &&
11839 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11841 if (IS_GEM(element) || element == EL_SP_INFOTRON)
11842 TEST_DrawTwinkleOnField(x, y);
11844 else if (element == EL_ACID)
11847 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11849 else if ((element == EL_EXIT_OPEN ||
11850 element == EL_EM_EXIT_OPEN ||
11851 element == EL_SP_EXIT_OPEN ||
11852 element == EL_STEEL_EXIT_OPEN ||
11853 element == EL_EM_STEEL_EXIT_OPEN ||
11854 element == EL_SP_TERMINAL ||
11855 element == EL_SP_TERMINAL_ACTIVE ||
11856 element == EL_EXTRA_TIME ||
11857 element == EL_SHIELD_NORMAL ||
11858 element == EL_SHIELD_DEADLY) &&
11859 IS_ANIMATED(graphic))
11860 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11861 else if (IS_MOVING(x, y))
11862 ContinueMoving(x, y);
11863 else if (IS_ACTIVE_BOMB(element))
11864 CheckDynamite(x, y);
11865 else if (element == EL_AMOEBA_GROWING)
11866 AmoebeWaechst(x, y);
11867 else if (element == EL_AMOEBA_SHRINKING)
11868 AmoebaDisappearing(x, y);
11870 #if !USE_NEW_AMOEBA_CODE
11871 else if (IS_AMOEBALIVE(element))
11872 AmoebeAbleger(x, y);
11875 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
11877 else if (element == EL_EXIT_CLOSED)
11879 else if (element == EL_EM_EXIT_CLOSED)
11881 else if (element == EL_STEEL_EXIT_CLOSED)
11882 CheckExitSteel(x, y);
11883 else if (element == EL_EM_STEEL_EXIT_CLOSED)
11884 CheckExitSteelEM(x, y);
11885 else if (element == EL_SP_EXIT_CLOSED)
11887 else if (element == EL_EXPANDABLE_WALL_GROWING ||
11888 element == EL_EXPANDABLE_STEELWALL_GROWING)
11889 MauerWaechst(x, y);
11890 else if (element == EL_EXPANDABLE_WALL ||
11891 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
11892 element == EL_EXPANDABLE_WALL_VERTICAL ||
11893 element == EL_EXPANDABLE_WALL_ANY ||
11894 element == EL_BD_EXPANDABLE_WALL)
11895 MauerAbleger(x, y);
11896 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
11897 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
11898 element == EL_EXPANDABLE_STEELWALL_ANY)
11899 MauerAblegerStahl(x, y);
11900 else if (element == EL_FLAMES)
11901 CheckForDragon(x, y);
11902 else if (element == EL_EXPLOSION)
11903 ; // drawing of correct explosion animation is handled separately
11904 else if (element == EL_ELEMENT_SNAPPING ||
11905 element == EL_DIAGONAL_SHRINKING ||
11906 element == EL_DIAGONAL_GROWING)
11908 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
11910 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11912 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
11913 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11915 if (IS_BELT_ACTIVE(element))
11916 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
11918 if (game.magic_wall_active)
11920 int jx = local_player->jx, jy = local_player->jy;
11922 // play the element sound at the position nearest to the player
11923 if ((element == EL_MAGIC_WALL_FULL ||
11924 element == EL_MAGIC_WALL_ACTIVE ||
11925 element == EL_MAGIC_WALL_EMPTYING ||
11926 element == EL_BD_MAGIC_WALL_FULL ||
11927 element == EL_BD_MAGIC_WALL_ACTIVE ||
11928 element == EL_BD_MAGIC_WALL_EMPTYING ||
11929 element == EL_DC_MAGIC_WALL_FULL ||
11930 element == EL_DC_MAGIC_WALL_ACTIVE ||
11931 element == EL_DC_MAGIC_WALL_EMPTYING) &&
11932 ABS(x-jx) + ABS(y-jy) < ABS(magic_wall_x-jx) + ABS(magic_wall_y-jy))
11940 #if USE_NEW_AMOEBA_CODE
11941 // new experimental amoeba growth stuff
11942 if (!(FrameCounter % 8))
11944 static unsigned int random = 1684108901;
11946 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
11948 x = RND(lev_fieldx);
11949 y = RND(lev_fieldy);
11950 element = Feld[x][y];
11952 if (!IS_PLAYER(x,y) &&
11953 (element == EL_EMPTY ||
11954 CAN_GROW_INTO(element) ||
11955 element == EL_QUICKSAND_EMPTY ||
11956 element == EL_QUICKSAND_FAST_EMPTY ||
11957 element == EL_ACID_SPLASH_LEFT ||
11958 element == EL_ACID_SPLASH_RIGHT))
11960 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
11961 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
11962 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
11963 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
11964 Feld[x][y] = EL_AMOEBA_DROP;
11967 random = random * 129 + 1;
11972 game.explosions_delayed = FALSE;
11974 SCAN_PLAYFIELD(x, y)
11976 element = Feld[x][y];
11978 if (ExplodeField[x][y])
11979 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
11980 else if (element == EL_EXPLOSION)
11981 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
11983 ExplodeField[x][y] = EX_TYPE_NONE;
11986 game.explosions_delayed = TRUE;
11988 if (game.magic_wall_active)
11990 if (!(game.magic_wall_time_left % 4))
11992 int element = Feld[magic_wall_x][magic_wall_y];
11994 if (element == EL_BD_MAGIC_WALL_FULL ||
11995 element == EL_BD_MAGIC_WALL_ACTIVE ||
11996 element == EL_BD_MAGIC_WALL_EMPTYING)
11997 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
11998 else if (element == EL_DC_MAGIC_WALL_FULL ||
11999 element == EL_DC_MAGIC_WALL_ACTIVE ||
12000 element == EL_DC_MAGIC_WALL_EMPTYING)
12001 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12003 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12006 if (game.magic_wall_time_left > 0)
12008 game.magic_wall_time_left--;
12010 if (!game.magic_wall_time_left)
12012 SCAN_PLAYFIELD(x, y)
12014 element = Feld[x][y];
12016 if (element == EL_MAGIC_WALL_ACTIVE ||
12017 element == EL_MAGIC_WALL_FULL)
12019 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12020 TEST_DrawLevelField(x, y);
12022 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12023 element == EL_BD_MAGIC_WALL_FULL)
12025 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12026 TEST_DrawLevelField(x, y);
12028 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12029 element == EL_DC_MAGIC_WALL_FULL)
12031 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12032 TEST_DrawLevelField(x, y);
12036 game.magic_wall_active = FALSE;
12041 if (game.light_time_left > 0)
12043 game.light_time_left--;
12045 if (game.light_time_left == 0)
12046 RedrawAllLightSwitchesAndInvisibleElements();
12049 if (game.timegate_time_left > 0)
12051 game.timegate_time_left--;
12053 if (game.timegate_time_left == 0)
12054 CloseAllOpenTimegates();
12057 if (game.lenses_time_left > 0)
12059 game.lenses_time_left--;
12061 if (game.lenses_time_left == 0)
12062 RedrawAllInvisibleElementsForLenses();
12065 if (game.magnify_time_left > 0)
12067 game.magnify_time_left--;
12069 if (game.magnify_time_left == 0)
12070 RedrawAllInvisibleElementsForMagnifier();
12073 for (i = 0; i < MAX_PLAYERS; i++)
12075 struct PlayerInfo *player = &stored_player[i];
12077 if (SHIELD_ON(player))
12079 if (player->shield_deadly_time_left)
12080 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12081 else if (player->shield_normal_time_left)
12082 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12086 #if USE_DELAYED_GFX_REDRAW
12087 SCAN_PLAYFIELD(x, y)
12089 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12091 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12092 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12094 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12095 DrawLevelField(x, y);
12097 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12098 DrawLevelFieldCrumbled(x, y);
12100 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12101 DrawLevelFieldCrumbledNeighbours(x, y);
12103 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12104 DrawTwinkleOnField(x, y);
12107 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12112 PlayAllPlayersSound();
12114 if (local_player->show_envelope != 0 && local_player->MovPos == 0)
12116 ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
12118 local_player->show_envelope = 0;
12121 // use random number generator in every frame to make it less predictable
12122 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12126 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12128 int min_x = x, min_y = y, max_x = x, max_y = y;
12131 for (i = 0; i < MAX_PLAYERS; i++)
12133 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12135 if (!stored_player[i].active || &stored_player[i] == player)
12138 min_x = MIN(min_x, jx);
12139 min_y = MIN(min_y, jy);
12140 max_x = MAX(max_x, jx);
12141 max_y = MAX(max_y, jy);
12144 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12147 static boolean AllPlayersInVisibleScreen(void)
12151 for (i = 0; i < MAX_PLAYERS; i++)
12153 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12155 if (!stored_player[i].active)
12158 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12165 void ScrollLevel(int dx, int dy)
12167 int scroll_offset = 2 * TILEX_VAR;
12170 BlitBitmap(drawto_field, drawto_field,
12171 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12172 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12173 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12174 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12175 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12176 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12180 x = (dx == 1 ? BX1 : BX2);
12181 for (y = BY1; y <= BY2; y++)
12182 DrawScreenField(x, y);
12187 y = (dy == 1 ? BY1 : BY2);
12188 for (x = BX1; x <= BX2; x++)
12189 DrawScreenField(x, y);
12192 redraw_mask |= REDRAW_FIELD;
12195 static boolean canFallDown(struct PlayerInfo *player)
12197 int jx = player->jx, jy = player->jy;
12199 return (IN_LEV_FIELD(jx, jy + 1) &&
12200 (IS_FREE(jx, jy + 1) ||
12201 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12202 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12203 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12206 static boolean canPassField(int x, int y, int move_dir)
12208 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12209 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12210 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12211 int nextx = x + dx;
12212 int nexty = y + dy;
12213 int element = Feld[x][y];
12215 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12216 !CAN_MOVE(element) &&
12217 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12218 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12219 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12222 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12224 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12225 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12226 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12230 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12231 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12232 (IS_DIGGABLE(Feld[newx][newy]) ||
12233 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12234 canPassField(newx, newy, move_dir)));
12237 static void CheckGravityMovement(struct PlayerInfo *player)
12239 if (player->gravity && !player->programmed_action)
12241 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12242 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12243 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12244 int jx = player->jx, jy = player->jy;
12245 boolean player_is_moving_to_valid_field =
12246 (!player_is_snapping &&
12247 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12248 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12249 boolean player_can_fall_down = canFallDown(player);
12251 if (player_can_fall_down &&
12252 !player_is_moving_to_valid_field)
12253 player->programmed_action = MV_DOWN;
12257 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12259 return CheckGravityMovement(player);
12261 if (player->gravity && !player->programmed_action)
12263 int jx = player->jx, jy = player->jy;
12264 boolean field_under_player_is_free =
12265 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12266 boolean player_is_standing_on_valid_field =
12267 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12268 (IS_WALKABLE(Feld[jx][jy]) &&
12269 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12271 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12272 player->programmed_action = MV_DOWN;
12277 MovePlayerOneStep()
12278 -----------------------------------------------------------------------------
12279 dx, dy: direction (non-diagonal) to try to move the player to
12280 real_dx, real_dy: direction as read from input device (can be diagonal)
12283 boolean MovePlayerOneStep(struct PlayerInfo *player,
12284 int dx, int dy, int real_dx, int real_dy)
12286 int jx = player->jx, jy = player->jy;
12287 int new_jx = jx + dx, new_jy = jy + dy;
12289 boolean player_can_move = !player->cannot_move;
12291 if (!player->active || (!dx && !dy))
12292 return MP_NO_ACTION;
12294 player->MovDir = (dx < 0 ? MV_LEFT :
12295 dx > 0 ? MV_RIGHT :
12297 dy > 0 ? MV_DOWN : MV_NONE);
12299 if (!IN_LEV_FIELD(new_jx, new_jy))
12300 return MP_NO_ACTION;
12302 if (!player_can_move)
12304 if (player->MovPos == 0)
12306 player->is_moving = FALSE;
12307 player->is_digging = FALSE;
12308 player->is_collecting = FALSE;
12309 player->is_snapping = FALSE;
12310 player->is_pushing = FALSE;
12314 if (!network.enabled && game.centered_player_nr == -1 &&
12315 !AllPlayersInSight(player, new_jx, new_jy))
12316 return MP_NO_ACTION;
12318 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12319 if (can_move != MP_MOVING)
12322 // check if DigField() has caused relocation of the player
12323 if (player->jx != jx || player->jy != jy)
12324 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12326 StorePlayer[jx][jy] = 0;
12327 player->last_jx = jx;
12328 player->last_jy = jy;
12329 player->jx = new_jx;
12330 player->jy = new_jy;
12331 StorePlayer[new_jx][new_jy] = player->element_nr;
12333 if (player->move_delay_value_next != -1)
12335 player->move_delay_value = player->move_delay_value_next;
12336 player->move_delay_value_next = -1;
12340 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12342 player->step_counter++;
12344 PlayerVisit[jx][jy] = FrameCounter;
12346 player->is_moving = TRUE;
12349 // should better be called in MovePlayer(), but this breaks some tapes
12350 ScrollPlayer(player, SCROLL_INIT);
12356 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12358 int jx = player->jx, jy = player->jy;
12359 int old_jx = jx, old_jy = jy;
12360 int moved = MP_NO_ACTION;
12362 if (!player->active)
12367 if (player->MovPos == 0)
12369 player->is_moving = FALSE;
12370 player->is_digging = FALSE;
12371 player->is_collecting = FALSE;
12372 player->is_snapping = FALSE;
12373 player->is_pushing = FALSE;
12379 if (player->move_delay > 0)
12382 player->move_delay = -1; // set to "uninitialized" value
12384 // store if player is automatically moved to next field
12385 player->is_auto_moving = (player->programmed_action != MV_NONE);
12387 // remove the last programmed player action
12388 player->programmed_action = 0;
12390 if (player->MovPos)
12392 // should only happen if pre-1.2 tape recordings are played
12393 // this is only for backward compatibility
12395 int original_move_delay_value = player->move_delay_value;
12398 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12402 // scroll remaining steps with finest movement resolution
12403 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12405 while (player->MovPos)
12407 ScrollPlayer(player, SCROLL_GO_ON);
12408 ScrollScreen(NULL, SCROLL_GO_ON);
12410 AdvanceFrameAndPlayerCounters(player->index_nr);
12413 BackToFront_WithFrameDelay(0);
12416 player->move_delay_value = original_move_delay_value;
12419 player->is_active = FALSE;
12421 if (player->last_move_dir & MV_HORIZONTAL)
12423 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12424 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12428 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12429 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12432 if (!moved && !player->is_active)
12434 player->is_moving = FALSE;
12435 player->is_digging = FALSE;
12436 player->is_collecting = FALSE;
12437 player->is_snapping = FALSE;
12438 player->is_pushing = FALSE;
12444 if (moved & MP_MOVING && !ScreenMovPos &&
12445 (player->index_nr == game.centered_player_nr ||
12446 game.centered_player_nr == -1))
12448 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12449 int offset = game.scroll_delay_value;
12451 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12453 // actual player has left the screen -- scroll in that direction
12454 if (jx != old_jx) // player has moved horizontally
12455 scroll_x += (jx - old_jx);
12456 else // player has moved vertically
12457 scroll_y += (jy - old_jy);
12461 if (jx != old_jx) // player has moved horizontally
12463 if ((player->MovDir == MV_LEFT && scroll_x > jx - MIDPOSX + offset) ||
12464 (player->MovDir == MV_RIGHT && scroll_x < jx - MIDPOSX - offset))
12465 scroll_x = jx-MIDPOSX + (scroll_x < jx-MIDPOSX ? -offset : +offset);
12467 // don't scroll over playfield boundaries
12468 if (scroll_x < SBX_Left || scroll_x > SBX_Right)
12469 scroll_x = (scroll_x < SBX_Left ? SBX_Left : SBX_Right);
12471 // don't scroll more than one field at a time
12472 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12474 // don't scroll against the player's moving direction
12475 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12476 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12477 scroll_x = old_scroll_x;
12479 else // player has moved vertically
12481 if ((player->MovDir == MV_UP && scroll_y > jy - MIDPOSY + offset) ||
12482 (player->MovDir == MV_DOWN && scroll_y < jy - MIDPOSY - offset))
12483 scroll_y = jy-MIDPOSY + (scroll_y < jy-MIDPOSY ? -offset : +offset);
12485 // don't scroll over playfield boundaries
12486 if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
12487 scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
12489 // don't scroll more than one field at a time
12490 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12492 // don't scroll against the player's moving direction
12493 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12494 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12495 scroll_y = old_scroll_y;
12499 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12501 if (!network.enabled && game.centered_player_nr == -1 &&
12502 !AllPlayersInVisibleScreen())
12504 scroll_x = old_scroll_x;
12505 scroll_y = old_scroll_y;
12509 ScrollScreen(player, SCROLL_INIT);
12510 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12515 player->StepFrame = 0;
12517 if (moved & MP_MOVING)
12519 if (old_jx != jx && old_jy == jy)
12520 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12521 else if (old_jx == jx && old_jy != jy)
12522 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12524 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12526 player->last_move_dir = player->MovDir;
12527 player->is_moving = TRUE;
12528 player->is_snapping = FALSE;
12529 player->is_switching = FALSE;
12530 player->is_dropping = FALSE;
12531 player->is_dropping_pressed = FALSE;
12532 player->drop_pressed_delay = 0;
12535 // should better be called here than above, but this breaks some tapes
12536 ScrollPlayer(player, SCROLL_INIT);
12541 CheckGravityMovementWhenNotMoving(player);
12543 player->is_moving = FALSE;
12545 /* at this point, the player is allowed to move, but cannot move right now
12546 (e.g. because of something blocking the way) -- ensure that the player
12547 is also allowed to move in the next frame (in old versions before 3.1.1,
12548 the player was forced to wait again for eight frames before next try) */
12550 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12551 player->move_delay = 0; // allow direct movement in the next frame
12554 if (player->move_delay == -1) // not yet initialized by DigField()
12555 player->move_delay = player->move_delay_value;
12557 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12559 TestIfPlayerTouchesBadThing(jx, jy);
12560 TestIfPlayerTouchesCustomElement(jx, jy);
12563 if (!player->active)
12564 RemovePlayer(player);
12569 void ScrollPlayer(struct PlayerInfo *player, int mode)
12571 int jx = player->jx, jy = player->jy;
12572 int last_jx = player->last_jx, last_jy = player->last_jy;
12573 int move_stepsize = TILEX / player->move_delay_value;
12575 if (!player->active)
12578 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12581 if (mode == SCROLL_INIT)
12583 player->actual_frame_counter = FrameCounter;
12584 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12586 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12587 Feld[last_jx][last_jy] == EL_EMPTY)
12589 int last_field_block_delay = 0; // start with no blocking at all
12590 int block_delay_adjustment = player->block_delay_adjustment;
12592 // if player blocks last field, add delay for exactly one move
12593 if (player->block_last_field)
12595 last_field_block_delay += player->move_delay_value;
12597 // when blocking enabled, prevent moving up despite gravity
12598 if (player->gravity && player->MovDir == MV_UP)
12599 block_delay_adjustment = -1;
12602 // add block delay adjustment (also possible when not blocking)
12603 last_field_block_delay += block_delay_adjustment;
12605 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12606 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12609 if (player->MovPos != 0) // player has not yet reached destination
12612 else if (!FrameReached(&player->actual_frame_counter, 1))
12615 if (player->MovPos != 0)
12617 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12618 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12620 // before DrawPlayer() to draw correct player graphic for this case
12621 if (player->MovPos == 0)
12622 CheckGravityMovement(player);
12625 if (player->MovPos == 0) // player reached destination field
12627 if (player->move_delay_reset_counter > 0)
12629 player->move_delay_reset_counter--;
12631 if (player->move_delay_reset_counter == 0)
12633 // continue with normal speed after quickly moving through gate
12634 HALVE_PLAYER_SPEED(player);
12636 // be able to make the next move without delay
12637 player->move_delay = 0;
12641 player->last_jx = jx;
12642 player->last_jy = jy;
12644 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12645 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12646 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12647 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12648 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12649 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12650 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12651 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12653 ExitPlayer(player);
12655 if ((local_player->friends_still_needed == 0 ||
12656 IS_SP_ELEMENT(Feld[jx][jy])) &&
12658 PlayerWins(local_player);
12661 // this breaks one level: "machine", level 000
12663 int move_direction = player->MovDir;
12664 int enter_side = MV_DIR_OPPOSITE(move_direction);
12665 int leave_side = move_direction;
12666 int old_jx = last_jx;
12667 int old_jy = last_jy;
12668 int old_element = Feld[old_jx][old_jy];
12669 int new_element = Feld[jx][jy];
12671 if (IS_CUSTOM_ELEMENT(old_element))
12672 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12674 player->index_bit, leave_side);
12676 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12677 CE_PLAYER_LEAVES_X,
12678 player->index_bit, leave_side);
12680 if (IS_CUSTOM_ELEMENT(new_element))
12681 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12682 player->index_bit, enter_side);
12684 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12685 CE_PLAYER_ENTERS_X,
12686 player->index_bit, enter_side);
12688 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12689 CE_MOVE_OF_X, move_direction);
12692 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12694 TestIfPlayerTouchesBadThing(jx, jy);
12695 TestIfPlayerTouchesCustomElement(jx, jy);
12697 /* needed because pushed element has not yet reached its destination,
12698 so it would trigger a change event at its previous field location */
12699 if (!player->is_pushing)
12700 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12702 if (!player->active)
12703 RemovePlayer(player);
12706 if (!local_player->LevelSolved && level.use_step_counter)
12716 if (TimeLeft <= 10 && setup.time_limit)
12717 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12719 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12721 DisplayGameControlValues();
12723 if (!TimeLeft && setup.time_limit)
12724 for (i = 0; i < MAX_PLAYERS; i++)
12725 KillPlayer(&stored_player[i]);
12727 else if (game.no_time_limit && !AllPlayersGone) // level w/o time limit
12729 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12731 DisplayGameControlValues();
12735 if (tape.single_step && tape.recording && !tape.pausing &&
12736 !player->programmed_action)
12737 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12739 if (!player->programmed_action)
12740 CheckSaveEngineSnapshot(player);
12744 void ScrollScreen(struct PlayerInfo *player, int mode)
12746 static unsigned int screen_frame_counter = 0;
12748 if (mode == SCROLL_INIT)
12750 // set scrolling step size according to actual player's moving speed
12751 ScrollStepSize = TILEX / player->move_delay_value;
12753 screen_frame_counter = FrameCounter;
12754 ScreenMovDir = player->MovDir;
12755 ScreenMovPos = player->MovPos;
12756 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12759 else if (!FrameReached(&screen_frame_counter, 1))
12764 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
12765 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12766 redraw_mask |= REDRAW_FIELD;
12769 ScreenMovDir = MV_NONE;
12772 void TestIfPlayerTouchesCustomElement(int x, int y)
12774 static int xy[4][2] =
12781 static int trigger_sides[4][2] =
12783 // center side border side
12784 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12785 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12786 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12787 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12789 static int touch_dir[4] =
12791 MV_LEFT | MV_RIGHT,
12796 int center_element = Feld[x][y]; // should always be non-moving!
12799 for (i = 0; i < NUM_DIRECTIONS; i++)
12801 int xx = x + xy[i][0];
12802 int yy = y + xy[i][1];
12803 int center_side = trigger_sides[i][0];
12804 int border_side = trigger_sides[i][1];
12805 int border_element;
12807 if (!IN_LEV_FIELD(xx, yy))
12810 if (IS_PLAYER(x, y)) // player found at center element
12812 struct PlayerInfo *player = PLAYERINFO(x, y);
12814 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12815 border_element = Feld[xx][yy]; // may be moving!
12816 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12817 border_element = Feld[xx][yy];
12818 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
12819 border_element = MovingOrBlocked2Element(xx, yy);
12821 continue; // center and border element do not touch
12823 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
12824 player->index_bit, border_side);
12825 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
12826 CE_PLAYER_TOUCHES_X,
12827 player->index_bit, border_side);
12830 /* use player element that is initially defined in the level playfield,
12831 not the player element that corresponds to the runtime player number
12832 (example: a level that contains EL_PLAYER_3 as the only player would
12833 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12834 int player_element = PLAYERINFO(x, y)->initial_element;
12836 CheckElementChangeBySide(xx, yy, border_element, player_element,
12837 CE_TOUCHING_X, border_side);
12840 else if (IS_PLAYER(xx, yy)) // player found at border element
12842 struct PlayerInfo *player = PLAYERINFO(xx, yy);
12844 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12846 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
12847 continue; // center and border element do not touch
12850 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
12851 player->index_bit, center_side);
12852 CheckTriggeredElementChangeByPlayer(x, y, center_element,
12853 CE_PLAYER_TOUCHES_X,
12854 player->index_bit, center_side);
12857 /* use player element that is initially defined in the level playfield,
12858 not the player element that corresponds to the runtime player number
12859 (example: a level that contains EL_PLAYER_3 as the only player would
12860 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12861 int player_element = PLAYERINFO(xx, yy)->initial_element;
12863 CheckElementChangeBySide(x, y, center_element, player_element,
12864 CE_TOUCHING_X, center_side);
12872 void TestIfElementTouchesCustomElement(int x, int y)
12874 static int xy[4][2] =
12881 static int trigger_sides[4][2] =
12883 // center side border side
12884 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12885 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12886 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12887 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12889 static int touch_dir[4] =
12891 MV_LEFT | MV_RIGHT,
12896 boolean change_center_element = FALSE;
12897 int center_element = Feld[x][y]; // should always be non-moving!
12898 int border_element_old[NUM_DIRECTIONS];
12901 for (i = 0; i < NUM_DIRECTIONS; i++)
12903 int xx = x + xy[i][0];
12904 int yy = y + xy[i][1];
12905 int border_element;
12907 border_element_old[i] = -1;
12909 if (!IN_LEV_FIELD(xx, yy))
12912 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12913 border_element = Feld[xx][yy]; // may be moving!
12914 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12915 border_element = Feld[xx][yy];
12916 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
12917 border_element = MovingOrBlocked2Element(xx, yy);
12919 continue; // center and border element do not touch
12921 border_element_old[i] = border_element;
12924 for (i = 0; i < NUM_DIRECTIONS; i++)
12926 int xx = x + xy[i][0];
12927 int yy = y + xy[i][1];
12928 int center_side = trigger_sides[i][0];
12929 int border_element = border_element_old[i];
12931 if (border_element == -1)
12934 // check for change of border element
12935 CheckElementChangeBySide(xx, yy, border_element, center_element,
12936 CE_TOUCHING_X, center_side);
12938 // (center element cannot be player, so we dont have to check this here)
12941 for (i = 0; i < NUM_DIRECTIONS; i++)
12943 int xx = x + xy[i][0];
12944 int yy = y + xy[i][1];
12945 int border_side = trigger_sides[i][1];
12946 int border_element = border_element_old[i];
12948 if (border_element == -1)
12951 // check for change of center element (but change it only once)
12952 if (!change_center_element)
12953 change_center_element =
12954 CheckElementChangeBySide(x, y, center_element, border_element,
12955 CE_TOUCHING_X, border_side);
12957 if (IS_PLAYER(xx, yy))
12959 /* use player element that is initially defined in the level playfield,
12960 not the player element that corresponds to the runtime player number
12961 (example: a level that contains EL_PLAYER_3 as the only player would
12962 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12963 int player_element = PLAYERINFO(xx, yy)->initial_element;
12965 CheckElementChangeBySide(x, y, center_element, player_element,
12966 CE_TOUCHING_X, border_side);
12971 void TestIfElementHitsCustomElement(int x, int y, int direction)
12973 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
12974 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
12975 int hitx = x + dx, hity = y + dy;
12976 int hitting_element = Feld[x][y];
12977 int touched_element;
12979 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
12982 touched_element = (IN_LEV_FIELD(hitx, hity) ?
12983 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
12985 if (IN_LEV_FIELD(hitx, hity))
12987 int opposite_direction = MV_DIR_OPPOSITE(direction);
12988 int hitting_side = direction;
12989 int touched_side = opposite_direction;
12990 boolean object_hit = (!IS_MOVING(hitx, hity) ||
12991 MovDir[hitx][hity] != direction ||
12992 ABS(MovPos[hitx][hity]) <= TILEY / 2);
12998 CheckElementChangeBySide(x, y, hitting_element, touched_element,
12999 CE_HITTING_X, touched_side);
13001 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13002 CE_HIT_BY_X, hitting_side);
13004 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13005 CE_HIT_BY_SOMETHING, opposite_direction);
13007 if (IS_PLAYER(hitx, hity))
13009 /* use player element that is initially defined in the level playfield,
13010 not the player element that corresponds to the runtime player number
13011 (example: a level that contains EL_PLAYER_3 as the only player would
13012 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13013 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13015 CheckElementChangeBySide(x, y, hitting_element, player_element,
13016 CE_HITTING_X, touched_side);
13021 // "hitting something" is also true when hitting the playfield border
13022 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13023 CE_HITTING_SOMETHING, direction);
13026 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13028 int i, kill_x = -1, kill_y = -1;
13030 int bad_element = -1;
13031 static int test_xy[4][2] =
13038 static int test_dir[4] =
13046 for (i = 0; i < NUM_DIRECTIONS; i++)
13048 int test_x, test_y, test_move_dir, test_element;
13050 test_x = good_x + test_xy[i][0];
13051 test_y = good_y + test_xy[i][1];
13053 if (!IN_LEV_FIELD(test_x, test_y))
13057 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13059 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13061 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13062 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13064 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13065 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13069 bad_element = test_element;
13075 if (kill_x != -1 || kill_y != -1)
13077 if (IS_PLAYER(good_x, good_y))
13079 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13081 if (player->shield_deadly_time_left > 0 &&
13082 !IS_INDESTRUCTIBLE(bad_element))
13083 Bang(kill_x, kill_y);
13084 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13085 KillPlayer(player);
13088 Bang(good_x, good_y);
13092 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13094 int i, kill_x = -1, kill_y = -1;
13095 int bad_element = Feld[bad_x][bad_y];
13096 static int test_xy[4][2] =
13103 static int touch_dir[4] =
13105 MV_LEFT | MV_RIGHT,
13110 static int test_dir[4] =
13118 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13121 for (i = 0; i < NUM_DIRECTIONS; i++)
13123 int test_x, test_y, test_move_dir, test_element;
13125 test_x = bad_x + test_xy[i][0];
13126 test_y = bad_y + test_xy[i][1];
13128 if (!IN_LEV_FIELD(test_x, test_y))
13132 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13134 test_element = Feld[test_x][test_y];
13136 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13137 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13139 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13140 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13142 // good thing is player or penguin that does not move away
13143 if (IS_PLAYER(test_x, test_y))
13145 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13147 if (bad_element == EL_ROBOT && player->is_moving)
13148 continue; // robot does not kill player if he is moving
13150 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13152 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13153 continue; // center and border element do not touch
13161 else if (test_element == EL_PENGUIN)
13171 if (kill_x != -1 || kill_y != -1)
13173 if (IS_PLAYER(kill_x, kill_y))
13175 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13177 if (player->shield_deadly_time_left > 0 &&
13178 !IS_INDESTRUCTIBLE(bad_element))
13179 Bang(bad_x, bad_y);
13180 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13181 KillPlayer(player);
13184 Bang(kill_x, kill_y);
13188 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13190 int bad_element = Feld[bad_x][bad_y];
13191 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13192 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13193 int test_x = bad_x + dx, test_y = bad_y + dy;
13194 int test_move_dir, test_element;
13195 int kill_x = -1, kill_y = -1;
13197 if (!IN_LEV_FIELD(test_x, test_y))
13201 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13203 test_element = Feld[test_x][test_y];
13205 if (test_move_dir != bad_move_dir)
13207 // good thing can be player or penguin that does not move away
13208 if (IS_PLAYER(test_x, test_y))
13210 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13212 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13213 player as being hit when he is moving towards the bad thing, because
13214 the "get hit by" condition would be lost after the player stops) */
13215 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13216 return; // player moves away from bad thing
13221 else if (test_element == EL_PENGUIN)
13228 if (kill_x != -1 || kill_y != -1)
13230 if (IS_PLAYER(kill_x, kill_y))
13232 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13234 if (player->shield_deadly_time_left > 0 &&
13235 !IS_INDESTRUCTIBLE(bad_element))
13236 Bang(bad_x, bad_y);
13237 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13238 KillPlayer(player);
13241 Bang(kill_x, kill_y);
13245 void TestIfPlayerTouchesBadThing(int x, int y)
13247 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13250 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13252 TestIfGoodThingHitsBadThing(x, y, move_dir);
13255 void TestIfBadThingTouchesPlayer(int x, int y)
13257 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13260 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13262 TestIfBadThingHitsGoodThing(x, y, move_dir);
13265 void TestIfFriendTouchesBadThing(int x, int y)
13267 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13270 void TestIfBadThingTouchesFriend(int x, int y)
13272 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13275 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13277 int i, kill_x = bad_x, kill_y = bad_y;
13278 static int xy[4][2] =
13286 for (i = 0; i < NUM_DIRECTIONS; i++)
13290 x = bad_x + xy[i][0];
13291 y = bad_y + xy[i][1];
13292 if (!IN_LEV_FIELD(x, y))
13295 element = Feld[x][y];
13296 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13297 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13305 if (kill_x != bad_x || kill_y != bad_y)
13306 Bang(bad_x, bad_y);
13309 void KillPlayer(struct PlayerInfo *player)
13311 int jx = player->jx, jy = player->jy;
13313 if (!player->active)
13317 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13318 player->killed, player->active, player->reanimated);
13321 /* the following code was introduced to prevent an infinite loop when calling
13323 -> CheckTriggeredElementChangeExt()
13324 -> ExecuteCustomElementAction()
13326 -> (infinitely repeating the above sequence of function calls)
13327 which occurs when killing the player while having a CE with the setting
13328 "kill player X when explosion of <player X>"; the solution using a new
13329 field "player->killed" was chosen for backwards compatibility, although
13330 clever use of the fields "player->active" etc. would probably also work */
13332 if (player->killed)
13336 player->killed = TRUE;
13338 // remove accessible field at the player's position
13339 Feld[jx][jy] = EL_EMPTY;
13341 // deactivate shield (else Bang()/Explode() would not work right)
13342 player->shield_normal_time_left = 0;
13343 player->shield_deadly_time_left = 0;
13346 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13347 player->killed, player->active, player->reanimated);
13353 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13354 player->killed, player->active, player->reanimated);
13357 if (player->reanimated) // killed player may have been reanimated
13358 player->killed = player->reanimated = FALSE;
13360 BuryPlayer(player);
13363 static void KillPlayerUnlessEnemyProtected(int x, int y)
13365 if (!PLAYER_ENEMY_PROTECTED(x, y))
13366 KillPlayer(PLAYERINFO(x, y));
13369 static void KillPlayerUnlessExplosionProtected(int x, int y)
13371 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13372 KillPlayer(PLAYERINFO(x, y));
13375 void BuryPlayer(struct PlayerInfo *player)
13377 int jx = player->jx, jy = player->jy;
13379 if (!player->active)
13382 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13383 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13385 player->GameOver = TRUE;
13386 RemovePlayer(player);
13389 void RemovePlayer(struct PlayerInfo *player)
13391 int jx = player->jx, jy = player->jy;
13392 int i, found = FALSE;
13394 player->present = FALSE;
13395 player->active = FALSE;
13397 if (!ExplodeField[jx][jy])
13398 StorePlayer[jx][jy] = 0;
13400 if (player->is_moving)
13401 TEST_DrawLevelField(player->last_jx, player->last_jy);
13403 for (i = 0; i < MAX_PLAYERS; i++)
13404 if (stored_player[i].active)
13408 AllPlayersGone = TRUE;
13414 void ExitPlayer(struct PlayerInfo *player)
13416 DrawPlayer(player); // needed here only to cleanup last field
13417 RemovePlayer(player);
13419 if (local_player->players_still_needed > 0)
13420 local_player->players_still_needed--;
13422 // also set if some players not yet gone, but not needed to solve level
13423 if (local_player->players_still_needed == 0)
13424 AllPlayersGone = TRUE;
13427 static void setFieldForSnapping(int x, int y, int element, int direction)
13429 struct ElementInfo *ei = &element_info[element];
13430 int direction_bit = MV_DIR_TO_BIT(direction);
13431 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13432 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13433 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13435 Feld[x][y] = EL_ELEMENT_SNAPPING;
13436 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13438 ResetGfxAnimation(x, y);
13440 GfxElement[x][y] = element;
13441 GfxAction[x][y] = action;
13442 GfxDir[x][y] = direction;
13443 GfxFrame[x][y] = -1;
13447 =============================================================================
13448 checkDiagonalPushing()
13449 -----------------------------------------------------------------------------
13450 check if diagonal input device direction results in pushing of object
13451 (by checking if the alternative direction is walkable, diggable, ...)
13452 =============================================================================
13455 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13456 int x, int y, int real_dx, int real_dy)
13458 int jx, jy, dx, dy, xx, yy;
13460 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13463 // diagonal direction: check alternative direction
13468 xx = jx + (dx == 0 ? real_dx : 0);
13469 yy = jy + (dy == 0 ? real_dy : 0);
13471 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13475 =============================================================================
13477 -----------------------------------------------------------------------------
13478 x, y: field next to player (non-diagonal) to try to dig to
13479 real_dx, real_dy: direction as read from input device (can be diagonal)
13480 =============================================================================
13483 static int DigField(struct PlayerInfo *player,
13484 int oldx, int oldy, int x, int y,
13485 int real_dx, int real_dy, int mode)
13487 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13488 boolean player_was_pushing = player->is_pushing;
13489 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13490 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13491 int jx = oldx, jy = oldy;
13492 int dx = x - jx, dy = y - jy;
13493 int nextx = x + dx, nexty = y + dy;
13494 int move_direction = (dx == -1 ? MV_LEFT :
13495 dx == +1 ? MV_RIGHT :
13497 dy == +1 ? MV_DOWN : MV_NONE);
13498 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13499 int dig_side = MV_DIR_OPPOSITE(move_direction);
13500 int old_element = Feld[jx][jy];
13501 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13504 if (is_player) // function can also be called by EL_PENGUIN
13506 if (player->MovPos == 0)
13508 player->is_digging = FALSE;
13509 player->is_collecting = FALSE;
13512 if (player->MovPos == 0) // last pushing move finished
13513 player->is_pushing = FALSE;
13515 if (mode == DF_NO_PUSH) // player just stopped pushing
13517 player->is_switching = FALSE;
13518 player->push_delay = -1;
13520 return MP_NO_ACTION;
13524 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13525 old_element = Back[jx][jy];
13527 // in case of element dropped at player position, check background
13528 else if (Back[jx][jy] != EL_EMPTY &&
13529 game.engine_version >= VERSION_IDENT(2,2,0,0))
13530 old_element = Back[jx][jy];
13532 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13533 return MP_NO_ACTION; // field has no opening in this direction
13535 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13536 return MP_NO_ACTION; // field has no opening in this direction
13538 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13542 Feld[jx][jy] = player->artwork_element;
13543 InitMovingField(jx, jy, MV_DOWN);
13544 Store[jx][jy] = EL_ACID;
13545 ContinueMoving(jx, jy);
13546 BuryPlayer(player);
13548 return MP_DONT_RUN_INTO;
13551 if (player_can_move && DONT_RUN_INTO(element))
13553 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13555 return MP_DONT_RUN_INTO;
13558 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13559 return MP_NO_ACTION;
13561 collect_count = element_info[element].collect_count_initial;
13563 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13564 return MP_NO_ACTION;
13566 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13567 player_can_move = player_can_move_or_snap;
13569 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13570 game.engine_version >= VERSION_IDENT(2,2,0,0))
13572 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13573 player->index_bit, dig_side);
13574 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13575 player->index_bit, dig_side);
13577 if (element == EL_DC_LANDMINE)
13580 if (Feld[x][y] != element) // field changed by snapping
13583 return MP_NO_ACTION;
13586 if (player->gravity && is_player && !player->is_auto_moving &&
13587 canFallDown(player) && move_direction != MV_DOWN &&
13588 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13589 return MP_NO_ACTION; // player cannot walk here due to gravity
13591 if (player_can_move &&
13592 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13594 int sound_element = SND_ELEMENT(element);
13595 int sound_action = ACTION_WALKING;
13597 if (IS_RND_GATE(element))
13599 if (!player->key[RND_GATE_NR(element)])
13600 return MP_NO_ACTION;
13602 else if (IS_RND_GATE_GRAY(element))
13604 if (!player->key[RND_GATE_GRAY_NR(element)])
13605 return MP_NO_ACTION;
13607 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13609 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13610 return MP_NO_ACTION;
13612 else if (element == EL_EXIT_OPEN ||
13613 element == EL_EM_EXIT_OPEN ||
13614 element == EL_EM_EXIT_OPENING ||
13615 element == EL_STEEL_EXIT_OPEN ||
13616 element == EL_EM_STEEL_EXIT_OPEN ||
13617 element == EL_EM_STEEL_EXIT_OPENING ||
13618 element == EL_SP_EXIT_OPEN ||
13619 element == EL_SP_EXIT_OPENING)
13621 sound_action = ACTION_PASSING; // player is passing exit
13623 else if (element == EL_EMPTY)
13625 sound_action = ACTION_MOVING; // nothing to walk on
13628 // play sound from background or player, whatever is available
13629 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13630 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13632 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13634 else if (player_can_move &&
13635 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13637 if (!ACCESS_FROM(element, opposite_direction))
13638 return MP_NO_ACTION; // field not accessible from this direction
13640 if (CAN_MOVE(element)) // only fixed elements can be passed!
13641 return MP_NO_ACTION;
13643 if (IS_EM_GATE(element))
13645 if (!player->key[EM_GATE_NR(element)])
13646 return MP_NO_ACTION;
13648 else if (IS_EM_GATE_GRAY(element))
13650 if (!player->key[EM_GATE_GRAY_NR(element)])
13651 return MP_NO_ACTION;
13653 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13655 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13656 return MP_NO_ACTION;
13658 else if (IS_EMC_GATE(element))
13660 if (!player->key[EMC_GATE_NR(element)])
13661 return MP_NO_ACTION;
13663 else if (IS_EMC_GATE_GRAY(element))
13665 if (!player->key[EMC_GATE_GRAY_NR(element)])
13666 return MP_NO_ACTION;
13668 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13670 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13671 return MP_NO_ACTION;
13673 else if (element == EL_DC_GATE_WHITE ||
13674 element == EL_DC_GATE_WHITE_GRAY ||
13675 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13677 if (player->num_white_keys == 0)
13678 return MP_NO_ACTION;
13680 player->num_white_keys--;
13682 else if (IS_SP_PORT(element))
13684 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13685 element == EL_SP_GRAVITY_PORT_RIGHT ||
13686 element == EL_SP_GRAVITY_PORT_UP ||
13687 element == EL_SP_GRAVITY_PORT_DOWN)
13688 player->gravity = !player->gravity;
13689 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13690 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13691 element == EL_SP_GRAVITY_ON_PORT_UP ||
13692 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13693 player->gravity = TRUE;
13694 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13695 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13696 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13697 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13698 player->gravity = FALSE;
13701 // automatically move to the next field with double speed
13702 player->programmed_action = move_direction;
13704 if (player->move_delay_reset_counter == 0)
13706 player->move_delay_reset_counter = 2; // two double speed steps
13708 DOUBLE_PLAYER_SPEED(player);
13711 PlayLevelSoundAction(x, y, ACTION_PASSING);
13713 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13717 if (mode != DF_SNAP)
13719 GfxElement[x][y] = GFX_ELEMENT(element);
13720 player->is_digging = TRUE;
13723 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13725 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13726 player->index_bit, dig_side);
13728 if (mode == DF_SNAP)
13730 if (level.block_snap_field)
13731 setFieldForSnapping(x, y, element, move_direction);
13733 TestIfElementTouchesCustomElement(x, y); // for empty space
13735 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13736 player->index_bit, dig_side);
13739 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13743 if (is_player && mode != DF_SNAP)
13745 GfxElement[x][y] = element;
13746 player->is_collecting = TRUE;
13749 if (element == EL_SPEED_PILL)
13751 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13753 else if (element == EL_EXTRA_TIME && level.time > 0)
13755 TimeLeft += level.extra_time;
13757 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13759 DisplayGameControlValues();
13761 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
13763 player->shield_normal_time_left += level.shield_normal_time;
13764 if (element == EL_SHIELD_DEADLY)
13765 player->shield_deadly_time_left += level.shield_deadly_time;
13767 else if (element == EL_DYNAMITE ||
13768 element == EL_EM_DYNAMITE ||
13769 element == EL_SP_DISK_RED)
13771 if (player->inventory_size < MAX_INVENTORY_SIZE)
13772 player->inventory_element[player->inventory_size++] = element;
13774 DrawGameDoorValues();
13776 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
13778 player->dynabomb_count++;
13779 player->dynabombs_left++;
13781 else if (element == EL_DYNABOMB_INCREASE_SIZE)
13783 player->dynabomb_size++;
13785 else if (element == EL_DYNABOMB_INCREASE_POWER)
13787 player->dynabomb_xl = TRUE;
13789 else if (IS_KEY(element))
13791 player->key[KEY_NR(element)] = TRUE;
13793 DrawGameDoorValues();
13795 else if (element == EL_DC_KEY_WHITE)
13797 player->num_white_keys++;
13799 // display white keys?
13800 // DrawGameDoorValues();
13802 else if (IS_ENVELOPE(element))
13804 player->show_envelope = element;
13806 else if (element == EL_EMC_LENSES)
13808 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
13810 RedrawAllInvisibleElementsForLenses();
13812 else if (element == EL_EMC_MAGNIFIER)
13814 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
13816 RedrawAllInvisibleElementsForMagnifier();
13818 else if (IS_DROPPABLE(element) ||
13819 IS_THROWABLE(element)) // can be collected and dropped
13823 if (collect_count == 0)
13824 player->inventory_infinite_element = element;
13826 for (i = 0; i < collect_count; i++)
13827 if (player->inventory_size < MAX_INVENTORY_SIZE)
13828 player->inventory_element[player->inventory_size++] = element;
13830 DrawGameDoorValues();
13832 else if (collect_count > 0)
13834 local_player->gems_still_needed -= collect_count;
13835 if (local_player->gems_still_needed < 0)
13836 local_player->gems_still_needed = 0;
13838 game.snapshot.collected_item = TRUE;
13840 game_panel_controls[GAME_PANEL_GEMS].value = local_player->gems_still_needed;
13842 DisplayGameControlValues();
13845 RaiseScoreElement(element);
13846 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
13849 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
13850 player->index_bit, dig_side);
13852 if (mode == DF_SNAP)
13854 if (level.block_snap_field)
13855 setFieldForSnapping(x, y, element, move_direction);
13857 TestIfElementTouchesCustomElement(x, y); // for empty space
13859 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13860 player->index_bit, dig_side);
13863 else if (player_can_move_or_snap && IS_PUSHABLE(element))
13865 if (mode == DF_SNAP && element != EL_BD_ROCK)
13866 return MP_NO_ACTION;
13868 if (CAN_FALL(element) && dy)
13869 return MP_NO_ACTION;
13871 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
13872 !(element == EL_SPRING && level.use_spring_bug))
13873 return MP_NO_ACTION;
13875 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
13876 ((move_direction & MV_VERTICAL &&
13877 ((element_info[element].move_pattern & MV_LEFT &&
13878 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
13879 (element_info[element].move_pattern & MV_RIGHT &&
13880 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
13881 (move_direction & MV_HORIZONTAL &&
13882 ((element_info[element].move_pattern & MV_UP &&
13883 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
13884 (element_info[element].move_pattern & MV_DOWN &&
13885 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
13886 return MP_NO_ACTION;
13888 // do not push elements already moving away faster than player
13889 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
13890 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
13891 return MP_NO_ACTION;
13893 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
13895 if (player->push_delay_value == -1 || !player_was_pushing)
13896 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13898 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13900 if (player->push_delay_value == -1)
13901 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13903 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
13905 if (!player->is_pushing)
13906 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13909 player->is_pushing = TRUE;
13910 player->is_active = TRUE;
13912 if (!(IN_LEV_FIELD(nextx, nexty) &&
13913 (IS_FREE(nextx, nexty) ||
13914 (IS_SB_ELEMENT(element) &&
13915 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
13916 (IS_CUSTOM_ELEMENT(element) &&
13917 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
13918 return MP_NO_ACTION;
13920 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
13921 return MP_NO_ACTION;
13923 if (player->push_delay == -1) // new pushing; restart delay
13924 player->push_delay = 0;
13926 if (player->push_delay < player->push_delay_value &&
13927 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
13928 element != EL_SPRING && element != EL_BALLOON)
13930 // make sure that there is no move delay before next try to push
13931 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13932 player->move_delay = 0;
13934 return MP_NO_ACTION;
13937 if (IS_CUSTOM_ELEMENT(element) &&
13938 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
13940 if (!DigFieldByCE(nextx, nexty, element))
13941 return MP_NO_ACTION;
13944 if (IS_SB_ELEMENT(element))
13946 boolean sokoban_task_solved = FALSE;
13948 if (element == EL_SOKOBAN_FIELD_FULL)
13950 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
13951 local_player->sokoban_fields_still_needed++;
13952 local_player->sokoban_objects_still_needed++;
13955 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
13957 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
13958 local_player->sokoban_fields_still_needed--;
13959 local_player->sokoban_objects_still_needed--;
13961 // sokoban object was pushed from empty field to sokoban field
13962 if (Back[x][y] == EL_EMPTY)
13963 sokoban_task_solved = TRUE;
13966 Feld[x][y] = EL_SOKOBAN_OBJECT;
13968 if (Back[x][y] == Back[nextx][nexty])
13969 PlayLevelSoundAction(x, y, ACTION_PUSHING);
13970 else if (Back[x][y] != 0)
13971 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
13974 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
13977 if (sokoban_task_solved &&
13978 local_player->sokoban_fields_still_needed == 0 &&
13979 local_player->sokoban_objects_still_needed == 0 &&
13980 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
13982 local_player->players_still_needed = 0;
13984 PlayerWins(local_player);
13986 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
13990 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
13992 InitMovingField(x, y, move_direction);
13993 GfxAction[x][y] = ACTION_PUSHING;
13995 if (mode == DF_SNAP)
13996 ContinueMoving(x, y);
13998 MovPos[x][y] = (dx != 0 ? dx : dy);
14000 Pushed[x][y] = TRUE;
14001 Pushed[nextx][nexty] = TRUE;
14003 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14004 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14006 player->push_delay_value = -1; // get new value later
14008 // check for element change _after_ element has been pushed
14009 if (game.use_change_when_pushing_bug)
14011 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14012 player->index_bit, dig_side);
14013 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14014 player->index_bit, dig_side);
14017 else if (IS_SWITCHABLE(element))
14019 if (PLAYER_SWITCHING(player, x, y))
14021 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14022 player->index_bit, dig_side);
14027 player->is_switching = TRUE;
14028 player->switch_x = x;
14029 player->switch_y = y;
14031 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14033 if (element == EL_ROBOT_WHEEL)
14035 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14039 game.robot_wheel_active = TRUE;
14041 TEST_DrawLevelField(x, y);
14043 else if (element == EL_SP_TERMINAL)
14047 SCAN_PLAYFIELD(xx, yy)
14049 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14053 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14055 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14057 ResetGfxAnimation(xx, yy);
14058 TEST_DrawLevelField(xx, yy);
14062 else if (IS_BELT_SWITCH(element))
14064 ToggleBeltSwitch(x, y);
14066 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14067 element == EL_SWITCHGATE_SWITCH_DOWN ||
14068 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14069 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14071 ToggleSwitchgateSwitch(x, y);
14073 else if (element == EL_LIGHT_SWITCH ||
14074 element == EL_LIGHT_SWITCH_ACTIVE)
14076 ToggleLightSwitch(x, y);
14078 else if (element == EL_TIMEGATE_SWITCH ||
14079 element == EL_DC_TIMEGATE_SWITCH)
14081 ActivateTimegateSwitch(x, y);
14083 else if (element == EL_BALLOON_SWITCH_LEFT ||
14084 element == EL_BALLOON_SWITCH_RIGHT ||
14085 element == EL_BALLOON_SWITCH_UP ||
14086 element == EL_BALLOON_SWITCH_DOWN ||
14087 element == EL_BALLOON_SWITCH_NONE ||
14088 element == EL_BALLOON_SWITCH_ANY)
14090 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14091 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14092 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14093 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14094 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14097 else if (element == EL_LAMP)
14099 Feld[x][y] = EL_LAMP_ACTIVE;
14100 local_player->lights_still_needed--;
14102 ResetGfxAnimation(x, y);
14103 TEST_DrawLevelField(x, y);
14105 else if (element == EL_TIME_ORB_FULL)
14107 Feld[x][y] = EL_TIME_ORB_EMPTY;
14109 if (level.time > 0 || level.use_time_orb_bug)
14111 TimeLeft += level.time_orb_time;
14112 game.no_time_limit = FALSE;
14114 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14116 DisplayGameControlValues();
14119 ResetGfxAnimation(x, y);
14120 TEST_DrawLevelField(x, y);
14122 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14123 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14127 game.ball_state = !game.ball_state;
14129 SCAN_PLAYFIELD(xx, yy)
14131 int e = Feld[xx][yy];
14133 if (game.ball_state)
14135 if (e == EL_EMC_MAGIC_BALL)
14136 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14137 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14138 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14142 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14143 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14144 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14145 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14150 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14151 player->index_bit, dig_side);
14153 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14154 player->index_bit, dig_side);
14156 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14157 player->index_bit, dig_side);
14163 if (!PLAYER_SWITCHING(player, x, y))
14165 player->is_switching = TRUE;
14166 player->switch_x = x;
14167 player->switch_y = y;
14169 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14170 player->index_bit, dig_side);
14171 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14172 player->index_bit, dig_side);
14174 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14175 player->index_bit, dig_side);
14176 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14177 player->index_bit, dig_side);
14180 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14181 player->index_bit, dig_side);
14182 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14183 player->index_bit, dig_side);
14185 return MP_NO_ACTION;
14188 player->push_delay = -1;
14190 if (is_player) // function can also be called by EL_PENGUIN
14192 if (Feld[x][y] != element) // really digged/collected something
14194 player->is_collecting = !player->is_digging;
14195 player->is_active = TRUE;
14202 static boolean DigFieldByCE(int x, int y, int digging_element)
14204 int element = Feld[x][y];
14206 if (!IS_FREE(x, y))
14208 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14209 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14212 // no element can dig solid indestructible elements
14213 if (IS_INDESTRUCTIBLE(element) &&
14214 !IS_DIGGABLE(element) &&
14215 !IS_COLLECTIBLE(element))
14218 if (AmoebaNr[x][y] &&
14219 (element == EL_AMOEBA_FULL ||
14220 element == EL_BD_AMOEBA ||
14221 element == EL_AMOEBA_GROWING))
14223 AmoebaCnt[AmoebaNr[x][y]]--;
14224 AmoebaCnt2[AmoebaNr[x][y]]--;
14227 if (IS_MOVING(x, y))
14228 RemoveMovingField(x, y);
14232 TEST_DrawLevelField(x, y);
14235 // if digged element was about to explode, prevent the explosion
14236 ExplodeField[x][y] = EX_TYPE_NONE;
14238 PlayLevelSoundAction(x, y, action);
14241 Store[x][y] = EL_EMPTY;
14243 // this makes it possible to leave the removed element again
14244 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14245 Store[x][y] = element;
14250 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14252 int jx = player->jx, jy = player->jy;
14253 int x = jx + dx, y = jy + dy;
14254 int snap_direction = (dx == -1 ? MV_LEFT :
14255 dx == +1 ? MV_RIGHT :
14257 dy == +1 ? MV_DOWN : MV_NONE);
14258 boolean can_continue_snapping = (level.continuous_snapping &&
14259 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14261 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14264 if (!player->active || !IN_LEV_FIELD(x, y))
14272 if (player->MovPos == 0)
14273 player->is_pushing = FALSE;
14275 player->is_snapping = FALSE;
14277 if (player->MovPos == 0)
14279 player->is_moving = FALSE;
14280 player->is_digging = FALSE;
14281 player->is_collecting = FALSE;
14287 // prevent snapping with already pressed snap key when not allowed
14288 if (player->is_snapping && !can_continue_snapping)
14291 player->MovDir = snap_direction;
14293 if (player->MovPos == 0)
14295 player->is_moving = FALSE;
14296 player->is_digging = FALSE;
14297 player->is_collecting = FALSE;
14300 player->is_dropping = FALSE;
14301 player->is_dropping_pressed = FALSE;
14302 player->drop_pressed_delay = 0;
14304 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14307 player->is_snapping = TRUE;
14308 player->is_active = TRUE;
14310 if (player->MovPos == 0)
14312 player->is_moving = FALSE;
14313 player->is_digging = FALSE;
14314 player->is_collecting = FALSE;
14317 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14318 TEST_DrawLevelField(player->last_jx, player->last_jy);
14320 TEST_DrawLevelField(x, y);
14325 static boolean DropElement(struct PlayerInfo *player)
14327 int old_element, new_element;
14328 int dropx = player->jx, dropy = player->jy;
14329 int drop_direction = player->MovDir;
14330 int drop_side = drop_direction;
14331 int drop_element = get_next_dropped_element(player);
14333 /* do not drop an element on top of another element; when holding drop key
14334 pressed without moving, dropped element must move away before the next
14335 element can be dropped (this is especially important if the next element
14336 is dynamite, which can be placed on background for historical reasons) */
14337 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14340 if (IS_THROWABLE(drop_element))
14342 dropx += GET_DX_FROM_DIR(drop_direction);
14343 dropy += GET_DY_FROM_DIR(drop_direction);
14345 if (!IN_LEV_FIELD(dropx, dropy))
14349 old_element = Feld[dropx][dropy]; // old element at dropping position
14350 new_element = drop_element; // default: no change when dropping
14352 // check if player is active, not moving and ready to drop
14353 if (!player->active || player->MovPos || player->drop_delay > 0)
14356 // check if player has anything that can be dropped
14357 if (new_element == EL_UNDEFINED)
14360 // only set if player has anything that can be dropped
14361 player->is_dropping_pressed = TRUE;
14363 // check if drop key was pressed long enough for EM style dynamite
14364 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14367 // check if anything can be dropped at the current position
14368 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14371 // collected custom elements can only be dropped on empty fields
14372 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14375 if (old_element != EL_EMPTY)
14376 Back[dropx][dropy] = old_element; // store old element on this field
14378 ResetGfxAnimation(dropx, dropy);
14379 ResetRandomAnimationValue(dropx, dropy);
14381 if (player->inventory_size > 0 ||
14382 player->inventory_infinite_element != EL_UNDEFINED)
14384 if (player->inventory_size > 0)
14386 player->inventory_size--;
14388 DrawGameDoorValues();
14390 if (new_element == EL_DYNAMITE)
14391 new_element = EL_DYNAMITE_ACTIVE;
14392 else if (new_element == EL_EM_DYNAMITE)
14393 new_element = EL_EM_DYNAMITE_ACTIVE;
14394 else if (new_element == EL_SP_DISK_RED)
14395 new_element = EL_SP_DISK_RED_ACTIVE;
14398 Feld[dropx][dropy] = new_element;
14400 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14401 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14402 el2img(Feld[dropx][dropy]), 0);
14404 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14406 // needed if previous element just changed to "empty" in the last frame
14407 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14409 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14410 player->index_bit, drop_side);
14411 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14413 player->index_bit, drop_side);
14415 TestIfElementTouchesCustomElement(dropx, dropy);
14417 else // player is dropping a dyna bomb
14419 player->dynabombs_left--;
14421 Feld[dropx][dropy] = new_element;
14423 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14424 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14425 el2img(Feld[dropx][dropy]), 0);
14427 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14430 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14431 InitField_WithBug1(dropx, dropy, FALSE);
14433 new_element = Feld[dropx][dropy]; // element might have changed
14435 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14436 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14438 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14439 MovDir[dropx][dropy] = drop_direction;
14441 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14443 // do not cause impact style collision by dropping elements that can fall
14444 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14447 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14448 player->is_dropping = TRUE;
14450 player->drop_pressed_delay = 0;
14451 player->is_dropping_pressed = FALSE;
14453 player->drop_x = dropx;
14454 player->drop_y = dropy;
14459 // ----------------------------------------------------------------------------
14460 // game sound playing functions
14461 // ----------------------------------------------------------------------------
14463 static int *loop_sound_frame = NULL;
14464 static int *loop_sound_volume = NULL;
14466 void InitPlayLevelSound(void)
14468 int num_sounds = getSoundListSize();
14470 checked_free(loop_sound_frame);
14471 checked_free(loop_sound_volume);
14473 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14474 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14477 static void PlayLevelSound(int x, int y, int nr)
14479 int sx = SCREENX(x), sy = SCREENY(y);
14480 int volume, stereo_position;
14481 int max_distance = 8;
14482 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14484 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14485 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14488 if (!IN_LEV_FIELD(x, y) ||
14489 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14490 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14493 volume = SOUND_MAX_VOLUME;
14495 if (!IN_SCR_FIELD(sx, sy))
14497 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14498 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14500 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14503 stereo_position = (SOUND_MAX_LEFT +
14504 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14505 (SCR_FIELDX + 2 * max_distance));
14507 if (IS_LOOP_SOUND(nr))
14509 /* This assures that quieter loop sounds do not overwrite louder ones,
14510 while restarting sound volume comparison with each new game frame. */
14512 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14515 loop_sound_volume[nr] = volume;
14516 loop_sound_frame[nr] = FrameCounter;
14519 PlaySoundExt(nr, volume, stereo_position, type);
14522 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14524 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14525 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14526 y < LEVELY(BY1) ? LEVELY(BY1) :
14527 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14531 static void PlayLevelSoundAction(int x, int y, int action)
14533 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14536 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14538 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14540 if (sound_effect != SND_UNDEFINED)
14541 PlayLevelSound(x, y, sound_effect);
14544 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14547 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14549 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14550 PlayLevelSound(x, y, sound_effect);
14553 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14555 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14557 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14558 PlayLevelSound(x, y, sound_effect);
14561 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14563 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14565 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14566 StopSound(sound_effect);
14569 static int getLevelMusicNr(void)
14571 if (levelset.music[level_nr] != MUS_UNDEFINED)
14572 return levelset.music[level_nr]; // from config file
14574 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14577 static void FadeLevelSounds(void)
14582 static void FadeLevelMusic(void)
14584 int music_nr = getLevelMusicNr();
14585 char *curr_music = getCurrentlyPlayingMusicFilename();
14586 char *next_music = getMusicInfoEntryFilename(music_nr);
14588 if (!strEqual(curr_music, next_music))
14592 void FadeLevelSoundsAndMusic(void)
14598 static void PlayLevelMusic(void)
14600 int music_nr = getLevelMusicNr();
14601 char *curr_music = getCurrentlyPlayingMusicFilename();
14602 char *next_music = getMusicInfoEntryFilename(music_nr);
14604 if (!strEqual(curr_music, next_music))
14605 PlayMusicLoop(music_nr);
14608 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14610 int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0);
14611 int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
14612 int x = xx - 1 - offset;
14613 int y = yy - 1 - offset;
14618 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14622 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14626 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14630 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14634 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14638 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14642 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14645 case SAMPLE_android_clone:
14646 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14649 case SAMPLE_android_move:
14650 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14653 case SAMPLE_spring:
14654 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14658 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14662 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14665 case SAMPLE_eater_eat:
14666 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14670 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14673 case SAMPLE_collect:
14674 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14677 case SAMPLE_diamond:
14678 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14681 case SAMPLE_squash:
14682 // !!! CHECK THIS !!!
14684 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14686 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14690 case SAMPLE_wonderfall:
14691 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14695 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14699 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14703 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14707 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14711 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14715 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14718 case SAMPLE_wonder:
14719 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14723 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14726 case SAMPLE_exit_open:
14727 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14730 case SAMPLE_exit_leave:
14731 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14734 case SAMPLE_dynamite:
14735 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14739 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14743 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14747 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14751 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14755 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
14759 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
14763 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
14768 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
14770 int element = map_element_SP_to_RND(element_sp);
14771 int action = map_action_SP_to_RND(action_sp);
14772 int offset = (setup.sp_show_border_elements ? 0 : 1);
14773 int x = xx - offset;
14774 int y = yy - offset;
14776 PlayLevelSoundElementAction(x, y, element, action);
14779 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
14781 int element = map_element_MM_to_RND(element_mm);
14782 int action = map_action_MM_to_RND(action_mm);
14784 int x = xx - offset;
14785 int y = yy - offset;
14787 if (!IS_MM_ELEMENT(element))
14788 element = EL_MM_DEFAULT;
14790 PlayLevelSoundElementAction(x, y, element, action);
14793 void PlaySound_MM(int sound_mm)
14795 int sound = map_sound_MM_to_RND(sound_mm);
14797 if (sound == SND_UNDEFINED)
14803 void PlaySoundLoop_MM(int sound_mm)
14805 int sound = map_sound_MM_to_RND(sound_mm);
14807 if (sound == SND_UNDEFINED)
14810 PlaySoundLoop(sound);
14813 void StopSound_MM(int sound_mm)
14815 int sound = map_sound_MM_to_RND(sound_mm);
14817 if (sound == SND_UNDEFINED)
14823 void RaiseScore(int value)
14825 local_player->score += value;
14827 game_panel_controls[GAME_PANEL_SCORE].value = local_player->score;
14829 DisplayGameControlValues();
14832 void RaiseScoreElement(int element)
14837 case EL_BD_DIAMOND:
14838 case EL_EMERALD_YELLOW:
14839 case EL_EMERALD_RED:
14840 case EL_EMERALD_PURPLE:
14841 case EL_SP_INFOTRON:
14842 RaiseScore(level.score[SC_EMERALD]);
14845 RaiseScore(level.score[SC_DIAMOND]);
14848 RaiseScore(level.score[SC_CRYSTAL]);
14851 RaiseScore(level.score[SC_PEARL]);
14854 case EL_BD_BUTTERFLY:
14855 case EL_SP_ELECTRON:
14856 RaiseScore(level.score[SC_BUG]);
14859 case EL_BD_FIREFLY:
14860 case EL_SP_SNIKSNAK:
14861 RaiseScore(level.score[SC_SPACESHIP]);
14864 case EL_DARK_YAMYAM:
14865 RaiseScore(level.score[SC_YAMYAM]);
14868 RaiseScore(level.score[SC_ROBOT]);
14871 RaiseScore(level.score[SC_PACMAN]);
14874 RaiseScore(level.score[SC_NUT]);
14877 case EL_EM_DYNAMITE:
14878 case EL_SP_DISK_RED:
14879 case EL_DYNABOMB_INCREASE_NUMBER:
14880 case EL_DYNABOMB_INCREASE_SIZE:
14881 case EL_DYNABOMB_INCREASE_POWER:
14882 RaiseScore(level.score[SC_DYNAMITE]);
14884 case EL_SHIELD_NORMAL:
14885 case EL_SHIELD_DEADLY:
14886 RaiseScore(level.score[SC_SHIELD]);
14888 case EL_EXTRA_TIME:
14889 RaiseScore(level.extra_time_score);
14903 case EL_DC_KEY_WHITE:
14904 RaiseScore(level.score[SC_KEY]);
14907 RaiseScore(element_info[element].collect_score);
14912 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
14914 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
14916 // closing door required in case of envelope style request dialogs
14918 CloseDoor(DOOR_CLOSE_1);
14920 if (network.enabled)
14921 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
14925 FadeSkipNextFadeIn();
14927 SetGameStatus(GAME_MODE_MAIN);
14932 else // continue playing the game
14934 if (tape.playing && tape.deactivate_display)
14935 TapeDeactivateDisplayOff(TRUE);
14937 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
14939 if (tape.playing && tape.deactivate_display)
14940 TapeDeactivateDisplayOn();
14944 void RequestQuitGame(boolean ask_if_really_quit)
14946 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
14947 boolean skip_request = AllPlayersGone || quick_quit;
14949 RequestQuitGameExt(skip_request, quick_quit,
14950 "Do you really want to quit the game?");
14953 void RequestRestartGame(char *message)
14955 game.restart_game_message = NULL;
14957 boolean has_started_game = hasStartedNetworkGame();
14958 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
14960 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
14962 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
14966 SetGameStatus(GAME_MODE_MAIN);
14972 void CheckGameOver(void)
14974 static boolean last_game_over = FALSE;
14975 static int game_over_delay = 0;
14976 int game_over_delay_value = 50;
14977 boolean game_over = checkGameFailed();
14979 // do not handle game over if request dialog is already active
14980 if (game.request_active)
14985 last_game_over = FALSE;
14986 game_over_delay = game_over_delay_value;
14991 if (game_over_delay > 0)
14998 if (last_game_over != game_over)
14999 game.restart_game_message = (hasStartedNetworkGame() ?
15000 "Game over! Play it again?" :
15003 last_game_over = game_over;
15006 boolean checkGameSolved(void)
15008 // set for all game engines if level was solved
15009 return local_player->LevelSolved_GameEnd;
15012 boolean checkGameFailed(void)
15014 if (!AllPlayersGone)
15017 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15018 return (level.native_em_level->lev->home > 0);
15019 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15020 return (game_sp.GameOver && !game_sp.LevelSolved);
15021 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15022 return (game_mm.game_over && !game_mm.level_solved);
15023 else // GAME_ENGINE_TYPE_RND
15024 return (local_player->GameOver && !local_player->LevelSolved);
15027 boolean checkGameEnded(void)
15029 return (checkGameSolved() || checkGameFailed());
15033 // ----------------------------------------------------------------------------
15034 // random generator functions
15035 // ----------------------------------------------------------------------------
15037 unsigned int InitEngineRandom_RND(int seed)
15039 game.num_random_calls = 0;
15041 return InitEngineRandom(seed);
15044 unsigned int RND(int max)
15048 game.num_random_calls++;
15050 return GetEngineRandom(max);
15057 // ----------------------------------------------------------------------------
15058 // game engine snapshot handling functions
15059 // ----------------------------------------------------------------------------
15061 struct EngineSnapshotInfo
15063 // runtime values for custom element collect score
15064 int collect_score[NUM_CUSTOM_ELEMENTS];
15066 // runtime values for group element choice position
15067 int choice_pos[NUM_GROUP_ELEMENTS];
15069 // runtime values for belt position animations
15070 int belt_graphic[4][NUM_BELT_PARTS];
15071 int belt_anim_mode[4][NUM_BELT_PARTS];
15074 static struct EngineSnapshotInfo engine_snapshot_rnd;
15075 static char *snapshot_level_identifier = NULL;
15076 static int snapshot_level_nr = -1;
15078 static void SaveEngineSnapshotValues_RND(void)
15080 static int belt_base_active_element[4] =
15082 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15083 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15084 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15085 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15089 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15091 int element = EL_CUSTOM_START + i;
15093 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15096 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15098 int element = EL_GROUP_START + i;
15100 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15103 for (i = 0; i < 4; i++)
15105 for (j = 0; j < NUM_BELT_PARTS; j++)
15107 int element = belt_base_active_element[i] + j;
15108 int graphic = el2img(element);
15109 int anim_mode = graphic_info[graphic].anim_mode;
15111 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15112 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15117 static void LoadEngineSnapshotValues_RND(void)
15119 unsigned int num_random_calls = game.num_random_calls;
15122 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15124 int element = EL_CUSTOM_START + i;
15126 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15129 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15131 int element = EL_GROUP_START + i;
15133 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15136 for (i = 0; i < 4; i++)
15138 for (j = 0; j < NUM_BELT_PARTS; j++)
15140 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15141 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15143 graphic_info[graphic].anim_mode = anim_mode;
15147 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15149 InitRND(tape.random_seed);
15150 for (i = 0; i < num_random_calls; i++)
15154 if (game.num_random_calls != num_random_calls)
15156 Error(ERR_INFO, "number of random calls out of sync");
15157 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15158 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15159 Error(ERR_EXIT, "this should not happen -- please debug");
15163 void FreeEngineSnapshotSingle(void)
15165 FreeSnapshotSingle();
15167 setString(&snapshot_level_identifier, NULL);
15168 snapshot_level_nr = -1;
15171 void FreeEngineSnapshotList(void)
15173 FreeSnapshotList();
15176 static ListNode *SaveEngineSnapshotBuffers(void)
15178 ListNode *buffers = NULL;
15180 // copy some special values to a structure better suited for the snapshot
15182 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15183 SaveEngineSnapshotValues_RND();
15184 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15185 SaveEngineSnapshotValues_EM();
15186 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15187 SaveEngineSnapshotValues_SP(&buffers);
15188 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15189 SaveEngineSnapshotValues_MM(&buffers);
15191 // save values stored in special snapshot structure
15193 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15194 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15195 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15196 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15197 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15198 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15199 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15200 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15202 // save further RND engine values
15204 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15205 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15206 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15208 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZX));
15209 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZY));
15210 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitX));
15211 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitY));
15213 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15214 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15215 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15216 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15217 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15219 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15220 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15221 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15223 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15225 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AllPlayersGone));
15227 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15228 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15230 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15231 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15232 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15233 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15234 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15235 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15236 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15237 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15238 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15239 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15240 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15241 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15242 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15243 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15244 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15245 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15246 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15247 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15249 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15250 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15252 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15253 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15254 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15256 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15257 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15259 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15260 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15261 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15262 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15263 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15265 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15266 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15269 ListNode *node = engine_snapshot_list_rnd;
15272 while (node != NULL)
15274 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15279 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15285 void SaveEngineSnapshotSingle(void)
15287 ListNode *buffers = SaveEngineSnapshotBuffers();
15289 // finally save all snapshot buffers to single snapshot
15290 SaveSnapshotSingle(buffers);
15292 // save level identification information
15293 setString(&snapshot_level_identifier, leveldir_current->identifier);
15294 snapshot_level_nr = level_nr;
15297 boolean CheckSaveEngineSnapshotToList(void)
15299 boolean save_snapshot =
15300 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15301 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15302 game.snapshot.changed_action) ||
15303 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15304 game.snapshot.collected_item));
15306 game.snapshot.changed_action = FALSE;
15307 game.snapshot.collected_item = FALSE;
15308 game.snapshot.save_snapshot = save_snapshot;
15310 return save_snapshot;
15313 void SaveEngineSnapshotToList(void)
15315 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15319 ListNode *buffers = SaveEngineSnapshotBuffers();
15321 // finally save all snapshot buffers to snapshot list
15322 SaveSnapshotToList(buffers);
15325 void SaveEngineSnapshotToListInitial(void)
15327 FreeEngineSnapshotList();
15329 SaveEngineSnapshotToList();
15332 static void LoadEngineSnapshotValues(void)
15334 // restore special values from snapshot structure
15336 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15337 LoadEngineSnapshotValues_RND();
15338 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15339 LoadEngineSnapshotValues_EM();
15340 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15341 LoadEngineSnapshotValues_SP();
15342 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15343 LoadEngineSnapshotValues_MM();
15346 void LoadEngineSnapshotSingle(void)
15348 LoadSnapshotSingle();
15350 LoadEngineSnapshotValues();
15353 static void LoadEngineSnapshot_Undo(int steps)
15355 LoadSnapshotFromList_Older(steps);
15357 LoadEngineSnapshotValues();
15360 static void LoadEngineSnapshot_Redo(int steps)
15362 LoadSnapshotFromList_Newer(steps);
15364 LoadEngineSnapshotValues();
15367 boolean CheckEngineSnapshotSingle(void)
15369 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15370 snapshot_level_nr == level_nr);
15373 boolean CheckEngineSnapshotList(void)
15375 return CheckSnapshotList();
15379 // ---------- new game button stuff -------------------------------------------
15386 boolean *setup_value;
15387 boolean allowed_on_tape;
15389 } gamebutton_info[NUM_GAME_BUTTONS] =
15392 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15393 GAME_CTRL_ID_STOP, NULL,
15397 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15398 GAME_CTRL_ID_PAUSE, NULL,
15402 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15403 GAME_CTRL_ID_PLAY, NULL,
15407 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15408 GAME_CTRL_ID_UNDO, NULL,
15412 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15413 GAME_CTRL_ID_REDO, NULL,
15417 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15418 GAME_CTRL_ID_SAVE, NULL,
15422 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15423 GAME_CTRL_ID_PAUSE2, NULL,
15427 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15428 GAME_CTRL_ID_LOAD, NULL,
15432 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15433 GAME_CTRL_ID_PANEL_STOP, NULL,
15437 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15438 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15439 FALSE, "pause game"
15442 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15443 GAME_CTRL_ID_PANEL_PLAY, NULL,
15447 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15448 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15449 TRUE, "background music on/off"
15452 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15453 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15454 TRUE, "sound loops on/off"
15457 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15458 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15459 TRUE, "normal sounds on/off"
15462 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15463 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15464 FALSE, "background music on/off"
15467 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15468 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15469 FALSE, "sound loops on/off"
15472 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15473 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15474 FALSE, "normal sounds on/off"
15478 void CreateGameButtons(void)
15482 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15484 int graphic = gamebutton_info[i].graphic;
15485 struct GraphicInfo *gfx = &graphic_info[graphic];
15486 struct XY *pos = gamebutton_info[i].pos;
15487 struct GadgetInfo *gi;
15490 unsigned int event_mask;
15491 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15492 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15493 int base_x = (on_tape ? VX : DX);
15494 int base_y = (on_tape ? VY : DY);
15495 int gd_x = gfx->src_x;
15496 int gd_y = gfx->src_y;
15497 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15498 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15499 int gd_xa = gfx->src_x + gfx->active_xoffset;
15500 int gd_ya = gfx->src_y + gfx->active_yoffset;
15501 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15502 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15505 if (gfx->bitmap == NULL)
15507 game_gadget[id] = NULL;
15512 if (id == GAME_CTRL_ID_STOP ||
15513 id == GAME_CTRL_ID_PANEL_STOP ||
15514 id == GAME_CTRL_ID_PLAY ||
15515 id == GAME_CTRL_ID_PANEL_PLAY ||
15516 id == GAME_CTRL_ID_SAVE ||
15517 id == GAME_CTRL_ID_LOAD)
15519 button_type = GD_TYPE_NORMAL_BUTTON;
15521 event_mask = GD_EVENT_RELEASED;
15523 else if (id == GAME_CTRL_ID_UNDO ||
15524 id == GAME_CTRL_ID_REDO)
15526 button_type = GD_TYPE_NORMAL_BUTTON;
15528 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15532 button_type = GD_TYPE_CHECK_BUTTON;
15533 checked = (gamebutton_info[i].setup_value != NULL ?
15534 *gamebutton_info[i].setup_value : FALSE);
15535 event_mask = GD_EVENT_PRESSED;
15538 gi = CreateGadget(GDI_CUSTOM_ID, id,
15539 GDI_IMAGE_ID, graphic,
15540 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15541 GDI_X, base_x + GDI_ACTIVE_POS(pos->x),
15542 GDI_Y, base_y + GDI_ACTIVE_POS(pos->y),
15543 GDI_WIDTH, gfx->width,
15544 GDI_HEIGHT, gfx->height,
15545 GDI_TYPE, button_type,
15546 GDI_STATE, GD_BUTTON_UNPRESSED,
15547 GDI_CHECKED, checked,
15548 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15549 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15550 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15551 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15552 GDI_DIRECT_DRAW, FALSE,
15553 GDI_EVENT_MASK, event_mask,
15554 GDI_CALLBACK_ACTION, HandleGameButtons,
15558 Error(ERR_EXIT, "cannot create gadget");
15560 game_gadget[id] = gi;
15564 void FreeGameButtons(void)
15568 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15569 FreeGadget(game_gadget[i]);
15572 static void UnmapGameButtonsAtSamePosition(int id)
15576 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15578 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15579 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15580 UnmapGadget(game_gadget[i]);
15583 static void UnmapGameButtonsAtSamePosition_All(void)
15585 if (setup.show_snapshot_buttons)
15587 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15588 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15589 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15593 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15594 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15595 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15597 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15598 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15599 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15603 static void MapGameButtonsAtSamePosition(int id)
15607 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15609 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15610 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15611 MapGadget(game_gadget[i]);
15613 UnmapGameButtonsAtSamePosition_All();
15616 void MapUndoRedoButtons(void)
15618 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15619 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15621 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15622 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15624 ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, TRUE, GDI_END);
15627 void UnmapUndoRedoButtons(void)
15629 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15630 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15632 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15633 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15635 ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, FALSE, GDI_END);
15638 static void MapGameButtonsExt(boolean on_tape)
15642 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15643 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15644 i != GAME_CTRL_ID_UNDO &&
15645 i != GAME_CTRL_ID_REDO)
15646 MapGadget(game_gadget[i]);
15648 UnmapGameButtonsAtSamePosition_All();
15650 RedrawGameButtons();
15653 static void UnmapGameButtonsExt(boolean on_tape)
15657 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15658 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15659 UnmapGadget(game_gadget[i]);
15662 static void RedrawGameButtonsExt(boolean on_tape)
15666 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15667 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15668 RedrawGadget(game_gadget[i]);
15670 // RedrawGadget() may have set REDRAW_ALL if buttons are defined off-area
15671 redraw_mask &= ~REDRAW_ALL;
15674 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15679 gi->checked = state;
15682 static void RedrawSoundButtonGadget(int id)
15684 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15685 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15686 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15687 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15688 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15689 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15692 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15693 RedrawGadget(game_gadget[id2]);
15696 void MapGameButtons(void)
15698 MapGameButtonsExt(FALSE);
15701 void UnmapGameButtons(void)
15703 UnmapGameButtonsExt(FALSE);
15706 void RedrawGameButtons(void)
15708 RedrawGameButtonsExt(FALSE);
15711 void MapGameButtonsOnTape(void)
15713 MapGameButtonsExt(TRUE);
15716 void UnmapGameButtonsOnTape(void)
15718 UnmapGameButtonsExt(TRUE);
15721 void RedrawGameButtonsOnTape(void)
15723 RedrawGameButtonsExt(TRUE);
15726 static void GameUndoRedoExt(void)
15728 ClearPlayerAction();
15730 tape.pausing = TRUE;
15733 UpdateAndDisplayGameControlValues();
15735 DrawCompleteVideoDisplay();
15736 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
15737 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
15738 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
15743 static void GameUndo(int steps)
15745 if (!CheckEngineSnapshotList())
15748 LoadEngineSnapshot_Undo(steps);
15753 static void GameRedo(int steps)
15755 if (!CheckEngineSnapshotList())
15758 LoadEngineSnapshot_Redo(steps);
15763 static void HandleGameButtonsExt(int id, int button)
15765 static boolean game_undo_executed = FALSE;
15766 int steps = BUTTON_STEPSIZE(button);
15767 boolean handle_game_buttons =
15768 (game_status == GAME_MODE_PLAYING ||
15769 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
15771 if (!handle_game_buttons)
15776 case GAME_CTRL_ID_STOP:
15777 case GAME_CTRL_ID_PANEL_STOP:
15778 if (game_status == GAME_MODE_MAIN)
15784 RequestQuitGame(TRUE);
15788 case GAME_CTRL_ID_PAUSE:
15789 case GAME_CTRL_ID_PAUSE2:
15790 case GAME_CTRL_ID_PANEL_PAUSE:
15791 if (network.enabled && game_status == GAME_MODE_PLAYING)
15794 SendToServer_ContinuePlaying();
15796 SendToServer_PausePlaying();
15799 TapeTogglePause(TAPE_TOGGLE_MANUAL);
15801 game_undo_executed = FALSE;
15805 case GAME_CTRL_ID_PLAY:
15806 case GAME_CTRL_ID_PANEL_PLAY:
15807 if (game_status == GAME_MODE_MAIN)
15809 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15811 else if (tape.pausing)
15813 if (network.enabled)
15814 SendToServer_ContinuePlaying();
15816 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
15820 case GAME_CTRL_ID_UNDO:
15821 // Important: When using "save snapshot when collecting an item" mode,
15822 // load last (current) snapshot for first "undo" after pressing "pause"
15823 // (else the last-but-one snapshot would be loaded, because the snapshot
15824 // pointer already points to the last snapshot when pressing "pause",
15825 // which is fine for "every step/move" mode, but not for "every collect")
15826 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15827 !game_undo_executed)
15830 game_undo_executed = TRUE;
15835 case GAME_CTRL_ID_REDO:
15839 case GAME_CTRL_ID_SAVE:
15843 case GAME_CTRL_ID_LOAD:
15847 case SOUND_CTRL_ID_MUSIC:
15848 case SOUND_CTRL_ID_PANEL_MUSIC:
15849 if (setup.sound_music)
15851 setup.sound_music = FALSE;
15855 else if (audio.music_available)
15857 setup.sound = setup.sound_music = TRUE;
15859 SetAudioMode(setup.sound);
15861 if (game_status == GAME_MODE_PLAYING)
15865 RedrawSoundButtonGadget(id);
15869 case SOUND_CTRL_ID_LOOPS:
15870 case SOUND_CTRL_ID_PANEL_LOOPS:
15871 if (setup.sound_loops)
15872 setup.sound_loops = FALSE;
15873 else if (audio.loops_available)
15875 setup.sound = setup.sound_loops = TRUE;
15877 SetAudioMode(setup.sound);
15880 RedrawSoundButtonGadget(id);
15884 case SOUND_CTRL_ID_SIMPLE:
15885 case SOUND_CTRL_ID_PANEL_SIMPLE:
15886 if (setup.sound_simple)
15887 setup.sound_simple = FALSE;
15888 else if (audio.sound_available)
15890 setup.sound = setup.sound_simple = TRUE;
15892 SetAudioMode(setup.sound);
15895 RedrawSoundButtonGadget(id);
15904 static void HandleGameButtons(struct GadgetInfo *gi)
15906 HandleGameButtonsExt(gi->custom_id, gi->event.button);
15909 void HandleSoundButtonKeys(Key key)
15911 if (key == setup.shortcut.sound_simple)
15912 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
15913 else if (key == setup.shortcut.sound_loops)
15914 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
15915 else if (key == setup.shortcut.sound_music)
15916 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);