1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_INVENTORY_COUNT 2
93 #define GAME_PANEL_INVENTORY_FIRST_1 3
94 #define GAME_PANEL_INVENTORY_FIRST_2 4
95 #define GAME_PANEL_INVENTORY_FIRST_3 5
96 #define GAME_PANEL_INVENTORY_FIRST_4 6
97 #define GAME_PANEL_INVENTORY_FIRST_5 7
98 #define GAME_PANEL_INVENTORY_FIRST_6 8
99 #define GAME_PANEL_INVENTORY_FIRST_7 9
100 #define GAME_PANEL_INVENTORY_FIRST_8 10
101 #define GAME_PANEL_INVENTORY_LAST_1 11
102 #define GAME_PANEL_INVENTORY_LAST_2 12
103 #define GAME_PANEL_INVENTORY_LAST_3 13
104 #define GAME_PANEL_INVENTORY_LAST_4 14
105 #define GAME_PANEL_INVENTORY_LAST_5 15
106 #define GAME_PANEL_INVENTORY_LAST_6 16
107 #define GAME_PANEL_INVENTORY_LAST_7 17
108 #define GAME_PANEL_INVENTORY_LAST_8 18
109 #define GAME_PANEL_KEY_1 19
110 #define GAME_PANEL_KEY_2 20
111 #define GAME_PANEL_KEY_3 21
112 #define GAME_PANEL_KEY_4 22
113 #define GAME_PANEL_KEY_5 23
114 #define GAME_PANEL_KEY_6 24
115 #define GAME_PANEL_KEY_7 25
116 #define GAME_PANEL_KEY_8 26
117 #define GAME_PANEL_KEY_WHITE 27
118 #define GAME_PANEL_KEY_WHITE_COUNT 28
119 #define GAME_PANEL_SCORE 29
120 #define GAME_PANEL_HIGHSCORE 30
121 #define GAME_PANEL_TIME 31
122 #define GAME_PANEL_TIME_HH 32
123 #define GAME_PANEL_TIME_MM 33
124 #define GAME_PANEL_TIME_SS 34
125 #define GAME_PANEL_TIME_ANIM 35
126 #define GAME_PANEL_HEALTH 36
127 #define GAME_PANEL_HEALTH_ANIM 37
128 #define GAME_PANEL_FRAME 38
129 #define GAME_PANEL_SHIELD_NORMAL 39
130 #define GAME_PANEL_SHIELD_NORMAL_TIME 40
131 #define GAME_PANEL_SHIELD_DEADLY 41
132 #define GAME_PANEL_SHIELD_DEADLY_TIME 42
133 #define GAME_PANEL_EXIT 43
134 #define GAME_PANEL_EMC_MAGIC_BALL 44
135 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 45
136 #define GAME_PANEL_LIGHT_SWITCH 46
137 #define GAME_PANEL_LIGHT_SWITCH_TIME 47
138 #define GAME_PANEL_TIMEGATE_SWITCH 48
139 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 49
140 #define GAME_PANEL_SWITCHGATE_SWITCH 50
141 #define GAME_PANEL_EMC_LENSES 51
142 #define GAME_PANEL_EMC_LENSES_TIME 52
143 #define GAME_PANEL_EMC_MAGNIFIER 53
144 #define GAME_PANEL_EMC_MAGNIFIER_TIME 54
145 #define GAME_PANEL_BALLOON_SWITCH 55
146 #define GAME_PANEL_DYNABOMB_NUMBER 56
147 #define GAME_PANEL_DYNABOMB_SIZE 57
148 #define GAME_PANEL_DYNABOMB_POWER 58
149 #define GAME_PANEL_PENGUINS 59
150 #define GAME_PANEL_SOKOBAN_OBJECTS 60
151 #define GAME_PANEL_SOKOBAN_FIELDS 61
152 #define GAME_PANEL_ROBOT_WHEEL 62
153 #define GAME_PANEL_CONVEYOR_BELT_1 63
154 #define GAME_PANEL_CONVEYOR_BELT_2 64
155 #define GAME_PANEL_CONVEYOR_BELT_3 65
156 #define GAME_PANEL_CONVEYOR_BELT_4 66
157 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 67
158 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 68
159 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 69
160 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 70
161 #define GAME_PANEL_MAGIC_WALL 71
162 #define GAME_PANEL_MAGIC_WALL_TIME 72
163 #define GAME_PANEL_GRAVITY_STATE 73
164 #define GAME_PANEL_GRAPHIC_1 74
165 #define GAME_PANEL_GRAPHIC_2 75
166 #define GAME_PANEL_GRAPHIC_3 76
167 #define GAME_PANEL_GRAPHIC_4 77
168 #define GAME_PANEL_GRAPHIC_5 78
169 #define GAME_PANEL_GRAPHIC_6 79
170 #define GAME_PANEL_GRAPHIC_7 80
171 #define GAME_PANEL_GRAPHIC_8 81
172 #define GAME_PANEL_ELEMENT_1 82
173 #define GAME_PANEL_ELEMENT_2 83
174 #define GAME_PANEL_ELEMENT_3 84
175 #define GAME_PANEL_ELEMENT_4 85
176 #define GAME_PANEL_ELEMENT_5 86
177 #define GAME_PANEL_ELEMENT_6 87
178 #define GAME_PANEL_ELEMENT_7 88
179 #define GAME_PANEL_ELEMENT_8 89
180 #define GAME_PANEL_ELEMENT_COUNT_1 90
181 #define GAME_PANEL_ELEMENT_COUNT_2 91
182 #define GAME_PANEL_ELEMENT_COUNT_3 92
183 #define GAME_PANEL_ELEMENT_COUNT_4 93
184 #define GAME_PANEL_ELEMENT_COUNT_5 94
185 #define GAME_PANEL_ELEMENT_COUNT_6 95
186 #define GAME_PANEL_ELEMENT_COUNT_7 96
187 #define GAME_PANEL_ELEMENT_COUNT_8 97
188 #define GAME_PANEL_CE_SCORE_1 98
189 #define GAME_PANEL_CE_SCORE_2 99
190 #define GAME_PANEL_CE_SCORE_3 100
191 #define GAME_PANEL_CE_SCORE_4 101
192 #define GAME_PANEL_CE_SCORE_5 102
193 #define GAME_PANEL_CE_SCORE_6 103
194 #define GAME_PANEL_CE_SCORE_7 104
195 #define GAME_PANEL_CE_SCORE_8 105
196 #define GAME_PANEL_CE_SCORE_1_ELEMENT 106
197 #define GAME_PANEL_CE_SCORE_2_ELEMENT 107
198 #define GAME_PANEL_CE_SCORE_3_ELEMENT 108
199 #define GAME_PANEL_CE_SCORE_4_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_5_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_6_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_7_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_8_ELEMENT 113
204 #define GAME_PANEL_PLAYER_NAME 114
205 #define GAME_PANEL_LEVEL_NAME 115
206 #define GAME_PANEL_LEVEL_AUTHOR 116
208 #define NUM_GAME_PANEL_CONTROLS 117
210 struct GamePanelOrderInfo
216 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
218 struct GamePanelControlInfo
222 struct TextPosInfo *pos;
225 int graphic, graphic_active;
227 int value, last_value;
228 int frame, last_frame;
233 static struct GamePanelControlInfo game_panel_controls[] =
236 GAME_PANEL_LEVEL_NUMBER,
237 &game.panel.level_number,
246 GAME_PANEL_INVENTORY_COUNT,
247 &game.panel.inventory_count,
251 GAME_PANEL_INVENTORY_FIRST_1,
252 &game.panel.inventory_first[0],
256 GAME_PANEL_INVENTORY_FIRST_2,
257 &game.panel.inventory_first[1],
261 GAME_PANEL_INVENTORY_FIRST_3,
262 &game.panel.inventory_first[2],
266 GAME_PANEL_INVENTORY_FIRST_4,
267 &game.panel.inventory_first[3],
271 GAME_PANEL_INVENTORY_FIRST_5,
272 &game.panel.inventory_first[4],
276 GAME_PANEL_INVENTORY_FIRST_6,
277 &game.panel.inventory_first[5],
281 GAME_PANEL_INVENTORY_FIRST_7,
282 &game.panel.inventory_first[6],
286 GAME_PANEL_INVENTORY_FIRST_8,
287 &game.panel.inventory_first[7],
291 GAME_PANEL_INVENTORY_LAST_1,
292 &game.panel.inventory_last[0],
296 GAME_PANEL_INVENTORY_LAST_2,
297 &game.panel.inventory_last[1],
301 GAME_PANEL_INVENTORY_LAST_3,
302 &game.panel.inventory_last[2],
306 GAME_PANEL_INVENTORY_LAST_4,
307 &game.panel.inventory_last[3],
311 GAME_PANEL_INVENTORY_LAST_5,
312 &game.panel.inventory_last[4],
316 GAME_PANEL_INVENTORY_LAST_6,
317 &game.panel.inventory_last[5],
321 GAME_PANEL_INVENTORY_LAST_7,
322 &game.panel.inventory_last[6],
326 GAME_PANEL_INVENTORY_LAST_8,
327 &game.panel.inventory_last[7],
371 GAME_PANEL_KEY_WHITE,
372 &game.panel.key_white,
376 GAME_PANEL_KEY_WHITE_COUNT,
377 &game.panel.key_white_count,
386 GAME_PANEL_HIGHSCORE,
387 &game.panel.highscore,
411 GAME_PANEL_TIME_ANIM,
412 &game.panel.time_anim,
415 IMG_GFX_GAME_PANEL_TIME_ANIM,
416 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
424 GAME_PANEL_HEALTH_ANIM,
425 &game.panel.health_anim,
428 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
429 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
437 GAME_PANEL_SHIELD_NORMAL,
438 &game.panel.shield_normal,
442 GAME_PANEL_SHIELD_NORMAL_TIME,
443 &game.panel.shield_normal_time,
447 GAME_PANEL_SHIELD_DEADLY,
448 &game.panel.shield_deadly,
452 GAME_PANEL_SHIELD_DEADLY_TIME,
453 &game.panel.shield_deadly_time,
462 GAME_PANEL_EMC_MAGIC_BALL,
463 &game.panel.emc_magic_ball,
467 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
468 &game.panel.emc_magic_ball_switch,
472 GAME_PANEL_LIGHT_SWITCH,
473 &game.panel.light_switch,
477 GAME_PANEL_LIGHT_SWITCH_TIME,
478 &game.panel.light_switch_time,
482 GAME_PANEL_TIMEGATE_SWITCH,
483 &game.panel.timegate_switch,
487 GAME_PANEL_TIMEGATE_SWITCH_TIME,
488 &game.panel.timegate_switch_time,
492 GAME_PANEL_SWITCHGATE_SWITCH,
493 &game.panel.switchgate_switch,
497 GAME_PANEL_EMC_LENSES,
498 &game.panel.emc_lenses,
502 GAME_PANEL_EMC_LENSES_TIME,
503 &game.panel.emc_lenses_time,
507 GAME_PANEL_EMC_MAGNIFIER,
508 &game.panel.emc_magnifier,
512 GAME_PANEL_EMC_MAGNIFIER_TIME,
513 &game.panel.emc_magnifier_time,
517 GAME_PANEL_BALLOON_SWITCH,
518 &game.panel.balloon_switch,
522 GAME_PANEL_DYNABOMB_NUMBER,
523 &game.panel.dynabomb_number,
527 GAME_PANEL_DYNABOMB_SIZE,
528 &game.panel.dynabomb_size,
532 GAME_PANEL_DYNABOMB_POWER,
533 &game.panel.dynabomb_power,
538 &game.panel.penguins,
542 GAME_PANEL_SOKOBAN_OBJECTS,
543 &game.panel.sokoban_objects,
547 GAME_PANEL_SOKOBAN_FIELDS,
548 &game.panel.sokoban_fields,
552 GAME_PANEL_ROBOT_WHEEL,
553 &game.panel.robot_wheel,
557 GAME_PANEL_CONVEYOR_BELT_1,
558 &game.panel.conveyor_belt[0],
562 GAME_PANEL_CONVEYOR_BELT_2,
563 &game.panel.conveyor_belt[1],
567 GAME_PANEL_CONVEYOR_BELT_3,
568 &game.panel.conveyor_belt[2],
572 GAME_PANEL_CONVEYOR_BELT_4,
573 &game.panel.conveyor_belt[3],
577 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
578 &game.panel.conveyor_belt_switch[0],
582 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
583 &game.panel.conveyor_belt_switch[1],
587 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
588 &game.panel.conveyor_belt_switch[2],
592 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
593 &game.panel.conveyor_belt_switch[3],
597 GAME_PANEL_MAGIC_WALL,
598 &game.panel.magic_wall,
602 GAME_PANEL_MAGIC_WALL_TIME,
603 &game.panel.magic_wall_time,
607 GAME_PANEL_GRAVITY_STATE,
608 &game.panel.gravity_state,
612 GAME_PANEL_GRAPHIC_1,
613 &game.panel.graphic[0],
617 GAME_PANEL_GRAPHIC_2,
618 &game.panel.graphic[1],
622 GAME_PANEL_GRAPHIC_3,
623 &game.panel.graphic[2],
627 GAME_PANEL_GRAPHIC_4,
628 &game.panel.graphic[3],
632 GAME_PANEL_GRAPHIC_5,
633 &game.panel.graphic[4],
637 GAME_PANEL_GRAPHIC_6,
638 &game.panel.graphic[5],
642 GAME_PANEL_GRAPHIC_7,
643 &game.panel.graphic[6],
647 GAME_PANEL_GRAPHIC_8,
648 &game.panel.graphic[7],
652 GAME_PANEL_ELEMENT_1,
653 &game.panel.element[0],
657 GAME_PANEL_ELEMENT_2,
658 &game.panel.element[1],
662 GAME_PANEL_ELEMENT_3,
663 &game.panel.element[2],
667 GAME_PANEL_ELEMENT_4,
668 &game.panel.element[3],
672 GAME_PANEL_ELEMENT_5,
673 &game.panel.element[4],
677 GAME_PANEL_ELEMENT_6,
678 &game.panel.element[5],
682 GAME_PANEL_ELEMENT_7,
683 &game.panel.element[6],
687 GAME_PANEL_ELEMENT_8,
688 &game.panel.element[7],
692 GAME_PANEL_ELEMENT_COUNT_1,
693 &game.panel.element_count[0],
697 GAME_PANEL_ELEMENT_COUNT_2,
698 &game.panel.element_count[1],
702 GAME_PANEL_ELEMENT_COUNT_3,
703 &game.panel.element_count[2],
707 GAME_PANEL_ELEMENT_COUNT_4,
708 &game.panel.element_count[3],
712 GAME_PANEL_ELEMENT_COUNT_5,
713 &game.panel.element_count[4],
717 GAME_PANEL_ELEMENT_COUNT_6,
718 &game.panel.element_count[5],
722 GAME_PANEL_ELEMENT_COUNT_7,
723 &game.panel.element_count[6],
727 GAME_PANEL_ELEMENT_COUNT_8,
728 &game.panel.element_count[7],
732 GAME_PANEL_CE_SCORE_1,
733 &game.panel.ce_score[0],
737 GAME_PANEL_CE_SCORE_2,
738 &game.panel.ce_score[1],
742 GAME_PANEL_CE_SCORE_3,
743 &game.panel.ce_score[2],
747 GAME_PANEL_CE_SCORE_4,
748 &game.panel.ce_score[3],
752 GAME_PANEL_CE_SCORE_5,
753 &game.panel.ce_score[4],
757 GAME_PANEL_CE_SCORE_6,
758 &game.panel.ce_score[5],
762 GAME_PANEL_CE_SCORE_7,
763 &game.panel.ce_score[6],
767 GAME_PANEL_CE_SCORE_8,
768 &game.panel.ce_score[7],
772 GAME_PANEL_CE_SCORE_1_ELEMENT,
773 &game.panel.ce_score_element[0],
777 GAME_PANEL_CE_SCORE_2_ELEMENT,
778 &game.panel.ce_score_element[1],
782 GAME_PANEL_CE_SCORE_3_ELEMENT,
783 &game.panel.ce_score_element[2],
787 GAME_PANEL_CE_SCORE_4_ELEMENT,
788 &game.panel.ce_score_element[3],
792 GAME_PANEL_CE_SCORE_5_ELEMENT,
793 &game.panel.ce_score_element[4],
797 GAME_PANEL_CE_SCORE_6_ELEMENT,
798 &game.panel.ce_score_element[5],
802 GAME_PANEL_CE_SCORE_7_ELEMENT,
803 &game.panel.ce_score_element[6],
807 GAME_PANEL_CE_SCORE_8_ELEMENT,
808 &game.panel.ce_score_element[7],
812 GAME_PANEL_PLAYER_NAME,
813 &game.panel.player_name,
817 GAME_PANEL_LEVEL_NAME,
818 &game.panel.level_name,
822 GAME_PANEL_LEVEL_AUTHOR,
823 &game.panel.level_author,
834 // values for delayed check of falling and moving elements and for collision
835 #define CHECK_DELAY_MOVING 3
836 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
837 #define CHECK_DELAY_COLLISION 2
838 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
840 // values for initial player move delay (initial delay counter value)
841 #define INITIAL_MOVE_DELAY_OFF -1
842 #define INITIAL_MOVE_DELAY_ON 0
844 // values for player movement speed (which is in fact a delay value)
845 #define MOVE_DELAY_MIN_SPEED 32
846 #define MOVE_DELAY_NORMAL_SPEED 8
847 #define MOVE_DELAY_HIGH_SPEED 4
848 #define MOVE_DELAY_MAX_SPEED 1
850 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
851 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
853 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
854 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
856 // values for scroll positions
857 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
858 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
860 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
861 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
864 // values for other actions
865 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
866 #define MOVE_STEPSIZE_MIN (1)
867 #define MOVE_STEPSIZE_MAX (TILEX)
869 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
870 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
872 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
874 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
875 RND(element_info[e].push_delay_random))
876 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
877 RND(element_info[e].drop_delay_random))
878 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
879 RND(element_info[e].move_delay_random))
880 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
881 (element_info[e].move_delay_random))
882 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
883 RND(element_info[e].ce_value_random_initial))
884 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
885 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
886 RND((c)->delay_random * (c)->delay_frames))
887 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
888 RND((c)->delay_random))
891 #define GET_VALID_RUNTIME_ELEMENT(e) \
892 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
894 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
895 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
896 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
897 (be) + (e) - EL_SELF)
899 #define GET_PLAYER_FROM_BITS(p) \
900 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
902 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
903 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
904 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
905 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
906 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
907 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
908 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
909 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
910 RESOLVED_REFERENCE_ELEMENT(be, e) : \
913 #define CAN_GROW_INTO(e) \
914 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
916 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
917 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
922 (CAN_MOVE_INTO_ACID(e) && \
923 Feld[x][y] == EL_ACID) || \
926 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
927 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
928 (CAN_MOVE_INTO_ACID(e) && \
929 Feld[x][y] == EL_ACID) || \
932 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
933 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
935 (CAN_MOVE_INTO_ACID(e) && \
936 Feld[x][y] == EL_ACID) || \
937 (DONT_COLLIDE_WITH(e) && \
939 !PLAYER_ENEMY_PROTECTED(x, y))))
941 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
942 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
944 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
945 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
947 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
948 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Feld[x][y] == EL_EMC_PLANT)
950 #define ANDROID_CAN_CLONE_FIELD(x, y) \
951 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Feld[x][y]) || \
952 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
954 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
955 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
957 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
958 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Feld[x][y] == EL_DIAMOND)
960 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
961 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Feld[x][y]))
963 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Feld[x][y]))
966 #define PIG_CAN_ENTER_FIELD(e, x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Feld[x][y]))
969 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Feld[x][y] == EL_EXIT_OPEN || \
971 Feld[x][y] == EL_EM_EXIT_OPEN || \
972 Feld[x][y] == EL_STEEL_EXIT_OPEN || \
973 Feld[x][y] == EL_EM_STEEL_EXIT_OPEN || \
974 IS_FOOD_PENGUIN(Feld[x][y])))
975 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
976 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
978 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
979 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
981 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
982 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
984 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
985 (IN_LEV_FIELD(x, y) && (Feld[x][y] == EL_EMC_SPRING_BUMPER || \
986 Feld[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
988 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
990 #define CE_ENTER_FIELD_COND(e, x, y) \
991 (!IS_PLAYER(x, y) && \
992 IS_EQUAL_OR_IN_GROUP(Feld[x][y], MOVE_ENTER_EL(e)))
994 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
995 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
997 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
998 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1000 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1001 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1002 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1003 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1005 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1007 // game button identifiers
1008 #define GAME_CTRL_ID_STOP 0
1009 #define GAME_CTRL_ID_PAUSE 1
1010 #define GAME_CTRL_ID_PLAY 2
1011 #define GAME_CTRL_ID_UNDO 3
1012 #define GAME_CTRL_ID_REDO 4
1013 #define GAME_CTRL_ID_SAVE 5
1014 #define GAME_CTRL_ID_PAUSE2 6
1015 #define GAME_CTRL_ID_LOAD 7
1016 #define GAME_CTRL_ID_PANEL_STOP 8
1017 #define GAME_CTRL_ID_PANEL_PAUSE 9
1018 #define GAME_CTRL_ID_PANEL_PLAY 10
1019 #define SOUND_CTRL_ID_MUSIC 11
1020 #define SOUND_CTRL_ID_LOOPS 12
1021 #define SOUND_CTRL_ID_SIMPLE 13
1022 #define SOUND_CTRL_ID_PANEL_MUSIC 14
1023 #define SOUND_CTRL_ID_PANEL_LOOPS 15
1024 #define SOUND_CTRL_ID_PANEL_SIMPLE 16
1026 #define NUM_GAME_BUTTONS 17
1029 // forward declaration for internal use
1031 static void CreateField(int, int, int);
1033 static void ResetGfxAnimation(int, int);
1035 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1036 static void AdvanceFrameAndPlayerCounters(int);
1038 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1039 static boolean MovePlayer(struct PlayerInfo *, int, int);
1040 static void ScrollPlayer(struct PlayerInfo *, int);
1041 static void ScrollScreen(struct PlayerInfo *, int);
1043 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1044 static boolean DigFieldByCE(int, int, int);
1045 static boolean SnapField(struct PlayerInfo *, int, int);
1046 static boolean DropElement(struct PlayerInfo *);
1048 static void InitBeltMovement(void);
1049 static void CloseAllOpenTimegates(void);
1050 static void CheckGravityMovement(struct PlayerInfo *);
1051 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1052 static void KillPlayerUnlessEnemyProtected(int, int);
1053 static void KillPlayerUnlessExplosionProtected(int, int);
1055 static void TestIfPlayerTouchesCustomElement(int, int);
1056 static void TestIfElementTouchesCustomElement(int, int);
1057 static void TestIfElementHitsCustomElement(int, int, int);
1059 static void HandleElementChange(int, int, int);
1060 static void ExecuteCustomElementAction(int, int, int, int);
1061 static boolean ChangeElement(int, int, int, int);
1063 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1064 #define CheckTriggeredElementChange(x, y, e, ev) \
1065 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1066 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1067 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1068 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1069 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1070 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1071 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1073 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1074 #define CheckElementChange(x, y, e, te, ev) \
1075 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1076 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1077 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1078 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1079 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1081 static void PlayLevelSound(int, int, int);
1082 static void PlayLevelSoundNearest(int, int, int);
1083 static void PlayLevelSoundAction(int, int, int);
1084 static void PlayLevelSoundElementAction(int, int, int, int);
1085 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1086 static void PlayLevelSoundActionIfLoop(int, int, int);
1087 static void StopLevelSoundActionIfLoop(int, int, int);
1088 static void PlayLevelMusic(void);
1089 static void FadeLevelSoundsAndMusic(void);
1091 static void HandleGameButtons(struct GadgetInfo *);
1093 int AmoebeNachbarNr(int, int);
1094 void AmoebeUmwandeln(int, int);
1095 void ContinueMoving(int, int);
1096 void Bang(int, int);
1097 void InitMovDir(int, int);
1098 void InitAmoebaNr(int, int);
1099 int NewHiScore(int);
1101 void TestIfGoodThingHitsBadThing(int, int, int);
1102 void TestIfBadThingHitsGoodThing(int, int, int);
1103 void TestIfPlayerTouchesBadThing(int, int);
1104 void TestIfPlayerRunsIntoBadThing(int, int, int);
1105 void TestIfBadThingTouchesPlayer(int, int);
1106 void TestIfBadThingRunsIntoPlayer(int, int, int);
1107 void TestIfFriendTouchesBadThing(int, int);
1108 void TestIfBadThingTouchesFriend(int, int);
1109 void TestIfBadThingTouchesOtherBadThing(int, int);
1110 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1112 void KillPlayer(struct PlayerInfo *);
1113 void BuryPlayer(struct PlayerInfo *);
1114 void RemovePlayer(struct PlayerInfo *);
1115 void ExitPlayer(struct PlayerInfo *);
1117 static int getInvisibleActiveFromInvisibleElement(int);
1118 static int getInvisibleFromInvisibleActiveElement(int);
1120 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1122 // for detection of endless loops, caused by custom element programming
1123 // (using maximal playfield width x 10 is just a rough approximation)
1124 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1126 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1128 if (recursion_loop_detected) \
1131 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1133 recursion_loop_detected = TRUE; \
1134 recursion_loop_element = (e); \
1137 recursion_loop_depth++; \
1140 #define RECURSION_LOOP_DETECTION_END() \
1142 recursion_loop_depth--; \
1145 static int recursion_loop_depth;
1146 static boolean recursion_loop_detected;
1147 static boolean recursion_loop_element;
1149 static int map_player_action[MAX_PLAYERS];
1152 // ----------------------------------------------------------------------------
1153 // definition of elements that automatically change to other elements after
1154 // a specified time, eventually calling a function when changing
1155 // ----------------------------------------------------------------------------
1157 // forward declaration for changer functions
1158 static void InitBuggyBase(int, int);
1159 static void WarnBuggyBase(int, int);
1161 static void InitTrap(int, int);
1162 static void ActivateTrap(int, int);
1163 static void ChangeActiveTrap(int, int);
1165 static void InitRobotWheel(int, int);
1166 static void RunRobotWheel(int, int);
1167 static void StopRobotWheel(int, int);
1169 static void InitTimegateWheel(int, int);
1170 static void RunTimegateWheel(int, int);
1172 static void InitMagicBallDelay(int, int);
1173 static void ActivateMagicBall(int, int);
1175 struct ChangingElementInfo
1180 void (*pre_change_function)(int x, int y);
1181 void (*change_function)(int x, int y);
1182 void (*post_change_function)(int x, int y);
1185 static struct ChangingElementInfo change_delay_list[] =
1220 EL_STEEL_EXIT_OPENING,
1228 EL_STEEL_EXIT_CLOSING,
1229 EL_STEEL_EXIT_CLOSED,
1252 EL_EM_STEEL_EXIT_OPENING,
1253 EL_EM_STEEL_EXIT_OPEN,
1260 EL_EM_STEEL_EXIT_CLOSING,
1284 EL_SWITCHGATE_OPENING,
1292 EL_SWITCHGATE_CLOSING,
1293 EL_SWITCHGATE_CLOSED,
1300 EL_TIMEGATE_OPENING,
1308 EL_TIMEGATE_CLOSING,
1317 EL_ACID_SPLASH_LEFT,
1325 EL_ACID_SPLASH_RIGHT,
1334 EL_SP_BUGGY_BASE_ACTIVATING,
1341 EL_SP_BUGGY_BASE_ACTIVATING,
1342 EL_SP_BUGGY_BASE_ACTIVE,
1349 EL_SP_BUGGY_BASE_ACTIVE,
1373 EL_ROBOT_WHEEL_ACTIVE,
1381 EL_TIMEGATE_SWITCH_ACTIVE,
1389 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1390 EL_DC_TIMEGATE_SWITCH,
1397 EL_EMC_MAGIC_BALL_ACTIVE,
1398 EL_EMC_MAGIC_BALL_ACTIVE,
1405 EL_EMC_SPRING_BUMPER_ACTIVE,
1406 EL_EMC_SPRING_BUMPER,
1413 EL_DIAGONAL_SHRINKING,
1421 EL_DIAGONAL_GROWING,
1442 int push_delay_fixed, push_delay_random;
1446 { EL_SPRING, 0, 0 },
1447 { EL_BALLOON, 0, 0 },
1449 { EL_SOKOBAN_OBJECT, 2, 0 },
1450 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1451 { EL_SATELLITE, 2, 0 },
1452 { EL_SP_DISK_YELLOW, 2, 0 },
1454 { EL_UNDEFINED, 0, 0 },
1462 move_stepsize_list[] =
1464 { EL_AMOEBA_DROP, 2 },
1465 { EL_AMOEBA_DROPPING, 2 },
1466 { EL_QUICKSAND_FILLING, 1 },
1467 { EL_QUICKSAND_EMPTYING, 1 },
1468 { EL_QUICKSAND_FAST_FILLING, 2 },
1469 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1470 { EL_MAGIC_WALL_FILLING, 2 },
1471 { EL_MAGIC_WALL_EMPTYING, 2 },
1472 { EL_BD_MAGIC_WALL_FILLING, 2 },
1473 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1474 { EL_DC_MAGIC_WALL_FILLING, 2 },
1475 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1477 { EL_UNDEFINED, 0 },
1485 collect_count_list[] =
1488 { EL_BD_DIAMOND, 1 },
1489 { EL_EMERALD_YELLOW, 1 },
1490 { EL_EMERALD_RED, 1 },
1491 { EL_EMERALD_PURPLE, 1 },
1493 { EL_SP_INFOTRON, 1 },
1497 { EL_UNDEFINED, 0 },
1505 access_direction_list[] =
1507 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1508 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1509 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1510 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1511 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1512 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1513 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1514 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1515 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1516 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1517 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1519 { EL_SP_PORT_LEFT, MV_RIGHT },
1520 { EL_SP_PORT_RIGHT, MV_LEFT },
1521 { EL_SP_PORT_UP, MV_DOWN },
1522 { EL_SP_PORT_DOWN, MV_UP },
1523 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1524 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1525 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1526 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1527 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1528 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1529 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1530 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1531 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1532 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1533 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1534 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1535 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1536 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1537 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1539 { EL_UNDEFINED, MV_NONE }
1542 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1544 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1545 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1546 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Feld[x][y]) || \
1547 IS_JUST_CHANGING(x, y))
1549 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1551 // static variables for playfield scan mode (scanning forward or backward)
1552 static int playfield_scan_start_x = 0;
1553 static int playfield_scan_start_y = 0;
1554 static int playfield_scan_delta_x = 1;
1555 static int playfield_scan_delta_y = 1;
1557 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1558 (y) >= 0 && (y) <= lev_fieldy - 1; \
1559 (y) += playfield_scan_delta_y) \
1560 for ((x) = playfield_scan_start_x; \
1561 (x) >= 0 && (x) <= lev_fieldx - 1; \
1562 (x) += playfield_scan_delta_x)
1565 void DEBUG_SetMaximumDynamite(void)
1569 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1570 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1571 local_player->inventory_element[local_player->inventory_size++] =
1576 static void InitPlayfieldScanModeVars(void)
1578 if (game.use_reverse_scan_direction)
1580 playfield_scan_start_x = lev_fieldx - 1;
1581 playfield_scan_start_y = lev_fieldy - 1;
1583 playfield_scan_delta_x = -1;
1584 playfield_scan_delta_y = -1;
1588 playfield_scan_start_x = 0;
1589 playfield_scan_start_y = 0;
1591 playfield_scan_delta_x = 1;
1592 playfield_scan_delta_y = 1;
1596 static void InitPlayfieldScanMode(int mode)
1598 game.use_reverse_scan_direction =
1599 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1601 InitPlayfieldScanModeVars();
1604 static int get_move_delay_from_stepsize(int move_stepsize)
1607 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1609 // make sure that stepsize value is always a power of 2
1610 move_stepsize = (1 << log_2(move_stepsize));
1612 return TILEX / move_stepsize;
1615 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1618 int player_nr = player->index_nr;
1619 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1620 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1622 // do no immediately change move delay -- the player might just be moving
1623 player->move_delay_value_next = move_delay;
1625 // information if player can move must be set separately
1626 player->cannot_move = cannot_move;
1630 player->move_delay = game.initial_move_delay[player_nr];
1631 player->move_delay_value = game.initial_move_delay_value[player_nr];
1633 player->move_delay_value_next = -1;
1635 player->move_delay_reset_counter = 0;
1639 void GetPlayerConfig(void)
1641 GameFrameDelay = setup.game_frame_delay;
1643 if (!audio.sound_available)
1644 setup.sound_simple = FALSE;
1646 if (!audio.loops_available)
1647 setup.sound_loops = FALSE;
1649 if (!audio.music_available)
1650 setup.sound_music = FALSE;
1652 if (!video.fullscreen_available)
1653 setup.fullscreen = FALSE;
1655 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1657 SetAudioMode(setup.sound);
1660 int GetElementFromGroupElement(int element)
1662 if (IS_GROUP_ELEMENT(element))
1664 struct ElementGroupInfo *group = element_info[element].group;
1665 int last_anim_random_frame = gfx.anim_random_frame;
1668 if (group->choice_mode == ANIM_RANDOM)
1669 gfx.anim_random_frame = RND(group->num_elements_resolved);
1671 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1672 group->choice_mode, 0,
1675 if (group->choice_mode == ANIM_RANDOM)
1676 gfx.anim_random_frame = last_anim_random_frame;
1678 group->choice_pos++;
1680 element = group->element_resolved[element_pos];
1686 static void IncrementSokobanFieldsNeeded(void)
1688 if (level.sb_fields_needed)
1689 game.sokoban_fields_still_needed++;
1692 static void IncrementSokobanObjectsNeeded(void)
1694 if (level.sb_objects_needed)
1695 game.sokoban_objects_still_needed++;
1698 static void DecrementSokobanFieldsNeeded(void)
1700 if (game.sokoban_fields_still_needed > 0)
1701 game.sokoban_fields_still_needed--;
1704 static void DecrementSokobanObjectsNeeded(void)
1706 if (game.sokoban_objects_still_needed > 0)
1707 game.sokoban_objects_still_needed--;
1710 static void InitPlayerField(int x, int y, int element, boolean init_game)
1712 if (element == EL_SP_MURPHY)
1716 if (stored_player[0].present)
1718 Feld[x][y] = EL_SP_MURPHY_CLONE;
1724 stored_player[0].initial_element = element;
1725 stored_player[0].use_murphy = TRUE;
1727 if (!level.use_artwork_element[0])
1728 stored_player[0].artwork_element = EL_SP_MURPHY;
1731 Feld[x][y] = EL_PLAYER_1;
1737 struct PlayerInfo *player = &stored_player[Feld[x][y] - EL_PLAYER_1];
1738 int jx = player->jx, jy = player->jy;
1740 player->present = TRUE;
1742 player->block_last_field = (element == EL_SP_MURPHY ?
1743 level.sp_block_last_field :
1744 level.block_last_field);
1746 // ---------- initialize player's last field block delay ------------------
1748 // always start with reliable default value (no adjustment needed)
1749 player->block_delay_adjustment = 0;
1751 // special case 1: in Supaplex, Murphy blocks last field one more frame
1752 if (player->block_last_field && element == EL_SP_MURPHY)
1753 player->block_delay_adjustment = 1;
1755 // special case 2: in game engines before 3.1.1, blocking was different
1756 if (game.use_block_last_field_bug)
1757 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1759 if (!network.enabled || player->connected_network)
1761 player->active = TRUE;
1763 // remove potentially duplicate players
1764 if (StorePlayer[jx][jy] == Feld[x][y])
1765 StorePlayer[jx][jy] = 0;
1767 StorePlayer[x][y] = Feld[x][y];
1769 #if DEBUG_INIT_PLAYER
1772 printf("- player element %d activated", player->element_nr);
1773 printf(" (local player is %d and currently %s)\n",
1774 local_player->element_nr,
1775 local_player->active ? "active" : "not active");
1780 Feld[x][y] = EL_EMPTY;
1782 player->jx = player->last_jx = x;
1783 player->jy = player->last_jy = y;
1788 int player_nr = GET_PLAYER_NR(element);
1789 struct PlayerInfo *player = &stored_player[player_nr];
1791 if (player->active && player->killed)
1792 player->reanimated = TRUE; // if player was just killed, reanimate him
1796 static void InitField(int x, int y, boolean init_game)
1798 int element = Feld[x][y];
1807 InitPlayerField(x, y, element, init_game);
1810 case EL_SOKOBAN_FIELD_PLAYER:
1811 element = Feld[x][y] = EL_PLAYER_1;
1812 InitField(x, y, init_game);
1814 element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1815 InitField(x, y, init_game);
1818 case EL_SOKOBAN_FIELD_EMPTY:
1819 IncrementSokobanFieldsNeeded();
1822 case EL_SOKOBAN_OBJECT:
1823 IncrementSokobanObjectsNeeded();
1827 if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID)
1828 Feld[x][y] = EL_ACID_POOL_TOPLEFT;
1829 else if (x > 0 && Feld[x-1][y] == EL_ACID)
1830 Feld[x][y] = EL_ACID_POOL_TOPRIGHT;
1831 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPLEFT)
1832 Feld[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1833 else if (y > 0 && Feld[x][y-1] == EL_ACID)
1834 Feld[x][y] = EL_ACID_POOL_BOTTOM;
1835 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1836 Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1845 case EL_SPACESHIP_RIGHT:
1846 case EL_SPACESHIP_UP:
1847 case EL_SPACESHIP_LEFT:
1848 case EL_SPACESHIP_DOWN:
1849 case EL_BD_BUTTERFLY:
1850 case EL_BD_BUTTERFLY_RIGHT:
1851 case EL_BD_BUTTERFLY_UP:
1852 case EL_BD_BUTTERFLY_LEFT:
1853 case EL_BD_BUTTERFLY_DOWN:
1855 case EL_BD_FIREFLY_RIGHT:
1856 case EL_BD_FIREFLY_UP:
1857 case EL_BD_FIREFLY_LEFT:
1858 case EL_BD_FIREFLY_DOWN:
1859 case EL_PACMAN_RIGHT:
1861 case EL_PACMAN_LEFT:
1862 case EL_PACMAN_DOWN:
1864 case EL_YAMYAM_LEFT:
1865 case EL_YAMYAM_RIGHT:
1867 case EL_YAMYAM_DOWN:
1868 case EL_DARK_YAMYAM:
1871 case EL_SP_SNIKSNAK:
1872 case EL_SP_ELECTRON:
1881 case EL_AMOEBA_FULL:
1886 case EL_AMOEBA_DROP:
1887 if (y == lev_fieldy - 1)
1889 Feld[x][y] = EL_AMOEBA_GROWING;
1890 Store[x][y] = EL_AMOEBA_WET;
1894 case EL_DYNAMITE_ACTIVE:
1895 case EL_SP_DISK_RED_ACTIVE:
1896 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1897 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1898 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1899 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1900 MovDelay[x][y] = 96;
1903 case EL_EM_DYNAMITE_ACTIVE:
1904 MovDelay[x][y] = 32;
1908 game.lights_still_needed++;
1912 game.friends_still_needed++;
1917 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1920 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1921 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1922 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1923 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1924 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1925 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1926 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1927 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1928 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1929 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1930 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1931 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1934 int belt_nr = getBeltNrFromBeltSwitchElement(Feld[x][y]);
1935 int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]);
1936 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[x][y]);
1938 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1940 game.belt_dir[belt_nr] = belt_dir;
1941 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1943 else // more than one switch -- set it like the first switch
1945 Feld[x][y] = Feld[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1950 case EL_LIGHT_SWITCH_ACTIVE:
1952 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1955 case EL_INVISIBLE_STEELWALL:
1956 case EL_INVISIBLE_WALL:
1957 case EL_INVISIBLE_SAND:
1958 if (game.light_time_left > 0 ||
1959 game.lenses_time_left > 0)
1960 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
1963 case EL_EMC_MAGIC_BALL:
1964 if (game.ball_state)
1965 Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1968 case EL_EMC_MAGIC_BALL_SWITCH:
1969 if (game.ball_state)
1970 Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1973 case EL_TRIGGER_PLAYER:
1974 case EL_TRIGGER_ELEMENT:
1975 case EL_TRIGGER_CE_VALUE:
1976 case EL_TRIGGER_CE_SCORE:
1978 case EL_ANY_ELEMENT:
1979 case EL_CURRENT_CE_VALUE:
1980 case EL_CURRENT_CE_SCORE:
1997 // reference elements should not be used on the playfield
1998 Feld[x][y] = EL_EMPTY;
2002 if (IS_CUSTOM_ELEMENT(element))
2004 if (CAN_MOVE(element))
2007 if (!element_info[element].use_last_ce_value || init_game)
2008 CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
2010 else if (IS_GROUP_ELEMENT(element))
2012 Feld[x][y] = GetElementFromGroupElement(element);
2014 InitField(x, y, init_game);
2021 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2024 static void InitField_WithBug1(int x, int y, boolean init_game)
2026 InitField(x, y, init_game);
2028 // not needed to call InitMovDir() -- already done by InitField()!
2029 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2030 CAN_MOVE(Feld[x][y]))
2034 static void InitField_WithBug2(int x, int y, boolean init_game)
2036 int old_element = Feld[x][y];
2038 InitField(x, y, init_game);
2040 // not needed to call InitMovDir() -- already done by InitField()!
2041 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2042 CAN_MOVE(old_element) &&
2043 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2046 /* this case is in fact a combination of not less than three bugs:
2047 first, it calls InitMovDir() for elements that can move, although this is
2048 already done by InitField(); then, it checks the element that was at this
2049 field _before_ the call to InitField() (which can change it); lastly, it
2050 was not called for "mole with direction" elements, which were treated as
2051 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2055 static int get_key_element_from_nr(int key_nr)
2057 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2058 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2059 EL_EM_KEY_1 : EL_KEY_1);
2061 return key_base_element + key_nr;
2064 static int get_next_dropped_element(struct PlayerInfo *player)
2066 return (player->inventory_size > 0 ?
2067 player->inventory_element[player->inventory_size - 1] :
2068 player->inventory_infinite_element != EL_UNDEFINED ?
2069 player->inventory_infinite_element :
2070 player->dynabombs_left > 0 ?
2071 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2075 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2077 // pos >= 0: get element from bottom of the stack;
2078 // pos < 0: get element from top of the stack
2082 int min_inventory_size = -pos;
2083 int inventory_pos = player->inventory_size - min_inventory_size;
2084 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2086 return (player->inventory_size >= min_inventory_size ?
2087 player->inventory_element[inventory_pos] :
2088 player->inventory_infinite_element != EL_UNDEFINED ?
2089 player->inventory_infinite_element :
2090 player->dynabombs_left >= min_dynabombs_left ?
2091 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2096 int min_dynabombs_left = pos + 1;
2097 int min_inventory_size = pos + 1 - player->dynabombs_left;
2098 int inventory_pos = pos - player->dynabombs_left;
2100 return (player->inventory_infinite_element != EL_UNDEFINED ?
2101 player->inventory_infinite_element :
2102 player->dynabombs_left >= min_dynabombs_left ?
2103 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2104 player->inventory_size >= min_inventory_size ?
2105 player->inventory_element[inventory_pos] :
2110 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2112 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2113 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2116 if (gpo1->sort_priority != gpo2->sort_priority)
2117 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2119 compare_result = gpo1->nr - gpo2->nr;
2121 return compare_result;
2124 int getPlayerInventorySize(int player_nr)
2126 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2127 return level.native_em_level->ply[player_nr]->dynamite;
2128 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2129 return level.native_sp_level->game_sp->red_disk_count;
2131 return stored_player[player_nr].inventory_size;
2134 static void InitGameControlValues(void)
2138 for (i = 0; game_panel_controls[i].nr != -1; i++)
2140 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2141 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2142 struct TextPosInfo *pos = gpc->pos;
2144 int type = gpc->type;
2148 Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
2149 Error(ERR_EXIT, "this should not happen -- please debug");
2152 // force update of game controls after initialization
2153 gpc->value = gpc->last_value = -1;
2154 gpc->frame = gpc->last_frame = -1;
2155 gpc->gfx_frame = -1;
2157 // determine panel value width for later calculation of alignment
2158 if (type == TYPE_INTEGER || type == TYPE_STRING)
2160 pos->width = pos->size * getFontWidth(pos->font);
2161 pos->height = getFontHeight(pos->font);
2163 else if (type == TYPE_ELEMENT)
2165 pos->width = pos->size;
2166 pos->height = pos->size;
2169 // fill structure for game panel draw order
2171 gpo->sort_priority = pos->sort_priority;
2174 // sort game panel controls according to sort_priority and control number
2175 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2176 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2179 static void UpdatePlayfieldElementCount(void)
2181 boolean use_element_count = FALSE;
2184 // first check if it is needed at all to calculate playfield element count
2185 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2186 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2187 use_element_count = TRUE;
2189 if (!use_element_count)
2192 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2193 element_info[i].element_count = 0;
2195 SCAN_PLAYFIELD(x, y)
2197 element_info[Feld[x][y]].element_count++;
2200 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2201 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2202 if (IS_IN_GROUP(j, i))
2203 element_info[EL_GROUP_START + i].element_count +=
2204 element_info[j].element_count;
2207 static void UpdateGameControlValues(void)
2210 int time = (game.LevelSolved ?
2211 game.LevelSolved_CountingTime :
2212 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2213 level.native_em_level->lev->time :
2214 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2215 level.native_sp_level->game_sp->time_played :
2216 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2217 game_mm.energy_left :
2218 game.no_time_limit ? TimePlayed : TimeLeft);
2219 int score = (game.LevelSolved ?
2220 game.LevelSolved_CountingScore :
2221 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2222 level.native_em_level->lev->score :
2223 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2224 level.native_sp_level->game_sp->score :
2225 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2228 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2229 level.native_em_level->lev->required :
2230 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2231 level.native_sp_level->game_sp->infotrons_still_needed :
2232 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2233 game_mm.kettles_still_needed :
2234 game.gems_still_needed);
2235 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2236 level.native_em_level->lev->required > 0 :
2237 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2238 level.native_sp_level->game_sp->infotrons_still_needed > 0 :
2239 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2240 game_mm.kettles_still_needed > 0 ||
2241 game_mm.lights_still_needed > 0 :
2242 game.gems_still_needed > 0 ||
2243 game.sokoban_fields_still_needed > 0 ||
2244 game.sokoban_objects_still_needed > 0 ||
2245 game.lights_still_needed > 0);
2246 int health = (game.LevelSolved ?
2247 game.LevelSolved_CountingHealth :
2248 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2249 MM_HEALTH(game_mm.laser_overload_value) :
2252 UpdatePlayfieldElementCount();
2254 // update game panel control values
2256 // used instead of "level_nr" (for network games)
2257 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2258 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2260 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2261 for (i = 0; i < MAX_NUM_KEYS; i++)
2262 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2263 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2264 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2266 if (game.centered_player_nr == -1)
2268 for (i = 0; i < MAX_PLAYERS; i++)
2270 // only one player in Supaplex game engine
2271 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2274 for (k = 0; k < MAX_NUM_KEYS; k++)
2276 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2278 if (level.native_em_level->ply[i]->keys & (1 << k))
2279 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2280 get_key_element_from_nr(k);
2282 else if (stored_player[i].key[k])
2283 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2284 get_key_element_from_nr(k);
2287 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2288 getPlayerInventorySize(i);
2290 if (stored_player[i].num_white_keys > 0)
2291 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2294 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2295 stored_player[i].num_white_keys;
2300 int player_nr = game.centered_player_nr;
2302 for (k = 0; k < MAX_NUM_KEYS; k++)
2304 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2306 if (level.native_em_level->ply[player_nr]->keys & (1 << k))
2307 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2308 get_key_element_from_nr(k);
2310 else if (stored_player[player_nr].key[k])
2311 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2312 get_key_element_from_nr(k);
2315 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2316 getPlayerInventorySize(player_nr);
2318 if (stored_player[player_nr].num_white_keys > 0)
2319 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2321 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2322 stored_player[player_nr].num_white_keys;
2325 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2327 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2328 get_inventory_element_from_pos(local_player, i);
2329 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2330 get_inventory_element_from_pos(local_player, -i - 1);
2333 game_panel_controls[GAME_PANEL_SCORE].value = score;
2334 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2336 game_panel_controls[GAME_PANEL_TIME].value = time;
2338 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2339 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2340 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2342 if (level.time == 0)
2343 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2345 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2347 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2348 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2350 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2352 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2353 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2355 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2356 local_player->shield_normal_time_left;
2357 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2358 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2360 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2361 local_player->shield_deadly_time_left;
2363 game_panel_controls[GAME_PANEL_EXIT].value =
2364 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2366 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2367 (game.ball_state ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2368 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2369 (game.ball_state ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2370 EL_EMC_MAGIC_BALL_SWITCH);
2372 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2373 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2374 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2375 game.light_time_left;
2377 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2378 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2379 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2380 game.timegate_time_left;
2382 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2383 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2385 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2386 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2387 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2388 game.lenses_time_left;
2390 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2391 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2392 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2393 game.magnify_time_left;
2395 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2396 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2397 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2398 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2399 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2400 EL_BALLOON_SWITCH_NONE);
2402 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2403 local_player->dynabomb_count;
2404 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2405 local_player->dynabomb_size;
2406 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2407 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2409 game_panel_controls[GAME_PANEL_PENGUINS].value =
2410 game.friends_still_needed;
2412 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2413 game.sokoban_objects_still_needed;
2414 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2415 game.sokoban_fields_still_needed;
2417 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2418 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2420 for (i = 0; i < NUM_BELTS; i++)
2422 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2423 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2424 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2425 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2426 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2429 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2430 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2431 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2432 game.magic_wall_time_left;
2434 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2435 local_player->gravity;
2437 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2438 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2440 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2441 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2442 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2443 game.panel.element[i].id : EL_UNDEFINED);
2445 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2446 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2447 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2448 element_info[game.panel.element_count[i].id].element_count : 0);
2450 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2451 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2452 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2453 element_info[game.panel.ce_score[i].id].collect_score : 0);
2455 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2456 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2457 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2458 element_info[game.panel.ce_score_element[i].id].collect_score :
2461 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2462 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2463 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2465 // update game panel control frames
2467 for (i = 0; game_panel_controls[i].nr != -1; i++)
2469 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2471 if (gpc->type == TYPE_ELEMENT)
2473 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2475 int last_anim_random_frame = gfx.anim_random_frame;
2476 int element = gpc->value;
2477 int graphic = el2panelimg(element);
2479 if (gpc->value != gpc->last_value)
2482 gpc->gfx_random = INIT_GFX_RANDOM();
2488 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2489 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2490 gpc->gfx_random = INIT_GFX_RANDOM();
2493 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2494 gfx.anim_random_frame = gpc->gfx_random;
2496 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2497 gpc->gfx_frame = element_info[element].collect_score;
2499 gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
2502 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2503 gfx.anim_random_frame = last_anim_random_frame;
2506 else if (gpc->type == TYPE_GRAPHIC)
2508 if (gpc->graphic != IMG_UNDEFINED)
2510 int last_anim_random_frame = gfx.anim_random_frame;
2511 int graphic = gpc->graphic;
2513 if (gpc->value != gpc->last_value)
2516 gpc->gfx_random = INIT_GFX_RANDOM();
2522 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2523 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2524 gpc->gfx_random = INIT_GFX_RANDOM();
2527 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2528 gfx.anim_random_frame = gpc->gfx_random;
2530 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2532 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2533 gfx.anim_random_frame = last_anim_random_frame;
2539 static void DisplayGameControlValues(void)
2541 boolean redraw_panel = FALSE;
2544 for (i = 0; game_panel_controls[i].nr != -1; i++)
2546 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2548 if (PANEL_DEACTIVATED(gpc->pos))
2551 if (gpc->value == gpc->last_value &&
2552 gpc->frame == gpc->last_frame)
2555 redraw_panel = TRUE;
2561 // copy default game door content to main double buffer
2563 // !!! CHECK AGAIN !!!
2564 SetPanelBackground();
2565 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2566 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2568 // redraw game control buttons
2569 RedrawGameButtons();
2571 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2573 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2575 int nr = game_panel_order[i].nr;
2576 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2577 struct TextPosInfo *pos = gpc->pos;
2578 int type = gpc->type;
2579 int value = gpc->value;
2580 int frame = gpc->frame;
2581 int size = pos->size;
2582 int font = pos->font;
2583 boolean draw_masked = pos->draw_masked;
2584 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2586 if (PANEL_DEACTIVATED(pos))
2589 gpc->last_value = value;
2590 gpc->last_frame = frame;
2592 if (type == TYPE_INTEGER)
2594 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2595 nr == GAME_PANEL_TIME)
2597 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2599 if (use_dynamic_size) // use dynamic number of digits
2601 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2602 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2603 int size2 = size1 + 1;
2604 int font1 = pos->font;
2605 int font2 = pos->font_alt;
2607 size = (value < value_change ? size1 : size2);
2608 font = (value < value_change ? font1 : font2);
2612 // correct text size if "digits" is zero or less
2614 size = strlen(int2str(value, size));
2616 // dynamically correct text alignment
2617 pos->width = size * getFontWidth(font);
2619 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2620 int2str(value, size), font, mask_mode);
2622 else if (type == TYPE_ELEMENT)
2624 int element, graphic;
2628 int dst_x = PANEL_XPOS(pos);
2629 int dst_y = PANEL_YPOS(pos);
2631 if (value != EL_UNDEFINED && value != EL_EMPTY)
2634 graphic = el2panelimg(value);
2636 // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
2638 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2641 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2644 width = graphic_info[graphic].width * size / TILESIZE;
2645 height = graphic_info[graphic].height * size / TILESIZE;
2648 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2651 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2655 else if (type == TYPE_GRAPHIC)
2657 int graphic = gpc->graphic;
2658 int graphic_active = gpc->graphic_active;
2662 int dst_x = PANEL_XPOS(pos);
2663 int dst_y = PANEL_YPOS(pos);
2664 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2665 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2667 if (graphic != IMG_UNDEFINED && !skip)
2669 if (pos->style == STYLE_REVERSE)
2670 value = 100 - value;
2672 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2674 if (pos->direction & MV_HORIZONTAL)
2676 width = graphic_info[graphic_active].width * value / 100;
2677 height = graphic_info[graphic_active].height;
2679 if (pos->direction == MV_LEFT)
2681 src_x += graphic_info[graphic_active].width - width;
2682 dst_x += graphic_info[graphic_active].width - width;
2687 width = graphic_info[graphic_active].width;
2688 height = graphic_info[graphic_active].height * value / 100;
2690 if (pos->direction == MV_UP)
2692 src_y += graphic_info[graphic_active].height - height;
2693 dst_y += graphic_info[graphic_active].height - height;
2698 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2701 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2704 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2706 if (pos->direction & MV_HORIZONTAL)
2708 if (pos->direction == MV_RIGHT)
2715 dst_x = PANEL_XPOS(pos);
2718 width = graphic_info[graphic].width - width;
2722 if (pos->direction == MV_DOWN)
2729 dst_y = PANEL_YPOS(pos);
2732 height = graphic_info[graphic].height - height;
2736 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2739 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2743 else if (type == TYPE_STRING)
2745 boolean active = (value != 0);
2746 char *state_normal = "off";
2747 char *state_active = "on";
2748 char *state = (active ? state_active : state_normal);
2749 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2750 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2751 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2752 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2754 if (nr == GAME_PANEL_GRAVITY_STATE)
2756 int font1 = pos->font; // (used for normal state)
2757 int font2 = pos->font_alt; // (used for active state)
2759 font = (active ? font2 : font1);
2768 // don't truncate output if "chars" is zero or less
2771 // dynamically correct text alignment
2772 pos->width = size * getFontWidth(font);
2775 s_cut = getStringCopyN(s, size);
2777 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2778 s_cut, font, mask_mode);
2784 redraw_mask |= REDRAW_DOOR_1;
2787 SetGameStatus(GAME_MODE_PLAYING);
2790 void UpdateAndDisplayGameControlValues(void)
2792 if (tape.deactivate_display)
2795 UpdateGameControlValues();
2796 DisplayGameControlValues();
2800 static void UpdateGameDoorValues(void)
2802 UpdateGameControlValues();
2806 void DrawGameDoorValues(void)
2808 DisplayGameControlValues();
2812 // ============================================================================
2814 // ----------------------------------------------------------------------------
2815 // initialize game engine due to level / tape version number
2816 // ============================================================================
2818 static void InitGameEngine(void)
2820 int i, j, k, l, x, y;
2822 // set game engine from tape file when re-playing, else from level file
2823 game.engine_version = (tape.playing ? tape.engine_version :
2824 level.game_version);
2826 // set single or multi-player game mode (needed for re-playing tapes)
2827 game.team_mode = setup.team_mode;
2831 int num_players = 0;
2833 for (i = 0; i < MAX_PLAYERS; i++)
2834 if (tape.player_participates[i])
2837 // multi-player tapes contain input data for more than one player
2838 game.team_mode = (num_players > 1);
2841 // --------------------------------------------------------------------------
2842 // set flags for bugs and changes according to active game engine version
2843 // --------------------------------------------------------------------------
2846 Summary of bugfix/change:
2847 Fixed handling for custom elements that change when pushed by the player.
2849 Fixed/changed in version:
2853 Before 3.1.0, custom elements that "change when pushing" changed directly
2854 after the player started pushing them (until then handled in "DigField()").
2855 Since 3.1.0, these custom elements are not changed until the "pushing"
2856 move of the element is finished (now handled in "ContinueMoving()").
2858 Affected levels/tapes:
2859 The first condition is generally needed for all levels/tapes before version
2860 3.1.0, which might use the old behaviour before it was changed; known tapes
2861 that are affected are some tapes from the level set "Walpurgis Gardens" by
2863 The second condition is an exception from the above case and is needed for
2864 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2865 above (including some development versions of 3.1.0), but before it was
2866 known that this change would break tapes like the above and was fixed in
2867 3.1.1, so that the changed behaviour was active although the engine version
2868 while recording maybe was before 3.1.0. There is at least one tape that is
2869 affected by this exception, which is the tape for the one-level set "Bug
2870 Machine" by Juergen Bonhagen.
2873 game.use_change_when_pushing_bug =
2874 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2876 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2877 tape.game_version < VERSION_IDENT(3,1,1,0)));
2880 Summary of bugfix/change:
2881 Fixed handling for blocking the field the player leaves when moving.
2883 Fixed/changed in version:
2887 Before 3.1.1, when "block last field when moving" was enabled, the field
2888 the player is leaving when moving was blocked for the time of the move,
2889 and was directly unblocked afterwards. This resulted in the last field
2890 being blocked for exactly one less than the number of frames of one player
2891 move. Additionally, even when blocking was disabled, the last field was
2892 blocked for exactly one frame.
2893 Since 3.1.1, due to changes in player movement handling, the last field
2894 is not blocked at all when blocking is disabled. When blocking is enabled,
2895 the last field is blocked for exactly the number of frames of one player
2896 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2897 last field is blocked for exactly one more than the number of frames of
2900 Affected levels/tapes:
2901 (!!! yet to be determined -- probably many !!!)
2904 game.use_block_last_field_bug =
2905 (game.engine_version < VERSION_IDENT(3,1,1,0));
2907 game_em.use_single_button =
2908 (game.engine_version > VERSION_IDENT(4,0,0,2));
2910 game_em.use_snap_key_bug =
2911 (game.engine_version < VERSION_IDENT(4,0,1,0));
2913 // --------------------------------------------------------------------------
2915 // set maximal allowed number of custom element changes per game frame
2916 game.max_num_changes_per_frame = 1;
2918 // default scan direction: scan playfield from top/left to bottom/right
2919 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
2921 // dynamically adjust element properties according to game engine version
2922 InitElementPropertiesEngine(game.engine_version);
2925 printf("level %d: level version == %06d\n", level_nr, level.game_version);
2926 printf(" tape version == %06d [%s] [file: %06d]\n",
2927 tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
2929 printf(" => game.engine_version == %06d\n", game.engine_version);
2932 // ---------- initialize player's initial move delay ------------------------
2934 // dynamically adjust player properties according to level information
2935 for (i = 0; i < MAX_PLAYERS; i++)
2936 game.initial_move_delay_value[i] =
2937 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
2939 // dynamically adjust player properties according to game engine version
2940 for (i = 0; i < MAX_PLAYERS; i++)
2941 game.initial_move_delay[i] =
2942 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
2943 game.initial_move_delay_value[i] : 0);
2945 // ---------- initialize player's initial push delay ------------------------
2947 // dynamically adjust player properties according to game engine version
2948 game.initial_push_delay_value =
2949 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
2951 // ---------- initialize changing elements ----------------------------------
2953 // initialize changing elements information
2954 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2956 struct ElementInfo *ei = &element_info[i];
2958 // this pointer might have been changed in the level editor
2959 ei->change = &ei->change_page[0];
2961 if (!IS_CUSTOM_ELEMENT(i))
2963 ei->change->target_element = EL_EMPTY_SPACE;
2964 ei->change->delay_fixed = 0;
2965 ei->change->delay_random = 0;
2966 ei->change->delay_frames = 1;
2969 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
2971 ei->has_change_event[j] = FALSE;
2973 ei->event_page_nr[j] = 0;
2974 ei->event_page[j] = &ei->change_page[0];
2978 // add changing elements from pre-defined list
2979 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
2981 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
2982 struct ElementInfo *ei = &element_info[ch_delay->element];
2984 ei->change->target_element = ch_delay->target_element;
2985 ei->change->delay_fixed = ch_delay->change_delay;
2987 ei->change->pre_change_function = ch_delay->pre_change_function;
2988 ei->change->change_function = ch_delay->change_function;
2989 ei->change->post_change_function = ch_delay->post_change_function;
2991 ei->change->can_change = TRUE;
2992 ei->change->can_change_or_has_action = TRUE;
2994 ei->has_change_event[CE_DELAY] = TRUE;
2996 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
2997 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3000 // ---------- initialize internal run-time variables ------------------------
3002 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3004 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3006 for (j = 0; j < ei->num_change_pages; j++)
3008 ei->change_page[j].can_change_or_has_action =
3009 (ei->change_page[j].can_change |
3010 ei->change_page[j].has_action);
3014 // add change events from custom element configuration
3015 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3017 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3019 for (j = 0; j < ei->num_change_pages; j++)
3021 if (!ei->change_page[j].can_change_or_has_action)
3024 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3026 // only add event page for the first page found with this event
3027 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3029 ei->has_change_event[k] = TRUE;
3031 ei->event_page_nr[k] = j;
3032 ei->event_page[k] = &ei->change_page[j];
3038 // ---------- initialize reference elements in change conditions ------------
3040 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3042 int element = EL_CUSTOM_START + i;
3043 struct ElementInfo *ei = &element_info[element];
3045 for (j = 0; j < ei->num_change_pages; j++)
3047 int trigger_element = ei->change_page[j].initial_trigger_element;
3049 if (trigger_element >= EL_PREV_CE_8 &&
3050 trigger_element <= EL_NEXT_CE_8)
3051 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3053 ei->change_page[j].trigger_element = trigger_element;
3057 // ---------- initialize run-time trigger player and element ----------------
3059 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3061 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3063 for (j = 0; j < ei->num_change_pages; j++)
3065 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3066 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3067 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3068 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3069 ei->change_page[j].actual_trigger_ce_value = 0;
3070 ei->change_page[j].actual_trigger_ce_score = 0;
3074 // ---------- initialize trigger events -------------------------------------
3076 // initialize trigger events information
3077 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3078 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3079 trigger_events[i][j] = FALSE;
3081 // add trigger events from element change event properties
3082 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3084 struct ElementInfo *ei = &element_info[i];
3086 for (j = 0; j < ei->num_change_pages; j++)
3088 if (!ei->change_page[j].can_change_or_has_action)
3091 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3093 int trigger_element = ei->change_page[j].trigger_element;
3095 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3097 if (ei->change_page[j].has_event[k])
3099 if (IS_GROUP_ELEMENT(trigger_element))
3101 struct ElementGroupInfo *group =
3102 element_info[trigger_element].group;
3104 for (l = 0; l < group->num_elements_resolved; l++)
3105 trigger_events[group->element_resolved[l]][k] = TRUE;
3107 else if (trigger_element == EL_ANY_ELEMENT)
3108 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3109 trigger_events[l][k] = TRUE;
3111 trigger_events[trigger_element][k] = TRUE;
3118 // ---------- initialize push delay -----------------------------------------
3120 // initialize push delay values to default
3121 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3123 if (!IS_CUSTOM_ELEMENT(i))
3125 // set default push delay values (corrected since version 3.0.7-1)
3126 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3128 element_info[i].push_delay_fixed = 2;
3129 element_info[i].push_delay_random = 8;
3133 element_info[i].push_delay_fixed = 8;
3134 element_info[i].push_delay_random = 8;
3139 // set push delay value for certain elements from pre-defined list
3140 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3142 int e = push_delay_list[i].element;
3144 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3145 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3148 // set push delay value for Supaplex elements for newer engine versions
3149 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3151 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3153 if (IS_SP_ELEMENT(i))
3155 // set SP push delay to just enough to push under a falling zonk
3156 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3158 element_info[i].push_delay_fixed = delay;
3159 element_info[i].push_delay_random = 0;
3164 // ---------- initialize move stepsize --------------------------------------
3166 // initialize move stepsize values to default
3167 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3168 if (!IS_CUSTOM_ELEMENT(i))
3169 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3171 // set move stepsize value for certain elements from pre-defined list
3172 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3174 int e = move_stepsize_list[i].element;
3176 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3179 // ---------- initialize collect score --------------------------------------
3181 // initialize collect score values for custom elements from initial value
3182 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3183 if (IS_CUSTOM_ELEMENT(i))
3184 element_info[i].collect_score = element_info[i].collect_score_initial;
3186 // ---------- initialize collect count --------------------------------------
3188 // initialize collect count values for non-custom elements
3189 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3190 if (!IS_CUSTOM_ELEMENT(i))
3191 element_info[i].collect_count_initial = 0;
3193 // add collect count values for all elements from pre-defined list
3194 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3195 element_info[collect_count_list[i].element].collect_count_initial =
3196 collect_count_list[i].count;
3198 // ---------- initialize access direction -----------------------------------
3200 // initialize access direction values to default (access from every side)
3201 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3202 if (!IS_CUSTOM_ELEMENT(i))
3203 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3205 // set access direction value for certain elements from pre-defined list
3206 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3207 element_info[access_direction_list[i].element].access_direction =
3208 access_direction_list[i].direction;
3210 // ---------- initialize explosion content ----------------------------------
3211 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3213 if (IS_CUSTOM_ELEMENT(i))
3216 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3218 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3220 element_info[i].content.e[x][y] =
3221 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3222 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3223 i == EL_PLAYER_3 ? EL_EMERALD :
3224 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3225 i == EL_MOLE ? EL_EMERALD_RED :
3226 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3227 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3228 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3229 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3230 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3231 i == EL_WALL_EMERALD ? EL_EMERALD :
3232 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3233 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3234 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3235 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3236 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3237 i == EL_WALL_PEARL ? EL_PEARL :
3238 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3243 // ---------- initialize recursion detection --------------------------------
3244 recursion_loop_depth = 0;
3245 recursion_loop_detected = FALSE;
3246 recursion_loop_element = EL_UNDEFINED;
3248 // ---------- initialize graphics engine ------------------------------------
3249 game.scroll_delay_value =
3250 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3251 setup.scroll_delay ? setup.scroll_delay_value : 0);
3252 game.scroll_delay_value =
3253 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3255 // ---------- initialize game engine snapshots ------------------------------
3256 for (i = 0; i < MAX_PLAYERS; i++)
3257 game.snapshot.last_action[i] = 0;
3258 game.snapshot.changed_action = FALSE;
3259 game.snapshot.collected_item = FALSE;
3260 game.snapshot.mode =
3261 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3262 SNAPSHOT_MODE_EVERY_STEP :
3263 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3264 SNAPSHOT_MODE_EVERY_MOVE :
3265 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3266 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3267 game.snapshot.save_snapshot = FALSE;
3269 // ---------- initialize level time for Supaplex engine ---------------------
3270 // Supaplex levels with time limit currently unsupported -- should be added
3271 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3275 static int get_num_special_action(int element, int action_first,
3278 int num_special_action = 0;
3281 for (i = action_first; i <= action_last; i++)
3283 boolean found = FALSE;
3285 for (j = 0; j < NUM_DIRECTIONS; j++)
3286 if (el_act_dir2img(element, i, j) !=
3287 el_act_dir2img(element, ACTION_DEFAULT, j))
3291 num_special_action++;
3296 return num_special_action;
3300 // ============================================================================
3302 // ----------------------------------------------------------------------------
3303 // initialize and start new game
3304 // ============================================================================
3306 #if DEBUG_INIT_PLAYER
3307 static void DebugPrintPlayerStatus(char *message)
3314 printf("%s:\n", message);
3316 for (i = 0; i < MAX_PLAYERS; i++)
3318 struct PlayerInfo *player = &stored_player[i];
3320 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3324 player->connected_locally,
3325 player->connected_network,
3328 if (local_player == player)
3329 printf(" (local player)");
3338 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3339 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3340 int fade_mask = REDRAW_FIELD;
3342 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3343 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3344 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3345 int initial_move_dir = MV_DOWN;
3348 // required here to update video display before fading (FIX THIS)
3349 DrawMaskedBorder(REDRAW_DOOR_2);
3351 if (!game.restart_level)
3352 CloseDoor(DOOR_CLOSE_1);
3354 SetGameStatus(GAME_MODE_PLAYING);
3356 if (level_editor_test_game)
3357 FadeSkipNextFadeIn();
3359 FadeSetEnterScreen();
3361 if (CheckIfGlobalBorderOrPlayfieldViewportHasChanged())
3362 fade_mask = REDRAW_ALL;
3364 FadeLevelSoundsAndMusic();
3366 ExpireSoundLoops(TRUE);
3370 // needed if different viewport properties defined for playing
3371 ChangeViewportPropertiesIfNeeded();
3375 DrawCompleteVideoDisplay();
3377 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3380 InitGameControlValues();
3382 // don't play tapes over network
3383 network_playing = (network.enabled && !tape.playing);
3385 for (i = 0; i < MAX_PLAYERS; i++)
3387 struct PlayerInfo *player = &stored_player[i];
3389 player->index_nr = i;
3390 player->index_bit = (1 << i);
3391 player->element_nr = EL_PLAYER_1 + i;
3393 player->present = FALSE;
3394 player->active = FALSE;
3395 player->mapped = FALSE;
3397 player->killed = FALSE;
3398 player->reanimated = FALSE;
3399 player->buried = FALSE;
3402 player->effective_action = 0;
3403 player->programmed_action = 0;
3405 player->mouse_action.lx = 0;
3406 player->mouse_action.ly = 0;
3407 player->mouse_action.button = 0;
3408 player->mouse_action.button_hint = 0;
3410 player->effective_mouse_action.lx = 0;
3411 player->effective_mouse_action.ly = 0;
3412 player->effective_mouse_action.button = 0;
3413 player->effective_mouse_action.button_hint = 0;
3415 for (j = 0; j < MAX_NUM_KEYS; j++)
3416 player->key[j] = FALSE;
3418 player->num_white_keys = 0;
3420 player->dynabomb_count = 0;
3421 player->dynabomb_size = 1;
3422 player->dynabombs_left = 0;
3423 player->dynabomb_xl = FALSE;
3425 player->MovDir = initial_move_dir;
3428 player->GfxDir = initial_move_dir;
3429 player->GfxAction = ACTION_DEFAULT;
3431 player->StepFrame = 0;
3433 player->initial_element = player->element_nr;
3434 player->artwork_element =
3435 (level.use_artwork_element[i] ? level.artwork_element[i] :
3436 player->element_nr);
3437 player->use_murphy = FALSE;
3439 player->block_last_field = FALSE; // initialized in InitPlayerField()
3440 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3442 player->gravity = level.initial_player_gravity[i];
3444 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3446 player->actual_frame_counter = 0;
3448 player->step_counter = 0;
3450 player->last_move_dir = initial_move_dir;
3452 player->is_active = FALSE;
3454 player->is_waiting = FALSE;
3455 player->is_moving = FALSE;
3456 player->is_auto_moving = FALSE;
3457 player->is_digging = FALSE;
3458 player->is_snapping = FALSE;
3459 player->is_collecting = FALSE;
3460 player->is_pushing = FALSE;
3461 player->is_switching = FALSE;
3462 player->is_dropping = FALSE;
3463 player->is_dropping_pressed = FALSE;
3465 player->is_bored = FALSE;
3466 player->is_sleeping = FALSE;
3468 player->was_waiting = TRUE;
3469 player->was_moving = FALSE;
3470 player->was_snapping = FALSE;
3471 player->was_dropping = FALSE;
3473 player->force_dropping = FALSE;
3475 player->frame_counter_bored = -1;
3476 player->frame_counter_sleeping = -1;
3478 player->anim_delay_counter = 0;
3479 player->post_delay_counter = 0;
3481 player->dir_waiting = initial_move_dir;
3482 player->action_waiting = ACTION_DEFAULT;
3483 player->last_action_waiting = ACTION_DEFAULT;
3484 player->special_action_bored = ACTION_DEFAULT;
3485 player->special_action_sleeping = ACTION_DEFAULT;
3487 player->switch_x = -1;
3488 player->switch_y = -1;
3490 player->drop_x = -1;
3491 player->drop_y = -1;
3493 player->show_envelope = 0;
3495 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3497 player->push_delay = -1; // initialized when pushing starts
3498 player->push_delay_value = game.initial_push_delay_value;
3500 player->drop_delay = 0;
3501 player->drop_pressed_delay = 0;
3503 player->last_jx = -1;
3504 player->last_jy = -1;
3508 player->shield_normal_time_left = 0;
3509 player->shield_deadly_time_left = 0;
3511 player->inventory_infinite_element = EL_UNDEFINED;
3512 player->inventory_size = 0;
3514 if (level.use_initial_inventory[i])
3516 for (j = 0; j < level.initial_inventory_size[i]; j++)
3518 int element = level.initial_inventory_content[i][j];
3519 int collect_count = element_info[element].collect_count_initial;
3522 if (!IS_CUSTOM_ELEMENT(element))
3525 if (collect_count == 0)
3526 player->inventory_infinite_element = element;
3528 for (k = 0; k < collect_count; k++)
3529 if (player->inventory_size < MAX_INVENTORY_SIZE)
3530 player->inventory_element[player->inventory_size++] = element;
3534 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3535 SnapField(player, 0, 0);
3537 map_player_action[i] = i;
3540 network_player_action_received = FALSE;
3542 // initial null action
3543 if (network_playing)
3544 SendToServer_MovePlayer(MV_NONE);
3549 TimeLeft = level.time;
3552 ScreenMovDir = MV_NONE;
3556 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3558 game.robot_wheel_x = -1;
3559 game.robot_wheel_y = -1;
3564 game.all_players_gone = FALSE;
3566 game.LevelSolved = FALSE;
3567 game.GameOver = FALSE;
3569 game.GamePlayed = !tape.playing;
3571 game.LevelSolved_GameWon = FALSE;
3572 game.LevelSolved_GameEnd = FALSE;
3573 game.LevelSolved_SaveTape = FALSE;
3574 game.LevelSolved_SaveScore = FALSE;
3576 game.LevelSolved_CountingTime = 0;
3577 game.LevelSolved_CountingScore = 0;
3578 game.LevelSolved_CountingHealth = 0;
3580 game.panel.active = TRUE;
3582 game.no_time_limit = (level.time == 0);
3584 game.yamyam_content_nr = 0;
3585 game.robot_wheel_active = FALSE;
3586 game.magic_wall_active = FALSE;
3587 game.magic_wall_time_left = 0;
3588 game.light_time_left = 0;
3589 game.timegate_time_left = 0;
3590 game.switchgate_pos = 0;
3591 game.wind_direction = level.wind_direction_initial;
3594 game.score_final = 0;
3596 game.health = MAX_HEALTH;
3597 game.health_final = MAX_HEALTH;
3599 game.gems_still_needed = level.gems_needed;
3600 game.sokoban_fields_still_needed = 0;
3601 game.sokoban_objects_still_needed = 0;
3602 game.lights_still_needed = 0;
3603 game.players_still_needed = 0;
3604 game.friends_still_needed = 0;
3606 game.lenses_time_left = 0;
3607 game.magnify_time_left = 0;
3609 game.ball_state = level.ball_state_initial;
3610 game.ball_content_nr = 0;
3612 game.explosions_delayed = TRUE;
3614 game.envelope_active = FALSE;
3616 for (i = 0; i < NUM_BELTS; i++)
3618 game.belt_dir[i] = MV_NONE;
3619 game.belt_dir_nr[i] = 3; // not moving, next moving left
3622 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3623 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3625 #if DEBUG_INIT_PLAYER
3626 DebugPrintPlayerStatus("Player status at level initialization");
3629 SCAN_PLAYFIELD(x, y)
3631 Feld[x][y] = Last[x][y] = level.field[x][y];
3632 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3633 ChangeDelay[x][y] = 0;
3634 ChangePage[x][y] = -1;
3635 CustomValue[x][y] = 0; // initialized in InitField()
3636 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3638 WasJustMoving[x][y] = 0;
3639 WasJustFalling[x][y] = 0;
3640 CheckCollision[x][y] = 0;
3641 CheckImpact[x][y] = 0;
3643 Pushed[x][y] = FALSE;
3645 ChangeCount[x][y] = 0;
3646 ChangeEvent[x][y] = -1;
3648 ExplodePhase[x][y] = 0;
3649 ExplodeDelay[x][y] = 0;
3650 ExplodeField[x][y] = EX_TYPE_NONE;
3652 RunnerVisit[x][y] = 0;
3653 PlayerVisit[x][y] = 0;
3656 GfxRandom[x][y] = INIT_GFX_RANDOM();
3657 GfxElement[x][y] = EL_UNDEFINED;
3658 GfxAction[x][y] = ACTION_DEFAULT;
3659 GfxDir[x][y] = MV_NONE;
3660 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3663 SCAN_PLAYFIELD(x, y)
3665 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3667 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3669 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3672 InitField(x, y, TRUE);
3674 ResetGfxAnimation(x, y);
3679 for (i = 0; i < MAX_PLAYERS; i++)
3681 struct PlayerInfo *player = &stored_player[i];
3683 // set number of special actions for bored and sleeping animation
3684 player->num_special_action_bored =
3685 get_num_special_action(player->artwork_element,
3686 ACTION_BORING_1, ACTION_BORING_LAST);
3687 player->num_special_action_sleeping =
3688 get_num_special_action(player->artwork_element,
3689 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3692 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3693 emulate_sb ? EMU_SOKOBAN :
3694 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3696 // initialize type of slippery elements
3697 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3699 if (!IS_CUSTOM_ELEMENT(i))
3701 // default: elements slip down either to the left or right randomly
3702 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3704 // SP style elements prefer to slip down on the left side
3705 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3706 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3708 // BD style elements prefer to slip down on the left side
3709 if (game.emulation == EMU_BOULDERDASH)
3710 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3714 // initialize explosion and ignition delay
3715 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3717 if (!IS_CUSTOM_ELEMENT(i))
3720 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3721 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3722 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3723 int last_phase = (num_phase + 1) * delay;
3724 int half_phase = (num_phase / 2) * delay;
3726 element_info[i].explosion_delay = last_phase - 1;
3727 element_info[i].ignition_delay = half_phase;
3729 if (i == EL_BLACK_ORB)
3730 element_info[i].ignition_delay = 1;
3734 // correct non-moving belts to start moving left
3735 for (i = 0; i < NUM_BELTS; i++)
3736 if (game.belt_dir[i] == MV_NONE)
3737 game.belt_dir_nr[i] = 3; // not moving, next moving left
3739 #if USE_NEW_PLAYER_ASSIGNMENTS
3740 for (i = 0; i < MAX_PLAYERS; i++)
3742 stored_player[i].connected = FALSE;
3744 // in network game mode, the local player might not be the first player
3745 if (stored_player[i].connected_locally)
3746 local_player = &stored_player[i];
3749 if (!network.enabled)
3750 local_player->connected = TRUE;
3754 for (i = 0; i < MAX_PLAYERS; i++)
3755 stored_player[i].connected = tape.player_participates[i];
3757 else if (network.enabled)
3759 // add team mode players connected over the network (needed for correct
3760 // assignment of player figures from level to locally playing players)
3762 for (i = 0; i < MAX_PLAYERS; i++)
3763 if (stored_player[i].connected_network)
3764 stored_player[i].connected = TRUE;
3766 else if (game.team_mode)
3768 // try to guess locally connected team mode players (needed for correct
3769 // assignment of player figures from level to locally playing players)
3771 for (i = 0; i < MAX_PLAYERS; i++)
3772 if (setup.input[i].use_joystick ||
3773 setup.input[i].key.left != KSYM_UNDEFINED)
3774 stored_player[i].connected = TRUE;
3777 #if DEBUG_INIT_PLAYER
3778 DebugPrintPlayerStatus("Player status after level initialization");
3781 #if DEBUG_INIT_PLAYER
3783 printf("Reassigning players ...\n");
3786 // check if any connected player was not found in playfield
3787 for (i = 0; i < MAX_PLAYERS; i++)
3789 struct PlayerInfo *player = &stored_player[i];
3791 if (player->connected && !player->present)
3793 struct PlayerInfo *field_player = NULL;
3795 #if DEBUG_INIT_PLAYER
3797 printf("- looking for field player for player %d ...\n", i + 1);
3800 // assign first free player found that is present in the playfield
3802 // first try: look for unmapped playfield player that is not connected
3803 for (j = 0; j < MAX_PLAYERS; j++)
3804 if (field_player == NULL &&
3805 stored_player[j].present &&
3806 !stored_player[j].mapped &&
3807 !stored_player[j].connected)
3808 field_player = &stored_player[j];
3810 // second try: look for *any* unmapped playfield player
3811 for (j = 0; j < MAX_PLAYERS; j++)
3812 if (field_player == NULL &&
3813 stored_player[j].present &&
3814 !stored_player[j].mapped)
3815 field_player = &stored_player[j];
3817 if (field_player != NULL)
3819 int jx = field_player->jx, jy = field_player->jy;
3821 #if DEBUG_INIT_PLAYER
3823 printf("- found player %d\n", field_player->index_nr + 1);
3826 player->present = FALSE;
3827 player->active = FALSE;
3829 field_player->present = TRUE;
3830 field_player->active = TRUE;
3833 player->initial_element = field_player->initial_element;
3834 player->artwork_element = field_player->artwork_element;
3836 player->block_last_field = field_player->block_last_field;
3837 player->block_delay_adjustment = field_player->block_delay_adjustment;
3840 StorePlayer[jx][jy] = field_player->element_nr;
3842 field_player->jx = field_player->last_jx = jx;
3843 field_player->jy = field_player->last_jy = jy;
3845 if (local_player == player)
3846 local_player = field_player;
3848 map_player_action[field_player->index_nr] = i;
3850 field_player->mapped = TRUE;
3852 #if DEBUG_INIT_PLAYER
3854 printf("- map_player_action[%d] == %d\n",
3855 field_player->index_nr + 1, i + 1);
3860 if (player->connected && player->present)
3861 player->mapped = TRUE;
3864 #if DEBUG_INIT_PLAYER
3865 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
3870 // check if any connected player was not found in playfield
3871 for (i = 0; i < MAX_PLAYERS; i++)
3873 struct PlayerInfo *player = &stored_player[i];
3875 if (player->connected && !player->present)
3877 for (j = 0; j < MAX_PLAYERS; j++)
3879 struct PlayerInfo *field_player = &stored_player[j];
3880 int jx = field_player->jx, jy = field_player->jy;
3882 // assign first free player found that is present in the playfield
3883 if (field_player->present && !field_player->connected)
3885 player->present = TRUE;
3886 player->active = TRUE;
3888 field_player->present = FALSE;
3889 field_player->active = FALSE;
3891 player->initial_element = field_player->initial_element;
3892 player->artwork_element = field_player->artwork_element;
3894 player->block_last_field = field_player->block_last_field;
3895 player->block_delay_adjustment = field_player->block_delay_adjustment;
3897 StorePlayer[jx][jy] = player->element_nr;
3899 player->jx = player->last_jx = jx;
3900 player->jy = player->last_jy = jy;
3910 printf("::: local_player->present == %d\n", local_player->present);
3913 // set focus to local player for network games, else to all players
3914 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
3915 game.centered_player_nr_next = game.centered_player_nr;
3916 game.set_centered_player = FALSE;
3918 if (network_playing && tape.recording)
3920 // store client dependent player focus when recording network games
3921 tape.centered_player_nr_next = game.centered_player_nr_next;
3922 tape.set_centered_player = TRUE;
3927 // when playing a tape, eliminate all players who do not participate
3929 #if USE_NEW_PLAYER_ASSIGNMENTS
3931 if (!game.team_mode)
3933 for (i = 0; i < MAX_PLAYERS; i++)
3935 if (stored_player[i].active &&
3936 !tape.player_participates[map_player_action[i]])
3938 struct PlayerInfo *player = &stored_player[i];
3939 int jx = player->jx, jy = player->jy;
3941 #if DEBUG_INIT_PLAYER
3943 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
3946 player->active = FALSE;
3947 StorePlayer[jx][jy] = 0;
3948 Feld[jx][jy] = EL_EMPTY;
3955 for (i = 0; i < MAX_PLAYERS; i++)
3957 if (stored_player[i].active &&
3958 !tape.player_participates[i])
3960 struct PlayerInfo *player = &stored_player[i];
3961 int jx = player->jx, jy = player->jy;
3963 player->active = FALSE;
3964 StorePlayer[jx][jy] = 0;
3965 Feld[jx][jy] = EL_EMPTY;
3970 else if (!network.enabled && !game.team_mode) // && !tape.playing
3972 // when in single player mode, eliminate all but the local player
3974 for (i = 0; i < MAX_PLAYERS; i++)
3976 struct PlayerInfo *player = &stored_player[i];
3978 if (player->active && player != local_player)
3980 int jx = player->jx, jy = player->jy;
3982 player->active = FALSE;
3983 player->present = FALSE;
3985 StorePlayer[jx][jy] = 0;
3986 Feld[jx][jy] = EL_EMPTY;
3991 for (i = 0; i < MAX_PLAYERS; i++)
3992 if (stored_player[i].active)
3993 game.players_still_needed++;
3995 if (level.solved_by_one_player)
3996 game.players_still_needed = 1;
3998 // when recording the game, store which players take part in the game
4001 #if USE_NEW_PLAYER_ASSIGNMENTS
4002 for (i = 0; i < MAX_PLAYERS; i++)
4003 if (stored_player[i].connected)
4004 tape.player_participates[i] = TRUE;
4006 for (i = 0; i < MAX_PLAYERS; i++)
4007 if (stored_player[i].active)
4008 tape.player_participates[i] = TRUE;
4012 #if DEBUG_INIT_PLAYER
4013 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4016 if (BorderElement == EL_EMPTY)
4019 SBX_Right = lev_fieldx - SCR_FIELDX;
4021 SBY_Lower = lev_fieldy - SCR_FIELDY;
4026 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4028 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4031 if (full_lev_fieldx <= SCR_FIELDX)
4032 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4033 if (full_lev_fieldy <= SCR_FIELDY)
4034 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4036 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4038 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4041 // if local player not found, look for custom element that might create
4042 // the player (make some assumptions about the right custom element)
4043 if (!local_player->present)
4045 int start_x = 0, start_y = 0;
4046 int found_rating = 0;
4047 int found_element = EL_UNDEFINED;
4048 int player_nr = local_player->index_nr;
4050 SCAN_PLAYFIELD(x, y)
4052 int element = Feld[x][y];
4057 if (level.use_start_element[player_nr] &&
4058 level.start_element[player_nr] == element &&
4065 found_element = element;
4068 if (!IS_CUSTOM_ELEMENT(element))
4071 if (CAN_CHANGE(element))
4073 for (i = 0; i < element_info[element].num_change_pages; i++)
4075 // check for player created from custom element as single target
4076 content = element_info[element].change_page[i].target_element;
4077 is_player = ELEM_IS_PLAYER(content);
4079 if (is_player && (found_rating < 3 ||
4080 (found_rating == 3 && element < found_element)))
4086 found_element = element;
4091 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4093 // check for player created from custom element as explosion content
4094 content = element_info[element].content.e[xx][yy];
4095 is_player = ELEM_IS_PLAYER(content);
4097 if (is_player && (found_rating < 2 ||
4098 (found_rating == 2 && element < found_element)))
4100 start_x = x + xx - 1;
4101 start_y = y + yy - 1;
4104 found_element = element;
4107 if (!CAN_CHANGE(element))
4110 for (i = 0; i < element_info[element].num_change_pages; i++)
4112 // check for player created from custom element as extended target
4114 element_info[element].change_page[i].target_content.e[xx][yy];
4116 is_player = ELEM_IS_PLAYER(content);
4118 if (is_player && (found_rating < 1 ||
4119 (found_rating == 1 && element < found_element)))
4121 start_x = x + xx - 1;
4122 start_y = y + yy - 1;
4125 found_element = element;
4131 scroll_x = SCROLL_POSITION_X(start_x);
4132 scroll_y = SCROLL_POSITION_Y(start_y);
4136 scroll_x = SCROLL_POSITION_X(local_player->jx);
4137 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4140 // !!! FIX THIS (START) !!!
4141 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4143 InitGameEngine_EM();
4145 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4147 InitGameEngine_SP();
4149 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4151 InitGameEngine_MM();
4155 DrawLevel(REDRAW_FIELD);
4158 // after drawing the level, correct some elements
4159 if (game.timegate_time_left == 0)
4160 CloseAllOpenTimegates();
4163 // blit playfield from scroll buffer to normal back buffer for fading in
4164 BlitScreenToBitmap(backbuffer);
4165 // !!! FIX THIS (END) !!!
4167 DrawMaskedBorder(fade_mask);
4172 // full screen redraw is required at this point in the following cases:
4173 // - special editor door undrawn when game was started from level editor
4174 // - drawing area (playfield) was changed and has to be removed completely
4175 redraw_mask = REDRAW_ALL;
4179 if (!game.restart_level)
4181 // copy default game door content to main double buffer
4183 // !!! CHECK AGAIN !!!
4184 SetPanelBackground();
4185 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4186 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4189 SetPanelBackground();
4190 SetDrawBackgroundMask(REDRAW_DOOR_1);
4192 UpdateAndDisplayGameControlValues();
4194 if (!game.restart_level)
4200 CreateGameButtons();
4205 // copy actual game door content to door double buffer for OpenDoor()
4206 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4208 OpenDoor(DOOR_OPEN_ALL);
4210 KeyboardAutoRepeatOffUnlessAutoplay();
4212 #if DEBUG_INIT_PLAYER
4213 DebugPrintPlayerStatus("Player status (final)");
4222 if (!game.restart_level && !tape.playing)
4224 LevelStats_incPlayed(level_nr);
4226 SaveLevelSetup_SeriesInfo();
4229 game.restart_level = FALSE;
4230 game.restart_game_message = NULL;
4231 game.request_active = FALSE;
4233 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4234 InitGameActions_MM();
4236 SaveEngineSnapshotToListInitial();
4238 if (!game.restart_level)
4240 PlaySound(SND_GAME_STARTING);
4242 if (setup.sound_music)
4247 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4248 int actual_player_x, int actual_player_y)
4250 // this is used for non-R'n'D game engines to update certain engine values
4252 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4254 actual_player_x = correctLevelPosX_EM(actual_player_x);
4255 actual_player_y = correctLevelPosY_EM(actual_player_y);
4258 // needed to determine if sounds are played within the visible screen area
4259 scroll_x = actual_scroll_x;
4260 scroll_y = actual_scroll_y;
4262 // needed to get player position for "follow finger" playing input method
4263 local_player->jx = actual_player_x;
4264 local_player->jy = actual_player_y;
4267 void InitMovDir(int x, int y)
4269 int i, element = Feld[x][y];
4270 static int xy[4][2] =
4277 static int direction[3][4] =
4279 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4280 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4281 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4290 Feld[x][y] = EL_BUG;
4291 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4294 case EL_SPACESHIP_RIGHT:
4295 case EL_SPACESHIP_UP:
4296 case EL_SPACESHIP_LEFT:
4297 case EL_SPACESHIP_DOWN:
4298 Feld[x][y] = EL_SPACESHIP;
4299 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4302 case EL_BD_BUTTERFLY_RIGHT:
4303 case EL_BD_BUTTERFLY_UP:
4304 case EL_BD_BUTTERFLY_LEFT:
4305 case EL_BD_BUTTERFLY_DOWN:
4306 Feld[x][y] = EL_BD_BUTTERFLY;
4307 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4310 case EL_BD_FIREFLY_RIGHT:
4311 case EL_BD_FIREFLY_UP:
4312 case EL_BD_FIREFLY_LEFT:
4313 case EL_BD_FIREFLY_DOWN:
4314 Feld[x][y] = EL_BD_FIREFLY;
4315 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4318 case EL_PACMAN_RIGHT:
4320 case EL_PACMAN_LEFT:
4321 case EL_PACMAN_DOWN:
4322 Feld[x][y] = EL_PACMAN;
4323 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4326 case EL_YAMYAM_LEFT:
4327 case EL_YAMYAM_RIGHT:
4329 case EL_YAMYAM_DOWN:
4330 Feld[x][y] = EL_YAMYAM;
4331 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4334 case EL_SP_SNIKSNAK:
4335 MovDir[x][y] = MV_UP;
4338 case EL_SP_ELECTRON:
4339 MovDir[x][y] = MV_LEFT;
4346 Feld[x][y] = EL_MOLE;
4347 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4351 if (IS_CUSTOM_ELEMENT(element))
4353 struct ElementInfo *ei = &element_info[element];
4354 int move_direction_initial = ei->move_direction_initial;
4355 int move_pattern = ei->move_pattern;
4357 if (move_direction_initial == MV_START_PREVIOUS)
4359 if (MovDir[x][y] != MV_NONE)
4362 move_direction_initial = MV_START_AUTOMATIC;
4365 if (move_direction_initial == MV_START_RANDOM)
4366 MovDir[x][y] = 1 << RND(4);
4367 else if (move_direction_initial & MV_ANY_DIRECTION)
4368 MovDir[x][y] = move_direction_initial;
4369 else if (move_pattern == MV_ALL_DIRECTIONS ||
4370 move_pattern == MV_TURNING_LEFT ||
4371 move_pattern == MV_TURNING_RIGHT ||
4372 move_pattern == MV_TURNING_LEFT_RIGHT ||
4373 move_pattern == MV_TURNING_RIGHT_LEFT ||
4374 move_pattern == MV_TURNING_RANDOM)
4375 MovDir[x][y] = 1 << RND(4);
4376 else if (move_pattern == MV_HORIZONTAL)
4377 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4378 else if (move_pattern == MV_VERTICAL)
4379 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4380 else if (move_pattern & MV_ANY_DIRECTION)
4381 MovDir[x][y] = element_info[element].move_pattern;
4382 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4383 move_pattern == MV_ALONG_RIGHT_SIDE)
4385 // use random direction as default start direction
4386 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4387 MovDir[x][y] = 1 << RND(4);
4389 for (i = 0; i < NUM_DIRECTIONS; i++)
4391 int x1 = x + xy[i][0];
4392 int y1 = y + xy[i][1];
4394 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4396 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4397 MovDir[x][y] = direction[0][i];
4399 MovDir[x][y] = direction[1][i];
4408 MovDir[x][y] = 1 << RND(4);
4410 if (element != EL_BUG &&
4411 element != EL_SPACESHIP &&
4412 element != EL_BD_BUTTERFLY &&
4413 element != EL_BD_FIREFLY)
4416 for (i = 0; i < NUM_DIRECTIONS; i++)
4418 int x1 = x + xy[i][0];
4419 int y1 = y + xy[i][1];
4421 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4423 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4425 MovDir[x][y] = direction[0][i];
4428 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4429 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4431 MovDir[x][y] = direction[1][i];
4440 GfxDir[x][y] = MovDir[x][y];
4443 void InitAmoebaNr(int x, int y)
4446 int group_nr = AmoebeNachbarNr(x, y);
4450 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4452 if (AmoebaCnt[i] == 0)
4460 AmoebaNr[x][y] = group_nr;
4461 AmoebaCnt[group_nr]++;
4462 AmoebaCnt2[group_nr]++;
4465 static void LevelSolved(void)
4467 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4468 game.players_still_needed > 0)
4471 game.LevelSolved = TRUE;
4472 game.GameOver = TRUE;
4474 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4475 level.native_em_level->lev->score :
4476 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4479 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4480 MM_HEALTH(game_mm.laser_overload_value) :
4483 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4484 game.LevelSolved_CountingScore = game.score_final;
4485 game.LevelSolved_CountingHealth = game.health_final;
4490 static int time_count_steps;
4491 static int time, time_final;
4492 static int score, score_final;
4493 static int health, health_final;
4494 static int game_over_delay_1 = 0;
4495 static int game_over_delay_2 = 0;
4496 static int game_over_delay_3 = 0;
4497 int game_over_delay_value_1 = 50;
4498 int game_over_delay_value_2 = 25;
4499 int game_over_delay_value_3 = 50;
4501 if (!game.LevelSolved_GameWon)
4505 // do not start end game actions before the player stops moving (to exit)
4506 if (local_player->MovPos)
4509 game.LevelSolved_GameWon = TRUE;
4510 game.LevelSolved_SaveTape = tape.recording;
4511 game.LevelSolved_SaveScore = !tape.playing;
4515 LevelStats_incSolved(level_nr);
4517 SaveLevelSetup_SeriesInfo();
4520 if (tape.auto_play) // tape might already be stopped here
4521 tape.auto_play_level_solved = TRUE;
4525 game_over_delay_1 = 0;
4526 game_over_delay_2 = 0;
4527 game_over_delay_3 = game_over_delay_value_3;
4529 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4530 score = score_final = game.score_final;
4531 health = health_final = game.health_final;
4533 if (level.score[SC_TIME_BONUS] > 0)
4538 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4540 else if (game.no_time_limit && TimePlayed < 999)
4543 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4546 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4548 game_over_delay_1 = game_over_delay_value_1;
4550 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4553 score_final += health * level.score[SC_TIME_BONUS];
4555 game_over_delay_2 = game_over_delay_value_2;
4558 game.score_final = score_final;
4559 game.health_final = health_final;
4562 if (level_editor_test_game)
4565 score = score_final;
4567 game.LevelSolved_CountingTime = time;
4568 game.LevelSolved_CountingScore = score;
4570 game_panel_controls[GAME_PANEL_TIME].value = time;
4571 game_panel_controls[GAME_PANEL_SCORE].value = score;
4573 DisplayGameControlValues();
4576 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4578 // check if last player has left the level
4579 if (game.exit_x >= 0 &&
4582 int x = game.exit_x;
4583 int y = game.exit_y;
4584 int element = Feld[x][y];
4586 // close exit door after last player
4587 if ((game.all_players_gone &&
4588 (element == EL_EXIT_OPEN ||
4589 element == EL_SP_EXIT_OPEN ||
4590 element == EL_STEEL_EXIT_OPEN)) ||
4591 element == EL_EM_EXIT_OPEN ||
4592 element == EL_EM_STEEL_EXIT_OPEN)
4596 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4597 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4598 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4599 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4600 EL_EM_STEEL_EXIT_CLOSING);
4602 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4605 // player disappears
4606 DrawLevelField(x, y);
4609 for (i = 0; i < MAX_PLAYERS; i++)
4611 struct PlayerInfo *player = &stored_player[i];
4613 if (player->present)
4615 RemovePlayer(player);
4617 // player disappears
4618 DrawLevelField(player->jx, player->jy);
4623 PlaySound(SND_GAME_WINNING);
4626 if (game_over_delay_1 > 0)
4628 game_over_delay_1--;
4633 if (time != time_final)
4635 int time_to_go = ABS(time_final - time);
4636 int time_count_dir = (time < time_final ? +1 : -1);
4638 if (time_to_go < time_count_steps)
4639 time_count_steps = 1;
4641 time += time_count_steps * time_count_dir;
4642 score += time_count_steps * level.score[SC_TIME_BONUS];
4644 game.LevelSolved_CountingTime = time;
4645 game.LevelSolved_CountingScore = score;
4647 game_panel_controls[GAME_PANEL_TIME].value = time;
4648 game_panel_controls[GAME_PANEL_SCORE].value = score;
4650 DisplayGameControlValues();
4652 if (time == time_final)
4653 StopSound(SND_GAME_LEVELTIME_BONUS);
4654 else if (setup.sound_loops)
4655 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4657 PlaySound(SND_GAME_LEVELTIME_BONUS);
4662 if (game_over_delay_2 > 0)
4664 game_over_delay_2--;
4669 if (health != health_final)
4671 int health_count_dir = (health < health_final ? +1 : -1);
4673 health += health_count_dir;
4674 score += level.score[SC_TIME_BONUS];
4676 game.LevelSolved_CountingHealth = health;
4677 game.LevelSolved_CountingScore = score;
4679 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4680 game_panel_controls[GAME_PANEL_SCORE].value = score;
4682 DisplayGameControlValues();
4684 if (health == health_final)
4685 StopSound(SND_GAME_LEVELTIME_BONUS);
4686 else if (setup.sound_loops)
4687 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4689 PlaySound(SND_GAME_LEVELTIME_BONUS);
4694 game.panel.active = FALSE;
4696 if (game_over_delay_3 > 0)
4698 game_over_delay_3--;
4708 // used instead of "level_nr" (needed for network games)
4709 int last_level_nr = levelset.level_nr;
4712 game.LevelSolved_GameEnd = TRUE;
4714 if (game.LevelSolved_SaveTape)
4716 // make sure that request dialog to save tape does not open door again
4717 if (!global.use_envelope_request)
4718 CloseDoor(DOOR_CLOSE_1);
4720 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4723 // if no tape is to be saved, close both doors simultaneously
4724 CloseDoor(DOOR_CLOSE_ALL);
4726 if (level_editor_test_game)
4728 SetGameStatus(GAME_MODE_MAIN);
4735 if (!game.LevelSolved_SaveScore)
4737 SetGameStatus(GAME_MODE_MAIN);
4744 if (level_nr == leveldir_current->handicap_level)
4746 leveldir_current->handicap_level++;
4748 SaveLevelSetup_SeriesInfo();
4751 if (setup.increment_levels &&
4752 level_nr < leveldir_current->last_level &&
4755 level_nr++; // advance to next level
4756 TapeErase(); // start with empty tape
4758 if (setup.auto_play_next_level)
4760 LoadLevel(level_nr);
4762 SaveLevelSetup_SeriesInfo();
4766 hi_pos = NewHiScore(last_level_nr);
4768 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4770 SetGameStatus(GAME_MODE_SCORES);
4772 DrawHallOfFame(last_level_nr, hi_pos);
4774 else if (setup.auto_play_next_level && setup.increment_levels &&
4775 last_level_nr < leveldir_current->last_level &&
4778 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4782 SetGameStatus(GAME_MODE_MAIN);
4788 int NewHiScore(int level_nr)
4792 boolean one_score_entry_per_name = !program.many_scores_per_name;
4794 LoadScore(level_nr);
4796 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4797 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4800 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4802 if (game.score_final > highscore[k].Score)
4804 // player has made it to the hall of fame
4806 if (k < MAX_SCORE_ENTRIES - 1)
4808 int m = MAX_SCORE_ENTRIES - 1;
4810 if (one_score_entry_per_name)
4812 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4813 if (strEqual(setup.player_name, highscore[l].Name))
4816 if (m == k) // player's new highscore overwrites his old one
4820 for (l = m; l > k; l--)
4822 strcpy(highscore[l].Name, highscore[l - 1].Name);
4823 highscore[l].Score = highscore[l - 1].Score;
4829 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4830 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4831 highscore[k].Score = game.score_final;
4836 else if (one_score_entry_per_name &&
4837 !strncmp(setup.player_name, highscore[k].Name,
4838 MAX_PLAYER_NAME_LEN))
4839 break; // player already there with a higher score
4843 SaveScore(level_nr);
4848 static int getElementMoveStepsizeExt(int x, int y, int direction)
4850 int element = Feld[x][y];
4851 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4852 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4853 int horiz_move = (dx != 0);
4854 int sign = (horiz_move ? dx : dy);
4855 int step = sign * element_info[element].move_stepsize;
4857 // special values for move stepsize for spring and things on conveyor belt
4860 if (CAN_FALL(element) &&
4861 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
4862 step = sign * MOVE_STEPSIZE_NORMAL / 2;
4863 else if (element == EL_SPRING)
4864 step = sign * MOVE_STEPSIZE_NORMAL * 2;
4870 static int getElementMoveStepsize(int x, int y)
4872 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
4875 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
4877 if (player->GfxAction != action || player->GfxDir != dir)
4879 player->GfxAction = action;
4880 player->GfxDir = dir;
4882 player->StepFrame = 0;
4886 static void ResetGfxFrame(int x, int y)
4888 // profiling showed that "autotest" spends 10~20% of its time in this function
4889 if (DrawingDeactivatedField())
4892 int element = Feld[x][y];
4893 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
4895 if (graphic_info[graphic].anim_global_sync)
4896 GfxFrame[x][y] = FrameCounter;
4897 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
4898 GfxFrame[x][y] = CustomValue[x][y];
4899 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
4900 GfxFrame[x][y] = element_info[element].collect_score;
4901 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
4902 GfxFrame[x][y] = ChangeDelay[x][y];
4905 static void ResetGfxAnimation(int x, int y)
4907 GfxAction[x][y] = ACTION_DEFAULT;
4908 GfxDir[x][y] = MovDir[x][y];
4911 ResetGfxFrame(x, y);
4914 static void ResetRandomAnimationValue(int x, int y)
4916 GfxRandom[x][y] = INIT_GFX_RANDOM();
4919 static void InitMovingField(int x, int y, int direction)
4921 int element = Feld[x][y];
4922 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4923 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4926 boolean is_moving_before, is_moving_after;
4928 // check if element was/is moving or being moved before/after mode change
4929 is_moving_before = (WasJustMoving[x][y] != 0);
4930 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
4932 // reset animation only for moving elements which change direction of moving
4933 // or which just started or stopped moving
4934 // (else CEs with property "can move" / "not moving" are reset each frame)
4935 if (is_moving_before != is_moving_after ||
4936 direction != MovDir[x][y])
4937 ResetGfxAnimation(x, y);
4939 MovDir[x][y] = direction;
4940 GfxDir[x][y] = direction;
4942 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
4943 direction == MV_DOWN && CAN_FALL(element) ?
4944 ACTION_FALLING : ACTION_MOVING);
4946 // this is needed for CEs with property "can move" / "not moving"
4948 if (is_moving_after)
4950 if (Feld[newx][newy] == EL_EMPTY)
4951 Feld[newx][newy] = EL_BLOCKED;
4953 MovDir[newx][newy] = MovDir[x][y];
4955 CustomValue[newx][newy] = CustomValue[x][y];
4957 GfxFrame[newx][newy] = GfxFrame[x][y];
4958 GfxRandom[newx][newy] = GfxRandom[x][y];
4959 GfxAction[newx][newy] = GfxAction[x][y];
4960 GfxDir[newx][newy] = GfxDir[x][y];
4964 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
4966 int direction = MovDir[x][y];
4967 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
4968 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
4974 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
4976 int oldx = x, oldy = y;
4977 int direction = MovDir[x][y];
4979 if (direction == MV_LEFT)
4981 else if (direction == MV_RIGHT)
4983 else if (direction == MV_UP)
4985 else if (direction == MV_DOWN)
4988 *comes_from_x = oldx;
4989 *comes_from_y = oldy;
4992 static int MovingOrBlocked2Element(int x, int y)
4994 int element = Feld[x][y];
4996 if (element == EL_BLOCKED)
5000 Blocked2Moving(x, y, &oldx, &oldy);
5001 return Feld[oldx][oldy];
5007 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5009 // like MovingOrBlocked2Element(), but if element is moving
5010 // and (x,y) is the field the moving element is just leaving,
5011 // return EL_BLOCKED instead of the element value
5012 int element = Feld[x][y];
5014 if (IS_MOVING(x, y))
5016 if (element == EL_BLOCKED)
5020 Blocked2Moving(x, y, &oldx, &oldy);
5021 return Feld[oldx][oldy];
5030 static void RemoveField(int x, int y)
5032 Feld[x][y] = EL_EMPTY;
5038 CustomValue[x][y] = 0;
5041 ChangeDelay[x][y] = 0;
5042 ChangePage[x][y] = -1;
5043 Pushed[x][y] = FALSE;
5045 GfxElement[x][y] = EL_UNDEFINED;
5046 GfxAction[x][y] = ACTION_DEFAULT;
5047 GfxDir[x][y] = MV_NONE;
5050 static void RemoveMovingField(int x, int y)
5052 int oldx = x, oldy = y, newx = x, newy = y;
5053 int element = Feld[x][y];
5054 int next_element = EL_UNDEFINED;
5056 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5059 if (IS_MOVING(x, y))
5061 Moving2Blocked(x, y, &newx, &newy);
5063 if (Feld[newx][newy] != EL_BLOCKED)
5065 // element is moving, but target field is not free (blocked), but
5066 // already occupied by something different (example: acid pool);
5067 // in this case, only remove the moving field, but not the target
5069 RemoveField(oldx, oldy);
5071 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5073 TEST_DrawLevelField(oldx, oldy);
5078 else if (element == EL_BLOCKED)
5080 Blocked2Moving(x, y, &oldx, &oldy);
5081 if (!IS_MOVING(oldx, oldy))
5085 if (element == EL_BLOCKED &&
5086 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5087 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5088 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5089 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5090 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5091 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5092 next_element = get_next_element(Feld[oldx][oldy]);
5094 RemoveField(oldx, oldy);
5095 RemoveField(newx, newy);
5097 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5099 if (next_element != EL_UNDEFINED)
5100 Feld[oldx][oldy] = next_element;
5102 TEST_DrawLevelField(oldx, oldy);
5103 TEST_DrawLevelField(newx, newy);
5106 void DrawDynamite(int x, int y)
5108 int sx = SCREENX(x), sy = SCREENY(y);
5109 int graphic = el2img(Feld[x][y]);
5112 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5115 if (IS_WALKABLE_INSIDE(Back[x][y]))
5119 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5120 else if (Store[x][y])
5121 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5123 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5125 if (Back[x][y] || Store[x][y])
5126 DrawGraphicThruMask(sx, sy, graphic, frame);
5128 DrawGraphic(sx, sy, graphic, frame);
5131 static void CheckDynamite(int x, int y)
5133 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5137 if (MovDelay[x][y] != 0)
5140 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5146 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5151 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5153 boolean num_checked_players = 0;
5156 for (i = 0; i < MAX_PLAYERS; i++)
5158 if (stored_player[i].active)
5160 int sx = stored_player[i].jx;
5161 int sy = stored_player[i].jy;
5163 if (num_checked_players == 0)
5170 *sx1 = MIN(*sx1, sx);
5171 *sy1 = MIN(*sy1, sy);
5172 *sx2 = MAX(*sx2, sx);
5173 *sy2 = MAX(*sy2, sy);
5176 num_checked_players++;
5181 static boolean checkIfAllPlayersFitToScreen_RND(void)
5183 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5185 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5187 return (sx2 - sx1 < SCR_FIELDX &&
5188 sy2 - sy1 < SCR_FIELDY);
5191 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5193 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5195 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5197 *sx = (sx1 + sx2) / 2;
5198 *sy = (sy1 + sy2) / 2;
5201 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5202 boolean center_screen, boolean quick_relocation)
5204 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5205 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5206 boolean no_delay = (tape.warp_forward);
5207 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5208 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5209 int new_scroll_x, new_scroll_y;
5211 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5213 // case 1: quick relocation inside visible screen (without scrolling)
5220 if (!level.shifted_relocation || center_screen)
5222 // relocation _with_ centering of screen
5224 new_scroll_x = SCROLL_POSITION_X(x);
5225 new_scroll_y = SCROLL_POSITION_Y(y);
5229 // relocation _without_ centering of screen
5231 int center_scroll_x = SCROLL_POSITION_X(old_x);
5232 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5233 int offset_x = x + (scroll_x - center_scroll_x);
5234 int offset_y = y + (scroll_y - center_scroll_y);
5236 // for new screen position, apply previous offset to center position
5237 new_scroll_x = SCROLL_POSITION_X(offset_x);
5238 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5241 if (quick_relocation)
5243 // case 2: quick relocation (redraw without visible scrolling)
5245 scroll_x = new_scroll_x;
5246 scroll_y = new_scroll_y;
5253 // case 3: visible relocation (with scrolling to new position)
5255 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5257 SetVideoFrameDelay(wait_delay_value);
5259 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5262 int fx = FX, fy = FY;
5264 dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5265 dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5267 if (dx == 0 && dy == 0) // no scrolling needed at all
5273 fx += dx * TILEX / 2;
5274 fy += dy * TILEY / 2;
5276 ScrollLevel(dx, dy);
5279 // scroll in two steps of half tile size to make things smoother
5280 BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY);
5282 // scroll second step to align at full tile size
5283 BlitScreenToBitmap(window);
5289 SetVideoFrameDelay(frame_delay_value_old);
5292 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5294 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5295 int player_nr = GET_PLAYER_NR(el_player);
5296 struct PlayerInfo *player = &stored_player[player_nr];
5297 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5298 boolean no_delay = (tape.warp_forward);
5299 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5300 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5301 int old_jx = player->jx;
5302 int old_jy = player->jy;
5303 int old_element = Feld[old_jx][old_jy];
5304 int element = Feld[jx][jy];
5305 boolean player_relocated = (old_jx != jx || old_jy != jy);
5307 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5308 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5309 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5310 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5311 int leave_side_horiz = move_dir_horiz;
5312 int leave_side_vert = move_dir_vert;
5313 int enter_side = enter_side_horiz | enter_side_vert;
5314 int leave_side = leave_side_horiz | leave_side_vert;
5316 if (player->buried) // do not reanimate dead player
5319 if (!player_relocated) // no need to relocate the player
5322 if (IS_PLAYER(jx, jy)) // player already placed at new position
5324 RemoveField(jx, jy); // temporarily remove newly placed player
5325 DrawLevelField(jx, jy);
5328 if (player->present)
5330 while (player->MovPos)
5332 ScrollPlayer(player, SCROLL_GO_ON);
5333 ScrollScreen(NULL, SCROLL_GO_ON);
5335 AdvanceFrameAndPlayerCounters(player->index_nr);
5339 BackToFront_WithFrameDelay(wait_delay_value);
5342 DrawPlayer(player); // needed here only to cleanup last field
5343 DrawLevelField(player->jx, player->jy); // remove player graphic
5345 player->is_moving = FALSE;
5348 if (IS_CUSTOM_ELEMENT(old_element))
5349 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5351 player->index_bit, leave_side);
5353 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5355 player->index_bit, leave_side);
5357 Feld[jx][jy] = el_player;
5358 InitPlayerField(jx, jy, el_player, TRUE);
5360 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5361 possible that the relocation target field did not contain a player element,
5362 but a walkable element, to which the new player was relocated -- in this
5363 case, restore that (already initialized!) element on the player field */
5364 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5366 Feld[jx][jy] = element; // restore previously existing element
5369 // only visually relocate centered player
5370 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5371 FALSE, level.instant_relocation);
5373 TestIfPlayerTouchesBadThing(jx, jy);
5374 TestIfPlayerTouchesCustomElement(jx, jy);
5376 if (IS_CUSTOM_ELEMENT(element))
5377 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5378 player->index_bit, enter_side);
5380 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5381 player->index_bit, enter_side);
5383 if (player->is_switching)
5385 /* ensure that relocation while still switching an element does not cause
5386 a new element to be treated as also switched directly after relocation
5387 (this is important for teleporter switches that teleport the player to
5388 a place where another teleporter switch is in the same direction, which
5389 would then incorrectly be treated as immediately switched before the
5390 direction key that caused the switch was released) */
5392 player->switch_x += jx - old_jx;
5393 player->switch_y += jy - old_jy;
5397 static void Explode(int ex, int ey, int phase, int mode)
5403 // !!! eliminate this variable !!!
5404 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5406 if (game.explosions_delayed)
5408 ExplodeField[ex][ey] = mode;
5412 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5414 int center_element = Feld[ex][ey];
5415 int artwork_element, explosion_element; // set these values later
5417 // remove things displayed in background while burning dynamite
5418 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5421 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5423 // put moving element to center field (and let it explode there)
5424 center_element = MovingOrBlocked2Element(ex, ey);
5425 RemoveMovingField(ex, ey);
5426 Feld[ex][ey] = center_element;
5429 // now "center_element" is finally determined -- set related values now
5430 artwork_element = center_element; // for custom player artwork
5431 explosion_element = center_element; // for custom player artwork
5433 if (IS_PLAYER(ex, ey))
5435 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5437 artwork_element = stored_player[player_nr].artwork_element;
5439 if (level.use_explosion_element[player_nr])
5441 explosion_element = level.explosion_element[player_nr];
5442 artwork_element = explosion_element;
5446 if (mode == EX_TYPE_NORMAL ||
5447 mode == EX_TYPE_CENTER ||
5448 mode == EX_TYPE_CROSS)
5449 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5451 last_phase = element_info[explosion_element].explosion_delay + 1;
5453 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5455 int xx = x - ex + 1;
5456 int yy = y - ey + 1;
5459 if (!IN_LEV_FIELD(x, y) ||
5460 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5461 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5464 element = Feld[x][y];
5466 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5468 element = MovingOrBlocked2Element(x, y);
5470 if (!IS_EXPLOSION_PROOF(element))
5471 RemoveMovingField(x, y);
5474 // indestructible elements can only explode in center (but not flames)
5475 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5476 mode == EX_TYPE_BORDER)) ||
5477 element == EL_FLAMES)
5480 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5481 behaviour, for example when touching a yamyam that explodes to rocks
5482 with active deadly shield, a rock is created under the player !!! */
5483 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5485 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5486 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5487 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5489 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5492 if (IS_ACTIVE_BOMB(element))
5494 // re-activate things under the bomb like gate or penguin
5495 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5502 // save walkable background elements while explosion on same tile
5503 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5504 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5505 Back[x][y] = element;
5507 // ignite explodable elements reached by other explosion
5508 if (element == EL_EXPLOSION)
5509 element = Store2[x][y];
5511 if (AmoebaNr[x][y] &&
5512 (element == EL_AMOEBA_FULL ||
5513 element == EL_BD_AMOEBA ||
5514 element == EL_AMOEBA_GROWING))
5516 AmoebaCnt[AmoebaNr[x][y]]--;
5517 AmoebaCnt2[AmoebaNr[x][y]]--;
5522 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5524 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5526 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5528 if (PLAYERINFO(ex, ey)->use_murphy)
5529 Store[x][y] = EL_EMPTY;
5532 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5533 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5534 else if (ELEM_IS_PLAYER(center_element))
5535 Store[x][y] = EL_EMPTY;
5536 else if (center_element == EL_YAMYAM)
5537 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5538 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5539 Store[x][y] = element_info[center_element].content.e[xx][yy];
5541 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5542 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5543 // otherwise) -- FIX THIS !!!
5544 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5545 Store[x][y] = element_info[element].content.e[1][1];
5547 else if (!CAN_EXPLODE(element))
5548 Store[x][y] = element_info[element].content.e[1][1];
5551 Store[x][y] = EL_EMPTY;
5553 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5554 center_element == EL_AMOEBA_TO_DIAMOND)
5555 Store2[x][y] = element;
5557 Feld[x][y] = EL_EXPLOSION;
5558 GfxElement[x][y] = artwork_element;
5560 ExplodePhase[x][y] = 1;
5561 ExplodeDelay[x][y] = last_phase;
5566 if (center_element == EL_YAMYAM)
5567 game.yamyam_content_nr =
5568 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5580 GfxFrame[x][y] = 0; // restart explosion animation
5582 last_phase = ExplodeDelay[x][y];
5584 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5586 // this can happen if the player leaves an explosion just in time
5587 if (GfxElement[x][y] == EL_UNDEFINED)
5588 GfxElement[x][y] = EL_EMPTY;
5590 border_element = Store2[x][y];
5591 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5592 border_element = StorePlayer[x][y];
5594 if (phase == element_info[border_element].ignition_delay ||
5595 phase == last_phase)
5597 boolean border_explosion = FALSE;
5599 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5600 !PLAYER_EXPLOSION_PROTECTED(x, y))
5602 KillPlayerUnlessExplosionProtected(x, y);
5603 border_explosion = TRUE;
5605 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5607 Feld[x][y] = Store2[x][y];
5610 border_explosion = TRUE;
5612 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5614 AmoebeUmwandeln(x, y);
5616 border_explosion = TRUE;
5619 // if an element just explodes due to another explosion (chain-reaction),
5620 // do not immediately end the new explosion when it was the last frame of
5621 // the explosion (as it would be done in the following "if"-statement!)
5622 if (border_explosion && phase == last_phase)
5626 if (phase == last_phase)
5630 element = Feld[x][y] = Store[x][y];
5631 Store[x][y] = Store2[x][y] = 0;
5632 GfxElement[x][y] = EL_UNDEFINED;
5634 // player can escape from explosions and might therefore be still alive
5635 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5636 element <= EL_PLAYER_IS_EXPLODING_4)
5638 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5639 int explosion_element = EL_PLAYER_1 + player_nr;
5640 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5641 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5643 if (level.use_explosion_element[player_nr])
5644 explosion_element = level.explosion_element[player_nr];
5646 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5647 element_info[explosion_element].content.e[xx][yy]);
5650 // restore probably existing indestructible background element
5651 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5652 element = Feld[x][y] = Back[x][y];
5655 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5656 GfxDir[x][y] = MV_NONE;
5657 ChangeDelay[x][y] = 0;
5658 ChangePage[x][y] = -1;
5660 CustomValue[x][y] = 0;
5662 InitField_WithBug2(x, y, FALSE);
5664 TEST_DrawLevelField(x, y);
5666 TestIfElementTouchesCustomElement(x, y);
5668 if (GFX_CRUMBLED(element))
5669 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5671 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5672 StorePlayer[x][y] = 0;
5674 if (ELEM_IS_PLAYER(element))
5675 RelocatePlayer(x, y, element);
5677 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5679 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5680 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5683 TEST_DrawLevelFieldCrumbled(x, y);
5685 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5687 DrawLevelElement(x, y, Back[x][y]);
5688 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5690 else if (IS_WALKABLE_UNDER(Back[x][y]))
5692 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5693 DrawLevelElementThruMask(x, y, Back[x][y]);
5695 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5696 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5700 static void DynaExplode(int ex, int ey)
5703 int dynabomb_element = Feld[ex][ey];
5704 int dynabomb_size = 1;
5705 boolean dynabomb_xl = FALSE;
5706 struct PlayerInfo *player;
5707 static int xy[4][2] =
5715 if (IS_ACTIVE_BOMB(dynabomb_element))
5717 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5718 dynabomb_size = player->dynabomb_size;
5719 dynabomb_xl = player->dynabomb_xl;
5720 player->dynabombs_left++;
5723 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5725 for (i = 0; i < NUM_DIRECTIONS; i++)
5727 for (j = 1; j <= dynabomb_size; j++)
5729 int x = ex + j * xy[i][0];
5730 int y = ey + j * xy[i][1];
5733 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5736 element = Feld[x][y];
5738 // do not restart explosions of fields with active bombs
5739 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5742 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5744 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5745 !IS_DIGGABLE(element) && !dynabomb_xl)
5751 void Bang(int x, int y)
5753 int element = MovingOrBlocked2Element(x, y);
5754 int explosion_type = EX_TYPE_NORMAL;
5756 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5758 struct PlayerInfo *player = PLAYERINFO(x, y);
5760 element = Feld[x][y] = player->initial_element;
5762 if (level.use_explosion_element[player->index_nr])
5764 int explosion_element = level.explosion_element[player->index_nr];
5766 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5767 explosion_type = EX_TYPE_CROSS;
5768 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5769 explosion_type = EX_TYPE_CENTER;
5777 case EL_BD_BUTTERFLY:
5780 case EL_DARK_YAMYAM:
5784 RaiseScoreElement(element);
5787 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5788 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5789 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5790 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5791 case EL_DYNABOMB_INCREASE_NUMBER:
5792 case EL_DYNABOMB_INCREASE_SIZE:
5793 case EL_DYNABOMB_INCREASE_POWER:
5794 explosion_type = EX_TYPE_DYNA;
5797 case EL_DC_LANDMINE:
5798 explosion_type = EX_TYPE_CENTER;
5803 case EL_LAMP_ACTIVE:
5804 case EL_AMOEBA_TO_DIAMOND:
5805 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5806 explosion_type = EX_TYPE_CENTER;
5810 if (element_info[element].explosion_type == EXPLODES_CROSS)
5811 explosion_type = EX_TYPE_CROSS;
5812 else if (element_info[element].explosion_type == EXPLODES_1X1)
5813 explosion_type = EX_TYPE_CENTER;
5817 if (explosion_type == EX_TYPE_DYNA)
5820 Explode(x, y, EX_PHASE_START, explosion_type);
5822 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5825 static void SplashAcid(int x, int y)
5827 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5828 (!IN_LEV_FIELD(x - 1, y - 2) ||
5829 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5830 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5832 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5833 (!IN_LEV_FIELD(x + 1, y - 2) ||
5834 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5835 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5837 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5840 static void InitBeltMovement(void)
5842 static int belt_base_element[4] =
5844 EL_CONVEYOR_BELT_1_LEFT,
5845 EL_CONVEYOR_BELT_2_LEFT,
5846 EL_CONVEYOR_BELT_3_LEFT,
5847 EL_CONVEYOR_BELT_4_LEFT
5849 static int belt_base_active_element[4] =
5851 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5852 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5853 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5854 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5859 // set frame order for belt animation graphic according to belt direction
5860 for (i = 0; i < NUM_BELTS; i++)
5864 for (j = 0; j < NUM_BELT_PARTS; j++)
5866 int element = belt_base_active_element[belt_nr] + j;
5867 int graphic_1 = el2img(element);
5868 int graphic_2 = el2panelimg(element);
5870 if (game.belt_dir[i] == MV_LEFT)
5872 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5873 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5877 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5878 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5883 SCAN_PLAYFIELD(x, y)
5885 int element = Feld[x][y];
5887 for (i = 0; i < NUM_BELTS; i++)
5889 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
5891 int e_belt_nr = getBeltNrFromBeltElement(element);
5894 if (e_belt_nr == belt_nr)
5896 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
5898 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
5905 static void ToggleBeltSwitch(int x, int y)
5907 static int belt_base_element[4] =
5909 EL_CONVEYOR_BELT_1_LEFT,
5910 EL_CONVEYOR_BELT_2_LEFT,
5911 EL_CONVEYOR_BELT_3_LEFT,
5912 EL_CONVEYOR_BELT_4_LEFT
5914 static int belt_base_active_element[4] =
5916 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5917 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5918 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5919 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5921 static int belt_base_switch_element[4] =
5923 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
5924 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
5925 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
5926 EL_CONVEYOR_BELT_4_SWITCH_LEFT
5928 static int belt_move_dir[4] =
5936 int element = Feld[x][y];
5937 int belt_nr = getBeltNrFromBeltSwitchElement(element);
5938 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
5939 int belt_dir = belt_move_dir[belt_dir_nr];
5942 if (!IS_BELT_SWITCH(element))
5945 game.belt_dir_nr[belt_nr] = belt_dir_nr;
5946 game.belt_dir[belt_nr] = belt_dir;
5948 if (belt_dir_nr == 3)
5951 // set frame order for belt animation graphic according to belt direction
5952 for (i = 0; i < NUM_BELT_PARTS; i++)
5954 int element = belt_base_active_element[belt_nr] + i;
5955 int graphic_1 = el2img(element);
5956 int graphic_2 = el2panelimg(element);
5958 if (belt_dir == MV_LEFT)
5960 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5961 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5965 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5966 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5970 SCAN_PLAYFIELD(xx, yy)
5972 int element = Feld[xx][yy];
5974 if (IS_BELT_SWITCH(element))
5976 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
5978 if (e_belt_nr == belt_nr)
5980 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
5981 TEST_DrawLevelField(xx, yy);
5984 else if (IS_BELT(element) && belt_dir != MV_NONE)
5986 int e_belt_nr = getBeltNrFromBeltElement(element);
5988 if (e_belt_nr == belt_nr)
5990 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
5992 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
5993 TEST_DrawLevelField(xx, yy);
5996 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
5998 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6000 if (e_belt_nr == belt_nr)
6002 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6004 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6005 TEST_DrawLevelField(xx, yy);
6011 static void ToggleSwitchgateSwitch(int x, int y)
6015 game.switchgate_pos = !game.switchgate_pos;
6017 SCAN_PLAYFIELD(xx, yy)
6019 int element = Feld[xx][yy];
6021 if (element == EL_SWITCHGATE_SWITCH_UP)
6023 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6024 TEST_DrawLevelField(xx, yy);
6026 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6028 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6029 TEST_DrawLevelField(xx, yy);
6031 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6033 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6034 TEST_DrawLevelField(xx, yy);
6036 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6038 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6039 TEST_DrawLevelField(xx, yy);
6041 else if (element == EL_SWITCHGATE_OPEN ||
6042 element == EL_SWITCHGATE_OPENING)
6044 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6046 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6048 else if (element == EL_SWITCHGATE_CLOSED ||
6049 element == EL_SWITCHGATE_CLOSING)
6051 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6053 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6058 static int getInvisibleActiveFromInvisibleElement(int element)
6060 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6061 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6062 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6066 static int getInvisibleFromInvisibleActiveElement(int element)
6068 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6069 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6070 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6074 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6078 SCAN_PLAYFIELD(x, y)
6080 int element = Feld[x][y];
6082 if (element == EL_LIGHT_SWITCH &&
6083 game.light_time_left > 0)
6085 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6086 TEST_DrawLevelField(x, y);
6088 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6089 game.light_time_left == 0)
6091 Feld[x][y] = EL_LIGHT_SWITCH;
6092 TEST_DrawLevelField(x, y);
6094 else if (element == EL_EMC_DRIPPER &&
6095 game.light_time_left > 0)
6097 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6098 TEST_DrawLevelField(x, y);
6100 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6101 game.light_time_left == 0)
6103 Feld[x][y] = EL_EMC_DRIPPER;
6104 TEST_DrawLevelField(x, y);
6106 else if (element == EL_INVISIBLE_STEELWALL ||
6107 element == EL_INVISIBLE_WALL ||
6108 element == EL_INVISIBLE_SAND)
6110 if (game.light_time_left > 0)
6111 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6113 TEST_DrawLevelField(x, y);
6115 // uncrumble neighbour fields, if needed
6116 if (element == EL_INVISIBLE_SAND)
6117 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6119 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6120 element == EL_INVISIBLE_WALL_ACTIVE ||
6121 element == EL_INVISIBLE_SAND_ACTIVE)
6123 if (game.light_time_left == 0)
6124 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6126 TEST_DrawLevelField(x, y);
6128 // re-crumble neighbour fields, if needed
6129 if (element == EL_INVISIBLE_SAND)
6130 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6135 static void RedrawAllInvisibleElementsForLenses(void)
6139 SCAN_PLAYFIELD(x, y)
6141 int element = Feld[x][y];
6143 if (element == EL_EMC_DRIPPER &&
6144 game.lenses_time_left > 0)
6146 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6147 TEST_DrawLevelField(x, y);
6149 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6150 game.lenses_time_left == 0)
6152 Feld[x][y] = EL_EMC_DRIPPER;
6153 TEST_DrawLevelField(x, y);
6155 else if (element == EL_INVISIBLE_STEELWALL ||
6156 element == EL_INVISIBLE_WALL ||
6157 element == EL_INVISIBLE_SAND)
6159 if (game.lenses_time_left > 0)
6160 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6162 TEST_DrawLevelField(x, y);
6164 // uncrumble neighbour fields, if needed
6165 if (element == EL_INVISIBLE_SAND)
6166 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6168 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6169 element == EL_INVISIBLE_WALL_ACTIVE ||
6170 element == EL_INVISIBLE_SAND_ACTIVE)
6172 if (game.lenses_time_left == 0)
6173 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6175 TEST_DrawLevelField(x, y);
6177 // re-crumble neighbour fields, if needed
6178 if (element == EL_INVISIBLE_SAND)
6179 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6184 static void RedrawAllInvisibleElementsForMagnifier(void)
6188 SCAN_PLAYFIELD(x, y)
6190 int element = Feld[x][y];
6192 if (element == EL_EMC_FAKE_GRASS &&
6193 game.magnify_time_left > 0)
6195 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6196 TEST_DrawLevelField(x, y);
6198 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6199 game.magnify_time_left == 0)
6201 Feld[x][y] = EL_EMC_FAKE_GRASS;
6202 TEST_DrawLevelField(x, y);
6204 else if (IS_GATE_GRAY(element) &&
6205 game.magnify_time_left > 0)
6207 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6208 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6209 IS_EM_GATE_GRAY(element) ?
6210 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6211 IS_EMC_GATE_GRAY(element) ?
6212 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6213 IS_DC_GATE_GRAY(element) ?
6214 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6216 TEST_DrawLevelField(x, y);
6218 else if (IS_GATE_GRAY_ACTIVE(element) &&
6219 game.magnify_time_left == 0)
6221 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6222 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6223 IS_EM_GATE_GRAY_ACTIVE(element) ?
6224 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6225 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6226 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6227 IS_DC_GATE_GRAY_ACTIVE(element) ?
6228 EL_DC_GATE_WHITE_GRAY :
6230 TEST_DrawLevelField(x, y);
6235 static void ToggleLightSwitch(int x, int y)
6237 int element = Feld[x][y];
6239 game.light_time_left =
6240 (element == EL_LIGHT_SWITCH ?
6241 level.time_light * FRAMES_PER_SECOND : 0);
6243 RedrawAllLightSwitchesAndInvisibleElements();
6246 static void ActivateTimegateSwitch(int x, int y)
6250 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6252 SCAN_PLAYFIELD(xx, yy)
6254 int element = Feld[xx][yy];
6256 if (element == EL_TIMEGATE_CLOSED ||
6257 element == EL_TIMEGATE_CLOSING)
6259 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6260 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6264 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6266 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6267 TEST_DrawLevelField(xx, yy);
6273 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6274 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6277 static void Impact(int x, int y)
6279 boolean last_line = (y == lev_fieldy - 1);
6280 boolean object_hit = FALSE;
6281 boolean impact = (last_line || object_hit);
6282 int element = Feld[x][y];
6283 int smashed = EL_STEELWALL;
6285 if (!last_line) // check if element below was hit
6287 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6290 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6291 MovDir[x][y + 1] != MV_DOWN ||
6292 MovPos[x][y + 1] <= TILEY / 2));
6294 // do not smash moving elements that left the smashed field in time
6295 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6296 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6299 #if USE_QUICKSAND_IMPACT_BUGFIX
6300 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6302 RemoveMovingField(x, y + 1);
6303 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6304 Feld[x][y + 2] = EL_ROCK;
6305 TEST_DrawLevelField(x, y + 2);
6310 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6312 RemoveMovingField(x, y + 1);
6313 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6314 Feld[x][y + 2] = EL_ROCK;
6315 TEST_DrawLevelField(x, y + 2);
6322 smashed = MovingOrBlocked2Element(x, y + 1);
6324 impact = (last_line || object_hit);
6327 if (!last_line && smashed == EL_ACID) // element falls into acid
6329 SplashAcid(x, y + 1);
6333 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6334 // only reset graphic animation if graphic really changes after impact
6336 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6338 ResetGfxAnimation(x, y);
6339 TEST_DrawLevelField(x, y);
6342 if (impact && CAN_EXPLODE_IMPACT(element))
6347 else if (impact && element == EL_PEARL &&
6348 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6350 ResetGfxAnimation(x, y);
6352 Feld[x][y] = EL_PEARL_BREAKING;
6353 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6356 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6358 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6363 if (impact && element == EL_AMOEBA_DROP)
6365 if (object_hit && IS_PLAYER(x, y + 1))
6366 KillPlayerUnlessEnemyProtected(x, y + 1);
6367 else if (object_hit && smashed == EL_PENGUIN)
6371 Feld[x][y] = EL_AMOEBA_GROWING;
6372 Store[x][y] = EL_AMOEBA_WET;
6374 ResetRandomAnimationValue(x, y);
6379 if (object_hit) // check which object was hit
6381 if ((CAN_PASS_MAGIC_WALL(element) &&
6382 (smashed == EL_MAGIC_WALL ||
6383 smashed == EL_BD_MAGIC_WALL)) ||
6384 (CAN_PASS_DC_MAGIC_WALL(element) &&
6385 smashed == EL_DC_MAGIC_WALL))
6388 int activated_magic_wall =
6389 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6390 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6391 EL_DC_MAGIC_WALL_ACTIVE);
6393 // activate magic wall / mill
6394 SCAN_PLAYFIELD(xx, yy)
6396 if (Feld[xx][yy] == smashed)
6397 Feld[xx][yy] = activated_magic_wall;
6400 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6401 game.magic_wall_active = TRUE;
6403 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6404 SND_MAGIC_WALL_ACTIVATING :
6405 smashed == EL_BD_MAGIC_WALL ?
6406 SND_BD_MAGIC_WALL_ACTIVATING :
6407 SND_DC_MAGIC_WALL_ACTIVATING));
6410 if (IS_PLAYER(x, y + 1))
6412 if (CAN_SMASH_PLAYER(element))
6414 KillPlayerUnlessEnemyProtected(x, y + 1);
6418 else if (smashed == EL_PENGUIN)
6420 if (CAN_SMASH_PLAYER(element))
6426 else if (element == EL_BD_DIAMOND)
6428 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6434 else if (((element == EL_SP_INFOTRON ||
6435 element == EL_SP_ZONK) &&
6436 (smashed == EL_SP_SNIKSNAK ||
6437 smashed == EL_SP_ELECTRON ||
6438 smashed == EL_SP_DISK_ORANGE)) ||
6439 (element == EL_SP_INFOTRON &&
6440 smashed == EL_SP_DISK_YELLOW))
6445 else if (CAN_SMASH_EVERYTHING(element))
6447 if (IS_CLASSIC_ENEMY(smashed) ||
6448 CAN_EXPLODE_SMASHED(smashed))
6453 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6455 if (smashed == EL_LAMP ||
6456 smashed == EL_LAMP_ACTIVE)
6461 else if (smashed == EL_NUT)
6463 Feld[x][y + 1] = EL_NUT_BREAKING;
6464 PlayLevelSound(x, y, SND_NUT_BREAKING);
6465 RaiseScoreElement(EL_NUT);
6468 else if (smashed == EL_PEARL)
6470 ResetGfxAnimation(x, y);
6472 Feld[x][y + 1] = EL_PEARL_BREAKING;
6473 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6476 else if (smashed == EL_DIAMOND)
6478 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6479 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6482 else if (IS_BELT_SWITCH(smashed))
6484 ToggleBeltSwitch(x, y + 1);
6486 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6487 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6488 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6489 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6491 ToggleSwitchgateSwitch(x, y + 1);
6493 else if (smashed == EL_LIGHT_SWITCH ||
6494 smashed == EL_LIGHT_SWITCH_ACTIVE)
6496 ToggleLightSwitch(x, y + 1);
6500 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6502 CheckElementChangeBySide(x, y + 1, smashed, element,
6503 CE_SWITCHED, CH_SIDE_TOP);
6504 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6510 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6515 // play sound of magic wall / mill
6517 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6518 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6519 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6521 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6522 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6523 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6524 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6525 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6526 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6531 // play sound of object that hits the ground
6532 if (last_line || object_hit)
6533 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6536 static void TurnRoundExt(int x, int y)
6548 { 0, 0 }, { 0, 0 }, { 0, 0 },
6553 int left, right, back;
6557 { MV_DOWN, MV_UP, MV_RIGHT },
6558 { MV_UP, MV_DOWN, MV_LEFT },
6560 { MV_LEFT, MV_RIGHT, MV_DOWN },
6564 { MV_RIGHT, MV_LEFT, MV_UP }
6567 int element = Feld[x][y];
6568 int move_pattern = element_info[element].move_pattern;
6570 int old_move_dir = MovDir[x][y];
6571 int left_dir = turn[old_move_dir].left;
6572 int right_dir = turn[old_move_dir].right;
6573 int back_dir = turn[old_move_dir].back;
6575 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6576 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6577 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6578 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6580 int left_x = x + left_dx, left_y = y + left_dy;
6581 int right_x = x + right_dx, right_y = y + right_dy;
6582 int move_x = x + move_dx, move_y = y + move_dy;
6586 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6588 TestIfBadThingTouchesOtherBadThing(x, y);
6590 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6591 MovDir[x][y] = right_dir;
6592 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6593 MovDir[x][y] = left_dir;
6595 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6597 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6600 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6602 TestIfBadThingTouchesOtherBadThing(x, y);
6604 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6605 MovDir[x][y] = left_dir;
6606 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6607 MovDir[x][y] = right_dir;
6609 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6611 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6614 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6616 TestIfBadThingTouchesOtherBadThing(x, y);
6618 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6619 MovDir[x][y] = left_dir;
6620 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6621 MovDir[x][y] = right_dir;
6623 if (MovDir[x][y] != old_move_dir)
6626 else if (element == EL_YAMYAM)
6628 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6629 boolean can_turn_right = YAMYAM_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] = 16 + 16 * RND(3);
6642 else if (element == EL_DARK_YAMYAM)
6644 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6646 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6649 if (can_turn_left && can_turn_right)
6650 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6651 else if (can_turn_left)
6652 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6653 else if (can_turn_right)
6654 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6656 MovDir[x][y] = back_dir;
6658 MovDelay[x][y] = 16 + 16 * RND(3);
6660 else if (element == EL_PACMAN)
6662 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6663 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6665 if (can_turn_left && can_turn_right)
6666 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6667 else if (can_turn_left)
6668 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6669 else if (can_turn_right)
6670 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6672 MovDir[x][y] = back_dir;
6674 MovDelay[x][y] = 6 + RND(40);
6676 else if (element == EL_PIG)
6678 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6679 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6680 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6681 boolean should_turn_left, should_turn_right, should_move_on;
6683 int rnd = RND(rnd_value);
6685 should_turn_left = (can_turn_left &&
6687 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6688 y + back_dy + left_dy)));
6689 should_turn_right = (can_turn_right &&
6691 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6692 y + back_dy + right_dy)));
6693 should_move_on = (can_move_on &&
6696 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6697 y + move_dy + left_dy) ||
6698 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6699 y + move_dy + right_dy)));
6701 if (should_turn_left || should_turn_right || should_move_on)
6703 if (should_turn_left && should_turn_right && should_move_on)
6704 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6705 rnd < 2 * rnd_value / 3 ? right_dir :
6707 else if (should_turn_left && should_turn_right)
6708 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6709 else if (should_turn_left && should_move_on)
6710 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6711 else if (should_turn_right && should_move_on)
6712 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6713 else if (should_turn_left)
6714 MovDir[x][y] = left_dir;
6715 else if (should_turn_right)
6716 MovDir[x][y] = right_dir;
6717 else if (should_move_on)
6718 MovDir[x][y] = old_move_dir;
6720 else if (can_move_on && rnd > rnd_value / 8)
6721 MovDir[x][y] = old_move_dir;
6722 else if (can_turn_left && can_turn_right)
6723 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6724 else if (can_turn_left && rnd > rnd_value / 8)
6725 MovDir[x][y] = left_dir;
6726 else if (can_turn_right && rnd > rnd_value/8)
6727 MovDir[x][y] = right_dir;
6729 MovDir[x][y] = back_dir;
6731 xx = x + move_xy[MovDir[x][y]].dx;
6732 yy = y + move_xy[MovDir[x][y]].dy;
6734 if (!IN_LEV_FIELD(xx, yy) ||
6735 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6736 MovDir[x][y] = old_move_dir;
6740 else if (element == EL_DRAGON)
6742 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6743 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6744 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6746 int rnd = RND(rnd_value);
6748 if (can_move_on && rnd > rnd_value / 8)
6749 MovDir[x][y] = old_move_dir;
6750 else if (can_turn_left && can_turn_right)
6751 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6752 else if (can_turn_left && rnd > rnd_value / 8)
6753 MovDir[x][y] = left_dir;
6754 else if (can_turn_right && rnd > rnd_value / 8)
6755 MovDir[x][y] = right_dir;
6757 MovDir[x][y] = back_dir;
6759 xx = x + move_xy[MovDir[x][y]].dx;
6760 yy = y + move_xy[MovDir[x][y]].dy;
6762 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6763 MovDir[x][y] = old_move_dir;
6767 else if (element == EL_MOLE)
6769 boolean can_move_on =
6770 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6771 IS_AMOEBOID(Feld[move_x][move_y]) ||
6772 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6775 boolean can_turn_left =
6776 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6777 IS_AMOEBOID(Feld[left_x][left_y])));
6779 boolean can_turn_right =
6780 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6781 IS_AMOEBOID(Feld[right_x][right_y])));
6783 if (can_turn_left && can_turn_right)
6784 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6785 else if (can_turn_left)
6786 MovDir[x][y] = left_dir;
6788 MovDir[x][y] = right_dir;
6791 if (MovDir[x][y] != old_move_dir)
6794 else if (element == EL_BALLOON)
6796 MovDir[x][y] = game.wind_direction;
6799 else if (element == EL_SPRING)
6801 if (MovDir[x][y] & MV_HORIZONTAL)
6803 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6804 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6806 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6807 ResetGfxAnimation(move_x, move_y);
6808 TEST_DrawLevelField(move_x, move_y);
6810 MovDir[x][y] = back_dir;
6812 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6813 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6814 MovDir[x][y] = MV_NONE;
6819 else if (element == EL_ROBOT ||
6820 element == EL_SATELLITE ||
6821 element == EL_PENGUIN ||
6822 element == EL_EMC_ANDROID)
6824 int attr_x = -1, attr_y = -1;
6826 if (game.all_players_gone)
6828 attr_x = game.exit_x;
6829 attr_y = game.exit_y;
6835 for (i = 0; i < MAX_PLAYERS; i++)
6837 struct PlayerInfo *player = &stored_player[i];
6838 int jx = player->jx, jy = player->jy;
6840 if (!player->active)
6844 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6852 if (element == EL_ROBOT &&
6853 game.robot_wheel_x >= 0 &&
6854 game.robot_wheel_y >= 0 &&
6855 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
6856 game.engine_version < VERSION_IDENT(3,1,0,0)))
6858 attr_x = game.robot_wheel_x;
6859 attr_y = game.robot_wheel_y;
6862 if (element == EL_PENGUIN)
6865 static int xy[4][2] =
6873 for (i = 0; i < NUM_DIRECTIONS; i++)
6875 int ex = x + xy[i][0];
6876 int ey = y + xy[i][1];
6878 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
6879 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
6880 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
6881 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
6890 MovDir[x][y] = MV_NONE;
6892 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
6893 else if (attr_x > x)
6894 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
6896 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
6897 else if (attr_y > y)
6898 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
6900 if (element == EL_ROBOT)
6904 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6905 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
6906 Moving2Blocked(x, y, &newx, &newy);
6908 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
6909 MovDelay[x][y] = 8 + 8 * !RND(3);
6911 MovDelay[x][y] = 16;
6913 else if (element == EL_PENGUIN)
6919 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6921 boolean first_horiz = RND(2);
6922 int new_move_dir = MovDir[x][y];
6925 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6926 Moving2Blocked(x, y, &newx, &newy);
6928 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6932 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6933 Moving2Blocked(x, y, &newx, &newy);
6935 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6938 MovDir[x][y] = old_move_dir;
6942 else if (element == EL_SATELLITE)
6948 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6950 boolean first_horiz = RND(2);
6951 int new_move_dir = MovDir[x][y];
6954 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6955 Moving2Blocked(x, y, &newx, &newy);
6957 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6961 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6962 Moving2Blocked(x, y, &newx, &newy);
6964 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6967 MovDir[x][y] = old_move_dir;
6971 else if (element == EL_EMC_ANDROID)
6973 static int check_pos[16] =
6975 -1, // 0 => (invalid)
6978 -1, // 3 => (invalid)
6980 0, // 5 => MV_LEFT | MV_UP
6981 2, // 6 => MV_RIGHT | MV_UP
6982 -1, // 7 => (invalid)
6984 6, // 9 => MV_LEFT | MV_DOWN
6985 4, // 10 => MV_RIGHT | MV_DOWN
6986 -1, // 11 => (invalid)
6987 -1, // 12 => (invalid)
6988 -1, // 13 => (invalid)
6989 -1, // 14 => (invalid)
6990 -1, // 15 => (invalid)
6998 { -1, -1, MV_LEFT | MV_UP },
7000 { +1, -1, MV_RIGHT | MV_UP },
7001 { +1, 0, MV_RIGHT },
7002 { +1, +1, MV_RIGHT | MV_DOWN },
7004 { -1, +1, MV_LEFT | MV_DOWN },
7007 int start_pos, check_order;
7008 boolean can_clone = FALSE;
7011 // check if there is any free field around current position
7012 for (i = 0; i < 8; i++)
7014 int newx = x + check_xy[i].dx;
7015 int newy = y + check_xy[i].dy;
7017 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7025 if (can_clone) // randomly find an element to clone
7029 start_pos = check_pos[RND(8)];
7030 check_order = (RND(2) ? -1 : +1);
7032 for (i = 0; i < 8; i++)
7034 int pos_raw = start_pos + i * check_order;
7035 int pos = (pos_raw + 8) % 8;
7036 int newx = x + check_xy[pos].dx;
7037 int newy = y + check_xy[pos].dy;
7039 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7041 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7042 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7044 Store[x][y] = Feld[newx][newy];
7053 if (can_clone) // randomly find a direction to move
7057 start_pos = check_pos[RND(8)];
7058 check_order = (RND(2) ? -1 : +1);
7060 for (i = 0; i < 8; i++)
7062 int pos_raw = start_pos + i * check_order;
7063 int pos = (pos_raw + 8) % 8;
7064 int newx = x + check_xy[pos].dx;
7065 int newy = y + check_xy[pos].dy;
7066 int new_move_dir = check_xy[pos].dir;
7068 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7070 MovDir[x][y] = new_move_dir;
7071 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7080 if (can_clone) // cloning and moving successful
7083 // cannot clone -- try to move towards player
7085 start_pos = check_pos[MovDir[x][y] & 0x0f];
7086 check_order = (RND(2) ? -1 : +1);
7088 for (i = 0; i < 3; i++)
7090 // first check start_pos, then previous/next or (next/previous) pos
7091 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7092 int pos = (pos_raw + 8) % 8;
7093 int newx = x + check_xy[pos].dx;
7094 int newy = y + check_xy[pos].dy;
7095 int new_move_dir = check_xy[pos].dir;
7097 if (IS_PLAYER(newx, newy))
7100 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7102 MovDir[x][y] = new_move_dir;
7103 MovDelay[x][y] = level.android_move_time * 8 + 1;
7110 else if (move_pattern == MV_TURNING_LEFT ||
7111 move_pattern == MV_TURNING_RIGHT ||
7112 move_pattern == MV_TURNING_LEFT_RIGHT ||
7113 move_pattern == MV_TURNING_RIGHT_LEFT ||
7114 move_pattern == MV_TURNING_RANDOM ||
7115 move_pattern == MV_ALL_DIRECTIONS)
7117 boolean can_turn_left =
7118 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7119 boolean can_turn_right =
7120 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7122 if (element_info[element].move_stepsize == 0) // "not moving"
7125 if (move_pattern == MV_TURNING_LEFT)
7126 MovDir[x][y] = left_dir;
7127 else if (move_pattern == MV_TURNING_RIGHT)
7128 MovDir[x][y] = right_dir;
7129 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7130 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7131 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7132 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7133 else if (move_pattern == MV_TURNING_RANDOM)
7134 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7135 can_turn_right && !can_turn_left ? right_dir :
7136 RND(2) ? left_dir : right_dir);
7137 else if (can_turn_left && can_turn_right)
7138 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7139 else if (can_turn_left)
7140 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7141 else if (can_turn_right)
7142 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7144 MovDir[x][y] = back_dir;
7146 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7148 else if (move_pattern == MV_HORIZONTAL ||
7149 move_pattern == MV_VERTICAL)
7151 if (move_pattern & old_move_dir)
7152 MovDir[x][y] = back_dir;
7153 else if (move_pattern == MV_HORIZONTAL)
7154 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7155 else if (move_pattern == MV_VERTICAL)
7156 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7158 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7160 else if (move_pattern & MV_ANY_DIRECTION)
7162 MovDir[x][y] = move_pattern;
7163 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7165 else if (move_pattern & MV_WIND_DIRECTION)
7167 MovDir[x][y] = game.wind_direction;
7168 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7170 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7172 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7173 MovDir[x][y] = left_dir;
7174 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7175 MovDir[x][y] = right_dir;
7177 if (MovDir[x][y] != old_move_dir)
7178 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7180 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7182 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7183 MovDir[x][y] = right_dir;
7184 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7185 MovDir[x][y] = left_dir;
7187 if (MovDir[x][y] != old_move_dir)
7188 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7190 else if (move_pattern == MV_TOWARDS_PLAYER ||
7191 move_pattern == MV_AWAY_FROM_PLAYER)
7193 int attr_x = -1, attr_y = -1;
7195 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7197 if (game.all_players_gone)
7199 attr_x = game.exit_x;
7200 attr_y = game.exit_y;
7206 for (i = 0; i < MAX_PLAYERS; i++)
7208 struct PlayerInfo *player = &stored_player[i];
7209 int jx = player->jx, jy = player->jy;
7211 if (!player->active)
7215 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7223 MovDir[x][y] = MV_NONE;
7225 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7226 else if (attr_x > x)
7227 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7229 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7230 else if (attr_y > y)
7231 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7233 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7235 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7237 boolean first_horiz = RND(2);
7238 int new_move_dir = MovDir[x][y];
7240 if (element_info[element].move_stepsize == 0) // "not moving"
7242 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7243 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7249 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7250 Moving2Blocked(x, y, &newx, &newy);
7252 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7256 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7257 Moving2Blocked(x, y, &newx, &newy);
7259 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7262 MovDir[x][y] = old_move_dir;
7265 else if (move_pattern == MV_WHEN_PUSHED ||
7266 move_pattern == MV_WHEN_DROPPED)
7268 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7269 MovDir[x][y] = MV_NONE;
7273 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7275 static int test_xy[7][2] =
7285 static int test_dir[7] =
7295 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7296 int move_preference = -1000000; // start with very low preference
7297 int new_move_dir = MV_NONE;
7298 int start_test = RND(4);
7301 for (i = 0; i < NUM_DIRECTIONS; i++)
7303 int move_dir = test_dir[start_test + i];
7304 int move_dir_preference;
7306 xx = x + test_xy[start_test + i][0];
7307 yy = y + test_xy[start_test + i][1];
7309 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7310 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7312 new_move_dir = move_dir;
7317 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7320 move_dir_preference = -1 * RunnerVisit[xx][yy];
7321 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7322 move_dir_preference = PlayerVisit[xx][yy];
7324 if (move_dir_preference > move_preference)
7326 // prefer field that has not been visited for the longest time
7327 move_preference = move_dir_preference;
7328 new_move_dir = move_dir;
7330 else if (move_dir_preference == move_preference &&
7331 move_dir == old_move_dir)
7333 // prefer last direction when all directions are preferred equally
7334 move_preference = move_dir_preference;
7335 new_move_dir = move_dir;
7339 MovDir[x][y] = new_move_dir;
7340 if (old_move_dir != new_move_dir)
7341 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7345 static void TurnRound(int x, int y)
7347 int direction = MovDir[x][y];
7351 GfxDir[x][y] = MovDir[x][y];
7353 if (direction != MovDir[x][y])
7357 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7359 ResetGfxFrame(x, y);
7362 static boolean JustBeingPushed(int x, int y)
7366 for (i = 0; i < MAX_PLAYERS; i++)
7368 struct PlayerInfo *player = &stored_player[i];
7370 if (player->active && player->is_pushing && player->MovPos)
7372 int next_jx = player->jx + (player->jx - player->last_jx);
7373 int next_jy = player->jy + (player->jy - player->last_jy);
7375 if (x == next_jx && y == next_jy)
7383 static void StartMoving(int x, int y)
7385 boolean started_moving = FALSE; // some elements can fall _and_ move
7386 int element = Feld[x][y];
7391 if (MovDelay[x][y] == 0)
7392 GfxAction[x][y] = ACTION_DEFAULT;
7394 if (CAN_FALL(element) && y < lev_fieldy - 1)
7396 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7397 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7398 if (JustBeingPushed(x, y))
7401 if (element == EL_QUICKSAND_FULL)
7403 if (IS_FREE(x, y + 1))
7405 InitMovingField(x, y, MV_DOWN);
7406 started_moving = TRUE;
7408 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7409 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7410 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7411 Store[x][y] = EL_ROCK;
7413 Store[x][y] = EL_ROCK;
7416 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7418 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7420 if (!MovDelay[x][y])
7422 MovDelay[x][y] = TILEY + 1;
7424 ResetGfxAnimation(x, y);
7425 ResetGfxAnimation(x, y + 1);
7430 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7431 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7438 Feld[x][y] = EL_QUICKSAND_EMPTY;
7439 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7440 Store[x][y + 1] = Store[x][y];
7443 PlayLevelSoundAction(x, y, ACTION_FILLING);
7445 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7447 if (!MovDelay[x][y])
7449 MovDelay[x][y] = TILEY + 1;
7451 ResetGfxAnimation(x, y);
7452 ResetGfxAnimation(x, y + 1);
7457 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7458 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7465 Feld[x][y] = EL_QUICKSAND_EMPTY;
7466 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7467 Store[x][y + 1] = Store[x][y];
7470 PlayLevelSoundAction(x, y, ACTION_FILLING);
7473 else if (element == EL_QUICKSAND_FAST_FULL)
7475 if (IS_FREE(x, y + 1))
7477 InitMovingField(x, y, MV_DOWN);
7478 started_moving = TRUE;
7480 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7481 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7482 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7483 Store[x][y] = EL_ROCK;
7485 Store[x][y] = EL_ROCK;
7488 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7490 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7492 if (!MovDelay[x][y])
7494 MovDelay[x][y] = TILEY + 1;
7496 ResetGfxAnimation(x, y);
7497 ResetGfxAnimation(x, y + 1);
7502 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7503 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7510 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7511 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7512 Store[x][y + 1] = Store[x][y];
7515 PlayLevelSoundAction(x, y, ACTION_FILLING);
7517 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7519 if (!MovDelay[x][y])
7521 MovDelay[x][y] = TILEY + 1;
7523 ResetGfxAnimation(x, y);
7524 ResetGfxAnimation(x, y + 1);
7529 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7530 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7537 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7538 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7539 Store[x][y + 1] = Store[x][y];
7542 PlayLevelSoundAction(x, y, ACTION_FILLING);
7545 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7546 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7548 InitMovingField(x, y, MV_DOWN);
7549 started_moving = TRUE;
7551 Feld[x][y] = EL_QUICKSAND_FILLING;
7552 Store[x][y] = element;
7554 PlayLevelSoundAction(x, y, ACTION_FILLING);
7556 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7557 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7559 InitMovingField(x, y, MV_DOWN);
7560 started_moving = TRUE;
7562 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7563 Store[x][y] = element;
7565 PlayLevelSoundAction(x, y, ACTION_FILLING);
7567 else if (element == EL_MAGIC_WALL_FULL)
7569 if (IS_FREE(x, y + 1))
7571 InitMovingField(x, y, MV_DOWN);
7572 started_moving = TRUE;
7574 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7575 Store[x][y] = EL_CHANGED(Store[x][y]);
7577 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7579 if (!MovDelay[x][y])
7580 MovDelay[x][y] = TILEY / 4 + 1;
7589 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7590 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7591 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7595 else if (element == EL_BD_MAGIC_WALL_FULL)
7597 if (IS_FREE(x, y + 1))
7599 InitMovingField(x, y, MV_DOWN);
7600 started_moving = TRUE;
7602 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7603 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7605 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7607 if (!MovDelay[x][y])
7608 MovDelay[x][y] = TILEY / 4 + 1;
7617 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7618 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7619 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7623 else if (element == EL_DC_MAGIC_WALL_FULL)
7625 if (IS_FREE(x, y + 1))
7627 InitMovingField(x, y, MV_DOWN);
7628 started_moving = TRUE;
7630 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7631 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7633 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7635 if (!MovDelay[x][y])
7636 MovDelay[x][y] = TILEY / 4 + 1;
7645 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7646 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7647 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7651 else if ((CAN_PASS_MAGIC_WALL(element) &&
7652 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7653 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7654 (CAN_PASS_DC_MAGIC_WALL(element) &&
7655 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7658 InitMovingField(x, y, MV_DOWN);
7659 started_moving = TRUE;
7662 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7663 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7664 EL_DC_MAGIC_WALL_FILLING);
7665 Store[x][y] = element;
7667 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7669 SplashAcid(x, y + 1);
7671 InitMovingField(x, y, MV_DOWN);
7672 started_moving = TRUE;
7674 Store[x][y] = EL_ACID;
7677 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7678 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7679 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7680 CAN_FALL(element) && WasJustFalling[x][y] &&
7681 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7683 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7684 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7685 (Feld[x][y + 1] == EL_BLOCKED)))
7687 /* this is needed for a special case not covered by calling "Impact()"
7688 from "ContinueMoving()": if an element moves to a tile directly below
7689 another element which was just falling on that tile (which was empty
7690 in the previous frame), the falling element above would just stop
7691 instead of smashing the element below (in previous version, the above
7692 element was just checked for "moving" instead of "falling", resulting
7693 in incorrect smashes caused by horizontal movement of the above
7694 element; also, the case of the player being the element to smash was
7695 simply not covered here... :-/ ) */
7697 CheckCollision[x][y] = 0;
7698 CheckImpact[x][y] = 0;
7702 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7704 if (MovDir[x][y] == MV_NONE)
7706 InitMovingField(x, y, MV_DOWN);
7707 started_moving = TRUE;
7710 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7712 if (WasJustFalling[x][y]) // prevent animation from being restarted
7713 MovDir[x][y] = MV_DOWN;
7715 InitMovingField(x, y, MV_DOWN);
7716 started_moving = TRUE;
7718 else if (element == EL_AMOEBA_DROP)
7720 Feld[x][y] = EL_AMOEBA_GROWING;
7721 Store[x][y] = EL_AMOEBA_WET;
7723 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7724 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7725 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7726 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7728 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7729 (IS_FREE(x - 1, y + 1) ||
7730 Feld[x - 1][y + 1] == EL_ACID));
7731 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7732 (IS_FREE(x + 1, y + 1) ||
7733 Feld[x + 1][y + 1] == EL_ACID));
7734 boolean can_fall_any = (can_fall_left || can_fall_right);
7735 boolean can_fall_both = (can_fall_left && can_fall_right);
7736 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7738 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7740 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7741 can_fall_right = FALSE;
7742 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7743 can_fall_left = FALSE;
7744 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7745 can_fall_right = FALSE;
7746 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7747 can_fall_left = FALSE;
7749 can_fall_any = (can_fall_left || can_fall_right);
7750 can_fall_both = FALSE;
7755 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7756 can_fall_right = FALSE; // slip down on left side
7758 can_fall_left = !(can_fall_right = RND(2));
7760 can_fall_both = FALSE;
7765 // if not determined otherwise, prefer left side for slipping down
7766 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7767 started_moving = TRUE;
7770 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7772 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7773 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7774 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7775 int belt_dir = game.belt_dir[belt_nr];
7777 if ((belt_dir == MV_LEFT && left_is_free) ||
7778 (belt_dir == MV_RIGHT && right_is_free))
7780 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7782 InitMovingField(x, y, belt_dir);
7783 started_moving = TRUE;
7785 Pushed[x][y] = TRUE;
7786 Pushed[nextx][y] = TRUE;
7788 GfxAction[x][y] = ACTION_DEFAULT;
7792 MovDir[x][y] = 0; // if element was moving, stop it
7797 // not "else if" because of elements that can fall and move (EL_SPRING)
7798 if (CAN_MOVE(element) && !started_moving)
7800 int move_pattern = element_info[element].move_pattern;
7803 Moving2Blocked(x, y, &newx, &newy);
7805 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7808 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7809 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7811 WasJustMoving[x][y] = 0;
7812 CheckCollision[x][y] = 0;
7814 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7816 if (Feld[x][y] != element) // element has changed
7820 if (!MovDelay[x][y]) // start new movement phase
7822 // all objects that can change their move direction after each step
7823 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7825 if (element != EL_YAMYAM &&
7826 element != EL_DARK_YAMYAM &&
7827 element != EL_PACMAN &&
7828 !(move_pattern & MV_ANY_DIRECTION) &&
7829 move_pattern != MV_TURNING_LEFT &&
7830 move_pattern != MV_TURNING_RIGHT &&
7831 move_pattern != MV_TURNING_LEFT_RIGHT &&
7832 move_pattern != MV_TURNING_RIGHT_LEFT &&
7833 move_pattern != MV_TURNING_RANDOM)
7837 if (MovDelay[x][y] && (element == EL_BUG ||
7838 element == EL_SPACESHIP ||
7839 element == EL_SP_SNIKSNAK ||
7840 element == EL_SP_ELECTRON ||
7841 element == EL_MOLE))
7842 TEST_DrawLevelField(x, y);
7846 if (MovDelay[x][y]) // wait some time before next movement
7850 if (element == EL_ROBOT ||
7851 element == EL_YAMYAM ||
7852 element == EL_DARK_YAMYAM)
7854 DrawLevelElementAnimationIfNeeded(x, y, element);
7855 PlayLevelSoundAction(x, y, ACTION_WAITING);
7857 else if (element == EL_SP_ELECTRON)
7858 DrawLevelElementAnimationIfNeeded(x, y, element);
7859 else if (element == EL_DRAGON)
7862 int dir = MovDir[x][y];
7863 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
7864 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
7865 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
7866 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
7867 dir == MV_UP ? IMG_FLAMES_1_UP :
7868 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
7869 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
7871 GfxAction[x][y] = ACTION_ATTACKING;
7873 if (IS_PLAYER(x, y))
7874 DrawPlayerField(x, y);
7876 TEST_DrawLevelField(x, y);
7878 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
7880 for (i = 1; i <= 3; i++)
7882 int xx = x + i * dx;
7883 int yy = y + i * dy;
7884 int sx = SCREENX(xx);
7885 int sy = SCREENY(yy);
7886 int flame_graphic = graphic + (i - 1);
7888 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
7893 int flamed = MovingOrBlocked2Element(xx, yy);
7895 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
7898 RemoveMovingField(xx, yy);
7900 ChangeDelay[xx][yy] = 0;
7902 Feld[xx][yy] = EL_FLAMES;
7904 if (IN_SCR_FIELD(sx, sy))
7906 TEST_DrawLevelFieldCrumbled(xx, yy);
7907 DrawGraphic(sx, sy, flame_graphic, frame);
7912 if (Feld[xx][yy] == EL_FLAMES)
7913 Feld[xx][yy] = EL_EMPTY;
7914 TEST_DrawLevelField(xx, yy);
7919 if (MovDelay[x][y]) // element still has to wait some time
7921 PlayLevelSoundAction(x, y, ACTION_WAITING);
7927 // now make next step
7929 Moving2Blocked(x, y, &newx, &newy); // get next screen position
7931 if (DONT_COLLIDE_WITH(element) &&
7932 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
7933 !PLAYER_ENEMY_PROTECTED(newx, newy))
7935 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
7940 else if (CAN_MOVE_INTO_ACID(element) &&
7941 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
7942 !IS_MV_DIAGONAL(MovDir[x][y]) &&
7943 (MovDir[x][y] == MV_DOWN ||
7944 game.engine_version >= VERSION_IDENT(3,1,0,0)))
7946 SplashAcid(newx, newy);
7947 Store[x][y] = EL_ACID;
7949 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
7951 if (Feld[newx][newy] == EL_EXIT_OPEN ||
7952 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
7953 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
7954 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
7957 TEST_DrawLevelField(x, y);
7959 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
7960 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
7961 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
7963 game.friends_still_needed--;
7964 if (!game.friends_still_needed &&
7966 game.all_players_gone)
7971 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
7973 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
7974 TEST_DrawLevelField(newx, newy);
7976 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
7978 else if (!IS_FREE(newx, newy))
7980 GfxAction[x][y] = ACTION_WAITING;
7982 if (IS_PLAYER(x, y))
7983 DrawPlayerField(x, y);
7985 TEST_DrawLevelField(x, y);
7990 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
7992 if (IS_FOOD_PIG(Feld[newx][newy]))
7994 if (IS_MOVING(newx, newy))
7995 RemoveMovingField(newx, newy);
7998 Feld[newx][newy] = EL_EMPTY;
7999 TEST_DrawLevelField(newx, newy);
8002 PlayLevelSound(x, y, SND_PIG_DIGGING);
8004 else if (!IS_FREE(newx, newy))
8006 if (IS_PLAYER(x, y))
8007 DrawPlayerField(x, y);
8009 TEST_DrawLevelField(x, y);
8014 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8016 if (Store[x][y] != EL_EMPTY)
8018 boolean can_clone = FALSE;
8021 // check if element to clone is still there
8022 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8024 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8032 // cannot clone or target field not free anymore -- do not clone
8033 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8034 Store[x][y] = EL_EMPTY;
8037 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8039 if (IS_MV_DIAGONAL(MovDir[x][y]))
8041 int diagonal_move_dir = MovDir[x][y];
8042 int stored = Store[x][y];
8043 int change_delay = 8;
8046 // android is moving diagonally
8048 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8050 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8051 GfxElement[x][y] = EL_EMC_ANDROID;
8052 GfxAction[x][y] = ACTION_SHRINKING;
8053 GfxDir[x][y] = diagonal_move_dir;
8054 ChangeDelay[x][y] = change_delay;
8056 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8059 DrawLevelGraphicAnimation(x, y, graphic);
8060 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8062 if (Feld[newx][newy] == EL_ACID)
8064 SplashAcid(newx, newy);
8069 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8071 Store[newx][newy] = EL_EMC_ANDROID;
8072 GfxElement[newx][newy] = EL_EMC_ANDROID;
8073 GfxAction[newx][newy] = ACTION_GROWING;
8074 GfxDir[newx][newy] = diagonal_move_dir;
8075 ChangeDelay[newx][newy] = change_delay;
8077 graphic = el_act_dir2img(GfxElement[newx][newy],
8078 GfxAction[newx][newy], GfxDir[newx][newy]);
8080 DrawLevelGraphicAnimation(newx, newy, graphic);
8081 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8087 Feld[newx][newy] = EL_EMPTY;
8088 TEST_DrawLevelField(newx, newy);
8090 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8093 else if (!IS_FREE(newx, newy))
8098 else if (IS_CUSTOM_ELEMENT(element) &&
8099 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8101 if (!DigFieldByCE(newx, newy, element))
8104 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8106 RunnerVisit[x][y] = FrameCounter;
8107 PlayerVisit[x][y] /= 8; // expire player visit path
8110 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8112 if (!IS_FREE(newx, newy))
8114 if (IS_PLAYER(x, y))
8115 DrawPlayerField(x, y);
8117 TEST_DrawLevelField(x, y);
8123 boolean wanna_flame = !RND(10);
8124 int dx = newx - x, dy = newy - y;
8125 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8126 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8127 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8128 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8129 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8130 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8133 IS_CLASSIC_ENEMY(element1) ||
8134 IS_CLASSIC_ENEMY(element2)) &&
8135 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8136 element1 != EL_FLAMES && element2 != EL_FLAMES)
8138 ResetGfxAnimation(x, y);
8139 GfxAction[x][y] = ACTION_ATTACKING;
8141 if (IS_PLAYER(x, y))
8142 DrawPlayerField(x, y);
8144 TEST_DrawLevelField(x, y);
8146 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8148 MovDelay[x][y] = 50;
8150 Feld[newx][newy] = EL_FLAMES;
8151 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8152 Feld[newx1][newy1] = EL_FLAMES;
8153 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8154 Feld[newx2][newy2] = EL_FLAMES;
8160 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8161 Feld[newx][newy] == EL_DIAMOND)
8163 if (IS_MOVING(newx, newy))
8164 RemoveMovingField(newx, newy);
8167 Feld[newx][newy] = EL_EMPTY;
8168 TEST_DrawLevelField(newx, newy);
8171 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8173 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8174 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8176 if (AmoebaNr[newx][newy])
8178 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8179 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8180 Feld[newx][newy] == EL_BD_AMOEBA)
8181 AmoebaCnt[AmoebaNr[newx][newy]]--;
8184 if (IS_MOVING(newx, newy))
8186 RemoveMovingField(newx, newy);
8190 Feld[newx][newy] = EL_EMPTY;
8191 TEST_DrawLevelField(newx, newy);
8194 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8196 else if ((element == EL_PACMAN || element == EL_MOLE)
8197 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8199 if (AmoebaNr[newx][newy])
8201 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8202 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8203 Feld[newx][newy] == EL_BD_AMOEBA)
8204 AmoebaCnt[AmoebaNr[newx][newy]]--;
8207 if (element == EL_MOLE)
8209 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8210 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8212 ResetGfxAnimation(x, y);
8213 GfxAction[x][y] = ACTION_DIGGING;
8214 TEST_DrawLevelField(x, y);
8216 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8218 return; // wait for shrinking amoeba
8220 else // element == EL_PACMAN
8222 Feld[newx][newy] = EL_EMPTY;
8223 TEST_DrawLevelField(newx, newy);
8224 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8227 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8228 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8229 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8231 // wait for shrinking amoeba to completely disappear
8234 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8236 // object was running against a wall
8240 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8241 DrawLevelElementAnimation(x, y, element);
8243 if (DONT_TOUCH(element))
8244 TestIfBadThingTouchesPlayer(x, y);
8249 InitMovingField(x, y, MovDir[x][y]);
8251 PlayLevelSoundAction(x, y, ACTION_MOVING);
8255 ContinueMoving(x, y);
8258 void ContinueMoving(int x, int y)
8260 int element = Feld[x][y];
8261 struct ElementInfo *ei = &element_info[element];
8262 int direction = MovDir[x][y];
8263 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8264 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8265 int newx = x + dx, newy = y + dy;
8266 int stored = Store[x][y];
8267 int stored_new = Store[newx][newy];
8268 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8269 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8270 boolean last_line = (newy == lev_fieldy - 1);
8272 MovPos[x][y] += getElementMoveStepsize(x, y);
8274 if (pushed_by_player) // special case: moving object pushed by player
8275 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8277 if (ABS(MovPos[x][y]) < TILEX)
8279 TEST_DrawLevelField(x, y);
8281 return; // element is still moving
8284 // element reached destination field
8286 Feld[x][y] = EL_EMPTY;
8287 Feld[newx][newy] = element;
8288 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8290 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8292 element = Feld[newx][newy] = EL_ACID;
8294 else if (element == EL_MOLE)
8296 Feld[x][y] = EL_SAND;
8298 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8300 else if (element == EL_QUICKSAND_FILLING)
8302 element = Feld[newx][newy] = get_next_element(element);
8303 Store[newx][newy] = Store[x][y];
8305 else if (element == EL_QUICKSAND_EMPTYING)
8307 Feld[x][y] = get_next_element(element);
8308 element = Feld[newx][newy] = Store[x][y];
8310 else if (element == EL_QUICKSAND_FAST_FILLING)
8312 element = Feld[newx][newy] = get_next_element(element);
8313 Store[newx][newy] = Store[x][y];
8315 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8317 Feld[x][y] = get_next_element(element);
8318 element = Feld[newx][newy] = Store[x][y];
8320 else if (element == EL_MAGIC_WALL_FILLING)
8322 element = Feld[newx][newy] = get_next_element(element);
8323 if (!game.magic_wall_active)
8324 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8325 Store[newx][newy] = Store[x][y];
8327 else if (element == EL_MAGIC_WALL_EMPTYING)
8329 Feld[x][y] = get_next_element(element);
8330 if (!game.magic_wall_active)
8331 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8332 element = Feld[newx][newy] = Store[x][y];
8334 InitField(newx, newy, FALSE);
8336 else if (element == EL_BD_MAGIC_WALL_FILLING)
8338 element = Feld[newx][newy] = get_next_element(element);
8339 if (!game.magic_wall_active)
8340 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8341 Store[newx][newy] = Store[x][y];
8343 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8345 Feld[x][y] = get_next_element(element);
8346 if (!game.magic_wall_active)
8347 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8348 element = Feld[newx][newy] = Store[x][y];
8350 InitField(newx, newy, FALSE);
8352 else if (element == EL_DC_MAGIC_WALL_FILLING)
8354 element = Feld[newx][newy] = get_next_element(element);
8355 if (!game.magic_wall_active)
8356 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8357 Store[newx][newy] = Store[x][y];
8359 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8361 Feld[x][y] = get_next_element(element);
8362 if (!game.magic_wall_active)
8363 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8364 element = Feld[newx][newy] = Store[x][y];
8366 InitField(newx, newy, FALSE);
8368 else if (element == EL_AMOEBA_DROPPING)
8370 Feld[x][y] = get_next_element(element);
8371 element = Feld[newx][newy] = Store[x][y];
8373 else if (element == EL_SOKOBAN_OBJECT)
8376 Feld[x][y] = Back[x][y];
8378 if (Back[newx][newy])
8379 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8381 Back[x][y] = Back[newx][newy] = 0;
8384 Store[x][y] = EL_EMPTY;
8389 MovDelay[newx][newy] = 0;
8391 if (CAN_CHANGE_OR_HAS_ACTION(element))
8393 // copy element change control values to new field
8394 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8395 ChangePage[newx][newy] = ChangePage[x][y];
8396 ChangeCount[newx][newy] = ChangeCount[x][y];
8397 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8400 CustomValue[newx][newy] = CustomValue[x][y];
8402 ChangeDelay[x][y] = 0;
8403 ChangePage[x][y] = -1;
8404 ChangeCount[x][y] = 0;
8405 ChangeEvent[x][y] = -1;
8407 CustomValue[x][y] = 0;
8409 // copy animation control values to new field
8410 GfxFrame[newx][newy] = GfxFrame[x][y];
8411 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8412 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8413 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8415 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8417 // some elements can leave other elements behind after moving
8418 if (ei->move_leave_element != EL_EMPTY &&
8419 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8420 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8422 int move_leave_element = ei->move_leave_element;
8424 // this makes it possible to leave the removed element again
8425 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8426 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8428 Feld[x][y] = move_leave_element;
8430 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8431 MovDir[x][y] = direction;
8433 InitField(x, y, FALSE);
8435 if (GFX_CRUMBLED(Feld[x][y]))
8436 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8438 if (ELEM_IS_PLAYER(move_leave_element))
8439 RelocatePlayer(x, y, move_leave_element);
8442 // do this after checking for left-behind element
8443 ResetGfxAnimation(x, y); // reset animation values for old field
8445 if (!CAN_MOVE(element) ||
8446 (CAN_FALL(element) && direction == MV_DOWN &&
8447 (element == EL_SPRING ||
8448 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8449 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8450 GfxDir[x][y] = MovDir[newx][newy] = 0;
8452 TEST_DrawLevelField(x, y);
8453 TEST_DrawLevelField(newx, newy);
8455 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8457 // prevent pushed element from moving on in pushed direction
8458 if (pushed_by_player && CAN_MOVE(element) &&
8459 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8460 !(element_info[element].move_pattern & direction))
8461 TurnRound(newx, newy);
8463 // prevent elements on conveyor belt from moving on in last direction
8464 if (pushed_by_conveyor && CAN_FALL(element) &&
8465 direction & MV_HORIZONTAL)
8466 MovDir[newx][newy] = 0;
8468 if (!pushed_by_player)
8470 int nextx = newx + dx, nexty = newy + dy;
8471 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8473 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8475 if (CAN_FALL(element) && direction == MV_DOWN)
8476 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8478 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8479 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8481 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8482 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8485 if (DONT_TOUCH(element)) // object may be nasty to player or others
8487 TestIfBadThingTouchesPlayer(newx, newy);
8488 TestIfBadThingTouchesFriend(newx, newy);
8490 if (!IS_CUSTOM_ELEMENT(element))
8491 TestIfBadThingTouchesOtherBadThing(newx, newy);
8493 else if (element == EL_PENGUIN)
8494 TestIfFriendTouchesBadThing(newx, newy);
8496 if (DONT_GET_HIT_BY(element))
8498 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8501 // give the player one last chance (one more frame) to move away
8502 if (CAN_FALL(element) && direction == MV_DOWN &&
8503 (last_line || (!IS_FREE(x, newy + 1) &&
8504 (!IS_PLAYER(x, newy + 1) ||
8505 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8508 if (pushed_by_player && !game.use_change_when_pushing_bug)
8510 int push_side = MV_DIR_OPPOSITE(direction);
8511 struct PlayerInfo *player = PLAYERINFO(x, y);
8513 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8514 player->index_bit, push_side);
8515 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8516 player->index_bit, push_side);
8519 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8520 MovDelay[newx][newy] = 1;
8522 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8524 TestIfElementTouchesCustomElement(x, y); // empty or new element
8525 TestIfElementHitsCustomElement(newx, newy, direction);
8526 TestIfPlayerTouchesCustomElement(newx, newy);
8527 TestIfElementTouchesCustomElement(newx, newy);
8529 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8530 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8531 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8532 MV_DIR_OPPOSITE(direction));
8535 int AmoebeNachbarNr(int ax, int ay)
8538 int element = Feld[ax][ay];
8540 static int xy[4][2] =
8548 for (i = 0; i < NUM_DIRECTIONS; i++)
8550 int x = ax + xy[i][0];
8551 int y = ay + xy[i][1];
8553 if (!IN_LEV_FIELD(x, y))
8556 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8557 group_nr = AmoebaNr[x][y];
8563 static void AmoebenVereinigen(int ax, int ay)
8565 int i, x, y, xx, yy;
8566 int new_group_nr = AmoebaNr[ax][ay];
8567 static int xy[4][2] =
8575 if (new_group_nr == 0)
8578 for (i = 0; i < NUM_DIRECTIONS; i++)
8583 if (!IN_LEV_FIELD(x, y))
8586 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8587 Feld[x][y] == EL_BD_AMOEBA ||
8588 Feld[x][y] == EL_AMOEBA_DEAD) &&
8589 AmoebaNr[x][y] != new_group_nr)
8591 int old_group_nr = AmoebaNr[x][y];
8593 if (old_group_nr == 0)
8596 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8597 AmoebaCnt[old_group_nr] = 0;
8598 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8599 AmoebaCnt2[old_group_nr] = 0;
8601 SCAN_PLAYFIELD(xx, yy)
8603 if (AmoebaNr[xx][yy] == old_group_nr)
8604 AmoebaNr[xx][yy] = new_group_nr;
8610 void AmoebeUmwandeln(int ax, int ay)
8614 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8616 int group_nr = AmoebaNr[ax][ay];
8621 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8622 printf("AmoebeUmwandeln(): This should never happen!\n");
8627 SCAN_PLAYFIELD(x, y)
8629 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8632 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8636 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8637 SND_AMOEBA_TURNING_TO_GEM :
8638 SND_AMOEBA_TURNING_TO_ROCK));
8643 static int xy[4][2] =
8651 for (i = 0; i < NUM_DIRECTIONS; i++)
8656 if (!IN_LEV_FIELD(x, y))
8659 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8661 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8662 SND_AMOEBA_TURNING_TO_GEM :
8663 SND_AMOEBA_TURNING_TO_ROCK));
8670 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8673 int group_nr = AmoebaNr[ax][ay];
8674 boolean done = FALSE;
8679 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8680 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8685 SCAN_PLAYFIELD(x, y)
8687 if (AmoebaNr[x][y] == group_nr &&
8688 (Feld[x][y] == EL_AMOEBA_DEAD ||
8689 Feld[x][y] == EL_BD_AMOEBA ||
8690 Feld[x][y] == EL_AMOEBA_GROWING))
8693 Feld[x][y] = new_element;
8694 InitField(x, y, FALSE);
8695 TEST_DrawLevelField(x, y);
8701 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8702 SND_BD_AMOEBA_TURNING_TO_ROCK :
8703 SND_BD_AMOEBA_TURNING_TO_GEM));
8706 static void AmoebeWaechst(int x, int y)
8708 static unsigned int sound_delay = 0;
8709 static unsigned int sound_delay_value = 0;
8711 if (!MovDelay[x][y]) // start new growing cycle
8715 if (DelayReached(&sound_delay, sound_delay_value))
8717 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8718 sound_delay_value = 30;
8722 if (MovDelay[x][y]) // wait some time before growing bigger
8725 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8727 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8728 6 - MovDelay[x][y]);
8730 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8733 if (!MovDelay[x][y])
8735 Feld[x][y] = Store[x][y];
8737 TEST_DrawLevelField(x, y);
8742 static void AmoebaDisappearing(int x, int y)
8744 static unsigned int sound_delay = 0;
8745 static unsigned int sound_delay_value = 0;
8747 if (!MovDelay[x][y]) // start new shrinking cycle
8751 if (DelayReached(&sound_delay, sound_delay_value))
8752 sound_delay_value = 30;
8755 if (MovDelay[x][y]) // wait some time before shrinking
8758 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8760 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8761 6 - MovDelay[x][y]);
8763 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8766 if (!MovDelay[x][y])
8768 Feld[x][y] = EL_EMPTY;
8769 TEST_DrawLevelField(x, y);
8771 // don't let mole enter this field in this cycle;
8772 // (give priority to objects falling to this field from above)
8778 static void AmoebeAbleger(int ax, int ay)
8781 int element = Feld[ax][ay];
8782 int graphic = el2img(element);
8783 int newax = ax, neway = ay;
8784 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8785 static int xy[4][2] =
8793 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8795 Feld[ax][ay] = EL_AMOEBA_DEAD;
8796 TEST_DrawLevelField(ax, ay);
8800 if (IS_ANIMATED(graphic))
8801 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8803 if (!MovDelay[ax][ay]) // start making new amoeba field
8804 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8806 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8809 if (MovDelay[ax][ay])
8813 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8816 int x = ax + xy[start][0];
8817 int y = ay + xy[start][1];
8819 if (!IN_LEV_FIELD(x, y))
8822 if (IS_FREE(x, y) ||
8823 CAN_GROW_INTO(Feld[x][y]) ||
8824 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8825 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8831 if (newax == ax && neway == ay)
8834 else // normal or "filled" (BD style) amoeba
8837 boolean waiting_for_player = FALSE;
8839 for (i = 0; i < NUM_DIRECTIONS; i++)
8841 int j = (start + i) % 4;
8842 int x = ax + xy[j][0];
8843 int y = ay + xy[j][1];
8845 if (!IN_LEV_FIELD(x, y))
8848 if (IS_FREE(x, y) ||
8849 CAN_GROW_INTO(Feld[x][y]) ||
8850 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8851 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8857 else if (IS_PLAYER(x, y))
8858 waiting_for_player = TRUE;
8861 if (newax == ax && neway == ay) // amoeba cannot grow
8863 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
8865 Feld[ax][ay] = EL_AMOEBA_DEAD;
8866 TEST_DrawLevelField(ax, ay);
8867 AmoebaCnt[AmoebaNr[ax][ay]]--;
8869 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
8871 if (element == EL_AMOEBA_FULL)
8872 AmoebeUmwandeln(ax, ay);
8873 else if (element == EL_BD_AMOEBA)
8874 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
8879 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
8881 // amoeba gets larger by growing in some direction
8883 int new_group_nr = AmoebaNr[ax][ay];
8886 if (new_group_nr == 0)
8888 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
8889 printf("AmoebeAbleger(): This should never happen!\n");
8894 AmoebaNr[newax][neway] = new_group_nr;
8895 AmoebaCnt[new_group_nr]++;
8896 AmoebaCnt2[new_group_nr]++;
8898 // if amoeba touches other amoeba(s) after growing, unify them
8899 AmoebenVereinigen(newax, neway);
8901 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
8903 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
8909 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
8910 (neway == lev_fieldy - 1 && newax != ax))
8912 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
8913 Store[newax][neway] = element;
8915 else if (neway == ay || element == EL_EMC_DRIPPER)
8917 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
8919 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
8923 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
8924 Feld[ax][ay] = EL_AMOEBA_DROPPING;
8925 Store[ax][ay] = EL_AMOEBA_DROP;
8926 ContinueMoving(ax, ay);
8930 TEST_DrawLevelField(newax, neway);
8933 static void Life(int ax, int ay)
8937 int element = Feld[ax][ay];
8938 int graphic = el2img(element);
8939 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
8941 boolean changed = FALSE;
8943 if (IS_ANIMATED(graphic))
8944 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8949 if (!MovDelay[ax][ay]) // start new "game of life" cycle
8950 MovDelay[ax][ay] = life_time;
8952 if (MovDelay[ax][ay]) // wait some time before next cycle
8955 if (MovDelay[ax][ay])
8959 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
8961 int xx = ax+x1, yy = ay+y1;
8962 int old_element = Feld[xx][yy];
8963 int num_neighbours = 0;
8965 if (!IN_LEV_FIELD(xx, yy))
8968 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
8970 int x = xx+x2, y = yy+y2;
8972 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
8975 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
8976 boolean is_neighbour = FALSE;
8978 if (level.use_life_bugs)
8980 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
8981 (IS_FREE(x, y) && Stop[x][y]));
8984 (Last[x][y] == element || is_player_cell);
8990 boolean is_free = FALSE;
8992 if (level.use_life_bugs)
8993 is_free = (IS_FREE(xx, yy));
8995 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
8997 if (xx == ax && yy == ay) // field in the middle
8999 if (num_neighbours < life_parameter[0] ||
9000 num_neighbours > life_parameter[1])
9002 Feld[xx][yy] = EL_EMPTY;
9003 if (Feld[xx][yy] != old_element)
9004 TEST_DrawLevelField(xx, yy);
9005 Stop[xx][yy] = TRUE;
9009 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9010 { // free border field
9011 if (num_neighbours >= life_parameter[2] &&
9012 num_neighbours <= life_parameter[3])
9014 Feld[xx][yy] = element;
9015 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9016 if (Feld[xx][yy] != old_element)
9017 TEST_DrawLevelField(xx, yy);
9018 Stop[xx][yy] = TRUE;
9025 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9026 SND_GAME_OF_LIFE_GROWING);
9029 static void InitRobotWheel(int x, int y)
9031 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9034 static void RunRobotWheel(int x, int y)
9036 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9039 static void StopRobotWheel(int x, int y)
9041 if (game.robot_wheel_x == x &&
9042 game.robot_wheel_y == y)
9044 game.robot_wheel_x = -1;
9045 game.robot_wheel_y = -1;
9046 game.robot_wheel_active = FALSE;
9050 static void InitTimegateWheel(int x, int y)
9052 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9055 static void RunTimegateWheel(int x, int y)
9057 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9060 static void InitMagicBallDelay(int x, int y)
9062 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9065 static void ActivateMagicBall(int bx, int by)
9069 if (level.ball_random)
9071 int pos_border = RND(8); // select one of the eight border elements
9072 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9073 int xx = pos_content % 3;
9074 int yy = pos_content / 3;
9079 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9080 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9084 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9086 int xx = x - bx + 1;
9087 int yy = y - by + 1;
9089 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9090 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9094 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9097 static void CheckExit(int x, int y)
9099 if (game.gems_still_needed > 0 ||
9100 game.sokoban_fields_still_needed > 0 ||
9101 game.sokoban_objects_still_needed > 0 ||
9102 game.lights_still_needed > 0)
9104 int element = Feld[x][y];
9105 int graphic = el2img(element);
9107 if (IS_ANIMATED(graphic))
9108 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9113 // do not re-open exit door closed after last player
9114 if (game.all_players_gone)
9117 Feld[x][y] = EL_EXIT_OPENING;
9119 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9122 static void CheckExitEM(int x, int y)
9124 if (game.gems_still_needed > 0 ||
9125 game.sokoban_fields_still_needed > 0 ||
9126 game.sokoban_objects_still_needed > 0 ||
9127 game.lights_still_needed > 0)
9129 int element = Feld[x][y];
9130 int graphic = el2img(element);
9132 if (IS_ANIMATED(graphic))
9133 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9138 // do not re-open exit door closed after last player
9139 if (game.all_players_gone)
9142 Feld[x][y] = EL_EM_EXIT_OPENING;
9144 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9147 static void CheckExitSteel(int x, int y)
9149 if (game.gems_still_needed > 0 ||
9150 game.sokoban_fields_still_needed > 0 ||
9151 game.sokoban_objects_still_needed > 0 ||
9152 game.lights_still_needed > 0)
9154 int element = Feld[x][y];
9155 int graphic = el2img(element);
9157 if (IS_ANIMATED(graphic))
9158 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9163 // do not re-open exit door closed after last player
9164 if (game.all_players_gone)
9167 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9169 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9172 static void CheckExitSteelEM(int x, int y)
9174 if (game.gems_still_needed > 0 ||
9175 game.sokoban_fields_still_needed > 0 ||
9176 game.sokoban_objects_still_needed > 0 ||
9177 game.lights_still_needed > 0)
9179 int element = Feld[x][y];
9180 int graphic = el2img(element);
9182 if (IS_ANIMATED(graphic))
9183 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9188 // do not re-open exit door closed after last player
9189 if (game.all_players_gone)
9192 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9194 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9197 static void CheckExitSP(int x, int y)
9199 if (game.gems_still_needed > 0)
9201 int element = Feld[x][y];
9202 int graphic = el2img(element);
9204 if (IS_ANIMATED(graphic))
9205 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9210 // do not re-open exit door closed after last player
9211 if (game.all_players_gone)
9214 Feld[x][y] = EL_SP_EXIT_OPENING;
9216 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9219 static void CloseAllOpenTimegates(void)
9223 SCAN_PLAYFIELD(x, y)
9225 int element = Feld[x][y];
9227 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9229 Feld[x][y] = EL_TIMEGATE_CLOSING;
9231 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9236 static void DrawTwinkleOnField(int x, int y)
9238 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9241 if (Feld[x][y] == EL_BD_DIAMOND)
9244 if (MovDelay[x][y] == 0) // next animation frame
9245 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9247 if (MovDelay[x][y] != 0) // wait some time before next frame
9251 DrawLevelElementAnimation(x, y, Feld[x][y]);
9253 if (MovDelay[x][y] != 0)
9255 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9256 10 - MovDelay[x][y]);
9258 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9263 static void MauerWaechst(int x, int y)
9267 if (!MovDelay[x][y]) // next animation frame
9268 MovDelay[x][y] = 3 * delay;
9270 if (MovDelay[x][y]) // wait some time before next frame
9274 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9276 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9277 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9279 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9282 if (!MovDelay[x][y])
9284 if (MovDir[x][y] == MV_LEFT)
9286 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9287 TEST_DrawLevelField(x - 1, y);
9289 else if (MovDir[x][y] == MV_RIGHT)
9291 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9292 TEST_DrawLevelField(x + 1, y);
9294 else if (MovDir[x][y] == MV_UP)
9296 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9297 TEST_DrawLevelField(x, y - 1);
9301 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9302 TEST_DrawLevelField(x, y + 1);
9305 Feld[x][y] = Store[x][y];
9307 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9308 TEST_DrawLevelField(x, y);
9313 static void MauerAbleger(int ax, int ay)
9315 int element = Feld[ax][ay];
9316 int graphic = el2img(element);
9317 boolean oben_frei = FALSE, unten_frei = FALSE;
9318 boolean links_frei = FALSE, rechts_frei = FALSE;
9319 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9320 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9321 boolean new_wall = FALSE;
9323 if (IS_ANIMATED(graphic))
9324 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9326 if (!MovDelay[ax][ay]) // start building new wall
9327 MovDelay[ax][ay] = 6;
9329 if (MovDelay[ax][ay]) // wait some time before building new wall
9332 if (MovDelay[ax][ay])
9336 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9338 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9340 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9342 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9345 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9346 element == EL_EXPANDABLE_WALL_ANY)
9350 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9351 Store[ax][ay-1] = element;
9352 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9353 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9354 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9355 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9360 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9361 Store[ax][ay+1] = element;
9362 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9363 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9364 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9365 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9370 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9371 element == EL_EXPANDABLE_WALL_ANY ||
9372 element == EL_EXPANDABLE_WALL ||
9373 element == EL_BD_EXPANDABLE_WALL)
9377 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9378 Store[ax-1][ay] = element;
9379 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9380 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9381 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9382 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9388 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9389 Store[ax+1][ay] = element;
9390 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9391 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9392 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9393 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9398 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9399 TEST_DrawLevelField(ax, ay);
9401 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9403 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9404 unten_massiv = TRUE;
9405 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9406 links_massiv = TRUE;
9407 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9408 rechts_massiv = TRUE;
9410 if (((oben_massiv && unten_massiv) ||
9411 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9412 element == EL_EXPANDABLE_WALL) &&
9413 ((links_massiv && rechts_massiv) ||
9414 element == EL_EXPANDABLE_WALL_VERTICAL))
9415 Feld[ax][ay] = EL_WALL;
9418 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9421 static void MauerAblegerStahl(int ax, int ay)
9423 int element = Feld[ax][ay];
9424 int graphic = el2img(element);
9425 boolean oben_frei = FALSE, unten_frei = FALSE;
9426 boolean links_frei = FALSE, rechts_frei = FALSE;
9427 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9428 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9429 boolean new_wall = FALSE;
9431 if (IS_ANIMATED(graphic))
9432 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9434 if (!MovDelay[ax][ay]) // start building new wall
9435 MovDelay[ax][ay] = 6;
9437 if (MovDelay[ax][ay]) // wait some time before building new wall
9440 if (MovDelay[ax][ay])
9444 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9446 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9448 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9450 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9453 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9454 element == EL_EXPANDABLE_STEELWALL_ANY)
9458 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9459 Store[ax][ay-1] = element;
9460 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9461 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9462 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9463 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9468 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9469 Store[ax][ay+1] = element;
9470 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9471 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9472 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9473 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9478 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9479 element == EL_EXPANDABLE_STEELWALL_ANY)
9483 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9484 Store[ax-1][ay] = element;
9485 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9486 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9487 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9488 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9494 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9495 Store[ax+1][ay] = element;
9496 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9497 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9498 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9499 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9504 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9506 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9507 unten_massiv = TRUE;
9508 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9509 links_massiv = TRUE;
9510 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9511 rechts_massiv = TRUE;
9513 if (((oben_massiv && unten_massiv) ||
9514 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9515 ((links_massiv && rechts_massiv) ||
9516 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9517 Feld[ax][ay] = EL_STEELWALL;
9520 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9523 static void CheckForDragon(int x, int y)
9526 boolean dragon_found = FALSE;
9527 static int xy[4][2] =
9535 for (i = 0; i < NUM_DIRECTIONS; i++)
9537 for (j = 0; j < 4; j++)
9539 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9541 if (IN_LEV_FIELD(xx, yy) &&
9542 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9544 if (Feld[xx][yy] == EL_DRAGON)
9545 dragon_found = TRUE;
9554 for (i = 0; i < NUM_DIRECTIONS; i++)
9556 for (j = 0; j < 3; j++)
9558 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9560 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9562 Feld[xx][yy] = EL_EMPTY;
9563 TEST_DrawLevelField(xx, yy);
9572 static void InitBuggyBase(int x, int y)
9574 int element = Feld[x][y];
9575 int activating_delay = FRAMES_PER_SECOND / 4;
9578 (element == EL_SP_BUGGY_BASE ?
9579 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9580 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9582 element == EL_SP_BUGGY_BASE_ACTIVE ?
9583 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9586 static void WarnBuggyBase(int x, int y)
9589 static int xy[4][2] =
9597 for (i = 0; i < NUM_DIRECTIONS; i++)
9599 int xx = x + xy[i][0];
9600 int yy = y + xy[i][1];
9602 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9604 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9611 static void InitTrap(int x, int y)
9613 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9616 static void ActivateTrap(int x, int y)
9618 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9621 static void ChangeActiveTrap(int x, int y)
9623 int graphic = IMG_TRAP_ACTIVE;
9625 // if new animation frame was drawn, correct crumbled sand border
9626 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9627 TEST_DrawLevelFieldCrumbled(x, y);
9630 static int getSpecialActionElement(int element, int number, int base_element)
9632 return (element != EL_EMPTY ? element :
9633 number != -1 ? base_element + number - 1 :
9637 static int getModifiedActionNumber(int value_old, int operator, int operand,
9638 int value_min, int value_max)
9640 int value_new = (operator == CA_MODE_SET ? operand :
9641 operator == CA_MODE_ADD ? value_old + operand :
9642 operator == CA_MODE_SUBTRACT ? value_old - operand :
9643 operator == CA_MODE_MULTIPLY ? value_old * operand :
9644 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9645 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9648 return (value_new < value_min ? value_min :
9649 value_new > value_max ? value_max :
9653 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9655 struct ElementInfo *ei = &element_info[element];
9656 struct ElementChangeInfo *change = &ei->change_page[page];
9657 int target_element = change->target_element;
9658 int action_type = change->action_type;
9659 int action_mode = change->action_mode;
9660 int action_arg = change->action_arg;
9661 int action_element = change->action_element;
9664 if (!change->has_action)
9667 // ---------- determine action paramater values -----------------------------
9669 int level_time_value =
9670 (level.time > 0 ? TimeLeft :
9673 int action_arg_element_raw =
9674 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9675 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9676 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9677 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9678 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9679 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9680 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9682 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9684 int action_arg_direction =
9685 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9686 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9687 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9688 change->actual_trigger_side :
9689 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9690 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9693 int action_arg_number_min =
9694 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9697 int action_arg_number_max =
9698 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9699 action_type == CA_SET_LEVEL_GEMS ? 999 :
9700 action_type == CA_SET_LEVEL_TIME ? 9999 :
9701 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9702 action_type == CA_SET_CE_VALUE ? 9999 :
9703 action_type == CA_SET_CE_SCORE ? 9999 :
9706 int action_arg_number_reset =
9707 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9708 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9709 action_type == CA_SET_LEVEL_TIME ? level.time :
9710 action_type == CA_SET_LEVEL_SCORE ? 0 :
9711 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9712 action_type == CA_SET_CE_SCORE ? 0 :
9715 int action_arg_number =
9716 (action_arg <= CA_ARG_MAX ? action_arg :
9717 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9718 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9719 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9720 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9721 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9722 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9723 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9724 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9725 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9726 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9727 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9728 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9729 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9730 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9731 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9732 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9733 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9734 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9735 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9736 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9737 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9740 int action_arg_number_old =
9741 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9742 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9743 action_type == CA_SET_LEVEL_SCORE ? game.score :
9744 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9745 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9748 int action_arg_number_new =
9749 getModifiedActionNumber(action_arg_number_old,
9750 action_mode, action_arg_number,
9751 action_arg_number_min, action_arg_number_max);
9753 int trigger_player_bits =
9754 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9755 change->actual_trigger_player_bits : change->trigger_player);
9757 int action_arg_player_bits =
9758 (action_arg >= CA_ARG_PLAYER_1 &&
9759 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9760 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9761 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9764 // ---------- execute action -----------------------------------------------
9766 switch (action_type)
9773 // ---------- level actions ----------------------------------------------
9775 case CA_RESTART_LEVEL:
9777 game.restart_level = TRUE;
9782 case CA_SHOW_ENVELOPE:
9784 int element = getSpecialActionElement(action_arg_element,
9785 action_arg_number, EL_ENVELOPE_1);
9787 if (IS_ENVELOPE(element))
9788 local_player->show_envelope = element;
9793 case CA_SET_LEVEL_TIME:
9795 if (level.time > 0) // only modify limited time value
9797 TimeLeft = action_arg_number_new;
9799 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9801 DisplayGameControlValues();
9803 if (!TimeLeft && setup.time_limit)
9804 for (i = 0; i < MAX_PLAYERS; i++)
9805 KillPlayer(&stored_player[i]);
9811 case CA_SET_LEVEL_SCORE:
9813 game.score = action_arg_number_new;
9815 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9817 DisplayGameControlValues();
9822 case CA_SET_LEVEL_GEMS:
9824 game.gems_still_needed = action_arg_number_new;
9826 game.snapshot.collected_item = TRUE;
9828 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9830 DisplayGameControlValues();
9835 case CA_SET_LEVEL_WIND:
9837 game.wind_direction = action_arg_direction;
9842 case CA_SET_LEVEL_RANDOM_SEED:
9844 // ensure that setting a new random seed while playing is predictable
9845 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9850 // ---------- player actions ---------------------------------------------
9852 case CA_MOVE_PLAYER:
9854 // automatically move to the next field in specified direction
9855 for (i = 0; i < MAX_PLAYERS; i++)
9856 if (trigger_player_bits & (1 << i))
9857 stored_player[i].programmed_action = action_arg_direction;
9862 case CA_EXIT_PLAYER:
9864 for (i = 0; i < MAX_PLAYERS; i++)
9865 if (action_arg_player_bits & (1 << i))
9866 ExitPlayer(&stored_player[i]);
9868 if (game.players_still_needed == 0)
9874 case CA_KILL_PLAYER:
9876 for (i = 0; i < MAX_PLAYERS; i++)
9877 if (action_arg_player_bits & (1 << i))
9878 KillPlayer(&stored_player[i]);
9883 case CA_SET_PLAYER_KEYS:
9885 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
9886 int element = getSpecialActionElement(action_arg_element,
9887 action_arg_number, EL_KEY_1);
9889 if (IS_KEY(element))
9891 for (i = 0; i < MAX_PLAYERS; i++)
9893 if (trigger_player_bits & (1 << i))
9895 stored_player[i].key[KEY_NR(element)] = key_state;
9897 DrawGameDoorValues();
9905 case CA_SET_PLAYER_SPEED:
9907 for (i = 0; i < MAX_PLAYERS; i++)
9909 if (trigger_player_bits & (1 << i))
9911 int move_stepsize = TILEX / stored_player[i].move_delay_value;
9913 if (action_arg == CA_ARG_SPEED_FASTER &&
9914 stored_player[i].cannot_move)
9916 action_arg_number = STEPSIZE_VERY_SLOW;
9918 else if (action_arg == CA_ARG_SPEED_SLOWER ||
9919 action_arg == CA_ARG_SPEED_FASTER)
9921 action_arg_number = 2;
9922 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
9925 else if (action_arg == CA_ARG_NUMBER_RESET)
9927 action_arg_number = level.initial_player_stepsize[i];
9931 getModifiedActionNumber(move_stepsize,
9934 action_arg_number_min,
9935 action_arg_number_max);
9937 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
9944 case CA_SET_PLAYER_SHIELD:
9946 for (i = 0; i < MAX_PLAYERS; i++)
9948 if (trigger_player_bits & (1 << i))
9950 if (action_arg == CA_ARG_SHIELD_OFF)
9952 stored_player[i].shield_normal_time_left = 0;
9953 stored_player[i].shield_deadly_time_left = 0;
9955 else if (action_arg == CA_ARG_SHIELD_NORMAL)
9957 stored_player[i].shield_normal_time_left = 999999;
9959 else if (action_arg == CA_ARG_SHIELD_DEADLY)
9961 stored_player[i].shield_normal_time_left = 999999;
9962 stored_player[i].shield_deadly_time_left = 999999;
9970 case CA_SET_PLAYER_GRAVITY:
9972 for (i = 0; i < MAX_PLAYERS; i++)
9974 if (trigger_player_bits & (1 << i))
9976 stored_player[i].gravity =
9977 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
9978 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
9979 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
9980 stored_player[i].gravity);
9987 case CA_SET_PLAYER_ARTWORK:
9989 for (i = 0; i < MAX_PLAYERS; i++)
9991 if (trigger_player_bits & (1 << i))
9993 int artwork_element = action_arg_element;
9995 if (action_arg == CA_ARG_ELEMENT_RESET)
9997 (level.use_artwork_element[i] ? level.artwork_element[i] :
9998 stored_player[i].element_nr);
10000 if (stored_player[i].artwork_element != artwork_element)
10001 stored_player[i].Frame = 0;
10003 stored_player[i].artwork_element = artwork_element;
10005 SetPlayerWaiting(&stored_player[i], FALSE);
10007 // set number of special actions for bored and sleeping animation
10008 stored_player[i].num_special_action_bored =
10009 get_num_special_action(artwork_element,
10010 ACTION_BORING_1, ACTION_BORING_LAST);
10011 stored_player[i].num_special_action_sleeping =
10012 get_num_special_action(artwork_element,
10013 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10020 case CA_SET_PLAYER_INVENTORY:
10022 for (i = 0; i < MAX_PLAYERS; i++)
10024 struct PlayerInfo *player = &stored_player[i];
10027 if (trigger_player_bits & (1 << i))
10029 int inventory_element = action_arg_element;
10031 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10032 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10033 action_arg == CA_ARG_ELEMENT_ACTION)
10035 int element = inventory_element;
10036 int collect_count = element_info[element].collect_count_initial;
10038 if (!IS_CUSTOM_ELEMENT(element))
10041 if (collect_count == 0)
10042 player->inventory_infinite_element = element;
10044 for (k = 0; k < collect_count; k++)
10045 if (player->inventory_size < MAX_INVENTORY_SIZE)
10046 player->inventory_element[player->inventory_size++] =
10049 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10050 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10051 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10053 if (player->inventory_infinite_element != EL_UNDEFINED &&
10054 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10055 action_arg_element_raw))
10056 player->inventory_infinite_element = EL_UNDEFINED;
10058 for (k = 0, j = 0; j < player->inventory_size; j++)
10060 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10061 action_arg_element_raw))
10062 player->inventory_element[k++] = player->inventory_element[j];
10065 player->inventory_size = k;
10067 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10069 if (player->inventory_size > 0)
10071 for (j = 0; j < player->inventory_size - 1; j++)
10072 player->inventory_element[j] = player->inventory_element[j + 1];
10074 player->inventory_size--;
10077 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10079 if (player->inventory_size > 0)
10080 player->inventory_size--;
10082 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10084 player->inventory_infinite_element = EL_UNDEFINED;
10085 player->inventory_size = 0;
10087 else if (action_arg == CA_ARG_INVENTORY_RESET)
10089 player->inventory_infinite_element = EL_UNDEFINED;
10090 player->inventory_size = 0;
10092 if (level.use_initial_inventory[i])
10094 for (j = 0; j < level.initial_inventory_size[i]; j++)
10096 int element = level.initial_inventory_content[i][j];
10097 int collect_count = element_info[element].collect_count_initial;
10099 if (!IS_CUSTOM_ELEMENT(element))
10102 if (collect_count == 0)
10103 player->inventory_infinite_element = element;
10105 for (k = 0; k < collect_count; k++)
10106 if (player->inventory_size < MAX_INVENTORY_SIZE)
10107 player->inventory_element[player->inventory_size++] =
10118 // ---------- CE actions -------------------------------------------------
10120 case CA_SET_CE_VALUE:
10122 int last_ce_value = CustomValue[x][y];
10124 CustomValue[x][y] = action_arg_number_new;
10126 if (CustomValue[x][y] != last_ce_value)
10128 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10129 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10131 if (CustomValue[x][y] == 0)
10133 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10134 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10141 case CA_SET_CE_SCORE:
10143 int last_ce_score = ei->collect_score;
10145 ei->collect_score = action_arg_number_new;
10147 if (ei->collect_score != last_ce_score)
10149 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10150 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10152 if (ei->collect_score == 0)
10156 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10157 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10160 This is a very special case that seems to be a mixture between
10161 CheckElementChange() and CheckTriggeredElementChange(): while
10162 the first one only affects single elements that are triggered
10163 directly, the second one affects multiple elements in the playfield
10164 that are triggered indirectly by another element. This is a third
10165 case: Changing the CE score always affects multiple identical CEs,
10166 so every affected CE must be checked, not only the single CE for
10167 which the CE score was changed in the first place (as every instance
10168 of that CE shares the same CE score, and therefore also can change)!
10170 SCAN_PLAYFIELD(xx, yy)
10172 if (Feld[xx][yy] == element)
10173 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10174 CE_SCORE_GETS_ZERO);
10182 case CA_SET_CE_ARTWORK:
10184 int artwork_element = action_arg_element;
10185 boolean reset_frame = FALSE;
10188 if (action_arg == CA_ARG_ELEMENT_RESET)
10189 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10192 if (ei->gfx_element != artwork_element)
10193 reset_frame = TRUE;
10195 ei->gfx_element = artwork_element;
10197 SCAN_PLAYFIELD(xx, yy)
10199 if (Feld[xx][yy] == element)
10203 ResetGfxAnimation(xx, yy);
10204 ResetRandomAnimationValue(xx, yy);
10207 TEST_DrawLevelField(xx, yy);
10214 // ---------- engine actions ---------------------------------------------
10216 case CA_SET_ENGINE_SCAN_MODE:
10218 InitPlayfieldScanMode(action_arg);
10228 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10230 int old_element = Feld[x][y];
10231 int new_element = GetElementFromGroupElement(element);
10232 int previous_move_direction = MovDir[x][y];
10233 int last_ce_value = CustomValue[x][y];
10234 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10235 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10236 boolean add_player_onto_element = (new_element_is_player &&
10237 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10238 IS_WALKABLE(old_element));
10240 if (!add_player_onto_element)
10242 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10243 RemoveMovingField(x, y);
10247 Feld[x][y] = new_element;
10249 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10250 MovDir[x][y] = previous_move_direction;
10252 if (element_info[new_element].use_last_ce_value)
10253 CustomValue[x][y] = last_ce_value;
10255 InitField_WithBug1(x, y, FALSE);
10257 new_element = Feld[x][y]; // element may have changed
10259 ResetGfxAnimation(x, y);
10260 ResetRandomAnimationValue(x, y);
10262 TEST_DrawLevelField(x, y);
10264 if (GFX_CRUMBLED(new_element))
10265 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10268 // check if element under the player changes from accessible to unaccessible
10269 // (needed for special case of dropping element which then changes)
10270 // (must be checked after creating new element for walkable group elements)
10271 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10272 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10279 // "ChangeCount" not set yet to allow "entered by player" change one time
10280 if (new_element_is_player)
10281 RelocatePlayer(x, y, new_element);
10284 ChangeCount[x][y]++; // count number of changes in the same frame
10286 TestIfBadThingTouchesPlayer(x, y);
10287 TestIfPlayerTouchesCustomElement(x, y);
10288 TestIfElementTouchesCustomElement(x, y);
10291 static void CreateField(int x, int y, int element)
10293 CreateFieldExt(x, y, element, FALSE);
10296 static void CreateElementFromChange(int x, int y, int element)
10298 element = GET_VALID_RUNTIME_ELEMENT(element);
10300 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10302 int old_element = Feld[x][y];
10304 // prevent changed element from moving in same engine frame
10305 // unless both old and new element can either fall or move
10306 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10307 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10311 CreateFieldExt(x, y, element, TRUE);
10314 static boolean ChangeElement(int x, int y, int element, int page)
10316 struct ElementInfo *ei = &element_info[element];
10317 struct ElementChangeInfo *change = &ei->change_page[page];
10318 int ce_value = CustomValue[x][y];
10319 int ce_score = ei->collect_score;
10320 int target_element;
10321 int old_element = Feld[x][y];
10323 // always use default change event to prevent running into a loop
10324 if (ChangeEvent[x][y] == -1)
10325 ChangeEvent[x][y] = CE_DELAY;
10327 if (ChangeEvent[x][y] == CE_DELAY)
10329 // reset actual trigger element, trigger player and action element
10330 change->actual_trigger_element = EL_EMPTY;
10331 change->actual_trigger_player = EL_EMPTY;
10332 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10333 change->actual_trigger_side = CH_SIDE_NONE;
10334 change->actual_trigger_ce_value = 0;
10335 change->actual_trigger_ce_score = 0;
10338 // do not change elements more than a specified maximum number of changes
10339 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10342 ChangeCount[x][y]++; // count number of changes in the same frame
10344 if (change->explode)
10351 if (change->use_target_content)
10353 boolean complete_replace = TRUE;
10354 boolean can_replace[3][3];
10357 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10360 boolean is_walkable;
10361 boolean is_diggable;
10362 boolean is_collectible;
10363 boolean is_removable;
10364 boolean is_destructible;
10365 int ex = x + xx - 1;
10366 int ey = y + yy - 1;
10367 int content_element = change->target_content.e[xx][yy];
10370 can_replace[xx][yy] = TRUE;
10372 if (ex == x && ey == y) // do not check changing element itself
10375 if (content_element == EL_EMPTY_SPACE)
10377 can_replace[xx][yy] = FALSE; // do not replace border with space
10382 if (!IN_LEV_FIELD(ex, ey))
10384 can_replace[xx][yy] = FALSE;
10385 complete_replace = FALSE;
10392 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10393 e = MovingOrBlocked2Element(ex, ey);
10395 is_empty = (IS_FREE(ex, ey) ||
10396 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10398 is_walkable = (is_empty || IS_WALKABLE(e));
10399 is_diggable = (is_empty || IS_DIGGABLE(e));
10400 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10401 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10402 is_removable = (is_diggable || is_collectible);
10404 can_replace[xx][yy] =
10405 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10406 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10407 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10408 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10409 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10410 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10411 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10413 if (!can_replace[xx][yy])
10414 complete_replace = FALSE;
10417 if (!change->only_if_complete || complete_replace)
10419 boolean something_has_changed = FALSE;
10421 if (change->only_if_complete && change->use_random_replace &&
10422 RND(100) < change->random_percentage)
10425 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10427 int ex = x + xx - 1;
10428 int ey = y + yy - 1;
10429 int content_element;
10431 if (can_replace[xx][yy] && (!change->use_random_replace ||
10432 RND(100) < change->random_percentage))
10434 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10435 RemoveMovingField(ex, ey);
10437 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10439 content_element = change->target_content.e[xx][yy];
10440 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10441 ce_value, ce_score);
10443 CreateElementFromChange(ex, ey, target_element);
10445 something_has_changed = TRUE;
10447 // for symmetry reasons, freeze newly created border elements
10448 if (ex != x || ey != y)
10449 Stop[ex][ey] = TRUE; // no more moving in this frame
10453 if (something_has_changed)
10455 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10456 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10462 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10463 ce_value, ce_score);
10465 if (element == EL_DIAGONAL_GROWING ||
10466 element == EL_DIAGONAL_SHRINKING)
10468 target_element = Store[x][y];
10470 Store[x][y] = EL_EMPTY;
10473 CreateElementFromChange(x, y, target_element);
10475 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10476 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10479 // this uses direct change before indirect change
10480 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10485 static void HandleElementChange(int x, int y, int page)
10487 int element = MovingOrBlocked2Element(x, y);
10488 struct ElementInfo *ei = &element_info[element];
10489 struct ElementChangeInfo *change = &ei->change_page[page];
10490 boolean handle_action_before_change = FALSE;
10493 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10494 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10497 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10498 x, y, element, element_info[element].token_name);
10499 printf("HandleElementChange(): This should never happen!\n");
10504 // this can happen with classic bombs on walkable, changing elements
10505 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10510 if (ChangeDelay[x][y] == 0) // initialize element change
10512 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10514 if (change->can_change)
10516 // !!! not clear why graphic animation should be reset at all here !!!
10517 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10518 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10521 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10523 When using an animation frame delay of 1 (this only happens with
10524 "sp_zonk.moving.left/right" in the classic graphics), the default
10525 (non-moving) animation shows wrong animation frames (while the
10526 moving animation, like "sp_zonk.moving.left/right", is correct,
10527 so this graphical bug never shows up with the classic graphics).
10528 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10529 be drawn instead of the correct frames 0,1,2,3. This is caused by
10530 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10531 an element change: First when the change delay ("ChangeDelay[][]")
10532 counter has reached zero after decrementing, then a second time in
10533 the next frame (after "GfxFrame[][]" was already incremented) when
10534 "ChangeDelay[][]" is reset to the initial delay value again.
10536 This causes frame 0 to be drawn twice, while the last frame won't
10537 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10539 As some animations may already be cleverly designed around this bug
10540 (at least the "Snake Bite" snake tail animation does this), it cannot
10541 simply be fixed here without breaking such existing animations.
10542 Unfortunately, it cannot easily be detected if a graphics set was
10543 designed "before" or "after" the bug was fixed. As a workaround,
10544 a new graphics set option "game.graphics_engine_version" was added
10545 to be able to specify the game's major release version for which the
10546 graphics set was designed, which can then be used to decide if the
10547 bugfix should be used (version 4 and above) or not (version 3 or
10548 below, or if no version was specified at all, as with old sets).
10550 (The wrong/fixed animation frames can be tested with the test level set
10551 "test_gfxframe" and level "000", which contains a specially prepared
10552 custom element at level position (x/y) == (11/9) which uses the zonk
10553 animation mentioned above. Using "game.graphics_engine_version: 4"
10554 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10555 This can also be seen from the debug output for this test element.)
10558 // when a custom element is about to change (for example by change delay),
10559 // do not reset graphic animation when the custom element is moving
10560 if (game.graphics_engine_version < 4 &&
10563 ResetGfxAnimation(x, y);
10564 ResetRandomAnimationValue(x, y);
10567 if (change->pre_change_function)
10568 change->pre_change_function(x, y);
10572 ChangeDelay[x][y]--;
10574 if (ChangeDelay[x][y] != 0) // continue element change
10576 if (change->can_change)
10578 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10580 if (IS_ANIMATED(graphic))
10581 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10583 if (change->change_function)
10584 change->change_function(x, y);
10587 else // finish element change
10589 if (ChangePage[x][y] != -1) // remember page from delayed change
10591 page = ChangePage[x][y];
10592 ChangePage[x][y] = -1;
10594 change = &ei->change_page[page];
10597 if (IS_MOVING(x, y)) // never change a running system ;-)
10599 ChangeDelay[x][y] = 1; // try change after next move step
10600 ChangePage[x][y] = page; // remember page to use for change
10605 // special case: set new level random seed before changing element
10606 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10607 handle_action_before_change = TRUE;
10609 if (change->has_action && handle_action_before_change)
10610 ExecuteCustomElementAction(x, y, element, page);
10612 if (change->can_change)
10614 if (ChangeElement(x, y, element, page))
10616 if (change->post_change_function)
10617 change->post_change_function(x, y);
10621 if (change->has_action && !handle_action_before_change)
10622 ExecuteCustomElementAction(x, y, element, page);
10626 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10627 int trigger_element,
10629 int trigger_player,
10633 boolean change_done_any = FALSE;
10634 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10637 if (!(trigger_events[trigger_element][trigger_event]))
10640 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10642 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10644 int element = EL_CUSTOM_START + i;
10645 boolean change_done = FALSE;
10648 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10649 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10652 for (p = 0; p < element_info[element].num_change_pages; p++)
10654 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10656 if (change->can_change_or_has_action &&
10657 change->has_event[trigger_event] &&
10658 change->trigger_side & trigger_side &&
10659 change->trigger_player & trigger_player &&
10660 change->trigger_page & trigger_page_bits &&
10661 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10663 change->actual_trigger_element = trigger_element;
10664 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10665 change->actual_trigger_player_bits = trigger_player;
10666 change->actual_trigger_side = trigger_side;
10667 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10668 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10670 if ((change->can_change && !change_done) || change->has_action)
10674 SCAN_PLAYFIELD(x, y)
10676 if (Feld[x][y] == element)
10678 if (change->can_change && !change_done)
10680 // if element already changed in this frame, not only prevent
10681 // another element change (checked in ChangeElement()), but
10682 // also prevent additional element actions for this element
10684 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10685 !level.use_action_after_change_bug)
10688 ChangeDelay[x][y] = 1;
10689 ChangeEvent[x][y] = trigger_event;
10691 HandleElementChange(x, y, p);
10693 else if (change->has_action)
10695 // if element already changed in this frame, not only prevent
10696 // another element change (checked in ChangeElement()), but
10697 // also prevent additional element actions for this element
10699 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10700 !level.use_action_after_change_bug)
10703 ExecuteCustomElementAction(x, y, element, p);
10704 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10709 if (change->can_change)
10711 change_done = TRUE;
10712 change_done_any = TRUE;
10719 RECURSION_LOOP_DETECTION_END();
10721 return change_done_any;
10724 static boolean CheckElementChangeExt(int x, int y,
10726 int trigger_element,
10728 int trigger_player,
10731 boolean change_done = FALSE;
10734 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10735 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10738 if (Feld[x][y] == EL_BLOCKED)
10740 Blocked2Moving(x, y, &x, &y);
10741 element = Feld[x][y];
10744 // check if element has already changed or is about to change after moving
10745 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10746 Feld[x][y] != element) ||
10748 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10749 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10750 ChangePage[x][y] != -1)))
10753 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10755 for (p = 0; p < element_info[element].num_change_pages; p++)
10757 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10759 /* check trigger element for all events where the element that is checked
10760 for changing interacts with a directly adjacent element -- this is
10761 different to element changes that affect other elements to change on the
10762 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10763 boolean check_trigger_element =
10764 (trigger_event == CE_TOUCHING_X ||
10765 trigger_event == CE_HITTING_X ||
10766 trigger_event == CE_HIT_BY_X ||
10767 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10769 if (change->can_change_or_has_action &&
10770 change->has_event[trigger_event] &&
10771 change->trigger_side & trigger_side &&
10772 change->trigger_player & trigger_player &&
10773 (!check_trigger_element ||
10774 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10776 change->actual_trigger_element = trigger_element;
10777 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10778 change->actual_trigger_player_bits = trigger_player;
10779 change->actual_trigger_side = trigger_side;
10780 change->actual_trigger_ce_value = CustomValue[x][y];
10781 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10783 // special case: trigger element not at (x,y) position for some events
10784 if (check_trigger_element)
10796 { 0, 0 }, { 0, 0 }, { 0, 0 },
10800 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10801 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10803 change->actual_trigger_ce_value = CustomValue[xx][yy];
10804 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10807 if (change->can_change && !change_done)
10809 ChangeDelay[x][y] = 1;
10810 ChangeEvent[x][y] = trigger_event;
10812 HandleElementChange(x, y, p);
10814 change_done = TRUE;
10816 else if (change->has_action)
10818 ExecuteCustomElementAction(x, y, element, p);
10819 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10824 RECURSION_LOOP_DETECTION_END();
10826 return change_done;
10829 static void PlayPlayerSound(struct PlayerInfo *player)
10831 int jx = player->jx, jy = player->jy;
10832 int sound_element = player->artwork_element;
10833 int last_action = player->last_action_waiting;
10834 int action = player->action_waiting;
10836 if (player->is_waiting)
10838 if (action != last_action)
10839 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10841 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10845 if (action != last_action)
10846 StopSound(element_info[sound_element].sound[last_action]);
10848 if (last_action == ACTION_SLEEPING)
10849 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
10853 static void PlayAllPlayersSound(void)
10857 for (i = 0; i < MAX_PLAYERS; i++)
10858 if (stored_player[i].active)
10859 PlayPlayerSound(&stored_player[i]);
10862 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
10864 boolean last_waiting = player->is_waiting;
10865 int move_dir = player->MovDir;
10867 player->dir_waiting = move_dir;
10868 player->last_action_waiting = player->action_waiting;
10872 if (!last_waiting) // not waiting -> waiting
10874 player->is_waiting = TRUE;
10876 player->frame_counter_bored =
10878 game.player_boring_delay_fixed +
10879 GetSimpleRandom(game.player_boring_delay_random);
10880 player->frame_counter_sleeping =
10882 game.player_sleeping_delay_fixed +
10883 GetSimpleRandom(game.player_sleeping_delay_random);
10885 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
10888 if (game.player_sleeping_delay_fixed +
10889 game.player_sleeping_delay_random > 0 &&
10890 player->anim_delay_counter == 0 &&
10891 player->post_delay_counter == 0 &&
10892 FrameCounter >= player->frame_counter_sleeping)
10893 player->is_sleeping = TRUE;
10894 else if (game.player_boring_delay_fixed +
10895 game.player_boring_delay_random > 0 &&
10896 FrameCounter >= player->frame_counter_bored)
10897 player->is_bored = TRUE;
10899 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
10900 player->is_bored ? ACTION_BORING :
10903 if (player->is_sleeping && player->use_murphy)
10905 // special case for sleeping Murphy when leaning against non-free tile
10907 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
10908 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
10909 !IS_MOVING(player->jx - 1, player->jy)))
10910 move_dir = MV_LEFT;
10911 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
10912 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
10913 !IS_MOVING(player->jx + 1, player->jy)))
10914 move_dir = MV_RIGHT;
10916 player->is_sleeping = FALSE;
10918 player->dir_waiting = move_dir;
10921 if (player->is_sleeping)
10923 if (player->num_special_action_sleeping > 0)
10925 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10927 int last_special_action = player->special_action_sleeping;
10928 int num_special_action = player->num_special_action_sleeping;
10929 int special_action =
10930 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
10931 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
10932 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
10933 last_special_action + 1 : ACTION_SLEEPING);
10934 int special_graphic =
10935 el_act_dir2img(player->artwork_element, special_action, move_dir);
10937 player->anim_delay_counter =
10938 graphic_info[special_graphic].anim_delay_fixed +
10939 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10940 player->post_delay_counter =
10941 graphic_info[special_graphic].post_delay_fixed +
10942 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10944 player->special_action_sleeping = special_action;
10947 if (player->anim_delay_counter > 0)
10949 player->action_waiting = player->special_action_sleeping;
10950 player->anim_delay_counter--;
10952 else if (player->post_delay_counter > 0)
10954 player->post_delay_counter--;
10958 else if (player->is_bored)
10960 if (player->num_special_action_bored > 0)
10962 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10964 int special_action =
10965 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
10966 int special_graphic =
10967 el_act_dir2img(player->artwork_element, special_action, move_dir);
10969 player->anim_delay_counter =
10970 graphic_info[special_graphic].anim_delay_fixed +
10971 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10972 player->post_delay_counter =
10973 graphic_info[special_graphic].post_delay_fixed +
10974 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10976 player->special_action_bored = special_action;
10979 if (player->anim_delay_counter > 0)
10981 player->action_waiting = player->special_action_bored;
10982 player->anim_delay_counter--;
10984 else if (player->post_delay_counter > 0)
10986 player->post_delay_counter--;
10991 else if (last_waiting) // waiting -> not waiting
10993 player->is_waiting = FALSE;
10994 player->is_bored = FALSE;
10995 player->is_sleeping = FALSE;
10997 player->frame_counter_bored = -1;
10998 player->frame_counter_sleeping = -1;
11000 player->anim_delay_counter = 0;
11001 player->post_delay_counter = 0;
11003 player->dir_waiting = player->MovDir;
11004 player->action_waiting = ACTION_DEFAULT;
11006 player->special_action_bored = ACTION_DEFAULT;
11007 player->special_action_sleeping = ACTION_DEFAULT;
11011 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11013 if ((!player->is_moving && player->was_moving) ||
11014 (player->MovPos == 0 && player->was_moving) ||
11015 (player->is_snapping && !player->was_snapping) ||
11016 (player->is_dropping && !player->was_dropping))
11018 if (!CheckSaveEngineSnapshotToList())
11021 player->was_moving = FALSE;
11022 player->was_snapping = TRUE;
11023 player->was_dropping = TRUE;
11027 if (player->is_moving)
11028 player->was_moving = TRUE;
11030 if (!player->is_snapping)
11031 player->was_snapping = FALSE;
11033 if (!player->is_dropping)
11034 player->was_dropping = FALSE;
11038 static void CheckSingleStepMode(struct PlayerInfo *player)
11040 if (tape.single_step && tape.recording && !tape.pausing)
11042 /* as it is called "single step mode", just return to pause mode when the
11043 player stopped moving after one tile (or never starts moving at all) */
11044 if (!player->is_moving &&
11045 !player->is_pushing &&
11046 !player->is_dropping_pressed)
11048 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11049 SnapField(player, 0, 0); // stop snapping
11053 CheckSaveEngineSnapshot(player);
11056 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11058 int left = player_action & JOY_LEFT;
11059 int right = player_action & JOY_RIGHT;
11060 int up = player_action & JOY_UP;
11061 int down = player_action & JOY_DOWN;
11062 int button1 = player_action & JOY_BUTTON_1;
11063 int button2 = player_action & JOY_BUTTON_2;
11064 int dx = (left ? -1 : right ? 1 : 0);
11065 int dy = (up ? -1 : down ? 1 : 0);
11067 if (!player->active || tape.pausing)
11073 SnapField(player, dx, dy);
11077 DropElement(player);
11079 MovePlayer(player, dx, dy);
11082 CheckSingleStepMode(player);
11084 SetPlayerWaiting(player, FALSE);
11086 return player_action;
11090 // no actions for this player (no input at player's configured device)
11092 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11093 SnapField(player, 0, 0);
11094 CheckGravityMovementWhenNotMoving(player);
11096 if (player->MovPos == 0)
11097 SetPlayerWaiting(player, TRUE);
11099 if (player->MovPos == 0) // needed for tape.playing
11100 player->is_moving = FALSE;
11102 player->is_dropping = FALSE;
11103 player->is_dropping_pressed = FALSE;
11104 player->drop_pressed_delay = 0;
11106 CheckSingleStepMode(player);
11112 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11115 if (!tape.use_mouse)
11118 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11119 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11120 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11123 static void SetTapeActionFromMouseAction(byte *tape_action,
11124 struct MouseActionInfo *mouse_action)
11126 if (!tape.use_mouse)
11129 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11130 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11131 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11134 static void CheckLevelSolved(void)
11136 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11138 if (game_em.level_solved &&
11139 !game_em.game_over) // game won
11143 game_em.game_over = TRUE;
11145 game.all_players_gone = TRUE;
11148 if (game_em.game_over) // game lost
11149 game.all_players_gone = TRUE;
11151 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11153 if (game_sp.level_solved &&
11154 !game_sp.game_over) // game won
11158 game_sp.game_over = TRUE;
11160 game.all_players_gone = TRUE;
11163 if (game_sp.game_over) // game lost
11164 game.all_players_gone = TRUE;
11166 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11168 if (game_mm.level_solved &&
11169 !game_mm.game_over) // game won
11173 game_mm.game_over = TRUE;
11175 game.all_players_gone = TRUE;
11178 if (game_mm.game_over) // game lost
11179 game.all_players_gone = TRUE;
11183 static void CheckLevelTime(void)
11187 if (TimeFrames >= FRAMES_PER_SECOND)
11192 for (i = 0; i < MAX_PLAYERS; i++)
11194 struct PlayerInfo *player = &stored_player[i];
11196 if (SHIELD_ON(player))
11198 player->shield_normal_time_left--;
11200 if (player->shield_deadly_time_left > 0)
11201 player->shield_deadly_time_left--;
11205 if (!game.LevelSolved && !level.use_step_counter)
11213 if (TimeLeft <= 10 && setup.time_limit)
11214 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11216 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11217 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11219 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11221 if (!TimeLeft && setup.time_limit)
11223 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11224 level.native_em_level->lev->killed_out_of_time = TRUE;
11226 for (i = 0; i < MAX_PLAYERS; i++)
11227 KillPlayer(&stored_player[i]);
11230 else if (game.no_time_limit && !game.all_players_gone)
11232 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11235 level.native_em_level->lev->time =
11236 (game.no_time_limit ? TimePlayed : TimeLeft);
11239 if (tape.recording || tape.playing)
11240 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11243 if (tape.recording || tape.playing)
11244 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11246 UpdateAndDisplayGameControlValues();
11249 void AdvanceFrameAndPlayerCounters(int player_nr)
11253 // advance frame counters (global frame counter and time frame counter)
11257 // advance player counters (counters for move delay, move animation etc.)
11258 for (i = 0; i < MAX_PLAYERS; i++)
11260 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11261 int move_delay_value = stored_player[i].move_delay_value;
11262 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11264 if (!advance_player_counters) // not all players may be affected
11267 if (move_frames == 0) // less than one move per game frame
11269 int stepsize = TILEX / move_delay_value;
11270 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11271 int count = (stored_player[i].is_moving ?
11272 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11274 if (count % delay == 0)
11278 stored_player[i].Frame += move_frames;
11280 if (stored_player[i].MovPos != 0)
11281 stored_player[i].StepFrame += move_frames;
11283 if (stored_player[i].move_delay > 0)
11284 stored_player[i].move_delay--;
11286 // due to bugs in previous versions, counter must count up, not down
11287 if (stored_player[i].push_delay != -1)
11288 stored_player[i].push_delay++;
11290 if (stored_player[i].drop_delay > 0)
11291 stored_player[i].drop_delay--;
11293 if (stored_player[i].is_dropping_pressed)
11294 stored_player[i].drop_pressed_delay++;
11298 void StartGameActions(boolean init_network_game, boolean record_tape,
11301 unsigned int new_random_seed = InitRND(random_seed);
11304 TapeStartRecording(new_random_seed);
11306 if (init_network_game)
11308 SendToServer_LevelFile();
11309 SendToServer_StartPlaying();
11317 static void GameActionsExt(void)
11320 static unsigned int game_frame_delay = 0;
11322 unsigned int game_frame_delay_value;
11323 byte *recorded_player_action;
11324 byte summarized_player_action = 0;
11325 byte tape_action[MAX_PLAYERS];
11328 // detect endless loops, caused by custom element programming
11329 if (recursion_loop_detected && recursion_loop_depth == 0)
11331 char *message = getStringCat3("Internal Error! Element ",
11332 EL_NAME(recursion_loop_element),
11333 " caused endless loop! Quit the game?");
11335 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11336 EL_NAME(recursion_loop_element));
11338 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11340 recursion_loop_detected = FALSE; // if game should be continued
11347 if (game.restart_level)
11348 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11350 CheckLevelSolved();
11352 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11355 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11358 if (game_status != GAME_MODE_PLAYING) // status might have changed
11361 game_frame_delay_value =
11362 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11364 if (tape.playing && tape.warp_forward && !tape.pausing)
11365 game_frame_delay_value = 0;
11367 SetVideoFrameDelay(game_frame_delay_value);
11369 // (de)activate virtual buttons depending on current game status
11370 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11372 if (game.all_players_gone) // if no players there to be controlled anymore
11373 SetOverlayActive(FALSE);
11374 else if (!tape.playing) // if game continues after tape stopped playing
11375 SetOverlayActive(TRUE);
11380 // ---------- main game synchronization point ----------
11382 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11384 printf("::: skip == %d\n", skip);
11387 // ---------- main game synchronization point ----------
11389 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11393 if (network_playing && !network_player_action_received)
11395 // try to get network player actions in time
11397 // last chance to get network player actions without main loop delay
11398 HandleNetworking();
11400 // game was quit by network peer
11401 if (game_status != GAME_MODE_PLAYING)
11404 // check if network player actions still missing and game still running
11405 if (!network_player_action_received && !checkGameEnded())
11406 return; // failed to get network player actions in time
11408 // do not yet reset "network_player_action_received" (for tape.pausing)
11414 // at this point we know that we really continue executing the game
11416 network_player_action_received = FALSE;
11418 // when playing tape, read previously recorded player input from tape data
11419 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11421 local_player->effective_mouse_action = local_player->mouse_action;
11423 if (recorded_player_action != NULL)
11424 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11425 recorded_player_action);
11427 // TapePlayAction() may return NULL when toggling to "pause before death"
11431 if (tape.set_centered_player)
11433 game.centered_player_nr_next = tape.centered_player_nr_next;
11434 game.set_centered_player = TRUE;
11437 for (i = 0; i < MAX_PLAYERS; i++)
11439 summarized_player_action |= stored_player[i].action;
11441 if (!network_playing && (game.team_mode || tape.playing))
11442 stored_player[i].effective_action = stored_player[i].action;
11445 if (network_playing && !checkGameEnded())
11446 SendToServer_MovePlayer(summarized_player_action);
11448 // summarize all actions at local players mapped input device position
11449 // (this allows using different input devices in single player mode)
11450 if (!network.enabled && !game.team_mode)
11451 stored_player[map_player_action[local_player->index_nr]].effective_action =
11452 summarized_player_action;
11454 if (tape.recording &&
11456 setup.input_on_focus &&
11457 game.centered_player_nr != -1)
11459 for (i = 0; i < MAX_PLAYERS; i++)
11460 stored_player[i].effective_action =
11461 (i == game.centered_player_nr ? summarized_player_action : 0);
11464 if (recorded_player_action != NULL)
11465 for (i = 0; i < MAX_PLAYERS; i++)
11466 stored_player[i].effective_action = recorded_player_action[i];
11468 for (i = 0; i < MAX_PLAYERS; i++)
11470 tape_action[i] = stored_player[i].effective_action;
11472 /* (this may happen in the RND game engine if a player was not present on
11473 the playfield on level start, but appeared later from a custom element */
11474 if (setup.team_mode &&
11477 !tape.player_participates[i])
11478 tape.player_participates[i] = TRUE;
11481 SetTapeActionFromMouseAction(tape_action,
11482 &local_player->effective_mouse_action);
11484 // only record actions from input devices, but not programmed actions
11485 if (tape.recording)
11486 TapeRecordAction(tape_action);
11488 // remember if game was played (especially after tape stopped playing)
11489 if (!tape.playing && summarized_player_action)
11490 game.GamePlayed = TRUE;
11492 #if USE_NEW_PLAYER_ASSIGNMENTS
11493 // !!! also map player actions in single player mode !!!
11494 // if (game.team_mode)
11497 byte mapped_action[MAX_PLAYERS];
11499 #if DEBUG_PLAYER_ACTIONS
11501 for (i = 0; i < MAX_PLAYERS; i++)
11502 printf(" %d, ", stored_player[i].effective_action);
11505 for (i = 0; i < MAX_PLAYERS; i++)
11506 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11508 for (i = 0; i < MAX_PLAYERS; i++)
11509 stored_player[i].effective_action = mapped_action[i];
11511 #if DEBUG_PLAYER_ACTIONS
11513 for (i = 0; i < MAX_PLAYERS; i++)
11514 printf(" %d, ", stored_player[i].effective_action);
11518 #if DEBUG_PLAYER_ACTIONS
11522 for (i = 0; i < MAX_PLAYERS; i++)
11523 printf(" %d, ", stored_player[i].effective_action);
11529 for (i = 0; i < MAX_PLAYERS; i++)
11531 // allow engine snapshot in case of changed movement attempt
11532 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11533 (stored_player[i].effective_action & KEY_MOTION))
11534 game.snapshot.changed_action = TRUE;
11536 // allow engine snapshot in case of snapping/dropping attempt
11537 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11538 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11539 game.snapshot.changed_action = TRUE;
11541 game.snapshot.last_action[i] = stored_player[i].effective_action;
11544 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11546 GameActions_EM_Main();
11548 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11550 GameActions_SP_Main();
11552 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11554 GameActions_MM_Main();
11558 GameActions_RND_Main();
11561 BlitScreenToBitmap(backbuffer);
11563 CheckLevelSolved();
11566 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11568 if (global.show_frames_per_second)
11570 static unsigned int fps_counter = 0;
11571 static int fps_frames = 0;
11572 unsigned int fps_delay_ms = Counter() - fps_counter;
11576 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11578 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11581 fps_counter = Counter();
11583 // always draw FPS to screen after FPS value was updated
11584 redraw_mask |= REDRAW_FPS;
11587 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11588 if (GetDrawDeactivationMask() == REDRAW_NONE)
11589 redraw_mask |= REDRAW_FPS;
11593 static void GameActions_CheckSaveEngineSnapshot(void)
11595 if (!game.snapshot.save_snapshot)
11598 // clear flag for saving snapshot _before_ saving snapshot
11599 game.snapshot.save_snapshot = FALSE;
11601 SaveEngineSnapshotToList();
11604 void GameActions(void)
11608 GameActions_CheckSaveEngineSnapshot();
11611 void GameActions_EM_Main(void)
11613 byte effective_action[MAX_PLAYERS];
11614 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11617 for (i = 0; i < MAX_PLAYERS; i++)
11618 effective_action[i] = stored_player[i].effective_action;
11620 GameActions_EM(effective_action, warp_mode);
11623 void GameActions_SP_Main(void)
11625 byte effective_action[MAX_PLAYERS];
11626 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11629 for (i = 0; i < MAX_PLAYERS; i++)
11630 effective_action[i] = stored_player[i].effective_action;
11632 GameActions_SP(effective_action, warp_mode);
11634 for (i = 0; i < MAX_PLAYERS; i++)
11636 if (stored_player[i].force_dropping)
11637 stored_player[i].action |= KEY_BUTTON_DROP;
11639 stored_player[i].force_dropping = FALSE;
11643 void GameActions_MM_Main(void)
11645 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11647 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11650 void GameActions_RND_Main(void)
11655 void GameActions_RND(void)
11657 int magic_wall_x = 0, magic_wall_y = 0;
11658 int i, x, y, element, graphic, last_gfx_frame;
11660 InitPlayfieldScanModeVars();
11662 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11664 SCAN_PLAYFIELD(x, y)
11666 ChangeCount[x][y] = 0;
11667 ChangeEvent[x][y] = -1;
11671 if (game.set_centered_player)
11673 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11675 // switching to "all players" only possible if all players fit to screen
11676 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11678 game.centered_player_nr_next = game.centered_player_nr;
11679 game.set_centered_player = FALSE;
11682 // do not switch focus to non-existing (or non-active) player
11683 if (game.centered_player_nr_next >= 0 &&
11684 !stored_player[game.centered_player_nr_next].active)
11686 game.centered_player_nr_next = game.centered_player_nr;
11687 game.set_centered_player = FALSE;
11691 if (game.set_centered_player &&
11692 ScreenMovPos == 0) // screen currently aligned at tile position
11696 if (game.centered_player_nr_next == -1)
11698 setScreenCenteredToAllPlayers(&sx, &sy);
11702 sx = stored_player[game.centered_player_nr_next].jx;
11703 sy = stored_player[game.centered_player_nr_next].jy;
11706 game.centered_player_nr = game.centered_player_nr_next;
11707 game.set_centered_player = FALSE;
11709 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11710 DrawGameDoorValues();
11713 for (i = 0; i < MAX_PLAYERS; i++)
11715 int actual_player_action = stored_player[i].effective_action;
11718 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11719 - rnd_equinox_tetrachloride 048
11720 - rnd_equinox_tetrachloride_ii 096
11721 - rnd_emanuel_schmieg 002
11722 - doctor_sloan_ww 001, 020
11724 if (stored_player[i].MovPos == 0)
11725 CheckGravityMovement(&stored_player[i]);
11728 // overwrite programmed action with tape action
11729 if (stored_player[i].programmed_action)
11730 actual_player_action = stored_player[i].programmed_action;
11732 PlayerActions(&stored_player[i], actual_player_action);
11734 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11737 ScrollScreen(NULL, SCROLL_GO_ON);
11739 /* for backwards compatibility, the following code emulates a fixed bug that
11740 occured when pushing elements (causing elements that just made their last
11741 pushing step to already (if possible) make their first falling step in the
11742 same game frame, which is bad); this code is also needed to use the famous
11743 "spring push bug" which is used in older levels and might be wanted to be
11744 used also in newer levels, but in this case the buggy pushing code is only
11745 affecting the "spring" element and no other elements */
11747 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11749 for (i = 0; i < MAX_PLAYERS; i++)
11751 struct PlayerInfo *player = &stored_player[i];
11752 int x = player->jx;
11753 int y = player->jy;
11755 if (player->active && player->is_pushing && player->is_moving &&
11757 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11758 Feld[x][y] == EL_SPRING))
11760 ContinueMoving(x, y);
11762 // continue moving after pushing (this is actually a bug)
11763 if (!IS_MOVING(x, y))
11764 Stop[x][y] = FALSE;
11769 SCAN_PLAYFIELD(x, y)
11771 Last[x][y] = Feld[x][y];
11773 ChangeCount[x][y] = 0;
11774 ChangeEvent[x][y] = -1;
11776 // this must be handled before main playfield loop
11777 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11780 if (MovDelay[x][y] <= 0)
11784 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11787 if (MovDelay[x][y] <= 0)
11790 TEST_DrawLevelField(x, y);
11792 TestIfElementTouchesCustomElement(x, y); // for empty space
11797 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11799 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11800 printf("GameActions(): This should never happen!\n");
11802 ChangePage[x][y] = -1;
11806 Stop[x][y] = FALSE;
11807 if (WasJustMoving[x][y] > 0)
11808 WasJustMoving[x][y]--;
11809 if (WasJustFalling[x][y] > 0)
11810 WasJustFalling[x][y]--;
11811 if (CheckCollision[x][y] > 0)
11812 CheckCollision[x][y]--;
11813 if (CheckImpact[x][y] > 0)
11814 CheckImpact[x][y]--;
11818 /* reset finished pushing action (not done in ContinueMoving() to allow
11819 continuous pushing animation for elements with zero push delay) */
11820 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11822 ResetGfxAnimation(x, y);
11823 TEST_DrawLevelField(x, y);
11827 if (IS_BLOCKED(x, y))
11831 Blocked2Moving(x, y, &oldx, &oldy);
11832 if (!IS_MOVING(oldx, oldy))
11834 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11835 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11836 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11837 printf("GameActions(): This should never happen!\n");
11843 SCAN_PLAYFIELD(x, y)
11845 element = Feld[x][y];
11846 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11847 last_gfx_frame = GfxFrame[x][y];
11849 ResetGfxFrame(x, y);
11851 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
11852 DrawLevelGraphicAnimation(x, y, graphic);
11854 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
11855 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
11856 ResetRandomAnimationValue(x, y);
11858 SetRandomAnimationValue(x, y);
11860 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
11862 if (IS_INACTIVE(element))
11864 if (IS_ANIMATED(graphic))
11865 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11870 // this may take place after moving, so 'element' may have changed
11871 if (IS_CHANGING(x, y) &&
11872 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
11874 int page = element_info[element].event_page_nr[CE_DELAY];
11876 HandleElementChange(x, y, page);
11878 element = Feld[x][y];
11879 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11882 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
11886 element = Feld[x][y];
11887 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11889 if (IS_ANIMATED(graphic) &&
11890 !IS_MOVING(x, y) &&
11892 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11894 if (IS_GEM(element) || element == EL_SP_INFOTRON)
11895 TEST_DrawTwinkleOnField(x, y);
11897 else if (element == EL_ACID)
11900 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11902 else if ((element == EL_EXIT_OPEN ||
11903 element == EL_EM_EXIT_OPEN ||
11904 element == EL_SP_EXIT_OPEN ||
11905 element == EL_STEEL_EXIT_OPEN ||
11906 element == EL_EM_STEEL_EXIT_OPEN ||
11907 element == EL_SP_TERMINAL ||
11908 element == EL_SP_TERMINAL_ACTIVE ||
11909 element == EL_EXTRA_TIME ||
11910 element == EL_SHIELD_NORMAL ||
11911 element == EL_SHIELD_DEADLY) &&
11912 IS_ANIMATED(graphic))
11913 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11914 else if (IS_MOVING(x, y))
11915 ContinueMoving(x, y);
11916 else if (IS_ACTIVE_BOMB(element))
11917 CheckDynamite(x, y);
11918 else if (element == EL_AMOEBA_GROWING)
11919 AmoebeWaechst(x, y);
11920 else if (element == EL_AMOEBA_SHRINKING)
11921 AmoebaDisappearing(x, y);
11923 #if !USE_NEW_AMOEBA_CODE
11924 else if (IS_AMOEBALIVE(element))
11925 AmoebeAbleger(x, y);
11928 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
11930 else if (element == EL_EXIT_CLOSED)
11932 else if (element == EL_EM_EXIT_CLOSED)
11934 else if (element == EL_STEEL_EXIT_CLOSED)
11935 CheckExitSteel(x, y);
11936 else if (element == EL_EM_STEEL_EXIT_CLOSED)
11937 CheckExitSteelEM(x, y);
11938 else if (element == EL_SP_EXIT_CLOSED)
11940 else if (element == EL_EXPANDABLE_WALL_GROWING ||
11941 element == EL_EXPANDABLE_STEELWALL_GROWING)
11942 MauerWaechst(x, y);
11943 else if (element == EL_EXPANDABLE_WALL ||
11944 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
11945 element == EL_EXPANDABLE_WALL_VERTICAL ||
11946 element == EL_EXPANDABLE_WALL_ANY ||
11947 element == EL_BD_EXPANDABLE_WALL)
11948 MauerAbleger(x, y);
11949 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
11950 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
11951 element == EL_EXPANDABLE_STEELWALL_ANY)
11952 MauerAblegerStahl(x, y);
11953 else if (element == EL_FLAMES)
11954 CheckForDragon(x, y);
11955 else if (element == EL_EXPLOSION)
11956 ; // drawing of correct explosion animation is handled separately
11957 else if (element == EL_ELEMENT_SNAPPING ||
11958 element == EL_DIAGONAL_SHRINKING ||
11959 element == EL_DIAGONAL_GROWING)
11961 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
11963 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11965 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
11966 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11968 if (IS_BELT_ACTIVE(element))
11969 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
11971 if (game.magic_wall_active)
11973 int jx = local_player->jx, jy = local_player->jy;
11975 // play the element sound at the position nearest to the player
11976 if ((element == EL_MAGIC_WALL_FULL ||
11977 element == EL_MAGIC_WALL_ACTIVE ||
11978 element == EL_MAGIC_WALL_EMPTYING ||
11979 element == EL_BD_MAGIC_WALL_FULL ||
11980 element == EL_BD_MAGIC_WALL_ACTIVE ||
11981 element == EL_BD_MAGIC_WALL_EMPTYING ||
11982 element == EL_DC_MAGIC_WALL_FULL ||
11983 element == EL_DC_MAGIC_WALL_ACTIVE ||
11984 element == EL_DC_MAGIC_WALL_EMPTYING) &&
11985 ABS(x-jx) + ABS(y-jy) < ABS(magic_wall_x-jx) + ABS(magic_wall_y-jy))
11993 #if USE_NEW_AMOEBA_CODE
11994 // new experimental amoeba growth stuff
11995 if (!(FrameCounter % 8))
11997 static unsigned int random = 1684108901;
11999 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12001 x = RND(lev_fieldx);
12002 y = RND(lev_fieldy);
12003 element = Feld[x][y];
12005 if (!IS_PLAYER(x,y) &&
12006 (element == EL_EMPTY ||
12007 CAN_GROW_INTO(element) ||
12008 element == EL_QUICKSAND_EMPTY ||
12009 element == EL_QUICKSAND_FAST_EMPTY ||
12010 element == EL_ACID_SPLASH_LEFT ||
12011 element == EL_ACID_SPLASH_RIGHT))
12013 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12014 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12015 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12016 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12017 Feld[x][y] = EL_AMOEBA_DROP;
12020 random = random * 129 + 1;
12025 game.explosions_delayed = FALSE;
12027 SCAN_PLAYFIELD(x, y)
12029 element = Feld[x][y];
12031 if (ExplodeField[x][y])
12032 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12033 else if (element == EL_EXPLOSION)
12034 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12036 ExplodeField[x][y] = EX_TYPE_NONE;
12039 game.explosions_delayed = TRUE;
12041 if (game.magic_wall_active)
12043 if (!(game.magic_wall_time_left % 4))
12045 int element = Feld[magic_wall_x][magic_wall_y];
12047 if (element == EL_BD_MAGIC_WALL_FULL ||
12048 element == EL_BD_MAGIC_WALL_ACTIVE ||
12049 element == EL_BD_MAGIC_WALL_EMPTYING)
12050 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12051 else if (element == EL_DC_MAGIC_WALL_FULL ||
12052 element == EL_DC_MAGIC_WALL_ACTIVE ||
12053 element == EL_DC_MAGIC_WALL_EMPTYING)
12054 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12056 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12059 if (game.magic_wall_time_left > 0)
12061 game.magic_wall_time_left--;
12063 if (!game.magic_wall_time_left)
12065 SCAN_PLAYFIELD(x, y)
12067 element = Feld[x][y];
12069 if (element == EL_MAGIC_WALL_ACTIVE ||
12070 element == EL_MAGIC_WALL_FULL)
12072 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12073 TEST_DrawLevelField(x, y);
12075 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12076 element == EL_BD_MAGIC_WALL_FULL)
12078 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12079 TEST_DrawLevelField(x, y);
12081 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12082 element == EL_DC_MAGIC_WALL_FULL)
12084 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12085 TEST_DrawLevelField(x, y);
12089 game.magic_wall_active = FALSE;
12094 if (game.light_time_left > 0)
12096 game.light_time_left--;
12098 if (game.light_time_left == 0)
12099 RedrawAllLightSwitchesAndInvisibleElements();
12102 if (game.timegate_time_left > 0)
12104 game.timegate_time_left--;
12106 if (game.timegate_time_left == 0)
12107 CloseAllOpenTimegates();
12110 if (game.lenses_time_left > 0)
12112 game.lenses_time_left--;
12114 if (game.lenses_time_left == 0)
12115 RedrawAllInvisibleElementsForLenses();
12118 if (game.magnify_time_left > 0)
12120 game.magnify_time_left--;
12122 if (game.magnify_time_left == 0)
12123 RedrawAllInvisibleElementsForMagnifier();
12126 for (i = 0; i < MAX_PLAYERS; i++)
12128 struct PlayerInfo *player = &stored_player[i];
12130 if (SHIELD_ON(player))
12132 if (player->shield_deadly_time_left)
12133 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12134 else if (player->shield_normal_time_left)
12135 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12139 #if USE_DELAYED_GFX_REDRAW
12140 SCAN_PLAYFIELD(x, y)
12142 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12144 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12145 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12147 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12148 DrawLevelField(x, y);
12150 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12151 DrawLevelFieldCrumbled(x, y);
12153 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12154 DrawLevelFieldCrumbledNeighbours(x, y);
12156 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12157 DrawTwinkleOnField(x, y);
12160 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12165 PlayAllPlayersSound();
12167 if (local_player->show_envelope != 0 && local_player->MovPos == 0)
12169 ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
12171 local_player->show_envelope = 0;
12174 // use random number generator in every frame to make it less predictable
12175 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12179 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12181 int min_x = x, min_y = y, max_x = x, max_y = y;
12184 for (i = 0; i < MAX_PLAYERS; i++)
12186 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12188 if (!stored_player[i].active || &stored_player[i] == player)
12191 min_x = MIN(min_x, jx);
12192 min_y = MIN(min_y, jy);
12193 max_x = MAX(max_x, jx);
12194 max_y = MAX(max_y, jy);
12197 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12200 static boolean AllPlayersInVisibleScreen(void)
12204 for (i = 0; i < MAX_PLAYERS; i++)
12206 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12208 if (!stored_player[i].active)
12211 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12218 void ScrollLevel(int dx, int dy)
12220 int scroll_offset = 2 * TILEX_VAR;
12223 BlitBitmap(drawto_field, drawto_field,
12224 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12225 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12226 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12227 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12228 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12229 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12233 x = (dx == 1 ? BX1 : BX2);
12234 for (y = BY1; y <= BY2; y++)
12235 DrawScreenField(x, y);
12240 y = (dy == 1 ? BY1 : BY2);
12241 for (x = BX1; x <= BX2; x++)
12242 DrawScreenField(x, y);
12245 redraw_mask |= REDRAW_FIELD;
12248 static boolean canFallDown(struct PlayerInfo *player)
12250 int jx = player->jx, jy = player->jy;
12252 return (IN_LEV_FIELD(jx, jy + 1) &&
12253 (IS_FREE(jx, jy + 1) ||
12254 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12255 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12256 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12259 static boolean canPassField(int x, int y, int move_dir)
12261 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12262 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12263 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12264 int nextx = x + dx;
12265 int nexty = y + dy;
12266 int element = Feld[x][y];
12268 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12269 !CAN_MOVE(element) &&
12270 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12271 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12272 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12275 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12277 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12278 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12279 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12283 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12284 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12285 (IS_DIGGABLE(Feld[newx][newy]) ||
12286 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12287 canPassField(newx, newy, move_dir)));
12290 static void CheckGravityMovement(struct PlayerInfo *player)
12292 if (player->gravity && !player->programmed_action)
12294 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12295 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12296 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12297 int jx = player->jx, jy = player->jy;
12298 boolean player_is_moving_to_valid_field =
12299 (!player_is_snapping &&
12300 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12301 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12302 boolean player_can_fall_down = canFallDown(player);
12304 if (player_can_fall_down &&
12305 !player_is_moving_to_valid_field)
12306 player->programmed_action = MV_DOWN;
12310 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12312 return CheckGravityMovement(player);
12314 if (player->gravity && !player->programmed_action)
12316 int jx = player->jx, jy = player->jy;
12317 boolean field_under_player_is_free =
12318 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12319 boolean player_is_standing_on_valid_field =
12320 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12321 (IS_WALKABLE(Feld[jx][jy]) &&
12322 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12324 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12325 player->programmed_action = MV_DOWN;
12330 MovePlayerOneStep()
12331 -----------------------------------------------------------------------------
12332 dx, dy: direction (non-diagonal) to try to move the player to
12333 real_dx, real_dy: direction as read from input device (can be diagonal)
12336 boolean MovePlayerOneStep(struct PlayerInfo *player,
12337 int dx, int dy, int real_dx, int real_dy)
12339 int jx = player->jx, jy = player->jy;
12340 int new_jx = jx + dx, new_jy = jy + dy;
12342 boolean player_can_move = !player->cannot_move;
12344 if (!player->active || (!dx && !dy))
12345 return MP_NO_ACTION;
12347 player->MovDir = (dx < 0 ? MV_LEFT :
12348 dx > 0 ? MV_RIGHT :
12350 dy > 0 ? MV_DOWN : MV_NONE);
12352 if (!IN_LEV_FIELD(new_jx, new_jy))
12353 return MP_NO_ACTION;
12355 if (!player_can_move)
12357 if (player->MovPos == 0)
12359 player->is_moving = FALSE;
12360 player->is_digging = FALSE;
12361 player->is_collecting = FALSE;
12362 player->is_snapping = FALSE;
12363 player->is_pushing = FALSE;
12367 if (!network.enabled && game.centered_player_nr == -1 &&
12368 !AllPlayersInSight(player, new_jx, new_jy))
12369 return MP_NO_ACTION;
12371 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12372 if (can_move != MP_MOVING)
12375 // check if DigField() has caused relocation of the player
12376 if (player->jx != jx || player->jy != jy)
12377 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12379 StorePlayer[jx][jy] = 0;
12380 player->last_jx = jx;
12381 player->last_jy = jy;
12382 player->jx = new_jx;
12383 player->jy = new_jy;
12384 StorePlayer[new_jx][new_jy] = player->element_nr;
12386 if (player->move_delay_value_next != -1)
12388 player->move_delay_value = player->move_delay_value_next;
12389 player->move_delay_value_next = -1;
12393 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12395 player->step_counter++;
12397 PlayerVisit[jx][jy] = FrameCounter;
12399 player->is_moving = TRUE;
12402 // should better be called in MovePlayer(), but this breaks some tapes
12403 ScrollPlayer(player, SCROLL_INIT);
12409 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12411 int jx = player->jx, jy = player->jy;
12412 int old_jx = jx, old_jy = jy;
12413 int moved = MP_NO_ACTION;
12415 if (!player->active)
12420 if (player->MovPos == 0)
12422 player->is_moving = FALSE;
12423 player->is_digging = FALSE;
12424 player->is_collecting = FALSE;
12425 player->is_snapping = FALSE;
12426 player->is_pushing = FALSE;
12432 if (player->move_delay > 0)
12435 player->move_delay = -1; // set to "uninitialized" value
12437 // store if player is automatically moved to next field
12438 player->is_auto_moving = (player->programmed_action != MV_NONE);
12440 // remove the last programmed player action
12441 player->programmed_action = 0;
12443 if (player->MovPos)
12445 // should only happen if pre-1.2 tape recordings are played
12446 // this is only for backward compatibility
12448 int original_move_delay_value = player->move_delay_value;
12451 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12455 // scroll remaining steps with finest movement resolution
12456 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12458 while (player->MovPos)
12460 ScrollPlayer(player, SCROLL_GO_ON);
12461 ScrollScreen(NULL, SCROLL_GO_ON);
12463 AdvanceFrameAndPlayerCounters(player->index_nr);
12466 BackToFront_WithFrameDelay(0);
12469 player->move_delay_value = original_move_delay_value;
12472 player->is_active = FALSE;
12474 if (player->last_move_dir & MV_HORIZONTAL)
12476 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12477 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12481 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12482 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12485 if (!moved && !player->is_active)
12487 player->is_moving = FALSE;
12488 player->is_digging = FALSE;
12489 player->is_collecting = FALSE;
12490 player->is_snapping = FALSE;
12491 player->is_pushing = FALSE;
12497 if (moved & MP_MOVING && !ScreenMovPos &&
12498 (player->index_nr == game.centered_player_nr ||
12499 game.centered_player_nr == -1))
12501 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12502 int offset = game.scroll_delay_value;
12504 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12506 // actual player has left the screen -- scroll in that direction
12507 if (jx != old_jx) // player has moved horizontally
12508 scroll_x += (jx - old_jx);
12509 else // player has moved vertically
12510 scroll_y += (jy - old_jy);
12514 if (jx != old_jx) // player has moved horizontally
12516 if ((player->MovDir == MV_LEFT && scroll_x > jx - MIDPOSX + offset) ||
12517 (player->MovDir == MV_RIGHT && scroll_x < jx - MIDPOSX - offset))
12518 scroll_x = jx-MIDPOSX + (scroll_x < jx-MIDPOSX ? -offset : +offset);
12520 // don't scroll over playfield boundaries
12521 if (scroll_x < SBX_Left || scroll_x > SBX_Right)
12522 scroll_x = (scroll_x < SBX_Left ? SBX_Left : SBX_Right);
12524 // don't scroll more than one field at a time
12525 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12527 // don't scroll against the player's moving direction
12528 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12529 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12530 scroll_x = old_scroll_x;
12532 else // player has moved vertically
12534 if ((player->MovDir == MV_UP && scroll_y > jy - MIDPOSY + offset) ||
12535 (player->MovDir == MV_DOWN && scroll_y < jy - MIDPOSY - offset))
12536 scroll_y = jy-MIDPOSY + (scroll_y < jy-MIDPOSY ? -offset : +offset);
12538 // don't scroll over playfield boundaries
12539 if (scroll_y < SBY_Upper || scroll_y > SBY_Lower)
12540 scroll_y = (scroll_y < SBY_Upper ? SBY_Upper : SBY_Lower);
12542 // don't scroll more than one field at a time
12543 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12545 // don't scroll against the player's moving direction
12546 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12547 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12548 scroll_y = old_scroll_y;
12552 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12554 if (!network.enabled && game.centered_player_nr == -1 &&
12555 !AllPlayersInVisibleScreen())
12557 scroll_x = old_scroll_x;
12558 scroll_y = old_scroll_y;
12562 ScrollScreen(player, SCROLL_INIT);
12563 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12568 player->StepFrame = 0;
12570 if (moved & MP_MOVING)
12572 if (old_jx != jx && old_jy == jy)
12573 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12574 else if (old_jx == jx && old_jy != jy)
12575 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12577 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12579 player->last_move_dir = player->MovDir;
12580 player->is_moving = TRUE;
12581 player->is_snapping = FALSE;
12582 player->is_switching = FALSE;
12583 player->is_dropping = FALSE;
12584 player->is_dropping_pressed = FALSE;
12585 player->drop_pressed_delay = 0;
12588 // should better be called here than above, but this breaks some tapes
12589 ScrollPlayer(player, SCROLL_INIT);
12594 CheckGravityMovementWhenNotMoving(player);
12596 player->is_moving = FALSE;
12598 /* at this point, the player is allowed to move, but cannot move right now
12599 (e.g. because of something blocking the way) -- ensure that the player
12600 is also allowed to move in the next frame (in old versions before 3.1.1,
12601 the player was forced to wait again for eight frames before next try) */
12603 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12604 player->move_delay = 0; // allow direct movement in the next frame
12607 if (player->move_delay == -1) // not yet initialized by DigField()
12608 player->move_delay = player->move_delay_value;
12610 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12612 TestIfPlayerTouchesBadThing(jx, jy);
12613 TestIfPlayerTouchesCustomElement(jx, jy);
12616 if (!player->active)
12617 RemovePlayer(player);
12622 void ScrollPlayer(struct PlayerInfo *player, int mode)
12624 int jx = player->jx, jy = player->jy;
12625 int last_jx = player->last_jx, last_jy = player->last_jy;
12626 int move_stepsize = TILEX / player->move_delay_value;
12628 if (!player->active)
12631 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12634 if (mode == SCROLL_INIT)
12636 player->actual_frame_counter = FrameCounter;
12637 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12639 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12640 Feld[last_jx][last_jy] == EL_EMPTY)
12642 int last_field_block_delay = 0; // start with no blocking at all
12643 int block_delay_adjustment = player->block_delay_adjustment;
12645 // if player blocks last field, add delay for exactly one move
12646 if (player->block_last_field)
12648 last_field_block_delay += player->move_delay_value;
12650 // when blocking enabled, prevent moving up despite gravity
12651 if (player->gravity && player->MovDir == MV_UP)
12652 block_delay_adjustment = -1;
12655 // add block delay adjustment (also possible when not blocking)
12656 last_field_block_delay += block_delay_adjustment;
12658 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12659 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12662 if (player->MovPos != 0) // player has not yet reached destination
12665 else if (!FrameReached(&player->actual_frame_counter, 1))
12668 if (player->MovPos != 0)
12670 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12671 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12673 // before DrawPlayer() to draw correct player graphic for this case
12674 if (player->MovPos == 0)
12675 CheckGravityMovement(player);
12678 if (player->MovPos == 0) // player reached destination field
12680 if (player->move_delay_reset_counter > 0)
12682 player->move_delay_reset_counter--;
12684 if (player->move_delay_reset_counter == 0)
12686 // continue with normal speed after quickly moving through gate
12687 HALVE_PLAYER_SPEED(player);
12689 // be able to make the next move without delay
12690 player->move_delay = 0;
12694 player->last_jx = jx;
12695 player->last_jy = jy;
12697 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12698 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12699 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12700 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12701 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12702 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12703 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12704 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12706 ExitPlayer(player);
12708 if (game.players_still_needed == 0 &&
12709 (game.friends_still_needed == 0 ||
12710 IS_SP_ELEMENT(Feld[jx][jy])))
12714 // this breaks one level: "machine", level 000
12716 int move_direction = player->MovDir;
12717 int enter_side = MV_DIR_OPPOSITE(move_direction);
12718 int leave_side = move_direction;
12719 int old_jx = last_jx;
12720 int old_jy = last_jy;
12721 int old_element = Feld[old_jx][old_jy];
12722 int new_element = Feld[jx][jy];
12724 if (IS_CUSTOM_ELEMENT(old_element))
12725 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12727 player->index_bit, leave_side);
12729 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12730 CE_PLAYER_LEAVES_X,
12731 player->index_bit, leave_side);
12733 if (IS_CUSTOM_ELEMENT(new_element))
12734 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12735 player->index_bit, enter_side);
12737 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12738 CE_PLAYER_ENTERS_X,
12739 player->index_bit, enter_side);
12741 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12742 CE_MOVE_OF_X, move_direction);
12745 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12747 TestIfPlayerTouchesBadThing(jx, jy);
12748 TestIfPlayerTouchesCustomElement(jx, jy);
12750 /* needed because pushed element has not yet reached its destination,
12751 so it would trigger a change event at its previous field location */
12752 if (!player->is_pushing)
12753 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12755 if (!player->active)
12756 RemovePlayer(player);
12759 if (!game.LevelSolved && level.use_step_counter)
12769 if (TimeLeft <= 10 && setup.time_limit)
12770 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12772 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12774 DisplayGameControlValues();
12776 if (!TimeLeft && setup.time_limit)
12777 for (i = 0; i < MAX_PLAYERS; i++)
12778 KillPlayer(&stored_player[i]);
12780 else if (game.no_time_limit && !game.all_players_gone)
12782 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12784 DisplayGameControlValues();
12788 if (tape.single_step && tape.recording && !tape.pausing &&
12789 !player->programmed_action)
12790 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12792 if (!player->programmed_action)
12793 CheckSaveEngineSnapshot(player);
12797 void ScrollScreen(struct PlayerInfo *player, int mode)
12799 static unsigned int screen_frame_counter = 0;
12801 if (mode == SCROLL_INIT)
12803 // set scrolling step size according to actual player's moving speed
12804 ScrollStepSize = TILEX / player->move_delay_value;
12806 screen_frame_counter = FrameCounter;
12807 ScreenMovDir = player->MovDir;
12808 ScreenMovPos = player->MovPos;
12809 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12812 else if (!FrameReached(&screen_frame_counter, 1))
12817 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
12818 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12819 redraw_mask |= REDRAW_FIELD;
12822 ScreenMovDir = MV_NONE;
12825 void TestIfPlayerTouchesCustomElement(int x, int y)
12827 static int xy[4][2] =
12834 static int trigger_sides[4][2] =
12836 // center side border side
12837 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12838 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12839 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12840 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12842 static int touch_dir[4] =
12844 MV_LEFT | MV_RIGHT,
12849 int center_element = Feld[x][y]; // should always be non-moving!
12852 for (i = 0; i < NUM_DIRECTIONS; i++)
12854 int xx = x + xy[i][0];
12855 int yy = y + xy[i][1];
12856 int center_side = trigger_sides[i][0];
12857 int border_side = trigger_sides[i][1];
12858 int border_element;
12860 if (!IN_LEV_FIELD(xx, yy))
12863 if (IS_PLAYER(x, y)) // player found at center element
12865 struct PlayerInfo *player = PLAYERINFO(x, y);
12867 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12868 border_element = Feld[xx][yy]; // may be moving!
12869 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12870 border_element = Feld[xx][yy];
12871 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
12872 border_element = MovingOrBlocked2Element(xx, yy);
12874 continue; // center and border element do not touch
12876 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
12877 player->index_bit, border_side);
12878 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
12879 CE_PLAYER_TOUCHES_X,
12880 player->index_bit, border_side);
12883 /* use player element that is initially defined in the level playfield,
12884 not the player element that corresponds to the runtime player number
12885 (example: a level that contains EL_PLAYER_3 as the only player would
12886 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12887 int player_element = PLAYERINFO(x, y)->initial_element;
12889 CheckElementChangeBySide(xx, yy, border_element, player_element,
12890 CE_TOUCHING_X, border_side);
12893 else if (IS_PLAYER(xx, yy)) // player found at border element
12895 struct PlayerInfo *player = PLAYERINFO(xx, yy);
12897 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12899 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
12900 continue; // center and border element do not touch
12903 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
12904 player->index_bit, center_side);
12905 CheckTriggeredElementChangeByPlayer(x, y, center_element,
12906 CE_PLAYER_TOUCHES_X,
12907 player->index_bit, center_side);
12910 /* use player element that is initially defined in the level playfield,
12911 not the player element that corresponds to the runtime player number
12912 (example: a level that contains EL_PLAYER_3 as the only player would
12913 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12914 int player_element = PLAYERINFO(xx, yy)->initial_element;
12916 CheckElementChangeBySide(x, y, center_element, player_element,
12917 CE_TOUCHING_X, center_side);
12925 void TestIfElementTouchesCustomElement(int x, int y)
12927 static int xy[4][2] =
12934 static int trigger_sides[4][2] =
12936 // center side border side
12937 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12938 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12939 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12940 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12942 static int touch_dir[4] =
12944 MV_LEFT | MV_RIGHT,
12949 boolean change_center_element = FALSE;
12950 int center_element = Feld[x][y]; // should always be non-moving!
12951 int border_element_old[NUM_DIRECTIONS];
12954 for (i = 0; i < NUM_DIRECTIONS; i++)
12956 int xx = x + xy[i][0];
12957 int yy = y + xy[i][1];
12958 int border_element;
12960 border_element_old[i] = -1;
12962 if (!IN_LEV_FIELD(xx, yy))
12965 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12966 border_element = Feld[xx][yy]; // may be moving!
12967 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12968 border_element = Feld[xx][yy];
12969 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
12970 border_element = MovingOrBlocked2Element(xx, yy);
12972 continue; // center and border element do not touch
12974 border_element_old[i] = border_element;
12977 for (i = 0; i < NUM_DIRECTIONS; i++)
12979 int xx = x + xy[i][0];
12980 int yy = y + xy[i][1];
12981 int center_side = trigger_sides[i][0];
12982 int border_element = border_element_old[i];
12984 if (border_element == -1)
12987 // check for change of border element
12988 CheckElementChangeBySide(xx, yy, border_element, center_element,
12989 CE_TOUCHING_X, center_side);
12991 // (center element cannot be player, so we dont have to check this here)
12994 for (i = 0; i < NUM_DIRECTIONS; i++)
12996 int xx = x + xy[i][0];
12997 int yy = y + xy[i][1];
12998 int border_side = trigger_sides[i][1];
12999 int border_element = border_element_old[i];
13001 if (border_element == -1)
13004 // check for change of center element (but change it only once)
13005 if (!change_center_element)
13006 change_center_element =
13007 CheckElementChangeBySide(x, y, center_element, border_element,
13008 CE_TOUCHING_X, border_side);
13010 if (IS_PLAYER(xx, yy))
13012 /* use player element that is initially defined in the level playfield,
13013 not the player element that corresponds to the runtime player number
13014 (example: a level that contains EL_PLAYER_3 as the only player would
13015 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13016 int player_element = PLAYERINFO(xx, yy)->initial_element;
13018 CheckElementChangeBySide(x, y, center_element, player_element,
13019 CE_TOUCHING_X, border_side);
13024 void TestIfElementHitsCustomElement(int x, int y, int direction)
13026 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13027 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13028 int hitx = x + dx, hity = y + dy;
13029 int hitting_element = Feld[x][y];
13030 int touched_element;
13032 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13035 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13036 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13038 if (IN_LEV_FIELD(hitx, hity))
13040 int opposite_direction = MV_DIR_OPPOSITE(direction);
13041 int hitting_side = direction;
13042 int touched_side = opposite_direction;
13043 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13044 MovDir[hitx][hity] != direction ||
13045 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13051 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13052 CE_HITTING_X, touched_side);
13054 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13055 CE_HIT_BY_X, hitting_side);
13057 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13058 CE_HIT_BY_SOMETHING, opposite_direction);
13060 if (IS_PLAYER(hitx, hity))
13062 /* use player element that is initially defined in the level playfield,
13063 not the player element that corresponds to the runtime player number
13064 (example: a level that contains EL_PLAYER_3 as the only player would
13065 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13066 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13068 CheckElementChangeBySide(x, y, hitting_element, player_element,
13069 CE_HITTING_X, touched_side);
13074 // "hitting something" is also true when hitting the playfield border
13075 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13076 CE_HITTING_SOMETHING, direction);
13079 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13081 int i, kill_x = -1, kill_y = -1;
13083 int bad_element = -1;
13084 static int test_xy[4][2] =
13091 static int test_dir[4] =
13099 for (i = 0; i < NUM_DIRECTIONS; i++)
13101 int test_x, test_y, test_move_dir, test_element;
13103 test_x = good_x + test_xy[i][0];
13104 test_y = good_y + test_xy[i][1];
13106 if (!IN_LEV_FIELD(test_x, test_y))
13110 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13112 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13114 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13115 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13117 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13118 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13122 bad_element = test_element;
13128 if (kill_x != -1 || kill_y != -1)
13130 if (IS_PLAYER(good_x, good_y))
13132 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13134 if (player->shield_deadly_time_left > 0 &&
13135 !IS_INDESTRUCTIBLE(bad_element))
13136 Bang(kill_x, kill_y);
13137 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13138 KillPlayer(player);
13141 Bang(good_x, good_y);
13145 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13147 int i, kill_x = -1, kill_y = -1;
13148 int bad_element = Feld[bad_x][bad_y];
13149 static int test_xy[4][2] =
13156 static int touch_dir[4] =
13158 MV_LEFT | MV_RIGHT,
13163 static int test_dir[4] =
13171 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13174 for (i = 0; i < NUM_DIRECTIONS; i++)
13176 int test_x, test_y, test_move_dir, test_element;
13178 test_x = bad_x + test_xy[i][0];
13179 test_y = bad_y + test_xy[i][1];
13181 if (!IN_LEV_FIELD(test_x, test_y))
13185 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13187 test_element = Feld[test_x][test_y];
13189 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13190 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13192 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13193 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13195 // good thing is player or penguin that does not move away
13196 if (IS_PLAYER(test_x, test_y))
13198 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13200 if (bad_element == EL_ROBOT && player->is_moving)
13201 continue; // robot does not kill player if he is moving
13203 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13205 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13206 continue; // center and border element do not touch
13214 else if (test_element == EL_PENGUIN)
13224 if (kill_x != -1 || kill_y != -1)
13226 if (IS_PLAYER(kill_x, kill_y))
13228 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13230 if (player->shield_deadly_time_left > 0 &&
13231 !IS_INDESTRUCTIBLE(bad_element))
13232 Bang(bad_x, bad_y);
13233 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13234 KillPlayer(player);
13237 Bang(kill_x, kill_y);
13241 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13243 int bad_element = Feld[bad_x][bad_y];
13244 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13245 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13246 int test_x = bad_x + dx, test_y = bad_y + dy;
13247 int test_move_dir, test_element;
13248 int kill_x = -1, kill_y = -1;
13250 if (!IN_LEV_FIELD(test_x, test_y))
13254 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13256 test_element = Feld[test_x][test_y];
13258 if (test_move_dir != bad_move_dir)
13260 // good thing can be player or penguin that does not move away
13261 if (IS_PLAYER(test_x, test_y))
13263 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13265 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13266 player as being hit when he is moving towards the bad thing, because
13267 the "get hit by" condition would be lost after the player stops) */
13268 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13269 return; // player moves away from bad thing
13274 else if (test_element == EL_PENGUIN)
13281 if (kill_x != -1 || kill_y != -1)
13283 if (IS_PLAYER(kill_x, kill_y))
13285 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13287 if (player->shield_deadly_time_left > 0 &&
13288 !IS_INDESTRUCTIBLE(bad_element))
13289 Bang(bad_x, bad_y);
13290 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13291 KillPlayer(player);
13294 Bang(kill_x, kill_y);
13298 void TestIfPlayerTouchesBadThing(int x, int y)
13300 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13303 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13305 TestIfGoodThingHitsBadThing(x, y, move_dir);
13308 void TestIfBadThingTouchesPlayer(int x, int y)
13310 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13313 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13315 TestIfBadThingHitsGoodThing(x, y, move_dir);
13318 void TestIfFriendTouchesBadThing(int x, int y)
13320 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13323 void TestIfBadThingTouchesFriend(int x, int y)
13325 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13328 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13330 int i, kill_x = bad_x, kill_y = bad_y;
13331 static int xy[4][2] =
13339 for (i = 0; i < NUM_DIRECTIONS; i++)
13343 x = bad_x + xy[i][0];
13344 y = bad_y + xy[i][1];
13345 if (!IN_LEV_FIELD(x, y))
13348 element = Feld[x][y];
13349 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13350 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13358 if (kill_x != bad_x || kill_y != bad_y)
13359 Bang(bad_x, bad_y);
13362 void KillPlayer(struct PlayerInfo *player)
13364 int jx = player->jx, jy = player->jy;
13366 if (!player->active)
13370 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13371 player->killed, player->active, player->reanimated);
13374 /* the following code was introduced to prevent an infinite loop when calling
13376 -> CheckTriggeredElementChangeExt()
13377 -> ExecuteCustomElementAction()
13379 -> (infinitely repeating the above sequence of function calls)
13380 which occurs when killing the player while having a CE with the setting
13381 "kill player X when explosion of <player X>"; the solution using a new
13382 field "player->killed" was chosen for backwards compatibility, although
13383 clever use of the fields "player->active" etc. would probably also work */
13385 if (player->killed)
13389 player->killed = TRUE;
13391 // remove accessible field at the player's position
13392 Feld[jx][jy] = EL_EMPTY;
13394 // deactivate shield (else Bang()/Explode() would not work right)
13395 player->shield_normal_time_left = 0;
13396 player->shield_deadly_time_left = 0;
13399 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13400 player->killed, player->active, player->reanimated);
13406 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13407 player->killed, player->active, player->reanimated);
13410 if (player->reanimated) // killed player may have been reanimated
13411 player->killed = player->reanimated = FALSE;
13413 BuryPlayer(player);
13416 static void KillPlayerUnlessEnemyProtected(int x, int y)
13418 if (!PLAYER_ENEMY_PROTECTED(x, y))
13419 KillPlayer(PLAYERINFO(x, y));
13422 static void KillPlayerUnlessExplosionProtected(int x, int y)
13424 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13425 KillPlayer(PLAYERINFO(x, y));
13428 void BuryPlayer(struct PlayerInfo *player)
13430 int jx = player->jx, jy = player->jy;
13432 if (!player->active)
13435 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13436 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13438 RemovePlayer(player);
13440 player->buried = TRUE;
13442 if (game.all_players_gone)
13443 game.GameOver = TRUE;
13446 void RemovePlayer(struct PlayerInfo *player)
13448 int jx = player->jx, jy = player->jy;
13449 int i, found = FALSE;
13451 player->present = FALSE;
13452 player->active = FALSE;
13454 if (!ExplodeField[jx][jy])
13455 StorePlayer[jx][jy] = 0;
13457 if (player->is_moving)
13458 TEST_DrawLevelField(player->last_jx, player->last_jy);
13460 for (i = 0; i < MAX_PLAYERS; i++)
13461 if (stored_player[i].active)
13466 game.all_players_gone = TRUE;
13467 game.GameOver = TRUE;
13470 game.exit_x = game.robot_wheel_x = jx;
13471 game.exit_y = game.robot_wheel_y = jy;
13474 void ExitPlayer(struct PlayerInfo *player)
13476 DrawPlayer(player); // needed here only to cleanup last field
13477 RemovePlayer(player);
13479 if (game.players_still_needed > 0)
13480 game.players_still_needed--;
13483 static void setFieldForSnapping(int x, int y, int element, int direction)
13485 struct ElementInfo *ei = &element_info[element];
13486 int direction_bit = MV_DIR_TO_BIT(direction);
13487 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13488 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13489 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13491 Feld[x][y] = EL_ELEMENT_SNAPPING;
13492 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13494 ResetGfxAnimation(x, y);
13496 GfxElement[x][y] = element;
13497 GfxAction[x][y] = action;
13498 GfxDir[x][y] = direction;
13499 GfxFrame[x][y] = -1;
13503 =============================================================================
13504 checkDiagonalPushing()
13505 -----------------------------------------------------------------------------
13506 check if diagonal input device direction results in pushing of object
13507 (by checking if the alternative direction is walkable, diggable, ...)
13508 =============================================================================
13511 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13512 int x, int y, int real_dx, int real_dy)
13514 int jx, jy, dx, dy, xx, yy;
13516 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13519 // diagonal direction: check alternative direction
13524 xx = jx + (dx == 0 ? real_dx : 0);
13525 yy = jy + (dy == 0 ? real_dy : 0);
13527 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13531 =============================================================================
13533 -----------------------------------------------------------------------------
13534 x, y: field next to player (non-diagonal) to try to dig to
13535 real_dx, real_dy: direction as read from input device (can be diagonal)
13536 =============================================================================
13539 static int DigField(struct PlayerInfo *player,
13540 int oldx, int oldy, int x, int y,
13541 int real_dx, int real_dy, int mode)
13543 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13544 boolean player_was_pushing = player->is_pushing;
13545 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13546 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13547 int jx = oldx, jy = oldy;
13548 int dx = x - jx, dy = y - jy;
13549 int nextx = x + dx, nexty = y + dy;
13550 int move_direction = (dx == -1 ? MV_LEFT :
13551 dx == +1 ? MV_RIGHT :
13553 dy == +1 ? MV_DOWN : MV_NONE);
13554 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13555 int dig_side = MV_DIR_OPPOSITE(move_direction);
13556 int old_element = Feld[jx][jy];
13557 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13560 if (is_player) // function can also be called by EL_PENGUIN
13562 if (player->MovPos == 0)
13564 player->is_digging = FALSE;
13565 player->is_collecting = FALSE;
13568 if (player->MovPos == 0) // last pushing move finished
13569 player->is_pushing = FALSE;
13571 if (mode == DF_NO_PUSH) // player just stopped pushing
13573 player->is_switching = FALSE;
13574 player->push_delay = -1;
13576 return MP_NO_ACTION;
13580 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13581 old_element = Back[jx][jy];
13583 // in case of element dropped at player position, check background
13584 else if (Back[jx][jy] != EL_EMPTY &&
13585 game.engine_version >= VERSION_IDENT(2,2,0,0))
13586 old_element = Back[jx][jy];
13588 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13589 return MP_NO_ACTION; // field has no opening in this direction
13591 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13592 return MP_NO_ACTION; // field has no opening in this direction
13594 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13598 Feld[jx][jy] = player->artwork_element;
13599 InitMovingField(jx, jy, MV_DOWN);
13600 Store[jx][jy] = EL_ACID;
13601 ContinueMoving(jx, jy);
13602 BuryPlayer(player);
13604 return MP_DONT_RUN_INTO;
13607 if (player_can_move && DONT_RUN_INTO(element))
13609 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13611 return MP_DONT_RUN_INTO;
13614 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13615 return MP_NO_ACTION;
13617 collect_count = element_info[element].collect_count_initial;
13619 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13620 return MP_NO_ACTION;
13622 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13623 player_can_move = player_can_move_or_snap;
13625 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13626 game.engine_version >= VERSION_IDENT(2,2,0,0))
13628 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13629 player->index_bit, dig_side);
13630 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13631 player->index_bit, dig_side);
13633 if (element == EL_DC_LANDMINE)
13636 if (Feld[x][y] != element) // field changed by snapping
13639 return MP_NO_ACTION;
13642 if (player->gravity && is_player && !player->is_auto_moving &&
13643 canFallDown(player) && move_direction != MV_DOWN &&
13644 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13645 return MP_NO_ACTION; // player cannot walk here due to gravity
13647 if (player_can_move &&
13648 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13650 int sound_element = SND_ELEMENT(element);
13651 int sound_action = ACTION_WALKING;
13653 if (IS_RND_GATE(element))
13655 if (!player->key[RND_GATE_NR(element)])
13656 return MP_NO_ACTION;
13658 else if (IS_RND_GATE_GRAY(element))
13660 if (!player->key[RND_GATE_GRAY_NR(element)])
13661 return MP_NO_ACTION;
13663 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13665 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13666 return MP_NO_ACTION;
13668 else if (element == EL_EXIT_OPEN ||
13669 element == EL_EM_EXIT_OPEN ||
13670 element == EL_EM_EXIT_OPENING ||
13671 element == EL_STEEL_EXIT_OPEN ||
13672 element == EL_EM_STEEL_EXIT_OPEN ||
13673 element == EL_EM_STEEL_EXIT_OPENING ||
13674 element == EL_SP_EXIT_OPEN ||
13675 element == EL_SP_EXIT_OPENING)
13677 sound_action = ACTION_PASSING; // player is passing exit
13679 else if (element == EL_EMPTY)
13681 sound_action = ACTION_MOVING; // nothing to walk on
13684 // play sound from background or player, whatever is available
13685 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13686 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13688 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13690 else if (player_can_move &&
13691 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13693 if (!ACCESS_FROM(element, opposite_direction))
13694 return MP_NO_ACTION; // field not accessible from this direction
13696 if (CAN_MOVE(element)) // only fixed elements can be passed!
13697 return MP_NO_ACTION;
13699 if (IS_EM_GATE(element))
13701 if (!player->key[EM_GATE_NR(element)])
13702 return MP_NO_ACTION;
13704 else if (IS_EM_GATE_GRAY(element))
13706 if (!player->key[EM_GATE_GRAY_NR(element)])
13707 return MP_NO_ACTION;
13709 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13711 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13712 return MP_NO_ACTION;
13714 else if (IS_EMC_GATE(element))
13716 if (!player->key[EMC_GATE_NR(element)])
13717 return MP_NO_ACTION;
13719 else if (IS_EMC_GATE_GRAY(element))
13721 if (!player->key[EMC_GATE_GRAY_NR(element)])
13722 return MP_NO_ACTION;
13724 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13726 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13727 return MP_NO_ACTION;
13729 else if (element == EL_DC_GATE_WHITE ||
13730 element == EL_DC_GATE_WHITE_GRAY ||
13731 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13733 if (player->num_white_keys == 0)
13734 return MP_NO_ACTION;
13736 player->num_white_keys--;
13738 else if (IS_SP_PORT(element))
13740 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13741 element == EL_SP_GRAVITY_PORT_RIGHT ||
13742 element == EL_SP_GRAVITY_PORT_UP ||
13743 element == EL_SP_GRAVITY_PORT_DOWN)
13744 player->gravity = !player->gravity;
13745 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13746 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13747 element == EL_SP_GRAVITY_ON_PORT_UP ||
13748 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13749 player->gravity = TRUE;
13750 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13751 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13752 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13753 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13754 player->gravity = FALSE;
13757 // automatically move to the next field with double speed
13758 player->programmed_action = move_direction;
13760 if (player->move_delay_reset_counter == 0)
13762 player->move_delay_reset_counter = 2; // two double speed steps
13764 DOUBLE_PLAYER_SPEED(player);
13767 PlayLevelSoundAction(x, y, ACTION_PASSING);
13769 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13773 if (mode != DF_SNAP)
13775 GfxElement[x][y] = GFX_ELEMENT(element);
13776 player->is_digging = TRUE;
13779 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13781 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13782 player->index_bit, dig_side);
13784 if (mode == DF_SNAP)
13786 if (level.block_snap_field)
13787 setFieldForSnapping(x, y, element, move_direction);
13789 TestIfElementTouchesCustomElement(x, y); // for empty space
13791 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13792 player->index_bit, dig_side);
13795 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13799 if (is_player && mode != DF_SNAP)
13801 GfxElement[x][y] = element;
13802 player->is_collecting = TRUE;
13805 if (element == EL_SPEED_PILL)
13807 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13809 else if (element == EL_EXTRA_TIME && level.time > 0)
13811 TimeLeft += level.extra_time;
13813 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13815 DisplayGameControlValues();
13817 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
13819 player->shield_normal_time_left += level.shield_normal_time;
13820 if (element == EL_SHIELD_DEADLY)
13821 player->shield_deadly_time_left += level.shield_deadly_time;
13823 else if (element == EL_DYNAMITE ||
13824 element == EL_EM_DYNAMITE ||
13825 element == EL_SP_DISK_RED)
13827 if (player->inventory_size < MAX_INVENTORY_SIZE)
13828 player->inventory_element[player->inventory_size++] = element;
13830 DrawGameDoorValues();
13832 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
13834 player->dynabomb_count++;
13835 player->dynabombs_left++;
13837 else if (element == EL_DYNABOMB_INCREASE_SIZE)
13839 player->dynabomb_size++;
13841 else if (element == EL_DYNABOMB_INCREASE_POWER)
13843 player->dynabomb_xl = TRUE;
13845 else if (IS_KEY(element))
13847 player->key[KEY_NR(element)] = TRUE;
13849 DrawGameDoorValues();
13851 else if (element == EL_DC_KEY_WHITE)
13853 player->num_white_keys++;
13855 // display white keys?
13856 // DrawGameDoorValues();
13858 else if (IS_ENVELOPE(element))
13860 player->show_envelope = element;
13862 else if (element == EL_EMC_LENSES)
13864 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
13866 RedrawAllInvisibleElementsForLenses();
13868 else if (element == EL_EMC_MAGNIFIER)
13870 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
13872 RedrawAllInvisibleElementsForMagnifier();
13874 else if (IS_DROPPABLE(element) ||
13875 IS_THROWABLE(element)) // can be collected and dropped
13879 if (collect_count == 0)
13880 player->inventory_infinite_element = element;
13882 for (i = 0; i < collect_count; i++)
13883 if (player->inventory_size < MAX_INVENTORY_SIZE)
13884 player->inventory_element[player->inventory_size++] = element;
13886 DrawGameDoorValues();
13888 else if (collect_count > 0)
13890 game.gems_still_needed -= collect_count;
13891 if (game.gems_still_needed < 0)
13892 game.gems_still_needed = 0;
13894 game.snapshot.collected_item = TRUE;
13896 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
13898 DisplayGameControlValues();
13901 RaiseScoreElement(element);
13902 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
13905 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
13906 player->index_bit, dig_side);
13908 if (mode == DF_SNAP)
13910 if (level.block_snap_field)
13911 setFieldForSnapping(x, y, element, move_direction);
13913 TestIfElementTouchesCustomElement(x, y); // for empty space
13915 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13916 player->index_bit, dig_side);
13919 else if (player_can_move_or_snap && IS_PUSHABLE(element))
13921 if (mode == DF_SNAP && element != EL_BD_ROCK)
13922 return MP_NO_ACTION;
13924 if (CAN_FALL(element) && dy)
13925 return MP_NO_ACTION;
13927 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
13928 !(element == EL_SPRING && level.use_spring_bug))
13929 return MP_NO_ACTION;
13931 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
13932 ((move_direction & MV_VERTICAL &&
13933 ((element_info[element].move_pattern & MV_LEFT &&
13934 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
13935 (element_info[element].move_pattern & MV_RIGHT &&
13936 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
13937 (move_direction & MV_HORIZONTAL &&
13938 ((element_info[element].move_pattern & MV_UP &&
13939 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
13940 (element_info[element].move_pattern & MV_DOWN &&
13941 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
13942 return MP_NO_ACTION;
13944 // do not push elements already moving away faster than player
13945 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
13946 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
13947 return MP_NO_ACTION;
13949 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
13951 if (player->push_delay_value == -1 || !player_was_pushing)
13952 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13954 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13956 if (player->push_delay_value == -1)
13957 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13959 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
13961 if (!player->is_pushing)
13962 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13965 player->is_pushing = TRUE;
13966 player->is_active = TRUE;
13968 if (!(IN_LEV_FIELD(nextx, nexty) &&
13969 (IS_FREE(nextx, nexty) ||
13970 (IS_SB_ELEMENT(element) &&
13971 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
13972 (IS_CUSTOM_ELEMENT(element) &&
13973 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
13974 return MP_NO_ACTION;
13976 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
13977 return MP_NO_ACTION;
13979 if (player->push_delay == -1) // new pushing; restart delay
13980 player->push_delay = 0;
13982 if (player->push_delay < player->push_delay_value &&
13983 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
13984 element != EL_SPRING && element != EL_BALLOON)
13986 // make sure that there is no move delay before next try to push
13987 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13988 player->move_delay = 0;
13990 return MP_NO_ACTION;
13993 if (IS_CUSTOM_ELEMENT(element) &&
13994 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
13996 if (!DigFieldByCE(nextx, nexty, element))
13997 return MP_NO_ACTION;
14000 if (IS_SB_ELEMENT(element))
14002 boolean sokoban_task_solved = FALSE;
14004 if (element == EL_SOKOBAN_FIELD_FULL)
14006 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14008 IncrementSokobanFieldsNeeded();
14009 IncrementSokobanObjectsNeeded();
14012 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14014 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14016 DecrementSokobanFieldsNeeded();
14017 DecrementSokobanObjectsNeeded();
14019 // sokoban object was pushed from empty field to sokoban field
14020 if (Back[x][y] == EL_EMPTY)
14021 sokoban_task_solved = TRUE;
14024 Feld[x][y] = EL_SOKOBAN_OBJECT;
14026 if (Back[x][y] == Back[nextx][nexty])
14027 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14028 else if (Back[x][y] != 0)
14029 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14032 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14035 if (sokoban_task_solved &&
14036 game.sokoban_fields_still_needed == 0 &&
14037 game.sokoban_objects_still_needed == 0 &&
14038 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14040 game.players_still_needed = 0;
14044 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14048 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14050 InitMovingField(x, y, move_direction);
14051 GfxAction[x][y] = ACTION_PUSHING;
14053 if (mode == DF_SNAP)
14054 ContinueMoving(x, y);
14056 MovPos[x][y] = (dx != 0 ? dx : dy);
14058 Pushed[x][y] = TRUE;
14059 Pushed[nextx][nexty] = TRUE;
14061 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14062 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14064 player->push_delay_value = -1; // get new value later
14066 // check for element change _after_ element has been pushed
14067 if (game.use_change_when_pushing_bug)
14069 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14070 player->index_bit, dig_side);
14071 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14072 player->index_bit, dig_side);
14075 else if (IS_SWITCHABLE(element))
14077 if (PLAYER_SWITCHING(player, x, y))
14079 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14080 player->index_bit, dig_side);
14085 player->is_switching = TRUE;
14086 player->switch_x = x;
14087 player->switch_y = y;
14089 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14091 if (element == EL_ROBOT_WHEEL)
14093 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14095 game.robot_wheel_x = x;
14096 game.robot_wheel_y = y;
14097 game.robot_wheel_active = TRUE;
14099 TEST_DrawLevelField(x, y);
14101 else if (element == EL_SP_TERMINAL)
14105 SCAN_PLAYFIELD(xx, yy)
14107 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14111 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14113 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14115 ResetGfxAnimation(xx, yy);
14116 TEST_DrawLevelField(xx, yy);
14120 else if (IS_BELT_SWITCH(element))
14122 ToggleBeltSwitch(x, y);
14124 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14125 element == EL_SWITCHGATE_SWITCH_DOWN ||
14126 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14127 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14129 ToggleSwitchgateSwitch(x, y);
14131 else if (element == EL_LIGHT_SWITCH ||
14132 element == EL_LIGHT_SWITCH_ACTIVE)
14134 ToggleLightSwitch(x, y);
14136 else if (element == EL_TIMEGATE_SWITCH ||
14137 element == EL_DC_TIMEGATE_SWITCH)
14139 ActivateTimegateSwitch(x, y);
14141 else if (element == EL_BALLOON_SWITCH_LEFT ||
14142 element == EL_BALLOON_SWITCH_RIGHT ||
14143 element == EL_BALLOON_SWITCH_UP ||
14144 element == EL_BALLOON_SWITCH_DOWN ||
14145 element == EL_BALLOON_SWITCH_NONE ||
14146 element == EL_BALLOON_SWITCH_ANY)
14148 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14149 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14150 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14151 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14152 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14155 else if (element == EL_LAMP)
14157 Feld[x][y] = EL_LAMP_ACTIVE;
14158 game.lights_still_needed--;
14160 ResetGfxAnimation(x, y);
14161 TEST_DrawLevelField(x, y);
14163 else if (element == EL_TIME_ORB_FULL)
14165 Feld[x][y] = EL_TIME_ORB_EMPTY;
14167 if (level.time > 0 || level.use_time_orb_bug)
14169 TimeLeft += level.time_orb_time;
14170 game.no_time_limit = FALSE;
14172 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14174 DisplayGameControlValues();
14177 ResetGfxAnimation(x, y);
14178 TEST_DrawLevelField(x, y);
14180 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14181 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14185 game.ball_state = !game.ball_state;
14187 SCAN_PLAYFIELD(xx, yy)
14189 int e = Feld[xx][yy];
14191 if (game.ball_state)
14193 if (e == EL_EMC_MAGIC_BALL)
14194 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14195 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14196 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14200 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14201 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14202 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14203 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14208 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14209 player->index_bit, dig_side);
14211 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14212 player->index_bit, dig_side);
14214 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14215 player->index_bit, dig_side);
14221 if (!PLAYER_SWITCHING(player, x, y))
14223 player->is_switching = TRUE;
14224 player->switch_x = x;
14225 player->switch_y = y;
14227 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14228 player->index_bit, dig_side);
14229 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14230 player->index_bit, dig_side);
14232 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14233 player->index_bit, dig_side);
14234 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14235 player->index_bit, dig_side);
14238 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14239 player->index_bit, dig_side);
14240 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14241 player->index_bit, dig_side);
14243 return MP_NO_ACTION;
14246 player->push_delay = -1;
14248 if (is_player) // function can also be called by EL_PENGUIN
14250 if (Feld[x][y] != element) // really digged/collected something
14252 player->is_collecting = !player->is_digging;
14253 player->is_active = TRUE;
14260 static boolean DigFieldByCE(int x, int y, int digging_element)
14262 int element = Feld[x][y];
14264 if (!IS_FREE(x, y))
14266 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14267 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14270 // no element can dig solid indestructible elements
14271 if (IS_INDESTRUCTIBLE(element) &&
14272 !IS_DIGGABLE(element) &&
14273 !IS_COLLECTIBLE(element))
14276 if (AmoebaNr[x][y] &&
14277 (element == EL_AMOEBA_FULL ||
14278 element == EL_BD_AMOEBA ||
14279 element == EL_AMOEBA_GROWING))
14281 AmoebaCnt[AmoebaNr[x][y]]--;
14282 AmoebaCnt2[AmoebaNr[x][y]]--;
14285 if (IS_MOVING(x, y))
14286 RemoveMovingField(x, y);
14290 TEST_DrawLevelField(x, y);
14293 // if digged element was about to explode, prevent the explosion
14294 ExplodeField[x][y] = EX_TYPE_NONE;
14296 PlayLevelSoundAction(x, y, action);
14299 Store[x][y] = EL_EMPTY;
14301 // this makes it possible to leave the removed element again
14302 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14303 Store[x][y] = element;
14308 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14310 int jx = player->jx, jy = player->jy;
14311 int x = jx + dx, y = jy + dy;
14312 int snap_direction = (dx == -1 ? MV_LEFT :
14313 dx == +1 ? MV_RIGHT :
14315 dy == +1 ? MV_DOWN : MV_NONE);
14316 boolean can_continue_snapping = (level.continuous_snapping &&
14317 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14319 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14322 if (!player->active || !IN_LEV_FIELD(x, y))
14330 if (player->MovPos == 0)
14331 player->is_pushing = FALSE;
14333 player->is_snapping = FALSE;
14335 if (player->MovPos == 0)
14337 player->is_moving = FALSE;
14338 player->is_digging = FALSE;
14339 player->is_collecting = FALSE;
14345 // prevent snapping with already pressed snap key when not allowed
14346 if (player->is_snapping && !can_continue_snapping)
14349 player->MovDir = snap_direction;
14351 if (player->MovPos == 0)
14353 player->is_moving = FALSE;
14354 player->is_digging = FALSE;
14355 player->is_collecting = FALSE;
14358 player->is_dropping = FALSE;
14359 player->is_dropping_pressed = FALSE;
14360 player->drop_pressed_delay = 0;
14362 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14365 player->is_snapping = TRUE;
14366 player->is_active = TRUE;
14368 if (player->MovPos == 0)
14370 player->is_moving = FALSE;
14371 player->is_digging = FALSE;
14372 player->is_collecting = FALSE;
14375 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14376 TEST_DrawLevelField(player->last_jx, player->last_jy);
14378 TEST_DrawLevelField(x, y);
14383 static boolean DropElement(struct PlayerInfo *player)
14385 int old_element, new_element;
14386 int dropx = player->jx, dropy = player->jy;
14387 int drop_direction = player->MovDir;
14388 int drop_side = drop_direction;
14389 int drop_element = get_next_dropped_element(player);
14391 /* do not drop an element on top of another element; when holding drop key
14392 pressed without moving, dropped element must move away before the next
14393 element can be dropped (this is especially important if the next element
14394 is dynamite, which can be placed on background for historical reasons) */
14395 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14398 if (IS_THROWABLE(drop_element))
14400 dropx += GET_DX_FROM_DIR(drop_direction);
14401 dropy += GET_DY_FROM_DIR(drop_direction);
14403 if (!IN_LEV_FIELD(dropx, dropy))
14407 old_element = Feld[dropx][dropy]; // old element at dropping position
14408 new_element = drop_element; // default: no change when dropping
14410 // check if player is active, not moving and ready to drop
14411 if (!player->active || player->MovPos || player->drop_delay > 0)
14414 // check if player has anything that can be dropped
14415 if (new_element == EL_UNDEFINED)
14418 // only set if player has anything that can be dropped
14419 player->is_dropping_pressed = TRUE;
14421 // check if drop key was pressed long enough for EM style dynamite
14422 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14425 // check if anything can be dropped at the current position
14426 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14429 // collected custom elements can only be dropped on empty fields
14430 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14433 if (old_element != EL_EMPTY)
14434 Back[dropx][dropy] = old_element; // store old element on this field
14436 ResetGfxAnimation(dropx, dropy);
14437 ResetRandomAnimationValue(dropx, dropy);
14439 if (player->inventory_size > 0 ||
14440 player->inventory_infinite_element != EL_UNDEFINED)
14442 if (player->inventory_size > 0)
14444 player->inventory_size--;
14446 DrawGameDoorValues();
14448 if (new_element == EL_DYNAMITE)
14449 new_element = EL_DYNAMITE_ACTIVE;
14450 else if (new_element == EL_EM_DYNAMITE)
14451 new_element = EL_EM_DYNAMITE_ACTIVE;
14452 else if (new_element == EL_SP_DISK_RED)
14453 new_element = EL_SP_DISK_RED_ACTIVE;
14456 Feld[dropx][dropy] = new_element;
14458 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14459 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14460 el2img(Feld[dropx][dropy]), 0);
14462 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14464 // needed if previous element just changed to "empty" in the last frame
14465 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14467 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14468 player->index_bit, drop_side);
14469 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14471 player->index_bit, drop_side);
14473 TestIfElementTouchesCustomElement(dropx, dropy);
14475 else // player is dropping a dyna bomb
14477 player->dynabombs_left--;
14479 Feld[dropx][dropy] = new_element;
14481 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14482 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14483 el2img(Feld[dropx][dropy]), 0);
14485 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14488 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14489 InitField_WithBug1(dropx, dropy, FALSE);
14491 new_element = Feld[dropx][dropy]; // element might have changed
14493 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14494 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14496 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14497 MovDir[dropx][dropy] = drop_direction;
14499 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14501 // do not cause impact style collision by dropping elements that can fall
14502 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14505 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14506 player->is_dropping = TRUE;
14508 player->drop_pressed_delay = 0;
14509 player->is_dropping_pressed = FALSE;
14511 player->drop_x = dropx;
14512 player->drop_y = dropy;
14517 // ----------------------------------------------------------------------------
14518 // game sound playing functions
14519 // ----------------------------------------------------------------------------
14521 static int *loop_sound_frame = NULL;
14522 static int *loop_sound_volume = NULL;
14524 void InitPlayLevelSound(void)
14526 int num_sounds = getSoundListSize();
14528 checked_free(loop_sound_frame);
14529 checked_free(loop_sound_volume);
14531 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14532 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14535 static void PlayLevelSound(int x, int y, int nr)
14537 int sx = SCREENX(x), sy = SCREENY(y);
14538 int volume, stereo_position;
14539 int max_distance = 8;
14540 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14542 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14543 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14546 if (!IN_LEV_FIELD(x, y) ||
14547 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14548 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14551 volume = SOUND_MAX_VOLUME;
14553 if (!IN_SCR_FIELD(sx, sy))
14555 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14556 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14558 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14561 stereo_position = (SOUND_MAX_LEFT +
14562 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14563 (SCR_FIELDX + 2 * max_distance));
14565 if (IS_LOOP_SOUND(nr))
14567 /* This assures that quieter loop sounds do not overwrite louder ones,
14568 while restarting sound volume comparison with each new game frame. */
14570 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14573 loop_sound_volume[nr] = volume;
14574 loop_sound_frame[nr] = FrameCounter;
14577 PlaySoundExt(nr, volume, stereo_position, type);
14580 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14582 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14583 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14584 y < LEVELY(BY1) ? LEVELY(BY1) :
14585 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14589 static void PlayLevelSoundAction(int x, int y, int action)
14591 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14594 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14596 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14598 if (sound_effect != SND_UNDEFINED)
14599 PlayLevelSound(x, y, sound_effect);
14602 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14605 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14607 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14608 PlayLevelSound(x, y, sound_effect);
14611 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14613 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14615 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14616 PlayLevelSound(x, y, sound_effect);
14619 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14621 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14623 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14624 StopSound(sound_effect);
14627 static int getLevelMusicNr(void)
14629 if (levelset.music[level_nr] != MUS_UNDEFINED)
14630 return levelset.music[level_nr]; // from config file
14632 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14635 static void FadeLevelSounds(void)
14640 static void FadeLevelMusic(void)
14642 int music_nr = getLevelMusicNr();
14643 char *curr_music = getCurrentlyPlayingMusicFilename();
14644 char *next_music = getMusicInfoEntryFilename(music_nr);
14646 if (!strEqual(curr_music, next_music))
14650 void FadeLevelSoundsAndMusic(void)
14656 static void PlayLevelMusic(void)
14658 int music_nr = getLevelMusicNr();
14659 char *curr_music = getCurrentlyPlayingMusicFilename();
14660 char *next_music = getMusicInfoEntryFilename(music_nr);
14662 if (!strEqual(curr_music, next_music))
14663 PlayMusicLoop(music_nr);
14666 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14668 int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0);
14669 int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
14670 int x = xx - 1 - offset;
14671 int y = yy - 1 - offset;
14676 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14680 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14684 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14688 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14692 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14696 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14700 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14703 case SAMPLE_android_clone:
14704 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14707 case SAMPLE_android_move:
14708 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14711 case SAMPLE_spring:
14712 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14716 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14720 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14723 case SAMPLE_eater_eat:
14724 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14728 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14731 case SAMPLE_collect:
14732 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14735 case SAMPLE_diamond:
14736 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14739 case SAMPLE_squash:
14740 // !!! CHECK THIS !!!
14742 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14744 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14748 case SAMPLE_wonderfall:
14749 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14753 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14757 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14761 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14765 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14769 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14773 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14776 case SAMPLE_wonder:
14777 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14781 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14784 case SAMPLE_exit_open:
14785 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14788 case SAMPLE_exit_leave:
14789 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14792 case SAMPLE_dynamite:
14793 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14797 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14801 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14805 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14809 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14813 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
14817 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
14821 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
14826 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
14828 int element = map_element_SP_to_RND(element_sp);
14829 int action = map_action_SP_to_RND(action_sp);
14830 int offset = (setup.sp_show_border_elements ? 0 : 1);
14831 int x = xx - offset;
14832 int y = yy - offset;
14834 PlayLevelSoundElementAction(x, y, element, action);
14837 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
14839 int element = map_element_MM_to_RND(element_mm);
14840 int action = map_action_MM_to_RND(action_mm);
14842 int x = xx - offset;
14843 int y = yy - offset;
14845 if (!IS_MM_ELEMENT(element))
14846 element = EL_MM_DEFAULT;
14848 PlayLevelSoundElementAction(x, y, element, action);
14851 void PlaySound_MM(int sound_mm)
14853 int sound = map_sound_MM_to_RND(sound_mm);
14855 if (sound == SND_UNDEFINED)
14861 void PlaySoundLoop_MM(int sound_mm)
14863 int sound = map_sound_MM_to_RND(sound_mm);
14865 if (sound == SND_UNDEFINED)
14868 PlaySoundLoop(sound);
14871 void StopSound_MM(int sound_mm)
14873 int sound = map_sound_MM_to_RND(sound_mm);
14875 if (sound == SND_UNDEFINED)
14881 void RaiseScore(int value)
14883 game.score += value;
14885 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
14887 DisplayGameControlValues();
14890 void RaiseScoreElement(int element)
14895 case EL_BD_DIAMOND:
14896 case EL_EMERALD_YELLOW:
14897 case EL_EMERALD_RED:
14898 case EL_EMERALD_PURPLE:
14899 case EL_SP_INFOTRON:
14900 RaiseScore(level.score[SC_EMERALD]);
14903 RaiseScore(level.score[SC_DIAMOND]);
14906 RaiseScore(level.score[SC_CRYSTAL]);
14909 RaiseScore(level.score[SC_PEARL]);
14912 case EL_BD_BUTTERFLY:
14913 case EL_SP_ELECTRON:
14914 RaiseScore(level.score[SC_BUG]);
14917 case EL_BD_FIREFLY:
14918 case EL_SP_SNIKSNAK:
14919 RaiseScore(level.score[SC_SPACESHIP]);
14922 case EL_DARK_YAMYAM:
14923 RaiseScore(level.score[SC_YAMYAM]);
14926 RaiseScore(level.score[SC_ROBOT]);
14929 RaiseScore(level.score[SC_PACMAN]);
14932 RaiseScore(level.score[SC_NUT]);
14935 case EL_EM_DYNAMITE:
14936 case EL_SP_DISK_RED:
14937 case EL_DYNABOMB_INCREASE_NUMBER:
14938 case EL_DYNABOMB_INCREASE_SIZE:
14939 case EL_DYNABOMB_INCREASE_POWER:
14940 RaiseScore(level.score[SC_DYNAMITE]);
14942 case EL_SHIELD_NORMAL:
14943 case EL_SHIELD_DEADLY:
14944 RaiseScore(level.score[SC_SHIELD]);
14946 case EL_EXTRA_TIME:
14947 RaiseScore(level.extra_time_score);
14961 case EL_DC_KEY_WHITE:
14962 RaiseScore(level.score[SC_KEY]);
14965 RaiseScore(element_info[element].collect_score);
14970 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
14972 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
14974 // closing door required in case of envelope style request dialogs
14976 CloseDoor(DOOR_CLOSE_1);
14978 if (network.enabled)
14979 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
14983 FadeSkipNextFadeIn();
14985 SetGameStatus(GAME_MODE_MAIN);
14990 else // continue playing the game
14992 if (tape.playing && tape.deactivate_display)
14993 TapeDeactivateDisplayOff(TRUE);
14995 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
14997 if (tape.playing && tape.deactivate_display)
14998 TapeDeactivateDisplayOn();
15002 void RequestQuitGame(boolean ask_if_really_quit)
15004 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15005 boolean skip_request = game.all_players_gone || quick_quit;
15007 RequestQuitGameExt(skip_request, quick_quit,
15008 "Do you really want to quit the game?");
15011 void RequestRestartGame(char *message)
15013 game.restart_game_message = NULL;
15015 boolean has_started_game = hasStartedNetworkGame();
15016 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15018 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15020 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15024 SetGameStatus(GAME_MODE_MAIN);
15030 void CheckGameOver(void)
15032 static boolean last_game_over = FALSE;
15033 static int game_over_delay = 0;
15034 int game_over_delay_value = 50;
15035 boolean game_over = checkGameFailed();
15037 // do not handle game over if request dialog is already active
15038 if (game.request_active)
15041 // do not ask to play again if game was never actually played
15042 if (!game.GamePlayed)
15047 last_game_over = FALSE;
15048 game_over_delay = game_over_delay_value;
15053 if (game_over_delay > 0)
15060 if (last_game_over != game_over)
15061 game.restart_game_message = (hasStartedNetworkGame() ?
15062 "Game over! Play it again?" :
15065 last_game_over = game_over;
15068 boolean checkGameSolved(void)
15070 // set for all game engines if level was solved
15071 return game.LevelSolved_GameEnd;
15074 boolean checkGameFailed(void)
15076 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15077 return (game_em.game_over && !game_em.level_solved);
15078 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15079 return (game_sp.game_over && !game_sp.level_solved);
15080 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15081 return (game_mm.game_over && !game_mm.level_solved);
15082 else // GAME_ENGINE_TYPE_RND
15083 return (game.GameOver && !game.LevelSolved);
15086 boolean checkGameEnded(void)
15088 return (checkGameSolved() || checkGameFailed());
15092 // ----------------------------------------------------------------------------
15093 // random generator functions
15094 // ----------------------------------------------------------------------------
15096 unsigned int InitEngineRandom_RND(int seed)
15098 game.num_random_calls = 0;
15100 return InitEngineRandom(seed);
15103 unsigned int RND(int max)
15107 game.num_random_calls++;
15109 return GetEngineRandom(max);
15116 // ----------------------------------------------------------------------------
15117 // game engine snapshot handling functions
15118 // ----------------------------------------------------------------------------
15120 struct EngineSnapshotInfo
15122 // runtime values for custom element collect score
15123 int collect_score[NUM_CUSTOM_ELEMENTS];
15125 // runtime values for group element choice position
15126 int choice_pos[NUM_GROUP_ELEMENTS];
15128 // runtime values for belt position animations
15129 int belt_graphic[4][NUM_BELT_PARTS];
15130 int belt_anim_mode[4][NUM_BELT_PARTS];
15133 static struct EngineSnapshotInfo engine_snapshot_rnd;
15134 static char *snapshot_level_identifier = NULL;
15135 static int snapshot_level_nr = -1;
15137 static void SaveEngineSnapshotValues_RND(void)
15139 static int belt_base_active_element[4] =
15141 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15142 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15143 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15144 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15148 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15150 int element = EL_CUSTOM_START + i;
15152 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15155 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15157 int element = EL_GROUP_START + i;
15159 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15162 for (i = 0; i < 4; i++)
15164 for (j = 0; j < NUM_BELT_PARTS; j++)
15166 int element = belt_base_active_element[i] + j;
15167 int graphic = el2img(element);
15168 int anim_mode = graphic_info[graphic].anim_mode;
15170 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15171 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15176 static void LoadEngineSnapshotValues_RND(void)
15178 unsigned int num_random_calls = game.num_random_calls;
15181 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15183 int element = EL_CUSTOM_START + i;
15185 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15188 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15190 int element = EL_GROUP_START + i;
15192 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15195 for (i = 0; i < 4; i++)
15197 for (j = 0; j < NUM_BELT_PARTS; j++)
15199 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15200 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15202 graphic_info[graphic].anim_mode = anim_mode;
15206 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15208 InitRND(tape.random_seed);
15209 for (i = 0; i < num_random_calls; i++)
15213 if (game.num_random_calls != num_random_calls)
15215 Error(ERR_INFO, "number of random calls out of sync");
15216 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15217 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15218 Error(ERR_EXIT, "this should not happen -- please debug");
15222 void FreeEngineSnapshotSingle(void)
15224 FreeSnapshotSingle();
15226 setString(&snapshot_level_identifier, NULL);
15227 snapshot_level_nr = -1;
15230 void FreeEngineSnapshotList(void)
15232 FreeSnapshotList();
15235 static ListNode *SaveEngineSnapshotBuffers(void)
15237 ListNode *buffers = NULL;
15239 // copy some special values to a structure better suited for the snapshot
15241 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15242 SaveEngineSnapshotValues_RND();
15243 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15244 SaveEngineSnapshotValues_EM();
15245 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15246 SaveEngineSnapshotValues_SP(&buffers);
15247 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15248 SaveEngineSnapshotValues_MM(&buffers);
15250 // save values stored in special snapshot structure
15252 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15253 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15254 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15255 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15256 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15257 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15258 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15259 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15261 // save further RND engine values
15263 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15264 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15265 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15267 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15268 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15269 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15270 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15271 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15273 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15274 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15275 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15277 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15279 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15280 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15282 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15283 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15284 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15285 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15286 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15287 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15288 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15289 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15290 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15291 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15292 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15293 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15294 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15295 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15296 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15297 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15298 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15299 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15301 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15302 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15304 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15305 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15306 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15308 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15309 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15311 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15312 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15313 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15314 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15315 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15317 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15318 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15321 ListNode *node = engine_snapshot_list_rnd;
15324 while (node != NULL)
15326 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15331 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15337 void SaveEngineSnapshotSingle(void)
15339 ListNode *buffers = SaveEngineSnapshotBuffers();
15341 // finally save all snapshot buffers to single snapshot
15342 SaveSnapshotSingle(buffers);
15344 // save level identification information
15345 setString(&snapshot_level_identifier, leveldir_current->identifier);
15346 snapshot_level_nr = level_nr;
15349 boolean CheckSaveEngineSnapshotToList(void)
15351 boolean save_snapshot =
15352 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15353 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15354 game.snapshot.changed_action) ||
15355 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15356 game.snapshot.collected_item));
15358 game.snapshot.changed_action = FALSE;
15359 game.snapshot.collected_item = FALSE;
15360 game.snapshot.save_snapshot = save_snapshot;
15362 return save_snapshot;
15365 void SaveEngineSnapshotToList(void)
15367 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15371 ListNode *buffers = SaveEngineSnapshotBuffers();
15373 // finally save all snapshot buffers to snapshot list
15374 SaveSnapshotToList(buffers);
15377 void SaveEngineSnapshotToListInitial(void)
15379 FreeEngineSnapshotList();
15381 SaveEngineSnapshotToList();
15384 static void LoadEngineSnapshotValues(void)
15386 // restore special values from snapshot structure
15388 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15389 LoadEngineSnapshotValues_RND();
15390 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15391 LoadEngineSnapshotValues_EM();
15392 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15393 LoadEngineSnapshotValues_SP();
15394 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15395 LoadEngineSnapshotValues_MM();
15398 void LoadEngineSnapshotSingle(void)
15400 LoadSnapshotSingle();
15402 LoadEngineSnapshotValues();
15405 static void LoadEngineSnapshot_Undo(int steps)
15407 LoadSnapshotFromList_Older(steps);
15409 LoadEngineSnapshotValues();
15412 static void LoadEngineSnapshot_Redo(int steps)
15414 LoadSnapshotFromList_Newer(steps);
15416 LoadEngineSnapshotValues();
15419 boolean CheckEngineSnapshotSingle(void)
15421 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15422 snapshot_level_nr == level_nr);
15425 boolean CheckEngineSnapshotList(void)
15427 return CheckSnapshotList();
15431 // ---------- new game button stuff -------------------------------------------
15438 boolean *setup_value;
15439 boolean allowed_on_tape;
15441 } gamebutton_info[NUM_GAME_BUTTONS] =
15444 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15445 GAME_CTRL_ID_STOP, NULL,
15449 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15450 GAME_CTRL_ID_PAUSE, NULL,
15454 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15455 GAME_CTRL_ID_PLAY, NULL,
15459 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15460 GAME_CTRL_ID_UNDO, NULL,
15464 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15465 GAME_CTRL_ID_REDO, NULL,
15469 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15470 GAME_CTRL_ID_SAVE, NULL,
15474 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15475 GAME_CTRL_ID_PAUSE2, NULL,
15479 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15480 GAME_CTRL_ID_LOAD, NULL,
15484 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15485 GAME_CTRL_ID_PANEL_STOP, NULL,
15489 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15490 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15491 FALSE, "pause game"
15494 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15495 GAME_CTRL_ID_PANEL_PLAY, NULL,
15499 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15500 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15501 TRUE, "background music on/off"
15504 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15505 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15506 TRUE, "sound loops on/off"
15509 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15510 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15511 TRUE, "normal sounds on/off"
15514 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15515 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15516 FALSE, "background music on/off"
15519 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15520 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15521 FALSE, "sound loops on/off"
15524 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15525 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15526 FALSE, "normal sounds on/off"
15530 void CreateGameButtons(void)
15534 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15536 int graphic = gamebutton_info[i].graphic;
15537 struct GraphicInfo *gfx = &graphic_info[graphic];
15538 struct XY *pos = gamebutton_info[i].pos;
15539 struct GadgetInfo *gi;
15542 unsigned int event_mask;
15543 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15544 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15545 int base_x = (on_tape ? VX : DX);
15546 int base_y = (on_tape ? VY : DY);
15547 int gd_x = gfx->src_x;
15548 int gd_y = gfx->src_y;
15549 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15550 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15551 int gd_xa = gfx->src_x + gfx->active_xoffset;
15552 int gd_ya = gfx->src_y + gfx->active_yoffset;
15553 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15554 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15557 if (gfx->bitmap == NULL)
15559 game_gadget[id] = NULL;
15564 if (id == GAME_CTRL_ID_STOP ||
15565 id == GAME_CTRL_ID_PANEL_STOP ||
15566 id == GAME_CTRL_ID_PLAY ||
15567 id == GAME_CTRL_ID_PANEL_PLAY ||
15568 id == GAME_CTRL_ID_SAVE ||
15569 id == GAME_CTRL_ID_LOAD)
15571 button_type = GD_TYPE_NORMAL_BUTTON;
15573 event_mask = GD_EVENT_RELEASED;
15575 else if (id == GAME_CTRL_ID_UNDO ||
15576 id == GAME_CTRL_ID_REDO)
15578 button_type = GD_TYPE_NORMAL_BUTTON;
15580 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15584 button_type = GD_TYPE_CHECK_BUTTON;
15585 checked = (gamebutton_info[i].setup_value != NULL ?
15586 *gamebutton_info[i].setup_value : FALSE);
15587 event_mask = GD_EVENT_PRESSED;
15590 gi = CreateGadget(GDI_CUSTOM_ID, id,
15591 GDI_IMAGE_ID, graphic,
15592 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15593 GDI_X, base_x + GDI_ACTIVE_POS(pos->x),
15594 GDI_Y, base_y + GDI_ACTIVE_POS(pos->y),
15595 GDI_WIDTH, gfx->width,
15596 GDI_HEIGHT, gfx->height,
15597 GDI_TYPE, button_type,
15598 GDI_STATE, GD_BUTTON_UNPRESSED,
15599 GDI_CHECKED, checked,
15600 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15601 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15602 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15603 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15604 GDI_DIRECT_DRAW, FALSE,
15605 GDI_EVENT_MASK, event_mask,
15606 GDI_CALLBACK_ACTION, HandleGameButtons,
15610 Error(ERR_EXIT, "cannot create gadget");
15612 game_gadget[id] = gi;
15616 void FreeGameButtons(void)
15620 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15621 FreeGadget(game_gadget[i]);
15624 static void UnmapGameButtonsAtSamePosition(int id)
15628 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15630 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15631 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15632 UnmapGadget(game_gadget[i]);
15635 static void UnmapGameButtonsAtSamePosition_All(void)
15637 if (setup.show_snapshot_buttons)
15639 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15640 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15641 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15645 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15646 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15647 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15649 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15650 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15651 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15655 static void MapGameButtonsAtSamePosition(int id)
15659 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15661 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15662 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15663 MapGadget(game_gadget[i]);
15665 UnmapGameButtonsAtSamePosition_All();
15668 void MapUndoRedoButtons(void)
15670 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15671 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15673 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15674 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15676 ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, TRUE, GDI_END);
15679 void UnmapUndoRedoButtons(void)
15681 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15682 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15684 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15685 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15687 ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, FALSE, GDI_END);
15690 static void MapGameButtonsExt(boolean on_tape)
15694 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15695 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15696 i != GAME_CTRL_ID_UNDO &&
15697 i != GAME_CTRL_ID_REDO)
15698 MapGadget(game_gadget[i]);
15700 UnmapGameButtonsAtSamePosition_All();
15702 RedrawGameButtons();
15705 static void UnmapGameButtonsExt(boolean on_tape)
15709 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15710 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15711 UnmapGadget(game_gadget[i]);
15714 static void RedrawGameButtonsExt(boolean on_tape)
15718 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15719 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15720 RedrawGadget(game_gadget[i]);
15722 // RedrawGadget() may have set REDRAW_ALL if buttons are defined off-area
15723 redraw_mask &= ~REDRAW_ALL;
15726 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15731 gi->checked = state;
15734 static void RedrawSoundButtonGadget(int id)
15736 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15737 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15738 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15739 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15740 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15741 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15744 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15745 RedrawGadget(game_gadget[id2]);
15748 void MapGameButtons(void)
15750 MapGameButtonsExt(FALSE);
15753 void UnmapGameButtons(void)
15755 UnmapGameButtonsExt(FALSE);
15758 void RedrawGameButtons(void)
15760 RedrawGameButtonsExt(FALSE);
15763 void MapGameButtonsOnTape(void)
15765 MapGameButtonsExt(TRUE);
15768 void UnmapGameButtonsOnTape(void)
15770 UnmapGameButtonsExt(TRUE);
15773 void RedrawGameButtonsOnTape(void)
15775 RedrawGameButtonsExt(TRUE);
15778 static void GameUndoRedoExt(void)
15780 ClearPlayerAction();
15782 tape.pausing = TRUE;
15785 UpdateAndDisplayGameControlValues();
15787 DrawCompleteVideoDisplay();
15788 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
15789 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
15790 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
15795 static void GameUndo(int steps)
15797 if (!CheckEngineSnapshotList())
15800 LoadEngineSnapshot_Undo(steps);
15805 static void GameRedo(int steps)
15807 if (!CheckEngineSnapshotList())
15810 LoadEngineSnapshot_Redo(steps);
15815 static void HandleGameButtonsExt(int id, int button)
15817 static boolean game_undo_executed = FALSE;
15818 int steps = BUTTON_STEPSIZE(button);
15819 boolean handle_game_buttons =
15820 (game_status == GAME_MODE_PLAYING ||
15821 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
15823 if (!handle_game_buttons)
15828 case GAME_CTRL_ID_STOP:
15829 case GAME_CTRL_ID_PANEL_STOP:
15830 if (game_status == GAME_MODE_MAIN)
15836 RequestQuitGame(TRUE);
15840 case GAME_CTRL_ID_PAUSE:
15841 case GAME_CTRL_ID_PAUSE2:
15842 case GAME_CTRL_ID_PANEL_PAUSE:
15843 if (network.enabled && game_status == GAME_MODE_PLAYING)
15846 SendToServer_ContinuePlaying();
15848 SendToServer_PausePlaying();
15851 TapeTogglePause(TAPE_TOGGLE_MANUAL);
15853 game_undo_executed = FALSE;
15857 case GAME_CTRL_ID_PLAY:
15858 case GAME_CTRL_ID_PANEL_PLAY:
15859 if (game_status == GAME_MODE_MAIN)
15861 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15863 else if (tape.pausing)
15865 if (network.enabled)
15866 SendToServer_ContinuePlaying();
15868 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
15872 case GAME_CTRL_ID_UNDO:
15873 // Important: When using "save snapshot when collecting an item" mode,
15874 // load last (current) snapshot for first "undo" after pressing "pause"
15875 // (else the last-but-one snapshot would be loaded, because the snapshot
15876 // pointer already points to the last snapshot when pressing "pause",
15877 // which is fine for "every step/move" mode, but not for "every collect")
15878 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15879 !game_undo_executed)
15882 game_undo_executed = TRUE;
15887 case GAME_CTRL_ID_REDO:
15891 case GAME_CTRL_ID_SAVE:
15895 case GAME_CTRL_ID_LOAD:
15899 case SOUND_CTRL_ID_MUSIC:
15900 case SOUND_CTRL_ID_PANEL_MUSIC:
15901 if (setup.sound_music)
15903 setup.sound_music = FALSE;
15907 else if (audio.music_available)
15909 setup.sound = setup.sound_music = TRUE;
15911 SetAudioMode(setup.sound);
15913 if (game_status == GAME_MODE_PLAYING)
15917 RedrawSoundButtonGadget(id);
15921 case SOUND_CTRL_ID_LOOPS:
15922 case SOUND_CTRL_ID_PANEL_LOOPS:
15923 if (setup.sound_loops)
15924 setup.sound_loops = FALSE;
15925 else if (audio.loops_available)
15927 setup.sound = setup.sound_loops = TRUE;
15929 SetAudioMode(setup.sound);
15932 RedrawSoundButtonGadget(id);
15936 case SOUND_CTRL_ID_SIMPLE:
15937 case SOUND_CTRL_ID_PANEL_SIMPLE:
15938 if (setup.sound_simple)
15939 setup.sound_simple = FALSE;
15940 else if (audio.sound_available)
15942 setup.sound = setup.sound_simple = TRUE;
15944 SetAudioMode(setup.sound);
15947 RedrawSoundButtonGadget(id);
15956 static void HandleGameButtons(struct GadgetInfo *gi)
15958 HandleGameButtonsExt(gi->custom_id, gi->event.button);
15961 void HandleSoundButtonKeys(Key key)
15963 if (key == setup.shortcut.sound_simple)
15964 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
15965 else if (key == setup.shortcut.sound_loops)
15966 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
15967 else if (key == setup.shortcut.sound_music)
15968 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);