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 GAME_CTRL_ID_TOUCH_STOP 11
1020 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1021 #define SOUND_CTRL_ID_MUSIC 13
1022 #define SOUND_CTRL_ID_LOOPS 14
1023 #define SOUND_CTRL_ID_SIMPLE 15
1024 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1025 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1026 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1028 #define NUM_GAME_BUTTONS 19
1031 // forward declaration for internal use
1033 static void CreateField(int, int, int);
1035 static void ResetGfxAnimation(int, int);
1037 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1038 static void AdvanceFrameAndPlayerCounters(int);
1040 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1041 static boolean MovePlayer(struct PlayerInfo *, int, int);
1042 static void ScrollPlayer(struct PlayerInfo *, int);
1043 static void ScrollScreen(struct PlayerInfo *, int);
1045 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1046 static boolean DigFieldByCE(int, int, int);
1047 static boolean SnapField(struct PlayerInfo *, int, int);
1048 static boolean DropElement(struct PlayerInfo *);
1050 static void InitBeltMovement(void);
1051 static void CloseAllOpenTimegates(void);
1052 static void CheckGravityMovement(struct PlayerInfo *);
1053 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1054 static void KillPlayerUnlessEnemyProtected(int, int);
1055 static void KillPlayerUnlessExplosionProtected(int, int);
1057 static void TestIfPlayerTouchesCustomElement(int, int);
1058 static void TestIfElementTouchesCustomElement(int, int);
1059 static void TestIfElementHitsCustomElement(int, int, int);
1061 static void HandleElementChange(int, int, int);
1062 static void ExecuteCustomElementAction(int, int, int, int);
1063 static boolean ChangeElement(int, int, int, int);
1065 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1066 #define CheckTriggeredElementChange(x, y, e, ev) \
1067 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1068 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1069 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1070 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1071 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1072 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1073 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1075 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1076 #define CheckElementChange(x, y, e, te, ev) \
1077 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1078 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1079 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1080 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1081 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1083 static void PlayLevelSound(int, int, int);
1084 static void PlayLevelSoundNearest(int, int, int);
1085 static void PlayLevelSoundAction(int, int, int);
1086 static void PlayLevelSoundElementAction(int, int, int, int);
1087 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1088 static void PlayLevelSoundActionIfLoop(int, int, int);
1089 static void StopLevelSoundActionIfLoop(int, int, int);
1090 static void PlayLevelMusic(void);
1091 static void FadeLevelSoundsAndMusic(void);
1093 static void HandleGameButtons(struct GadgetInfo *);
1095 int AmoebeNachbarNr(int, int);
1096 void AmoebeUmwandeln(int, int);
1097 void ContinueMoving(int, int);
1098 void Bang(int, int);
1099 void InitMovDir(int, int);
1100 void InitAmoebaNr(int, int);
1101 int NewHiScore(int);
1103 void TestIfGoodThingHitsBadThing(int, int, int);
1104 void TestIfBadThingHitsGoodThing(int, int, int);
1105 void TestIfPlayerTouchesBadThing(int, int);
1106 void TestIfPlayerRunsIntoBadThing(int, int, int);
1107 void TestIfBadThingTouchesPlayer(int, int);
1108 void TestIfBadThingRunsIntoPlayer(int, int, int);
1109 void TestIfFriendTouchesBadThing(int, int);
1110 void TestIfBadThingTouchesFriend(int, int);
1111 void TestIfBadThingTouchesOtherBadThing(int, int);
1112 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1114 void KillPlayer(struct PlayerInfo *);
1115 void BuryPlayer(struct PlayerInfo *);
1116 void RemovePlayer(struct PlayerInfo *);
1117 void ExitPlayer(struct PlayerInfo *);
1119 static int getInvisibleActiveFromInvisibleElement(int);
1120 static int getInvisibleFromInvisibleActiveElement(int);
1122 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1124 // for detection of endless loops, caused by custom element programming
1125 // (using maximal playfield width x 10 is just a rough approximation)
1126 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1128 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1130 if (recursion_loop_detected) \
1133 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1135 recursion_loop_detected = TRUE; \
1136 recursion_loop_element = (e); \
1139 recursion_loop_depth++; \
1142 #define RECURSION_LOOP_DETECTION_END() \
1144 recursion_loop_depth--; \
1147 static int recursion_loop_depth;
1148 static boolean recursion_loop_detected;
1149 static boolean recursion_loop_element;
1151 static int map_player_action[MAX_PLAYERS];
1154 // ----------------------------------------------------------------------------
1155 // definition of elements that automatically change to other elements after
1156 // a specified time, eventually calling a function when changing
1157 // ----------------------------------------------------------------------------
1159 // forward declaration for changer functions
1160 static void InitBuggyBase(int, int);
1161 static void WarnBuggyBase(int, int);
1163 static void InitTrap(int, int);
1164 static void ActivateTrap(int, int);
1165 static void ChangeActiveTrap(int, int);
1167 static void InitRobotWheel(int, int);
1168 static void RunRobotWheel(int, int);
1169 static void StopRobotWheel(int, int);
1171 static void InitTimegateWheel(int, int);
1172 static void RunTimegateWheel(int, int);
1174 static void InitMagicBallDelay(int, int);
1175 static void ActivateMagicBall(int, int);
1177 struct ChangingElementInfo
1182 void (*pre_change_function)(int x, int y);
1183 void (*change_function)(int x, int y);
1184 void (*post_change_function)(int x, int y);
1187 static struct ChangingElementInfo change_delay_list[] =
1222 EL_STEEL_EXIT_OPENING,
1230 EL_STEEL_EXIT_CLOSING,
1231 EL_STEEL_EXIT_CLOSED,
1254 EL_EM_STEEL_EXIT_OPENING,
1255 EL_EM_STEEL_EXIT_OPEN,
1262 EL_EM_STEEL_EXIT_CLOSING,
1286 EL_SWITCHGATE_OPENING,
1294 EL_SWITCHGATE_CLOSING,
1295 EL_SWITCHGATE_CLOSED,
1302 EL_TIMEGATE_OPENING,
1310 EL_TIMEGATE_CLOSING,
1319 EL_ACID_SPLASH_LEFT,
1327 EL_ACID_SPLASH_RIGHT,
1336 EL_SP_BUGGY_BASE_ACTIVATING,
1343 EL_SP_BUGGY_BASE_ACTIVATING,
1344 EL_SP_BUGGY_BASE_ACTIVE,
1351 EL_SP_BUGGY_BASE_ACTIVE,
1375 EL_ROBOT_WHEEL_ACTIVE,
1383 EL_TIMEGATE_SWITCH_ACTIVE,
1391 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1392 EL_DC_TIMEGATE_SWITCH,
1399 EL_EMC_MAGIC_BALL_ACTIVE,
1400 EL_EMC_MAGIC_BALL_ACTIVE,
1407 EL_EMC_SPRING_BUMPER_ACTIVE,
1408 EL_EMC_SPRING_BUMPER,
1415 EL_DIAGONAL_SHRINKING,
1423 EL_DIAGONAL_GROWING,
1444 int push_delay_fixed, push_delay_random;
1448 { EL_SPRING, 0, 0 },
1449 { EL_BALLOON, 0, 0 },
1451 { EL_SOKOBAN_OBJECT, 2, 0 },
1452 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1453 { EL_SATELLITE, 2, 0 },
1454 { EL_SP_DISK_YELLOW, 2, 0 },
1456 { EL_UNDEFINED, 0, 0 },
1464 move_stepsize_list[] =
1466 { EL_AMOEBA_DROP, 2 },
1467 { EL_AMOEBA_DROPPING, 2 },
1468 { EL_QUICKSAND_FILLING, 1 },
1469 { EL_QUICKSAND_EMPTYING, 1 },
1470 { EL_QUICKSAND_FAST_FILLING, 2 },
1471 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1472 { EL_MAGIC_WALL_FILLING, 2 },
1473 { EL_MAGIC_WALL_EMPTYING, 2 },
1474 { EL_BD_MAGIC_WALL_FILLING, 2 },
1475 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1476 { EL_DC_MAGIC_WALL_FILLING, 2 },
1477 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1479 { EL_UNDEFINED, 0 },
1487 collect_count_list[] =
1490 { EL_BD_DIAMOND, 1 },
1491 { EL_EMERALD_YELLOW, 1 },
1492 { EL_EMERALD_RED, 1 },
1493 { EL_EMERALD_PURPLE, 1 },
1495 { EL_SP_INFOTRON, 1 },
1499 { EL_UNDEFINED, 0 },
1507 access_direction_list[] =
1509 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1510 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1511 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1512 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1513 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1514 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1515 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1516 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1517 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1518 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1519 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1521 { EL_SP_PORT_LEFT, MV_RIGHT },
1522 { EL_SP_PORT_RIGHT, MV_LEFT },
1523 { EL_SP_PORT_UP, MV_DOWN },
1524 { EL_SP_PORT_DOWN, MV_UP },
1525 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1526 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1527 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1528 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1529 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1530 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1531 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1532 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1533 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1534 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1535 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1536 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1537 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1538 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1539 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1541 { EL_UNDEFINED, MV_NONE }
1544 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1546 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1547 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1548 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Feld[x][y]) || \
1549 IS_JUST_CHANGING(x, y))
1551 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1553 // static variables for playfield scan mode (scanning forward or backward)
1554 static int playfield_scan_start_x = 0;
1555 static int playfield_scan_start_y = 0;
1556 static int playfield_scan_delta_x = 1;
1557 static int playfield_scan_delta_y = 1;
1559 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1560 (y) >= 0 && (y) <= lev_fieldy - 1; \
1561 (y) += playfield_scan_delta_y) \
1562 for ((x) = playfield_scan_start_x; \
1563 (x) >= 0 && (x) <= lev_fieldx - 1; \
1564 (x) += playfield_scan_delta_x)
1567 void DEBUG_SetMaximumDynamite(void)
1571 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1572 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1573 local_player->inventory_element[local_player->inventory_size++] =
1578 static void InitPlayfieldScanModeVars(void)
1580 if (game.use_reverse_scan_direction)
1582 playfield_scan_start_x = lev_fieldx - 1;
1583 playfield_scan_start_y = lev_fieldy - 1;
1585 playfield_scan_delta_x = -1;
1586 playfield_scan_delta_y = -1;
1590 playfield_scan_start_x = 0;
1591 playfield_scan_start_y = 0;
1593 playfield_scan_delta_x = 1;
1594 playfield_scan_delta_y = 1;
1598 static void InitPlayfieldScanMode(int mode)
1600 game.use_reverse_scan_direction =
1601 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1603 InitPlayfieldScanModeVars();
1606 static int get_move_delay_from_stepsize(int move_stepsize)
1609 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1611 // make sure that stepsize value is always a power of 2
1612 move_stepsize = (1 << log_2(move_stepsize));
1614 return TILEX / move_stepsize;
1617 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1620 int player_nr = player->index_nr;
1621 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1622 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1624 // do no immediately change move delay -- the player might just be moving
1625 player->move_delay_value_next = move_delay;
1627 // information if player can move must be set separately
1628 player->cannot_move = cannot_move;
1632 player->move_delay = game.initial_move_delay[player_nr];
1633 player->move_delay_value = game.initial_move_delay_value[player_nr];
1635 player->move_delay_value_next = -1;
1637 player->move_delay_reset_counter = 0;
1641 void GetPlayerConfig(void)
1643 GameFrameDelay = setup.game_frame_delay;
1645 if (!audio.sound_available)
1646 setup.sound_simple = FALSE;
1648 if (!audio.loops_available)
1649 setup.sound_loops = FALSE;
1651 if (!audio.music_available)
1652 setup.sound_music = FALSE;
1654 if (!video.fullscreen_available)
1655 setup.fullscreen = FALSE;
1657 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1659 SetAudioMode(setup.sound);
1662 int GetElementFromGroupElement(int element)
1664 if (IS_GROUP_ELEMENT(element))
1666 struct ElementGroupInfo *group = element_info[element].group;
1667 int last_anim_random_frame = gfx.anim_random_frame;
1670 if (group->choice_mode == ANIM_RANDOM)
1671 gfx.anim_random_frame = RND(group->num_elements_resolved);
1673 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1674 group->choice_mode, 0,
1677 if (group->choice_mode == ANIM_RANDOM)
1678 gfx.anim_random_frame = last_anim_random_frame;
1680 group->choice_pos++;
1682 element = group->element_resolved[element_pos];
1688 static void IncrementSokobanFieldsNeeded(void)
1690 if (level.sb_fields_needed)
1691 game.sokoban_fields_still_needed++;
1694 static void IncrementSokobanObjectsNeeded(void)
1696 if (level.sb_objects_needed)
1697 game.sokoban_objects_still_needed++;
1700 static void DecrementSokobanFieldsNeeded(void)
1702 if (game.sokoban_fields_still_needed > 0)
1703 game.sokoban_fields_still_needed--;
1706 static void DecrementSokobanObjectsNeeded(void)
1708 if (game.sokoban_objects_still_needed > 0)
1709 game.sokoban_objects_still_needed--;
1712 static void InitPlayerField(int x, int y, int element, boolean init_game)
1714 if (element == EL_SP_MURPHY)
1718 if (stored_player[0].present)
1720 Feld[x][y] = EL_SP_MURPHY_CLONE;
1726 stored_player[0].initial_element = element;
1727 stored_player[0].use_murphy = TRUE;
1729 if (!level.use_artwork_element[0])
1730 stored_player[0].artwork_element = EL_SP_MURPHY;
1733 Feld[x][y] = EL_PLAYER_1;
1739 struct PlayerInfo *player = &stored_player[Feld[x][y] - EL_PLAYER_1];
1740 int jx = player->jx, jy = player->jy;
1742 player->present = TRUE;
1744 player->block_last_field = (element == EL_SP_MURPHY ?
1745 level.sp_block_last_field :
1746 level.block_last_field);
1748 // ---------- initialize player's last field block delay ------------------
1750 // always start with reliable default value (no adjustment needed)
1751 player->block_delay_adjustment = 0;
1753 // special case 1: in Supaplex, Murphy blocks last field one more frame
1754 if (player->block_last_field && element == EL_SP_MURPHY)
1755 player->block_delay_adjustment = 1;
1757 // special case 2: in game engines before 3.1.1, blocking was different
1758 if (game.use_block_last_field_bug)
1759 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1761 if (!network.enabled || player->connected_network)
1763 player->active = TRUE;
1765 // remove potentially duplicate players
1766 if (StorePlayer[jx][jy] == Feld[x][y])
1767 StorePlayer[jx][jy] = 0;
1769 StorePlayer[x][y] = Feld[x][y];
1771 #if DEBUG_INIT_PLAYER
1774 printf("- player element %d activated", player->element_nr);
1775 printf(" (local player is %d and currently %s)\n",
1776 local_player->element_nr,
1777 local_player->active ? "active" : "not active");
1782 Feld[x][y] = EL_EMPTY;
1784 player->jx = player->last_jx = x;
1785 player->jy = player->last_jy = y;
1790 int player_nr = GET_PLAYER_NR(element);
1791 struct PlayerInfo *player = &stored_player[player_nr];
1793 if (player->active && player->killed)
1794 player->reanimated = TRUE; // if player was just killed, reanimate him
1798 static void InitField(int x, int y, boolean init_game)
1800 int element = Feld[x][y];
1809 InitPlayerField(x, y, element, init_game);
1812 case EL_SOKOBAN_FIELD_PLAYER:
1813 element = Feld[x][y] = EL_PLAYER_1;
1814 InitField(x, y, init_game);
1816 element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1817 InitField(x, y, init_game);
1820 case EL_SOKOBAN_FIELD_EMPTY:
1821 IncrementSokobanFieldsNeeded();
1824 case EL_SOKOBAN_OBJECT:
1825 IncrementSokobanObjectsNeeded();
1829 if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID)
1830 Feld[x][y] = EL_ACID_POOL_TOPLEFT;
1831 else if (x > 0 && Feld[x-1][y] == EL_ACID)
1832 Feld[x][y] = EL_ACID_POOL_TOPRIGHT;
1833 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPLEFT)
1834 Feld[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1835 else if (y > 0 && Feld[x][y-1] == EL_ACID)
1836 Feld[x][y] = EL_ACID_POOL_BOTTOM;
1837 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1838 Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1847 case EL_SPACESHIP_RIGHT:
1848 case EL_SPACESHIP_UP:
1849 case EL_SPACESHIP_LEFT:
1850 case EL_SPACESHIP_DOWN:
1851 case EL_BD_BUTTERFLY:
1852 case EL_BD_BUTTERFLY_RIGHT:
1853 case EL_BD_BUTTERFLY_UP:
1854 case EL_BD_BUTTERFLY_LEFT:
1855 case EL_BD_BUTTERFLY_DOWN:
1857 case EL_BD_FIREFLY_RIGHT:
1858 case EL_BD_FIREFLY_UP:
1859 case EL_BD_FIREFLY_LEFT:
1860 case EL_BD_FIREFLY_DOWN:
1861 case EL_PACMAN_RIGHT:
1863 case EL_PACMAN_LEFT:
1864 case EL_PACMAN_DOWN:
1866 case EL_YAMYAM_LEFT:
1867 case EL_YAMYAM_RIGHT:
1869 case EL_YAMYAM_DOWN:
1870 case EL_DARK_YAMYAM:
1873 case EL_SP_SNIKSNAK:
1874 case EL_SP_ELECTRON:
1883 case EL_AMOEBA_FULL:
1888 case EL_AMOEBA_DROP:
1889 if (y == lev_fieldy - 1)
1891 Feld[x][y] = EL_AMOEBA_GROWING;
1892 Store[x][y] = EL_AMOEBA_WET;
1896 case EL_DYNAMITE_ACTIVE:
1897 case EL_SP_DISK_RED_ACTIVE:
1898 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1899 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1900 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1901 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1902 MovDelay[x][y] = 96;
1905 case EL_EM_DYNAMITE_ACTIVE:
1906 MovDelay[x][y] = 32;
1910 game.lights_still_needed++;
1914 game.friends_still_needed++;
1919 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1922 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1923 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1924 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1925 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1926 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1927 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1928 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1929 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1930 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1931 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1932 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1933 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1936 int belt_nr = getBeltNrFromBeltSwitchElement(Feld[x][y]);
1937 int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]);
1938 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[x][y]);
1940 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1942 game.belt_dir[belt_nr] = belt_dir;
1943 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1945 else // more than one switch -- set it like the first switch
1947 Feld[x][y] = Feld[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1952 case EL_LIGHT_SWITCH_ACTIVE:
1954 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1957 case EL_INVISIBLE_STEELWALL:
1958 case EL_INVISIBLE_WALL:
1959 case EL_INVISIBLE_SAND:
1960 if (game.light_time_left > 0 ||
1961 game.lenses_time_left > 0)
1962 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
1965 case EL_EMC_MAGIC_BALL:
1966 if (game.ball_active)
1967 Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1970 case EL_EMC_MAGIC_BALL_SWITCH:
1971 if (game.ball_active)
1972 Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1975 case EL_TRIGGER_PLAYER:
1976 case EL_TRIGGER_ELEMENT:
1977 case EL_TRIGGER_CE_VALUE:
1978 case EL_TRIGGER_CE_SCORE:
1980 case EL_ANY_ELEMENT:
1981 case EL_CURRENT_CE_VALUE:
1982 case EL_CURRENT_CE_SCORE:
1999 // reference elements should not be used on the playfield
2000 Feld[x][y] = EL_EMPTY;
2004 if (IS_CUSTOM_ELEMENT(element))
2006 if (CAN_MOVE(element))
2009 if (!element_info[element].use_last_ce_value || init_game)
2010 CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
2012 else if (IS_GROUP_ELEMENT(element))
2014 Feld[x][y] = GetElementFromGroupElement(element);
2016 InitField(x, y, init_game);
2023 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2026 static void InitField_WithBug1(int x, int y, boolean init_game)
2028 InitField(x, y, init_game);
2030 // not needed to call InitMovDir() -- already done by InitField()!
2031 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2032 CAN_MOVE(Feld[x][y]))
2036 static void InitField_WithBug2(int x, int y, boolean init_game)
2038 int old_element = Feld[x][y];
2040 InitField(x, y, init_game);
2042 // not needed to call InitMovDir() -- already done by InitField()!
2043 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2044 CAN_MOVE(old_element) &&
2045 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2048 /* this case is in fact a combination of not less than three bugs:
2049 first, it calls InitMovDir() for elements that can move, although this is
2050 already done by InitField(); then, it checks the element that was at this
2051 field _before_ the call to InitField() (which can change it); lastly, it
2052 was not called for "mole with direction" elements, which were treated as
2053 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2057 static int get_key_element_from_nr(int key_nr)
2059 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2060 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2061 EL_EM_KEY_1 : EL_KEY_1);
2063 return key_base_element + key_nr;
2066 static int get_next_dropped_element(struct PlayerInfo *player)
2068 return (player->inventory_size > 0 ?
2069 player->inventory_element[player->inventory_size - 1] :
2070 player->inventory_infinite_element != EL_UNDEFINED ?
2071 player->inventory_infinite_element :
2072 player->dynabombs_left > 0 ?
2073 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2077 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2079 // pos >= 0: get element from bottom of the stack;
2080 // pos < 0: get element from top of the stack
2084 int min_inventory_size = -pos;
2085 int inventory_pos = player->inventory_size - min_inventory_size;
2086 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2088 return (player->inventory_size >= min_inventory_size ?
2089 player->inventory_element[inventory_pos] :
2090 player->inventory_infinite_element != EL_UNDEFINED ?
2091 player->inventory_infinite_element :
2092 player->dynabombs_left >= min_dynabombs_left ?
2093 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2098 int min_dynabombs_left = pos + 1;
2099 int min_inventory_size = pos + 1 - player->dynabombs_left;
2100 int inventory_pos = pos - player->dynabombs_left;
2102 return (player->inventory_infinite_element != EL_UNDEFINED ?
2103 player->inventory_infinite_element :
2104 player->dynabombs_left >= min_dynabombs_left ?
2105 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2106 player->inventory_size >= min_inventory_size ?
2107 player->inventory_element[inventory_pos] :
2112 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2114 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2115 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2118 if (gpo1->sort_priority != gpo2->sort_priority)
2119 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2121 compare_result = gpo1->nr - gpo2->nr;
2123 return compare_result;
2126 int getPlayerInventorySize(int player_nr)
2128 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2129 return game_em.ply[player_nr]->dynamite;
2130 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2131 return game_sp.red_disk_count;
2133 return stored_player[player_nr].inventory_size;
2136 static void InitGameControlValues(void)
2140 for (i = 0; game_panel_controls[i].nr != -1; i++)
2142 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2143 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2144 struct TextPosInfo *pos = gpc->pos;
2146 int type = gpc->type;
2150 Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
2151 Error(ERR_EXIT, "this should not happen -- please debug");
2154 // force update of game controls after initialization
2155 gpc->value = gpc->last_value = -1;
2156 gpc->frame = gpc->last_frame = -1;
2157 gpc->gfx_frame = -1;
2159 // determine panel value width for later calculation of alignment
2160 if (type == TYPE_INTEGER || type == TYPE_STRING)
2162 pos->width = pos->size * getFontWidth(pos->font);
2163 pos->height = getFontHeight(pos->font);
2165 else if (type == TYPE_ELEMENT)
2167 pos->width = pos->size;
2168 pos->height = pos->size;
2171 // fill structure for game panel draw order
2173 gpo->sort_priority = pos->sort_priority;
2176 // sort game panel controls according to sort_priority and control number
2177 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2178 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2181 static void UpdatePlayfieldElementCount(void)
2183 boolean use_element_count = FALSE;
2186 // first check if it is needed at all to calculate playfield element count
2187 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2188 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2189 use_element_count = TRUE;
2191 if (!use_element_count)
2194 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2195 element_info[i].element_count = 0;
2197 SCAN_PLAYFIELD(x, y)
2199 element_info[Feld[x][y]].element_count++;
2202 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2203 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2204 if (IS_IN_GROUP(j, i))
2205 element_info[EL_GROUP_START + i].element_count +=
2206 element_info[j].element_count;
2209 static void UpdateGameControlValues(void)
2212 int time = (game.LevelSolved ?
2213 game.LevelSolved_CountingTime :
2214 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2216 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2217 game_sp.time_played :
2218 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2219 game_mm.energy_left :
2220 game.no_time_limit ? TimePlayed : TimeLeft);
2221 int score = (game.LevelSolved ?
2222 game.LevelSolved_CountingScore :
2223 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2224 game_em.lev->score :
2225 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2227 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2230 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2231 game_em.lev->gems_needed :
2232 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2233 game_sp.infotrons_still_needed :
2234 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2235 game_mm.kettles_still_needed :
2236 game.gems_still_needed);
2237 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2238 game_em.lev->gems_needed > 0 :
2239 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2240 game_sp.infotrons_still_needed > 0 :
2241 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2242 game_mm.kettles_still_needed > 0 ||
2243 game_mm.lights_still_needed > 0 :
2244 game.gems_still_needed > 0 ||
2245 game.sokoban_fields_still_needed > 0 ||
2246 game.sokoban_objects_still_needed > 0 ||
2247 game.lights_still_needed > 0);
2248 int health = (game.LevelSolved ?
2249 game.LevelSolved_CountingHealth :
2250 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2251 MM_HEALTH(game_mm.laser_overload_value) :
2254 UpdatePlayfieldElementCount();
2256 // update game panel control values
2258 // used instead of "level_nr" (for network games)
2259 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2260 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2262 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2263 for (i = 0; i < MAX_NUM_KEYS; i++)
2264 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2265 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2266 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2268 if (game.centered_player_nr == -1)
2270 for (i = 0; i < MAX_PLAYERS; i++)
2272 // only one player in Supaplex game engine
2273 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2276 for (k = 0; k < MAX_NUM_KEYS; k++)
2278 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2280 if (game_em.ply[i]->keys & (1 << k))
2281 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2282 get_key_element_from_nr(k);
2284 else if (stored_player[i].key[k])
2285 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2286 get_key_element_from_nr(k);
2289 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2290 getPlayerInventorySize(i);
2292 if (stored_player[i].num_white_keys > 0)
2293 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2296 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2297 stored_player[i].num_white_keys;
2302 int player_nr = game.centered_player_nr;
2304 for (k = 0; k < MAX_NUM_KEYS; k++)
2306 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2308 if (game_em.ply[player_nr]->keys & (1 << k))
2309 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2310 get_key_element_from_nr(k);
2312 else if (stored_player[player_nr].key[k])
2313 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2314 get_key_element_from_nr(k);
2317 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2318 getPlayerInventorySize(player_nr);
2320 if (stored_player[player_nr].num_white_keys > 0)
2321 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2323 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2324 stored_player[player_nr].num_white_keys;
2327 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2329 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2330 get_inventory_element_from_pos(local_player, i);
2331 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2332 get_inventory_element_from_pos(local_player, -i - 1);
2335 game_panel_controls[GAME_PANEL_SCORE].value = score;
2336 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2338 game_panel_controls[GAME_PANEL_TIME].value = time;
2340 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2341 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2342 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2344 if (level.time == 0)
2345 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2347 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2349 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2350 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2352 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2354 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2355 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2357 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2358 local_player->shield_normal_time_left;
2359 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2360 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2362 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2363 local_player->shield_deadly_time_left;
2365 game_panel_controls[GAME_PANEL_EXIT].value =
2366 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2368 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2369 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2370 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2371 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2372 EL_EMC_MAGIC_BALL_SWITCH);
2374 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2375 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2376 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2377 game.light_time_left;
2379 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2380 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2381 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2382 game.timegate_time_left;
2384 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2385 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2387 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2388 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2389 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2390 game.lenses_time_left;
2392 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2393 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2394 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2395 game.magnify_time_left;
2397 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2398 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2399 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2400 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2401 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2402 EL_BALLOON_SWITCH_NONE);
2404 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2405 local_player->dynabomb_count;
2406 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2407 local_player->dynabomb_size;
2408 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2409 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2411 game_panel_controls[GAME_PANEL_PENGUINS].value =
2412 game.friends_still_needed;
2414 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2415 game.sokoban_objects_still_needed;
2416 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2417 game.sokoban_fields_still_needed;
2419 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2420 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2422 for (i = 0; i < NUM_BELTS; i++)
2424 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2425 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2426 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2427 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2428 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2431 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2432 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2433 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2434 game.magic_wall_time_left;
2436 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2437 local_player->gravity;
2439 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2440 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2442 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2443 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2444 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2445 game.panel.element[i].id : EL_UNDEFINED);
2447 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2448 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2449 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2450 element_info[game.panel.element_count[i].id].element_count : 0);
2452 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2453 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2454 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2455 element_info[game.panel.ce_score[i].id].collect_score : 0);
2457 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2458 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2459 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2460 element_info[game.panel.ce_score_element[i].id].collect_score :
2463 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2464 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2465 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2467 // update game panel control frames
2469 for (i = 0; game_panel_controls[i].nr != -1; i++)
2471 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2473 if (gpc->type == TYPE_ELEMENT)
2475 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2477 int last_anim_random_frame = gfx.anim_random_frame;
2478 int element = gpc->value;
2479 int graphic = el2panelimg(element);
2481 if (gpc->value != gpc->last_value)
2484 gpc->gfx_random = INIT_GFX_RANDOM();
2490 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2491 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2492 gpc->gfx_random = INIT_GFX_RANDOM();
2495 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2496 gfx.anim_random_frame = gpc->gfx_random;
2498 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2499 gpc->gfx_frame = element_info[element].collect_score;
2501 gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
2504 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2505 gfx.anim_random_frame = last_anim_random_frame;
2508 else if (gpc->type == TYPE_GRAPHIC)
2510 if (gpc->graphic != IMG_UNDEFINED)
2512 int last_anim_random_frame = gfx.anim_random_frame;
2513 int graphic = gpc->graphic;
2515 if (gpc->value != gpc->last_value)
2518 gpc->gfx_random = INIT_GFX_RANDOM();
2524 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2525 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2526 gpc->gfx_random = INIT_GFX_RANDOM();
2529 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2530 gfx.anim_random_frame = gpc->gfx_random;
2532 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2534 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2535 gfx.anim_random_frame = last_anim_random_frame;
2541 static void DisplayGameControlValues(void)
2543 boolean redraw_panel = FALSE;
2546 for (i = 0; game_panel_controls[i].nr != -1; i++)
2548 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2550 if (PANEL_DEACTIVATED(gpc->pos))
2553 if (gpc->value == gpc->last_value &&
2554 gpc->frame == gpc->last_frame)
2557 redraw_panel = TRUE;
2563 // copy default game door content to main double buffer
2565 // !!! CHECK AGAIN !!!
2566 SetPanelBackground();
2567 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2568 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2570 // redraw game control buttons
2571 RedrawGameButtons();
2573 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2575 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2577 int nr = game_panel_order[i].nr;
2578 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2579 struct TextPosInfo *pos = gpc->pos;
2580 int type = gpc->type;
2581 int value = gpc->value;
2582 int frame = gpc->frame;
2583 int size = pos->size;
2584 int font = pos->font;
2585 boolean draw_masked = pos->draw_masked;
2586 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2588 if (PANEL_DEACTIVATED(pos))
2591 gpc->last_value = value;
2592 gpc->last_frame = frame;
2594 if (type == TYPE_INTEGER)
2596 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2597 nr == GAME_PANEL_TIME)
2599 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2601 if (use_dynamic_size) // use dynamic number of digits
2603 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2604 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2605 int size2 = size1 + 1;
2606 int font1 = pos->font;
2607 int font2 = pos->font_alt;
2609 size = (value < value_change ? size1 : size2);
2610 font = (value < value_change ? font1 : font2);
2614 // correct text size if "digits" is zero or less
2616 size = strlen(int2str(value, size));
2618 // dynamically correct text alignment
2619 pos->width = size * getFontWidth(font);
2621 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2622 int2str(value, size), font, mask_mode);
2624 else if (type == TYPE_ELEMENT)
2626 int element, graphic;
2630 int dst_x = PANEL_XPOS(pos);
2631 int dst_y = PANEL_YPOS(pos);
2633 if (value != EL_UNDEFINED && value != EL_EMPTY)
2636 graphic = el2panelimg(value);
2638 // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
2640 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2643 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2646 width = graphic_info[graphic].width * size / TILESIZE;
2647 height = graphic_info[graphic].height * size / TILESIZE;
2650 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2653 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2657 else if (type == TYPE_GRAPHIC)
2659 int graphic = gpc->graphic;
2660 int graphic_active = gpc->graphic_active;
2664 int dst_x = PANEL_XPOS(pos);
2665 int dst_y = PANEL_YPOS(pos);
2666 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2667 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2669 if (graphic != IMG_UNDEFINED && !skip)
2671 if (pos->style == STYLE_REVERSE)
2672 value = 100 - value;
2674 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2676 if (pos->direction & MV_HORIZONTAL)
2678 width = graphic_info[graphic_active].width * value / 100;
2679 height = graphic_info[graphic_active].height;
2681 if (pos->direction == MV_LEFT)
2683 src_x += graphic_info[graphic_active].width - width;
2684 dst_x += graphic_info[graphic_active].width - width;
2689 width = graphic_info[graphic_active].width;
2690 height = graphic_info[graphic_active].height * value / 100;
2692 if (pos->direction == MV_UP)
2694 src_y += graphic_info[graphic_active].height - height;
2695 dst_y += graphic_info[graphic_active].height - height;
2700 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2703 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2706 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2708 if (pos->direction & MV_HORIZONTAL)
2710 if (pos->direction == MV_RIGHT)
2717 dst_x = PANEL_XPOS(pos);
2720 width = graphic_info[graphic].width - width;
2724 if (pos->direction == MV_DOWN)
2731 dst_y = PANEL_YPOS(pos);
2734 height = graphic_info[graphic].height - height;
2738 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2741 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2745 else if (type == TYPE_STRING)
2747 boolean active = (value != 0);
2748 char *state_normal = "off";
2749 char *state_active = "on";
2750 char *state = (active ? state_active : state_normal);
2751 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2752 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2753 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2754 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2756 if (nr == GAME_PANEL_GRAVITY_STATE)
2758 int font1 = pos->font; // (used for normal state)
2759 int font2 = pos->font_alt; // (used for active state)
2761 font = (active ? font2 : font1);
2770 // don't truncate output if "chars" is zero or less
2773 // dynamically correct text alignment
2774 pos->width = size * getFontWidth(font);
2777 s_cut = getStringCopyN(s, size);
2779 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2780 s_cut, font, mask_mode);
2786 redraw_mask |= REDRAW_DOOR_1;
2789 SetGameStatus(GAME_MODE_PLAYING);
2792 void UpdateAndDisplayGameControlValues(void)
2794 if (tape.deactivate_display)
2797 UpdateGameControlValues();
2798 DisplayGameControlValues();
2802 static void UpdateGameDoorValues(void)
2804 UpdateGameControlValues();
2808 void DrawGameDoorValues(void)
2810 DisplayGameControlValues();
2814 // ============================================================================
2816 // ----------------------------------------------------------------------------
2817 // initialize game engine due to level / tape version number
2818 // ============================================================================
2820 static void InitGameEngine(void)
2822 int i, j, k, l, x, y;
2824 // set game engine from tape file when re-playing, else from level file
2825 game.engine_version = (tape.playing ? tape.engine_version :
2826 level.game_version);
2828 // set single or multi-player game mode (needed for re-playing tapes)
2829 game.team_mode = setup.team_mode;
2833 int num_players = 0;
2835 for (i = 0; i < MAX_PLAYERS; i++)
2836 if (tape.player_participates[i])
2839 // multi-player tapes contain input data for more than one player
2840 game.team_mode = (num_players > 1);
2843 // --------------------------------------------------------------------------
2844 // set flags for bugs and changes according to active game engine version
2845 // --------------------------------------------------------------------------
2848 Summary of bugfix/change:
2849 Fixed handling for custom elements that change when pushed by the player.
2851 Fixed/changed in version:
2855 Before 3.1.0, custom elements that "change when pushing" changed directly
2856 after the player started pushing them (until then handled in "DigField()").
2857 Since 3.1.0, these custom elements are not changed until the "pushing"
2858 move of the element is finished (now handled in "ContinueMoving()").
2860 Affected levels/tapes:
2861 The first condition is generally needed for all levels/tapes before version
2862 3.1.0, which might use the old behaviour before it was changed; known tapes
2863 that are affected are some tapes from the level set "Walpurgis Gardens" by
2865 The second condition is an exception from the above case and is needed for
2866 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2867 above (including some development versions of 3.1.0), but before it was
2868 known that this change would break tapes like the above and was fixed in
2869 3.1.1, so that the changed behaviour was active although the engine version
2870 while recording maybe was before 3.1.0. There is at least one tape that is
2871 affected by this exception, which is the tape for the one-level set "Bug
2872 Machine" by Juergen Bonhagen.
2875 game.use_change_when_pushing_bug =
2876 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2878 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2879 tape.game_version < VERSION_IDENT(3,1,1,0)));
2882 Summary of bugfix/change:
2883 Fixed handling for blocking the field the player leaves when moving.
2885 Fixed/changed in version:
2889 Before 3.1.1, when "block last field when moving" was enabled, the field
2890 the player is leaving when moving was blocked for the time of the move,
2891 and was directly unblocked afterwards. This resulted in the last field
2892 being blocked for exactly one less than the number of frames of one player
2893 move. Additionally, even when blocking was disabled, the last field was
2894 blocked for exactly one frame.
2895 Since 3.1.1, due to changes in player movement handling, the last field
2896 is not blocked at all when blocking is disabled. When blocking is enabled,
2897 the last field is blocked for exactly the number of frames of one player
2898 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2899 last field is blocked for exactly one more than the number of frames of
2902 Affected levels/tapes:
2903 (!!! yet to be determined -- probably many !!!)
2906 game.use_block_last_field_bug =
2907 (game.engine_version < VERSION_IDENT(3,1,1,0));
2909 /* various special flags and settings for native Emerald Mine game engine */
2911 game_em.use_single_button =
2912 (game.engine_version > VERSION_IDENT(4,0,0,2));
2914 game_em.use_snap_key_bug =
2915 (game.engine_version < VERSION_IDENT(4,0,1,0));
2917 game_em.use_old_explosions =
2918 (game.engine_version < VERSION_IDENT(4,1,4,2));
2920 // --------------------------------------------------------------------------
2922 // set maximal allowed number of custom element changes per game frame
2923 game.max_num_changes_per_frame = 1;
2925 // default scan direction: scan playfield from top/left to bottom/right
2926 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
2928 // dynamically adjust element properties according to game engine version
2929 InitElementPropertiesEngine(game.engine_version);
2932 printf("level %d: level version == %06d\n", level_nr, level.game_version);
2933 printf(" tape version == %06d [%s] [file: %06d]\n",
2934 tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
2936 printf(" => game.engine_version == %06d\n", game.engine_version);
2939 // ---------- initialize player's initial move delay ------------------------
2941 // dynamically adjust player properties according to level information
2942 for (i = 0; i < MAX_PLAYERS; i++)
2943 game.initial_move_delay_value[i] =
2944 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
2946 // dynamically adjust player properties according to game engine version
2947 for (i = 0; i < MAX_PLAYERS; i++)
2948 game.initial_move_delay[i] =
2949 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
2950 game.initial_move_delay_value[i] : 0);
2952 // ---------- initialize player's initial push delay ------------------------
2954 // dynamically adjust player properties according to game engine version
2955 game.initial_push_delay_value =
2956 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
2958 // ---------- initialize changing elements ----------------------------------
2960 // initialize changing elements information
2961 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2963 struct ElementInfo *ei = &element_info[i];
2965 // this pointer might have been changed in the level editor
2966 ei->change = &ei->change_page[0];
2968 if (!IS_CUSTOM_ELEMENT(i))
2970 ei->change->target_element = EL_EMPTY_SPACE;
2971 ei->change->delay_fixed = 0;
2972 ei->change->delay_random = 0;
2973 ei->change->delay_frames = 1;
2976 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
2978 ei->has_change_event[j] = FALSE;
2980 ei->event_page_nr[j] = 0;
2981 ei->event_page[j] = &ei->change_page[0];
2985 // add changing elements from pre-defined list
2986 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
2988 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
2989 struct ElementInfo *ei = &element_info[ch_delay->element];
2991 ei->change->target_element = ch_delay->target_element;
2992 ei->change->delay_fixed = ch_delay->change_delay;
2994 ei->change->pre_change_function = ch_delay->pre_change_function;
2995 ei->change->change_function = ch_delay->change_function;
2996 ei->change->post_change_function = ch_delay->post_change_function;
2998 ei->change->can_change = TRUE;
2999 ei->change->can_change_or_has_action = TRUE;
3001 ei->has_change_event[CE_DELAY] = TRUE;
3003 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3004 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3007 // ---------- initialize internal run-time variables ------------------------
3009 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3011 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3013 for (j = 0; j < ei->num_change_pages; j++)
3015 ei->change_page[j].can_change_or_has_action =
3016 (ei->change_page[j].can_change |
3017 ei->change_page[j].has_action);
3021 // add change events from custom element configuration
3022 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3024 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3026 for (j = 0; j < ei->num_change_pages; j++)
3028 if (!ei->change_page[j].can_change_or_has_action)
3031 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3033 // only add event page for the first page found with this event
3034 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3036 ei->has_change_event[k] = TRUE;
3038 ei->event_page_nr[k] = j;
3039 ei->event_page[k] = &ei->change_page[j];
3045 // ---------- initialize reference elements in change conditions ------------
3047 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3049 int element = EL_CUSTOM_START + i;
3050 struct ElementInfo *ei = &element_info[element];
3052 for (j = 0; j < ei->num_change_pages; j++)
3054 int trigger_element = ei->change_page[j].initial_trigger_element;
3056 if (trigger_element >= EL_PREV_CE_8 &&
3057 trigger_element <= EL_NEXT_CE_8)
3058 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3060 ei->change_page[j].trigger_element = trigger_element;
3064 // ---------- initialize run-time trigger player and element ----------------
3066 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3068 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3070 for (j = 0; j < ei->num_change_pages; j++)
3072 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3073 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3074 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3075 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3076 ei->change_page[j].actual_trigger_ce_value = 0;
3077 ei->change_page[j].actual_trigger_ce_score = 0;
3081 // ---------- initialize trigger events -------------------------------------
3083 // initialize trigger events information
3084 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3085 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3086 trigger_events[i][j] = FALSE;
3088 // add trigger events from element change event properties
3089 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3091 struct ElementInfo *ei = &element_info[i];
3093 for (j = 0; j < ei->num_change_pages; j++)
3095 if (!ei->change_page[j].can_change_or_has_action)
3098 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3100 int trigger_element = ei->change_page[j].trigger_element;
3102 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3104 if (ei->change_page[j].has_event[k])
3106 if (IS_GROUP_ELEMENT(trigger_element))
3108 struct ElementGroupInfo *group =
3109 element_info[trigger_element].group;
3111 for (l = 0; l < group->num_elements_resolved; l++)
3112 trigger_events[group->element_resolved[l]][k] = TRUE;
3114 else if (trigger_element == EL_ANY_ELEMENT)
3115 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3116 trigger_events[l][k] = TRUE;
3118 trigger_events[trigger_element][k] = TRUE;
3125 // ---------- initialize push delay -----------------------------------------
3127 // initialize push delay values to default
3128 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3130 if (!IS_CUSTOM_ELEMENT(i))
3132 // set default push delay values (corrected since version 3.0.7-1)
3133 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3135 element_info[i].push_delay_fixed = 2;
3136 element_info[i].push_delay_random = 8;
3140 element_info[i].push_delay_fixed = 8;
3141 element_info[i].push_delay_random = 8;
3146 // set push delay value for certain elements from pre-defined list
3147 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3149 int e = push_delay_list[i].element;
3151 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3152 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3155 // set push delay value for Supaplex elements for newer engine versions
3156 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3158 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3160 if (IS_SP_ELEMENT(i))
3162 // set SP push delay to just enough to push under a falling zonk
3163 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3165 element_info[i].push_delay_fixed = delay;
3166 element_info[i].push_delay_random = 0;
3171 // ---------- initialize move stepsize --------------------------------------
3173 // initialize move stepsize values to default
3174 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3175 if (!IS_CUSTOM_ELEMENT(i))
3176 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3178 // set move stepsize value for certain elements from pre-defined list
3179 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3181 int e = move_stepsize_list[i].element;
3183 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3186 // ---------- initialize collect score --------------------------------------
3188 // initialize collect score values for custom elements from initial value
3189 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3190 if (IS_CUSTOM_ELEMENT(i))
3191 element_info[i].collect_score = element_info[i].collect_score_initial;
3193 // ---------- initialize collect count --------------------------------------
3195 // initialize collect count values for non-custom elements
3196 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3197 if (!IS_CUSTOM_ELEMENT(i))
3198 element_info[i].collect_count_initial = 0;
3200 // add collect count values for all elements from pre-defined list
3201 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3202 element_info[collect_count_list[i].element].collect_count_initial =
3203 collect_count_list[i].count;
3205 // ---------- initialize access direction -----------------------------------
3207 // initialize access direction values to default (access from every side)
3208 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3209 if (!IS_CUSTOM_ELEMENT(i))
3210 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3212 // set access direction value for certain elements from pre-defined list
3213 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3214 element_info[access_direction_list[i].element].access_direction =
3215 access_direction_list[i].direction;
3217 // ---------- initialize explosion content ----------------------------------
3218 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3220 if (IS_CUSTOM_ELEMENT(i))
3223 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3225 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3227 element_info[i].content.e[x][y] =
3228 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3229 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3230 i == EL_PLAYER_3 ? EL_EMERALD :
3231 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3232 i == EL_MOLE ? EL_EMERALD_RED :
3233 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3234 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3235 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3236 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3237 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3238 i == EL_WALL_EMERALD ? EL_EMERALD :
3239 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3240 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3241 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3242 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3243 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3244 i == EL_WALL_PEARL ? EL_PEARL :
3245 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3250 // ---------- initialize recursion detection --------------------------------
3251 recursion_loop_depth = 0;
3252 recursion_loop_detected = FALSE;
3253 recursion_loop_element = EL_UNDEFINED;
3255 // ---------- initialize graphics engine ------------------------------------
3256 game.scroll_delay_value =
3257 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3258 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3259 !setup.forced_scroll_delay ? 0 :
3260 setup.scroll_delay ? setup.scroll_delay_value : 0);
3261 game.scroll_delay_value =
3262 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3264 // ---------- initialize game engine snapshots ------------------------------
3265 for (i = 0; i < MAX_PLAYERS; i++)
3266 game.snapshot.last_action[i] = 0;
3267 game.snapshot.changed_action = FALSE;
3268 game.snapshot.collected_item = FALSE;
3269 game.snapshot.mode =
3270 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3271 SNAPSHOT_MODE_EVERY_STEP :
3272 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3273 SNAPSHOT_MODE_EVERY_MOVE :
3274 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3275 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3276 game.snapshot.save_snapshot = FALSE;
3278 // ---------- initialize level time for Supaplex engine ---------------------
3279 // Supaplex levels with time limit currently unsupported -- should be added
3280 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3284 static int get_num_special_action(int element, int action_first,
3287 int num_special_action = 0;
3290 for (i = action_first; i <= action_last; i++)
3292 boolean found = FALSE;
3294 for (j = 0; j < NUM_DIRECTIONS; j++)
3295 if (el_act_dir2img(element, i, j) !=
3296 el_act_dir2img(element, ACTION_DEFAULT, j))
3300 num_special_action++;
3305 return num_special_action;
3309 // ============================================================================
3311 // ----------------------------------------------------------------------------
3312 // initialize and start new game
3313 // ============================================================================
3315 #if DEBUG_INIT_PLAYER
3316 static void DebugPrintPlayerStatus(char *message)
3323 printf("%s:\n", message);
3325 for (i = 0; i < MAX_PLAYERS; i++)
3327 struct PlayerInfo *player = &stored_player[i];
3329 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3333 player->connected_locally,
3334 player->connected_network,
3337 if (local_player == player)
3338 printf(" (local player)");
3347 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3348 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3349 int fade_mask = REDRAW_FIELD;
3351 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3352 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3353 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3354 int initial_move_dir = MV_DOWN;
3357 // required here to update video display before fading (FIX THIS)
3358 DrawMaskedBorder(REDRAW_DOOR_2);
3360 if (!game.restart_level)
3361 CloseDoor(DOOR_CLOSE_1);
3363 SetGameStatus(GAME_MODE_PLAYING);
3365 if (level_editor_test_game)
3366 FadeSkipNextFadeOut();
3368 FadeSetEnterScreen();
3371 fade_mask = REDRAW_ALL;
3373 FadeLevelSoundsAndMusic();
3375 ExpireSoundLoops(TRUE);
3379 if (level_editor_test_game)
3380 FadeSkipNextFadeIn();
3382 // needed if different viewport properties defined for playing
3383 ChangeViewportPropertiesIfNeeded();
3387 DrawCompleteVideoDisplay();
3389 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3392 InitGameControlValues();
3394 // don't play tapes over network
3395 network_playing = (network.enabled && !tape.playing);
3397 for (i = 0; i < MAX_PLAYERS; i++)
3399 struct PlayerInfo *player = &stored_player[i];
3401 player->index_nr = i;
3402 player->index_bit = (1 << i);
3403 player->element_nr = EL_PLAYER_1 + i;
3405 player->present = FALSE;
3406 player->active = FALSE;
3407 player->mapped = FALSE;
3409 player->killed = FALSE;
3410 player->reanimated = FALSE;
3411 player->buried = FALSE;
3414 player->effective_action = 0;
3415 player->programmed_action = 0;
3416 player->snap_action = 0;
3418 player->mouse_action.lx = 0;
3419 player->mouse_action.ly = 0;
3420 player->mouse_action.button = 0;
3421 player->mouse_action.button_hint = 0;
3423 player->effective_mouse_action.lx = 0;
3424 player->effective_mouse_action.ly = 0;
3425 player->effective_mouse_action.button = 0;
3426 player->effective_mouse_action.button_hint = 0;
3428 for (j = 0; j < MAX_NUM_KEYS; j++)
3429 player->key[j] = FALSE;
3431 player->num_white_keys = 0;
3433 player->dynabomb_count = 0;
3434 player->dynabomb_size = 1;
3435 player->dynabombs_left = 0;
3436 player->dynabomb_xl = FALSE;
3438 player->MovDir = initial_move_dir;
3441 player->GfxDir = initial_move_dir;
3442 player->GfxAction = ACTION_DEFAULT;
3444 player->StepFrame = 0;
3446 player->initial_element = player->element_nr;
3447 player->artwork_element =
3448 (level.use_artwork_element[i] ? level.artwork_element[i] :
3449 player->element_nr);
3450 player->use_murphy = FALSE;
3452 player->block_last_field = FALSE; // initialized in InitPlayerField()
3453 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3455 player->gravity = level.initial_player_gravity[i];
3457 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3459 player->actual_frame_counter = 0;
3461 player->step_counter = 0;
3463 player->last_move_dir = initial_move_dir;
3465 player->is_active = FALSE;
3467 player->is_waiting = FALSE;
3468 player->is_moving = FALSE;
3469 player->is_auto_moving = FALSE;
3470 player->is_digging = FALSE;
3471 player->is_snapping = FALSE;
3472 player->is_collecting = FALSE;
3473 player->is_pushing = FALSE;
3474 player->is_switching = FALSE;
3475 player->is_dropping = FALSE;
3476 player->is_dropping_pressed = FALSE;
3478 player->is_bored = FALSE;
3479 player->is_sleeping = FALSE;
3481 player->was_waiting = TRUE;
3482 player->was_moving = FALSE;
3483 player->was_snapping = FALSE;
3484 player->was_dropping = FALSE;
3486 player->force_dropping = FALSE;
3488 player->frame_counter_bored = -1;
3489 player->frame_counter_sleeping = -1;
3491 player->anim_delay_counter = 0;
3492 player->post_delay_counter = 0;
3494 player->dir_waiting = initial_move_dir;
3495 player->action_waiting = ACTION_DEFAULT;
3496 player->last_action_waiting = ACTION_DEFAULT;
3497 player->special_action_bored = ACTION_DEFAULT;
3498 player->special_action_sleeping = ACTION_DEFAULT;
3500 player->switch_x = -1;
3501 player->switch_y = -1;
3503 player->drop_x = -1;
3504 player->drop_y = -1;
3506 player->show_envelope = 0;
3508 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3510 player->push_delay = -1; // initialized when pushing starts
3511 player->push_delay_value = game.initial_push_delay_value;
3513 player->drop_delay = 0;
3514 player->drop_pressed_delay = 0;
3516 player->last_jx = -1;
3517 player->last_jy = -1;
3521 player->shield_normal_time_left = 0;
3522 player->shield_deadly_time_left = 0;
3524 player->inventory_infinite_element = EL_UNDEFINED;
3525 player->inventory_size = 0;
3527 if (level.use_initial_inventory[i])
3529 for (j = 0; j < level.initial_inventory_size[i]; j++)
3531 int element = level.initial_inventory_content[i][j];
3532 int collect_count = element_info[element].collect_count_initial;
3535 if (!IS_CUSTOM_ELEMENT(element))
3538 if (collect_count == 0)
3539 player->inventory_infinite_element = element;
3541 for (k = 0; k < collect_count; k++)
3542 if (player->inventory_size < MAX_INVENTORY_SIZE)
3543 player->inventory_element[player->inventory_size++] = element;
3547 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3548 SnapField(player, 0, 0);
3550 map_player_action[i] = i;
3553 network_player_action_received = FALSE;
3555 // initial null action
3556 if (network_playing)
3557 SendToServer_MovePlayer(MV_NONE);
3562 TimeLeft = level.time;
3565 ScreenMovDir = MV_NONE;
3569 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3571 game.robot_wheel_x = -1;
3572 game.robot_wheel_y = -1;
3577 game.all_players_gone = FALSE;
3579 game.LevelSolved = FALSE;
3580 game.GameOver = FALSE;
3582 game.GamePlayed = !tape.playing;
3584 game.LevelSolved_GameWon = FALSE;
3585 game.LevelSolved_GameEnd = FALSE;
3586 game.LevelSolved_SaveTape = FALSE;
3587 game.LevelSolved_SaveScore = FALSE;
3589 game.LevelSolved_CountingTime = 0;
3590 game.LevelSolved_CountingScore = 0;
3591 game.LevelSolved_CountingHealth = 0;
3593 game.panel.active = TRUE;
3595 game.no_time_limit = (level.time == 0);
3597 game.yamyam_content_nr = 0;
3598 game.robot_wheel_active = FALSE;
3599 game.magic_wall_active = FALSE;
3600 game.magic_wall_time_left = 0;
3601 game.light_time_left = 0;
3602 game.timegate_time_left = 0;
3603 game.switchgate_pos = 0;
3604 game.wind_direction = level.wind_direction_initial;
3607 game.score_final = 0;
3609 game.health = MAX_HEALTH;
3610 game.health_final = MAX_HEALTH;
3612 game.gems_still_needed = level.gems_needed;
3613 game.sokoban_fields_still_needed = 0;
3614 game.sokoban_objects_still_needed = 0;
3615 game.lights_still_needed = 0;
3616 game.players_still_needed = 0;
3617 game.friends_still_needed = 0;
3619 game.lenses_time_left = 0;
3620 game.magnify_time_left = 0;
3622 game.ball_active = level.ball_active_initial;
3623 game.ball_content_nr = 0;
3625 game.explosions_delayed = TRUE;
3627 game.envelope_active = FALSE;
3629 for (i = 0; i < NUM_BELTS; i++)
3631 game.belt_dir[i] = MV_NONE;
3632 game.belt_dir_nr[i] = 3; // not moving, next moving left
3635 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3636 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3638 #if DEBUG_INIT_PLAYER
3639 DebugPrintPlayerStatus("Player status at level initialization");
3642 SCAN_PLAYFIELD(x, y)
3644 Feld[x][y] = Last[x][y] = level.field[x][y];
3645 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3646 ChangeDelay[x][y] = 0;
3647 ChangePage[x][y] = -1;
3648 CustomValue[x][y] = 0; // initialized in InitField()
3649 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3651 WasJustMoving[x][y] = 0;
3652 WasJustFalling[x][y] = 0;
3653 CheckCollision[x][y] = 0;
3654 CheckImpact[x][y] = 0;
3656 Pushed[x][y] = FALSE;
3658 ChangeCount[x][y] = 0;
3659 ChangeEvent[x][y] = -1;
3661 ExplodePhase[x][y] = 0;
3662 ExplodeDelay[x][y] = 0;
3663 ExplodeField[x][y] = EX_TYPE_NONE;
3665 RunnerVisit[x][y] = 0;
3666 PlayerVisit[x][y] = 0;
3669 GfxRandom[x][y] = INIT_GFX_RANDOM();
3670 GfxElement[x][y] = EL_UNDEFINED;
3671 GfxAction[x][y] = ACTION_DEFAULT;
3672 GfxDir[x][y] = MV_NONE;
3673 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3676 SCAN_PLAYFIELD(x, y)
3678 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3680 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3682 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3685 InitField(x, y, TRUE);
3687 ResetGfxAnimation(x, y);
3692 for (i = 0; i < MAX_PLAYERS; i++)
3694 struct PlayerInfo *player = &stored_player[i];
3696 // set number of special actions for bored and sleeping animation
3697 player->num_special_action_bored =
3698 get_num_special_action(player->artwork_element,
3699 ACTION_BORING_1, ACTION_BORING_LAST);
3700 player->num_special_action_sleeping =
3701 get_num_special_action(player->artwork_element,
3702 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3705 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3706 emulate_sb ? EMU_SOKOBAN :
3707 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3709 // initialize type of slippery elements
3710 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3712 if (!IS_CUSTOM_ELEMENT(i))
3714 // default: elements slip down either to the left or right randomly
3715 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3717 // SP style elements prefer to slip down on the left side
3718 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3719 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3721 // BD style elements prefer to slip down on the left side
3722 if (game.emulation == EMU_BOULDERDASH)
3723 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3727 // initialize explosion and ignition delay
3728 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3730 if (!IS_CUSTOM_ELEMENT(i))
3733 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3734 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3735 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3736 int last_phase = (num_phase + 1) * delay;
3737 int half_phase = (num_phase / 2) * delay;
3739 element_info[i].explosion_delay = last_phase - 1;
3740 element_info[i].ignition_delay = half_phase;
3742 if (i == EL_BLACK_ORB)
3743 element_info[i].ignition_delay = 1;
3747 // correct non-moving belts to start moving left
3748 for (i = 0; i < NUM_BELTS; i++)
3749 if (game.belt_dir[i] == MV_NONE)
3750 game.belt_dir_nr[i] = 3; // not moving, next moving left
3752 #if USE_NEW_PLAYER_ASSIGNMENTS
3753 // use preferred player also in local single-player mode
3754 if (!network.enabled && !game.team_mode)
3756 int old_index_nr = local_player->index_nr;
3757 int new_index_nr = setup.network_player_nr;
3759 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3761 stored_player[old_index_nr].connected_locally = FALSE;
3762 stored_player[new_index_nr].connected_locally = TRUE;
3766 for (i = 0; i < MAX_PLAYERS; i++)
3768 stored_player[i].connected = FALSE;
3770 // in network game mode, the local player might not be the first player
3771 if (stored_player[i].connected_locally)
3772 local_player = &stored_player[i];
3775 if (!network.enabled)
3776 local_player->connected = TRUE;
3780 for (i = 0; i < MAX_PLAYERS; i++)
3781 stored_player[i].connected = tape.player_participates[i];
3783 else if (network.enabled)
3785 // add team mode players connected over the network (needed for correct
3786 // assignment of player figures from level to locally playing players)
3788 for (i = 0; i < MAX_PLAYERS; i++)
3789 if (stored_player[i].connected_network)
3790 stored_player[i].connected = TRUE;
3792 else if (game.team_mode)
3794 // try to guess locally connected team mode players (needed for correct
3795 // assignment of player figures from level to locally playing players)
3797 for (i = 0; i < MAX_PLAYERS; i++)
3798 if (setup.input[i].use_joystick ||
3799 setup.input[i].key.left != KSYM_UNDEFINED)
3800 stored_player[i].connected = TRUE;
3803 #if DEBUG_INIT_PLAYER
3804 DebugPrintPlayerStatus("Player status after level initialization");
3807 #if DEBUG_INIT_PLAYER
3809 printf("Reassigning players ...\n");
3812 // check if any connected player was not found in playfield
3813 for (i = 0; i < MAX_PLAYERS; i++)
3815 struct PlayerInfo *player = &stored_player[i];
3817 if (player->connected && !player->present)
3819 struct PlayerInfo *field_player = NULL;
3821 #if DEBUG_INIT_PLAYER
3823 printf("- looking for field player for player %d ...\n", i + 1);
3826 // assign first free player found that is present in the playfield
3828 // first try: look for unmapped playfield player that is not connected
3829 for (j = 0; j < MAX_PLAYERS; j++)
3830 if (field_player == NULL &&
3831 stored_player[j].present &&
3832 !stored_player[j].mapped &&
3833 !stored_player[j].connected)
3834 field_player = &stored_player[j];
3836 // second try: look for *any* unmapped playfield player
3837 for (j = 0; j < MAX_PLAYERS; j++)
3838 if (field_player == NULL &&
3839 stored_player[j].present &&
3840 !stored_player[j].mapped)
3841 field_player = &stored_player[j];
3843 if (field_player != NULL)
3845 int jx = field_player->jx, jy = field_player->jy;
3847 #if DEBUG_INIT_PLAYER
3849 printf("- found player %d\n", field_player->index_nr + 1);
3852 player->present = FALSE;
3853 player->active = FALSE;
3855 field_player->present = TRUE;
3856 field_player->active = TRUE;
3859 player->initial_element = field_player->initial_element;
3860 player->artwork_element = field_player->artwork_element;
3862 player->block_last_field = field_player->block_last_field;
3863 player->block_delay_adjustment = field_player->block_delay_adjustment;
3866 StorePlayer[jx][jy] = field_player->element_nr;
3868 field_player->jx = field_player->last_jx = jx;
3869 field_player->jy = field_player->last_jy = jy;
3871 if (local_player == player)
3872 local_player = field_player;
3874 map_player_action[field_player->index_nr] = i;
3876 field_player->mapped = TRUE;
3878 #if DEBUG_INIT_PLAYER
3880 printf("- map_player_action[%d] == %d\n",
3881 field_player->index_nr + 1, i + 1);
3886 if (player->connected && player->present)
3887 player->mapped = TRUE;
3890 #if DEBUG_INIT_PLAYER
3891 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
3896 // check if any connected player was not found in playfield
3897 for (i = 0; i < MAX_PLAYERS; i++)
3899 struct PlayerInfo *player = &stored_player[i];
3901 if (player->connected && !player->present)
3903 for (j = 0; j < MAX_PLAYERS; j++)
3905 struct PlayerInfo *field_player = &stored_player[j];
3906 int jx = field_player->jx, jy = field_player->jy;
3908 // assign first free player found that is present in the playfield
3909 if (field_player->present && !field_player->connected)
3911 player->present = TRUE;
3912 player->active = TRUE;
3914 field_player->present = FALSE;
3915 field_player->active = FALSE;
3917 player->initial_element = field_player->initial_element;
3918 player->artwork_element = field_player->artwork_element;
3920 player->block_last_field = field_player->block_last_field;
3921 player->block_delay_adjustment = field_player->block_delay_adjustment;
3923 StorePlayer[jx][jy] = player->element_nr;
3925 player->jx = player->last_jx = jx;
3926 player->jy = player->last_jy = jy;
3936 printf("::: local_player->present == %d\n", local_player->present);
3939 // set focus to local player for network games, else to all players
3940 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
3941 game.centered_player_nr_next = game.centered_player_nr;
3942 game.set_centered_player = FALSE;
3943 game.set_centered_player_wrap = FALSE;
3945 if (network_playing && tape.recording)
3947 // store client dependent player focus when recording network games
3948 tape.centered_player_nr_next = game.centered_player_nr_next;
3949 tape.set_centered_player = TRUE;
3954 // when playing a tape, eliminate all players who do not participate
3956 #if USE_NEW_PLAYER_ASSIGNMENTS
3958 if (!game.team_mode)
3960 for (i = 0; i < MAX_PLAYERS; i++)
3962 if (stored_player[i].active &&
3963 !tape.player_participates[map_player_action[i]])
3965 struct PlayerInfo *player = &stored_player[i];
3966 int jx = player->jx, jy = player->jy;
3968 #if DEBUG_INIT_PLAYER
3970 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
3973 player->active = FALSE;
3974 StorePlayer[jx][jy] = 0;
3975 Feld[jx][jy] = EL_EMPTY;
3982 for (i = 0; i < MAX_PLAYERS; i++)
3984 if (stored_player[i].active &&
3985 !tape.player_participates[i])
3987 struct PlayerInfo *player = &stored_player[i];
3988 int jx = player->jx, jy = player->jy;
3990 player->active = FALSE;
3991 StorePlayer[jx][jy] = 0;
3992 Feld[jx][jy] = EL_EMPTY;
3997 else if (!network.enabled && !game.team_mode) // && !tape.playing
3999 // when in single player mode, eliminate all but the local player
4001 for (i = 0; i < MAX_PLAYERS; i++)
4003 struct PlayerInfo *player = &stored_player[i];
4005 if (player->active && player != local_player)
4007 int jx = player->jx, jy = player->jy;
4009 player->active = FALSE;
4010 player->present = FALSE;
4012 StorePlayer[jx][jy] = 0;
4013 Feld[jx][jy] = EL_EMPTY;
4018 for (i = 0; i < MAX_PLAYERS; i++)
4019 if (stored_player[i].active)
4020 game.players_still_needed++;
4022 if (level.solved_by_one_player)
4023 game.players_still_needed = 1;
4025 // when recording the game, store which players take part in the game
4028 #if USE_NEW_PLAYER_ASSIGNMENTS
4029 for (i = 0; i < MAX_PLAYERS; i++)
4030 if (stored_player[i].connected)
4031 tape.player_participates[i] = TRUE;
4033 for (i = 0; i < MAX_PLAYERS; i++)
4034 if (stored_player[i].active)
4035 tape.player_participates[i] = TRUE;
4039 #if DEBUG_INIT_PLAYER
4040 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4043 if (BorderElement == EL_EMPTY)
4046 SBX_Right = lev_fieldx - SCR_FIELDX;
4048 SBY_Lower = lev_fieldy - SCR_FIELDY;
4053 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4055 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4058 if (full_lev_fieldx <= SCR_FIELDX)
4059 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4060 if (full_lev_fieldy <= SCR_FIELDY)
4061 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4063 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4065 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4068 // if local player not found, look for custom element that might create
4069 // the player (make some assumptions about the right custom element)
4070 if (!local_player->present)
4072 int start_x = 0, start_y = 0;
4073 int found_rating = 0;
4074 int found_element = EL_UNDEFINED;
4075 int player_nr = local_player->index_nr;
4077 SCAN_PLAYFIELD(x, y)
4079 int element = Feld[x][y];
4084 if (level.use_start_element[player_nr] &&
4085 level.start_element[player_nr] == element &&
4092 found_element = element;
4095 if (!IS_CUSTOM_ELEMENT(element))
4098 if (CAN_CHANGE(element))
4100 for (i = 0; i < element_info[element].num_change_pages; i++)
4102 // check for player created from custom element as single target
4103 content = element_info[element].change_page[i].target_element;
4104 is_player = ELEM_IS_PLAYER(content);
4106 if (is_player && (found_rating < 3 ||
4107 (found_rating == 3 && element < found_element)))
4113 found_element = element;
4118 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4120 // check for player created from custom element as explosion content
4121 content = element_info[element].content.e[xx][yy];
4122 is_player = ELEM_IS_PLAYER(content);
4124 if (is_player && (found_rating < 2 ||
4125 (found_rating == 2 && element < found_element)))
4127 start_x = x + xx - 1;
4128 start_y = y + yy - 1;
4131 found_element = element;
4134 if (!CAN_CHANGE(element))
4137 for (i = 0; i < element_info[element].num_change_pages; i++)
4139 // check for player created from custom element as extended target
4141 element_info[element].change_page[i].target_content.e[xx][yy];
4143 is_player = ELEM_IS_PLAYER(content);
4145 if (is_player && (found_rating < 1 ||
4146 (found_rating == 1 && element < found_element)))
4148 start_x = x + xx - 1;
4149 start_y = y + yy - 1;
4152 found_element = element;
4158 scroll_x = SCROLL_POSITION_X(start_x);
4159 scroll_y = SCROLL_POSITION_Y(start_y);
4163 scroll_x = SCROLL_POSITION_X(local_player->jx);
4164 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4167 // !!! FIX THIS (START) !!!
4168 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4170 InitGameEngine_EM();
4172 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4174 InitGameEngine_SP();
4176 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4178 InitGameEngine_MM();
4182 DrawLevel(REDRAW_FIELD);
4185 // after drawing the level, correct some elements
4186 if (game.timegate_time_left == 0)
4187 CloseAllOpenTimegates();
4190 // blit playfield from scroll buffer to normal back buffer for fading in
4191 BlitScreenToBitmap(backbuffer);
4192 // !!! FIX THIS (END) !!!
4194 DrawMaskedBorder(fade_mask);
4199 // full screen redraw is required at this point in the following cases:
4200 // - special editor door undrawn when game was started from level editor
4201 // - drawing area (playfield) was changed and has to be removed completely
4202 redraw_mask = REDRAW_ALL;
4206 if (!game.restart_level)
4208 // copy default game door content to main double buffer
4210 // !!! CHECK AGAIN !!!
4211 SetPanelBackground();
4212 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4213 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4216 SetPanelBackground();
4217 SetDrawBackgroundMask(REDRAW_DOOR_1);
4219 UpdateAndDisplayGameControlValues();
4221 if (!game.restart_level)
4227 CreateGameButtons();
4232 // copy actual game door content to door double buffer for OpenDoor()
4233 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4235 OpenDoor(DOOR_OPEN_ALL);
4237 KeyboardAutoRepeatOffUnlessAutoplay();
4239 #if DEBUG_INIT_PLAYER
4240 DebugPrintPlayerStatus("Player status (final)");
4249 if (!game.restart_level && !tape.playing)
4251 LevelStats_incPlayed(level_nr);
4253 SaveLevelSetup_SeriesInfo();
4256 game.restart_level = FALSE;
4257 game.restart_game_message = NULL;
4258 game.request_active = FALSE;
4260 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4261 InitGameActions_MM();
4263 SaveEngineSnapshotToListInitial();
4265 if (!game.restart_level)
4267 PlaySound(SND_GAME_STARTING);
4269 if (setup.sound_music)
4274 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4275 int actual_player_x, int actual_player_y)
4277 // this is used for non-R'n'D game engines to update certain engine values
4279 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4281 actual_player_x = correctLevelPosX_EM(actual_player_x);
4282 actual_player_y = correctLevelPosY_EM(actual_player_y);
4285 // needed to determine if sounds are played within the visible screen area
4286 scroll_x = actual_scroll_x;
4287 scroll_y = actual_scroll_y;
4289 // needed to get player position for "follow finger" playing input method
4290 local_player->jx = actual_player_x;
4291 local_player->jy = actual_player_y;
4294 void InitMovDir(int x, int y)
4296 int i, element = Feld[x][y];
4297 static int xy[4][2] =
4304 static int direction[3][4] =
4306 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4307 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4308 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4317 Feld[x][y] = EL_BUG;
4318 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4321 case EL_SPACESHIP_RIGHT:
4322 case EL_SPACESHIP_UP:
4323 case EL_SPACESHIP_LEFT:
4324 case EL_SPACESHIP_DOWN:
4325 Feld[x][y] = EL_SPACESHIP;
4326 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4329 case EL_BD_BUTTERFLY_RIGHT:
4330 case EL_BD_BUTTERFLY_UP:
4331 case EL_BD_BUTTERFLY_LEFT:
4332 case EL_BD_BUTTERFLY_DOWN:
4333 Feld[x][y] = EL_BD_BUTTERFLY;
4334 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4337 case EL_BD_FIREFLY_RIGHT:
4338 case EL_BD_FIREFLY_UP:
4339 case EL_BD_FIREFLY_LEFT:
4340 case EL_BD_FIREFLY_DOWN:
4341 Feld[x][y] = EL_BD_FIREFLY;
4342 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4345 case EL_PACMAN_RIGHT:
4347 case EL_PACMAN_LEFT:
4348 case EL_PACMAN_DOWN:
4349 Feld[x][y] = EL_PACMAN;
4350 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4353 case EL_YAMYAM_LEFT:
4354 case EL_YAMYAM_RIGHT:
4356 case EL_YAMYAM_DOWN:
4357 Feld[x][y] = EL_YAMYAM;
4358 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4361 case EL_SP_SNIKSNAK:
4362 MovDir[x][y] = MV_UP;
4365 case EL_SP_ELECTRON:
4366 MovDir[x][y] = MV_LEFT;
4373 Feld[x][y] = EL_MOLE;
4374 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4378 if (IS_CUSTOM_ELEMENT(element))
4380 struct ElementInfo *ei = &element_info[element];
4381 int move_direction_initial = ei->move_direction_initial;
4382 int move_pattern = ei->move_pattern;
4384 if (move_direction_initial == MV_START_PREVIOUS)
4386 if (MovDir[x][y] != MV_NONE)
4389 move_direction_initial = MV_START_AUTOMATIC;
4392 if (move_direction_initial == MV_START_RANDOM)
4393 MovDir[x][y] = 1 << RND(4);
4394 else if (move_direction_initial & MV_ANY_DIRECTION)
4395 MovDir[x][y] = move_direction_initial;
4396 else if (move_pattern == MV_ALL_DIRECTIONS ||
4397 move_pattern == MV_TURNING_LEFT ||
4398 move_pattern == MV_TURNING_RIGHT ||
4399 move_pattern == MV_TURNING_LEFT_RIGHT ||
4400 move_pattern == MV_TURNING_RIGHT_LEFT ||
4401 move_pattern == MV_TURNING_RANDOM)
4402 MovDir[x][y] = 1 << RND(4);
4403 else if (move_pattern == MV_HORIZONTAL)
4404 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4405 else if (move_pattern == MV_VERTICAL)
4406 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4407 else if (move_pattern & MV_ANY_DIRECTION)
4408 MovDir[x][y] = element_info[element].move_pattern;
4409 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4410 move_pattern == MV_ALONG_RIGHT_SIDE)
4412 // use random direction as default start direction
4413 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4414 MovDir[x][y] = 1 << RND(4);
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 (move_pattern == MV_ALONG_RIGHT_SIDE)
4424 MovDir[x][y] = direction[0][i];
4426 MovDir[x][y] = direction[1][i];
4435 MovDir[x][y] = 1 << RND(4);
4437 if (element != EL_BUG &&
4438 element != EL_SPACESHIP &&
4439 element != EL_BD_BUTTERFLY &&
4440 element != EL_BD_FIREFLY)
4443 for (i = 0; i < NUM_DIRECTIONS; i++)
4445 int x1 = x + xy[i][0];
4446 int y1 = y + xy[i][1];
4448 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4450 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4452 MovDir[x][y] = direction[0][i];
4455 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4456 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4458 MovDir[x][y] = direction[1][i];
4467 GfxDir[x][y] = MovDir[x][y];
4470 void InitAmoebaNr(int x, int y)
4473 int group_nr = AmoebeNachbarNr(x, y);
4477 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4479 if (AmoebaCnt[i] == 0)
4487 AmoebaNr[x][y] = group_nr;
4488 AmoebaCnt[group_nr]++;
4489 AmoebaCnt2[group_nr]++;
4492 static void LevelSolved(void)
4494 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4495 game.players_still_needed > 0)
4498 game.LevelSolved = TRUE;
4499 game.GameOver = TRUE;
4501 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4502 game_em.lev->score :
4503 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4506 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4507 MM_HEALTH(game_mm.laser_overload_value) :
4510 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4511 game.LevelSolved_CountingScore = game.score_final;
4512 game.LevelSolved_CountingHealth = game.health_final;
4517 static int time_count_steps;
4518 static int time, time_final;
4519 static int score, score_final;
4520 static int health, health_final;
4521 static int game_over_delay_1 = 0;
4522 static int game_over_delay_2 = 0;
4523 static int game_over_delay_3 = 0;
4524 int game_over_delay_value_1 = 50;
4525 int game_over_delay_value_2 = 25;
4526 int game_over_delay_value_3 = 50;
4528 if (!game.LevelSolved_GameWon)
4532 // do not start end game actions before the player stops moving (to exit)
4533 if (local_player->active && local_player->MovPos)
4536 game.LevelSolved_GameWon = TRUE;
4537 game.LevelSolved_SaveTape = tape.recording;
4538 game.LevelSolved_SaveScore = !tape.playing;
4542 LevelStats_incSolved(level_nr);
4544 SaveLevelSetup_SeriesInfo();
4547 if (tape.auto_play) // tape might already be stopped here
4548 tape.auto_play_level_solved = TRUE;
4552 game_over_delay_1 = 0;
4553 game_over_delay_2 = 0;
4554 game_over_delay_3 = game_over_delay_value_3;
4556 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4557 score = score_final = game.score_final;
4558 health = health_final = game.health_final;
4560 if (level.score[SC_TIME_BONUS] > 0)
4565 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4567 else if (game.no_time_limit && TimePlayed < 999)
4570 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4573 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4575 game_over_delay_1 = game_over_delay_value_1;
4577 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4580 score_final += health * level.score[SC_TIME_BONUS];
4582 game_over_delay_2 = game_over_delay_value_2;
4585 game.score_final = score_final;
4586 game.health_final = health_final;
4589 if (level_editor_test_game)
4592 score = score_final;
4594 game.LevelSolved_CountingTime = time;
4595 game.LevelSolved_CountingScore = score;
4597 game_panel_controls[GAME_PANEL_TIME].value = time;
4598 game_panel_controls[GAME_PANEL_SCORE].value = score;
4600 DisplayGameControlValues();
4603 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4605 // check if last player has left the level
4606 if (game.exit_x >= 0 &&
4609 int x = game.exit_x;
4610 int y = game.exit_y;
4611 int element = Feld[x][y];
4613 // close exit door after last player
4614 if ((game.all_players_gone &&
4615 (element == EL_EXIT_OPEN ||
4616 element == EL_SP_EXIT_OPEN ||
4617 element == EL_STEEL_EXIT_OPEN)) ||
4618 element == EL_EM_EXIT_OPEN ||
4619 element == EL_EM_STEEL_EXIT_OPEN)
4623 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4624 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4625 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4626 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4627 EL_EM_STEEL_EXIT_CLOSING);
4629 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4632 // player disappears
4633 DrawLevelField(x, y);
4636 for (i = 0; i < MAX_PLAYERS; i++)
4638 struct PlayerInfo *player = &stored_player[i];
4640 if (player->present)
4642 RemovePlayer(player);
4644 // player disappears
4645 DrawLevelField(player->jx, player->jy);
4650 PlaySound(SND_GAME_WINNING);
4653 if (game_over_delay_1 > 0)
4655 game_over_delay_1--;
4660 if (time != time_final)
4662 int time_to_go = ABS(time_final - time);
4663 int time_count_dir = (time < time_final ? +1 : -1);
4665 if (time_to_go < time_count_steps)
4666 time_count_steps = 1;
4668 time += time_count_steps * time_count_dir;
4669 score += time_count_steps * level.score[SC_TIME_BONUS];
4671 game.LevelSolved_CountingTime = time;
4672 game.LevelSolved_CountingScore = score;
4674 game_panel_controls[GAME_PANEL_TIME].value = time;
4675 game_panel_controls[GAME_PANEL_SCORE].value = score;
4677 DisplayGameControlValues();
4679 if (time == time_final)
4680 StopSound(SND_GAME_LEVELTIME_BONUS);
4681 else if (setup.sound_loops)
4682 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4684 PlaySound(SND_GAME_LEVELTIME_BONUS);
4689 if (game_over_delay_2 > 0)
4691 game_over_delay_2--;
4696 if (health != health_final)
4698 int health_count_dir = (health < health_final ? +1 : -1);
4700 health += health_count_dir;
4701 score += level.score[SC_TIME_BONUS];
4703 game.LevelSolved_CountingHealth = health;
4704 game.LevelSolved_CountingScore = score;
4706 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4707 game_panel_controls[GAME_PANEL_SCORE].value = score;
4709 DisplayGameControlValues();
4711 if (health == health_final)
4712 StopSound(SND_GAME_LEVELTIME_BONUS);
4713 else if (setup.sound_loops)
4714 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4716 PlaySound(SND_GAME_LEVELTIME_BONUS);
4721 game.panel.active = FALSE;
4723 if (game_over_delay_3 > 0)
4725 game_over_delay_3--;
4735 // used instead of "level_nr" (needed for network games)
4736 int last_level_nr = levelset.level_nr;
4739 game.LevelSolved_GameEnd = TRUE;
4741 if (game.LevelSolved_SaveTape)
4743 // make sure that request dialog to save tape does not open door again
4744 if (!global.use_envelope_request)
4745 CloseDoor(DOOR_CLOSE_1);
4747 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4750 // if no tape is to be saved, close both doors simultaneously
4751 CloseDoor(DOOR_CLOSE_ALL);
4753 if (level_editor_test_game)
4755 SetGameStatus(GAME_MODE_MAIN);
4762 if (!game.LevelSolved_SaveScore)
4764 SetGameStatus(GAME_MODE_MAIN);
4771 if (level_nr == leveldir_current->handicap_level)
4773 leveldir_current->handicap_level++;
4775 SaveLevelSetup_SeriesInfo();
4778 if (setup.increment_levels &&
4779 level_nr < leveldir_current->last_level &&
4782 level_nr++; // advance to next level
4783 TapeErase(); // start with empty tape
4785 if (setup.auto_play_next_level)
4787 LoadLevel(level_nr);
4789 SaveLevelSetup_SeriesInfo();
4793 hi_pos = NewHiScore(last_level_nr);
4795 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4797 SetGameStatus(GAME_MODE_SCORES);
4799 DrawHallOfFame(last_level_nr, hi_pos);
4801 else if (setup.auto_play_next_level && setup.increment_levels &&
4802 last_level_nr < leveldir_current->last_level &&
4805 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4809 SetGameStatus(GAME_MODE_MAIN);
4815 int NewHiScore(int level_nr)
4819 boolean one_score_entry_per_name = !program.many_scores_per_name;
4821 LoadScore(level_nr);
4823 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4824 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4827 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4829 if (game.score_final > highscore[k].Score)
4831 // player has made it to the hall of fame
4833 if (k < MAX_SCORE_ENTRIES - 1)
4835 int m = MAX_SCORE_ENTRIES - 1;
4837 if (one_score_entry_per_name)
4839 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4840 if (strEqual(setup.player_name, highscore[l].Name))
4843 if (m == k) // player's new highscore overwrites his old one
4847 for (l = m; l > k; l--)
4849 strcpy(highscore[l].Name, highscore[l - 1].Name);
4850 highscore[l].Score = highscore[l - 1].Score;
4856 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4857 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4858 highscore[k].Score = game.score_final;
4863 else if (one_score_entry_per_name &&
4864 !strncmp(setup.player_name, highscore[k].Name,
4865 MAX_PLAYER_NAME_LEN))
4866 break; // player already there with a higher score
4870 SaveScore(level_nr);
4875 static int getElementMoveStepsizeExt(int x, int y, int direction)
4877 int element = Feld[x][y];
4878 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4879 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4880 int horiz_move = (dx != 0);
4881 int sign = (horiz_move ? dx : dy);
4882 int step = sign * element_info[element].move_stepsize;
4884 // special values for move stepsize for spring and things on conveyor belt
4887 if (CAN_FALL(element) &&
4888 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
4889 step = sign * MOVE_STEPSIZE_NORMAL / 2;
4890 else if (element == EL_SPRING)
4891 step = sign * MOVE_STEPSIZE_NORMAL * 2;
4897 static int getElementMoveStepsize(int x, int y)
4899 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
4902 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
4904 if (player->GfxAction != action || player->GfxDir != dir)
4906 player->GfxAction = action;
4907 player->GfxDir = dir;
4909 player->StepFrame = 0;
4913 static void ResetGfxFrame(int x, int y)
4915 // profiling showed that "autotest" spends 10~20% of its time in this function
4916 if (DrawingDeactivatedField())
4919 int element = Feld[x][y];
4920 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
4922 if (graphic_info[graphic].anim_global_sync)
4923 GfxFrame[x][y] = FrameCounter;
4924 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
4925 GfxFrame[x][y] = CustomValue[x][y];
4926 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
4927 GfxFrame[x][y] = element_info[element].collect_score;
4928 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
4929 GfxFrame[x][y] = ChangeDelay[x][y];
4932 static void ResetGfxAnimation(int x, int y)
4934 GfxAction[x][y] = ACTION_DEFAULT;
4935 GfxDir[x][y] = MovDir[x][y];
4938 ResetGfxFrame(x, y);
4941 static void ResetRandomAnimationValue(int x, int y)
4943 GfxRandom[x][y] = INIT_GFX_RANDOM();
4946 static void InitMovingField(int x, int y, int direction)
4948 int element = Feld[x][y];
4949 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4950 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4953 boolean is_moving_before, is_moving_after;
4955 // check if element was/is moving or being moved before/after mode change
4956 is_moving_before = (WasJustMoving[x][y] != 0);
4957 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
4959 // reset animation only for moving elements which change direction of moving
4960 // or which just started or stopped moving
4961 // (else CEs with property "can move" / "not moving" are reset each frame)
4962 if (is_moving_before != is_moving_after ||
4963 direction != MovDir[x][y])
4964 ResetGfxAnimation(x, y);
4966 MovDir[x][y] = direction;
4967 GfxDir[x][y] = direction;
4969 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
4970 direction == MV_DOWN && CAN_FALL(element) ?
4971 ACTION_FALLING : ACTION_MOVING);
4973 // this is needed for CEs with property "can move" / "not moving"
4975 if (is_moving_after)
4977 if (Feld[newx][newy] == EL_EMPTY)
4978 Feld[newx][newy] = EL_BLOCKED;
4980 MovDir[newx][newy] = MovDir[x][y];
4982 CustomValue[newx][newy] = CustomValue[x][y];
4984 GfxFrame[newx][newy] = GfxFrame[x][y];
4985 GfxRandom[newx][newy] = GfxRandom[x][y];
4986 GfxAction[newx][newy] = GfxAction[x][y];
4987 GfxDir[newx][newy] = GfxDir[x][y];
4991 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
4993 int direction = MovDir[x][y];
4994 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
4995 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5001 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5003 int oldx = x, oldy = y;
5004 int direction = MovDir[x][y];
5006 if (direction == MV_LEFT)
5008 else if (direction == MV_RIGHT)
5010 else if (direction == MV_UP)
5012 else if (direction == MV_DOWN)
5015 *comes_from_x = oldx;
5016 *comes_from_y = oldy;
5019 static int MovingOrBlocked2Element(int x, int y)
5021 int element = Feld[x][y];
5023 if (element == EL_BLOCKED)
5027 Blocked2Moving(x, y, &oldx, &oldy);
5028 return Feld[oldx][oldy];
5034 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5036 // like MovingOrBlocked2Element(), but if element is moving
5037 // and (x,y) is the field the moving element is just leaving,
5038 // return EL_BLOCKED instead of the element value
5039 int element = Feld[x][y];
5041 if (IS_MOVING(x, y))
5043 if (element == EL_BLOCKED)
5047 Blocked2Moving(x, y, &oldx, &oldy);
5048 return Feld[oldx][oldy];
5057 static void RemoveField(int x, int y)
5059 Feld[x][y] = EL_EMPTY;
5065 CustomValue[x][y] = 0;
5068 ChangeDelay[x][y] = 0;
5069 ChangePage[x][y] = -1;
5070 Pushed[x][y] = FALSE;
5072 GfxElement[x][y] = EL_UNDEFINED;
5073 GfxAction[x][y] = ACTION_DEFAULT;
5074 GfxDir[x][y] = MV_NONE;
5077 static void RemoveMovingField(int x, int y)
5079 int oldx = x, oldy = y, newx = x, newy = y;
5080 int element = Feld[x][y];
5081 int next_element = EL_UNDEFINED;
5083 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5086 if (IS_MOVING(x, y))
5088 Moving2Blocked(x, y, &newx, &newy);
5090 if (Feld[newx][newy] != EL_BLOCKED)
5092 // element is moving, but target field is not free (blocked), but
5093 // already occupied by something different (example: acid pool);
5094 // in this case, only remove the moving field, but not the target
5096 RemoveField(oldx, oldy);
5098 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5100 TEST_DrawLevelField(oldx, oldy);
5105 else if (element == EL_BLOCKED)
5107 Blocked2Moving(x, y, &oldx, &oldy);
5108 if (!IS_MOVING(oldx, oldy))
5112 if (element == EL_BLOCKED &&
5113 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5114 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5115 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5116 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5117 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5118 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5119 next_element = get_next_element(Feld[oldx][oldy]);
5121 RemoveField(oldx, oldy);
5122 RemoveField(newx, newy);
5124 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5126 if (next_element != EL_UNDEFINED)
5127 Feld[oldx][oldy] = next_element;
5129 TEST_DrawLevelField(oldx, oldy);
5130 TEST_DrawLevelField(newx, newy);
5133 void DrawDynamite(int x, int y)
5135 int sx = SCREENX(x), sy = SCREENY(y);
5136 int graphic = el2img(Feld[x][y]);
5139 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5142 if (IS_WALKABLE_INSIDE(Back[x][y]))
5146 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5147 else if (Store[x][y])
5148 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5150 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5152 if (Back[x][y] || Store[x][y])
5153 DrawGraphicThruMask(sx, sy, graphic, frame);
5155 DrawGraphic(sx, sy, graphic, frame);
5158 static void CheckDynamite(int x, int y)
5160 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5164 if (MovDelay[x][y] != 0)
5167 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5173 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5178 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5180 boolean num_checked_players = 0;
5183 for (i = 0; i < MAX_PLAYERS; i++)
5185 if (stored_player[i].active)
5187 int sx = stored_player[i].jx;
5188 int sy = stored_player[i].jy;
5190 if (num_checked_players == 0)
5197 *sx1 = MIN(*sx1, sx);
5198 *sy1 = MIN(*sy1, sy);
5199 *sx2 = MAX(*sx2, sx);
5200 *sy2 = MAX(*sy2, sy);
5203 num_checked_players++;
5208 static boolean checkIfAllPlayersFitToScreen_RND(void)
5210 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5212 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5214 return (sx2 - sx1 < SCR_FIELDX &&
5215 sy2 - sy1 < SCR_FIELDY);
5218 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5220 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5222 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5224 *sx = (sx1 + sx2) / 2;
5225 *sy = (sy1 + sy2) / 2;
5228 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5229 boolean center_screen, boolean quick_relocation)
5231 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5232 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5233 boolean no_delay = (tape.warp_forward);
5234 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5235 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5236 int new_scroll_x, new_scroll_y;
5238 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5240 // case 1: quick relocation inside visible screen (without scrolling)
5247 if (!level.shifted_relocation || center_screen)
5249 // relocation _with_ centering of screen
5251 new_scroll_x = SCROLL_POSITION_X(x);
5252 new_scroll_y = SCROLL_POSITION_Y(y);
5256 // relocation _without_ centering of screen
5258 int center_scroll_x = SCROLL_POSITION_X(old_x);
5259 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5260 int offset_x = x + (scroll_x - center_scroll_x);
5261 int offset_y = y + (scroll_y - center_scroll_y);
5263 // for new screen position, apply previous offset to center position
5264 new_scroll_x = SCROLL_POSITION_X(offset_x);
5265 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5268 if (quick_relocation)
5270 // case 2: quick relocation (redraw without visible scrolling)
5272 scroll_x = new_scroll_x;
5273 scroll_y = new_scroll_y;
5280 // case 3: visible relocation (with scrolling to new position)
5282 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5284 SetVideoFrameDelay(wait_delay_value);
5286 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5288 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5289 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5291 if (dx == 0 && dy == 0) // no scrolling needed at all
5297 // set values for horizontal/vertical screen scrolling (half tile size)
5298 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5299 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5300 int pos_x = dx * TILEX / 2;
5301 int pos_y = dy * TILEY / 2;
5302 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5303 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5305 ScrollLevel(dx, dy);
5308 // scroll in two steps of half tile size to make things smoother
5309 BlitScreenToBitmapExt_RND(window, fx, fy);
5311 // scroll second step to align at full tile size
5312 BlitScreenToBitmap(window);
5318 SetVideoFrameDelay(frame_delay_value_old);
5321 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5323 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5324 int player_nr = GET_PLAYER_NR(el_player);
5325 struct PlayerInfo *player = &stored_player[player_nr];
5326 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5327 boolean no_delay = (tape.warp_forward);
5328 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5329 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5330 int old_jx = player->jx;
5331 int old_jy = player->jy;
5332 int old_element = Feld[old_jx][old_jy];
5333 int element = Feld[jx][jy];
5334 boolean player_relocated = (old_jx != jx || old_jy != jy);
5336 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5337 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5338 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5339 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5340 int leave_side_horiz = move_dir_horiz;
5341 int leave_side_vert = move_dir_vert;
5342 int enter_side = enter_side_horiz | enter_side_vert;
5343 int leave_side = leave_side_horiz | leave_side_vert;
5345 if (player->buried) // do not reanimate dead player
5348 if (!player_relocated) // no need to relocate the player
5351 if (IS_PLAYER(jx, jy)) // player already placed at new position
5353 RemoveField(jx, jy); // temporarily remove newly placed player
5354 DrawLevelField(jx, jy);
5357 if (player->present)
5359 while (player->MovPos)
5361 ScrollPlayer(player, SCROLL_GO_ON);
5362 ScrollScreen(NULL, SCROLL_GO_ON);
5364 AdvanceFrameAndPlayerCounters(player->index_nr);
5368 BackToFront_WithFrameDelay(wait_delay_value);
5371 DrawPlayer(player); // needed here only to cleanup last field
5372 DrawLevelField(player->jx, player->jy); // remove player graphic
5374 player->is_moving = FALSE;
5377 if (IS_CUSTOM_ELEMENT(old_element))
5378 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5380 player->index_bit, leave_side);
5382 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5384 player->index_bit, leave_side);
5386 Feld[jx][jy] = el_player;
5387 InitPlayerField(jx, jy, el_player, TRUE);
5389 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5390 possible that the relocation target field did not contain a player element,
5391 but a walkable element, to which the new player was relocated -- in this
5392 case, restore that (already initialized!) element on the player field */
5393 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5395 Feld[jx][jy] = element; // restore previously existing element
5398 // only visually relocate centered player
5399 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5400 FALSE, level.instant_relocation);
5402 TestIfPlayerTouchesBadThing(jx, jy);
5403 TestIfPlayerTouchesCustomElement(jx, jy);
5405 if (IS_CUSTOM_ELEMENT(element))
5406 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5407 player->index_bit, enter_side);
5409 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5410 player->index_bit, enter_side);
5412 if (player->is_switching)
5414 /* ensure that relocation while still switching an element does not cause
5415 a new element to be treated as also switched directly after relocation
5416 (this is important for teleporter switches that teleport the player to
5417 a place where another teleporter switch is in the same direction, which
5418 would then incorrectly be treated as immediately switched before the
5419 direction key that caused the switch was released) */
5421 player->switch_x += jx - old_jx;
5422 player->switch_y += jy - old_jy;
5426 static void Explode(int ex, int ey, int phase, int mode)
5432 // !!! eliminate this variable !!!
5433 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5435 if (game.explosions_delayed)
5437 ExplodeField[ex][ey] = mode;
5441 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5443 int center_element = Feld[ex][ey];
5444 int artwork_element, explosion_element; // set these values later
5446 // remove things displayed in background while burning dynamite
5447 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5450 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5452 // put moving element to center field (and let it explode there)
5453 center_element = MovingOrBlocked2Element(ex, ey);
5454 RemoveMovingField(ex, ey);
5455 Feld[ex][ey] = center_element;
5458 // now "center_element" is finally determined -- set related values now
5459 artwork_element = center_element; // for custom player artwork
5460 explosion_element = center_element; // for custom player artwork
5462 if (IS_PLAYER(ex, ey))
5464 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5466 artwork_element = stored_player[player_nr].artwork_element;
5468 if (level.use_explosion_element[player_nr])
5470 explosion_element = level.explosion_element[player_nr];
5471 artwork_element = explosion_element;
5475 if (mode == EX_TYPE_NORMAL ||
5476 mode == EX_TYPE_CENTER ||
5477 mode == EX_TYPE_CROSS)
5478 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5480 last_phase = element_info[explosion_element].explosion_delay + 1;
5482 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5484 int xx = x - ex + 1;
5485 int yy = y - ey + 1;
5488 if (!IN_LEV_FIELD(x, y) ||
5489 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5490 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5493 element = Feld[x][y];
5495 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5497 element = MovingOrBlocked2Element(x, y);
5499 if (!IS_EXPLOSION_PROOF(element))
5500 RemoveMovingField(x, y);
5503 // indestructible elements can only explode in center (but not flames)
5504 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5505 mode == EX_TYPE_BORDER)) ||
5506 element == EL_FLAMES)
5509 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5510 behaviour, for example when touching a yamyam that explodes to rocks
5511 with active deadly shield, a rock is created under the player !!! */
5512 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5514 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5515 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5516 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5518 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5521 if (IS_ACTIVE_BOMB(element))
5523 // re-activate things under the bomb like gate or penguin
5524 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5531 // save walkable background elements while explosion on same tile
5532 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5533 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5534 Back[x][y] = element;
5536 // ignite explodable elements reached by other explosion
5537 if (element == EL_EXPLOSION)
5538 element = Store2[x][y];
5540 if (AmoebaNr[x][y] &&
5541 (element == EL_AMOEBA_FULL ||
5542 element == EL_BD_AMOEBA ||
5543 element == EL_AMOEBA_GROWING))
5545 AmoebaCnt[AmoebaNr[x][y]]--;
5546 AmoebaCnt2[AmoebaNr[x][y]]--;
5551 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5553 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5555 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5557 if (PLAYERINFO(ex, ey)->use_murphy)
5558 Store[x][y] = EL_EMPTY;
5561 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5562 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5563 else if (ELEM_IS_PLAYER(center_element))
5564 Store[x][y] = EL_EMPTY;
5565 else if (center_element == EL_YAMYAM)
5566 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5567 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5568 Store[x][y] = element_info[center_element].content.e[xx][yy];
5570 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5571 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5572 // otherwise) -- FIX THIS !!!
5573 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5574 Store[x][y] = element_info[element].content.e[1][1];
5576 else if (!CAN_EXPLODE(element))
5577 Store[x][y] = element_info[element].content.e[1][1];
5580 Store[x][y] = EL_EMPTY;
5582 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5583 center_element == EL_AMOEBA_TO_DIAMOND)
5584 Store2[x][y] = element;
5586 Feld[x][y] = EL_EXPLOSION;
5587 GfxElement[x][y] = artwork_element;
5589 ExplodePhase[x][y] = 1;
5590 ExplodeDelay[x][y] = last_phase;
5595 if (center_element == EL_YAMYAM)
5596 game.yamyam_content_nr =
5597 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5609 GfxFrame[x][y] = 0; // restart explosion animation
5611 last_phase = ExplodeDelay[x][y];
5613 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5615 // this can happen if the player leaves an explosion just in time
5616 if (GfxElement[x][y] == EL_UNDEFINED)
5617 GfxElement[x][y] = EL_EMPTY;
5619 border_element = Store2[x][y];
5620 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5621 border_element = StorePlayer[x][y];
5623 if (phase == element_info[border_element].ignition_delay ||
5624 phase == last_phase)
5626 boolean border_explosion = FALSE;
5628 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5629 !PLAYER_EXPLOSION_PROTECTED(x, y))
5631 KillPlayerUnlessExplosionProtected(x, y);
5632 border_explosion = TRUE;
5634 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5636 Feld[x][y] = Store2[x][y];
5639 border_explosion = TRUE;
5641 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5643 AmoebeUmwandeln(x, y);
5645 border_explosion = TRUE;
5648 // if an element just explodes due to another explosion (chain-reaction),
5649 // do not immediately end the new explosion when it was the last frame of
5650 // the explosion (as it would be done in the following "if"-statement!)
5651 if (border_explosion && phase == last_phase)
5655 if (phase == last_phase)
5659 element = Feld[x][y] = Store[x][y];
5660 Store[x][y] = Store2[x][y] = 0;
5661 GfxElement[x][y] = EL_UNDEFINED;
5663 // player can escape from explosions and might therefore be still alive
5664 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5665 element <= EL_PLAYER_IS_EXPLODING_4)
5667 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5668 int explosion_element = EL_PLAYER_1 + player_nr;
5669 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5670 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5672 if (level.use_explosion_element[player_nr])
5673 explosion_element = level.explosion_element[player_nr];
5675 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5676 element_info[explosion_element].content.e[xx][yy]);
5679 // restore probably existing indestructible background element
5680 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5681 element = Feld[x][y] = Back[x][y];
5684 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5685 GfxDir[x][y] = MV_NONE;
5686 ChangeDelay[x][y] = 0;
5687 ChangePage[x][y] = -1;
5689 CustomValue[x][y] = 0;
5691 InitField_WithBug2(x, y, FALSE);
5693 TEST_DrawLevelField(x, y);
5695 TestIfElementTouchesCustomElement(x, y);
5697 if (GFX_CRUMBLED(element))
5698 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5700 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5701 StorePlayer[x][y] = 0;
5703 if (ELEM_IS_PLAYER(element))
5704 RelocatePlayer(x, y, element);
5706 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5708 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5709 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5712 TEST_DrawLevelFieldCrumbled(x, y);
5714 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5716 DrawLevelElement(x, y, Back[x][y]);
5717 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5719 else if (IS_WALKABLE_UNDER(Back[x][y]))
5721 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5722 DrawLevelElementThruMask(x, y, Back[x][y]);
5724 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5725 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5729 static void DynaExplode(int ex, int ey)
5732 int dynabomb_element = Feld[ex][ey];
5733 int dynabomb_size = 1;
5734 boolean dynabomb_xl = FALSE;
5735 struct PlayerInfo *player;
5736 static int xy[4][2] =
5744 if (IS_ACTIVE_BOMB(dynabomb_element))
5746 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5747 dynabomb_size = player->dynabomb_size;
5748 dynabomb_xl = player->dynabomb_xl;
5749 player->dynabombs_left++;
5752 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5754 for (i = 0; i < NUM_DIRECTIONS; i++)
5756 for (j = 1; j <= dynabomb_size; j++)
5758 int x = ex + j * xy[i][0];
5759 int y = ey + j * xy[i][1];
5762 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5765 element = Feld[x][y];
5767 // do not restart explosions of fields with active bombs
5768 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5771 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5773 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5774 !IS_DIGGABLE(element) && !dynabomb_xl)
5780 void Bang(int x, int y)
5782 int element = MovingOrBlocked2Element(x, y);
5783 int explosion_type = EX_TYPE_NORMAL;
5785 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5787 struct PlayerInfo *player = PLAYERINFO(x, y);
5789 element = Feld[x][y] = player->initial_element;
5791 if (level.use_explosion_element[player->index_nr])
5793 int explosion_element = level.explosion_element[player->index_nr];
5795 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5796 explosion_type = EX_TYPE_CROSS;
5797 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5798 explosion_type = EX_TYPE_CENTER;
5806 case EL_BD_BUTTERFLY:
5809 case EL_DARK_YAMYAM:
5813 RaiseScoreElement(element);
5816 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5817 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5818 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5819 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5820 case EL_DYNABOMB_INCREASE_NUMBER:
5821 case EL_DYNABOMB_INCREASE_SIZE:
5822 case EL_DYNABOMB_INCREASE_POWER:
5823 explosion_type = EX_TYPE_DYNA;
5826 case EL_DC_LANDMINE:
5827 explosion_type = EX_TYPE_CENTER;
5832 case EL_LAMP_ACTIVE:
5833 case EL_AMOEBA_TO_DIAMOND:
5834 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5835 explosion_type = EX_TYPE_CENTER;
5839 if (element_info[element].explosion_type == EXPLODES_CROSS)
5840 explosion_type = EX_TYPE_CROSS;
5841 else if (element_info[element].explosion_type == EXPLODES_1X1)
5842 explosion_type = EX_TYPE_CENTER;
5846 if (explosion_type == EX_TYPE_DYNA)
5849 Explode(x, y, EX_PHASE_START, explosion_type);
5851 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5854 static void SplashAcid(int x, int y)
5856 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5857 (!IN_LEV_FIELD(x - 1, y - 2) ||
5858 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5859 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5861 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5862 (!IN_LEV_FIELD(x + 1, y - 2) ||
5863 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5864 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5866 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5869 static void InitBeltMovement(void)
5871 static int belt_base_element[4] =
5873 EL_CONVEYOR_BELT_1_LEFT,
5874 EL_CONVEYOR_BELT_2_LEFT,
5875 EL_CONVEYOR_BELT_3_LEFT,
5876 EL_CONVEYOR_BELT_4_LEFT
5878 static int belt_base_active_element[4] =
5880 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5881 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5882 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5883 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5888 // set frame order for belt animation graphic according to belt direction
5889 for (i = 0; i < NUM_BELTS; i++)
5893 for (j = 0; j < NUM_BELT_PARTS; j++)
5895 int element = belt_base_active_element[belt_nr] + j;
5896 int graphic_1 = el2img(element);
5897 int graphic_2 = el2panelimg(element);
5899 if (game.belt_dir[i] == MV_LEFT)
5901 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5902 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5906 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5907 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5912 SCAN_PLAYFIELD(x, y)
5914 int element = Feld[x][y];
5916 for (i = 0; i < NUM_BELTS; i++)
5918 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
5920 int e_belt_nr = getBeltNrFromBeltElement(element);
5923 if (e_belt_nr == belt_nr)
5925 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
5927 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
5934 static void ToggleBeltSwitch(int x, int y)
5936 static int belt_base_element[4] =
5938 EL_CONVEYOR_BELT_1_LEFT,
5939 EL_CONVEYOR_BELT_2_LEFT,
5940 EL_CONVEYOR_BELT_3_LEFT,
5941 EL_CONVEYOR_BELT_4_LEFT
5943 static int belt_base_active_element[4] =
5945 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5946 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5947 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5948 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5950 static int belt_base_switch_element[4] =
5952 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
5953 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
5954 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
5955 EL_CONVEYOR_BELT_4_SWITCH_LEFT
5957 static int belt_move_dir[4] =
5965 int element = Feld[x][y];
5966 int belt_nr = getBeltNrFromBeltSwitchElement(element);
5967 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
5968 int belt_dir = belt_move_dir[belt_dir_nr];
5971 if (!IS_BELT_SWITCH(element))
5974 game.belt_dir_nr[belt_nr] = belt_dir_nr;
5975 game.belt_dir[belt_nr] = belt_dir;
5977 if (belt_dir_nr == 3)
5980 // set frame order for belt animation graphic according to belt direction
5981 for (i = 0; i < NUM_BELT_PARTS; i++)
5983 int element = belt_base_active_element[belt_nr] + i;
5984 int graphic_1 = el2img(element);
5985 int graphic_2 = el2panelimg(element);
5987 if (belt_dir == MV_LEFT)
5989 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5990 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5994 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5995 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5999 SCAN_PLAYFIELD(xx, yy)
6001 int element = Feld[xx][yy];
6003 if (IS_BELT_SWITCH(element))
6005 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6007 if (e_belt_nr == belt_nr)
6009 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6010 TEST_DrawLevelField(xx, yy);
6013 else if (IS_BELT(element) && belt_dir != MV_NONE)
6015 int e_belt_nr = getBeltNrFromBeltElement(element);
6017 if (e_belt_nr == belt_nr)
6019 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
6021 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6022 TEST_DrawLevelField(xx, yy);
6025 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6027 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6029 if (e_belt_nr == belt_nr)
6031 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6033 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6034 TEST_DrawLevelField(xx, yy);
6040 static void ToggleSwitchgateSwitch(int x, int y)
6044 game.switchgate_pos = !game.switchgate_pos;
6046 SCAN_PLAYFIELD(xx, yy)
6048 int element = Feld[xx][yy];
6050 if (element == EL_SWITCHGATE_SWITCH_UP)
6052 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6053 TEST_DrawLevelField(xx, yy);
6055 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6057 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6058 TEST_DrawLevelField(xx, yy);
6060 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6062 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6063 TEST_DrawLevelField(xx, yy);
6065 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6067 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6068 TEST_DrawLevelField(xx, yy);
6070 else if (element == EL_SWITCHGATE_OPEN ||
6071 element == EL_SWITCHGATE_OPENING)
6073 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6075 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6077 else if (element == EL_SWITCHGATE_CLOSED ||
6078 element == EL_SWITCHGATE_CLOSING)
6080 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6082 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6087 static int getInvisibleActiveFromInvisibleElement(int element)
6089 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6090 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6091 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6095 static int getInvisibleFromInvisibleActiveElement(int element)
6097 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6098 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6099 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6103 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6107 SCAN_PLAYFIELD(x, y)
6109 int element = Feld[x][y];
6111 if (element == EL_LIGHT_SWITCH &&
6112 game.light_time_left > 0)
6114 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6115 TEST_DrawLevelField(x, y);
6117 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6118 game.light_time_left == 0)
6120 Feld[x][y] = EL_LIGHT_SWITCH;
6121 TEST_DrawLevelField(x, y);
6123 else if (element == EL_EMC_DRIPPER &&
6124 game.light_time_left > 0)
6126 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6127 TEST_DrawLevelField(x, y);
6129 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6130 game.light_time_left == 0)
6132 Feld[x][y] = EL_EMC_DRIPPER;
6133 TEST_DrawLevelField(x, y);
6135 else if (element == EL_INVISIBLE_STEELWALL ||
6136 element == EL_INVISIBLE_WALL ||
6137 element == EL_INVISIBLE_SAND)
6139 if (game.light_time_left > 0)
6140 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6142 TEST_DrawLevelField(x, y);
6144 // uncrumble neighbour fields, if needed
6145 if (element == EL_INVISIBLE_SAND)
6146 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6148 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6149 element == EL_INVISIBLE_WALL_ACTIVE ||
6150 element == EL_INVISIBLE_SAND_ACTIVE)
6152 if (game.light_time_left == 0)
6153 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6155 TEST_DrawLevelField(x, y);
6157 // re-crumble neighbour fields, if needed
6158 if (element == EL_INVISIBLE_SAND)
6159 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6164 static void RedrawAllInvisibleElementsForLenses(void)
6168 SCAN_PLAYFIELD(x, y)
6170 int element = Feld[x][y];
6172 if (element == EL_EMC_DRIPPER &&
6173 game.lenses_time_left > 0)
6175 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6176 TEST_DrawLevelField(x, y);
6178 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6179 game.lenses_time_left == 0)
6181 Feld[x][y] = EL_EMC_DRIPPER;
6182 TEST_DrawLevelField(x, y);
6184 else if (element == EL_INVISIBLE_STEELWALL ||
6185 element == EL_INVISIBLE_WALL ||
6186 element == EL_INVISIBLE_SAND)
6188 if (game.lenses_time_left > 0)
6189 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6191 TEST_DrawLevelField(x, y);
6193 // uncrumble neighbour fields, if needed
6194 if (element == EL_INVISIBLE_SAND)
6195 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6197 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6198 element == EL_INVISIBLE_WALL_ACTIVE ||
6199 element == EL_INVISIBLE_SAND_ACTIVE)
6201 if (game.lenses_time_left == 0)
6202 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6204 TEST_DrawLevelField(x, y);
6206 // re-crumble neighbour fields, if needed
6207 if (element == EL_INVISIBLE_SAND)
6208 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6213 static void RedrawAllInvisibleElementsForMagnifier(void)
6217 SCAN_PLAYFIELD(x, y)
6219 int element = Feld[x][y];
6221 if (element == EL_EMC_FAKE_GRASS &&
6222 game.magnify_time_left > 0)
6224 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6225 TEST_DrawLevelField(x, y);
6227 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6228 game.magnify_time_left == 0)
6230 Feld[x][y] = EL_EMC_FAKE_GRASS;
6231 TEST_DrawLevelField(x, y);
6233 else if (IS_GATE_GRAY(element) &&
6234 game.magnify_time_left > 0)
6236 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6237 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6238 IS_EM_GATE_GRAY(element) ?
6239 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6240 IS_EMC_GATE_GRAY(element) ?
6241 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6242 IS_DC_GATE_GRAY(element) ?
6243 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6245 TEST_DrawLevelField(x, y);
6247 else if (IS_GATE_GRAY_ACTIVE(element) &&
6248 game.magnify_time_left == 0)
6250 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6251 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6252 IS_EM_GATE_GRAY_ACTIVE(element) ?
6253 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6254 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6255 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6256 IS_DC_GATE_GRAY_ACTIVE(element) ?
6257 EL_DC_GATE_WHITE_GRAY :
6259 TEST_DrawLevelField(x, y);
6264 static void ToggleLightSwitch(int x, int y)
6266 int element = Feld[x][y];
6268 game.light_time_left =
6269 (element == EL_LIGHT_SWITCH ?
6270 level.time_light * FRAMES_PER_SECOND : 0);
6272 RedrawAllLightSwitchesAndInvisibleElements();
6275 static void ActivateTimegateSwitch(int x, int y)
6279 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6281 SCAN_PLAYFIELD(xx, yy)
6283 int element = Feld[xx][yy];
6285 if (element == EL_TIMEGATE_CLOSED ||
6286 element == EL_TIMEGATE_CLOSING)
6288 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6289 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6293 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6295 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6296 TEST_DrawLevelField(xx, yy);
6302 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6303 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6306 static void Impact(int x, int y)
6308 boolean last_line = (y == lev_fieldy - 1);
6309 boolean object_hit = FALSE;
6310 boolean impact = (last_line || object_hit);
6311 int element = Feld[x][y];
6312 int smashed = EL_STEELWALL;
6314 if (!last_line) // check if element below was hit
6316 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6319 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6320 MovDir[x][y + 1] != MV_DOWN ||
6321 MovPos[x][y + 1] <= TILEY / 2));
6323 // do not smash moving elements that left the smashed field in time
6324 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6325 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6328 #if USE_QUICKSAND_IMPACT_BUGFIX
6329 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6331 RemoveMovingField(x, y + 1);
6332 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6333 Feld[x][y + 2] = EL_ROCK;
6334 TEST_DrawLevelField(x, y + 2);
6339 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6341 RemoveMovingField(x, y + 1);
6342 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6343 Feld[x][y + 2] = EL_ROCK;
6344 TEST_DrawLevelField(x, y + 2);
6351 smashed = MovingOrBlocked2Element(x, y + 1);
6353 impact = (last_line || object_hit);
6356 if (!last_line && smashed == EL_ACID) // element falls into acid
6358 SplashAcid(x, y + 1);
6362 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6363 // only reset graphic animation if graphic really changes after impact
6365 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6367 ResetGfxAnimation(x, y);
6368 TEST_DrawLevelField(x, y);
6371 if (impact && CAN_EXPLODE_IMPACT(element))
6376 else if (impact && element == EL_PEARL &&
6377 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6379 ResetGfxAnimation(x, y);
6381 Feld[x][y] = EL_PEARL_BREAKING;
6382 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6385 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6387 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6392 if (impact && element == EL_AMOEBA_DROP)
6394 if (object_hit && IS_PLAYER(x, y + 1))
6395 KillPlayerUnlessEnemyProtected(x, y + 1);
6396 else if (object_hit && smashed == EL_PENGUIN)
6400 Feld[x][y] = EL_AMOEBA_GROWING;
6401 Store[x][y] = EL_AMOEBA_WET;
6403 ResetRandomAnimationValue(x, y);
6408 if (object_hit) // check which object was hit
6410 if ((CAN_PASS_MAGIC_WALL(element) &&
6411 (smashed == EL_MAGIC_WALL ||
6412 smashed == EL_BD_MAGIC_WALL)) ||
6413 (CAN_PASS_DC_MAGIC_WALL(element) &&
6414 smashed == EL_DC_MAGIC_WALL))
6417 int activated_magic_wall =
6418 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6419 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6420 EL_DC_MAGIC_WALL_ACTIVE);
6422 // activate magic wall / mill
6423 SCAN_PLAYFIELD(xx, yy)
6425 if (Feld[xx][yy] == smashed)
6426 Feld[xx][yy] = activated_magic_wall;
6429 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6430 game.magic_wall_active = TRUE;
6432 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6433 SND_MAGIC_WALL_ACTIVATING :
6434 smashed == EL_BD_MAGIC_WALL ?
6435 SND_BD_MAGIC_WALL_ACTIVATING :
6436 SND_DC_MAGIC_WALL_ACTIVATING));
6439 if (IS_PLAYER(x, y + 1))
6441 if (CAN_SMASH_PLAYER(element))
6443 KillPlayerUnlessEnemyProtected(x, y + 1);
6447 else if (smashed == EL_PENGUIN)
6449 if (CAN_SMASH_PLAYER(element))
6455 else if (element == EL_BD_DIAMOND)
6457 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6463 else if (((element == EL_SP_INFOTRON ||
6464 element == EL_SP_ZONK) &&
6465 (smashed == EL_SP_SNIKSNAK ||
6466 smashed == EL_SP_ELECTRON ||
6467 smashed == EL_SP_DISK_ORANGE)) ||
6468 (element == EL_SP_INFOTRON &&
6469 smashed == EL_SP_DISK_YELLOW))
6474 else if (CAN_SMASH_EVERYTHING(element))
6476 if (IS_CLASSIC_ENEMY(smashed) ||
6477 CAN_EXPLODE_SMASHED(smashed))
6482 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6484 if (smashed == EL_LAMP ||
6485 smashed == EL_LAMP_ACTIVE)
6490 else if (smashed == EL_NUT)
6492 Feld[x][y + 1] = EL_NUT_BREAKING;
6493 PlayLevelSound(x, y, SND_NUT_BREAKING);
6494 RaiseScoreElement(EL_NUT);
6497 else if (smashed == EL_PEARL)
6499 ResetGfxAnimation(x, y);
6501 Feld[x][y + 1] = EL_PEARL_BREAKING;
6502 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6505 else if (smashed == EL_DIAMOND)
6507 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6508 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6511 else if (IS_BELT_SWITCH(smashed))
6513 ToggleBeltSwitch(x, y + 1);
6515 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6516 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6517 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6518 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6520 ToggleSwitchgateSwitch(x, y + 1);
6522 else if (smashed == EL_LIGHT_SWITCH ||
6523 smashed == EL_LIGHT_SWITCH_ACTIVE)
6525 ToggleLightSwitch(x, y + 1);
6529 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6531 CheckElementChangeBySide(x, y + 1, smashed, element,
6532 CE_SWITCHED, CH_SIDE_TOP);
6533 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6539 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6544 // play sound of magic wall / mill
6546 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6547 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6548 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6550 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6551 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6552 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6553 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6554 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6555 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6560 // play sound of object that hits the ground
6561 if (last_line || object_hit)
6562 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6565 static void TurnRoundExt(int x, int y)
6577 { 0, 0 }, { 0, 0 }, { 0, 0 },
6582 int left, right, back;
6586 { MV_DOWN, MV_UP, MV_RIGHT },
6587 { MV_UP, MV_DOWN, MV_LEFT },
6589 { MV_LEFT, MV_RIGHT, MV_DOWN },
6593 { MV_RIGHT, MV_LEFT, MV_UP }
6596 int element = Feld[x][y];
6597 int move_pattern = element_info[element].move_pattern;
6599 int old_move_dir = MovDir[x][y];
6600 int left_dir = turn[old_move_dir].left;
6601 int right_dir = turn[old_move_dir].right;
6602 int back_dir = turn[old_move_dir].back;
6604 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6605 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6606 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6607 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6609 int left_x = x + left_dx, left_y = y + left_dy;
6610 int right_x = x + right_dx, right_y = y + right_dy;
6611 int move_x = x + move_dx, move_y = y + move_dy;
6615 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6617 TestIfBadThingTouchesOtherBadThing(x, y);
6619 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6620 MovDir[x][y] = right_dir;
6621 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6622 MovDir[x][y] = left_dir;
6624 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6626 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6629 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6631 TestIfBadThingTouchesOtherBadThing(x, y);
6633 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6634 MovDir[x][y] = left_dir;
6635 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6636 MovDir[x][y] = right_dir;
6638 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6640 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6643 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6645 TestIfBadThingTouchesOtherBadThing(x, y);
6647 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6648 MovDir[x][y] = left_dir;
6649 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6650 MovDir[x][y] = right_dir;
6652 if (MovDir[x][y] != old_move_dir)
6655 else if (element == EL_YAMYAM)
6657 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6658 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6660 if (can_turn_left && can_turn_right)
6661 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6662 else if (can_turn_left)
6663 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6664 else if (can_turn_right)
6665 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6667 MovDir[x][y] = back_dir;
6669 MovDelay[x][y] = 16 + 16 * RND(3);
6671 else if (element == EL_DARK_YAMYAM)
6673 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6675 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6678 if (can_turn_left && can_turn_right)
6679 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6680 else if (can_turn_left)
6681 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6682 else if (can_turn_right)
6683 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6685 MovDir[x][y] = back_dir;
6687 MovDelay[x][y] = 16 + 16 * RND(3);
6689 else if (element == EL_PACMAN)
6691 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6692 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6694 if (can_turn_left && can_turn_right)
6695 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6696 else if (can_turn_left)
6697 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6698 else if (can_turn_right)
6699 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6701 MovDir[x][y] = back_dir;
6703 MovDelay[x][y] = 6 + RND(40);
6705 else if (element == EL_PIG)
6707 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6708 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6709 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6710 boolean should_turn_left, should_turn_right, should_move_on;
6712 int rnd = RND(rnd_value);
6714 should_turn_left = (can_turn_left &&
6716 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6717 y + back_dy + left_dy)));
6718 should_turn_right = (can_turn_right &&
6720 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6721 y + back_dy + right_dy)));
6722 should_move_on = (can_move_on &&
6725 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6726 y + move_dy + left_dy) ||
6727 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6728 y + move_dy + right_dy)));
6730 if (should_turn_left || should_turn_right || should_move_on)
6732 if (should_turn_left && should_turn_right && should_move_on)
6733 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6734 rnd < 2 * rnd_value / 3 ? right_dir :
6736 else if (should_turn_left && should_turn_right)
6737 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6738 else if (should_turn_left && should_move_on)
6739 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6740 else if (should_turn_right && should_move_on)
6741 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6742 else if (should_turn_left)
6743 MovDir[x][y] = left_dir;
6744 else if (should_turn_right)
6745 MovDir[x][y] = right_dir;
6746 else if (should_move_on)
6747 MovDir[x][y] = old_move_dir;
6749 else if (can_move_on && rnd > rnd_value / 8)
6750 MovDir[x][y] = old_move_dir;
6751 else if (can_turn_left && can_turn_right)
6752 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6753 else if (can_turn_left && rnd > rnd_value / 8)
6754 MovDir[x][y] = left_dir;
6755 else if (can_turn_right && rnd > rnd_value/8)
6756 MovDir[x][y] = right_dir;
6758 MovDir[x][y] = back_dir;
6760 xx = x + move_xy[MovDir[x][y]].dx;
6761 yy = y + move_xy[MovDir[x][y]].dy;
6763 if (!IN_LEV_FIELD(xx, yy) ||
6764 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6765 MovDir[x][y] = old_move_dir;
6769 else if (element == EL_DRAGON)
6771 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6772 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6773 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6775 int rnd = RND(rnd_value);
6777 if (can_move_on && rnd > rnd_value / 8)
6778 MovDir[x][y] = old_move_dir;
6779 else if (can_turn_left && can_turn_right)
6780 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6781 else if (can_turn_left && rnd > rnd_value / 8)
6782 MovDir[x][y] = left_dir;
6783 else if (can_turn_right && rnd > rnd_value / 8)
6784 MovDir[x][y] = right_dir;
6786 MovDir[x][y] = back_dir;
6788 xx = x + move_xy[MovDir[x][y]].dx;
6789 yy = y + move_xy[MovDir[x][y]].dy;
6791 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6792 MovDir[x][y] = old_move_dir;
6796 else if (element == EL_MOLE)
6798 boolean can_move_on =
6799 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6800 IS_AMOEBOID(Feld[move_x][move_y]) ||
6801 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6804 boolean can_turn_left =
6805 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6806 IS_AMOEBOID(Feld[left_x][left_y])));
6808 boolean can_turn_right =
6809 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6810 IS_AMOEBOID(Feld[right_x][right_y])));
6812 if (can_turn_left && can_turn_right)
6813 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6814 else if (can_turn_left)
6815 MovDir[x][y] = left_dir;
6817 MovDir[x][y] = right_dir;
6820 if (MovDir[x][y] != old_move_dir)
6823 else if (element == EL_BALLOON)
6825 MovDir[x][y] = game.wind_direction;
6828 else if (element == EL_SPRING)
6830 if (MovDir[x][y] & MV_HORIZONTAL)
6832 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6833 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6835 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6836 ResetGfxAnimation(move_x, move_y);
6837 TEST_DrawLevelField(move_x, move_y);
6839 MovDir[x][y] = back_dir;
6841 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6842 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6843 MovDir[x][y] = MV_NONE;
6848 else if (element == EL_ROBOT ||
6849 element == EL_SATELLITE ||
6850 element == EL_PENGUIN ||
6851 element == EL_EMC_ANDROID)
6853 int attr_x = -1, attr_y = -1;
6855 if (game.all_players_gone)
6857 attr_x = game.exit_x;
6858 attr_y = game.exit_y;
6864 for (i = 0; i < MAX_PLAYERS; i++)
6866 struct PlayerInfo *player = &stored_player[i];
6867 int jx = player->jx, jy = player->jy;
6869 if (!player->active)
6873 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6881 if (element == EL_ROBOT &&
6882 game.robot_wheel_x >= 0 &&
6883 game.robot_wheel_y >= 0 &&
6884 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
6885 game.engine_version < VERSION_IDENT(3,1,0,0)))
6887 attr_x = game.robot_wheel_x;
6888 attr_y = game.robot_wheel_y;
6891 if (element == EL_PENGUIN)
6894 static int xy[4][2] =
6902 for (i = 0; i < NUM_DIRECTIONS; i++)
6904 int ex = x + xy[i][0];
6905 int ey = y + xy[i][1];
6907 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
6908 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
6909 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
6910 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
6919 MovDir[x][y] = MV_NONE;
6921 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
6922 else if (attr_x > x)
6923 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
6925 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
6926 else if (attr_y > y)
6927 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
6929 if (element == EL_ROBOT)
6933 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6934 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
6935 Moving2Blocked(x, y, &newx, &newy);
6937 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
6938 MovDelay[x][y] = 8 + 8 * !RND(3);
6940 MovDelay[x][y] = 16;
6942 else if (element == EL_PENGUIN)
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 (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6961 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6962 Moving2Blocked(x, y, &newx, &newy);
6964 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6967 MovDir[x][y] = old_move_dir;
6971 else if (element == EL_SATELLITE)
6977 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6979 boolean first_horiz = RND(2);
6980 int new_move_dir = MovDir[x][y];
6983 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6984 Moving2Blocked(x, y, &newx, &newy);
6986 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6990 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6991 Moving2Blocked(x, y, &newx, &newy);
6993 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6996 MovDir[x][y] = old_move_dir;
7000 else if (element == EL_EMC_ANDROID)
7002 static int check_pos[16] =
7004 -1, // 0 => (invalid)
7007 -1, // 3 => (invalid)
7009 0, // 5 => MV_LEFT | MV_UP
7010 2, // 6 => MV_RIGHT | MV_UP
7011 -1, // 7 => (invalid)
7013 6, // 9 => MV_LEFT | MV_DOWN
7014 4, // 10 => MV_RIGHT | MV_DOWN
7015 -1, // 11 => (invalid)
7016 -1, // 12 => (invalid)
7017 -1, // 13 => (invalid)
7018 -1, // 14 => (invalid)
7019 -1, // 15 => (invalid)
7027 { -1, -1, MV_LEFT | MV_UP },
7029 { +1, -1, MV_RIGHT | MV_UP },
7030 { +1, 0, MV_RIGHT },
7031 { +1, +1, MV_RIGHT | MV_DOWN },
7033 { -1, +1, MV_LEFT | MV_DOWN },
7036 int start_pos, check_order;
7037 boolean can_clone = FALSE;
7040 // check if there is any free field around current position
7041 for (i = 0; i < 8; i++)
7043 int newx = x + check_xy[i].dx;
7044 int newy = y + check_xy[i].dy;
7046 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7054 if (can_clone) // randomly find an element to clone
7058 start_pos = check_pos[RND(8)];
7059 check_order = (RND(2) ? -1 : +1);
7061 for (i = 0; i < 8; i++)
7063 int pos_raw = start_pos + i * check_order;
7064 int pos = (pos_raw + 8) % 8;
7065 int newx = x + check_xy[pos].dx;
7066 int newy = y + check_xy[pos].dy;
7068 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7070 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7071 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7073 Store[x][y] = Feld[newx][newy];
7082 if (can_clone) // randomly find a direction to move
7086 start_pos = check_pos[RND(8)];
7087 check_order = (RND(2) ? -1 : +1);
7089 for (i = 0; i < 8; i++)
7091 int pos_raw = start_pos + i * 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 (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7099 MovDir[x][y] = new_move_dir;
7100 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7109 if (can_clone) // cloning and moving successful
7112 // cannot clone -- try to move towards player
7114 start_pos = check_pos[MovDir[x][y] & 0x0f];
7115 check_order = (RND(2) ? -1 : +1);
7117 for (i = 0; i < 3; i++)
7119 // first check start_pos, then previous/next or (next/previous) pos
7120 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7121 int pos = (pos_raw + 8) % 8;
7122 int newx = x + check_xy[pos].dx;
7123 int newy = y + check_xy[pos].dy;
7124 int new_move_dir = check_xy[pos].dir;
7126 if (IS_PLAYER(newx, newy))
7129 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7131 MovDir[x][y] = new_move_dir;
7132 MovDelay[x][y] = level.android_move_time * 8 + 1;
7139 else if (move_pattern == MV_TURNING_LEFT ||
7140 move_pattern == MV_TURNING_RIGHT ||
7141 move_pattern == MV_TURNING_LEFT_RIGHT ||
7142 move_pattern == MV_TURNING_RIGHT_LEFT ||
7143 move_pattern == MV_TURNING_RANDOM ||
7144 move_pattern == MV_ALL_DIRECTIONS)
7146 boolean can_turn_left =
7147 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7148 boolean can_turn_right =
7149 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7151 if (element_info[element].move_stepsize == 0) // "not moving"
7154 if (move_pattern == MV_TURNING_LEFT)
7155 MovDir[x][y] = left_dir;
7156 else if (move_pattern == MV_TURNING_RIGHT)
7157 MovDir[x][y] = right_dir;
7158 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7159 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7160 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7161 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7162 else if (move_pattern == MV_TURNING_RANDOM)
7163 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7164 can_turn_right && !can_turn_left ? right_dir :
7165 RND(2) ? left_dir : right_dir);
7166 else if (can_turn_left && can_turn_right)
7167 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7168 else if (can_turn_left)
7169 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7170 else if (can_turn_right)
7171 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7173 MovDir[x][y] = back_dir;
7175 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7177 else if (move_pattern == MV_HORIZONTAL ||
7178 move_pattern == MV_VERTICAL)
7180 if (move_pattern & old_move_dir)
7181 MovDir[x][y] = back_dir;
7182 else if (move_pattern == MV_HORIZONTAL)
7183 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7184 else if (move_pattern == MV_VERTICAL)
7185 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7187 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7189 else if (move_pattern & MV_ANY_DIRECTION)
7191 MovDir[x][y] = move_pattern;
7192 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7194 else if (move_pattern & MV_WIND_DIRECTION)
7196 MovDir[x][y] = game.wind_direction;
7197 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7199 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7201 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7202 MovDir[x][y] = left_dir;
7203 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7204 MovDir[x][y] = right_dir;
7206 if (MovDir[x][y] != old_move_dir)
7207 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7209 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7211 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7212 MovDir[x][y] = right_dir;
7213 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7214 MovDir[x][y] = left_dir;
7216 if (MovDir[x][y] != old_move_dir)
7217 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7219 else if (move_pattern == MV_TOWARDS_PLAYER ||
7220 move_pattern == MV_AWAY_FROM_PLAYER)
7222 int attr_x = -1, attr_y = -1;
7224 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7226 if (game.all_players_gone)
7228 attr_x = game.exit_x;
7229 attr_y = game.exit_y;
7235 for (i = 0; i < MAX_PLAYERS; i++)
7237 struct PlayerInfo *player = &stored_player[i];
7238 int jx = player->jx, jy = player->jy;
7240 if (!player->active)
7244 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7252 MovDir[x][y] = MV_NONE;
7254 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7255 else if (attr_x > x)
7256 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7258 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7259 else if (attr_y > y)
7260 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7262 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7264 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7266 boolean first_horiz = RND(2);
7267 int new_move_dir = MovDir[x][y];
7269 if (element_info[element].move_stepsize == 0) // "not moving"
7271 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7272 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7278 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7279 Moving2Blocked(x, y, &newx, &newy);
7281 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7285 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7286 Moving2Blocked(x, y, &newx, &newy);
7288 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7291 MovDir[x][y] = old_move_dir;
7294 else if (move_pattern == MV_WHEN_PUSHED ||
7295 move_pattern == MV_WHEN_DROPPED)
7297 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7298 MovDir[x][y] = MV_NONE;
7302 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7304 static int test_xy[7][2] =
7314 static int test_dir[7] =
7324 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7325 int move_preference = -1000000; // start with very low preference
7326 int new_move_dir = MV_NONE;
7327 int start_test = RND(4);
7330 for (i = 0; i < NUM_DIRECTIONS; i++)
7332 int move_dir = test_dir[start_test + i];
7333 int move_dir_preference;
7335 xx = x + test_xy[start_test + i][0];
7336 yy = y + test_xy[start_test + i][1];
7338 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7339 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7341 new_move_dir = move_dir;
7346 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7349 move_dir_preference = -1 * RunnerVisit[xx][yy];
7350 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7351 move_dir_preference = PlayerVisit[xx][yy];
7353 if (move_dir_preference > move_preference)
7355 // prefer field that has not been visited for the longest time
7356 move_preference = move_dir_preference;
7357 new_move_dir = move_dir;
7359 else if (move_dir_preference == move_preference &&
7360 move_dir == old_move_dir)
7362 // prefer last direction when all directions are preferred equally
7363 move_preference = move_dir_preference;
7364 new_move_dir = move_dir;
7368 MovDir[x][y] = new_move_dir;
7369 if (old_move_dir != new_move_dir)
7370 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7374 static void TurnRound(int x, int y)
7376 int direction = MovDir[x][y];
7380 GfxDir[x][y] = MovDir[x][y];
7382 if (direction != MovDir[x][y])
7386 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7388 ResetGfxFrame(x, y);
7391 static boolean JustBeingPushed(int x, int y)
7395 for (i = 0; i < MAX_PLAYERS; i++)
7397 struct PlayerInfo *player = &stored_player[i];
7399 if (player->active && player->is_pushing && player->MovPos)
7401 int next_jx = player->jx + (player->jx - player->last_jx);
7402 int next_jy = player->jy + (player->jy - player->last_jy);
7404 if (x == next_jx && y == next_jy)
7412 static void StartMoving(int x, int y)
7414 boolean started_moving = FALSE; // some elements can fall _and_ move
7415 int element = Feld[x][y];
7420 if (MovDelay[x][y] == 0)
7421 GfxAction[x][y] = ACTION_DEFAULT;
7423 if (CAN_FALL(element) && y < lev_fieldy - 1)
7425 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7426 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7427 if (JustBeingPushed(x, y))
7430 if (element == EL_QUICKSAND_FULL)
7432 if (IS_FREE(x, y + 1))
7434 InitMovingField(x, y, MV_DOWN);
7435 started_moving = TRUE;
7437 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7438 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7439 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7440 Store[x][y] = EL_ROCK;
7442 Store[x][y] = EL_ROCK;
7445 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7447 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7449 if (!MovDelay[x][y])
7451 MovDelay[x][y] = TILEY + 1;
7453 ResetGfxAnimation(x, y);
7454 ResetGfxAnimation(x, y + 1);
7459 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7460 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7467 Feld[x][y] = EL_QUICKSAND_EMPTY;
7468 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7469 Store[x][y + 1] = Store[x][y];
7472 PlayLevelSoundAction(x, y, ACTION_FILLING);
7474 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7476 if (!MovDelay[x][y])
7478 MovDelay[x][y] = TILEY + 1;
7480 ResetGfxAnimation(x, y);
7481 ResetGfxAnimation(x, y + 1);
7486 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7487 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7494 Feld[x][y] = EL_QUICKSAND_EMPTY;
7495 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7496 Store[x][y + 1] = Store[x][y];
7499 PlayLevelSoundAction(x, y, ACTION_FILLING);
7502 else if (element == EL_QUICKSAND_FAST_FULL)
7504 if (IS_FREE(x, y + 1))
7506 InitMovingField(x, y, MV_DOWN);
7507 started_moving = TRUE;
7509 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7510 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7511 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7512 Store[x][y] = EL_ROCK;
7514 Store[x][y] = EL_ROCK;
7517 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7519 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7521 if (!MovDelay[x][y])
7523 MovDelay[x][y] = TILEY + 1;
7525 ResetGfxAnimation(x, y);
7526 ResetGfxAnimation(x, y + 1);
7531 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7532 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7539 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7540 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7541 Store[x][y + 1] = Store[x][y];
7544 PlayLevelSoundAction(x, y, ACTION_FILLING);
7546 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7548 if (!MovDelay[x][y])
7550 MovDelay[x][y] = TILEY + 1;
7552 ResetGfxAnimation(x, y);
7553 ResetGfxAnimation(x, y + 1);
7558 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7559 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7566 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7567 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7568 Store[x][y + 1] = Store[x][y];
7571 PlayLevelSoundAction(x, y, ACTION_FILLING);
7574 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7575 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7577 InitMovingField(x, y, MV_DOWN);
7578 started_moving = TRUE;
7580 Feld[x][y] = EL_QUICKSAND_FILLING;
7581 Store[x][y] = element;
7583 PlayLevelSoundAction(x, y, ACTION_FILLING);
7585 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7586 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7588 InitMovingField(x, y, MV_DOWN);
7589 started_moving = TRUE;
7591 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7592 Store[x][y] = element;
7594 PlayLevelSoundAction(x, y, ACTION_FILLING);
7596 else if (element == EL_MAGIC_WALL_FULL)
7598 if (IS_FREE(x, y + 1))
7600 InitMovingField(x, y, MV_DOWN);
7601 started_moving = TRUE;
7603 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7604 Store[x][y] = EL_CHANGED(Store[x][y]);
7606 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7608 if (!MovDelay[x][y])
7609 MovDelay[x][y] = TILEY / 4 + 1;
7618 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7619 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7620 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7624 else if (element == EL_BD_MAGIC_WALL_FULL)
7626 if (IS_FREE(x, y + 1))
7628 InitMovingField(x, y, MV_DOWN);
7629 started_moving = TRUE;
7631 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7632 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7634 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7636 if (!MovDelay[x][y])
7637 MovDelay[x][y] = TILEY / 4 + 1;
7646 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7647 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7648 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7652 else if (element == EL_DC_MAGIC_WALL_FULL)
7654 if (IS_FREE(x, y + 1))
7656 InitMovingField(x, y, MV_DOWN);
7657 started_moving = TRUE;
7659 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7660 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7662 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7664 if (!MovDelay[x][y])
7665 MovDelay[x][y] = TILEY / 4 + 1;
7674 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7675 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7676 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7680 else if ((CAN_PASS_MAGIC_WALL(element) &&
7681 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7682 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7683 (CAN_PASS_DC_MAGIC_WALL(element) &&
7684 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7687 InitMovingField(x, y, MV_DOWN);
7688 started_moving = TRUE;
7691 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7692 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7693 EL_DC_MAGIC_WALL_FILLING);
7694 Store[x][y] = element;
7696 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7698 SplashAcid(x, y + 1);
7700 InitMovingField(x, y, MV_DOWN);
7701 started_moving = TRUE;
7703 Store[x][y] = EL_ACID;
7706 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7707 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7708 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7709 CAN_FALL(element) && WasJustFalling[x][y] &&
7710 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7712 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7713 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7714 (Feld[x][y + 1] == EL_BLOCKED)))
7716 /* this is needed for a special case not covered by calling "Impact()"
7717 from "ContinueMoving()": if an element moves to a tile directly below
7718 another element which was just falling on that tile (which was empty
7719 in the previous frame), the falling element above would just stop
7720 instead of smashing the element below (in previous version, the above
7721 element was just checked for "moving" instead of "falling", resulting
7722 in incorrect smashes caused by horizontal movement of the above
7723 element; also, the case of the player being the element to smash was
7724 simply not covered here... :-/ ) */
7726 CheckCollision[x][y] = 0;
7727 CheckImpact[x][y] = 0;
7731 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7733 if (MovDir[x][y] == MV_NONE)
7735 InitMovingField(x, y, MV_DOWN);
7736 started_moving = TRUE;
7739 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7741 if (WasJustFalling[x][y]) // prevent animation from being restarted
7742 MovDir[x][y] = MV_DOWN;
7744 InitMovingField(x, y, MV_DOWN);
7745 started_moving = TRUE;
7747 else if (element == EL_AMOEBA_DROP)
7749 Feld[x][y] = EL_AMOEBA_GROWING;
7750 Store[x][y] = EL_AMOEBA_WET;
7752 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7753 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7754 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7755 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7757 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7758 (IS_FREE(x - 1, y + 1) ||
7759 Feld[x - 1][y + 1] == EL_ACID));
7760 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7761 (IS_FREE(x + 1, y + 1) ||
7762 Feld[x + 1][y + 1] == EL_ACID));
7763 boolean can_fall_any = (can_fall_left || can_fall_right);
7764 boolean can_fall_both = (can_fall_left && can_fall_right);
7765 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7767 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7769 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7770 can_fall_right = FALSE;
7771 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7772 can_fall_left = FALSE;
7773 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7774 can_fall_right = FALSE;
7775 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7776 can_fall_left = FALSE;
7778 can_fall_any = (can_fall_left || can_fall_right);
7779 can_fall_both = FALSE;
7784 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7785 can_fall_right = FALSE; // slip down on left side
7787 can_fall_left = !(can_fall_right = RND(2));
7789 can_fall_both = FALSE;
7794 // if not determined otherwise, prefer left side for slipping down
7795 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7796 started_moving = TRUE;
7799 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7801 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7802 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7803 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7804 int belt_dir = game.belt_dir[belt_nr];
7806 if ((belt_dir == MV_LEFT && left_is_free) ||
7807 (belt_dir == MV_RIGHT && right_is_free))
7809 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7811 InitMovingField(x, y, belt_dir);
7812 started_moving = TRUE;
7814 Pushed[x][y] = TRUE;
7815 Pushed[nextx][y] = TRUE;
7817 GfxAction[x][y] = ACTION_DEFAULT;
7821 MovDir[x][y] = 0; // if element was moving, stop it
7826 // not "else if" because of elements that can fall and move (EL_SPRING)
7827 if (CAN_MOVE(element) && !started_moving)
7829 int move_pattern = element_info[element].move_pattern;
7832 Moving2Blocked(x, y, &newx, &newy);
7834 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7837 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7838 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7840 WasJustMoving[x][y] = 0;
7841 CheckCollision[x][y] = 0;
7843 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7845 if (Feld[x][y] != element) // element has changed
7849 if (!MovDelay[x][y]) // start new movement phase
7851 // all objects that can change their move direction after each step
7852 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7854 if (element != EL_YAMYAM &&
7855 element != EL_DARK_YAMYAM &&
7856 element != EL_PACMAN &&
7857 !(move_pattern & MV_ANY_DIRECTION) &&
7858 move_pattern != MV_TURNING_LEFT &&
7859 move_pattern != MV_TURNING_RIGHT &&
7860 move_pattern != MV_TURNING_LEFT_RIGHT &&
7861 move_pattern != MV_TURNING_RIGHT_LEFT &&
7862 move_pattern != MV_TURNING_RANDOM)
7866 if (MovDelay[x][y] && (element == EL_BUG ||
7867 element == EL_SPACESHIP ||
7868 element == EL_SP_SNIKSNAK ||
7869 element == EL_SP_ELECTRON ||
7870 element == EL_MOLE))
7871 TEST_DrawLevelField(x, y);
7875 if (MovDelay[x][y]) // wait some time before next movement
7879 if (element == EL_ROBOT ||
7880 element == EL_YAMYAM ||
7881 element == EL_DARK_YAMYAM)
7883 DrawLevelElementAnimationIfNeeded(x, y, element);
7884 PlayLevelSoundAction(x, y, ACTION_WAITING);
7886 else if (element == EL_SP_ELECTRON)
7887 DrawLevelElementAnimationIfNeeded(x, y, element);
7888 else if (element == EL_DRAGON)
7891 int dir = MovDir[x][y];
7892 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
7893 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
7894 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
7895 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
7896 dir == MV_UP ? IMG_FLAMES_1_UP :
7897 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
7898 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
7900 GfxAction[x][y] = ACTION_ATTACKING;
7902 if (IS_PLAYER(x, y))
7903 DrawPlayerField(x, y);
7905 TEST_DrawLevelField(x, y);
7907 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
7909 for (i = 1; i <= 3; i++)
7911 int xx = x + i * dx;
7912 int yy = y + i * dy;
7913 int sx = SCREENX(xx);
7914 int sy = SCREENY(yy);
7915 int flame_graphic = graphic + (i - 1);
7917 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
7922 int flamed = MovingOrBlocked2Element(xx, yy);
7924 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
7927 RemoveMovingField(xx, yy);
7929 ChangeDelay[xx][yy] = 0;
7931 Feld[xx][yy] = EL_FLAMES;
7933 if (IN_SCR_FIELD(sx, sy))
7935 TEST_DrawLevelFieldCrumbled(xx, yy);
7936 DrawGraphic(sx, sy, flame_graphic, frame);
7941 if (Feld[xx][yy] == EL_FLAMES)
7942 Feld[xx][yy] = EL_EMPTY;
7943 TEST_DrawLevelField(xx, yy);
7948 if (MovDelay[x][y]) // element still has to wait some time
7950 PlayLevelSoundAction(x, y, ACTION_WAITING);
7956 // now make next step
7958 Moving2Blocked(x, y, &newx, &newy); // get next screen position
7960 if (DONT_COLLIDE_WITH(element) &&
7961 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
7962 !PLAYER_ENEMY_PROTECTED(newx, newy))
7964 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
7969 else if (CAN_MOVE_INTO_ACID(element) &&
7970 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
7971 !IS_MV_DIAGONAL(MovDir[x][y]) &&
7972 (MovDir[x][y] == MV_DOWN ||
7973 game.engine_version >= VERSION_IDENT(3,1,0,0)))
7975 SplashAcid(newx, newy);
7976 Store[x][y] = EL_ACID;
7978 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
7980 if (Feld[newx][newy] == EL_EXIT_OPEN ||
7981 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
7982 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
7983 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
7986 TEST_DrawLevelField(x, y);
7988 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
7989 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
7990 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
7992 game.friends_still_needed--;
7993 if (!game.friends_still_needed &&
7995 game.all_players_gone)
8000 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
8002 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8003 TEST_DrawLevelField(newx, newy);
8005 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8007 else if (!IS_FREE(newx, newy))
8009 GfxAction[x][y] = ACTION_WAITING;
8011 if (IS_PLAYER(x, y))
8012 DrawPlayerField(x, y);
8014 TEST_DrawLevelField(x, y);
8019 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8021 if (IS_FOOD_PIG(Feld[newx][newy]))
8023 if (IS_MOVING(newx, newy))
8024 RemoveMovingField(newx, newy);
8027 Feld[newx][newy] = EL_EMPTY;
8028 TEST_DrawLevelField(newx, newy);
8031 PlayLevelSound(x, y, SND_PIG_DIGGING);
8033 else if (!IS_FREE(newx, newy))
8035 if (IS_PLAYER(x, y))
8036 DrawPlayerField(x, y);
8038 TEST_DrawLevelField(x, y);
8043 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8045 if (Store[x][y] != EL_EMPTY)
8047 boolean can_clone = FALSE;
8050 // check if element to clone is still there
8051 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8053 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8061 // cannot clone or target field not free anymore -- do not clone
8062 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8063 Store[x][y] = EL_EMPTY;
8066 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8068 if (IS_MV_DIAGONAL(MovDir[x][y]))
8070 int diagonal_move_dir = MovDir[x][y];
8071 int stored = Store[x][y];
8072 int change_delay = 8;
8075 // android is moving diagonally
8077 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8079 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8080 GfxElement[x][y] = EL_EMC_ANDROID;
8081 GfxAction[x][y] = ACTION_SHRINKING;
8082 GfxDir[x][y] = diagonal_move_dir;
8083 ChangeDelay[x][y] = change_delay;
8085 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8088 DrawLevelGraphicAnimation(x, y, graphic);
8089 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8091 if (Feld[newx][newy] == EL_ACID)
8093 SplashAcid(newx, newy);
8098 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8100 Store[newx][newy] = EL_EMC_ANDROID;
8101 GfxElement[newx][newy] = EL_EMC_ANDROID;
8102 GfxAction[newx][newy] = ACTION_GROWING;
8103 GfxDir[newx][newy] = diagonal_move_dir;
8104 ChangeDelay[newx][newy] = change_delay;
8106 graphic = el_act_dir2img(GfxElement[newx][newy],
8107 GfxAction[newx][newy], GfxDir[newx][newy]);
8109 DrawLevelGraphicAnimation(newx, newy, graphic);
8110 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8116 Feld[newx][newy] = EL_EMPTY;
8117 TEST_DrawLevelField(newx, newy);
8119 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8122 else if (!IS_FREE(newx, newy))
8127 else if (IS_CUSTOM_ELEMENT(element) &&
8128 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8130 if (!DigFieldByCE(newx, newy, element))
8133 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8135 RunnerVisit[x][y] = FrameCounter;
8136 PlayerVisit[x][y] /= 8; // expire player visit path
8139 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8141 if (!IS_FREE(newx, newy))
8143 if (IS_PLAYER(x, y))
8144 DrawPlayerField(x, y);
8146 TEST_DrawLevelField(x, y);
8152 boolean wanna_flame = !RND(10);
8153 int dx = newx - x, dy = newy - y;
8154 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8155 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8156 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8157 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8158 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8159 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8162 IS_CLASSIC_ENEMY(element1) ||
8163 IS_CLASSIC_ENEMY(element2)) &&
8164 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8165 element1 != EL_FLAMES && element2 != EL_FLAMES)
8167 ResetGfxAnimation(x, y);
8168 GfxAction[x][y] = ACTION_ATTACKING;
8170 if (IS_PLAYER(x, y))
8171 DrawPlayerField(x, y);
8173 TEST_DrawLevelField(x, y);
8175 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8177 MovDelay[x][y] = 50;
8179 Feld[newx][newy] = EL_FLAMES;
8180 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8181 Feld[newx1][newy1] = EL_FLAMES;
8182 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8183 Feld[newx2][newy2] = EL_FLAMES;
8189 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8190 Feld[newx][newy] == EL_DIAMOND)
8192 if (IS_MOVING(newx, newy))
8193 RemoveMovingField(newx, newy);
8196 Feld[newx][newy] = EL_EMPTY;
8197 TEST_DrawLevelField(newx, newy);
8200 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8202 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8203 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8205 if (AmoebaNr[newx][newy])
8207 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8208 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8209 Feld[newx][newy] == EL_BD_AMOEBA)
8210 AmoebaCnt[AmoebaNr[newx][newy]]--;
8213 if (IS_MOVING(newx, newy))
8215 RemoveMovingField(newx, newy);
8219 Feld[newx][newy] = EL_EMPTY;
8220 TEST_DrawLevelField(newx, newy);
8223 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8225 else if ((element == EL_PACMAN || element == EL_MOLE)
8226 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8228 if (AmoebaNr[newx][newy])
8230 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8231 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8232 Feld[newx][newy] == EL_BD_AMOEBA)
8233 AmoebaCnt[AmoebaNr[newx][newy]]--;
8236 if (element == EL_MOLE)
8238 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8239 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8241 ResetGfxAnimation(x, y);
8242 GfxAction[x][y] = ACTION_DIGGING;
8243 TEST_DrawLevelField(x, y);
8245 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8247 return; // wait for shrinking amoeba
8249 else // element == EL_PACMAN
8251 Feld[newx][newy] = EL_EMPTY;
8252 TEST_DrawLevelField(newx, newy);
8253 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8256 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8257 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8258 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8260 // wait for shrinking amoeba to completely disappear
8263 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8265 // object was running against a wall
8269 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8270 DrawLevelElementAnimation(x, y, element);
8272 if (DONT_TOUCH(element))
8273 TestIfBadThingTouchesPlayer(x, y);
8278 InitMovingField(x, y, MovDir[x][y]);
8280 PlayLevelSoundAction(x, y, ACTION_MOVING);
8284 ContinueMoving(x, y);
8287 void ContinueMoving(int x, int y)
8289 int element = Feld[x][y];
8290 struct ElementInfo *ei = &element_info[element];
8291 int direction = MovDir[x][y];
8292 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8293 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8294 int newx = x + dx, newy = y + dy;
8295 int stored = Store[x][y];
8296 int stored_new = Store[newx][newy];
8297 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8298 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8299 boolean last_line = (newy == lev_fieldy - 1);
8301 MovPos[x][y] += getElementMoveStepsize(x, y);
8303 if (pushed_by_player) // special case: moving object pushed by player
8304 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8306 if (ABS(MovPos[x][y]) < TILEX)
8308 TEST_DrawLevelField(x, y);
8310 return; // element is still moving
8313 // element reached destination field
8315 Feld[x][y] = EL_EMPTY;
8316 Feld[newx][newy] = element;
8317 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8319 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8321 element = Feld[newx][newy] = EL_ACID;
8323 else if (element == EL_MOLE)
8325 Feld[x][y] = EL_SAND;
8327 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8329 else if (element == EL_QUICKSAND_FILLING)
8331 element = Feld[newx][newy] = get_next_element(element);
8332 Store[newx][newy] = Store[x][y];
8334 else if (element == EL_QUICKSAND_EMPTYING)
8336 Feld[x][y] = get_next_element(element);
8337 element = Feld[newx][newy] = Store[x][y];
8339 else if (element == EL_QUICKSAND_FAST_FILLING)
8341 element = Feld[newx][newy] = get_next_element(element);
8342 Store[newx][newy] = Store[x][y];
8344 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8346 Feld[x][y] = get_next_element(element);
8347 element = Feld[newx][newy] = Store[x][y];
8349 else if (element == EL_MAGIC_WALL_FILLING)
8351 element = Feld[newx][newy] = get_next_element(element);
8352 if (!game.magic_wall_active)
8353 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8354 Store[newx][newy] = Store[x][y];
8356 else if (element == EL_MAGIC_WALL_EMPTYING)
8358 Feld[x][y] = get_next_element(element);
8359 if (!game.magic_wall_active)
8360 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8361 element = Feld[newx][newy] = Store[x][y];
8363 InitField(newx, newy, FALSE);
8365 else if (element == EL_BD_MAGIC_WALL_FILLING)
8367 element = Feld[newx][newy] = get_next_element(element);
8368 if (!game.magic_wall_active)
8369 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8370 Store[newx][newy] = Store[x][y];
8372 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8374 Feld[x][y] = get_next_element(element);
8375 if (!game.magic_wall_active)
8376 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8377 element = Feld[newx][newy] = Store[x][y];
8379 InitField(newx, newy, FALSE);
8381 else if (element == EL_DC_MAGIC_WALL_FILLING)
8383 element = Feld[newx][newy] = get_next_element(element);
8384 if (!game.magic_wall_active)
8385 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8386 Store[newx][newy] = Store[x][y];
8388 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8390 Feld[x][y] = get_next_element(element);
8391 if (!game.magic_wall_active)
8392 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8393 element = Feld[newx][newy] = Store[x][y];
8395 InitField(newx, newy, FALSE);
8397 else if (element == EL_AMOEBA_DROPPING)
8399 Feld[x][y] = get_next_element(element);
8400 element = Feld[newx][newy] = Store[x][y];
8402 else if (element == EL_SOKOBAN_OBJECT)
8405 Feld[x][y] = Back[x][y];
8407 if (Back[newx][newy])
8408 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8410 Back[x][y] = Back[newx][newy] = 0;
8413 Store[x][y] = EL_EMPTY;
8418 MovDelay[newx][newy] = 0;
8420 if (CAN_CHANGE_OR_HAS_ACTION(element))
8422 // copy element change control values to new field
8423 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8424 ChangePage[newx][newy] = ChangePage[x][y];
8425 ChangeCount[newx][newy] = ChangeCount[x][y];
8426 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8429 CustomValue[newx][newy] = CustomValue[x][y];
8431 ChangeDelay[x][y] = 0;
8432 ChangePage[x][y] = -1;
8433 ChangeCount[x][y] = 0;
8434 ChangeEvent[x][y] = -1;
8436 CustomValue[x][y] = 0;
8438 // copy animation control values to new field
8439 GfxFrame[newx][newy] = GfxFrame[x][y];
8440 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8441 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8442 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8444 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8446 // some elements can leave other elements behind after moving
8447 if (ei->move_leave_element != EL_EMPTY &&
8448 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8449 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8451 int move_leave_element = ei->move_leave_element;
8453 // this makes it possible to leave the removed element again
8454 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8455 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8457 Feld[x][y] = move_leave_element;
8459 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8460 MovDir[x][y] = direction;
8462 InitField(x, y, FALSE);
8464 if (GFX_CRUMBLED(Feld[x][y]))
8465 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8467 if (ELEM_IS_PLAYER(move_leave_element))
8468 RelocatePlayer(x, y, move_leave_element);
8471 // do this after checking for left-behind element
8472 ResetGfxAnimation(x, y); // reset animation values for old field
8474 if (!CAN_MOVE(element) ||
8475 (CAN_FALL(element) && direction == MV_DOWN &&
8476 (element == EL_SPRING ||
8477 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8478 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8479 GfxDir[x][y] = MovDir[newx][newy] = 0;
8481 TEST_DrawLevelField(x, y);
8482 TEST_DrawLevelField(newx, newy);
8484 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8486 // prevent pushed element from moving on in pushed direction
8487 if (pushed_by_player && CAN_MOVE(element) &&
8488 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8489 !(element_info[element].move_pattern & direction))
8490 TurnRound(newx, newy);
8492 // prevent elements on conveyor belt from moving on in last direction
8493 if (pushed_by_conveyor && CAN_FALL(element) &&
8494 direction & MV_HORIZONTAL)
8495 MovDir[newx][newy] = 0;
8497 if (!pushed_by_player)
8499 int nextx = newx + dx, nexty = newy + dy;
8500 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8502 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8504 if (CAN_FALL(element) && direction == MV_DOWN)
8505 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8507 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8508 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8510 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8511 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8514 if (DONT_TOUCH(element)) // object may be nasty to player or others
8516 TestIfBadThingTouchesPlayer(newx, newy);
8517 TestIfBadThingTouchesFriend(newx, newy);
8519 if (!IS_CUSTOM_ELEMENT(element))
8520 TestIfBadThingTouchesOtherBadThing(newx, newy);
8522 else if (element == EL_PENGUIN)
8523 TestIfFriendTouchesBadThing(newx, newy);
8525 if (DONT_GET_HIT_BY(element))
8527 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8530 // give the player one last chance (one more frame) to move away
8531 if (CAN_FALL(element) && direction == MV_DOWN &&
8532 (last_line || (!IS_FREE(x, newy + 1) &&
8533 (!IS_PLAYER(x, newy + 1) ||
8534 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8537 if (pushed_by_player && !game.use_change_when_pushing_bug)
8539 int push_side = MV_DIR_OPPOSITE(direction);
8540 struct PlayerInfo *player = PLAYERINFO(x, y);
8542 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8543 player->index_bit, push_side);
8544 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8545 player->index_bit, push_side);
8548 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8549 MovDelay[newx][newy] = 1;
8551 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8553 TestIfElementTouchesCustomElement(x, y); // empty or new element
8554 TestIfElementHitsCustomElement(newx, newy, direction);
8555 TestIfPlayerTouchesCustomElement(newx, newy);
8556 TestIfElementTouchesCustomElement(newx, newy);
8558 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8559 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8560 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8561 MV_DIR_OPPOSITE(direction));
8564 int AmoebeNachbarNr(int ax, int ay)
8567 int element = Feld[ax][ay];
8569 static int xy[4][2] =
8577 for (i = 0; i < NUM_DIRECTIONS; i++)
8579 int x = ax + xy[i][0];
8580 int y = ay + xy[i][1];
8582 if (!IN_LEV_FIELD(x, y))
8585 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8586 group_nr = AmoebaNr[x][y];
8592 static void AmoebenVereinigen(int ax, int ay)
8594 int i, x, y, xx, yy;
8595 int new_group_nr = AmoebaNr[ax][ay];
8596 static int xy[4][2] =
8604 if (new_group_nr == 0)
8607 for (i = 0; i < NUM_DIRECTIONS; i++)
8612 if (!IN_LEV_FIELD(x, y))
8615 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8616 Feld[x][y] == EL_BD_AMOEBA ||
8617 Feld[x][y] == EL_AMOEBA_DEAD) &&
8618 AmoebaNr[x][y] != new_group_nr)
8620 int old_group_nr = AmoebaNr[x][y];
8622 if (old_group_nr == 0)
8625 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8626 AmoebaCnt[old_group_nr] = 0;
8627 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8628 AmoebaCnt2[old_group_nr] = 0;
8630 SCAN_PLAYFIELD(xx, yy)
8632 if (AmoebaNr[xx][yy] == old_group_nr)
8633 AmoebaNr[xx][yy] = new_group_nr;
8639 void AmoebeUmwandeln(int ax, int ay)
8643 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8645 int group_nr = AmoebaNr[ax][ay];
8650 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8651 printf("AmoebeUmwandeln(): This should never happen!\n");
8656 SCAN_PLAYFIELD(x, y)
8658 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8661 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8665 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8666 SND_AMOEBA_TURNING_TO_GEM :
8667 SND_AMOEBA_TURNING_TO_ROCK));
8672 static int xy[4][2] =
8680 for (i = 0; i < NUM_DIRECTIONS; i++)
8685 if (!IN_LEV_FIELD(x, y))
8688 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8690 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8691 SND_AMOEBA_TURNING_TO_GEM :
8692 SND_AMOEBA_TURNING_TO_ROCK));
8699 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8702 int group_nr = AmoebaNr[ax][ay];
8703 boolean done = FALSE;
8708 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8709 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8714 SCAN_PLAYFIELD(x, y)
8716 if (AmoebaNr[x][y] == group_nr &&
8717 (Feld[x][y] == EL_AMOEBA_DEAD ||
8718 Feld[x][y] == EL_BD_AMOEBA ||
8719 Feld[x][y] == EL_AMOEBA_GROWING))
8722 Feld[x][y] = new_element;
8723 InitField(x, y, FALSE);
8724 TEST_DrawLevelField(x, y);
8730 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8731 SND_BD_AMOEBA_TURNING_TO_ROCK :
8732 SND_BD_AMOEBA_TURNING_TO_GEM));
8735 static void AmoebeWaechst(int x, int y)
8737 static unsigned int sound_delay = 0;
8738 static unsigned int sound_delay_value = 0;
8740 if (!MovDelay[x][y]) // start new growing cycle
8744 if (DelayReached(&sound_delay, sound_delay_value))
8746 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8747 sound_delay_value = 30;
8751 if (MovDelay[x][y]) // wait some time before growing bigger
8754 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8756 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8757 6 - MovDelay[x][y]);
8759 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8762 if (!MovDelay[x][y])
8764 Feld[x][y] = Store[x][y];
8766 TEST_DrawLevelField(x, y);
8771 static void AmoebaDisappearing(int x, int y)
8773 static unsigned int sound_delay = 0;
8774 static unsigned int sound_delay_value = 0;
8776 if (!MovDelay[x][y]) // start new shrinking cycle
8780 if (DelayReached(&sound_delay, sound_delay_value))
8781 sound_delay_value = 30;
8784 if (MovDelay[x][y]) // wait some time before shrinking
8787 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8789 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8790 6 - MovDelay[x][y]);
8792 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8795 if (!MovDelay[x][y])
8797 Feld[x][y] = EL_EMPTY;
8798 TEST_DrawLevelField(x, y);
8800 // don't let mole enter this field in this cycle;
8801 // (give priority to objects falling to this field from above)
8807 static void AmoebeAbleger(int ax, int ay)
8810 int element = Feld[ax][ay];
8811 int graphic = el2img(element);
8812 int newax = ax, neway = ay;
8813 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8814 static int xy[4][2] =
8822 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8824 Feld[ax][ay] = EL_AMOEBA_DEAD;
8825 TEST_DrawLevelField(ax, ay);
8829 if (IS_ANIMATED(graphic))
8830 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8832 if (!MovDelay[ax][ay]) // start making new amoeba field
8833 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8835 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8838 if (MovDelay[ax][ay])
8842 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8845 int x = ax + xy[start][0];
8846 int y = ay + xy[start][1];
8848 if (!IN_LEV_FIELD(x, y))
8851 if (IS_FREE(x, y) ||
8852 CAN_GROW_INTO(Feld[x][y]) ||
8853 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8854 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8860 if (newax == ax && neway == ay)
8863 else // normal or "filled" (BD style) amoeba
8866 boolean waiting_for_player = FALSE;
8868 for (i = 0; i < NUM_DIRECTIONS; i++)
8870 int j = (start + i) % 4;
8871 int x = ax + xy[j][0];
8872 int y = ay + xy[j][1];
8874 if (!IN_LEV_FIELD(x, y))
8877 if (IS_FREE(x, y) ||
8878 CAN_GROW_INTO(Feld[x][y]) ||
8879 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8880 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8886 else if (IS_PLAYER(x, y))
8887 waiting_for_player = TRUE;
8890 if (newax == ax && neway == ay) // amoeba cannot grow
8892 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
8894 Feld[ax][ay] = EL_AMOEBA_DEAD;
8895 TEST_DrawLevelField(ax, ay);
8896 AmoebaCnt[AmoebaNr[ax][ay]]--;
8898 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
8900 if (element == EL_AMOEBA_FULL)
8901 AmoebeUmwandeln(ax, ay);
8902 else if (element == EL_BD_AMOEBA)
8903 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
8908 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
8910 // amoeba gets larger by growing in some direction
8912 int new_group_nr = AmoebaNr[ax][ay];
8915 if (new_group_nr == 0)
8917 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
8918 printf("AmoebeAbleger(): This should never happen!\n");
8923 AmoebaNr[newax][neway] = new_group_nr;
8924 AmoebaCnt[new_group_nr]++;
8925 AmoebaCnt2[new_group_nr]++;
8927 // if amoeba touches other amoeba(s) after growing, unify them
8928 AmoebenVereinigen(newax, neway);
8930 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
8932 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
8938 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
8939 (neway == lev_fieldy - 1 && newax != ax))
8941 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
8942 Store[newax][neway] = element;
8944 else if (neway == ay || element == EL_EMC_DRIPPER)
8946 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
8948 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
8952 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
8953 Feld[ax][ay] = EL_AMOEBA_DROPPING;
8954 Store[ax][ay] = EL_AMOEBA_DROP;
8955 ContinueMoving(ax, ay);
8959 TEST_DrawLevelField(newax, neway);
8962 static void Life(int ax, int ay)
8966 int element = Feld[ax][ay];
8967 int graphic = el2img(element);
8968 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
8970 boolean changed = FALSE;
8972 if (IS_ANIMATED(graphic))
8973 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8978 if (!MovDelay[ax][ay]) // start new "game of life" cycle
8979 MovDelay[ax][ay] = life_time;
8981 if (MovDelay[ax][ay]) // wait some time before next cycle
8984 if (MovDelay[ax][ay])
8988 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
8990 int xx = ax+x1, yy = ay+y1;
8991 int old_element = Feld[xx][yy];
8992 int num_neighbours = 0;
8994 if (!IN_LEV_FIELD(xx, yy))
8997 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
8999 int x = xx+x2, y = yy+y2;
9001 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9004 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9005 boolean is_neighbour = FALSE;
9007 if (level.use_life_bugs)
9009 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9010 (IS_FREE(x, y) && Stop[x][y]));
9013 (Last[x][y] == element || is_player_cell);
9019 boolean is_free = FALSE;
9021 if (level.use_life_bugs)
9022 is_free = (IS_FREE(xx, yy));
9024 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9026 if (xx == ax && yy == ay) // field in the middle
9028 if (num_neighbours < life_parameter[0] ||
9029 num_neighbours > life_parameter[1])
9031 Feld[xx][yy] = EL_EMPTY;
9032 if (Feld[xx][yy] != old_element)
9033 TEST_DrawLevelField(xx, yy);
9034 Stop[xx][yy] = TRUE;
9038 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9039 { // free border field
9040 if (num_neighbours >= life_parameter[2] &&
9041 num_neighbours <= life_parameter[3])
9043 Feld[xx][yy] = element;
9044 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9045 if (Feld[xx][yy] != old_element)
9046 TEST_DrawLevelField(xx, yy);
9047 Stop[xx][yy] = TRUE;
9054 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9055 SND_GAME_OF_LIFE_GROWING);
9058 static void InitRobotWheel(int x, int y)
9060 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9063 static void RunRobotWheel(int x, int y)
9065 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9068 static void StopRobotWheel(int x, int y)
9070 if (game.robot_wheel_x == x &&
9071 game.robot_wheel_y == y)
9073 game.robot_wheel_x = -1;
9074 game.robot_wheel_y = -1;
9075 game.robot_wheel_active = FALSE;
9079 static void InitTimegateWheel(int x, int y)
9081 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9084 static void RunTimegateWheel(int x, int y)
9086 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9089 static void InitMagicBallDelay(int x, int y)
9091 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9094 static void ActivateMagicBall(int bx, int by)
9098 if (level.ball_random)
9100 int pos_border = RND(8); // select one of the eight border elements
9101 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9102 int xx = pos_content % 3;
9103 int yy = pos_content / 3;
9108 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9109 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9113 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9115 int xx = x - bx + 1;
9116 int yy = y - by + 1;
9118 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9119 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9123 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9126 static void CheckExit(int x, int y)
9128 if (game.gems_still_needed > 0 ||
9129 game.sokoban_fields_still_needed > 0 ||
9130 game.sokoban_objects_still_needed > 0 ||
9131 game.lights_still_needed > 0)
9133 int element = Feld[x][y];
9134 int graphic = el2img(element);
9136 if (IS_ANIMATED(graphic))
9137 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9142 // do not re-open exit door closed after last player
9143 if (game.all_players_gone)
9146 Feld[x][y] = EL_EXIT_OPENING;
9148 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9151 static void CheckExitEM(int x, int y)
9153 if (game.gems_still_needed > 0 ||
9154 game.sokoban_fields_still_needed > 0 ||
9155 game.sokoban_objects_still_needed > 0 ||
9156 game.lights_still_needed > 0)
9158 int element = Feld[x][y];
9159 int graphic = el2img(element);
9161 if (IS_ANIMATED(graphic))
9162 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9167 // do not re-open exit door closed after last player
9168 if (game.all_players_gone)
9171 Feld[x][y] = EL_EM_EXIT_OPENING;
9173 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9176 static void CheckExitSteel(int x, int y)
9178 if (game.gems_still_needed > 0 ||
9179 game.sokoban_fields_still_needed > 0 ||
9180 game.sokoban_objects_still_needed > 0 ||
9181 game.lights_still_needed > 0)
9183 int element = Feld[x][y];
9184 int graphic = el2img(element);
9186 if (IS_ANIMATED(graphic))
9187 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9192 // do not re-open exit door closed after last player
9193 if (game.all_players_gone)
9196 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9198 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9201 static void CheckExitSteelEM(int x, int y)
9203 if (game.gems_still_needed > 0 ||
9204 game.sokoban_fields_still_needed > 0 ||
9205 game.sokoban_objects_still_needed > 0 ||
9206 game.lights_still_needed > 0)
9208 int element = Feld[x][y];
9209 int graphic = el2img(element);
9211 if (IS_ANIMATED(graphic))
9212 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9217 // do not re-open exit door closed after last player
9218 if (game.all_players_gone)
9221 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9223 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9226 static void CheckExitSP(int x, int y)
9228 if (game.gems_still_needed > 0)
9230 int element = Feld[x][y];
9231 int graphic = el2img(element);
9233 if (IS_ANIMATED(graphic))
9234 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9239 // do not re-open exit door closed after last player
9240 if (game.all_players_gone)
9243 Feld[x][y] = EL_SP_EXIT_OPENING;
9245 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9248 static void CloseAllOpenTimegates(void)
9252 SCAN_PLAYFIELD(x, y)
9254 int element = Feld[x][y];
9256 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9258 Feld[x][y] = EL_TIMEGATE_CLOSING;
9260 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9265 static void DrawTwinkleOnField(int x, int y)
9267 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9270 if (Feld[x][y] == EL_BD_DIAMOND)
9273 if (MovDelay[x][y] == 0) // next animation frame
9274 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9276 if (MovDelay[x][y] != 0) // wait some time before next frame
9280 DrawLevelElementAnimation(x, y, Feld[x][y]);
9282 if (MovDelay[x][y] != 0)
9284 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9285 10 - MovDelay[x][y]);
9287 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9292 static void MauerWaechst(int x, int y)
9296 if (!MovDelay[x][y]) // next animation frame
9297 MovDelay[x][y] = 3 * delay;
9299 if (MovDelay[x][y]) // wait some time before next frame
9303 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9305 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9306 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9308 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9311 if (!MovDelay[x][y])
9313 if (MovDir[x][y] == MV_LEFT)
9315 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9316 TEST_DrawLevelField(x - 1, y);
9318 else if (MovDir[x][y] == MV_RIGHT)
9320 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9321 TEST_DrawLevelField(x + 1, y);
9323 else if (MovDir[x][y] == MV_UP)
9325 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9326 TEST_DrawLevelField(x, y - 1);
9330 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9331 TEST_DrawLevelField(x, y + 1);
9334 Feld[x][y] = Store[x][y];
9336 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9337 TEST_DrawLevelField(x, y);
9342 static void MauerAbleger(int ax, int ay)
9344 int element = Feld[ax][ay];
9345 int graphic = el2img(element);
9346 boolean oben_frei = FALSE, unten_frei = FALSE;
9347 boolean links_frei = FALSE, rechts_frei = FALSE;
9348 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9349 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9350 boolean new_wall = FALSE;
9352 if (IS_ANIMATED(graphic))
9353 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9355 if (!MovDelay[ax][ay]) // start building new wall
9356 MovDelay[ax][ay] = 6;
9358 if (MovDelay[ax][ay]) // wait some time before building new wall
9361 if (MovDelay[ax][ay])
9365 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9367 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9369 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9371 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9374 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9375 element == EL_EXPANDABLE_WALL_ANY)
9379 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9380 Store[ax][ay-1] = element;
9381 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9382 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9383 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9384 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9389 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9390 Store[ax][ay+1] = element;
9391 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9392 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9393 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9394 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9399 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9400 element == EL_EXPANDABLE_WALL_ANY ||
9401 element == EL_EXPANDABLE_WALL ||
9402 element == EL_BD_EXPANDABLE_WALL)
9406 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9407 Store[ax-1][ay] = element;
9408 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9409 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9410 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9411 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9417 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9418 Store[ax+1][ay] = element;
9419 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9420 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9421 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9422 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9427 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9428 TEST_DrawLevelField(ax, ay);
9430 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9432 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9433 unten_massiv = TRUE;
9434 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9435 links_massiv = TRUE;
9436 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9437 rechts_massiv = TRUE;
9439 if (((oben_massiv && unten_massiv) ||
9440 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9441 element == EL_EXPANDABLE_WALL) &&
9442 ((links_massiv && rechts_massiv) ||
9443 element == EL_EXPANDABLE_WALL_VERTICAL))
9444 Feld[ax][ay] = EL_WALL;
9447 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9450 static void MauerAblegerStahl(int ax, int ay)
9452 int element = Feld[ax][ay];
9453 int graphic = el2img(element);
9454 boolean oben_frei = FALSE, unten_frei = FALSE;
9455 boolean links_frei = FALSE, rechts_frei = FALSE;
9456 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9457 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9458 boolean new_wall = FALSE;
9460 if (IS_ANIMATED(graphic))
9461 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9463 if (!MovDelay[ax][ay]) // start building new wall
9464 MovDelay[ax][ay] = 6;
9466 if (MovDelay[ax][ay]) // wait some time before building new wall
9469 if (MovDelay[ax][ay])
9473 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9475 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9477 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9479 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9482 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9483 element == EL_EXPANDABLE_STEELWALL_ANY)
9487 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9488 Store[ax][ay-1] = element;
9489 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9490 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9491 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9492 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9497 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9498 Store[ax][ay+1] = element;
9499 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9500 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9501 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9502 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9507 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9508 element == EL_EXPANDABLE_STEELWALL_ANY)
9512 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9513 Store[ax-1][ay] = element;
9514 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9515 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9516 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9517 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9523 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9524 Store[ax+1][ay] = element;
9525 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9526 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9527 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9528 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9533 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9535 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9536 unten_massiv = TRUE;
9537 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9538 links_massiv = TRUE;
9539 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9540 rechts_massiv = TRUE;
9542 if (((oben_massiv && unten_massiv) ||
9543 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9544 ((links_massiv && rechts_massiv) ||
9545 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9546 Feld[ax][ay] = EL_STEELWALL;
9549 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9552 static void CheckForDragon(int x, int y)
9555 boolean dragon_found = FALSE;
9556 static int xy[4][2] =
9564 for (i = 0; i < NUM_DIRECTIONS; i++)
9566 for (j = 0; j < 4; j++)
9568 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9570 if (IN_LEV_FIELD(xx, yy) &&
9571 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9573 if (Feld[xx][yy] == EL_DRAGON)
9574 dragon_found = TRUE;
9583 for (i = 0; i < NUM_DIRECTIONS; i++)
9585 for (j = 0; j < 3; j++)
9587 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9589 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9591 Feld[xx][yy] = EL_EMPTY;
9592 TEST_DrawLevelField(xx, yy);
9601 static void InitBuggyBase(int x, int y)
9603 int element = Feld[x][y];
9604 int activating_delay = FRAMES_PER_SECOND / 4;
9607 (element == EL_SP_BUGGY_BASE ?
9608 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9609 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9611 element == EL_SP_BUGGY_BASE_ACTIVE ?
9612 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9615 static void WarnBuggyBase(int x, int y)
9618 static int xy[4][2] =
9626 for (i = 0; i < NUM_DIRECTIONS; i++)
9628 int xx = x + xy[i][0];
9629 int yy = y + xy[i][1];
9631 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9633 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9640 static void InitTrap(int x, int y)
9642 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9645 static void ActivateTrap(int x, int y)
9647 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9650 static void ChangeActiveTrap(int x, int y)
9652 int graphic = IMG_TRAP_ACTIVE;
9654 // if new animation frame was drawn, correct crumbled sand border
9655 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9656 TEST_DrawLevelFieldCrumbled(x, y);
9659 static int getSpecialActionElement(int element, int number, int base_element)
9661 return (element != EL_EMPTY ? element :
9662 number != -1 ? base_element + number - 1 :
9666 static int getModifiedActionNumber(int value_old, int operator, int operand,
9667 int value_min, int value_max)
9669 int value_new = (operator == CA_MODE_SET ? operand :
9670 operator == CA_MODE_ADD ? value_old + operand :
9671 operator == CA_MODE_SUBTRACT ? value_old - operand :
9672 operator == CA_MODE_MULTIPLY ? value_old * operand :
9673 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9674 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9677 return (value_new < value_min ? value_min :
9678 value_new > value_max ? value_max :
9682 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9684 struct ElementInfo *ei = &element_info[element];
9685 struct ElementChangeInfo *change = &ei->change_page[page];
9686 int target_element = change->target_element;
9687 int action_type = change->action_type;
9688 int action_mode = change->action_mode;
9689 int action_arg = change->action_arg;
9690 int action_element = change->action_element;
9693 if (!change->has_action)
9696 // ---------- determine action paramater values -----------------------------
9698 int level_time_value =
9699 (level.time > 0 ? TimeLeft :
9702 int action_arg_element_raw =
9703 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9704 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9705 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9706 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9707 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9708 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9709 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9711 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9713 int action_arg_direction =
9714 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9715 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9716 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9717 change->actual_trigger_side :
9718 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9719 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9722 int action_arg_number_min =
9723 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9726 int action_arg_number_max =
9727 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9728 action_type == CA_SET_LEVEL_GEMS ? 999 :
9729 action_type == CA_SET_LEVEL_TIME ? 9999 :
9730 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9731 action_type == CA_SET_CE_VALUE ? 9999 :
9732 action_type == CA_SET_CE_SCORE ? 9999 :
9735 int action_arg_number_reset =
9736 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9737 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9738 action_type == CA_SET_LEVEL_TIME ? level.time :
9739 action_type == CA_SET_LEVEL_SCORE ? 0 :
9740 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9741 action_type == CA_SET_CE_SCORE ? 0 :
9744 int action_arg_number =
9745 (action_arg <= CA_ARG_MAX ? action_arg :
9746 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9747 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9748 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9749 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9750 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9751 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9752 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9753 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9754 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9755 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9756 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9757 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9758 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9759 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9760 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9761 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9762 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9763 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9764 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9765 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9766 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9769 int action_arg_number_old =
9770 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9771 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9772 action_type == CA_SET_LEVEL_SCORE ? game.score :
9773 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9774 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9777 int action_arg_number_new =
9778 getModifiedActionNumber(action_arg_number_old,
9779 action_mode, action_arg_number,
9780 action_arg_number_min, action_arg_number_max);
9782 int trigger_player_bits =
9783 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9784 change->actual_trigger_player_bits : change->trigger_player);
9786 int action_arg_player_bits =
9787 (action_arg >= CA_ARG_PLAYER_1 &&
9788 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9789 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9790 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9793 // ---------- execute action -----------------------------------------------
9795 switch (action_type)
9802 // ---------- level actions ----------------------------------------------
9804 case CA_RESTART_LEVEL:
9806 game.restart_level = TRUE;
9811 case CA_SHOW_ENVELOPE:
9813 int element = getSpecialActionElement(action_arg_element,
9814 action_arg_number, EL_ENVELOPE_1);
9816 if (IS_ENVELOPE(element))
9817 local_player->show_envelope = element;
9822 case CA_SET_LEVEL_TIME:
9824 if (level.time > 0) // only modify limited time value
9826 TimeLeft = action_arg_number_new;
9828 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9830 DisplayGameControlValues();
9832 if (!TimeLeft && setup.time_limit)
9833 for (i = 0; i < MAX_PLAYERS; i++)
9834 KillPlayer(&stored_player[i]);
9840 case CA_SET_LEVEL_SCORE:
9842 game.score = action_arg_number_new;
9844 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9846 DisplayGameControlValues();
9851 case CA_SET_LEVEL_GEMS:
9853 game.gems_still_needed = action_arg_number_new;
9855 game.snapshot.collected_item = TRUE;
9857 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9859 DisplayGameControlValues();
9864 case CA_SET_LEVEL_WIND:
9866 game.wind_direction = action_arg_direction;
9871 case CA_SET_LEVEL_RANDOM_SEED:
9873 // ensure that setting a new random seed while playing is predictable
9874 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9879 // ---------- player actions ---------------------------------------------
9881 case CA_MOVE_PLAYER:
9883 // automatically move to the next field in specified direction
9884 for (i = 0; i < MAX_PLAYERS; i++)
9885 if (trigger_player_bits & (1 << i))
9886 stored_player[i].programmed_action = action_arg_direction;
9891 case CA_EXIT_PLAYER:
9893 for (i = 0; i < MAX_PLAYERS; i++)
9894 if (action_arg_player_bits & (1 << i))
9895 ExitPlayer(&stored_player[i]);
9897 if (game.players_still_needed == 0)
9903 case CA_KILL_PLAYER:
9905 for (i = 0; i < MAX_PLAYERS; i++)
9906 if (action_arg_player_bits & (1 << i))
9907 KillPlayer(&stored_player[i]);
9912 case CA_SET_PLAYER_KEYS:
9914 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
9915 int element = getSpecialActionElement(action_arg_element,
9916 action_arg_number, EL_KEY_1);
9918 if (IS_KEY(element))
9920 for (i = 0; i < MAX_PLAYERS; i++)
9922 if (trigger_player_bits & (1 << i))
9924 stored_player[i].key[KEY_NR(element)] = key_state;
9926 DrawGameDoorValues();
9934 case CA_SET_PLAYER_SPEED:
9936 for (i = 0; i < MAX_PLAYERS; i++)
9938 if (trigger_player_bits & (1 << i))
9940 int move_stepsize = TILEX / stored_player[i].move_delay_value;
9942 if (action_arg == CA_ARG_SPEED_FASTER &&
9943 stored_player[i].cannot_move)
9945 action_arg_number = STEPSIZE_VERY_SLOW;
9947 else if (action_arg == CA_ARG_SPEED_SLOWER ||
9948 action_arg == CA_ARG_SPEED_FASTER)
9950 action_arg_number = 2;
9951 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
9954 else if (action_arg == CA_ARG_NUMBER_RESET)
9956 action_arg_number = level.initial_player_stepsize[i];
9960 getModifiedActionNumber(move_stepsize,
9963 action_arg_number_min,
9964 action_arg_number_max);
9966 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
9973 case CA_SET_PLAYER_SHIELD:
9975 for (i = 0; i < MAX_PLAYERS; i++)
9977 if (trigger_player_bits & (1 << i))
9979 if (action_arg == CA_ARG_SHIELD_OFF)
9981 stored_player[i].shield_normal_time_left = 0;
9982 stored_player[i].shield_deadly_time_left = 0;
9984 else if (action_arg == CA_ARG_SHIELD_NORMAL)
9986 stored_player[i].shield_normal_time_left = 999999;
9988 else if (action_arg == CA_ARG_SHIELD_DEADLY)
9990 stored_player[i].shield_normal_time_left = 999999;
9991 stored_player[i].shield_deadly_time_left = 999999;
9999 case CA_SET_PLAYER_GRAVITY:
10001 for (i = 0; i < MAX_PLAYERS; i++)
10003 if (trigger_player_bits & (1 << i))
10005 stored_player[i].gravity =
10006 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10007 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10008 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10009 stored_player[i].gravity);
10016 case CA_SET_PLAYER_ARTWORK:
10018 for (i = 0; i < MAX_PLAYERS; i++)
10020 if (trigger_player_bits & (1 << i))
10022 int artwork_element = action_arg_element;
10024 if (action_arg == CA_ARG_ELEMENT_RESET)
10026 (level.use_artwork_element[i] ? level.artwork_element[i] :
10027 stored_player[i].element_nr);
10029 if (stored_player[i].artwork_element != artwork_element)
10030 stored_player[i].Frame = 0;
10032 stored_player[i].artwork_element = artwork_element;
10034 SetPlayerWaiting(&stored_player[i], FALSE);
10036 // set number of special actions for bored and sleeping animation
10037 stored_player[i].num_special_action_bored =
10038 get_num_special_action(artwork_element,
10039 ACTION_BORING_1, ACTION_BORING_LAST);
10040 stored_player[i].num_special_action_sleeping =
10041 get_num_special_action(artwork_element,
10042 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10049 case CA_SET_PLAYER_INVENTORY:
10051 for (i = 0; i < MAX_PLAYERS; i++)
10053 struct PlayerInfo *player = &stored_player[i];
10056 if (trigger_player_bits & (1 << i))
10058 int inventory_element = action_arg_element;
10060 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10061 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10062 action_arg == CA_ARG_ELEMENT_ACTION)
10064 int element = inventory_element;
10065 int collect_count = element_info[element].collect_count_initial;
10067 if (!IS_CUSTOM_ELEMENT(element))
10070 if (collect_count == 0)
10071 player->inventory_infinite_element = element;
10073 for (k = 0; k < collect_count; k++)
10074 if (player->inventory_size < MAX_INVENTORY_SIZE)
10075 player->inventory_element[player->inventory_size++] =
10078 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10079 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10080 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10082 if (player->inventory_infinite_element != EL_UNDEFINED &&
10083 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10084 action_arg_element_raw))
10085 player->inventory_infinite_element = EL_UNDEFINED;
10087 for (k = 0, j = 0; j < player->inventory_size; j++)
10089 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10090 action_arg_element_raw))
10091 player->inventory_element[k++] = player->inventory_element[j];
10094 player->inventory_size = k;
10096 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10098 if (player->inventory_size > 0)
10100 for (j = 0; j < player->inventory_size - 1; j++)
10101 player->inventory_element[j] = player->inventory_element[j + 1];
10103 player->inventory_size--;
10106 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10108 if (player->inventory_size > 0)
10109 player->inventory_size--;
10111 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10113 player->inventory_infinite_element = EL_UNDEFINED;
10114 player->inventory_size = 0;
10116 else if (action_arg == CA_ARG_INVENTORY_RESET)
10118 player->inventory_infinite_element = EL_UNDEFINED;
10119 player->inventory_size = 0;
10121 if (level.use_initial_inventory[i])
10123 for (j = 0; j < level.initial_inventory_size[i]; j++)
10125 int element = level.initial_inventory_content[i][j];
10126 int collect_count = element_info[element].collect_count_initial;
10128 if (!IS_CUSTOM_ELEMENT(element))
10131 if (collect_count == 0)
10132 player->inventory_infinite_element = element;
10134 for (k = 0; k < collect_count; k++)
10135 if (player->inventory_size < MAX_INVENTORY_SIZE)
10136 player->inventory_element[player->inventory_size++] =
10147 // ---------- CE actions -------------------------------------------------
10149 case CA_SET_CE_VALUE:
10151 int last_ce_value = CustomValue[x][y];
10153 CustomValue[x][y] = action_arg_number_new;
10155 if (CustomValue[x][y] != last_ce_value)
10157 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10158 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10160 if (CustomValue[x][y] == 0)
10162 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10163 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10170 case CA_SET_CE_SCORE:
10172 int last_ce_score = ei->collect_score;
10174 ei->collect_score = action_arg_number_new;
10176 if (ei->collect_score != last_ce_score)
10178 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10179 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10181 if (ei->collect_score == 0)
10185 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10186 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10189 This is a very special case that seems to be a mixture between
10190 CheckElementChange() and CheckTriggeredElementChange(): while
10191 the first one only affects single elements that are triggered
10192 directly, the second one affects multiple elements in the playfield
10193 that are triggered indirectly by another element. This is a third
10194 case: Changing the CE score always affects multiple identical CEs,
10195 so every affected CE must be checked, not only the single CE for
10196 which the CE score was changed in the first place (as every instance
10197 of that CE shares the same CE score, and therefore also can change)!
10199 SCAN_PLAYFIELD(xx, yy)
10201 if (Feld[xx][yy] == element)
10202 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10203 CE_SCORE_GETS_ZERO);
10211 case CA_SET_CE_ARTWORK:
10213 int artwork_element = action_arg_element;
10214 boolean reset_frame = FALSE;
10217 if (action_arg == CA_ARG_ELEMENT_RESET)
10218 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10221 if (ei->gfx_element != artwork_element)
10222 reset_frame = TRUE;
10224 ei->gfx_element = artwork_element;
10226 SCAN_PLAYFIELD(xx, yy)
10228 if (Feld[xx][yy] == element)
10232 ResetGfxAnimation(xx, yy);
10233 ResetRandomAnimationValue(xx, yy);
10236 TEST_DrawLevelField(xx, yy);
10243 // ---------- engine actions ---------------------------------------------
10245 case CA_SET_ENGINE_SCAN_MODE:
10247 InitPlayfieldScanMode(action_arg);
10257 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10259 int old_element = Feld[x][y];
10260 int new_element = GetElementFromGroupElement(element);
10261 int previous_move_direction = MovDir[x][y];
10262 int last_ce_value = CustomValue[x][y];
10263 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10264 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10265 boolean add_player_onto_element = (new_element_is_player &&
10266 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10267 IS_WALKABLE(old_element));
10269 if (!add_player_onto_element)
10271 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10272 RemoveMovingField(x, y);
10276 Feld[x][y] = new_element;
10278 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10279 MovDir[x][y] = previous_move_direction;
10281 if (element_info[new_element].use_last_ce_value)
10282 CustomValue[x][y] = last_ce_value;
10284 InitField_WithBug1(x, y, FALSE);
10286 new_element = Feld[x][y]; // element may have changed
10288 ResetGfxAnimation(x, y);
10289 ResetRandomAnimationValue(x, y);
10291 TEST_DrawLevelField(x, y);
10293 if (GFX_CRUMBLED(new_element))
10294 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10297 // check if element under the player changes from accessible to unaccessible
10298 // (needed for special case of dropping element which then changes)
10299 // (must be checked after creating new element for walkable group elements)
10300 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10301 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10308 // "ChangeCount" not set yet to allow "entered by player" change one time
10309 if (new_element_is_player)
10310 RelocatePlayer(x, y, new_element);
10313 ChangeCount[x][y]++; // count number of changes in the same frame
10315 TestIfBadThingTouchesPlayer(x, y);
10316 TestIfPlayerTouchesCustomElement(x, y);
10317 TestIfElementTouchesCustomElement(x, y);
10320 static void CreateField(int x, int y, int element)
10322 CreateFieldExt(x, y, element, FALSE);
10325 static void CreateElementFromChange(int x, int y, int element)
10327 element = GET_VALID_RUNTIME_ELEMENT(element);
10329 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10331 int old_element = Feld[x][y];
10333 // prevent changed element from moving in same engine frame
10334 // unless both old and new element can either fall or move
10335 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10336 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10340 CreateFieldExt(x, y, element, TRUE);
10343 static boolean ChangeElement(int x, int y, int element, int page)
10345 struct ElementInfo *ei = &element_info[element];
10346 struct ElementChangeInfo *change = &ei->change_page[page];
10347 int ce_value = CustomValue[x][y];
10348 int ce_score = ei->collect_score;
10349 int target_element;
10350 int old_element = Feld[x][y];
10352 // always use default change event to prevent running into a loop
10353 if (ChangeEvent[x][y] == -1)
10354 ChangeEvent[x][y] = CE_DELAY;
10356 if (ChangeEvent[x][y] == CE_DELAY)
10358 // reset actual trigger element, trigger player and action element
10359 change->actual_trigger_element = EL_EMPTY;
10360 change->actual_trigger_player = EL_EMPTY;
10361 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10362 change->actual_trigger_side = CH_SIDE_NONE;
10363 change->actual_trigger_ce_value = 0;
10364 change->actual_trigger_ce_score = 0;
10367 // do not change elements more than a specified maximum number of changes
10368 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10371 ChangeCount[x][y]++; // count number of changes in the same frame
10373 if (change->explode)
10380 if (change->use_target_content)
10382 boolean complete_replace = TRUE;
10383 boolean can_replace[3][3];
10386 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10389 boolean is_walkable;
10390 boolean is_diggable;
10391 boolean is_collectible;
10392 boolean is_removable;
10393 boolean is_destructible;
10394 int ex = x + xx - 1;
10395 int ey = y + yy - 1;
10396 int content_element = change->target_content.e[xx][yy];
10399 can_replace[xx][yy] = TRUE;
10401 if (ex == x && ey == y) // do not check changing element itself
10404 if (content_element == EL_EMPTY_SPACE)
10406 can_replace[xx][yy] = FALSE; // do not replace border with space
10411 if (!IN_LEV_FIELD(ex, ey))
10413 can_replace[xx][yy] = FALSE;
10414 complete_replace = FALSE;
10421 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10422 e = MovingOrBlocked2Element(ex, ey);
10424 is_empty = (IS_FREE(ex, ey) ||
10425 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10427 is_walkable = (is_empty || IS_WALKABLE(e));
10428 is_diggable = (is_empty || IS_DIGGABLE(e));
10429 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10430 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10431 is_removable = (is_diggable || is_collectible);
10433 can_replace[xx][yy] =
10434 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10435 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10436 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10437 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10438 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10439 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10440 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10442 if (!can_replace[xx][yy])
10443 complete_replace = FALSE;
10446 if (!change->only_if_complete || complete_replace)
10448 boolean something_has_changed = FALSE;
10450 if (change->only_if_complete && change->use_random_replace &&
10451 RND(100) < change->random_percentage)
10454 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10456 int ex = x + xx - 1;
10457 int ey = y + yy - 1;
10458 int content_element;
10460 if (can_replace[xx][yy] && (!change->use_random_replace ||
10461 RND(100) < change->random_percentage))
10463 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10464 RemoveMovingField(ex, ey);
10466 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10468 content_element = change->target_content.e[xx][yy];
10469 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10470 ce_value, ce_score);
10472 CreateElementFromChange(ex, ey, target_element);
10474 something_has_changed = TRUE;
10476 // for symmetry reasons, freeze newly created border elements
10477 if (ex != x || ey != y)
10478 Stop[ex][ey] = TRUE; // no more moving in this frame
10482 if (something_has_changed)
10484 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10485 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10491 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10492 ce_value, ce_score);
10494 if (element == EL_DIAGONAL_GROWING ||
10495 element == EL_DIAGONAL_SHRINKING)
10497 target_element = Store[x][y];
10499 Store[x][y] = EL_EMPTY;
10502 CreateElementFromChange(x, y, target_element);
10504 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10505 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10508 // this uses direct change before indirect change
10509 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10514 static void HandleElementChange(int x, int y, int page)
10516 int element = MovingOrBlocked2Element(x, y);
10517 struct ElementInfo *ei = &element_info[element];
10518 struct ElementChangeInfo *change = &ei->change_page[page];
10519 boolean handle_action_before_change = FALSE;
10522 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10523 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10526 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10527 x, y, element, element_info[element].token_name);
10528 printf("HandleElementChange(): This should never happen!\n");
10533 // this can happen with classic bombs on walkable, changing elements
10534 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10539 if (ChangeDelay[x][y] == 0) // initialize element change
10541 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10543 if (change->can_change)
10545 // !!! not clear why graphic animation should be reset at all here !!!
10546 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10547 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10550 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10552 When using an animation frame delay of 1 (this only happens with
10553 "sp_zonk.moving.left/right" in the classic graphics), the default
10554 (non-moving) animation shows wrong animation frames (while the
10555 moving animation, like "sp_zonk.moving.left/right", is correct,
10556 so this graphical bug never shows up with the classic graphics).
10557 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10558 be drawn instead of the correct frames 0,1,2,3. This is caused by
10559 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10560 an element change: First when the change delay ("ChangeDelay[][]")
10561 counter has reached zero after decrementing, then a second time in
10562 the next frame (after "GfxFrame[][]" was already incremented) when
10563 "ChangeDelay[][]" is reset to the initial delay value again.
10565 This causes frame 0 to be drawn twice, while the last frame won't
10566 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10568 As some animations may already be cleverly designed around this bug
10569 (at least the "Snake Bite" snake tail animation does this), it cannot
10570 simply be fixed here without breaking such existing animations.
10571 Unfortunately, it cannot easily be detected if a graphics set was
10572 designed "before" or "after" the bug was fixed. As a workaround,
10573 a new graphics set option "game.graphics_engine_version" was added
10574 to be able to specify the game's major release version for which the
10575 graphics set was designed, which can then be used to decide if the
10576 bugfix should be used (version 4 and above) or not (version 3 or
10577 below, or if no version was specified at all, as with old sets).
10579 (The wrong/fixed animation frames can be tested with the test level set
10580 "test_gfxframe" and level "000", which contains a specially prepared
10581 custom element at level position (x/y) == (11/9) which uses the zonk
10582 animation mentioned above. Using "game.graphics_engine_version: 4"
10583 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10584 This can also be seen from the debug output for this test element.)
10587 // when a custom element is about to change (for example by change delay),
10588 // do not reset graphic animation when the custom element is moving
10589 if (game.graphics_engine_version < 4 &&
10592 ResetGfxAnimation(x, y);
10593 ResetRandomAnimationValue(x, y);
10596 if (change->pre_change_function)
10597 change->pre_change_function(x, y);
10601 ChangeDelay[x][y]--;
10603 if (ChangeDelay[x][y] != 0) // continue element change
10605 if (change->can_change)
10607 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10609 if (IS_ANIMATED(graphic))
10610 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10612 if (change->change_function)
10613 change->change_function(x, y);
10616 else // finish element change
10618 if (ChangePage[x][y] != -1) // remember page from delayed change
10620 page = ChangePage[x][y];
10621 ChangePage[x][y] = -1;
10623 change = &ei->change_page[page];
10626 if (IS_MOVING(x, y)) // never change a running system ;-)
10628 ChangeDelay[x][y] = 1; // try change after next move step
10629 ChangePage[x][y] = page; // remember page to use for change
10634 // special case: set new level random seed before changing element
10635 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10636 handle_action_before_change = TRUE;
10638 if (change->has_action && handle_action_before_change)
10639 ExecuteCustomElementAction(x, y, element, page);
10641 if (change->can_change)
10643 if (ChangeElement(x, y, element, page))
10645 if (change->post_change_function)
10646 change->post_change_function(x, y);
10650 if (change->has_action && !handle_action_before_change)
10651 ExecuteCustomElementAction(x, y, element, page);
10655 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10656 int trigger_element,
10658 int trigger_player,
10662 boolean change_done_any = FALSE;
10663 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10666 if (!(trigger_events[trigger_element][trigger_event]))
10669 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10671 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10673 int element = EL_CUSTOM_START + i;
10674 boolean change_done = FALSE;
10677 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10678 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10681 for (p = 0; p < element_info[element].num_change_pages; p++)
10683 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10685 if (change->can_change_or_has_action &&
10686 change->has_event[trigger_event] &&
10687 change->trigger_side & trigger_side &&
10688 change->trigger_player & trigger_player &&
10689 change->trigger_page & trigger_page_bits &&
10690 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10692 change->actual_trigger_element = trigger_element;
10693 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10694 change->actual_trigger_player_bits = trigger_player;
10695 change->actual_trigger_side = trigger_side;
10696 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10697 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10699 if ((change->can_change && !change_done) || change->has_action)
10703 SCAN_PLAYFIELD(x, y)
10705 if (Feld[x][y] == element)
10707 if (change->can_change && !change_done)
10709 // if element already changed in this frame, not only prevent
10710 // another element change (checked in ChangeElement()), but
10711 // also prevent additional element actions for this element
10713 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10714 !level.use_action_after_change_bug)
10717 ChangeDelay[x][y] = 1;
10718 ChangeEvent[x][y] = trigger_event;
10720 HandleElementChange(x, y, p);
10722 else if (change->has_action)
10724 // if element already changed in this frame, not only prevent
10725 // another element change (checked in ChangeElement()), but
10726 // also prevent additional element actions for this element
10728 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10729 !level.use_action_after_change_bug)
10732 ExecuteCustomElementAction(x, y, element, p);
10733 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10738 if (change->can_change)
10740 change_done = TRUE;
10741 change_done_any = TRUE;
10748 RECURSION_LOOP_DETECTION_END();
10750 return change_done_any;
10753 static boolean CheckElementChangeExt(int x, int y,
10755 int trigger_element,
10757 int trigger_player,
10760 boolean change_done = FALSE;
10763 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10764 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10767 if (Feld[x][y] == EL_BLOCKED)
10769 Blocked2Moving(x, y, &x, &y);
10770 element = Feld[x][y];
10773 // check if element has already changed or is about to change after moving
10774 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10775 Feld[x][y] != element) ||
10777 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10778 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10779 ChangePage[x][y] != -1)))
10782 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10784 for (p = 0; p < element_info[element].num_change_pages; p++)
10786 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10788 /* check trigger element for all events where the element that is checked
10789 for changing interacts with a directly adjacent element -- this is
10790 different to element changes that affect other elements to change on the
10791 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10792 boolean check_trigger_element =
10793 (trigger_event == CE_TOUCHING_X ||
10794 trigger_event == CE_HITTING_X ||
10795 trigger_event == CE_HIT_BY_X ||
10796 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10798 if (change->can_change_or_has_action &&
10799 change->has_event[trigger_event] &&
10800 change->trigger_side & trigger_side &&
10801 change->trigger_player & trigger_player &&
10802 (!check_trigger_element ||
10803 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10805 change->actual_trigger_element = trigger_element;
10806 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10807 change->actual_trigger_player_bits = trigger_player;
10808 change->actual_trigger_side = trigger_side;
10809 change->actual_trigger_ce_value = CustomValue[x][y];
10810 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10812 // special case: trigger element not at (x,y) position for some events
10813 if (check_trigger_element)
10825 { 0, 0 }, { 0, 0 }, { 0, 0 },
10829 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10830 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10832 change->actual_trigger_ce_value = CustomValue[xx][yy];
10833 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10836 if (change->can_change && !change_done)
10838 ChangeDelay[x][y] = 1;
10839 ChangeEvent[x][y] = trigger_event;
10841 HandleElementChange(x, y, p);
10843 change_done = TRUE;
10845 else if (change->has_action)
10847 ExecuteCustomElementAction(x, y, element, p);
10848 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10853 RECURSION_LOOP_DETECTION_END();
10855 return change_done;
10858 static void PlayPlayerSound(struct PlayerInfo *player)
10860 int jx = player->jx, jy = player->jy;
10861 int sound_element = player->artwork_element;
10862 int last_action = player->last_action_waiting;
10863 int action = player->action_waiting;
10865 if (player->is_waiting)
10867 if (action != last_action)
10868 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10870 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10874 if (action != last_action)
10875 StopSound(element_info[sound_element].sound[last_action]);
10877 if (last_action == ACTION_SLEEPING)
10878 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
10882 static void PlayAllPlayersSound(void)
10886 for (i = 0; i < MAX_PLAYERS; i++)
10887 if (stored_player[i].active)
10888 PlayPlayerSound(&stored_player[i]);
10891 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
10893 boolean last_waiting = player->is_waiting;
10894 int move_dir = player->MovDir;
10896 player->dir_waiting = move_dir;
10897 player->last_action_waiting = player->action_waiting;
10901 if (!last_waiting) // not waiting -> waiting
10903 player->is_waiting = TRUE;
10905 player->frame_counter_bored =
10907 game.player_boring_delay_fixed +
10908 GetSimpleRandom(game.player_boring_delay_random);
10909 player->frame_counter_sleeping =
10911 game.player_sleeping_delay_fixed +
10912 GetSimpleRandom(game.player_sleeping_delay_random);
10914 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
10917 if (game.player_sleeping_delay_fixed +
10918 game.player_sleeping_delay_random > 0 &&
10919 player->anim_delay_counter == 0 &&
10920 player->post_delay_counter == 0 &&
10921 FrameCounter >= player->frame_counter_sleeping)
10922 player->is_sleeping = TRUE;
10923 else if (game.player_boring_delay_fixed +
10924 game.player_boring_delay_random > 0 &&
10925 FrameCounter >= player->frame_counter_bored)
10926 player->is_bored = TRUE;
10928 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
10929 player->is_bored ? ACTION_BORING :
10932 if (player->is_sleeping && player->use_murphy)
10934 // special case for sleeping Murphy when leaning against non-free tile
10936 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
10937 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
10938 !IS_MOVING(player->jx - 1, player->jy)))
10939 move_dir = MV_LEFT;
10940 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
10941 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
10942 !IS_MOVING(player->jx + 1, player->jy)))
10943 move_dir = MV_RIGHT;
10945 player->is_sleeping = FALSE;
10947 player->dir_waiting = move_dir;
10950 if (player->is_sleeping)
10952 if (player->num_special_action_sleeping > 0)
10954 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10956 int last_special_action = player->special_action_sleeping;
10957 int num_special_action = player->num_special_action_sleeping;
10958 int special_action =
10959 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
10960 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
10961 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
10962 last_special_action + 1 : ACTION_SLEEPING);
10963 int special_graphic =
10964 el_act_dir2img(player->artwork_element, special_action, move_dir);
10966 player->anim_delay_counter =
10967 graphic_info[special_graphic].anim_delay_fixed +
10968 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10969 player->post_delay_counter =
10970 graphic_info[special_graphic].post_delay_fixed +
10971 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10973 player->special_action_sleeping = special_action;
10976 if (player->anim_delay_counter > 0)
10978 player->action_waiting = player->special_action_sleeping;
10979 player->anim_delay_counter--;
10981 else if (player->post_delay_counter > 0)
10983 player->post_delay_counter--;
10987 else if (player->is_bored)
10989 if (player->num_special_action_bored > 0)
10991 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10993 int special_action =
10994 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
10995 int special_graphic =
10996 el_act_dir2img(player->artwork_element, special_action, move_dir);
10998 player->anim_delay_counter =
10999 graphic_info[special_graphic].anim_delay_fixed +
11000 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11001 player->post_delay_counter =
11002 graphic_info[special_graphic].post_delay_fixed +
11003 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11005 player->special_action_bored = special_action;
11008 if (player->anim_delay_counter > 0)
11010 player->action_waiting = player->special_action_bored;
11011 player->anim_delay_counter--;
11013 else if (player->post_delay_counter > 0)
11015 player->post_delay_counter--;
11020 else if (last_waiting) // waiting -> not waiting
11022 player->is_waiting = FALSE;
11023 player->is_bored = FALSE;
11024 player->is_sleeping = FALSE;
11026 player->frame_counter_bored = -1;
11027 player->frame_counter_sleeping = -1;
11029 player->anim_delay_counter = 0;
11030 player->post_delay_counter = 0;
11032 player->dir_waiting = player->MovDir;
11033 player->action_waiting = ACTION_DEFAULT;
11035 player->special_action_bored = ACTION_DEFAULT;
11036 player->special_action_sleeping = ACTION_DEFAULT;
11040 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11042 if ((!player->is_moving && player->was_moving) ||
11043 (player->MovPos == 0 && player->was_moving) ||
11044 (player->is_snapping && !player->was_snapping) ||
11045 (player->is_dropping && !player->was_dropping))
11047 if (!CheckSaveEngineSnapshotToList())
11050 player->was_moving = FALSE;
11051 player->was_snapping = TRUE;
11052 player->was_dropping = TRUE;
11056 if (player->is_moving)
11057 player->was_moving = TRUE;
11059 if (!player->is_snapping)
11060 player->was_snapping = FALSE;
11062 if (!player->is_dropping)
11063 player->was_dropping = FALSE;
11067 static void CheckSingleStepMode(struct PlayerInfo *player)
11069 if (tape.single_step && tape.recording && !tape.pausing)
11071 /* as it is called "single step mode", just return to pause mode when the
11072 player stopped moving after one tile (or never starts moving at all) */
11073 if (!player->is_moving &&
11074 !player->is_pushing &&
11075 !player->is_dropping_pressed)
11076 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11079 CheckSaveEngineSnapshot(player);
11082 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11084 int left = player_action & JOY_LEFT;
11085 int right = player_action & JOY_RIGHT;
11086 int up = player_action & JOY_UP;
11087 int down = player_action & JOY_DOWN;
11088 int button1 = player_action & JOY_BUTTON_1;
11089 int button2 = player_action & JOY_BUTTON_2;
11090 int dx = (left ? -1 : right ? 1 : 0);
11091 int dy = (up ? -1 : down ? 1 : 0);
11093 if (!player->active || tape.pausing)
11099 SnapField(player, dx, dy);
11103 DropElement(player);
11105 MovePlayer(player, dx, dy);
11108 CheckSingleStepMode(player);
11110 SetPlayerWaiting(player, FALSE);
11112 return player_action;
11116 // no actions for this player (no input at player's configured device)
11118 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11119 SnapField(player, 0, 0);
11120 CheckGravityMovementWhenNotMoving(player);
11122 if (player->MovPos == 0)
11123 SetPlayerWaiting(player, TRUE);
11125 if (player->MovPos == 0) // needed for tape.playing
11126 player->is_moving = FALSE;
11128 player->is_dropping = FALSE;
11129 player->is_dropping_pressed = FALSE;
11130 player->drop_pressed_delay = 0;
11132 CheckSingleStepMode(player);
11138 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11141 if (!tape.use_mouse)
11144 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11145 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11146 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11149 static void SetTapeActionFromMouseAction(byte *tape_action,
11150 struct MouseActionInfo *mouse_action)
11152 if (!tape.use_mouse)
11155 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11156 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11157 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11160 static void CheckLevelSolved(void)
11162 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11164 if (game_em.level_solved &&
11165 !game_em.game_over) // game won
11169 game_em.game_over = TRUE;
11171 game.all_players_gone = TRUE;
11174 if (game_em.game_over) // game lost
11175 game.all_players_gone = TRUE;
11177 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11179 if (game_sp.level_solved &&
11180 !game_sp.game_over) // game won
11184 game_sp.game_over = TRUE;
11186 game.all_players_gone = TRUE;
11189 if (game_sp.game_over) // game lost
11190 game.all_players_gone = TRUE;
11192 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11194 if (game_mm.level_solved &&
11195 !game_mm.game_over) // game won
11199 game_mm.game_over = TRUE;
11201 game.all_players_gone = TRUE;
11204 if (game_mm.game_over) // game lost
11205 game.all_players_gone = TRUE;
11209 static void CheckLevelTime(void)
11213 if (TimeFrames >= FRAMES_PER_SECOND)
11218 for (i = 0; i < MAX_PLAYERS; i++)
11220 struct PlayerInfo *player = &stored_player[i];
11222 if (SHIELD_ON(player))
11224 player->shield_normal_time_left--;
11226 if (player->shield_deadly_time_left > 0)
11227 player->shield_deadly_time_left--;
11231 if (!game.LevelSolved && !level.use_step_counter)
11239 if (TimeLeft <= 10 && setup.time_limit)
11240 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11242 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11243 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11245 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11247 if (!TimeLeft && setup.time_limit)
11249 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11250 game_em.lev->killed_out_of_time = TRUE;
11252 for (i = 0; i < MAX_PLAYERS; i++)
11253 KillPlayer(&stored_player[i]);
11256 else if (game.no_time_limit && !game.all_players_gone)
11258 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11261 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11264 if (tape.recording || tape.playing)
11265 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11268 if (tape.recording || tape.playing)
11269 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11271 UpdateAndDisplayGameControlValues();
11274 void AdvanceFrameAndPlayerCounters(int player_nr)
11278 // advance frame counters (global frame counter and time frame counter)
11282 // advance player counters (counters for move delay, move animation etc.)
11283 for (i = 0; i < MAX_PLAYERS; i++)
11285 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11286 int move_delay_value = stored_player[i].move_delay_value;
11287 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11289 if (!advance_player_counters) // not all players may be affected
11292 if (move_frames == 0) // less than one move per game frame
11294 int stepsize = TILEX / move_delay_value;
11295 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11296 int count = (stored_player[i].is_moving ?
11297 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11299 if (count % delay == 0)
11303 stored_player[i].Frame += move_frames;
11305 if (stored_player[i].MovPos != 0)
11306 stored_player[i].StepFrame += move_frames;
11308 if (stored_player[i].move_delay > 0)
11309 stored_player[i].move_delay--;
11311 // due to bugs in previous versions, counter must count up, not down
11312 if (stored_player[i].push_delay != -1)
11313 stored_player[i].push_delay++;
11315 if (stored_player[i].drop_delay > 0)
11316 stored_player[i].drop_delay--;
11318 if (stored_player[i].is_dropping_pressed)
11319 stored_player[i].drop_pressed_delay++;
11323 void StartGameActions(boolean init_network_game, boolean record_tape,
11326 unsigned int new_random_seed = InitRND(random_seed);
11329 TapeStartRecording(new_random_seed);
11331 if (init_network_game)
11333 SendToServer_LevelFile();
11334 SendToServer_StartPlaying();
11342 static void GameActionsExt(void)
11345 static unsigned int game_frame_delay = 0;
11347 unsigned int game_frame_delay_value;
11348 byte *recorded_player_action;
11349 byte summarized_player_action = 0;
11350 byte tape_action[MAX_PLAYERS];
11353 // detect endless loops, caused by custom element programming
11354 if (recursion_loop_detected && recursion_loop_depth == 0)
11356 char *message = getStringCat3("Internal Error! Element ",
11357 EL_NAME(recursion_loop_element),
11358 " caused endless loop! Quit the game?");
11360 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11361 EL_NAME(recursion_loop_element));
11363 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11365 recursion_loop_detected = FALSE; // if game should be continued
11372 if (game.restart_level)
11373 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11375 CheckLevelSolved();
11377 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11380 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11383 if (game_status != GAME_MODE_PLAYING) // status might have changed
11386 game_frame_delay_value =
11387 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11389 if (tape.playing && tape.warp_forward && !tape.pausing)
11390 game_frame_delay_value = 0;
11392 SetVideoFrameDelay(game_frame_delay_value);
11394 // (de)activate virtual buttons depending on current game status
11395 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11397 if (game.all_players_gone) // if no players there to be controlled anymore
11398 SetOverlayActive(FALSE);
11399 else if (!tape.playing) // if game continues after tape stopped playing
11400 SetOverlayActive(TRUE);
11405 // ---------- main game synchronization point ----------
11407 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11409 printf("::: skip == %d\n", skip);
11412 // ---------- main game synchronization point ----------
11414 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11418 if (network_playing && !network_player_action_received)
11420 // try to get network player actions in time
11422 // last chance to get network player actions without main loop delay
11423 HandleNetworking();
11425 // game was quit by network peer
11426 if (game_status != GAME_MODE_PLAYING)
11429 // check if network player actions still missing and game still running
11430 if (!network_player_action_received && !checkGameEnded())
11431 return; // failed to get network player actions in time
11433 // do not yet reset "network_player_action_received" (for tape.pausing)
11439 // at this point we know that we really continue executing the game
11441 network_player_action_received = FALSE;
11443 // when playing tape, read previously recorded player input from tape data
11444 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11446 local_player->effective_mouse_action = local_player->mouse_action;
11448 if (recorded_player_action != NULL)
11449 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11450 recorded_player_action);
11452 // TapePlayAction() may return NULL when toggling to "pause before death"
11456 if (tape.set_centered_player)
11458 game.centered_player_nr_next = tape.centered_player_nr_next;
11459 game.set_centered_player = TRUE;
11462 for (i = 0; i < MAX_PLAYERS; i++)
11464 summarized_player_action |= stored_player[i].action;
11466 if (!network_playing && (game.team_mode || tape.playing))
11467 stored_player[i].effective_action = stored_player[i].action;
11470 if (network_playing && !checkGameEnded())
11471 SendToServer_MovePlayer(summarized_player_action);
11473 // summarize all actions at local players mapped input device position
11474 // (this allows using different input devices in single player mode)
11475 if (!network.enabled && !game.team_mode)
11476 stored_player[map_player_action[local_player->index_nr]].effective_action =
11477 summarized_player_action;
11479 // summarize all actions at centered player in local team mode
11480 if (tape.recording &&
11481 setup.team_mode && !network.enabled &&
11482 setup.input_on_focus &&
11483 game.centered_player_nr != -1)
11485 for (i = 0; i < MAX_PLAYERS; i++)
11486 stored_player[map_player_action[i]].effective_action =
11487 (i == game.centered_player_nr ? summarized_player_action : 0);
11490 if (recorded_player_action != NULL)
11491 for (i = 0; i < MAX_PLAYERS; i++)
11492 stored_player[i].effective_action = recorded_player_action[i];
11494 for (i = 0; i < MAX_PLAYERS; i++)
11496 tape_action[i] = stored_player[i].effective_action;
11498 /* (this may happen in the RND game engine if a player was not present on
11499 the playfield on level start, but appeared later from a custom element */
11500 if (setup.team_mode &&
11503 !tape.player_participates[i])
11504 tape.player_participates[i] = TRUE;
11507 SetTapeActionFromMouseAction(tape_action,
11508 &local_player->effective_mouse_action);
11510 // only record actions from input devices, but not programmed actions
11511 if (tape.recording)
11512 TapeRecordAction(tape_action);
11514 // remember if game was played (especially after tape stopped playing)
11515 if (!tape.playing && summarized_player_action)
11516 game.GamePlayed = TRUE;
11518 #if USE_NEW_PLAYER_ASSIGNMENTS
11519 // !!! also map player actions in single player mode !!!
11520 // if (game.team_mode)
11523 byte mapped_action[MAX_PLAYERS];
11525 #if DEBUG_PLAYER_ACTIONS
11527 for (i = 0; i < MAX_PLAYERS; i++)
11528 printf(" %d, ", stored_player[i].effective_action);
11531 for (i = 0; i < MAX_PLAYERS; i++)
11532 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11534 for (i = 0; i < MAX_PLAYERS; i++)
11535 stored_player[i].effective_action = mapped_action[i];
11537 #if DEBUG_PLAYER_ACTIONS
11539 for (i = 0; i < MAX_PLAYERS; i++)
11540 printf(" %d, ", stored_player[i].effective_action);
11544 #if DEBUG_PLAYER_ACTIONS
11548 for (i = 0; i < MAX_PLAYERS; i++)
11549 printf(" %d, ", stored_player[i].effective_action);
11555 for (i = 0; i < MAX_PLAYERS; i++)
11557 // allow engine snapshot in case of changed movement attempt
11558 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11559 (stored_player[i].effective_action & KEY_MOTION))
11560 game.snapshot.changed_action = TRUE;
11562 // allow engine snapshot in case of snapping/dropping attempt
11563 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11564 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11565 game.snapshot.changed_action = TRUE;
11567 game.snapshot.last_action[i] = stored_player[i].effective_action;
11570 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11572 GameActions_EM_Main();
11574 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11576 GameActions_SP_Main();
11578 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11580 GameActions_MM_Main();
11584 GameActions_RND_Main();
11587 BlitScreenToBitmap(backbuffer);
11589 CheckLevelSolved();
11592 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11594 if (global.show_frames_per_second)
11596 static unsigned int fps_counter = 0;
11597 static int fps_frames = 0;
11598 unsigned int fps_delay_ms = Counter() - fps_counter;
11602 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11604 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11607 fps_counter = Counter();
11609 // always draw FPS to screen after FPS value was updated
11610 redraw_mask |= REDRAW_FPS;
11613 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11614 if (GetDrawDeactivationMask() == REDRAW_NONE)
11615 redraw_mask |= REDRAW_FPS;
11619 static void GameActions_CheckSaveEngineSnapshot(void)
11621 if (!game.snapshot.save_snapshot)
11624 // clear flag for saving snapshot _before_ saving snapshot
11625 game.snapshot.save_snapshot = FALSE;
11627 SaveEngineSnapshotToList();
11630 void GameActions(void)
11634 GameActions_CheckSaveEngineSnapshot();
11637 void GameActions_EM_Main(void)
11639 byte effective_action[MAX_PLAYERS];
11640 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11643 for (i = 0; i < MAX_PLAYERS; i++)
11644 effective_action[i] = stored_player[i].effective_action;
11646 GameActions_EM(effective_action, warp_mode);
11649 void GameActions_SP_Main(void)
11651 byte effective_action[MAX_PLAYERS];
11652 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11655 for (i = 0; i < MAX_PLAYERS; i++)
11656 effective_action[i] = stored_player[i].effective_action;
11658 GameActions_SP(effective_action, warp_mode);
11660 for (i = 0; i < MAX_PLAYERS; i++)
11662 if (stored_player[i].force_dropping)
11663 stored_player[i].action |= KEY_BUTTON_DROP;
11665 stored_player[i].force_dropping = FALSE;
11669 void GameActions_MM_Main(void)
11671 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11673 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11676 void GameActions_RND_Main(void)
11681 void GameActions_RND(void)
11683 int magic_wall_x = 0, magic_wall_y = 0;
11684 int i, x, y, element, graphic, last_gfx_frame;
11686 InitPlayfieldScanModeVars();
11688 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11690 SCAN_PLAYFIELD(x, y)
11692 ChangeCount[x][y] = 0;
11693 ChangeEvent[x][y] = -1;
11697 if (game.set_centered_player)
11699 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11701 // switching to "all players" only possible if all players fit to screen
11702 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11704 game.centered_player_nr_next = game.centered_player_nr;
11705 game.set_centered_player = FALSE;
11708 // do not switch focus to non-existing (or non-active) player
11709 if (game.centered_player_nr_next >= 0 &&
11710 !stored_player[game.centered_player_nr_next].active)
11712 game.centered_player_nr_next = game.centered_player_nr;
11713 game.set_centered_player = FALSE;
11717 if (game.set_centered_player &&
11718 ScreenMovPos == 0) // screen currently aligned at tile position
11722 if (game.centered_player_nr_next == -1)
11724 setScreenCenteredToAllPlayers(&sx, &sy);
11728 sx = stored_player[game.centered_player_nr_next].jx;
11729 sy = stored_player[game.centered_player_nr_next].jy;
11732 game.centered_player_nr = game.centered_player_nr_next;
11733 game.set_centered_player = FALSE;
11735 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11736 DrawGameDoorValues();
11739 for (i = 0; i < MAX_PLAYERS; i++)
11741 int actual_player_action = stored_player[i].effective_action;
11744 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11745 - rnd_equinox_tetrachloride 048
11746 - rnd_equinox_tetrachloride_ii 096
11747 - rnd_emanuel_schmieg 002
11748 - doctor_sloan_ww 001, 020
11750 if (stored_player[i].MovPos == 0)
11751 CheckGravityMovement(&stored_player[i]);
11754 // overwrite programmed action with tape action
11755 if (stored_player[i].programmed_action)
11756 actual_player_action = stored_player[i].programmed_action;
11758 PlayerActions(&stored_player[i], actual_player_action);
11760 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11763 ScrollScreen(NULL, SCROLL_GO_ON);
11765 /* for backwards compatibility, the following code emulates a fixed bug that
11766 occured when pushing elements (causing elements that just made their last
11767 pushing step to already (if possible) make their first falling step in the
11768 same game frame, which is bad); this code is also needed to use the famous
11769 "spring push bug" which is used in older levels and might be wanted to be
11770 used also in newer levels, but in this case the buggy pushing code is only
11771 affecting the "spring" element and no other elements */
11773 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11775 for (i = 0; i < MAX_PLAYERS; i++)
11777 struct PlayerInfo *player = &stored_player[i];
11778 int x = player->jx;
11779 int y = player->jy;
11781 if (player->active && player->is_pushing && player->is_moving &&
11783 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11784 Feld[x][y] == EL_SPRING))
11786 ContinueMoving(x, y);
11788 // continue moving after pushing (this is actually a bug)
11789 if (!IS_MOVING(x, y))
11790 Stop[x][y] = FALSE;
11795 SCAN_PLAYFIELD(x, y)
11797 Last[x][y] = Feld[x][y];
11799 ChangeCount[x][y] = 0;
11800 ChangeEvent[x][y] = -1;
11802 // this must be handled before main playfield loop
11803 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11806 if (MovDelay[x][y] <= 0)
11810 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11813 if (MovDelay[x][y] <= 0)
11816 TEST_DrawLevelField(x, y);
11818 TestIfElementTouchesCustomElement(x, y); // for empty space
11823 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11825 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11826 printf("GameActions(): This should never happen!\n");
11828 ChangePage[x][y] = -1;
11832 Stop[x][y] = FALSE;
11833 if (WasJustMoving[x][y] > 0)
11834 WasJustMoving[x][y]--;
11835 if (WasJustFalling[x][y] > 0)
11836 WasJustFalling[x][y]--;
11837 if (CheckCollision[x][y] > 0)
11838 CheckCollision[x][y]--;
11839 if (CheckImpact[x][y] > 0)
11840 CheckImpact[x][y]--;
11844 /* reset finished pushing action (not done in ContinueMoving() to allow
11845 continuous pushing animation for elements with zero push delay) */
11846 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11848 ResetGfxAnimation(x, y);
11849 TEST_DrawLevelField(x, y);
11853 if (IS_BLOCKED(x, y))
11857 Blocked2Moving(x, y, &oldx, &oldy);
11858 if (!IS_MOVING(oldx, oldy))
11860 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11861 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11862 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11863 printf("GameActions(): This should never happen!\n");
11869 SCAN_PLAYFIELD(x, y)
11871 element = Feld[x][y];
11872 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11873 last_gfx_frame = GfxFrame[x][y];
11875 ResetGfxFrame(x, y);
11877 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
11878 DrawLevelGraphicAnimation(x, y, graphic);
11880 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
11881 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
11882 ResetRandomAnimationValue(x, y);
11884 SetRandomAnimationValue(x, y);
11886 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
11888 if (IS_INACTIVE(element))
11890 if (IS_ANIMATED(graphic))
11891 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11896 // this may take place after moving, so 'element' may have changed
11897 if (IS_CHANGING(x, y) &&
11898 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
11900 int page = element_info[element].event_page_nr[CE_DELAY];
11902 HandleElementChange(x, y, page);
11904 element = Feld[x][y];
11905 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11908 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
11912 element = Feld[x][y];
11913 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11915 if (IS_ANIMATED(graphic) &&
11916 !IS_MOVING(x, y) &&
11918 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11920 if (IS_GEM(element) || element == EL_SP_INFOTRON)
11921 TEST_DrawTwinkleOnField(x, y);
11923 else if (element == EL_ACID)
11926 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11928 else if ((element == EL_EXIT_OPEN ||
11929 element == EL_EM_EXIT_OPEN ||
11930 element == EL_SP_EXIT_OPEN ||
11931 element == EL_STEEL_EXIT_OPEN ||
11932 element == EL_EM_STEEL_EXIT_OPEN ||
11933 element == EL_SP_TERMINAL ||
11934 element == EL_SP_TERMINAL_ACTIVE ||
11935 element == EL_EXTRA_TIME ||
11936 element == EL_SHIELD_NORMAL ||
11937 element == EL_SHIELD_DEADLY) &&
11938 IS_ANIMATED(graphic))
11939 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11940 else if (IS_MOVING(x, y))
11941 ContinueMoving(x, y);
11942 else if (IS_ACTIVE_BOMB(element))
11943 CheckDynamite(x, y);
11944 else if (element == EL_AMOEBA_GROWING)
11945 AmoebeWaechst(x, y);
11946 else if (element == EL_AMOEBA_SHRINKING)
11947 AmoebaDisappearing(x, y);
11949 #if !USE_NEW_AMOEBA_CODE
11950 else if (IS_AMOEBALIVE(element))
11951 AmoebeAbleger(x, y);
11954 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
11956 else if (element == EL_EXIT_CLOSED)
11958 else if (element == EL_EM_EXIT_CLOSED)
11960 else if (element == EL_STEEL_EXIT_CLOSED)
11961 CheckExitSteel(x, y);
11962 else if (element == EL_EM_STEEL_EXIT_CLOSED)
11963 CheckExitSteelEM(x, y);
11964 else if (element == EL_SP_EXIT_CLOSED)
11966 else if (element == EL_EXPANDABLE_WALL_GROWING ||
11967 element == EL_EXPANDABLE_STEELWALL_GROWING)
11968 MauerWaechst(x, y);
11969 else if (element == EL_EXPANDABLE_WALL ||
11970 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
11971 element == EL_EXPANDABLE_WALL_VERTICAL ||
11972 element == EL_EXPANDABLE_WALL_ANY ||
11973 element == EL_BD_EXPANDABLE_WALL)
11974 MauerAbleger(x, y);
11975 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
11976 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
11977 element == EL_EXPANDABLE_STEELWALL_ANY)
11978 MauerAblegerStahl(x, y);
11979 else if (element == EL_FLAMES)
11980 CheckForDragon(x, y);
11981 else if (element == EL_EXPLOSION)
11982 ; // drawing of correct explosion animation is handled separately
11983 else if (element == EL_ELEMENT_SNAPPING ||
11984 element == EL_DIAGONAL_SHRINKING ||
11985 element == EL_DIAGONAL_GROWING)
11987 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
11989 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11991 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
11992 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11994 if (IS_BELT_ACTIVE(element))
11995 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
11997 if (game.magic_wall_active)
11999 int jx = local_player->jx, jy = local_player->jy;
12001 // play the element sound at the position nearest to the player
12002 if ((element == EL_MAGIC_WALL_FULL ||
12003 element == EL_MAGIC_WALL_ACTIVE ||
12004 element == EL_MAGIC_WALL_EMPTYING ||
12005 element == EL_BD_MAGIC_WALL_FULL ||
12006 element == EL_BD_MAGIC_WALL_ACTIVE ||
12007 element == EL_BD_MAGIC_WALL_EMPTYING ||
12008 element == EL_DC_MAGIC_WALL_FULL ||
12009 element == EL_DC_MAGIC_WALL_ACTIVE ||
12010 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12011 ABS(x - jx) + ABS(y - jy) <
12012 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12020 #if USE_NEW_AMOEBA_CODE
12021 // new experimental amoeba growth stuff
12022 if (!(FrameCounter % 8))
12024 static unsigned int random = 1684108901;
12026 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12028 x = RND(lev_fieldx);
12029 y = RND(lev_fieldy);
12030 element = Feld[x][y];
12032 if (!IS_PLAYER(x,y) &&
12033 (element == EL_EMPTY ||
12034 CAN_GROW_INTO(element) ||
12035 element == EL_QUICKSAND_EMPTY ||
12036 element == EL_QUICKSAND_FAST_EMPTY ||
12037 element == EL_ACID_SPLASH_LEFT ||
12038 element == EL_ACID_SPLASH_RIGHT))
12040 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12041 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12042 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12043 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12044 Feld[x][y] = EL_AMOEBA_DROP;
12047 random = random * 129 + 1;
12052 game.explosions_delayed = FALSE;
12054 SCAN_PLAYFIELD(x, y)
12056 element = Feld[x][y];
12058 if (ExplodeField[x][y])
12059 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12060 else if (element == EL_EXPLOSION)
12061 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12063 ExplodeField[x][y] = EX_TYPE_NONE;
12066 game.explosions_delayed = TRUE;
12068 if (game.magic_wall_active)
12070 if (!(game.magic_wall_time_left % 4))
12072 int element = Feld[magic_wall_x][magic_wall_y];
12074 if (element == EL_BD_MAGIC_WALL_FULL ||
12075 element == EL_BD_MAGIC_WALL_ACTIVE ||
12076 element == EL_BD_MAGIC_WALL_EMPTYING)
12077 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12078 else if (element == EL_DC_MAGIC_WALL_FULL ||
12079 element == EL_DC_MAGIC_WALL_ACTIVE ||
12080 element == EL_DC_MAGIC_WALL_EMPTYING)
12081 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12083 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12086 if (game.magic_wall_time_left > 0)
12088 game.magic_wall_time_left--;
12090 if (!game.magic_wall_time_left)
12092 SCAN_PLAYFIELD(x, y)
12094 element = Feld[x][y];
12096 if (element == EL_MAGIC_WALL_ACTIVE ||
12097 element == EL_MAGIC_WALL_FULL)
12099 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12100 TEST_DrawLevelField(x, y);
12102 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12103 element == EL_BD_MAGIC_WALL_FULL)
12105 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12106 TEST_DrawLevelField(x, y);
12108 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12109 element == EL_DC_MAGIC_WALL_FULL)
12111 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12112 TEST_DrawLevelField(x, y);
12116 game.magic_wall_active = FALSE;
12121 if (game.light_time_left > 0)
12123 game.light_time_left--;
12125 if (game.light_time_left == 0)
12126 RedrawAllLightSwitchesAndInvisibleElements();
12129 if (game.timegate_time_left > 0)
12131 game.timegate_time_left--;
12133 if (game.timegate_time_left == 0)
12134 CloseAllOpenTimegates();
12137 if (game.lenses_time_left > 0)
12139 game.lenses_time_left--;
12141 if (game.lenses_time_left == 0)
12142 RedrawAllInvisibleElementsForLenses();
12145 if (game.magnify_time_left > 0)
12147 game.magnify_time_left--;
12149 if (game.magnify_time_left == 0)
12150 RedrawAllInvisibleElementsForMagnifier();
12153 for (i = 0; i < MAX_PLAYERS; i++)
12155 struct PlayerInfo *player = &stored_player[i];
12157 if (SHIELD_ON(player))
12159 if (player->shield_deadly_time_left)
12160 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12161 else if (player->shield_normal_time_left)
12162 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12166 #if USE_DELAYED_GFX_REDRAW
12167 SCAN_PLAYFIELD(x, y)
12169 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12171 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12172 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12174 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12175 DrawLevelField(x, y);
12177 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12178 DrawLevelFieldCrumbled(x, y);
12180 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12181 DrawLevelFieldCrumbledNeighbours(x, y);
12183 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12184 DrawTwinkleOnField(x, y);
12187 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12192 PlayAllPlayersSound();
12194 for (i = 0; i < MAX_PLAYERS; i++)
12196 struct PlayerInfo *player = &stored_player[i];
12198 if (player->show_envelope != 0 && (!player->active ||
12199 player->MovPos == 0))
12201 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12203 player->show_envelope = 0;
12207 // use random number generator in every frame to make it less predictable
12208 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12212 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12214 int min_x = x, min_y = y, max_x = x, max_y = y;
12217 for (i = 0; i < MAX_PLAYERS; i++)
12219 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12221 if (!stored_player[i].active || &stored_player[i] == player)
12224 min_x = MIN(min_x, jx);
12225 min_y = MIN(min_y, jy);
12226 max_x = MAX(max_x, jx);
12227 max_y = MAX(max_y, jy);
12230 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12233 static boolean AllPlayersInVisibleScreen(void)
12237 for (i = 0; i < MAX_PLAYERS; i++)
12239 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12241 if (!stored_player[i].active)
12244 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12251 void ScrollLevel(int dx, int dy)
12253 int scroll_offset = 2 * TILEX_VAR;
12256 BlitBitmap(drawto_field, drawto_field,
12257 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12258 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12259 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12260 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12261 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12262 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12266 x = (dx == 1 ? BX1 : BX2);
12267 for (y = BY1; y <= BY2; y++)
12268 DrawScreenField(x, y);
12273 y = (dy == 1 ? BY1 : BY2);
12274 for (x = BX1; x <= BX2; x++)
12275 DrawScreenField(x, y);
12278 redraw_mask |= REDRAW_FIELD;
12281 static boolean canFallDown(struct PlayerInfo *player)
12283 int jx = player->jx, jy = player->jy;
12285 return (IN_LEV_FIELD(jx, jy + 1) &&
12286 (IS_FREE(jx, jy + 1) ||
12287 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12288 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12289 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12292 static boolean canPassField(int x, int y, int move_dir)
12294 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12295 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12296 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12297 int nextx = x + dx;
12298 int nexty = y + dy;
12299 int element = Feld[x][y];
12301 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12302 !CAN_MOVE(element) &&
12303 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12304 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12305 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12308 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12310 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12311 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12312 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12316 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12317 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12318 (IS_DIGGABLE(Feld[newx][newy]) ||
12319 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12320 canPassField(newx, newy, move_dir)));
12323 static void CheckGravityMovement(struct PlayerInfo *player)
12325 if (player->gravity && !player->programmed_action)
12327 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12328 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12329 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12330 int jx = player->jx, jy = player->jy;
12331 boolean player_is_moving_to_valid_field =
12332 (!player_is_snapping &&
12333 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12334 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12335 boolean player_can_fall_down = canFallDown(player);
12337 if (player_can_fall_down &&
12338 !player_is_moving_to_valid_field)
12339 player->programmed_action = MV_DOWN;
12343 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12345 return CheckGravityMovement(player);
12347 if (player->gravity && !player->programmed_action)
12349 int jx = player->jx, jy = player->jy;
12350 boolean field_under_player_is_free =
12351 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12352 boolean player_is_standing_on_valid_field =
12353 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12354 (IS_WALKABLE(Feld[jx][jy]) &&
12355 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12357 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12358 player->programmed_action = MV_DOWN;
12363 MovePlayerOneStep()
12364 -----------------------------------------------------------------------------
12365 dx, dy: direction (non-diagonal) to try to move the player to
12366 real_dx, real_dy: direction as read from input device (can be diagonal)
12369 boolean MovePlayerOneStep(struct PlayerInfo *player,
12370 int dx, int dy, int real_dx, int real_dy)
12372 int jx = player->jx, jy = player->jy;
12373 int new_jx = jx + dx, new_jy = jy + dy;
12375 boolean player_can_move = !player->cannot_move;
12377 if (!player->active || (!dx && !dy))
12378 return MP_NO_ACTION;
12380 player->MovDir = (dx < 0 ? MV_LEFT :
12381 dx > 0 ? MV_RIGHT :
12383 dy > 0 ? MV_DOWN : MV_NONE);
12385 if (!IN_LEV_FIELD(new_jx, new_jy))
12386 return MP_NO_ACTION;
12388 if (!player_can_move)
12390 if (player->MovPos == 0)
12392 player->is_moving = FALSE;
12393 player->is_digging = FALSE;
12394 player->is_collecting = FALSE;
12395 player->is_snapping = FALSE;
12396 player->is_pushing = FALSE;
12400 if (!network.enabled && game.centered_player_nr == -1 &&
12401 !AllPlayersInSight(player, new_jx, new_jy))
12402 return MP_NO_ACTION;
12404 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12405 if (can_move != MP_MOVING)
12408 // check if DigField() has caused relocation of the player
12409 if (player->jx != jx || player->jy != jy)
12410 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12412 StorePlayer[jx][jy] = 0;
12413 player->last_jx = jx;
12414 player->last_jy = jy;
12415 player->jx = new_jx;
12416 player->jy = new_jy;
12417 StorePlayer[new_jx][new_jy] = player->element_nr;
12419 if (player->move_delay_value_next != -1)
12421 player->move_delay_value = player->move_delay_value_next;
12422 player->move_delay_value_next = -1;
12426 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12428 player->step_counter++;
12430 PlayerVisit[jx][jy] = FrameCounter;
12432 player->is_moving = TRUE;
12435 // should better be called in MovePlayer(), but this breaks some tapes
12436 ScrollPlayer(player, SCROLL_INIT);
12442 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12444 int jx = player->jx, jy = player->jy;
12445 int old_jx = jx, old_jy = jy;
12446 int moved = MP_NO_ACTION;
12448 if (!player->active)
12453 if (player->MovPos == 0)
12455 player->is_moving = FALSE;
12456 player->is_digging = FALSE;
12457 player->is_collecting = FALSE;
12458 player->is_snapping = FALSE;
12459 player->is_pushing = FALSE;
12465 if (player->move_delay > 0)
12468 player->move_delay = -1; // set to "uninitialized" value
12470 // store if player is automatically moved to next field
12471 player->is_auto_moving = (player->programmed_action != MV_NONE);
12473 // remove the last programmed player action
12474 player->programmed_action = 0;
12476 if (player->MovPos)
12478 // should only happen if pre-1.2 tape recordings are played
12479 // this is only for backward compatibility
12481 int original_move_delay_value = player->move_delay_value;
12484 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12488 // scroll remaining steps with finest movement resolution
12489 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12491 while (player->MovPos)
12493 ScrollPlayer(player, SCROLL_GO_ON);
12494 ScrollScreen(NULL, SCROLL_GO_ON);
12496 AdvanceFrameAndPlayerCounters(player->index_nr);
12499 BackToFront_WithFrameDelay(0);
12502 player->move_delay_value = original_move_delay_value;
12505 player->is_active = FALSE;
12507 if (player->last_move_dir & MV_HORIZONTAL)
12509 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12510 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12514 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12515 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12518 if (!moved && !player->is_active)
12520 player->is_moving = FALSE;
12521 player->is_digging = FALSE;
12522 player->is_collecting = FALSE;
12523 player->is_snapping = FALSE;
12524 player->is_pushing = FALSE;
12530 if (moved & MP_MOVING && !ScreenMovPos &&
12531 (player->index_nr == game.centered_player_nr ||
12532 game.centered_player_nr == -1))
12534 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12536 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12538 // actual player has left the screen -- scroll in that direction
12539 if (jx != old_jx) // player has moved horizontally
12540 scroll_x += (jx - old_jx);
12541 else // player has moved vertically
12542 scroll_y += (jy - old_jy);
12546 int offset_raw = game.scroll_delay_value;
12548 if (jx != old_jx) // player has moved horizontally
12550 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12551 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12552 int new_scroll_x = jx - MIDPOSX + offset_x;
12554 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12555 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12556 scroll_x = new_scroll_x;
12558 // don't scroll over playfield boundaries
12559 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12561 // don't scroll more than one field at a time
12562 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12564 // don't scroll against the player's moving direction
12565 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12566 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12567 scroll_x = old_scroll_x;
12569 else // player has moved vertically
12571 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12572 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12573 int new_scroll_y = jy - MIDPOSY + offset_y;
12575 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12576 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12577 scroll_y = new_scroll_y;
12579 // don't scroll over playfield boundaries
12580 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12582 // don't scroll more than one field at a time
12583 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12585 // don't scroll against the player's moving direction
12586 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12587 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12588 scroll_y = old_scroll_y;
12592 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12594 if (!network.enabled && game.centered_player_nr == -1 &&
12595 !AllPlayersInVisibleScreen())
12597 scroll_x = old_scroll_x;
12598 scroll_y = old_scroll_y;
12602 ScrollScreen(player, SCROLL_INIT);
12603 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12608 player->StepFrame = 0;
12610 if (moved & MP_MOVING)
12612 if (old_jx != jx && old_jy == jy)
12613 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12614 else if (old_jx == jx && old_jy != jy)
12615 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12617 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12619 player->last_move_dir = player->MovDir;
12620 player->is_moving = TRUE;
12621 player->is_snapping = FALSE;
12622 player->is_switching = FALSE;
12623 player->is_dropping = FALSE;
12624 player->is_dropping_pressed = FALSE;
12625 player->drop_pressed_delay = 0;
12628 // should better be called here than above, but this breaks some tapes
12629 ScrollPlayer(player, SCROLL_INIT);
12634 CheckGravityMovementWhenNotMoving(player);
12636 player->is_moving = FALSE;
12638 /* at this point, the player is allowed to move, but cannot move right now
12639 (e.g. because of something blocking the way) -- ensure that the player
12640 is also allowed to move in the next frame (in old versions before 3.1.1,
12641 the player was forced to wait again for eight frames before next try) */
12643 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12644 player->move_delay = 0; // allow direct movement in the next frame
12647 if (player->move_delay == -1) // not yet initialized by DigField()
12648 player->move_delay = player->move_delay_value;
12650 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12652 TestIfPlayerTouchesBadThing(jx, jy);
12653 TestIfPlayerTouchesCustomElement(jx, jy);
12656 if (!player->active)
12657 RemovePlayer(player);
12662 void ScrollPlayer(struct PlayerInfo *player, int mode)
12664 int jx = player->jx, jy = player->jy;
12665 int last_jx = player->last_jx, last_jy = player->last_jy;
12666 int move_stepsize = TILEX / player->move_delay_value;
12668 if (!player->active)
12671 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12674 if (mode == SCROLL_INIT)
12676 player->actual_frame_counter = FrameCounter;
12677 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12679 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12680 Feld[last_jx][last_jy] == EL_EMPTY)
12682 int last_field_block_delay = 0; // start with no blocking at all
12683 int block_delay_adjustment = player->block_delay_adjustment;
12685 // if player blocks last field, add delay for exactly one move
12686 if (player->block_last_field)
12688 last_field_block_delay += player->move_delay_value;
12690 // when blocking enabled, prevent moving up despite gravity
12691 if (player->gravity && player->MovDir == MV_UP)
12692 block_delay_adjustment = -1;
12695 // add block delay adjustment (also possible when not blocking)
12696 last_field_block_delay += block_delay_adjustment;
12698 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12699 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12702 if (player->MovPos != 0) // player has not yet reached destination
12705 else if (!FrameReached(&player->actual_frame_counter, 1))
12708 if (player->MovPos != 0)
12710 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12711 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12713 // before DrawPlayer() to draw correct player graphic for this case
12714 if (player->MovPos == 0)
12715 CheckGravityMovement(player);
12718 if (player->MovPos == 0) // player reached destination field
12720 if (player->move_delay_reset_counter > 0)
12722 player->move_delay_reset_counter--;
12724 if (player->move_delay_reset_counter == 0)
12726 // continue with normal speed after quickly moving through gate
12727 HALVE_PLAYER_SPEED(player);
12729 // be able to make the next move without delay
12730 player->move_delay = 0;
12734 player->last_jx = jx;
12735 player->last_jy = jy;
12737 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12738 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12739 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12740 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12741 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12742 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12743 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12744 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12746 ExitPlayer(player);
12748 if (game.players_still_needed == 0 &&
12749 (game.friends_still_needed == 0 ||
12750 IS_SP_ELEMENT(Feld[jx][jy])))
12754 // this breaks one level: "machine", level 000
12756 int move_direction = player->MovDir;
12757 int enter_side = MV_DIR_OPPOSITE(move_direction);
12758 int leave_side = move_direction;
12759 int old_jx = last_jx;
12760 int old_jy = last_jy;
12761 int old_element = Feld[old_jx][old_jy];
12762 int new_element = Feld[jx][jy];
12764 if (IS_CUSTOM_ELEMENT(old_element))
12765 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12767 player->index_bit, leave_side);
12769 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12770 CE_PLAYER_LEAVES_X,
12771 player->index_bit, leave_side);
12773 if (IS_CUSTOM_ELEMENT(new_element))
12774 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12775 player->index_bit, enter_side);
12777 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12778 CE_PLAYER_ENTERS_X,
12779 player->index_bit, enter_side);
12781 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12782 CE_MOVE_OF_X, move_direction);
12785 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12787 TestIfPlayerTouchesBadThing(jx, jy);
12788 TestIfPlayerTouchesCustomElement(jx, jy);
12790 /* needed because pushed element has not yet reached its destination,
12791 so it would trigger a change event at its previous field location */
12792 if (!player->is_pushing)
12793 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12795 if (!player->active)
12796 RemovePlayer(player);
12799 if (!game.LevelSolved && level.use_step_counter)
12809 if (TimeLeft <= 10 && setup.time_limit)
12810 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12812 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12814 DisplayGameControlValues();
12816 if (!TimeLeft && setup.time_limit)
12817 for (i = 0; i < MAX_PLAYERS; i++)
12818 KillPlayer(&stored_player[i]);
12820 else if (game.no_time_limit && !game.all_players_gone)
12822 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12824 DisplayGameControlValues();
12828 if (tape.single_step && tape.recording && !tape.pausing &&
12829 !player->programmed_action)
12830 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12832 if (!player->programmed_action)
12833 CheckSaveEngineSnapshot(player);
12837 void ScrollScreen(struct PlayerInfo *player, int mode)
12839 static unsigned int screen_frame_counter = 0;
12841 if (mode == SCROLL_INIT)
12843 // set scrolling step size according to actual player's moving speed
12844 ScrollStepSize = TILEX / player->move_delay_value;
12846 screen_frame_counter = FrameCounter;
12847 ScreenMovDir = player->MovDir;
12848 ScreenMovPos = player->MovPos;
12849 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12852 else if (!FrameReached(&screen_frame_counter, 1))
12857 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
12858 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12859 redraw_mask |= REDRAW_FIELD;
12862 ScreenMovDir = MV_NONE;
12865 void TestIfPlayerTouchesCustomElement(int x, int y)
12867 static int xy[4][2] =
12874 static int trigger_sides[4][2] =
12876 // center side border side
12877 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12878 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12879 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12880 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12882 static int touch_dir[4] =
12884 MV_LEFT | MV_RIGHT,
12889 int center_element = Feld[x][y]; // should always be non-moving!
12892 for (i = 0; i < NUM_DIRECTIONS; i++)
12894 int xx = x + xy[i][0];
12895 int yy = y + xy[i][1];
12896 int center_side = trigger_sides[i][0];
12897 int border_side = trigger_sides[i][1];
12898 int border_element;
12900 if (!IN_LEV_FIELD(xx, yy))
12903 if (IS_PLAYER(x, y)) // player found at center element
12905 struct PlayerInfo *player = PLAYERINFO(x, y);
12907 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12908 border_element = Feld[xx][yy]; // may be moving!
12909 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12910 border_element = Feld[xx][yy];
12911 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
12912 border_element = MovingOrBlocked2Element(xx, yy);
12914 continue; // center and border element do not touch
12916 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
12917 player->index_bit, border_side);
12918 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
12919 CE_PLAYER_TOUCHES_X,
12920 player->index_bit, border_side);
12923 /* use player element that is initially defined in the level playfield,
12924 not the player element that corresponds to the runtime player number
12925 (example: a level that contains EL_PLAYER_3 as the only player would
12926 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12927 int player_element = PLAYERINFO(x, y)->initial_element;
12929 CheckElementChangeBySide(xx, yy, border_element, player_element,
12930 CE_TOUCHING_X, border_side);
12933 else if (IS_PLAYER(xx, yy)) // player found at border element
12935 struct PlayerInfo *player = PLAYERINFO(xx, yy);
12937 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12939 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
12940 continue; // center and border element do not touch
12943 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
12944 player->index_bit, center_side);
12945 CheckTriggeredElementChangeByPlayer(x, y, center_element,
12946 CE_PLAYER_TOUCHES_X,
12947 player->index_bit, center_side);
12950 /* use player element that is initially defined in the level playfield,
12951 not the player element that corresponds to the runtime player number
12952 (example: a level that contains EL_PLAYER_3 as the only player would
12953 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12954 int player_element = PLAYERINFO(xx, yy)->initial_element;
12956 CheckElementChangeBySide(x, y, center_element, player_element,
12957 CE_TOUCHING_X, center_side);
12965 void TestIfElementTouchesCustomElement(int x, int y)
12967 static int xy[4][2] =
12974 static int trigger_sides[4][2] =
12976 // center side border side
12977 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12978 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12979 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12980 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12982 static int touch_dir[4] =
12984 MV_LEFT | MV_RIGHT,
12989 boolean change_center_element = FALSE;
12990 int center_element = Feld[x][y]; // should always be non-moving!
12991 int border_element_old[NUM_DIRECTIONS];
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_element;
13000 border_element_old[i] = -1;
13002 if (!IN_LEV_FIELD(xx, yy))
13005 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13006 border_element = Feld[xx][yy]; // may be moving!
13007 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13008 border_element = Feld[xx][yy];
13009 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13010 border_element = MovingOrBlocked2Element(xx, yy);
13012 continue; // center and border element do not touch
13014 border_element_old[i] = border_element;
13017 for (i = 0; i < NUM_DIRECTIONS; i++)
13019 int xx = x + xy[i][0];
13020 int yy = y + xy[i][1];
13021 int center_side = trigger_sides[i][0];
13022 int border_element = border_element_old[i];
13024 if (border_element == -1)
13027 // check for change of border element
13028 CheckElementChangeBySide(xx, yy, border_element, center_element,
13029 CE_TOUCHING_X, center_side);
13031 // (center element cannot be player, so we dont have to check this here)
13034 for (i = 0; i < NUM_DIRECTIONS; i++)
13036 int xx = x + xy[i][0];
13037 int yy = y + xy[i][1];
13038 int border_side = trigger_sides[i][1];
13039 int border_element = border_element_old[i];
13041 if (border_element == -1)
13044 // check for change of center element (but change it only once)
13045 if (!change_center_element)
13046 change_center_element =
13047 CheckElementChangeBySide(x, y, center_element, border_element,
13048 CE_TOUCHING_X, border_side);
13050 if (IS_PLAYER(xx, yy))
13052 /* use player element that is initially defined in the level playfield,
13053 not the player element that corresponds to the runtime player number
13054 (example: a level that contains EL_PLAYER_3 as the only player would
13055 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13056 int player_element = PLAYERINFO(xx, yy)->initial_element;
13058 CheckElementChangeBySide(x, y, center_element, player_element,
13059 CE_TOUCHING_X, border_side);
13064 void TestIfElementHitsCustomElement(int x, int y, int direction)
13066 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13067 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13068 int hitx = x + dx, hity = y + dy;
13069 int hitting_element = Feld[x][y];
13070 int touched_element;
13072 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13075 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13076 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13078 if (IN_LEV_FIELD(hitx, hity))
13080 int opposite_direction = MV_DIR_OPPOSITE(direction);
13081 int hitting_side = direction;
13082 int touched_side = opposite_direction;
13083 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13084 MovDir[hitx][hity] != direction ||
13085 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13091 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13092 CE_HITTING_X, touched_side);
13094 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13095 CE_HIT_BY_X, hitting_side);
13097 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13098 CE_HIT_BY_SOMETHING, opposite_direction);
13100 if (IS_PLAYER(hitx, hity))
13102 /* use player element that is initially defined in the level playfield,
13103 not the player element that corresponds to the runtime player number
13104 (example: a level that contains EL_PLAYER_3 as the only player would
13105 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13106 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13108 CheckElementChangeBySide(x, y, hitting_element, player_element,
13109 CE_HITTING_X, touched_side);
13114 // "hitting something" is also true when hitting the playfield border
13115 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13116 CE_HITTING_SOMETHING, direction);
13119 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13121 int i, kill_x = -1, kill_y = -1;
13123 int bad_element = -1;
13124 static int test_xy[4][2] =
13131 static int test_dir[4] =
13139 for (i = 0; i < NUM_DIRECTIONS; i++)
13141 int test_x, test_y, test_move_dir, test_element;
13143 test_x = good_x + test_xy[i][0];
13144 test_y = good_y + test_xy[i][1];
13146 if (!IN_LEV_FIELD(test_x, test_y))
13150 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13152 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13154 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13155 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13157 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13158 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13162 bad_element = test_element;
13168 if (kill_x != -1 || kill_y != -1)
13170 if (IS_PLAYER(good_x, good_y))
13172 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13174 if (player->shield_deadly_time_left > 0 &&
13175 !IS_INDESTRUCTIBLE(bad_element))
13176 Bang(kill_x, kill_y);
13177 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13178 KillPlayer(player);
13181 Bang(good_x, good_y);
13185 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13187 int i, kill_x = -1, kill_y = -1;
13188 int bad_element = Feld[bad_x][bad_y];
13189 static int test_xy[4][2] =
13196 static int touch_dir[4] =
13198 MV_LEFT | MV_RIGHT,
13203 static int test_dir[4] =
13211 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13214 for (i = 0; i < NUM_DIRECTIONS; i++)
13216 int test_x, test_y, test_move_dir, test_element;
13218 test_x = bad_x + test_xy[i][0];
13219 test_y = bad_y + test_xy[i][1];
13221 if (!IN_LEV_FIELD(test_x, test_y))
13225 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13227 test_element = Feld[test_x][test_y];
13229 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13230 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13232 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13233 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13235 // good thing is player or penguin that does not move away
13236 if (IS_PLAYER(test_x, test_y))
13238 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13240 if (bad_element == EL_ROBOT && player->is_moving)
13241 continue; // robot does not kill player if he is moving
13243 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13245 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13246 continue; // center and border element do not touch
13254 else if (test_element == EL_PENGUIN)
13264 if (kill_x != -1 || kill_y != -1)
13266 if (IS_PLAYER(kill_x, kill_y))
13268 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13270 if (player->shield_deadly_time_left > 0 &&
13271 !IS_INDESTRUCTIBLE(bad_element))
13272 Bang(bad_x, bad_y);
13273 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13274 KillPlayer(player);
13277 Bang(kill_x, kill_y);
13281 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13283 int bad_element = Feld[bad_x][bad_y];
13284 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13285 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13286 int test_x = bad_x + dx, test_y = bad_y + dy;
13287 int test_move_dir, test_element;
13288 int kill_x = -1, kill_y = -1;
13290 if (!IN_LEV_FIELD(test_x, test_y))
13294 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13296 test_element = Feld[test_x][test_y];
13298 if (test_move_dir != bad_move_dir)
13300 // good thing can be player or penguin that does not move away
13301 if (IS_PLAYER(test_x, test_y))
13303 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13305 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13306 player as being hit when he is moving towards the bad thing, because
13307 the "get hit by" condition would be lost after the player stops) */
13308 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13309 return; // player moves away from bad thing
13314 else if (test_element == EL_PENGUIN)
13321 if (kill_x != -1 || kill_y != -1)
13323 if (IS_PLAYER(kill_x, kill_y))
13325 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13327 if (player->shield_deadly_time_left > 0 &&
13328 !IS_INDESTRUCTIBLE(bad_element))
13329 Bang(bad_x, bad_y);
13330 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13331 KillPlayer(player);
13334 Bang(kill_x, kill_y);
13338 void TestIfPlayerTouchesBadThing(int x, int y)
13340 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13343 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13345 TestIfGoodThingHitsBadThing(x, y, move_dir);
13348 void TestIfBadThingTouchesPlayer(int x, int y)
13350 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13353 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13355 TestIfBadThingHitsGoodThing(x, y, move_dir);
13358 void TestIfFriendTouchesBadThing(int x, int y)
13360 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13363 void TestIfBadThingTouchesFriend(int x, int y)
13365 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13368 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13370 int i, kill_x = bad_x, kill_y = bad_y;
13371 static int xy[4][2] =
13379 for (i = 0; i < NUM_DIRECTIONS; i++)
13383 x = bad_x + xy[i][0];
13384 y = bad_y + xy[i][1];
13385 if (!IN_LEV_FIELD(x, y))
13388 element = Feld[x][y];
13389 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13390 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13398 if (kill_x != bad_x || kill_y != bad_y)
13399 Bang(bad_x, bad_y);
13402 void KillPlayer(struct PlayerInfo *player)
13404 int jx = player->jx, jy = player->jy;
13406 if (!player->active)
13410 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13411 player->killed, player->active, player->reanimated);
13414 /* the following code was introduced to prevent an infinite loop when calling
13416 -> CheckTriggeredElementChangeExt()
13417 -> ExecuteCustomElementAction()
13419 -> (infinitely repeating the above sequence of function calls)
13420 which occurs when killing the player while having a CE with the setting
13421 "kill player X when explosion of <player X>"; the solution using a new
13422 field "player->killed" was chosen for backwards compatibility, although
13423 clever use of the fields "player->active" etc. would probably also work */
13425 if (player->killed)
13429 player->killed = TRUE;
13431 // remove accessible field at the player's position
13432 Feld[jx][jy] = EL_EMPTY;
13434 // deactivate shield (else Bang()/Explode() would not work right)
13435 player->shield_normal_time_left = 0;
13436 player->shield_deadly_time_left = 0;
13439 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13440 player->killed, player->active, player->reanimated);
13446 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13447 player->killed, player->active, player->reanimated);
13450 if (player->reanimated) // killed player may have been reanimated
13451 player->killed = player->reanimated = FALSE;
13453 BuryPlayer(player);
13456 static void KillPlayerUnlessEnemyProtected(int x, int y)
13458 if (!PLAYER_ENEMY_PROTECTED(x, y))
13459 KillPlayer(PLAYERINFO(x, y));
13462 static void KillPlayerUnlessExplosionProtected(int x, int y)
13464 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13465 KillPlayer(PLAYERINFO(x, y));
13468 void BuryPlayer(struct PlayerInfo *player)
13470 int jx = player->jx, jy = player->jy;
13472 if (!player->active)
13475 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13476 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13478 RemovePlayer(player);
13480 player->buried = TRUE;
13482 if (game.all_players_gone)
13483 game.GameOver = TRUE;
13486 void RemovePlayer(struct PlayerInfo *player)
13488 int jx = player->jx, jy = player->jy;
13489 int i, found = FALSE;
13491 player->present = FALSE;
13492 player->active = FALSE;
13494 // required for some CE actions (even if the player is not active anymore)
13495 player->MovPos = 0;
13497 if (!ExplodeField[jx][jy])
13498 StorePlayer[jx][jy] = 0;
13500 if (player->is_moving)
13501 TEST_DrawLevelField(player->last_jx, player->last_jy);
13503 for (i = 0; i < MAX_PLAYERS; i++)
13504 if (stored_player[i].active)
13509 game.all_players_gone = TRUE;
13510 game.GameOver = TRUE;
13513 game.exit_x = game.robot_wheel_x = jx;
13514 game.exit_y = game.robot_wheel_y = jy;
13517 void ExitPlayer(struct PlayerInfo *player)
13519 DrawPlayer(player); // needed here only to cleanup last field
13520 RemovePlayer(player);
13522 if (game.players_still_needed > 0)
13523 game.players_still_needed--;
13526 static void setFieldForSnapping(int x, int y, int element, int direction)
13528 struct ElementInfo *ei = &element_info[element];
13529 int direction_bit = MV_DIR_TO_BIT(direction);
13530 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13531 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13532 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13534 Feld[x][y] = EL_ELEMENT_SNAPPING;
13535 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13537 ResetGfxAnimation(x, y);
13539 GfxElement[x][y] = element;
13540 GfxAction[x][y] = action;
13541 GfxDir[x][y] = direction;
13542 GfxFrame[x][y] = -1;
13546 =============================================================================
13547 checkDiagonalPushing()
13548 -----------------------------------------------------------------------------
13549 check if diagonal input device direction results in pushing of object
13550 (by checking if the alternative direction is walkable, diggable, ...)
13551 =============================================================================
13554 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13555 int x, int y, int real_dx, int real_dy)
13557 int jx, jy, dx, dy, xx, yy;
13559 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13562 // diagonal direction: check alternative direction
13567 xx = jx + (dx == 0 ? real_dx : 0);
13568 yy = jy + (dy == 0 ? real_dy : 0);
13570 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13574 =============================================================================
13576 -----------------------------------------------------------------------------
13577 x, y: field next to player (non-diagonal) to try to dig to
13578 real_dx, real_dy: direction as read from input device (can be diagonal)
13579 =============================================================================
13582 static int DigField(struct PlayerInfo *player,
13583 int oldx, int oldy, int x, int y,
13584 int real_dx, int real_dy, int mode)
13586 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13587 boolean player_was_pushing = player->is_pushing;
13588 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13589 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13590 int jx = oldx, jy = oldy;
13591 int dx = x - jx, dy = y - jy;
13592 int nextx = x + dx, nexty = y + dy;
13593 int move_direction = (dx == -1 ? MV_LEFT :
13594 dx == +1 ? MV_RIGHT :
13596 dy == +1 ? MV_DOWN : MV_NONE);
13597 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13598 int dig_side = MV_DIR_OPPOSITE(move_direction);
13599 int old_element = Feld[jx][jy];
13600 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13603 if (is_player) // function can also be called by EL_PENGUIN
13605 if (player->MovPos == 0)
13607 player->is_digging = FALSE;
13608 player->is_collecting = FALSE;
13611 if (player->MovPos == 0) // last pushing move finished
13612 player->is_pushing = FALSE;
13614 if (mode == DF_NO_PUSH) // player just stopped pushing
13616 player->is_switching = FALSE;
13617 player->push_delay = -1;
13619 return MP_NO_ACTION;
13623 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13624 old_element = Back[jx][jy];
13626 // in case of element dropped at player position, check background
13627 else if (Back[jx][jy] != EL_EMPTY &&
13628 game.engine_version >= VERSION_IDENT(2,2,0,0))
13629 old_element = Back[jx][jy];
13631 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13632 return MP_NO_ACTION; // field has no opening in this direction
13634 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13635 return MP_NO_ACTION; // field has no opening in this direction
13637 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13641 Feld[jx][jy] = player->artwork_element;
13642 InitMovingField(jx, jy, MV_DOWN);
13643 Store[jx][jy] = EL_ACID;
13644 ContinueMoving(jx, jy);
13645 BuryPlayer(player);
13647 return MP_DONT_RUN_INTO;
13650 if (player_can_move && DONT_RUN_INTO(element))
13652 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13654 return MP_DONT_RUN_INTO;
13657 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13658 return MP_NO_ACTION;
13660 collect_count = element_info[element].collect_count_initial;
13662 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13663 return MP_NO_ACTION;
13665 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13666 player_can_move = player_can_move_or_snap;
13668 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13669 game.engine_version >= VERSION_IDENT(2,2,0,0))
13671 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13672 player->index_bit, dig_side);
13673 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13674 player->index_bit, dig_side);
13676 if (element == EL_DC_LANDMINE)
13679 if (Feld[x][y] != element) // field changed by snapping
13682 return MP_NO_ACTION;
13685 if (player->gravity && is_player && !player->is_auto_moving &&
13686 canFallDown(player) && move_direction != MV_DOWN &&
13687 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13688 return MP_NO_ACTION; // player cannot walk here due to gravity
13690 if (player_can_move &&
13691 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13693 int sound_element = SND_ELEMENT(element);
13694 int sound_action = ACTION_WALKING;
13696 if (IS_RND_GATE(element))
13698 if (!player->key[RND_GATE_NR(element)])
13699 return MP_NO_ACTION;
13701 else if (IS_RND_GATE_GRAY(element))
13703 if (!player->key[RND_GATE_GRAY_NR(element)])
13704 return MP_NO_ACTION;
13706 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13708 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13709 return MP_NO_ACTION;
13711 else if (element == EL_EXIT_OPEN ||
13712 element == EL_EM_EXIT_OPEN ||
13713 element == EL_EM_EXIT_OPENING ||
13714 element == EL_STEEL_EXIT_OPEN ||
13715 element == EL_EM_STEEL_EXIT_OPEN ||
13716 element == EL_EM_STEEL_EXIT_OPENING ||
13717 element == EL_SP_EXIT_OPEN ||
13718 element == EL_SP_EXIT_OPENING)
13720 sound_action = ACTION_PASSING; // player is passing exit
13722 else if (element == EL_EMPTY)
13724 sound_action = ACTION_MOVING; // nothing to walk on
13727 // play sound from background or player, whatever is available
13728 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13729 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13731 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13733 else if (player_can_move &&
13734 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13736 if (!ACCESS_FROM(element, opposite_direction))
13737 return MP_NO_ACTION; // field not accessible from this direction
13739 if (CAN_MOVE(element)) // only fixed elements can be passed!
13740 return MP_NO_ACTION;
13742 if (IS_EM_GATE(element))
13744 if (!player->key[EM_GATE_NR(element)])
13745 return MP_NO_ACTION;
13747 else if (IS_EM_GATE_GRAY(element))
13749 if (!player->key[EM_GATE_GRAY_NR(element)])
13750 return MP_NO_ACTION;
13752 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13754 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13755 return MP_NO_ACTION;
13757 else if (IS_EMC_GATE(element))
13759 if (!player->key[EMC_GATE_NR(element)])
13760 return MP_NO_ACTION;
13762 else if (IS_EMC_GATE_GRAY(element))
13764 if (!player->key[EMC_GATE_GRAY_NR(element)])
13765 return MP_NO_ACTION;
13767 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13769 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13770 return MP_NO_ACTION;
13772 else if (element == EL_DC_GATE_WHITE ||
13773 element == EL_DC_GATE_WHITE_GRAY ||
13774 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13776 if (player->num_white_keys == 0)
13777 return MP_NO_ACTION;
13779 player->num_white_keys--;
13781 else if (IS_SP_PORT(element))
13783 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13784 element == EL_SP_GRAVITY_PORT_RIGHT ||
13785 element == EL_SP_GRAVITY_PORT_UP ||
13786 element == EL_SP_GRAVITY_PORT_DOWN)
13787 player->gravity = !player->gravity;
13788 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13789 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13790 element == EL_SP_GRAVITY_ON_PORT_UP ||
13791 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13792 player->gravity = TRUE;
13793 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13794 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13795 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13796 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13797 player->gravity = FALSE;
13800 // automatically move to the next field with double speed
13801 player->programmed_action = move_direction;
13803 if (player->move_delay_reset_counter == 0)
13805 player->move_delay_reset_counter = 2; // two double speed steps
13807 DOUBLE_PLAYER_SPEED(player);
13810 PlayLevelSoundAction(x, y, ACTION_PASSING);
13812 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13816 if (mode != DF_SNAP)
13818 GfxElement[x][y] = GFX_ELEMENT(element);
13819 player->is_digging = TRUE;
13822 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13824 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13825 player->index_bit, dig_side);
13827 if (mode == DF_SNAP)
13829 if (level.block_snap_field)
13830 setFieldForSnapping(x, y, element, move_direction);
13832 TestIfElementTouchesCustomElement(x, y); // for empty space
13834 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13835 player->index_bit, dig_side);
13838 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13842 if (is_player && mode != DF_SNAP)
13844 GfxElement[x][y] = element;
13845 player->is_collecting = TRUE;
13848 if (element == EL_SPEED_PILL)
13850 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13852 else if (element == EL_EXTRA_TIME && level.time > 0)
13854 TimeLeft += level.extra_time;
13856 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13858 DisplayGameControlValues();
13860 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
13862 player->shield_normal_time_left += level.shield_normal_time;
13863 if (element == EL_SHIELD_DEADLY)
13864 player->shield_deadly_time_left += level.shield_deadly_time;
13866 else if (element == EL_DYNAMITE ||
13867 element == EL_EM_DYNAMITE ||
13868 element == EL_SP_DISK_RED)
13870 if (player->inventory_size < MAX_INVENTORY_SIZE)
13871 player->inventory_element[player->inventory_size++] = element;
13873 DrawGameDoorValues();
13875 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
13877 player->dynabomb_count++;
13878 player->dynabombs_left++;
13880 else if (element == EL_DYNABOMB_INCREASE_SIZE)
13882 player->dynabomb_size++;
13884 else if (element == EL_DYNABOMB_INCREASE_POWER)
13886 player->dynabomb_xl = TRUE;
13888 else if (IS_KEY(element))
13890 player->key[KEY_NR(element)] = TRUE;
13892 DrawGameDoorValues();
13894 else if (element == EL_DC_KEY_WHITE)
13896 player->num_white_keys++;
13898 // display white keys?
13899 // DrawGameDoorValues();
13901 else if (IS_ENVELOPE(element))
13903 player->show_envelope = element;
13905 else if (element == EL_EMC_LENSES)
13907 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
13909 RedrawAllInvisibleElementsForLenses();
13911 else if (element == EL_EMC_MAGNIFIER)
13913 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
13915 RedrawAllInvisibleElementsForMagnifier();
13917 else if (IS_DROPPABLE(element) ||
13918 IS_THROWABLE(element)) // can be collected and dropped
13922 if (collect_count == 0)
13923 player->inventory_infinite_element = element;
13925 for (i = 0; i < collect_count; i++)
13926 if (player->inventory_size < MAX_INVENTORY_SIZE)
13927 player->inventory_element[player->inventory_size++] = element;
13929 DrawGameDoorValues();
13931 else if (collect_count > 0)
13933 game.gems_still_needed -= collect_count;
13934 if (game.gems_still_needed < 0)
13935 game.gems_still_needed = 0;
13937 game.snapshot.collected_item = TRUE;
13939 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
13941 DisplayGameControlValues();
13944 RaiseScoreElement(element);
13945 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
13948 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
13949 player->index_bit, dig_side);
13951 if (mode == DF_SNAP)
13953 if (level.block_snap_field)
13954 setFieldForSnapping(x, y, element, move_direction);
13956 TestIfElementTouchesCustomElement(x, y); // for empty space
13958 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13959 player->index_bit, dig_side);
13962 else if (player_can_move_or_snap && IS_PUSHABLE(element))
13964 if (mode == DF_SNAP && element != EL_BD_ROCK)
13965 return MP_NO_ACTION;
13967 if (CAN_FALL(element) && dy)
13968 return MP_NO_ACTION;
13970 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
13971 !(element == EL_SPRING && level.use_spring_bug))
13972 return MP_NO_ACTION;
13974 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
13975 ((move_direction & MV_VERTICAL &&
13976 ((element_info[element].move_pattern & MV_LEFT &&
13977 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
13978 (element_info[element].move_pattern & MV_RIGHT &&
13979 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
13980 (move_direction & MV_HORIZONTAL &&
13981 ((element_info[element].move_pattern & MV_UP &&
13982 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
13983 (element_info[element].move_pattern & MV_DOWN &&
13984 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
13985 return MP_NO_ACTION;
13987 // do not push elements already moving away faster than player
13988 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
13989 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
13990 return MP_NO_ACTION;
13992 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
13994 if (player->push_delay_value == -1 || !player_was_pushing)
13995 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13997 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13999 if (player->push_delay_value == -1)
14000 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14002 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14004 if (!player->is_pushing)
14005 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14008 player->is_pushing = TRUE;
14009 player->is_active = TRUE;
14011 if (!(IN_LEV_FIELD(nextx, nexty) &&
14012 (IS_FREE(nextx, nexty) ||
14013 (IS_SB_ELEMENT(element) &&
14014 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14015 (IS_CUSTOM_ELEMENT(element) &&
14016 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14017 return MP_NO_ACTION;
14019 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14020 return MP_NO_ACTION;
14022 if (player->push_delay == -1) // new pushing; restart delay
14023 player->push_delay = 0;
14025 if (player->push_delay < player->push_delay_value &&
14026 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14027 element != EL_SPRING && element != EL_BALLOON)
14029 // make sure that there is no move delay before next try to push
14030 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14031 player->move_delay = 0;
14033 return MP_NO_ACTION;
14036 if (IS_CUSTOM_ELEMENT(element) &&
14037 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14039 if (!DigFieldByCE(nextx, nexty, element))
14040 return MP_NO_ACTION;
14043 if (IS_SB_ELEMENT(element))
14045 boolean sokoban_task_solved = FALSE;
14047 if (element == EL_SOKOBAN_FIELD_FULL)
14049 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14051 IncrementSokobanFieldsNeeded();
14052 IncrementSokobanObjectsNeeded();
14055 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14057 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14059 DecrementSokobanFieldsNeeded();
14060 DecrementSokobanObjectsNeeded();
14062 // sokoban object was pushed from empty field to sokoban field
14063 if (Back[x][y] == EL_EMPTY)
14064 sokoban_task_solved = TRUE;
14067 Feld[x][y] = EL_SOKOBAN_OBJECT;
14069 if (Back[x][y] == Back[nextx][nexty])
14070 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14071 else if (Back[x][y] != 0)
14072 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14075 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14078 if (sokoban_task_solved &&
14079 game.sokoban_fields_still_needed == 0 &&
14080 game.sokoban_objects_still_needed == 0 &&
14081 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14083 game.players_still_needed = 0;
14087 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14091 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14093 InitMovingField(x, y, move_direction);
14094 GfxAction[x][y] = ACTION_PUSHING;
14096 if (mode == DF_SNAP)
14097 ContinueMoving(x, y);
14099 MovPos[x][y] = (dx != 0 ? dx : dy);
14101 Pushed[x][y] = TRUE;
14102 Pushed[nextx][nexty] = TRUE;
14104 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14105 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14107 player->push_delay_value = -1; // get new value later
14109 // check for element change _after_ element has been pushed
14110 if (game.use_change_when_pushing_bug)
14112 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14113 player->index_bit, dig_side);
14114 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14115 player->index_bit, dig_side);
14118 else if (IS_SWITCHABLE(element))
14120 if (PLAYER_SWITCHING(player, x, y))
14122 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14123 player->index_bit, dig_side);
14128 player->is_switching = TRUE;
14129 player->switch_x = x;
14130 player->switch_y = y;
14132 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14134 if (element == EL_ROBOT_WHEEL)
14136 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14138 game.robot_wheel_x = x;
14139 game.robot_wheel_y = y;
14140 game.robot_wheel_active = TRUE;
14142 TEST_DrawLevelField(x, y);
14144 else if (element == EL_SP_TERMINAL)
14148 SCAN_PLAYFIELD(xx, yy)
14150 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14154 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14156 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14158 ResetGfxAnimation(xx, yy);
14159 TEST_DrawLevelField(xx, yy);
14163 else if (IS_BELT_SWITCH(element))
14165 ToggleBeltSwitch(x, y);
14167 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14168 element == EL_SWITCHGATE_SWITCH_DOWN ||
14169 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14170 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14172 ToggleSwitchgateSwitch(x, y);
14174 else if (element == EL_LIGHT_SWITCH ||
14175 element == EL_LIGHT_SWITCH_ACTIVE)
14177 ToggleLightSwitch(x, y);
14179 else if (element == EL_TIMEGATE_SWITCH ||
14180 element == EL_DC_TIMEGATE_SWITCH)
14182 ActivateTimegateSwitch(x, y);
14184 else if (element == EL_BALLOON_SWITCH_LEFT ||
14185 element == EL_BALLOON_SWITCH_RIGHT ||
14186 element == EL_BALLOON_SWITCH_UP ||
14187 element == EL_BALLOON_SWITCH_DOWN ||
14188 element == EL_BALLOON_SWITCH_NONE ||
14189 element == EL_BALLOON_SWITCH_ANY)
14191 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14192 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14193 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14194 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14195 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14198 else if (element == EL_LAMP)
14200 Feld[x][y] = EL_LAMP_ACTIVE;
14201 game.lights_still_needed--;
14203 ResetGfxAnimation(x, y);
14204 TEST_DrawLevelField(x, y);
14206 else if (element == EL_TIME_ORB_FULL)
14208 Feld[x][y] = EL_TIME_ORB_EMPTY;
14210 if (level.time > 0 || level.use_time_orb_bug)
14212 TimeLeft += level.time_orb_time;
14213 game.no_time_limit = FALSE;
14215 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14217 DisplayGameControlValues();
14220 ResetGfxAnimation(x, y);
14221 TEST_DrawLevelField(x, y);
14223 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14224 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14228 game.ball_active = !game.ball_active;
14230 SCAN_PLAYFIELD(xx, yy)
14232 int e = Feld[xx][yy];
14234 if (game.ball_active)
14236 if (e == EL_EMC_MAGIC_BALL)
14237 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14238 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14239 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14243 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14244 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14245 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14246 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14251 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14252 player->index_bit, dig_side);
14254 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14255 player->index_bit, dig_side);
14257 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14258 player->index_bit, dig_side);
14264 if (!PLAYER_SWITCHING(player, x, y))
14266 player->is_switching = TRUE;
14267 player->switch_x = x;
14268 player->switch_y = y;
14270 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14271 player->index_bit, dig_side);
14272 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14273 player->index_bit, dig_side);
14275 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14276 player->index_bit, dig_side);
14277 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14278 player->index_bit, dig_side);
14281 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14282 player->index_bit, dig_side);
14283 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14284 player->index_bit, dig_side);
14286 return MP_NO_ACTION;
14289 player->push_delay = -1;
14291 if (is_player) // function can also be called by EL_PENGUIN
14293 if (Feld[x][y] != element) // really digged/collected something
14295 player->is_collecting = !player->is_digging;
14296 player->is_active = TRUE;
14303 static boolean DigFieldByCE(int x, int y, int digging_element)
14305 int element = Feld[x][y];
14307 if (!IS_FREE(x, y))
14309 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14310 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14313 // no element can dig solid indestructible elements
14314 if (IS_INDESTRUCTIBLE(element) &&
14315 !IS_DIGGABLE(element) &&
14316 !IS_COLLECTIBLE(element))
14319 if (AmoebaNr[x][y] &&
14320 (element == EL_AMOEBA_FULL ||
14321 element == EL_BD_AMOEBA ||
14322 element == EL_AMOEBA_GROWING))
14324 AmoebaCnt[AmoebaNr[x][y]]--;
14325 AmoebaCnt2[AmoebaNr[x][y]]--;
14328 if (IS_MOVING(x, y))
14329 RemoveMovingField(x, y);
14333 TEST_DrawLevelField(x, y);
14336 // if digged element was about to explode, prevent the explosion
14337 ExplodeField[x][y] = EX_TYPE_NONE;
14339 PlayLevelSoundAction(x, y, action);
14342 Store[x][y] = EL_EMPTY;
14344 // this makes it possible to leave the removed element again
14345 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14346 Store[x][y] = element;
14351 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14353 int jx = player->jx, jy = player->jy;
14354 int x = jx + dx, y = jy + dy;
14355 int snap_direction = (dx == -1 ? MV_LEFT :
14356 dx == +1 ? MV_RIGHT :
14358 dy == +1 ? MV_DOWN : MV_NONE);
14359 boolean can_continue_snapping = (level.continuous_snapping &&
14360 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14362 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14365 if (!player->active || !IN_LEV_FIELD(x, y))
14373 if (player->MovPos == 0)
14374 player->is_pushing = FALSE;
14376 player->is_snapping = FALSE;
14378 if (player->MovPos == 0)
14380 player->is_moving = FALSE;
14381 player->is_digging = FALSE;
14382 player->is_collecting = FALSE;
14388 // prevent snapping with already pressed snap key when not allowed
14389 if (player->is_snapping && !can_continue_snapping)
14392 player->MovDir = snap_direction;
14394 if (player->MovPos == 0)
14396 player->is_moving = FALSE;
14397 player->is_digging = FALSE;
14398 player->is_collecting = FALSE;
14401 player->is_dropping = FALSE;
14402 player->is_dropping_pressed = FALSE;
14403 player->drop_pressed_delay = 0;
14405 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14408 player->is_snapping = TRUE;
14409 player->is_active = TRUE;
14411 if (player->MovPos == 0)
14413 player->is_moving = FALSE;
14414 player->is_digging = FALSE;
14415 player->is_collecting = FALSE;
14418 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14419 TEST_DrawLevelField(player->last_jx, player->last_jy);
14421 TEST_DrawLevelField(x, y);
14426 static boolean DropElement(struct PlayerInfo *player)
14428 int old_element, new_element;
14429 int dropx = player->jx, dropy = player->jy;
14430 int drop_direction = player->MovDir;
14431 int drop_side = drop_direction;
14432 int drop_element = get_next_dropped_element(player);
14434 /* do not drop an element on top of another element; when holding drop key
14435 pressed without moving, dropped element must move away before the next
14436 element can be dropped (this is especially important if the next element
14437 is dynamite, which can be placed on background for historical reasons) */
14438 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14441 if (IS_THROWABLE(drop_element))
14443 dropx += GET_DX_FROM_DIR(drop_direction);
14444 dropy += GET_DY_FROM_DIR(drop_direction);
14446 if (!IN_LEV_FIELD(dropx, dropy))
14450 old_element = Feld[dropx][dropy]; // old element at dropping position
14451 new_element = drop_element; // default: no change when dropping
14453 // check if player is active, not moving and ready to drop
14454 if (!player->active || player->MovPos || player->drop_delay > 0)
14457 // check if player has anything that can be dropped
14458 if (new_element == EL_UNDEFINED)
14461 // only set if player has anything that can be dropped
14462 player->is_dropping_pressed = TRUE;
14464 // check if drop key was pressed long enough for EM style dynamite
14465 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14468 // check if anything can be dropped at the current position
14469 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14472 // collected custom elements can only be dropped on empty fields
14473 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14476 if (old_element != EL_EMPTY)
14477 Back[dropx][dropy] = old_element; // store old element on this field
14479 ResetGfxAnimation(dropx, dropy);
14480 ResetRandomAnimationValue(dropx, dropy);
14482 if (player->inventory_size > 0 ||
14483 player->inventory_infinite_element != EL_UNDEFINED)
14485 if (player->inventory_size > 0)
14487 player->inventory_size--;
14489 DrawGameDoorValues();
14491 if (new_element == EL_DYNAMITE)
14492 new_element = EL_DYNAMITE_ACTIVE;
14493 else if (new_element == EL_EM_DYNAMITE)
14494 new_element = EL_EM_DYNAMITE_ACTIVE;
14495 else if (new_element == EL_SP_DISK_RED)
14496 new_element = EL_SP_DISK_RED_ACTIVE;
14499 Feld[dropx][dropy] = new_element;
14501 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14502 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14503 el2img(Feld[dropx][dropy]), 0);
14505 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14507 // needed if previous element just changed to "empty" in the last frame
14508 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14510 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14511 player->index_bit, drop_side);
14512 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14514 player->index_bit, drop_side);
14516 TestIfElementTouchesCustomElement(dropx, dropy);
14518 else // player is dropping a dyna bomb
14520 player->dynabombs_left--;
14522 Feld[dropx][dropy] = new_element;
14524 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14525 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14526 el2img(Feld[dropx][dropy]), 0);
14528 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14531 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14532 InitField_WithBug1(dropx, dropy, FALSE);
14534 new_element = Feld[dropx][dropy]; // element might have changed
14536 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14537 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14539 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14540 MovDir[dropx][dropy] = drop_direction;
14542 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14544 // do not cause impact style collision by dropping elements that can fall
14545 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14548 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14549 player->is_dropping = TRUE;
14551 player->drop_pressed_delay = 0;
14552 player->is_dropping_pressed = FALSE;
14554 player->drop_x = dropx;
14555 player->drop_y = dropy;
14560 // ----------------------------------------------------------------------------
14561 // game sound playing functions
14562 // ----------------------------------------------------------------------------
14564 static int *loop_sound_frame = NULL;
14565 static int *loop_sound_volume = NULL;
14567 void InitPlayLevelSound(void)
14569 int num_sounds = getSoundListSize();
14571 checked_free(loop_sound_frame);
14572 checked_free(loop_sound_volume);
14574 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14575 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14578 static void PlayLevelSound(int x, int y, int nr)
14580 int sx = SCREENX(x), sy = SCREENY(y);
14581 int volume, stereo_position;
14582 int max_distance = 8;
14583 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14585 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14586 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14589 if (!IN_LEV_FIELD(x, y) ||
14590 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14591 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14594 volume = SOUND_MAX_VOLUME;
14596 if (!IN_SCR_FIELD(sx, sy))
14598 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14599 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14601 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14604 stereo_position = (SOUND_MAX_LEFT +
14605 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14606 (SCR_FIELDX + 2 * max_distance));
14608 if (IS_LOOP_SOUND(nr))
14610 /* This assures that quieter loop sounds do not overwrite louder ones,
14611 while restarting sound volume comparison with each new game frame. */
14613 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14616 loop_sound_volume[nr] = volume;
14617 loop_sound_frame[nr] = FrameCounter;
14620 PlaySoundExt(nr, volume, stereo_position, type);
14623 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14625 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14626 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14627 y < LEVELY(BY1) ? LEVELY(BY1) :
14628 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14632 static void PlayLevelSoundAction(int x, int y, int action)
14634 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14637 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14639 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14641 if (sound_effect != SND_UNDEFINED)
14642 PlayLevelSound(x, y, sound_effect);
14645 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14648 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14650 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14651 PlayLevelSound(x, y, sound_effect);
14654 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14656 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14658 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14659 PlayLevelSound(x, y, sound_effect);
14662 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14664 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14666 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14667 StopSound(sound_effect);
14670 static int getLevelMusicNr(void)
14672 if (levelset.music[level_nr] != MUS_UNDEFINED)
14673 return levelset.music[level_nr]; // from config file
14675 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14678 static void FadeLevelSounds(void)
14683 static void FadeLevelMusic(void)
14685 int music_nr = getLevelMusicNr();
14686 char *curr_music = getCurrentlyPlayingMusicFilename();
14687 char *next_music = getMusicInfoEntryFilename(music_nr);
14689 if (!strEqual(curr_music, next_music))
14693 void FadeLevelSoundsAndMusic(void)
14699 static void PlayLevelMusic(void)
14701 int music_nr = getLevelMusicNr();
14702 char *curr_music = getCurrentlyPlayingMusicFilename();
14703 char *next_music = getMusicInfoEntryFilename(music_nr);
14705 if (!strEqual(curr_music, next_music))
14706 PlayMusicLoop(music_nr);
14709 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14711 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14712 int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
14713 int x = xx - 1 - offset;
14714 int y = yy - 1 - offset;
14719 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14723 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14727 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14731 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14735 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14739 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14743 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14746 case SOUND_android_clone:
14747 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14750 case SOUND_android_move:
14751 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14755 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14759 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14763 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14766 case SOUND_eater_eat:
14767 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14771 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14774 case SOUND_collect:
14775 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14778 case SOUND_diamond:
14779 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14783 // !!! CHECK THIS !!!
14785 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14787 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14791 case SOUND_wonderfall:
14792 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14796 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14800 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14804 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14808 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14812 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14816 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14820 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14824 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14827 case SOUND_exit_open:
14828 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14831 case SOUND_exit_leave:
14832 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14835 case SOUND_dynamite:
14836 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14840 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14844 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14848 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14852 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14856 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
14860 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
14864 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
14869 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
14871 int element = map_element_SP_to_RND(element_sp);
14872 int action = map_action_SP_to_RND(action_sp);
14873 int offset = (setup.sp_show_border_elements ? 0 : 1);
14874 int x = xx - offset;
14875 int y = yy - offset;
14877 PlayLevelSoundElementAction(x, y, element, action);
14880 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
14882 int element = map_element_MM_to_RND(element_mm);
14883 int action = map_action_MM_to_RND(action_mm);
14885 int x = xx - offset;
14886 int y = yy - offset;
14888 if (!IS_MM_ELEMENT(element))
14889 element = EL_MM_DEFAULT;
14891 PlayLevelSoundElementAction(x, y, element, action);
14894 void PlaySound_MM(int sound_mm)
14896 int sound = map_sound_MM_to_RND(sound_mm);
14898 if (sound == SND_UNDEFINED)
14904 void PlaySoundLoop_MM(int sound_mm)
14906 int sound = map_sound_MM_to_RND(sound_mm);
14908 if (sound == SND_UNDEFINED)
14911 PlaySoundLoop(sound);
14914 void StopSound_MM(int sound_mm)
14916 int sound = map_sound_MM_to_RND(sound_mm);
14918 if (sound == SND_UNDEFINED)
14924 void RaiseScore(int value)
14926 game.score += value;
14928 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
14930 DisplayGameControlValues();
14933 void RaiseScoreElement(int element)
14938 case EL_BD_DIAMOND:
14939 case EL_EMERALD_YELLOW:
14940 case EL_EMERALD_RED:
14941 case EL_EMERALD_PURPLE:
14942 case EL_SP_INFOTRON:
14943 RaiseScore(level.score[SC_EMERALD]);
14946 RaiseScore(level.score[SC_DIAMOND]);
14949 RaiseScore(level.score[SC_CRYSTAL]);
14952 RaiseScore(level.score[SC_PEARL]);
14955 case EL_BD_BUTTERFLY:
14956 case EL_SP_ELECTRON:
14957 RaiseScore(level.score[SC_BUG]);
14960 case EL_BD_FIREFLY:
14961 case EL_SP_SNIKSNAK:
14962 RaiseScore(level.score[SC_SPACESHIP]);
14965 case EL_DARK_YAMYAM:
14966 RaiseScore(level.score[SC_YAMYAM]);
14969 RaiseScore(level.score[SC_ROBOT]);
14972 RaiseScore(level.score[SC_PACMAN]);
14975 RaiseScore(level.score[SC_NUT]);
14978 case EL_EM_DYNAMITE:
14979 case EL_SP_DISK_RED:
14980 case EL_DYNABOMB_INCREASE_NUMBER:
14981 case EL_DYNABOMB_INCREASE_SIZE:
14982 case EL_DYNABOMB_INCREASE_POWER:
14983 RaiseScore(level.score[SC_DYNAMITE]);
14985 case EL_SHIELD_NORMAL:
14986 case EL_SHIELD_DEADLY:
14987 RaiseScore(level.score[SC_SHIELD]);
14989 case EL_EXTRA_TIME:
14990 RaiseScore(level.extra_time_score);
15004 case EL_DC_KEY_WHITE:
15005 RaiseScore(level.score[SC_KEY]);
15008 RaiseScore(element_info[element].collect_score);
15013 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15015 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15017 // closing door required in case of envelope style request dialogs
15020 // prevent short reactivation of overlay buttons while closing door
15021 SetOverlayActive(FALSE);
15023 CloseDoor(DOOR_CLOSE_1);
15026 if (network.enabled)
15027 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15031 FadeSkipNextFadeIn();
15033 SetGameStatus(GAME_MODE_MAIN);
15038 else // continue playing the game
15040 if (tape.playing && tape.deactivate_display)
15041 TapeDeactivateDisplayOff(TRUE);
15043 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15045 if (tape.playing && tape.deactivate_display)
15046 TapeDeactivateDisplayOn();
15050 void RequestQuitGame(boolean ask_if_really_quit)
15052 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15053 boolean skip_request = game.all_players_gone || quick_quit;
15055 RequestQuitGameExt(skip_request, quick_quit,
15056 "Do you really want to quit the game?");
15059 void RequestRestartGame(char *message)
15061 game.restart_game_message = NULL;
15063 boolean has_started_game = hasStartedNetworkGame();
15064 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15066 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15068 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15072 SetGameStatus(GAME_MODE_MAIN);
15078 void CheckGameOver(void)
15080 static boolean last_game_over = FALSE;
15081 static int game_over_delay = 0;
15082 int game_over_delay_value = 50;
15083 boolean game_over = checkGameFailed();
15085 // do not handle game over if request dialog is already active
15086 if (game.request_active)
15089 // do not ask to play again if game was never actually played
15090 if (!game.GamePlayed)
15095 last_game_over = FALSE;
15096 game_over_delay = game_over_delay_value;
15101 if (game_over_delay > 0)
15108 if (last_game_over != game_over)
15109 game.restart_game_message = (hasStartedNetworkGame() ?
15110 "Game over! Play it again?" :
15113 last_game_over = game_over;
15116 boolean checkGameSolved(void)
15118 // set for all game engines if level was solved
15119 return game.LevelSolved_GameEnd;
15122 boolean checkGameFailed(void)
15124 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15125 return (game_em.game_over && !game_em.level_solved);
15126 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15127 return (game_sp.game_over && !game_sp.level_solved);
15128 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15129 return (game_mm.game_over && !game_mm.level_solved);
15130 else // GAME_ENGINE_TYPE_RND
15131 return (game.GameOver && !game.LevelSolved);
15134 boolean checkGameEnded(void)
15136 return (checkGameSolved() || checkGameFailed());
15140 // ----------------------------------------------------------------------------
15141 // random generator functions
15142 // ----------------------------------------------------------------------------
15144 unsigned int InitEngineRandom_RND(int seed)
15146 game.num_random_calls = 0;
15148 return InitEngineRandom(seed);
15151 unsigned int RND(int max)
15155 game.num_random_calls++;
15157 return GetEngineRandom(max);
15164 // ----------------------------------------------------------------------------
15165 // game engine snapshot handling functions
15166 // ----------------------------------------------------------------------------
15168 struct EngineSnapshotInfo
15170 // runtime values for custom element collect score
15171 int collect_score[NUM_CUSTOM_ELEMENTS];
15173 // runtime values for group element choice position
15174 int choice_pos[NUM_GROUP_ELEMENTS];
15176 // runtime values for belt position animations
15177 int belt_graphic[4][NUM_BELT_PARTS];
15178 int belt_anim_mode[4][NUM_BELT_PARTS];
15181 static struct EngineSnapshotInfo engine_snapshot_rnd;
15182 static char *snapshot_level_identifier = NULL;
15183 static int snapshot_level_nr = -1;
15185 static void SaveEngineSnapshotValues_RND(void)
15187 static int belt_base_active_element[4] =
15189 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15190 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15191 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15192 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15196 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15198 int element = EL_CUSTOM_START + i;
15200 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15203 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15205 int element = EL_GROUP_START + i;
15207 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15210 for (i = 0; i < 4; i++)
15212 for (j = 0; j < NUM_BELT_PARTS; j++)
15214 int element = belt_base_active_element[i] + j;
15215 int graphic = el2img(element);
15216 int anim_mode = graphic_info[graphic].anim_mode;
15218 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15219 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15224 static void LoadEngineSnapshotValues_RND(void)
15226 unsigned int num_random_calls = game.num_random_calls;
15229 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15231 int element = EL_CUSTOM_START + i;
15233 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15236 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15238 int element = EL_GROUP_START + i;
15240 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15243 for (i = 0; i < 4; i++)
15245 for (j = 0; j < NUM_BELT_PARTS; j++)
15247 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15248 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15250 graphic_info[graphic].anim_mode = anim_mode;
15254 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15256 InitRND(tape.random_seed);
15257 for (i = 0; i < num_random_calls; i++)
15261 if (game.num_random_calls != num_random_calls)
15263 Error(ERR_INFO, "number of random calls out of sync");
15264 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15265 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15266 Error(ERR_EXIT, "this should not happen -- please debug");
15270 void FreeEngineSnapshotSingle(void)
15272 FreeSnapshotSingle();
15274 setString(&snapshot_level_identifier, NULL);
15275 snapshot_level_nr = -1;
15278 void FreeEngineSnapshotList(void)
15280 FreeSnapshotList();
15283 static ListNode *SaveEngineSnapshotBuffers(void)
15285 ListNode *buffers = NULL;
15287 // copy some special values to a structure better suited for the snapshot
15289 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15290 SaveEngineSnapshotValues_RND();
15291 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15292 SaveEngineSnapshotValues_EM();
15293 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15294 SaveEngineSnapshotValues_SP(&buffers);
15295 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15296 SaveEngineSnapshotValues_MM(&buffers);
15298 // save values stored in special snapshot structure
15300 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15301 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15302 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15303 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15304 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15305 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15306 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15307 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15309 // save further RND engine values
15311 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15312 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15313 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15315 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15316 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15317 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15318 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15319 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15321 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15322 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15323 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15325 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15327 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15328 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15330 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15331 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15332 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15333 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15334 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15335 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15336 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15337 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15338 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15339 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15340 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15341 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15342 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15343 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15344 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15345 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15346 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15347 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15349 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15350 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15352 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15353 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15354 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15356 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15357 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15359 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15360 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15361 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15362 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15363 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15365 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15366 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15369 ListNode *node = engine_snapshot_list_rnd;
15372 while (node != NULL)
15374 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15379 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15385 void SaveEngineSnapshotSingle(void)
15387 ListNode *buffers = SaveEngineSnapshotBuffers();
15389 // finally save all snapshot buffers to single snapshot
15390 SaveSnapshotSingle(buffers);
15392 // save level identification information
15393 setString(&snapshot_level_identifier, leveldir_current->identifier);
15394 snapshot_level_nr = level_nr;
15397 boolean CheckSaveEngineSnapshotToList(void)
15399 boolean save_snapshot =
15400 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15401 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15402 game.snapshot.changed_action) ||
15403 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15404 game.snapshot.collected_item));
15406 game.snapshot.changed_action = FALSE;
15407 game.snapshot.collected_item = FALSE;
15408 game.snapshot.save_snapshot = save_snapshot;
15410 return save_snapshot;
15413 void SaveEngineSnapshotToList(void)
15415 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15419 ListNode *buffers = SaveEngineSnapshotBuffers();
15421 // finally save all snapshot buffers to snapshot list
15422 SaveSnapshotToList(buffers);
15425 void SaveEngineSnapshotToListInitial(void)
15427 FreeEngineSnapshotList();
15429 SaveEngineSnapshotToList();
15432 static void LoadEngineSnapshotValues(void)
15434 // restore special values from snapshot structure
15436 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15437 LoadEngineSnapshotValues_RND();
15438 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15439 LoadEngineSnapshotValues_EM();
15440 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15441 LoadEngineSnapshotValues_SP();
15442 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15443 LoadEngineSnapshotValues_MM();
15446 void LoadEngineSnapshotSingle(void)
15448 LoadSnapshotSingle();
15450 LoadEngineSnapshotValues();
15453 static void LoadEngineSnapshot_Undo(int steps)
15455 LoadSnapshotFromList_Older(steps);
15457 LoadEngineSnapshotValues();
15460 static void LoadEngineSnapshot_Redo(int steps)
15462 LoadSnapshotFromList_Newer(steps);
15464 LoadEngineSnapshotValues();
15467 boolean CheckEngineSnapshotSingle(void)
15469 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15470 snapshot_level_nr == level_nr);
15473 boolean CheckEngineSnapshotList(void)
15475 return CheckSnapshotList();
15479 // ---------- new game button stuff -------------------------------------------
15486 boolean *setup_value;
15487 boolean allowed_on_tape;
15488 boolean is_touch_button;
15490 } gamebutton_info[NUM_GAME_BUTTONS] =
15493 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15494 GAME_CTRL_ID_STOP, NULL,
15495 TRUE, FALSE, "stop game"
15498 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15499 GAME_CTRL_ID_PAUSE, NULL,
15500 TRUE, FALSE, "pause game"
15503 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15504 GAME_CTRL_ID_PLAY, NULL,
15505 TRUE, FALSE, "play game"
15508 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15509 GAME_CTRL_ID_UNDO, NULL,
15510 TRUE, FALSE, "undo step"
15513 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15514 GAME_CTRL_ID_REDO, NULL,
15515 TRUE, FALSE, "redo step"
15518 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15519 GAME_CTRL_ID_SAVE, NULL,
15520 TRUE, FALSE, "save game"
15523 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15524 GAME_CTRL_ID_PAUSE2, NULL,
15525 TRUE, FALSE, "pause game"
15528 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15529 GAME_CTRL_ID_LOAD, NULL,
15530 TRUE, FALSE, "load game"
15533 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15534 GAME_CTRL_ID_PANEL_STOP, NULL,
15535 FALSE, FALSE, "stop game"
15538 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15539 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15540 FALSE, FALSE, "pause game"
15543 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15544 GAME_CTRL_ID_PANEL_PLAY, NULL,
15545 FALSE, FALSE, "play game"
15548 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15549 GAME_CTRL_ID_TOUCH_STOP, NULL,
15550 FALSE, TRUE, "stop game"
15553 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15554 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15555 FALSE, TRUE, "pause game"
15558 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15559 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15560 TRUE, FALSE, "background music on/off"
15563 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15564 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15565 TRUE, FALSE, "sound loops on/off"
15568 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15569 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15570 TRUE, FALSE, "normal sounds on/off"
15573 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15574 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15575 FALSE, FALSE, "background music on/off"
15578 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15579 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15580 FALSE, FALSE, "sound loops on/off"
15583 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15584 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15585 FALSE, FALSE, "normal sounds on/off"
15589 void CreateGameButtons(void)
15593 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15595 int graphic = gamebutton_info[i].graphic;
15596 struct GraphicInfo *gfx = &graphic_info[graphic];
15597 struct XY *pos = gamebutton_info[i].pos;
15598 struct GadgetInfo *gi;
15601 unsigned int event_mask;
15602 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15603 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15604 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15605 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15606 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15607 int gd_x = gfx->src_x;
15608 int gd_y = gfx->src_y;
15609 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15610 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15611 int gd_xa = gfx->src_x + gfx->active_xoffset;
15612 int gd_ya = gfx->src_y + gfx->active_yoffset;
15613 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15614 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15615 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15616 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15619 if (gfx->bitmap == NULL)
15621 game_gadget[id] = NULL;
15626 if (id == GAME_CTRL_ID_STOP ||
15627 id == GAME_CTRL_ID_PANEL_STOP ||
15628 id == GAME_CTRL_ID_TOUCH_STOP ||
15629 id == GAME_CTRL_ID_PLAY ||
15630 id == GAME_CTRL_ID_PANEL_PLAY ||
15631 id == GAME_CTRL_ID_SAVE ||
15632 id == GAME_CTRL_ID_LOAD)
15634 button_type = GD_TYPE_NORMAL_BUTTON;
15636 event_mask = GD_EVENT_RELEASED;
15638 else if (id == GAME_CTRL_ID_UNDO ||
15639 id == GAME_CTRL_ID_REDO)
15641 button_type = GD_TYPE_NORMAL_BUTTON;
15643 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15647 button_type = GD_TYPE_CHECK_BUTTON;
15648 checked = (gamebutton_info[i].setup_value != NULL ?
15649 *gamebutton_info[i].setup_value : FALSE);
15650 event_mask = GD_EVENT_PRESSED;
15653 gi = CreateGadget(GDI_CUSTOM_ID, id,
15654 GDI_IMAGE_ID, graphic,
15655 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15658 GDI_WIDTH, gfx->width,
15659 GDI_HEIGHT, gfx->height,
15660 GDI_TYPE, button_type,
15661 GDI_STATE, GD_BUTTON_UNPRESSED,
15662 GDI_CHECKED, checked,
15663 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15664 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15665 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15666 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15667 GDI_DIRECT_DRAW, FALSE,
15668 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15669 GDI_EVENT_MASK, event_mask,
15670 GDI_CALLBACK_ACTION, HandleGameButtons,
15674 Error(ERR_EXIT, "cannot create gadget");
15676 game_gadget[id] = gi;
15680 void FreeGameButtons(void)
15684 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15685 FreeGadget(game_gadget[i]);
15688 static void UnmapGameButtonsAtSamePosition(int id)
15692 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15694 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15695 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15696 UnmapGadget(game_gadget[i]);
15699 static void UnmapGameButtonsAtSamePosition_All(void)
15701 if (setup.show_snapshot_buttons)
15703 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15704 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15705 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15709 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15710 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15711 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15713 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15714 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15715 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15719 static void MapGameButtonsAtSamePosition(int id)
15723 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15725 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15726 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15727 MapGadget(game_gadget[i]);
15729 UnmapGameButtonsAtSamePosition_All();
15732 void MapUndoRedoButtons(void)
15734 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15735 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15737 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15738 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15741 void UnmapUndoRedoButtons(void)
15743 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15744 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15746 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15747 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15750 void ModifyPauseButtons(void)
15754 GAME_CTRL_ID_PAUSE,
15755 GAME_CTRL_ID_PAUSE2,
15756 GAME_CTRL_ID_PANEL_PAUSE,
15757 GAME_CTRL_ID_TOUCH_PAUSE,
15762 for (i = 0; ids[i] > -1; i++)
15763 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15766 static void MapGameButtonsExt(boolean on_tape)
15770 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15771 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15772 i != GAME_CTRL_ID_UNDO &&
15773 i != GAME_CTRL_ID_REDO)
15774 MapGadget(game_gadget[i]);
15776 UnmapGameButtonsAtSamePosition_All();
15778 RedrawGameButtons();
15781 static void UnmapGameButtonsExt(boolean on_tape)
15785 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15786 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15787 UnmapGadget(game_gadget[i]);
15790 static void RedrawGameButtonsExt(boolean on_tape)
15794 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15795 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15796 RedrawGadget(game_gadget[i]);
15799 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15804 gi->checked = state;
15807 static void RedrawSoundButtonGadget(int id)
15809 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15810 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15811 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15812 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15813 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15814 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15817 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15818 RedrawGadget(game_gadget[id2]);
15821 void MapGameButtons(void)
15823 MapGameButtonsExt(FALSE);
15826 void UnmapGameButtons(void)
15828 UnmapGameButtonsExt(FALSE);
15831 void RedrawGameButtons(void)
15833 RedrawGameButtonsExt(FALSE);
15836 void MapGameButtonsOnTape(void)
15838 MapGameButtonsExt(TRUE);
15841 void UnmapGameButtonsOnTape(void)
15843 UnmapGameButtonsExt(TRUE);
15846 void RedrawGameButtonsOnTape(void)
15848 RedrawGameButtonsExt(TRUE);
15851 static void GameUndoRedoExt(void)
15853 ClearPlayerAction();
15855 tape.pausing = TRUE;
15858 UpdateAndDisplayGameControlValues();
15860 DrawCompleteVideoDisplay();
15861 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
15862 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
15863 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
15868 static void GameUndo(int steps)
15870 if (!CheckEngineSnapshotList())
15873 LoadEngineSnapshot_Undo(steps);
15878 static void GameRedo(int steps)
15880 if (!CheckEngineSnapshotList())
15883 LoadEngineSnapshot_Redo(steps);
15888 static void HandleGameButtonsExt(int id, int button)
15890 static boolean game_undo_executed = FALSE;
15891 int steps = BUTTON_STEPSIZE(button);
15892 boolean handle_game_buttons =
15893 (game_status == GAME_MODE_PLAYING ||
15894 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
15896 if (!handle_game_buttons)
15901 case GAME_CTRL_ID_STOP:
15902 case GAME_CTRL_ID_PANEL_STOP:
15903 case GAME_CTRL_ID_TOUCH_STOP:
15904 if (game_status == GAME_MODE_MAIN)
15910 RequestQuitGame(TRUE);
15914 case GAME_CTRL_ID_PAUSE:
15915 case GAME_CTRL_ID_PAUSE2:
15916 case GAME_CTRL_ID_PANEL_PAUSE:
15917 case GAME_CTRL_ID_TOUCH_PAUSE:
15918 if (network.enabled && game_status == GAME_MODE_PLAYING)
15921 SendToServer_ContinuePlaying();
15923 SendToServer_PausePlaying();
15926 TapeTogglePause(TAPE_TOGGLE_MANUAL);
15928 game_undo_executed = FALSE;
15932 case GAME_CTRL_ID_PLAY:
15933 case GAME_CTRL_ID_PANEL_PLAY:
15934 if (game_status == GAME_MODE_MAIN)
15936 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15938 else if (tape.pausing)
15940 if (network.enabled)
15941 SendToServer_ContinuePlaying();
15943 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
15947 case GAME_CTRL_ID_UNDO:
15948 // Important: When using "save snapshot when collecting an item" mode,
15949 // load last (current) snapshot for first "undo" after pressing "pause"
15950 // (else the last-but-one snapshot would be loaded, because the snapshot
15951 // pointer already points to the last snapshot when pressing "pause",
15952 // which is fine for "every step/move" mode, but not for "every collect")
15953 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15954 !game_undo_executed)
15957 game_undo_executed = TRUE;
15962 case GAME_CTRL_ID_REDO:
15966 case GAME_CTRL_ID_SAVE:
15970 case GAME_CTRL_ID_LOAD:
15974 case SOUND_CTRL_ID_MUSIC:
15975 case SOUND_CTRL_ID_PANEL_MUSIC:
15976 if (setup.sound_music)
15978 setup.sound_music = FALSE;
15982 else if (audio.music_available)
15984 setup.sound = setup.sound_music = TRUE;
15986 SetAudioMode(setup.sound);
15988 if (game_status == GAME_MODE_PLAYING)
15992 RedrawSoundButtonGadget(id);
15996 case SOUND_CTRL_ID_LOOPS:
15997 case SOUND_CTRL_ID_PANEL_LOOPS:
15998 if (setup.sound_loops)
15999 setup.sound_loops = FALSE;
16000 else if (audio.loops_available)
16002 setup.sound = setup.sound_loops = TRUE;
16004 SetAudioMode(setup.sound);
16007 RedrawSoundButtonGadget(id);
16011 case SOUND_CTRL_ID_SIMPLE:
16012 case SOUND_CTRL_ID_PANEL_SIMPLE:
16013 if (setup.sound_simple)
16014 setup.sound_simple = FALSE;
16015 else if (audio.sound_available)
16017 setup.sound = setup.sound_simple = TRUE;
16019 SetAudioMode(setup.sound);
16022 RedrawSoundButtonGadget(id);
16031 static void HandleGameButtons(struct GadgetInfo *gi)
16033 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16036 void HandleSoundButtonKeys(Key key)
16038 if (key == setup.shortcut.sound_simple)
16039 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16040 else if (key == setup.shortcut.sound_loops)
16041 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16042 else if (key == setup.shortcut.sound_music)
16043 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);