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_state)
1967 Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1970 case EL_EMC_MAGIC_BALL_SWITCH:
1971 if (game.ball_state)
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_state ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2370 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2371 (game.ball_state ? 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 setup.scroll_delay ? setup.scroll_delay_value : 0);
3259 game.scroll_delay_value =
3260 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3262 // ---------- initialize game engine snapshots ------------------------------
3263 for (i = 0; i < MAX_PLAYERS; i++)
3264 game.snapshot.last_action[i] = 0;
3265 game.snapshot.changed_action = FALSE;
3266 game.snapshot.collected_item = FALSE;
3267 game.snapshot.mode =
3268 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3269 SNAPSHOT_MODE_EVERY_STEP :
3270 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3271 SNAPSHOT_MODE_EVERY_MOVE :
3272 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3273 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3274 game.snapshot.save_snapshot = FALSE;
3276 // ---------- initialize level time for Supaplex engine ---------------------
3277 // Supaplex levels with time limit currently unsupported -- should be added
3278 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3282 static int get_num_special_action(int element, int action_first,
3285 int num_special_action = 0;
3288 for (i = action_first; i <= action_last; i++)
3290 boolean found = FALSE;
3292 for (j = 0; j < NUM_DIRECTIONS; j++)
3293 if (el_act_dir2img(element, i, j) !=
3294 el_act_dir2img(element, ACTION_DEFAULT, j))
3298 num_special_action++;
3303 return num_special_action;
3307 // ============================================================================
3309 // ----------------------------------------------------------------------------
3310 // initialize and start new game
3311 // ============================================================================
3313 #if DEBUG_INIT_PLAYER
3314 static void DebugPrintPlayerStatus(char *message)
3321 printf("%s:\n", message);
3323 for (i = 0; i < MAX_PLAYERS; i++)
3325 struct PlayerInfo *player = &stored_player[i];
3327 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3331 player->connected_locally,
3332 player->connected_network,
3335 if (local_player == player)
3336 printf(" (local player)");
3345 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3346 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3347 int fade_mask = REDRAW_FIELD;
3349 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3350 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3351 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3352 int initial_move_dir = MV_DOWN;
3355 // required here to update video display before fading (FIX THIS)
3356 DrawMaskedBorder(REDRAW_DOOR_2);
3358 if (!game.restart_level)
3359 CloseDoor(DOOR_CLOSE_1);
3361 SetGameStatus(GAME_MODE_PLAYING);
3363 if (level_editor_test_game)
3364 FadeSkipNextFadeOut();
3366 FadeSetEnterScreen();
3369 fade_mask = REDRAW_ALL;
3371 FadeLevelSoundsAndMusic();
3373 ExpireSoundLoops(TRUE);
3377 if (level_editor_test_game)
3378 FadeSkipNextFadeIn();
3380 // needed if different viewport properties defined for playing
3381 ChangeViewportPropertiesIfNeeded();
3385 DrawCompleteVideoDisplay();
3387 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3390 InitGameControlValues();
3392 // don't play tapes over network
3393 network_playing = (network.enabled && !tape.playing);
3395 for (i = 0; i < MAX_PLAYERS; i++)
3397 struct PlayerInfo *player = &stored_player[i];
3399 player->index_nr = i;
3400 player->index_bit = (1 << i);
3401 player->element_nr = EL_PLAYER_1 + i;
3403 player->present = FALSE;
3404 player->active = FALSE;
3405 player->mapped = FALSE;
3407 player->killed = FALSE;
3408 player->reanimated = FALSE;
3409 player->buried = FALSE;
3412 player->effective_action = 0;
3413 player->programmed_action = 0;
3414 player->snap_action = 0;
3416 player->mouse_action.lx = 0;
3417 player->mouse_action.ly = 0;
3418 player->mouse_action.button = 0;
3419 player->mouse_action.button_hint = 0;
3421 player->effective_mouse_action.lx = 0;
3422 player->effective_mouse_action.ly = 0;
3423 player->effective_mouse_action.button = 0;
3424 player->effective_mouse_action.button_hint = 0;
3426 for (j = 0; j < MAX_NUM_KEYS; j++)
3427 player->key[j] = FALSE;
3429 player->num_white_keys = 0;
3431 player->dynabomb_count = 0;
3432 player->dynabomb_size = 1;
3433 player->dynabombs_left = 0;
3434 player->dynabomb_xl = FALSE;
3436 player->MovDir = initial_move_dir;
3439 player->GfxDir = initial_move_dir;
3440 player->GfxAction = ACTION_DEFAULT;
3442 player->StepFrame = 0;
3444 player->initial_element = player->element_nr;
3445 player->artwork_element =
3446 (level.use_artwork_element[i] ? level.artwork_element[i] :
3447 player->element_nr);
3448 player->use_murphy = FALSE;
3450 player->block_last_field = FALSE; // initialized in InitPlayerField()
3451 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3453 player->gravity = level.initial_player_gravity[i];
3455 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3457 player->actual_frame_counter = 0;
3459 player->step_counter = 0;
3461 player->last_move_dir = initial_move_dir;
3463 player->is_active = FALSE;
3465 player->is_waiting = FALSE;
3466 player->is_moving = FALSE;
3467 player->is_auto_moving = FALSE;
3468 player->is_digging = FALSE;
3469 player->is_snapping = FALSE;
3470 player->is_collecting = FALSE;
3471 player->is_pushing = FALSE;
3472 player->is_switching = FALSE;
3473 player->is_dropping = FALSE;
3474 player->is_dropping_pressed = FALSE;
3476 player->is_bored = FALSE;
3477 player->is_sleeping = FALSE;
3479 player->was_waiting = TRUE;
3480 player->was_moving = FALSE;
3481 player->was_snapping = FALSE;
3482 player->was_dropping = FALSE;
3484 player->force_dropping = FALSE;
3486 player->frame_counter_bored = -1;
3487 player->frame_counter_sleeping = -1;
3489 player->anim_delay_counter = 0;
3490 player->post_delay_counter = 0;
3492 player->dir_waiting = initial_move_dir;
3493 player->action_waiting = ACTION_DEFAULT;
3494 player->last_action_waiting = ACTION_DEFAULT;
3495 player->special_action_bored = ACTION_DEFAULT;
3496 player->special_action_sleeping = ACTION_DEFAULT;
3498 player->switch_x = -1;
3499 player->switch_y = -1;
3501 player->drop_x = -1;
3502 player->drop_y = -1;
3504 player->show_envelope = 0;
3506 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3508 player->push_delay = -1; // initialized when pushing starts
3509 player->push_delay_value = game.initial_push_delay_value;
3511 player->drop_delay = 0;
3512 player->drop_pressed_delay = 0;
3514 player->last_jx = -1;
3515 player->last_jy = -1;
3519 player->shield_normal_time_left = 0;
3520 player->shield_deadly_time_left = 0;
3522 player->inventory_infinite_element = EL_UNDEFINED;
3523 player->inventory_size = 0;
3525 if (level.use_initial_inventory[i])
3527 for (j = 0; j < level.initial_inventory_size[i]; j++)
3529 int element = level.initial_inventory_content[i][j];
3530 int collect_count = element_info[element].collect_count_initial;
3533 if (!IS_CUSTOM_ELEMENT(element))
3536 if (collect_count == 0)
3537 player->inventory_infinite_element = element;
3539 for (k = 0; k < collect_count; k++)
3540 if (player->inventory_size < MAX_INVENTORY_SIZE)
3541 player->inventory_element[player->inventory_size++] = element;
3545 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3546 SnapField(player, 0, 0);
3548 map_player_action[i] = i;
3551 network_player_action_received = FALSE;
3553 // initial null action
3554 if (network_playing)
3555 SendToServer_MovePlayer(MV_NONE);
3560 TimeLeft = level.time;
3563 ScreenMovDir = MV_NONE;
3567 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3569 game.robot_wheel_x = -1;
3570 game.robot_wheel_y = -1;
3575 game.all_players_gone = FALSE;
3577 game.LevelSolved = FALSE;
3578 game.GameOver = FALSE;
3580 game.GamePlayed = !tape.playing;
3582 game.LevelSolved_GameWon = FALSE;
3583 game.LevelSolved_GameEnd = FALSE;
3584 game.LevelSolved_SaveTape = FALSE;
3585 game.LevelSolved_SaveScore = FALSE;
3587 game.LevelSolved_CountingTime = 0;
3588 game.LevelSolved_CountingScore = 0;
3589 game.LevelSolved_CountingHealth = 0;
3591 game.panel.active = TRUE;
3593 game.no_time_limit = (level.time == 0);
3595 game.yamyam_content_nr = 0;
3596 game.robot_wheel_active = FALSE;
3597 game.magic_wall_active = FALSE;
3598 game.magic_wall_time_left = 0;
3599 game.light_time_left = 0;
3600 game.timegate_time_left = 0;
3601 game.switchgate_pos = 0;
3602 game.wind_direction = level.wind_direction_initial;
3605 game.score_final = 0;
3607 game.health = MAX_HEALTH;
3608 game.health_final = MAX_HEALTH;
3610 game.gems_still_needed = level.gems_needed;
3611 game.sokoban_fields_still_needed = 0;
3612 game.sokoban_objects_still_needed = 0;
3613 game.lights_still_needed = 0;
3614 game.players_still_needed = 0;
3615 game.friends_still_needed = 0;
3617 game.lenses_time_left = 0;
3618 game.magnify_time_left = 0;
3620 game.ball_state = level.ball_state_initial;
3621 game.ball_content_nr = 0;
3623 game.explosions_delayed = TRUE;
3625 game.envelope_active = FALSE;
3627 for (i = 0; i < NUM_BELTS; i++)
3629 game.belt_dir[i] = MV_NONE;
3630 game.belt_dir_nr[i] = 3; // not moving, next moving left
3633 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3634 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3636 #if DEBUG_INIT_PLAYER
3637 DebugPrintPlayerStatus("Player status at level initialization");
3640 SCAN_PLAYFIELD(x, y)
3642 Feld[x][y] = Last[x][y] = level.field[x][y];
3643 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3644 ChangeDelay[x][y] = 0;
3645 ChangePage[x][y] = -1;
3646 CustomValue[x][y] = 0; // initialized in InitField()
3647 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3649 WasJustMoving[x][y] = 0;
3650 WasJustFalling[x][y] = 0;
3651 CheckCollision[x][y] = 0;
3652 CheckImpact[x][y] = 0;
3654 Pushed[x][y] = FALSE;
3656 ChangeCount[x][y] = 0;
3657 ChangeEvent[x][y] = -1;
3659 ExplodePhase[x][y] = 0;
3660 ExplodeDelay[x][y] = 0;
3661 ExplodeField[x][y] = EX_TYPE_NONE;
3663 RunnerVisit[x][y] = 0;
3664 PlayerVisit[x][y] = 0;
3667 GfxRandom[x][y] = INIT_GFX_RANDOM();
3668 GfxElement[x][y] = EL_UNDEFINED;
3669 GfxAction[x][y] = ACTION_DEFAULT;
3670 GfxDir[x][y] = MV_NONE;
3671 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3674 SCAN_PLAYFIELD(x, y)
3676 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3678 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3680 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3683 InitField(x, y, TRUE);
3685 ResetGfxAnimation(x, y);
3690 for (i = 0; i < MAX_PLAYERS; i++)
3692 struct PlayerInfo *player = &stored_player[i];
3694 // set number of special actions for bored and sleeping animation
3695 player->num_special_action_bored =
3696 get_num_special_action(player->artwork_element,
3697 ACTION_BORING_1, ACTION_BORING_LAST);
3698 player->num_special_action_sleeping =
3699 get_num_special_action(player->artwork_element,
3700 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3703 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3704 emulate_sb ? EMU_SOKOBAN :
3705 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3707 // initialize type of slippery elements
3708 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3710 if (!IS_CUSTOM_ELEMENT(i))
3712 // default: elements slip down either to the left or right randomly
3713 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3715 // SP style elements prefer to slip down on the left side
3716 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3717 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3719 // BD style elements prefer to slip down on the left side
3720 if (game.emulation == EMU_BOULDERDASH)
3721 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3725 // initialize explosion and ignition delay
3726 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3728 if (!IS_CUSTOM_ELEMENT(i))
3731 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3732 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3733 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3734 int last_phase = (num_phase + 1) * delay;
3735 int half_phase = (num_phase / 2) * delay;
3737 element_info[i].explosion_delay = last_phase - 1;
3738 element_info[i].ignition_delay = half_phase;
3740 if (i == EL_BLACK_ORB)
3741 element_info[i].ignition_delay = 1;
3745 // correct non-moving belts to start moving left
3746 for (i = 0; i < NUM_BELTS; i++)
3747 if (game.belt_dir[i] == MV_NONE)
3748 game.belt_dir_nr[i] = 3; // not moving, next moving left
3750 #if USE_NEW_PLAYER_ASSIGNMENTS
3751 // use preferred player also in local single-player mode
3752 if (!network.enabled && !game.team_mode)
3754 int old_index_nr = local_player->index_nr;
3755 int new_index_nr = setup.network_player_nr;
3757 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3759 stored_player[old_index_nr].connected_locally = FALSE;
3760 stored_player[new_index_nr].connected_locally = TRUE;
3764 for (i = 0; i < MAX_PLAYERS; i++)
3766 stored_player[i].connected = FALSE;
3768 // in network game mode, the local player might not be the first player
3769 if (stored_player[i].connected_locally)
3770 local_player = &stored_player[i];
3773 if (!network.enabled)
3774 local_player->connected = TRUE;
3778 for (i = 0; i < MAX_PLAYERS; i++)
3779 stored_player[i].connected = tape.player_participates[i];
3781 else if (network.enabled)
3783 // add team mode players connected over the network (needed for correct
3784 // assignment of player figures from level to locally playing players)
3786 for (i = 0; i < MAX_PLAYERS; i++)
3787 if (stored_player[i].connected_network)
3788 stored_player[i].connected = TRUE;
3790 else if (game.team_mode)
3792 // try to guess locally connected team mode players (needed for correct
3793 // assignment of player figures from level to locally playing players)
3795 for (i = 0; i < MAX_PLAYERS; i++)
3796 if (setup.input[i].use_joystick ||
3797 setup.input[i].key.left != KSYM_UNDEFINED)
3798 stored_player[i].connected = TRUE;
3801 #if DEBUG_INIT_PLAYER
3802 DebugPrintPlayerStatus("Player status after level initialization");
3805 #if DEBUG_INIT_PLAYER
3807 printf("Reassigning players ...\n");
3810 // check if any connected player was not found in playfield
3811 for (i = 0; i < MAX_PLAYERS; i++)
3813 struct PlayerInfo *player = &stored_player[i];
3815 if (player->connected && !player->present)
3817 struct PlayerInfo *field_player = NULL;
3819 #if DEBUG_INIT_PLAYER
3821 printf("- looking for field player for player %d ...\n", i + 1);
3824 // assign first free player found that is present in the playfield
3826 // first try: look for unmapped playfield player that is not connected
3827 for (j = 0; j < MAX_PLAYERS; j++)
3828 if (field_player == NULL &&
3829 stored_player[j].present &&
3830 !stored_player[j].mapped &&
3831 !stored_player[j].connected)
3832 field_player = &stored_player[j];
3834 // second try: look for *any* unmapped playfield player
3835 for (j = 0; j < MAX_PLAYERS; j++)
3836 if (field_player == NULL &&
3837 stored_player[j].present &&
3838 !stored_player[j].mapped)
3839 field_player = &stored_player[j];
3841 if (field_player != NULL)
3843 int jx = field_player->jx, jy = field_player->jy;
3845 #if DEBUG_INIT_PLAYER
3847 printf("- found player %d\n", field_player->index_nr + 1);
3850 player->present = FALSE;
3851 player->active = FALSE;
3853 field_player->present = TRUE;
3854 field_player->active = TRUE;
3857 player->initial_element = field_player->initial_element;
3858 player->artwork_element = field_player->artwork_element;
3860 player->block_last_field = field_player->block_last_field;
3861 player->block_delay_adjustment = field_player->block_delay_adjustment;
3864 StorePlayer[jx][jy] = field_player->element_nr;
3866 field_player->jx = field_player->last_jx = jx;
3867 field_player->jy = field_player->last_jy = jy;
3869 if (local_player == player)
3870 local_player = field_player;
3872 map_player_action[field_player->index_nr] = i;
3874 field_player->mapped = TRUE;
3876 #if DEBUG_INIT_PLAYER
3878 printf("- map_player_action[%d] == %d\n",
3879 field_player->index_nr + 1, i + 1);
3884 if (player->connected && player->present)
3885 player->mapped = TRUE;
3888 #if DEBUG_INIT_PLAYER
3889 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
3894 // check if any connected player was not found in playfield
3895 for (i = 0; i < MAX_PLAYERS; i++)
3897 struct PlayerInfo *player = &stored_player[i];
3899 if (player->connected && !player->present)
3901 for (j = 0; j < MAX_PLAYERS; j++)
3903 struct PlayerInfo *field_player = &stored_player[j];
3904 int jx = field_player->jx, jy = field_player->jy;
3906 // assign first free player found that is present in the playfield
3907 if (field_player->present && !field_player->connected)
3909 player->present = TRUE;
3910 player->active = TRUE;
3912 field_player->present = FALSE;
3913 field_player->active = FALSE;
3915 player->initial_element = field_player->initial_element;
3916 player->artwork_element = field_player->artwork_element;
3918 player->block_last_field = field_player->block_last_field;
3919 player->block_delay_adjustment = field_player->block_delay_adjustment;
3921 StorePlayer[jx][jy] = player->element_nr;
3923 player->jx = player->last_jx = jx;
3924 player->jy = player->last_jy = jy;
3934 printf("::: local_player->present == %d\n", local_player->present);
3937 // set focus to local player for network games, else to all players
3938 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
3939 game.centered_player_nr_next = game.centered_player_nr;
3940 game.set_centered_player = FALSE;
3941 game.set_centered_player_fast = FALSE;
3943 if (network_playing && tape.recording)
3945 // store client dependent player focus when recording network games
3946 tape.centered_player_nr_next = game.centered_player_nr_next;
3947 tape.set_centered_player = TRUE;
3952 // when playing a tape, eliminate all players who do not participate
3954 #if USE_NEW_PLAYER_ASSIGNMENTS
3956 if (!game.team_mode)
3958 for (i = 0; i < MAX_PLAYERS; i++)
3960 if (stored_player[i].active &&
3961 !tape.player_participates[map_player_action[i]])
3963 struct PlayerInfo *player = &stored_player[i];
3964 int jx = player->jx, jy = player->jy;
3966 #if DEBUG_INIT_PLAYER
3968 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
3971 player->active = FALSE;
3972 StorePlayer[jx][jy] = 0;
3973 Feld[jx][jy] = EL_EMPTY;
3980 for (i = 0; i < MAX_PLAYERS; i++)
3982 if (stored_player[i].active &&
3983 !tape.player_participates[i])
3985 struct PlayerInfo *player = &stored_player[i];
3986 int jx = player->jx, jy = player->jy;
3988 player->active = FALSE;
3989 StorePlayer[jx][jy] = 0;
3990 Feld[jx][jy] = EL_EMPTY;
3995 else if (!network.enabled && !game.team_mode) // && !tape.playing
3997 // when in single player mode, eliminate all but the local player
3999 for (i = 0; i < MAX_PLAYERS; i++)
4001 struct PlayerInfo *player = &stored_player[i];
4003 if (player->active && player != local_player)
4005 int jx = player->jx, jy = player->jy;
4007 player->active = FALSE;
4008 player->present = FALSE;
4010 StorePlayer[jx][jy] = 0;
4011 Feld[jx][jy] = EL_EMPTY;
4016 for (i = 0; i < MAX_PLAYERS; i++)
4017 if (stored_player[i].active)
4018 game.players_still_needed++;
4020 if (level.solved_by_one_player)
4021 game.players_still_needed = 1;
4023 // when recording the game, store which players take part in the game
4026 #if USE_NEW_PLAYER_ASSIGNMENTS
4027 for (i = 0; i < MAX_PLAYERS; i++)
4028 if (stored_player[i].connected)
4029 tape.player_participates[i] = TRUE;
4031 for (i = 0; i < MAX_PLAYERS; i++)
4032 if (stored_player[i].active)
4033 tape.player_participates[i] = TRUE;
4037 #if DEBUG_INIT_PLAYER
4038 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4041 if (BorderElement == EL_EMPTY)
4044 SBX_Right = lev_fieldx - SCR_FIELDX;
4046 SBY_Lower = lev_fieldy - SCR_FIELDY;
4051 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4053 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4056 if (full_lev_fieldx <= SCR_FIELDX)
4057 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4058 if (full_lev_fieldy <= SCR_FIELDY)
4059 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4061 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4063 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4066 // if local player not found, look for custom element that might create
4067 // the player (make some assumptions about the right custom element)
4068 if (!local_player->present)
4070 int start_x = 0, start_y = 0;
4071 int found_rating = 0;
4072 int found_element = EL_UNDEFINED;
4073 int player_nr = local_player->index_nr;
4075 SCAN_PLAYFIELD(x, y)
4077 int element = Feld[x][y];
4082 if (level.use_start_element[player_nr] &&
4083 level.start_element[player_nr] == element &&
4090 found_element = element;
4093 if (!IS_CUSTOM_ELEMENT(element))
4096 if (CAN_CHANGE(element))
4098 for (i = 0; i < element_info[element].num_change_pages; i++)
4100 // check for player created from custom element as single target
4101 content = element_info[element].change_page[i].target_element;
4102 is_player = ELEM_IS_PLAYER(content);
4104 if (is_player && (found_rating < 3 ||
4105 (found_rating == 3 && element < found_element)))
4111 found_element = element;
4116 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4118 // check for player created from custom element as explosion content
4119 content = element_info[element].content.e[xx][yy];
4120 is_player = ELEM_IS_PLAYER(content);
4122 if (is_player && (found_rating < 2 ||
4123 (found_rating == 2 && element < found_element)))
4125 start_x = x + xx - 1;
4126 start_y = y + yy - 1;
4129 found_element = element;
4132 if (!CAN_CHANGE(element))
4135 for (i = 0; i < element_info[element].num_change_pages; i++)
4137 // check for player created from custom element as extended target
4139 element_info[element].change_page[i].target_content.e[xx][yy];
4141 is_player = ELEM_IS_PLAYER(content);
4143 if (is_player && (found_rating < 1 ||
4144 (found_rating == 1 && element < found_element)))
4146 start_x = x + xx - 1;
4147 start_y = y + yy - 1;
4150 found_element = element;
4156 scroll_x = SCROLL_POSITION_X(start_x);
4157 scroll_y = SCROLL_POSITION_Y(start_y);
4161 scroll_x = SCROLL_POSITION_X(local_player->jx);
4162 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4165 // !!! FIX THIS (START) !!!
4166 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4168 InitGameEngine_EM();
4170 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4172 InitGameEngine_SP();
4174 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4176 InitGameEngine_MM();
4180 DrawLevel(REDRAW_FIELD);
4183 // after drawing the level, correct some elements
4184 if (game.timegate_time_left == 0)
4185 CloseAllOpenTimegates();
4188 // blit playfield from scroll buffer to normal back buffer for fading in
4189 BlitScreenToBitmap(backbuffer);
4190 // !!! FIX THIS (END) !!!
4192 DrawMaskedBorder(fade_mask);
4197 // full screen redraw is required at this point in the following cases:
4198 // - special editor door undrawn when game was started from level editor
4199 // - drawing area (playfield) was changed and has to be removed completely
4200 redraw_mask = REDRAW_ALL;
4204 if (!game.restart_level)
4206 // copy default game door content to main double buffer
4208 // !!! CHECK AGAIN !!!
4209 SetPanelBackground();
4210 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4211 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4214 SetPanelBackground();
4215 SetDrawBackgroundMask(REDRAW_DOOR_1);
4217 UpdateAndDisplayGameControlValues();
4219 if (!game.restart_level)
4225 CreateGameButtons();
4230 // copy actual game door content to door double buffer for OpenDoor()
4231 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4233 OpenDoor(DOOR_OPEN_ALL);
4235 KeyboardAutoRepeatOffUnlessAutoplay();
4237 #if DEBUG_INIT_PLAYER
4238 DebugPrintPlayerStatus("Player status (final)");
4247 if (!game.restart_level && !tape.playing)
4249 LevelStats_incPlayed(level_nr);
4251 SaveLevelSetup_SeriesInfo();
4254 game.restart_level = FALSE;
4255 game.restart_game_message = NULL;
4256 game.request_active = FALSE;
4258 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4259 InitGameActions_MM();
4261 SaveEngineSnapshotToListInitial();
4263 if (!game.restart_level)
4265 PlaySound(SND_GAME_STARTING);
4267 if (setup.sound_music)
4272 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4273 int actual_player_x, int actual_player_y)
4275 // this is used for non-R'n'D game engines to update certain engine values
4277 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4279 actual_player_x = correctLevelPosX_EM(actual_player_x);
4280 actual_player_y = correctLevelPosY_EM(actual_player_y);
4283 // needed to determine if sounds are played within the visible screen area
4284 scroll_x = actual_scroll_x;
4285 scroll_y = actual_scroll_y;
4287 // needed to get player position for "follow finger" playing input method
4288 local_player->jx = actual_player_x;
4289 local_player->jy = actual_player_y;
4292 void InitMovDir(int x, int y)
4294 int i, element = Feld[x][y];
4295 static int xy[4][2] =
4302 static int direction[3][4] =
4304 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4305 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4306 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4315 Feld[x][y] = EL_BUG;
4316 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4319 case EL_SPACESHIP_RIGHT:
4320 case EL_SPACESHIP_UP:
4321 case EL_SPACESHIP_LEFT:
4322 case EL_SPACESHIP_DOWN:
4323 Feld[x][y] = EL_SPACESHIP;
4324 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4327 case EL_BD_BUTTERFLY_RIGHT:
4328 case EL_BD_BUTTERFLY_UP:
4329 case EL_BD_BUTTERFLY_LEFT:
4330 case EL_BD_BUTTERFLY_DOWN:
4331 Feld[x][y] = EL_BD_BUTTERFLY;
4332 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4335 case EL_BD_FIREFLY_RIGHT:
4336 case EL_BD_FIREFLY_UP:
4337 case EL_BD_FIREFLY_LEFT:
4338 case EL_BD_FIREFLY_DOWN:
4339 Feld[x][y] = EL_BD_FIREFLY;
4340 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4343 case EL_PACMAN_RIGHT:
4345 case EL_PACMAN_LEFT:
4346 case EL_PACMAN_DOWN:
4347 Feld[x][y] = EL_PACMAN;
4348 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4351 case EL_YAMYAM_LEFT:
4352 case EL_YAMYAM_RIGHT:
4354 case EL_YAMYAM_DOWN:
4355 Feld[x][y] = EL_YAMYAM;
4356 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4359 case EL_SP_SNIKSNAK:
4360 MovDir[x][y] = MV_UP;
4363 case EL_SP_ELECTRON:
4364 MovDir[x][y] = MV_LEFT;
4371 Feld[x][y] = EL_MOLE;
4372 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4376 if (IS_CUSTOM_ELEMENT(element))
4378 struct ElementInfo *ei = &element_info[element];
4379 int move_direction_initial = ei->move_direction_initial;
4380 int move_pattern = ei->move_pattern;
4382 if (move_direction_initial == MV_START_PREVIOUS)
4384 if (MovDir[x][y] != MV_NONE)
4387 move_direction_initial = MV_START_AUTOMATIC;
4390 if (move_direction_initial == MV_START_RANDOM)
4391 MovDir[x][y] = 1 << RND(4);
4392 else if (move_direction_initial & MV_ANY_DIRECTION)
4393 MovDir[x][y] = move_direction_initial;
4394 else if (move_pattern == MV_ALL_DIRECTIONS ||
4395 move_pattern == MV_TURNING_LEFT ||
4396 move_pattern == MV_TURNING_RIGHT ||
4397 move_pattern == MV_TURNING_LEFT_RIGHT ||
4398 move_pattern == MV_TURNING_RIGHT_LEFT ||
4399 move_pattern == MV_TURNING_RANDOM)
4400 MovDir[x][y] = 1 << RND(4);
4401 else if (move_pattern == MV_HORIZONTAL)
4402 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4403 else if (move_pattern == MV_VERTICAL)
4404 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4405 else if (move_pattern & MV_ANY_DIRECTION)
4406 MovDir[x][y] = element_info[element].move_pattern;
4407 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4408 move_pattern == MV_ALONG_RIGHT_SIDE)
4410 // use random direction as default start direction
4411 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4412 MovDir[x][y] = 1 << RND(4);
4414 for (i = 0; i < NUM_DIRECTIONS; i++)
4416 int x1 = x + xy[i][0];
4417 int y1 = y + xy[i][1];
4419 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4421 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4422 MovDir[x][y] = direction[0][i];
4424 MovDir[x][y] = direction[1][i];
4433 MovDir[x][y] = 1 << RND(4);
4435 if (element != EL_BUG &&
4436 element != EL_SPACESHIP &&
4437 element != EL_BD_BUTTERFLY &&
4438 element != EL_BD_FIREFLY)
4441 for (i = 0; i < NUM_DIRECTIONS; i++)
4443 int x1 = x + xy[i][0];
4444 int y1 = y + xy[i][1];
4446 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4448 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4450 MovDir[x][y] = direction[0][i];
4453 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4454 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4456 MovDir[x][y] = direction[1][i];
4465 GfxDir[x][y] = MovDir[x][y];
4468 void InitAmoebaNr(int x, int y)
4471 int group_nr = AmoebeNachbarNr(x, y);
4475 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4477 if (AmoebaCnt[i] == 0)
4485 AmoebaNr[x][y] = group_nr;
4486 AmoebaCnt[group_nr]++;
4487 AmoebaCnt2[group_nr]++;
4490 static void LevelSolved(void)
4492 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4493 game.players_still_needed > 0)
4496 game.LevelSolved = TRUE;
4497 game.GameOver = TRUE;
4499 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4500 game_em.lev->score :
4501 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4504 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4505 MM_HEALTH(game_mm.laser_overload_value) :
4508 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4509 game.LevelSolved_CountingScore = game.score_final;
4510 game.LevelSolved_CountingHealth = game.health_final;
4515 static int time_count_steps;
4516 static int time, time_final;
4517 static int score, score_final;
4518 static int health, health_final;
4519 static int game_over_delay_1 = 0;
4520 static int game_over_delay_2 = 0;
4521 static int game_over_delay_3 = 0;
4522 int game_over_delay_value_1 = 50;
4523 int game_over_delay_value_2 = 25;
4524 int game_over_delay_value_3 = 50;
4526 if (!game.LevelSolved_GameWon)
4530 // do not start end game actions before the player stops moving (to exit)
4531 if (local_player->active && local_player->MovPos)
4534 game.LevelSolved_GameWon = TRUE;
4535 game.LevelSolved_SaveTape = tape.recording;
4536 game.LevelSolved_SaveScore = !tape.playing;
4540 LevelStats_incSolved(level_nr);
4542 SaveLevelSetup_SeriesInfo();
4545 if (tape.auto_play) // tape might already be stopped here
4546 tape.auto_play_level_solved = TRUE;
4550 game_over_delay_1 = 0;
4551 game_over_delay_2 = 0;
4552 game_over_delay_3 = game_over_delay_value_3;
4554 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4555 score = score_final = game.score_final;
4556 health = health_final = game.health_final;
4558 if (level.score[SC_TIME_BONUS] > 0)
4563 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4565 else if (game.no_time_limit && TimePlayed < 999)
4568 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4571 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4573 game_over_delay_1 = game_over_delay_value_1;
4575 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4578 score_final += health * level.score[SC_TIME_BONUS];
4580 game_over_delay_2 = game_over_delay_value_2;
4583 game.score_final = score_final;
4584 game.health_final = health_final;
4587 if (level_editor_test_game)
4590 score = score_final;
4592 game.LevelSolved_CountingTime = time;
4593 game.LevelSolved_CountingScore = score;
4595 game_panel_controls[GAME_PANEL_TIME].value = time;
4596 game_panel_controls[GAME_PANEL_SCORE].value = score;
4598 DisplayGameControlValues();
4601 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4603 // check if last player has left the level
4604 if (game.exit_x >= 0 &&
4607 int x = game.exit_x;
4608 int y = game.exit_y;
4609 int element = Feld[x][y];
4611 // close exit door after last player
4612 if ((game.all_players_gone &&
4613 (element == EL_EXIT_OPEN ||
4614 element == EL_SP_EXIT_OPEN ||
4615 element == EL_STEEL_EXIT_OPEN)) ||
4616 element == EL_EM_EXIT_OPEN ||
4617 element == EL_EM_STEEL_EXIT_OPEN)
4621 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4622 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4623 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4624 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4625 EL_EM_STEEL_EXIT_CLOSING);
4627 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4630 // player disappears
4631 DrawLevelField(x, y);
4634 for (i = 0; i < MAX_PLAYERS; i++)
4636 struct PlayerInfo *player = &stored_player[i];
4638 if (player->present)
4640 RemovePlayer(player);
4642 // player disappears
4643 DrawLevelField(player->jx, player->jy);
4648 PlaySound(SND_GAME_WINNING);
4651 if (game_over_delay_1 > 0)
4653 game_over_delay_1--;
4658 if (time != time_final)
4660 int time_to_go = ABS(time_final - time);
4661 int time_count_dir = (time < time_final ? +1 : -1);
4663 if (time_to_go < time_count_steps)
4664 time_count_steps = 1;
4666 time += time_count_steps * time_count_dir;
4667 score += time_count_steps * level.score[SC_TIME_BONUS];
4669 game.LevelSolved_CountingTime = time;
4670 game.LevelSolved_CountingScore = score;
4672 game_panel_controls[GAME_PANEL_TIME].value = time;
4673 game_panel_controls[GAME_PANEL_SCORE].value = score;
4675 DisplayGameControlValues();
4677 if (time == time_final)
4678 StopSound(SND_GAME_LEVELTIME_BONUS);
4679 else if (setup.sound_loops)
4680 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4682 PlaySound(SND_GAME_LEVELTIME_BONUS);
4687 if (game_over_delay_2 > 0)
4689 game_over_delay_2--;
4694 if (health != health_final)
4696 int health_count_dir = (health < health_final ? +1 : -1);
4698 health += health_count_dir;
4699 score += level.score[SC_TIME_BONUS];
4701 game.LevelSolved_CountingHealth = health;
4702 game.LevelSolved_CountingScore = score;
4704 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4705 game_panel_controls[GAME_PANEL_SCORE].value = score;
4707 DisplayGameControlValues();
4709 if (health == health_final)
4710 StopSound(SND_GAME_LEVELTIME_BONUS);
4711 else if (setup.sound_loops)
4712 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4714 PlaySound(SND_GAME_LEVELTIME_BONUS);
4719 game.panel.active = FALSE;
4721 if (game_over_delay_3 > 0)
4723 game_over_delay_3--;
4733 // used instead of "level_nr" (needed for network games)
4734 int last_level_nr = levelset.level_nr;
4737 game.LevelSolved_GameEnd = TRUE;
4739 if (game.LevelSolved_SaveTape)
4741 // make sure that request dialog to save tape does not open door again
4742 if (!global.use_envelope_request)
4743 CloseDoor(DOOR_CLOSE_1);
4745 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4748 // if no tape is to be saved, close both doors simultaneously
4749 CloseDoor(DOOR_CLOSE_ALL);
4751 if (level_editor_test_game)
4753 SetGameStatus(GAME_MODE_MAIN);
4760 if (!game.LevelSolved_SaveScore)
4762 SetGameStatus(GAME_MODE_MAIN);
4769 if (level_nr == leveldir_current->handicap_level)
4771 leveldir_current->handicap_level++;
4773 SaveLevelSetup_SeriesInfo();
4776 if (setup.increment_levels &&
4777 level_nr < leveldir_current->last_level &&
4780 level_nr++; // advance to next level
4781 TapeErase(); // start with empty tape
4783 if (setup.auto_play_next_level)
4785 LoadLevel(level_nr);
4787 SaveLevelSetup_SeriesInfo();
4791 hi_pos = NewHiScore(last_level_nr);
4793 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4795 SetGameStatus(GAME_MODE_SCORES);
4797 DrawHallOfFame(last_level_nr, hi_pos);
4799 else if (setup.auto_play_next_level && setup.increment_levels &&
4800 last_level_nr < leveldir_current->last_level &&
4803 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4807 SetGameStatus(GAME_MODE_MAIN);
4813 int NewHiScore(int level_nr)
4817 boolean one_score_entry_per_name = !program.many_scores_per_name;
4819 LoadScore(level_nr);
4821 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4822 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4825 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4827 if (game.score_final > highscore[k].Score)
4829 // player has made it to the hall of fame
4831 if (k < MAX_SCORE_ENTRIES - 1)
4833 int m = MAX_SCORE_ENTRIES - 1;
4835 if (one_score_entry_per_name)
4837 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4838 if (strEqual(setup.player_name, highscore[l].Name))
4841 if (m == k) // player's new highscore overwrites his old one
4845 for (l = m; l > k; l--)
4847 strcpy(highscore[l].Name, highscore[l - 1].Name);
4848 highscore[l].Score = highscore[l - 1].Score;
4854 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4855 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4856 highscore[k].Score = game.score_final;
4861 else if (one_score_entry_per_name &&
4862 !strncmp(setup.player_name, highscore[k].Name,
4863 MAX_PLAYER_NAME_LEN))
4864 break; // player already there with a higher score
4868 SaveScore(level_nr);
4873 static int getElementMoveStepsizeExt(int x, int y, int direction)
4875 int element = Feld[x][y];
4876 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4877 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4878 int horiz_move = (dx != 0);
4879 int sign = (horiz_move ? dx : dy);
4880 int step = sign * element_info[element].move_stepsize;
4882 // special values for move stepsize for spring and things on conveyor belt
4885 if (CAN_FALL(element) &&
4886 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
4887 step = sign * MOVE_STEPSIZE_NORMAL / 2;
4888 else if (element == EL_SPRING)
4889 step = sign * MOVE_STEPSIZE_NORMAL * 2;
4895 static int getElementMoveStepsize(int x, int y)
4897 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
4900 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
4902 if (player->GfxAction != action || player->GfxDir != dir)
4904 player->GfxAction = action;
4905 player->GfxDir = dir;
4907 player->StepFrame = 0;
4911 static void ResetGfxFrame(int x, int y)
4913 // profiling showed that "autotest" spends 10~20% of its time in this function
4914 if (DrawingDeactivatedField())
4917 int element = Feld[x][y];
4918 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
4920 if (graphic_info[graphic].anim_global_sync)
4921 GfxFrame[x][y] = FrameCounter;
4922 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
4923 GfxFrame[x][y] = CustomValue[x][y];
4924 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
4925 GfxFrame[x][y] = element_info[element].collect_score;
4926 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
4927 GfxFrame[x][y] = ChangeDelay[x][y];
4930 static void ResetGfxAnimation(int x, int y)
4932 GfxAction[x][y] = ACTION_DEFAULT;
4933 GfxDir[x][y] = MovDir[x][y];
4936 ResetGfxFrame(x, y);
4939 static void ResetRandomAnimationValue(int x, int y)
4941 GfxRandom[x][y] = INIT_GFX_RANDOM();
4944 static void InitMovingField(int x, int y, int direction)
4946 int element = Feld[x][y];
4947 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4948 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4951 boolean is_moving_before, is_moving_after;
4953 // check if element was/is moving or being moved before/after mode change
4954 is_moving_before = (WasJustMoving[x][y] != 0);
4955 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
4957 // reset animation only for moving elements which change direction of moving
4958 // or which just started or stopped moving
4959 // (else CEs with property "can move" / "not moving" are reset each frame)
4960 if (is_moving_before != is_moving_after ||
4961 direction != MovDir[x][y])
4962 ResetGfxAnimation(x, y);
4964 MovDir[x][y] = direction;
4965 GfxDir[x][y] = direction;
4967 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
4968 direction == MV_DOWN && CAN_FALL(element) ?
4969 ACTION_FALLING : ACTION_MOVING);
4971 // this is needed for CEs with property "can move" / "not moving"
4973 if (is_moving_after)
4975 if (Feld[newx][newy] == EL_EMPTY)
4976 Feld[newx][newy] = EL_BLOCKED;
4978 MovDir[newx][newy] = MovDir[x][y];
4980 CustomValue[newx][newy] = CustomValue[x][y];
4982 GfxFrame[newx][newy] = GfxFrame[x][y];
4983 GfxRandom[newx][newy] = GfxRandom[x][y];
4984 GfxAction[newx][newy] = GfxAction[x][y];
4985 GfxDir[newx][newy] = GfxDir[x][y];
4989 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
4991 int direction = MovDir[x][y];
4992 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
4993 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
4999 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5001 int oldx = x, oldy = y;
5002 int direction = MovDir[x][y];
5004 if (direction == MV_LEFT)
5006 else if (direction == MV_RIGHT)
5008 else if (direction == MV_UP)
5010 else if (direction == MV_DOWN)
5013 *comes_from_x = oldx;
5014 *comes_from_y = oldy;
5017 static int MovingOrBlocked2Element(int x, int y)
5019 int element = Feld[x][y];
5021 if (element == EL_BLOCKED)
5025 Blocked2Moving(x, y, &oldx, &oldy);
5026 return Feld[oldx][oldy];
5032 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5034 // like MovingOrBlocked2Element(), but if element is moving
5035 // and (x,y) is the field the moving element is just leaving,
5036 // return EL_BLOCKED instead of the element value
5037 int element = Feld[x][y];
5039 if (IS_MOVING(x, y))
5041 if (element == EL_BLOCKED)
5045 Blocked2Moving(x, y, &oldx, &oldy);
5046 return Feld[oldx][oldy];
5055 static void RemoveField(int x, int y)
5057 Feld[x][y] = EL_EMPTY;
5063 CustomValue[x][y] = 0;
5066 ChangeDelay[x][y] = 0;
5067 ChangePage[x][y] = -1;
5068 Pushed[x][y] = FALSE;
5070 GfxElement[x][y] = EL_UNDEFINED;
5071 GfxAction[x][y] = ACTION_DEFAULT;
5072 GfxDir[x][y] = MV_NONE;
5075 static void RemoveMovingField(int x, int y)
5077 int oldx = x, oldy = y, newx = x, newy = y;
5078 int element = Feld[x][y];
5079 int next_element = EL_UNDEFINED;
5081 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5084 if (IS_MOVING(x, y))
5086 Moving2Blocked(x, y, &newx, &newy);
5088 if (Feld[newx][newy] != EL_BLOCKED)
5090 // element is moving, but target field is not free (blocked), but
5091 // already occupied by something different (example: acid pool);
5092 // in this case, only remove the moving field, but not the target
5094 RemoveField(oldx, oldy);
5096 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5098 TEST_DrawLevelField(oldx, oldy);
5103 else if (element == EL_BLOCKED)
5105 Blocked2Moving(x, y, &oldx, &oldy);
5106 if (!IS_MOVING(oldx, oldy))
5110 if (element == EL_BLOCKED &&
5111 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5112 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5113 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5114 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5115 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5116 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5117 next_element = get_next_element(Feld[oldx][oldy]);
5119 RemoveField(oldx, oldy);
5120 RemoveField(newx, newy);
5122 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5124 if (next_element != EL_UNDEFINED)
5125 Feld[oldx][oldy] = next_element;
5127 TEST_DrawLevelField(oldx, oldy);
5128 TEST_DrawLevelField(newx, newy);
5131 void DrawDynamite(int x, int y)
5133 int sx = SCREENX(x), sy = SCREENY(y);
5134 int graphic = el2img(Feld[x][y]);
5137 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5140 if (IS_WALKABLE_INSIDE(Back[x][y]))
5144 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5145 else if (Store[x][y])
5146 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5148 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5150 if (Back[x][y] || Store[x][y])
5151 DrawGraphicThruMask(sx, sy, graphic, frame);
5153 DrawGraphic(sx, sy, graphic, frame);
5156 static void CheckDynamite(int x, int y)
5158 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5162 if (MovDelay[x][y] != 0)
5165 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5171 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5176 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5178 boolean num_checked_players = 0;
5181 for (i = 0; i < MAX_PLAYERS; i++)
5183 if (stored_player[i].active)
5185 int sx = stored_player[i].jx;
5186 int sy = stored_player[i].jy;
5188 if (num_checked_players == 0)
5195 *sx1 = MIN(*sx1, sx);
5196 *sy1 = MIN(*sy1, sy);
5197 *sx2 = MAX(*sx2, sx);
5198 *sy2 = MAX(*sy2, sy);
5201 num_checked_players++;
5206 static boolean checkIfAllPlayersFitToScreen_RND(void)
5208 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5210 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5212 return (sx2 - sx1 < SCR_FIELDX &&
5213 sy2 - sy1 < SCR_FIELDY);
5216 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5218 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5220 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5222 *sx = (sx1 + sx2) / 2;
5223 *sy = (sy1 + sy2) / 2;
5226 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5227 boolean center_screen, boolean quick_relocation)
5229 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5230 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5231 boolean no_delay = (tape.warp_forward);
5232 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5233 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5234 int new_scroll_x, new_scroll_y;
5236 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5238 // case 1: quick relocation inside visible screen (without scrolling)
5245 if (!level.shifted_relocation || center_screen)
5247 // relocation _with_ centering of screen
5249 new_scroll_x = SCROLL_POSITION_X(x);
5250 new_scroll_y = SCROLL_POSITION_Y(y);
5254 // relocation _without_ centering of screen
5256 int center_scroll_x = SCROLL_POSITION_X(old_x);
5257 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5258 int offset_x = x + (scroll_x - center_scroll_x);
5259 int offset_y = y + (scroll_y - center_scroll_y);
5261 // for new screen position, apply previous offset to center position
5262 new_scroll_x = SCROLL_POSITION_X(offset_x);
5263 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5266 if (quick_relocation)
5268 // case 2: quick relocation (redraw without visible scrolling)
5270 scroll_x = new_scroll_x;
5271 scroll_y = new_scroll_y;
5278 // case 3: visible relocation (with scrolling to new position)
5280 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5282 SetVideoFrameDelay(wait_delay_value);
5284 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5286 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5287 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5289 if (dx == 0 && dy == 0) // no scrolling needed at all
5295 // set values for horizontal/vertical screen scrolling (half tile size)
5296 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5297 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5298 int pos_x = dx * TILEX / 2;
5299 int pos_y = dy * TILEY / 2;
5300 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5301 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5303 ScrollLevel(dx, dy);
5306 // scroll in two steps of half tile size to make things smoother
5307 BlitScreenToBitmapExt_RND(window, fx, fy);
5309 // scroll second step to align at full tile size
5310 BlitScreenToBitmap(window);
5316 SetVideoFrameDelay(frame_delay_value_old);
5319 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5321 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5322 int player_nr = GET_PLAYER_NR(el_player);
5323 struct PlayerInfo *player = &stored_player[player_nr];
5324 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5325 boolean no_delay = (tape.warp_forward);
5326 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5327 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5328 int old_jx = player->jx;
5329 int old_jy = player->jy;
5330 int old_element = Feld[old_jx][old_jy];
5331 int element = Feld[jx][jy];
5332 boolean player_relocated = (old_jx != jx || old_jy != jy);
5334 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5335 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5336 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5337 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5338 int leave_side_horiz = move_dir_horiz;
5339 int leave_side_vert = move_dir_vert;
5340 int enter_side = enter_side_horiz | enter_side_vert;
5341 int leave_side = leave_side_horiz | leave_side_vert;
5343 if (player->buried) // do not reanimate dead player
5346 if (!player_relocated) // no need to relocate the player
5349 if (IS_PLAYER(jx, jy)) // player already placed at new position
5351 RemoveField(jx, jy); // temporarily remove newly placed player
5352 DrawLevelField(jx, jy);
5355 if (player->present)
5357 while (player->MovPos)
5359 ScrollPlayer(player, SCROLL_GO_ON);
5360 ScrollScreen(NULL, SCROLL_GO_ON);
5362 AdvanceFrameAndPlayerCounters(player->index_nr);
5366 BackToFront_WithFrameDelay(wait_delay_value);
5369 DrawPlayer(player); // needed here only to cleanup last field
5370 DrawLevelField(player->jx, player->jy); // remove player graphic
5372 player->is_moving = FALSE;
5375 if (IS_CUSTOM_ELEMENT(old_element))
5376 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5378 player->index_bit, leave_side);
5380 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5382 player->index_bit, leave_side);
5384 Feld[jx][jy] = el_player;
5385 InitPlayerField(jx, jy, el_player, TRUE);
5387 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5388 possible that the relocation target field did not contain a player element,
5389 but a walkable element, to which the new player was relocated -- in this
5390 case, restore that (already initialized!) element on the player field */
5391 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5393 Feld[jx][jy] = element; // restore previously existing element
5396 // only visually relocate centered player
5397 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5398 FALSE, level.instant_relocation);
5400 TestIfPlayerTouchesBadThing(jx, jy);
5401 TestIfPlayerTouchesCustomElement(jx, jy);
5403 if (IS_CUSTOM_ELEMENT(element))
5404 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5405 player->index_bit, enter_side);
5407 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5408 player->index_bit, enter_side);
5410 if (player->is_switching)
5412 /* ensure that relocation while still switching an element does not cause
5413 a new element to be treated as also switched directly after relocation
5414 (this is important for teleporter switches that teleport the player to
5415 a place where another teleporter switch is in the same direction, which
5416 would then incorrectly be treated as immediately switched before the
5417 direction key that caused the switch was released) */
5419 player->switch_x += jx - old_jx;
5420 player->switch_y += jy - old_jy;
5424 static void Explode(int ex, int ey, int phase, int mode)
5430 // !!! eliminate this variable !!!
5431 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5433 if (game.explosions_delayed)
5435 ExplodeField[ex][ey] = mode;
5439 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5441 int center_element = Feld[ex][ey];
5442 int artwork_element, explosion_element; // set these values later
5444 // remove things displayed in background while burning dynamite
5445 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5448 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5450 // put moving element to center field (and let it explode there)
5451 center_element = MovingOrBlocked2Element(ex, ey);
5452 RemoveMovingField(ex, ey);
5453 Feld[ex][ey] = center_element;
5456 // now "center_element" is finally determined -- set related values now
5457 artwork_element = center_element; // for custom player artwork
5458 explosion_element = center_element; // for custom player artwork
5460 if (IS_PLAYER(ex, ey))
5462 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5464 artwork_element = stored_player[player_nr].artwork_element;
5466 if (level.use_explosion_element[player_nr])
5468 explosion_element = level.explosion_element[player_nr];
5469 artwork_element = explosion_element;
5473 if (mode == EX_TYPE_NORMAL ||
5474 mode == EX_TYPE_CENTER ||
5475 mode == EX_TYPE_CROSS)
5476 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5478 last_phase = element_info[explosion_element].explosion_delay + 1;
5480 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5482 int xx = x - ex + 1;
5483 int yy = y - ey + 1;
5486 if (!IN_LEV_FIELD(x, y) ||
5487 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5488 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5491 element = Feld[x][y];
5493 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5495 element = MovingOrBlocked2Element(x, y);
5497 if (!IS_EXPLOSION_PROOF(element))
5498 RemoveMovingField(x, y);
5501 // indestructible elements can only explode in center (but not flames)
5502 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5503 mode == EX_TYPE_BORDER)) ||
5504 element == EL_FLAMES)
5507 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5508 behaviour, for example when touching a yamyam that explodes to rocks
5509 with active deadly shield, a rock is created under the player !!! */
5510 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5512 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5513 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5514 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5516 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5519 if (IS_ACTIVE_BOMB(element))
5521 // re-activate things under the bomb like gate or penguin
5522 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5529 // save walkable background elements while explosion on same tile
5530 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5531 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5532 Back[x][y] = element;
5534 // ignite explodable elements reached by other explosion
5535 if (element == EL_EXPLOSION)
5536 element = Store2[x][y];
5538 if (AmoebaNr[x][y] &&
5539 (element == EL_AMOEBA_FULL ||
5540 element == EL_BD_AMOEBA ||
5541 element == EL_AMOEBA_GROWING))
5543 AmoebaCnt[AmoebaNr[x][y]]--;
5544 AmoebaCnt2[AmoebaNr[x][y]]--;
5549 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5551 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5553 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5555 if (PLAYERINFO(ex, ey)->use_murphy)
5556 Store[x][y] = EL_EMPTY;
5559 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5560 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5561 else if (ELEM_IS_PLAYER(center_element))
5562 Store[x][y] = EL_EMPTY;
5563 else if (center_element == EL_YAMYAM)
5564 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5565 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5566 Store[x][y] = element_info[center_element].content.e[xx][yy];
5568 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5569 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5570 // otherwise) -- FIX THIS !!!
5571 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5572 Store[x][y] = element_info[element].content.e[1][1];
5574 else if (!CAN_EXPLODE(element))
5575 Store[x][y] = element_info[element].content.e[1][1];
5578 Store[x][y] = EL_EMPTY;
5580 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5581 center_element == EL_AMOEBA_TO_DIAMOND)
5582 Store2[x][y] = element;
5584 Feld[x][y] = EL_EXPLOSION;
5585 GfxElement[x][y] = artwork_element;
5587 ExplodePhase[x][y] = 1;
5588 ExplodeDelay[x][y] = last_phase;
5593 if (center_element == EL_YAMYAM)
5594 game.yamyam_content_nr =
5595 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5607 GfxFrame[x][y] = 0; // restart explosion animation
5609 last_phase = ExplodeDelay[x][y];
5611 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5613 // this can happen if the player leaves an explosion just in time
5614 if (GfxElement[x][y] == EL_UNDEFINED)
5615 GfxElement[x][y] = EL_EMPTY;
5617 border_element = Store2[x][y];
5618 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5619 border_element = StorePlayer[x][y];
5621 if (phase == element_info[border_element].ignition_delay ||
5622 phase == last_phase)
5624 boolean border_explosion = FALSE;
5626 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5627 !PLAYER_EXPLOSION_PROTECTED(x, y))
5629 KillPlayerUnlessExplosionProtected(x, y);
5630 border_explosion = TRUE;
5632 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5634 Feld[x][y] = Store2[x][y];
5637 border_explosion = TRUE;
5639 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5641 AmoebeUmwandeln(x, y);
5643 border_explosion = TRUE;
5646 // if an element just explodes due to another explosion (chain-reaction),
5647 // do not immediately end the new explosion when it was the last frame of
5648 // the explosion (as it would be done in the following "if"-statement!)
5649 if (border_explosion && phase == last_phase)
5653 if (phase == last_phase)
5657 element = Feld[x][y] = Store[x][y];
5658 Store[x][y] = Store2[x][y] = 0;
5659 GfxElement[x][y] = EL_UNDEFINED;
5661 // player can escape from explosions and might therefore be still alive
5662 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5663 element <= EL_PLAYER_IS_EXPLODING_4)
5665 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5666 int explosion_element = EL_PLAYER_1 + player_nr;
5667 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5668 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5670 if (level.use_explosion_element[player_nr])
5671 explosion_element = level.explosion_element[player_nr];
5673 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5674 element_info[explosion_element].content.e[xx][yy]);
5677 // restore probably existing indestructible background element
5678 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5679 element = Feld[x][y] = Back[x][y];
5682 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5683 GfxDir[x][y] = MV_NONE;
5684 ChangeDelay[x][y] = 0;
5685 ChangePage[x][y] = -1;
5687 CustomValue[x][y] = 0;
5689 InitField_WithBug2(x, y, FALSE);
5691 TEST_DrawLevelField(x, y);
5693 TestIfElementTouchesCustomElement(x, y);
5695 if (GFX_CRUMBLED(element))
5696 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5698 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5699 StorePlayer[x][y] = 0;
5701 if (ELEM_IS_PLAYER(element))
5702 RelocatePlayer(x, y, element);
5704 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5706 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5707 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5710 TEST_DrawLevelFieldCrumbled(x, y);
5712 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5714 DrawLevelElement(x, y, Back[x][y]);
5715 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5717 else if (IS_WALKABLE_UNDER(Back[x][y]))
5719 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5720 DrawLevelElementThruMask(x, y, Back[x][y]);
5722 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5723 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5727 static void DynaExplode(int ex, int ey)
5730 int dynabomb_element = Feld[ex][ey];
5731 int dynabomb_size = 1;
5732 boolean dynabomb_xl = FALSE;
5733 struct PlayerInfo *player;
5734 static int xy[4][2] =
5742 if (IS_ACTIVE_BOMB(dynabomb_element))
5744 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5745 dynabomb_size = player->dynabomb_size;
5746 dynabomb_xl = player->dynabomb_xl;
5747 player->dynabombs_left++;
5750 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5752 for (i = 0; i < NUM_DIRECTIONS; i++)
5754 for (j = 1; j <= dynabomb_size; j++)
5756 int x = ex + j * xy[i][0];
5757 int y = ey + j * xy[i][1];
5760 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5763 element = Feld[x][y];
5765 // do not restart explosions of fields with active bombs
5766 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5769 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5771 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5772 !IS_DIGGABLE(element) && !dynabomb_xl)
5778 void Bang(int x, int y)
5780 int element = MovingOrBlocked2Element(x, y);
5781 int explosion_type = EX_TYPE_NORMAL;
5783 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5785 struct PlayerInfo *player = PLAYERINFO(x, y);
5787 element = Feld[x][y] = player->initial_element;
5789 if (level.use_explosion_element[player->index_nr])
5791 int explosion_element = level.explosion_element[player->index_nr];
5793 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5794 explosion_type = EX_TYPE_CROSS;
5795 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5796 explosion_type = EX_TYPE_CENTER;
5804 case EL_BD_BUTTERFLY:
5807 case EL_DARK_YAMYAM:
5811 RaiseScoreElement(element);
5814 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5815 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5816 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5817 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5818 case EL_DYNABOMB_INCREASE_NUMBER:
5819 case EL_DYNABOMB_INCREASE_SIZE:
5820 case EL_DYNABOMB_INCREASE_POWER:
5821 explosion_type = EX_TYPE_DYNA;
5824 case EL_DC_LANDMINE:
5825 explosion_type = EX_TYPE_CENTER;
5830 case EL_LAMP_ACTIVE:
5831 case EL_AMOEBA_TO_DIAMOND:
5832 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5833 explosion_type = EX_TYPE_CENTER;
5837 if (element_info[element].explosion_type == EXPLODES_CROSS)
5838 explosion_type = EX_TYPE_CROSS;
5839 else if (element_info[element].explosion_type == EXPLODES_1X1)
5840 explosion_type = EX_TYPE_CENTER;
5844 if (explosion_type == EX_TYPE_DYNA)
5847 Explode(x, y, EX_PHASE_START, explosion_type);
5849 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5852 static void SplashAcid(int x, int y)
5854 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5855 (!IN_LEV_FIELD(x - 1, y - 2) ||
5856 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5857 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5859 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5860 (!IN_LEV_FIELD(x + 1, y - 2) ||
5861 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5862 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5864 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5867 static void InitBeltMovement(void)
5869 static int belt_base_element[4] =
5871 EL_CONVEYOR_BELT_1_LEFT,
5872 EL_CONVEYOR_BELT_2_LEFT,
5873 EL_CONVEYOR_BELT_3_LEFT,
5874 EL_CONVEYOR_BELT_4_LEFT
5876 static int belt_base_active_element[4] =
5878 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5879 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5880 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5881 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5886 // set frame order for belt animation graphic according to belt direction
5887 for (i = 0; i < NUM_BELTS; i++)
5891 for (j = 0; j < NUM_BELT_PARTS; j++)
5893 int element = belt_base_active_element[belt_nr] + j;
5894 int graphic_1 = el2img(element);
5895 int graphic_2 = el2panelimg(element);
5897 if (game.belt_dir[i] == MV_LEFT)
5899 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5900 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5904 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5905 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5910 SCAN_PLAYFIELD(x, y)
5912 int element = Feld[x][y];
5914 for (i = 0; i < NUM_BELTS; i++)
5916 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
5918 int e_belt_nr = getBeltNrFromBeltElement(element);
5921 if (e_belt_nr == belt_nr)
5923 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
5925 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
5932 static void ToggleBeltSwitch(int x, int y)
5934 static int belt_base_element[4] =
5936 EL_CONVEYOR_BELT_1_LEFT,
5937 EL_CONVEYOR_BELT_2_LEFT,
5938 EL_CONVEYOR_BELT_3_LEFT,
5939 EL_CONVEYOR_BELT_4_LEFT
5941 static int belt_base_active_element[4] =
5943 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5944 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5945 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5946 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5948 static int belt_base_switch_element[4] =
5950 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
5951 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
5952 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
5953 EL_CONVEYOR_BELT_4_SWITCH_LEFT
5955 static int belt_move_dir[4] =
5963 int element = Feld[x][y];
5964 int belt_nr = getBeltNrFromBeltSwitchElement(element);
5965 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
5966 int belt_dir = belt_move_dir[belt_dir_nr];
5969 if (!IS_BELT_SWITCH(element))
5972 game.belt_dir_nr[belt_nr] = belt_dir_nr;
5973 game.belt_dir[belt_nr] = belt_dir;
5975 if (belt_dir_nr == 3)
5978 // set frame order for belt animation graphic according to belt direction
5979 for (i = 0; i < NUM_BELT_PARTS; i++)
5981 int element = belt_base_active_element[belt_nr] + i;
5982 int graphic_1 = el2img(element);
5983 int graphic_2 = el2panelimg(element);
5985 if (belt_dir == MV_LEFT)
5987 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5988 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5992 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5993 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5997 SCAN_PLAYFIELD(xx, yy)
5999 int element = Feld[xx][yy];
6001 if (IS_BELT_SWITCH(element))
6003 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6005 if (e_belt_nr == belt_nr)
6007 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6008 TEST_DrawLevelField(xx, yy);
6011 else if (IS_BELT(element) && belt_dir != MV_NONE)
6013 int e_belt_nr = getBeltNrFromBeltElement(element);
6015 if (e_belt_nr == belt_nr)
6017 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
6019 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6020 TEST_DrawLevelField(xx, yy);
6023 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6025 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6027 if (e_belt_nr == belt_nr)
6029 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6031 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6032 TEST_DrawLevelField(xx, yy);
6038 static void ToggleSwitchgateSwitch(int x, int y)
6042 game.switchgate_pos = !game.switchgate_pos;
6044 SCAN_PLAYFIELD(xx, yy)
6046 int element = Feld[xx][yy];
6048 if (element == EL_SWITCHGATE_SWITCH_UP)
6050 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6051 TEST_DrawLevelField(xx, yy);
6053 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6055 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6056 TEST_DrawLevelField(xx, yy);
6058 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6060 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6061 TEST_DrawLevelField(xx, yy);
6063 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6065 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6066 TEST_DrawLevelField(xx, yy);
6068 else if (element == EL_SWITCHGATE_OPEN ||
6069 element == EL_SWITCHGATE_OPENING)
6071 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6073 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6075 else if (element == EL_SWITCHGATE_CLOSED ||
6076 element == EL_SWITCHGATE_CLOSING)
6078 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6080 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6085 static int getInvisibleActiveFromInvisibleElement(int element)
6087 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6088 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6089 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6093 static int getInvisibleFromInvisibleActiveElement(int element)
6095 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6096 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6097 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6101 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6105 SCAN_PLAYFIELD(x, y)
6107 int element = Feld[x][y];
6109 if (element == EL_LIGHT_SWITCH &&
6110 game.light_time_left > 0)
6112 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6113 TEST_DrawLevelField(x, y);
6115 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6116 game.light_time_left == 0)
6118 Feld[x][y] = EL_LIGHT_SWITCH;
6119 TEST_DrawLevelField(x, y);
6121 else if (element == EL_EMC_DRIPPER &&
6122 game.light_time_left > 0)
6124 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6125 TEST_DrawLevelField(x, y);
6127 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6128 game.light_time_left == 0)
6130 Feld[x][y] = EL_EMC_DRIPPER;
6131 TEST_DrawLevelField(x, y);
6133 else if (element == EL_INVISIBLE_STEELWALL ||
6134 element == EL_INVISIBLE_WALL ||
6135 element == EL_INVISIBLE_SAND)
6137 if (game.light_time_left > 0)
6138 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6140 TEST_DrawLevelField(x, y);
6142 // uncrumble neighbour fields, if needed
6143 if (element == EL_INVISIBLE_SAND)
6144 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6146 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6147 element == EL_INVISIBLE_WALL_ACTIVE ||
6148 element == EL_INVISIBLE_SAND_ACTIVE)
6150 if (game.light_time_left == 0)
6151 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6153 TEST_DrawLevelField(x, y);
6155 // re-crumble neighbour fields, if needed
6156 if (element == EL_INVISIBLE_SAND)
6157 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6162 static void RedrawAllInvisibleElementsForLenses(void)
6166 SCAN_PLAYFIELD(x, y)
6168 int element = Feld[x][y];
6170 if (element == EL_EMC_DRIPPER &&
6171 game.lenses_time_left > 0)
6173 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6174 TEST_DrawLevelField(x, y);
6176 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6177 game.lenses_time_left == 0)
6179 Feld[x][y] = EL_EMC_DRIPPER;
6180 TEST_DrawLevelField(x, y);
6182 else if (element == EL_INVISIBLE_STEELWALL ||
6183 element == EL_INVISIBLE_WALL ||
6184 element == EL_INVISIBLE_SAND)
6186 if (game.lenses_time_left > 0)
6187 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6189 TEST_DrawLevelField(x, y);
6191 // uncrumble neighbour fields, if needed
6192 if (element == EL_INVISIBLE_SAND)
6193 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6195 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6196 element == EL_INVISIBLE_WALL_ACTIVE ||
6197 element == EL_INVISIBLE_SAND_ACTIVE)
6199 if (game.lenses_time_left == 0)
6200 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6202 TEST_DrawLevelField(x, y);
6204 // re-crumble neighbour fields, if needed
6205 if (element == EL_INVISIBLE_SAND)
6206 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6211 static void RedrawAllInvisibleElementsForMagnifier(void)
6215 SCAN_PLAYFIELD(x, y)
6217 int element = Feld[x][y];
6219 if (element == EL_EMC_FAKE_GRASS &&
6220 game.magnify_time_left > 0)
6222 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6223 TEST_DrawLevelField(x, y);
6225 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6226 game.magnify_time_left == 0)
6228 Feld[x][y] = EL_EMC_FAKE_GRASS;
6229 TEST_DrawLevelField(x, y);
6231 else if (IS_GATE_GRAY(element) &&
6232 game.magnify_time_left > 0)
6234 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6235 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6236 IS_EM_GATE_GRAY(element) ?
6237 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6238 IS_EMC_GATE_GRAY(element) ?
6239 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6240 IS_DC_GATE_GRAY(element) ?
6241 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6243 TEST_DrawLevelField(x, y);
6245 else if (IS_GATE_GRAY_ACTIVE(element) &&
6246 game.magnify_time_left == 0)
6248 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6249 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6250 IS_EM_GATE_GRAY_ACTIVE(element) ?
6251 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6252 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6253 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6254 IS_DC_GATE_GRAY_ACTIVE(element) ?
6255 EL_DC_GATE_WHITE_GRAY :
6257 TEST_DrawLevelField(x, y);
6262 static void ToggleLightSwitch(int x, int y)
6264 int element = Feld[x][y];
6266 game.light_time_left =
6267 (element == EL_LIGHT_SWITCH ?
6268 level.time_light * FRAMES_PER_SECOND : 0);
6270 RedrawAllLightSwitchesAndInvisibleElements();
6273 static void ActivateTimegateSwitch(int x, int y)
6277 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6279 SCAN_PLAYFIELD(xx, yy)
6281 int element = Feld[xx][yy];
6283 if (element == EL_TIMEGATE_CLOSED ||
6284 element == EL_TIMEGATE_CLOSING)
6286 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6287 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6291 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6293 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6294 TEST_DrawLevelField(xx, yy);
6300 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6301 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6304 static void Impact(int x, int y)
6306 boolean last_line = (y == lev_fieldy - 1);
6307 boolean object_hit = FALSE;
6308 boolean impact = (last_line || object_hit);
6309 int element = Feld[x][y];
6310 int smashed = EL_STEELWALL;
6312 if (!last_line) // check if element below was hit
6314 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6317 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6318 MovDir[x][y + 1] != MV_DOWN ||
6319 MovPos[x][y + 1] <= TILEY / 2));
6321 // do not smash moving elements that left the smashed field in time
6322 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6323 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6326 #if USE_QUICKSAND_IMPACT_BUGFIX
6327 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6329 RemoveMovingField(x, y + 1);
6330 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6331 Feld[x][y + 2] = EL_ROCK;
6332 TEST_DrawLevelField(x, y + 2);
6337 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6339 RemoveMovingField(x, y + 1);
6340 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6341 Feld[x][y + 2] = EL_ROCK;
6342 TEST_DrawLevelField(x, y + 2);
6349 smashed = MovingOrBlocked2Element(x, y + 1);
6351 impact = (last_line || object_hit);
6354 if (!last_line && smashed == EL_ACID) // element falls into acid
6356 SplashAcid(x, y + 1);
6360 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6361 // only reset graphic animation if graphic really changes after impact
6363 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6365 ResetGfxAnimation(x, y);
6366 TEST_DrawLevelField(x, y);
6369 if (impact && CAN_EXPLODE_IMPACT(element))
6374 else if (impact && element == EL_PEARL &&
6375 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6377 ResetGfxAnimation(x, y);
6379 Feld[x][y] = EL_PEARL_BREAKING;
6380 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6383 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6385 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6390 if (impact && element == EL_AMOEBA_DROP)
6392 if (object_hit && IS_PLAYER(x, y + 1))
6393 KillPlayerUnlessEnemyProtected(x, y + 1);
6394 else if (object_hit && smashed == EL_PENGUIN)
6398 Feld[x][y] = EL_AMOEBA_GROWING;
6399 Store[x][y] = EL_AMOEBA_WET;
6401 ResetRandomAnimationValue(x, y);
6406 if (object_hit) // check which object was hit
6408 if ((CAN_PASS_MAGIC_WALL(element) &&
6409 (smashed == EL_MAGIC_WALL ||
6410 smashed == EL_BD_MAGIC_WALL)) ||
6411 (CAN_PASS_DC_MAGIC_WALL(element) &&
6412 smashed == EL_DC_MAGIC_WALL))
6415 int activated_magic_wall =
6416 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6417 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6418 EL_DC_MAGIC_WALL_ACTIVE);
6420 // activate magic wall / mill
6421 SCAN_PLAYFIELD(xx, yy)
6423 if (Feld[xx][yy] == smashed)
6424 Feld[xx][yy] = activated_magic_wall;
6427 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6428 game.magic_wall_active = TRUE;
6430 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6431 SND_MAGIC_WALL_ACTIVATING :
6432 smashed == EL_BD_MAGIC_WALL ?
6433 SND_BD_MAGIC_WALL_ACTIVATING :
6434 SND_DC_MAGIC_WALL_ACTIVATING));
6437 if (IS_PLAYER(x, y + 1))
6439 if (CAN_SMASH_PLAYER(element))
6441 KillPlayerUnlessEnemyProtected(x, y + 1);
6445 else if (smashed == EL_PENGUIN)
6447 if (CAN_SMASH_PLAYER(element))
6453 else if (element == EL_BD_DIAMOND)
6455 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6461 else if (((element == EL_SP_INFOTRON ||
6462 element == EL_SP_ZONK) &&
6463 (smashed == EL_SP_SNIKSNAK ||
6464 smashed == EL_SP_ELECTRON ||
6465 smashed == EL_SP_DISK_ORANGE)) ||
6466 (element == EL_SP_INFOTRON &&
6467 smashed == EL_SP_DISK_YELLOW))
6472 else if (CAN_SMASH_EVERYTHING(element))
6474 if (IS_CLASSIC_ENEMY(smashed) ||
6475 CAN_EXPLODE_SMASHED(smashed))
6480 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6482 if (smashed == EL_LAMP ||
6483 smashed == EL_LAMP_ACTIVE)
6488 else if (smashed == EL_NUT)
6490 Feld[x][y + 1] = EL_NUT_BREAKING;
6491 PlayLevelSound(x, y, SND_NUT_BREAKING);
6492 RaiseScoreElement(EL_NUT);
6495 else if (smashed == EL_PEARL)
6497 ResetGfxAnimation(x, y);
6499 Feld[x][y + 1] = EL_PEARL_BREAKING;
6500 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6503 else if (smashed == EL_DIAMOND)
6505 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6506 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6509 else if (IS_BELT_SWITCH(smashed))
6511 ToggleBeltSwitch(x, y + 1);
6513 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6514 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6515 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6516 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6518 ToggleSwitchgateSwitch(x, y + 1);
6520 else if (smashed == EL_LIGHT_SWITCH ||
6521 smashed == EL_LIGHT_SWITCH_ACTIVE)
6523 ToggleLightSwitch(x, y + 1);
6527 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6529 CheckElementChangeBySide(x, y + 1, smashed, element,
6530 CE_SWITCHED, CH_SIDE_TOP);
6531 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6537 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6542 // play sound of magic wall / mill
6544 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6545 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6546 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6548 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6549 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6550 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6551 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6552 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6553 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6558 // play sound of object that hits the ground
6559 if (last_line || object_hit)
6560 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6563 static void TurnRoundExt(int x, int y)
6575 { 0, 0 }, { 0, 0 }, { 0, 0 },
6580 int left, right, back;
6584 { MV_DOWN, MV_UP, MV_RIGHT },
6585 { MV_UP, MV_DOWN, MV_LEFT },
6587 { MV_LEFT, MV_RIGHT, MV_DOWN },
6591 { MV_RIGHT, MV_LEFT, MV_UP }
6594 int element = Feld[x][y];
6595 int move_pattern = element_info[element].move_pattern;
6597 int old_move_dir = MovDir[x][y];
6598 int left_dir = turn[old_move_dir].left;
6599 int right_dir = turn[old_move_dir].right;
6600 int back_dir = turn[old_move_dir].back;
6602 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6603 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6604 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6605 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6607 int left_x = x + left_dx, left_y = y + left_dy;
6608 int right_x = x + right_dx, right_y = y + right_dy;
6609 int move_x = x + move_dx, move_y = y + move_dy;
6613 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6615 TestIfBadThingTouchesOtherBadThing(x, y);
6617 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6618 MovDir[x][y] = right_dir;
6619 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6620 MovDir[x][y] = left_dir;
6622 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6624 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6627 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6629 TestIfBadThingTouchesOtherBadThing(x, y);
6631 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6632 MovDir[x][y] = left_dir;
6633 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6634 MovDir[x][y] = right_dir;
6636 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6638 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6641 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6643 TestIfBadThingTouchesOtherBadThing(x, y);
6645 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6646 MovDir[x][y] = left_dir;
6647 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6648 MovDir[x][y] = right_dir;
6650 if (MovDir[x][y] != old_move_dir)
6653 else if (element == EL_YAMYAM)
6655 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6656 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6658 if (can_turn_left && can_turn_right)
6659 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6660 else if (can_turn_left)
6661 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6662 else if (can_turn_right)
6663 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6665 MovDir[x][y] = back_dir;
6667 MovDelay[x][y] = 16 + 16 * RND(3);
6669 else if (element == EL_DARK_YAMYAM)
6671 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6673 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6676 if (can_turn_left && can_turn_right)
6677 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6678 else if (can_turn_left)
6679 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6680 else if (can_turn_right)
6681 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6683 MovDir[x][y] = back_dir;
6685 MovDelay[x][y] = 16 + 16 * RND(3);
6687 else if (element == EL_PACMAN)
6689 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6690 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6692 if (can_turn_left && can_turn_right)
6693 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6694 else if (can_turn_left)
6695 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6696 else if (can_turn_right)
6697 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6699 MovDir[x][y] = back_dir;
6701 MovDelay[x][y] = 6 + RND(40);
6703 else if (element == EL_PIG)
6705 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6706 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6707 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6708 boolean should_turn_left, should_turn_right, should_move_on;
6710 int rnd = RND(rnd_value);
6712 should_turn_left = (can_turn_left &&
6714 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6715 y + back_dy + left_dy)));
6716 should_turn_right = (can_turn_right &&
6718 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6719 y + back_dy + right_dy)));
6720 should_move_on = (can_move_on &&
6723 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6724 y + move_dy + left_dy) ||
6725 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6726 y + move_dy + right_dy)));
6728 if (should_turn_left || should_turn_right || should_move_on)
6730 if (should_turn_left && should_turn_right && should_move_on)
6731 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6732 rnd < 2 * rnd_value / 3 ? right_dir :
6734 else if (should_turn_left && should_turn_right)
6735 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6736 else if (should_turn_left && should_move_on)
6737 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6738 else if (should_turn_right && should_move_on)
6739 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6740 else if (should_turn_left)
6741 MovDir[x][y] = left_dir;
6742 else if (should_turn_right)
6743 MovDir[x][y] = right_dir;
6744 else if (should_move_on)
6745 MovDir[x][y] = old_move_dir;
6747 else if (can_move_on && rnd > rnd_value / 8)
6748 MovDir[x][y] = old_move_dir;
6749 else if (can_turn_left && can_turn_right)
6750 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6751 else if (can_turn_left && rnd > rnd_value / 8)
6752 MovDir[x][y] = left_dir;
6753 else if (can_turn_right && rnd > rnd_value/8)
6754 MovDir[x][y] = right_dir;
6756 MovDir[x][y] = back_dir;
6758 xx = x + move_xy[MovDir[x][y]].dx;
6759 yy = y + move_xy[MovDir[x][y]].dy;
6761 if (!IN_LEV_FIELD(xx, yy) ||
6762 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6763 MovDir[x][y] = old_move_dir;
6767 else if (element == EL_DRAGON)
6769 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6770 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6771 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6773 int rnd = RND(rnd_value);
6775 if (can_move_on && rnd > rnd_value / 8)
6776 MovDir[x][y] = old_move_dir;
6777 else if (can_turn_left && can_turn_right)
6778 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6779 else if (can_turn_left && rnd > rnd_value / 8)
6780 MovDir[x][y] = left_dir;
6781 else if (can_turn_right && rnd > rnd_value / 8)
6782 MovDir[x][y] = right_dir;
6784 MovDir[x][y] = back_dir;
6786 xx = x + move_xy[MovDir[x][y]].dx;
6787 yy = y + move_xy[MovDir[x][y]].dy;
6789 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6790 MovDir[x][y] = old_move_dir;
6794 else if (element == EL_MOLE)
6796 boolean can_move_on =
6797 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6798 IS_AMOEBOID(Feld[move_x][move_y]) ||
6799 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6802 boolean can_turn_left =
6803 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6804 IS_AMOEBOID(Feld[left_x][left_y])));
6806 boolean can_turn_right =
6807 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6808 IS_AMOEBOID(Feld[right_x][right_y])));
6810 if (can_turn_left && can_turn_right)
6811 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6812 else if (can_turn_left)
6813 MovDir[x][y] = left_dir;
6815 MovDir[x][y] = right_dir;
6818 if (MovDir[x][y] != old_move_dir)
6821 else if (element == EL_BALLOON)
6823 MovDir[x][y] = game.wind_direction;
6826 else if (element == EL_SPRING)
6828 if (MovDir[x][y] & MV_HORIZONTAL)
6830 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6831 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6833 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6834 ResetGfxAnimation(move_x, move_y);
6835 TEST_DrawLevelField(move_x, move_y);
6837 MovDir[x][y] = back_dir;
6839 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6840 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6841 MovDir[x][y] = MV_NONE;
6846 else if (element == EL_ROBOT ||
6847 element == EL_SATELLITE ||
6848 element == EL_PENGUIN ||
6849 element == EL_EMC_ANDROID)
6851 int attr_x = -1, attr_y = -1;
6853 if (game.all_players_gone)
6855 attr_x = game.exit_x;
6856 attr_y = game.exit_y;
6862 for (i = 0; i < MAX_PLAYERS; i++)
6864 struct PlayerInfo *player = &stored_player[i];
6865 int jx = player->jx, jy = player->jy;
6867 if (!player->active)
6871 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6879 if (element == EL_ROBOT &&
6880 game.robot_wheel_x >= 0 &&
6881 game.robot_wheel_y >= 0 &&
6882 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
6883 game.engine_version < VERSION_IDENT(3,1,0,0)))
6885 attr_x = game.robot_wheel_x;
6886 attr_y = game.robot_wheel_y;
6889 if (element == EL_PENGUIN)
6892 static int xy[4][2] =
6900 for (i = 0; i < NUM_DIRECTIONS; i++)
6902 int ex = x + xy[i][0];
6903 int ey = y + xy[i][1];
6905 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
6906 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
6907 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
6908 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
6917 MovDir[x][y] = MV_NONE;
6919 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
6920 else if (attr_x > x)
6921 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
6923 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
6924 else if (attr_y > y)
6925 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
6927 if (element == EL_ROBOT)
6931 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6932 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
6933 Moving2Blocked(x, y, &newx, &newy);
6935 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
6936 MovDelay[x][y] = 8 + 8 * !RND(3);
6938 MovDelay[x][y] = 16;
6940 else if (element == EL_PENGUIN)
6946 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6948 boolean first_horiz = RND(2);
6949 int new_move_dir = MovDir[x][y];
6952 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6953 Moving2Blocked(x, y, &newx, &newy);
6955 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6959 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6960 Moving2Blocked(x, y, &newx, &newy);
6962 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6965 MovDir[x][y] = old_move_dir;
6969 else if (element == EL_SATELLITE)
6975 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6977 boolean first_horiz = RND(2);
6978 int new_move_dir = MovDir[x][y];
6981 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6982 Moving2Blocked(x, y, &newx, &newy);
6984 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6988 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6989 Moving2Blocked(x, y, &newx, &newy);
6991 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6994 MovDir[x][y] = old_move_dir;
6998 else if (element == EL_EMC_ANDROID)
7000 static int check_pos[16] =
7002 -1, // 0 => (invalid)
7005 -1, // 3 => (invalid)
7007 0, // 5 => MV_LEFT | MV_UP
7008 2, // 6 => MV_RIGHT | MV_UP
7009 -1, // 7 => (invalid)
7011 6, // 9 => MV_LEFT | MV_DOWN
7012 4, // 10 => MV_RIGHT | MV_DOWN
7013 -1, // 11 => (invalid)
7014 -1, // 12 => (invalid)
7015 -1, // 13 => (invalid)
7016 -1, // 14 => (invalid)
7017 -1, // 15 => (invalid)
7025 { -1, -1, MV_LEFT | MV_UP },
7027 { +1, -1, MV_RIGHT | MV_UP },
7028 { +1, 0, MV_RIGHT },
7029 { +1, +1, MV_RIGHT | MV_DOWN },
7031 { -1, +1, MV_LEFT | MV_DOWN },
7034 int start_pos, check_order;
7035 boolean can_clone = FALSE;
7038 // check if there is any free field around current position
7039 for (i = 0; i < 8; i++)
7041 int newx = x + check_xy[i].dx;
7042 int newy = y + check_xy[i].dy;
7044 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7052 if (can_clone) // randomly find an element to clone
7056 start_pos = check_pos[RND(8)];
7057 check_order = (RND(2) ? -1 : +1);
7059 for (i = 0; i < 8; i++)
7061 int pos_raw = start_pos + i * check_order;
7062 int pos = (pos_raw + 8) % 8;
7063 int newx = x + check_xy[pos].dx;
7064 int newy = y + check_xy[pos].dy;
7066 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7068 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7069 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7071 Store[x][y] = Feld[newx][newy];
7080 if (can_clone) // randomly find a direction to move
7084 start_pos = check_pos[RND(8)];
7085 check_order = (RND(2) ? -1 : +1);
7087 for (i = 0; i < 8; i++)
7089 int pos_raw = start_pos + i * check_order;
7090 int pos = (pos_raw + 8) % 8;
7091 int newx = x + check_xy[pos].dx;
7092 int newy = y + check_xy[pos].dy;
7093 int new_move_dir = check_xy[pos].dir;
7095 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7097 MovDir[x][y] = new_move_dir;
7098 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7107 if (can_clone) // cloning and moving successful
7110 // cannot clone -- try to move towards player
7112 start_pos = check_pos[MovDir[x][y] & 0x0f];
7113 check_order = (RND(2) ? -1 : +1);
7115 for (i = 0; i < 3; i++)
7117 // first check start_pos, then previous/next or (next/previous) pos
7118 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7119 int pos = (pos_raw + 8) % 8;
7120 int newx = x + check_xy[pos].dx;
7121 int newy = y + check_xy[pos].dy;
7122 int new_move_dir = check_xy[pos].dir;
7124 if (IS_PLAYER(newx, newy))
7127 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7129 MovDir[x][y] = new_move_dir;
7130 MovDelay[x][y] = level.android_move_time * 8 + 1;
7137 else if (move_pattern == MV_TURNING_LEFT ||
7138 move_pattern == MV_TURNING_RIGHT ||
7139 move_pattern == MV_TURNING_LEFT_RIGHT ||
7140 move_pattern == MV_TURNING_RIGHT_LEFT ||
7141 move_pattern == MV_TURNING_RANDOM ||
7142 move_pattern == MV_ALL_DIRECTIONS)
7144 boolean can_turn_left =
7145 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7146 boolean can_turn_right =
7147 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7149 if (element_info[element].move_stepsize == 0) // "not moving"
7152 if (move_pattern == MV_TURNING_LEFT)
7153 MovDir[x][y] = left_dir;
7154 else if (move_pattern == MV_TURNING_RIGHT)
7155 MovDir[x][y] = right_dir;
7156 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7157 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7158 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7159 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7160 else if (move_pattern == MV_TURNING_RANDOM)
7161 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7162 can_turn_right && !can_turn_left ? right_dir :
7163 RND(2) ? left_dir : right_dir);
7164 else if (can_turn_left && can_turn_right)
7165 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7166 else if (can_turn_left)
7167 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7168 else if (can_turn_right)
7169 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7171 MovDir[x][y] = back_dir;
7173 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7175 else if (move_pattern == MV_HORIZONTAL ||
7176 move_pattern == MV_VERTICAL)
7178 if (move_pattern & old_move_dir)
7179 MovDir[x][y] = back_dir;
7180 else if (move_pattern == MV_HORIZONTAL)
7181 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7182 else if (move_pattern == MV_VERTICAL)
7183 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7185 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7187 else if (move_pattern & MV_ANY_DIRECTION)
7189 MovDir[x][y] = move_pattern;
7190 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7192 else if (move_pattern & MV_WIND_DIRECTION)
7194 MovDir[x][y] = game.wind_direction;
7195 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7197 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7199 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7200 MovDir[x][y] = left_dir;
7201 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7202 MovDir[x][y] = right_dir;
7204 if (MovDir[x][y] != old_move_dir)
7205 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7207 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7209 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7210 MovDir[x][y] = right_dir;
7211 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7212 MovDir[x][y] = left_dir;
7214 if (MovDir[x][y] != old_move_dir)
7215 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7217 else if (move_pattern == MV_TOWARDS_PLAYER ||
7218 move_pattern == MV_AWAY_FROM_PLAYER)
7220 int attr_x = -1, attr_y = -1;
7222 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7224 if (game.all_players_gone)
7226 attr_x = game.exit_x;
7227 attr_y = game.exit_y;
7233 for (i = 0; i < MAX_PLAYERS; i++)
7235 struct PlayerInfo *player = &stored_player[i];
7236 int jx = player->jx, jy = player->jy;
7238 if (!player->active)
7242 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7250 MovDir[x][y] = MV_NONE;
7252 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7253 else if (attr_x > x)
7254 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7256 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7257 else if (attr_y > y)
7258 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7260 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7262 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7264 boolean first_horiz = RND(2);
7265 int new_move_dir = MovDir[x][y];
7267 if (element_info[element].move_stepsize == 0) // "not moving"
7269 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7270 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7276 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7277 Moving2Blocked(x, y, &newx, &newy);
7279 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7283 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7284 Moving2Blocked(x, y, &newx, &newy);
7286 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7289 MovDir[x][y] = old_move_dir;
7292 else if (move_pattern == MV_WHEN_PUSHED ||
7293 move_pattern == MV_WHEN_DROPPED)
7295 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7296 MovDir[x][y] = MV_NONE;
7300 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7302 static int test_xy[7][2] =
7312 static int test_dir[7] =
7322 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7323 int move_preference = -1000000; // start with very low preference
7324 int new_move_dir = MV_NONE;
7325 int start_test = RND(4);
7328 for (i = 0; i < NUM_DIRECTIONS; i++)
7330 int move_dir = test_dir[start_test + i];
7331 int move_dir_preference;
7333 xx = x + test_xy[start_test + i][0];
7334 yy = y + test_xy[start_test + i][1];
7336 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7337 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7339 new_move_dir = move_dir;
7344 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7347 move_dir_preference = -1 * RunnerVisit[xx][yy];
7348 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7349 move_dir_preference = PlayerVisit[xx][yy];
7351 if (move_dir_preference > move_preference)
7353 // prefer field that has not been visited for the longest time
7354 move_preference = move_dir_preference;
7355 new_move_dir = move_dir;
7357 else if (move_dir_preference == move_preference &&
7358 move_dir == old_move_dir)
7360 // prefer last direction when all directions are preferred equally
7361 move_preference = move_dir_preference;
7362 new_move_dir = move_dir;
7366 MovDir[x][y] = new_move_dir;
7367 if (old_move_dir != new_move_dir)
7368 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7372 static void TurnRound(int x, int y)
7374 int direction = MovDir[x][y];
7378 GfxDir[x][y] = MovDir[x][y];
7380 if (direction != MovDir[x][y])
7384 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7386 ResetGfxFrame(x, y);
7389 static boolean JustBeingPushed(int x, int y)
7393 for (i = 0; i < MAX_PLAYERS; i++)
7395 struct PlayerInfo *player = &stored_player[i];
7397 if (player->active && player->is_pushing && player->MovPos)
7399 int next_jx = player->jx + (player->jx - player->last_jx);
7400 int next_jy = player->jy + (player->jy - player->last_jy);
7402 if (x == next_jx && y == next_jy)
7410 static void StartMoving(int x, int y)
7412 boolean started_moving = FALSE; // some elements can fall _and_ move
7413 int element = Feld[x][y];
7418 if (MovDelay[x][y] == 0)
7419 GfxAction[x][y] = ACTION_DEFAULT;
7421 if (CAN_FALL(element) && y < lev_fieldy - 1)
7423 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7424 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7425 if (JustBeingPushed(x, y))
7428 if (element == EL_QUICKSAND_FULL)
7430 if (IS_FREE(x, y + 1))
7432 InitMovingField(x, y, MV_DOWN);
7433 started_moving = TRUE;
7435 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7436 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7437 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7438 Store[x][y] = EL_ROCK;
7440 Store[x][y] = EL_ROCK;
7443 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7445 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7447 if (!MovDelay[x][y])
7449 MovDelay[x][y] = TILEY + 1;
7451 ResetGfxAnimation(x, y);
7452 ResetGfxAnimation(x, y + 1);
7457 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7458 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7465 Feld[x][y] = EL_QUICKSAND_EMPTY;
7466 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7467 Store[x][y + 1] = Store[x][y];
7470 PlayLevelSoundAction(x, y, ACTION_FILLING);
7472 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7474 if (!MovDelay[x][y])
7476 MovDelay[x][y] = TILEY + 1;
7478 ResetGfxAnimation(x, y);
7479 ResetGfxAnimation(x, y + 1);
7484 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7485 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7492 Feld[x][y] = EL_QUICKSAND_EMPTY;
7493 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7494 Store[x][y + 1] = Store[x][y];
7497 PlayLevelSoundAction(x, y, ACTION_FILLING);
7500 else if (element == EL_QUICKSAND_FAST_FULL)
7502 if (IS_FREE(x, y + 1))
7504 InitMovingField(x, y, MV_DOWN);
7505 started_moving = TRUE;
7507 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7508 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7509 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7510 Store[x][y] = EL_ROCK;
7512 Store[x][y] = EL_ROCK;
7515 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7517 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7519 if (!MovDelay[x][y])
7521 MovDelay[x][y] = TILEY + 1;
7523 ResetGfxAnimation(x, y);
7524 ResetGfxAnimation(x, y + 1);
7529 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7530 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7537 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7538 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7539 Store[x][y + 1] = Store[x][y];
7542 PlayLevelSoundAction(x, y, ACTION_FILLING);
7544 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7546 if (!MovDelay[x][y])
7548 MovDelay[x][y] = TILEY + 1;
7550 ResetGfxAnimation(x, y);
7551 ResetGfxAnimation(x, y + 1);
7556 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7557 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7564 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7565 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7566 Store[x][y + 1] = Store[x][y];
7569 PlayLevelSoundAction(x, y, ACTION_FILLING);
7572 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7573 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7575 InitMovingField(x, y, MV_DOWN);
7576 started_moving = TRUE;
7578 Feld[x][y] = EL_QUICKSAND_FILLING;
7579 Store[x][y] = element;
7581 PlayLevelSoundAction(x, y, ACTION_FILLING);
7583 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7584 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7586 InitMovingField(x, y, MV_DOWN);
7587 started_moving = TRUE;
7589 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7590 Store[x][y] = element;
7592 PlayLevelSoundAction(x, y, ACTION_FILLING);
7594 else if (element == EL_MAGIC_WALL_FULL)
7596 if (IS_FREE(x, y + 1))
7598 InitMovingField(x, y, MV_DOWN);
7599 started_moving = TRUE;
7601 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7602 Store[x][y] = EL_CHANGED(Store[x][y]);
7604 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7606 if (!MovDelay[x][y])
7607 MovDelay[x][y] = TILEY / 4 + 1;
7616 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7617 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7618 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7622 else if (element == EL_BD_MAGIC_WALL_FULL)
7624 if (IS_FREE(x, y + 1))
7626 InitMovingField(x, y, MV_DOWN);
7627 started_moving = TRUE;
7629 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7630 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7632 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7634 if (!MovDelay[x][y])
7635 MovDelay[x][y] = TILEY / 4 + 1;
7644 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7645 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7646 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7650 else if (element == EL_DC_MAGIC_WALL_FULL)
7652 if (IS_FREE(x, y + 1))
7654 InitMovingField(x, y, MV_DOWN);
7655 started_moving = TRUE;
7657 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7658 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7660 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7662 if (!MovDelay[x][y])
7663 MovDelay[x][y] = TILEY / 4 + 1;
7672 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7673 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7674 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7678 else if ((CAN_PASS_MAGIC_WALL(element) &&
7679 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7680 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7681 (CAN_PASS_DC_MAGIC_WALL(element) &&
7682 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7685 InitMovingField(x, y, MV_DOWN);
7686 started_moving = TRUE;
7689 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7690 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7691 EL_DC_MAGIC_WALL_FILLING);
7692 Store[x][y] = element;
7694 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7696 SplashAcid(x, y + 1);
7698 InitMovingField(x, y, MV_DOWN);
7699 started_moving = TRUE;
7701 Store[x][y] = EL_ACID;
7704 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7705 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7706 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7707 CAN_FALL(element) && WasJustFalling[x][y] &&
7708 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7710 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7711 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7712 (Feld[x][y + 1] == EL_BLOCKED)))
7714 /* this is needed for a special case not covered by calling "Impact()"
7715 from "ContinueMoving()": if an element moves to a tile directly below
7716 another element which was just falling on that tile (which was empty
7717 in the previous frame), the falling element above would just stop
7718 instead of smashing the element below (in previous version, the above
7719 element was just checked for "moving" instead of "falling", resulting
7720 in incorrect smashes caused by horizontal movement of the above
7721 element; also, the case of the player being the element to smash was
7722 simply not covered here... :-/ ) */
7724 CheckCollision[x][y] = 0;
7725 CheckImpact[x][y] = 0;
7729 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7731 if (MovDir[x][y] == MV_NONE)
7733 InitMovingField(x, y, MV_DOWN);
7734 started_moving = TRUE;
7737 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7739 if (WasJustFalling[x][y]) // prevent animation from being restarted
7740 MovDir[x][y] = MV_DOWN;
7742 InitMovingField(x, y, MV_DOWN);
7743 started_moving = TRUE;
7745 else if (element == EL_AMOEBA_DROP)
7747 Feld[x][y] = EL_AMOEBA_GROWING;
7748 Store[x][y] = EL_AMOEBA_WET;
7750 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7751 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7752 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7753 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7755 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7756 (IS_FREE(x - 1, y + 1) ||
7757 Feld[x - 1][y + 1] == EL_ACID));
7758 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7759 (IS_FREE(x + 1, y + 1) ||
7760 Feld[x + 1][y + 1] == EL_ACID));
7761 boolean can_fall_any = (can_fall_left || can_fall_right);
7762 boolean can_fall_both = (can_fall_left && can_fall_right);
7763 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7765 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7767 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7768 can_fall_right = FALSE;
7769 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7770 can_fall_left = FALSE;
7771 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7772 can_fall_right = FALSE;
7773 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7774 can_fall_left = FALSE;
7776 can_fall_any = (can_fall_left || can_fall_right);
7777 can_fall_both = FALSE;
7782 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7783 can_fall_right = FALSE; // slip down on left side
7785 can_fall_left = !(can_fall_right = RND(2));
7787 can_fall_both = FALSE;
7792 // if not determined otherwise, prefer left side for slipping down
7793 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7794 started_moving = TRUE;
7797 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7799 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7800 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7801 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7802 int belt_dir = game.belt_dir[belt_nr];
7804 if ((belt_dir == MV_LEFT && left_is_free) ||
7805 (belt_dir == MV_RIGHT && right_is_free))
7807 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7809 InitMovingField(x, y, belt_dir);
7810 started_moving = TRUE;
7812 Pushed[x][y] = TRUE;
7813 Pushed[nextx][y] = TRUE;
7815 GfxAction[x][y] = ACTION_DEFAULT;
7819 MovDir[x][y] = 0; // if element was moving, stop it
7824 // not "else if" because of elements that can fall and move (EL_SPRING)
7825 if (CAN_MOVE(element) && !started_moving)
7827 int move_pattern = element_info[element].move_pattern;
7830 Moving2Blocked(x, y, &newx, &newy);
7832 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7835 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7836 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7838 WasJustMoving[x][y] = 0;
7839 CheckCollision[x][y] = 0;
7841 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7843 if (Feld[x][y] != element) // element has changed
7847 if (!MovDelay[x][y]) // start new movement phase
7849 // all objects that can change their move direction after each step
7850 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7852 if (element != EL_YAMYAM &&
7853 element != EL_DARK_YAMYAM &&
7854 element != EL_PACMAN &&
7855 !(move_pattern & MV_ANY_DIRECTION) &&
7856 move_pattern != MV_TURNING_LEFT &&
7857 move_pattern != MV_TURNING_RIGHT &&
7858 move_pattern != MV_TURNING_LEFT_RIGHT &&
7859 move_pattern != MV_TURNING_RIGHT_LEFT &&
7860 move_pattern != MV_TURNING_RANDOM)
7864 if (MovDelay[x][y] && (element == EL_BUG ||
7865 element == EL_SPACESHIP ||
7866 element == EL_SP_SNIKSNAK ||
7867 element == EL_SP_ELECTRON ||
7868 element == EL_MOLE))
7869 TEST_DrawLevelField(x, y);
7873 if (MovDelay[x][y]) // wait some time before next movement
7877 if (element == EL_ROBOT ||
7878 element == EL_YAMYAM ||
7879 element == EL_DARK_YAMYAM)
7881 DrawLevelElementAnimationIfNeeded(x, y, element);
7882 PlayLevelSoundAction(x, y, ACTION_WAITING);
7884 else if (element == EL_SP_ELECTRON)
7885 DrawLevelElementAnimationIfNeeded(x, y, element);
7886 else if (element == EL_DRAGON)
7889 int dir = MovDir[x][y];
7890 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
7891 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
7892 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
7893 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
7894 dir == MV_UP ? IMG_FLAMES_1_UP :
7895 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
7896 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
7898 GfxAction[x][y] = ACTION_ATTACKING;
7900 if (IS_PLAYER(x, y))
7901 DrawPlayerField(x, y);
7903 TEST_DrawLevelField(x, y);
7905 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
7907 for (i = 1; i <= 3; i++)
7909 int xx = x + i * dx;
7910 int yy = y + i * dy;
7911 int sx = SCREENX(xx);
7912 int sy = SCREENY(yy);
7913 int flame_graphic = graphic + (i - 1);
7915 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
7920 int flamed = MovingOrBlocked2Element(xx, yy);
7922 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
7925 RemoveMovingField(xx, yy);
7927 ChangeDelay[xx][yy] = 0;
7929 Feld[xx][yy] = EL_FLAMES;
7931 if (IN_SCR_FIELD(sx, sy))
7933 TEST_DrawLevelFieldCrumbled(xx, yy);
7934 DrawGraphic(sx, sy, flame_graphic, frame);
7939 if (Feld[xx][yy] == EL_FLAMES)
7940 Feld[xx][yy] = EL_EMPTY;
7941 TEST_DrawLevelField(xx, yy);
7946 if (MovDelay[x][y]) // element still has to wait some time
7948 PlayLevelSoundAction(x, y, ACTION_WAITING);
7954 // now make next step
7956 Moving2Blocked(x, y, &newx, &newy); // get next screen position
7958 if (DONT_COLLIDE_WITH(element) &&
7959 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
7960 !PLAYER_ENEMY_PROTECTED(newx, newy))
7962 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
7967 else if (CAN_MOVE_INTO_ACID(element) &&
7968 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
7969 !IS_MV_DIAGONAL(MovDir[x][y]) &&
7970 (MovDir[x][y] == MV_DOWN ||
7971 game.engine_version >= VERSION_IDENT(3,1,0,0)))
7973 SplashAcid(newx, newy);
7974 Store[x][y] = EL_ACID;
7976 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
7978 if (Feld[newx][newy] == EL_EXIT_OPEN ||
7979 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
7980 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
7981 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
7984 TEST_DrawLevelField(x, y);
7986 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
7987 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
7988 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
7990 game.friends_still_needed--;
7991 if (!game.friends_still_needed &&
7993 game.all_players_gone)
7998 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
8000 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8001 TEST_DrawLevelField(newx, newy);
8003 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8005 else if (!IS_FREE(newx, newy))
8007 GfxAction[x][y] = ACTION_WAITING;
8009 if (IS_PLAYER(x, y))
8010 DrawPlayerField(x, y);
8012 TEST_DrawLevelField(x, y);
8017 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8019 if (IS_FOOD_PIG(Feld[newx][newy]))
8021 if (IS_MOVING(newx, newy))
8022 RemoveMovingField(newx, newy);
8025 Feld[newx][newy] = EL_EMPTY;
8026 TEST_DrawLevelField(newx, newy);
8029 PlayLevelSound(x, y, SND_PIG_DIGGING);
8031 else if (!IS_FREE(newx, newy))
8033 if (IS_PLAYER(x, y))
8034 DrawPlayerField(x, y);
8036 TEST_DrawLevelField(x, y);
8041 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8043 if (Store[x][y] != EL_EMPTY)
8045 boolean can_clone = FALSE;
8048 // check if element to clone is still there
8049 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8051 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8059 // cannot clone or target field not free anymore -- do not clone
8060 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8061 Store[x][y] = EL_EMPTY;
8064 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8066 if (IS_MV_DIAGONAL(MovDir[x][y]))
8068 int diagonal_move_dir = MovDir[x][y];
8069 int stored = Store[x][y];
8070 int change_delay = 8;
8073 // android is moving diagonally
8075 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8077 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8078 GfxElement[x][y] = EL_EMC_ANDROID;
8079 GfxAction[x][y] = ACTION_SHRINKING;
8080 GfxDir[x][y] = diagonal_move_dir;
8081 ChangeDelay[x][y] = change_delay;
8083 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8086 DrawLevelGraphicAnimation(x, y, graphic);
8087 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8089 if (Feld[newx][newy] == EL_ACID)
8091 SplashAcid(newx, newy);
8096 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8098 Store[newx][newy] = EL_EMC_ANDROID;
8099 GfxElement[newx][newy] = EL_EMC_ANDROID;
8100 GfxAction[newx][newy] = ACTION_GROWING;
8101 GfxDir[newx][newy] = diagonal_move_dir;
8102 ChangeDelay[newx][newy] = change_delay;
8104 graphic = el_act_dir2img(GfxElement[newx][newy],
8105 GfxAction[newx][newy], GfxDir[newx][newy]);
8107 DrawLevelGraphicAnimation(newx, newy, graphic);
8108 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8114 Feld[newx][newy] = EL_EMPTY;
8115 TEST_DrawLevelField(newx, newy);
8117 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8120 else if (!IS_FREE(newx, newy))
8125 else if (IS_CUSTOM_ELEMENT(element) &&
8126 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8128 if (!DigFieldByCE(newx, newy, element))
8131 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8133 RunnerVisit[x][y] = FrameCounter;
8134 PlayerVisit[x][y] /= 8; // expire player visit path
8137 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8139 if (!IS_FREE(newx, newy))
8141 if (IS_PLAYER(x, y))
8142 DrawPlayerField(x, y);
8144 TEST_DrawLevelField(x, y);
8150 boolean wanna_flame = !RND(10);
8151 int dx = newx - x, dy = newy - y;
8152 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8153 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8154 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8155 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8156 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8157 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8160 IS_CLASSIC_ENEMY(element1) ||
8161 IS_CLASSIC_ENEMY(element2)) &&
8162 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8163 element1 != EL_FLAMES && element2 != EL_FLAMES)
8165 ResetGfxAnimation(x, y);
8166 GfxAction[x][y] = ACTION_ATTACKING;
8168 if (IS_PLAYER(x, y))
8169 DrawPlayerField(x, y);
8171 TEST_DrawLevelField(x, y);
8173 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8175 MovDelay[x][y] = 50;
8177 Feld[newx][newy] = EL_FLAMES;
8178 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8179 Feld[newx1][newy1] = EL_FLAMES;
8180 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8181 Feld[newx2][newy2] = EL_FLAMES;
8187 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8188 Feld[newx][newy] == EL_DIAMOND)
8190 if (IS_MOVING(newx, newy))
8191 RemoveMovingField(newx, newy);
8194 Feld[newx][newy] = EL_EMPTY;
8195 TEST_DrawLevelField(newx, newy);
8198 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8200 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8201 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8203 if (AmoebaNr[newx][newy])
8205 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8206 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8207 Feld[newx][newy] == EL_BD_AMOEBA)
8208 AmoebaCnt[AmoebaNr[newx][newy]]--;
8211 if (IS_MOVING(newx, newy))
8213 RemoveMovingField(newx, newy);
8217 Feld[newx][newy] = EL_EMPTY;
8218 TEST_DrawLevelField(newx, newy);
8221 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8223 else if ((element == EL_PACMAN || element == EL_MOLE)
8224 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8226 if (AmoebaNr[newx][newy])
8228 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8229 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8230 Feld[newx][newy] == EL_BD_AMOEBA)
8231 AmoebaCnt[AmoebaNr[newx][newy]]--;
8234 if (element == EL_MOLE)
8236 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8237 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8239 ResetGfxAnimation(x, y);
8240 GfxAction[x][y] = ACTION_DIGGING;
8241 TEST_DrawLevelField(x, y);
8243 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8245 return; // wait for shrinking amoeba
8247 else // element == EL_PACMAN
8249 Feld[newx][newy] = EL_EMPTY;
8250 TEST_DrawLevelField(newx, newy);
8251 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8254 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8255 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8256 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8258 // wait for shrinking amoeba to completely disappear
8261 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8263 // object was running against a wall
8267 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8268 DrawLevelElementAnimation(x, y, element);
8270 if (DONT_TOUCH(element))
8271 TestIfBadThingTouchesPlayer(x, y);
8276 InitMovingField(x, y, MovDir[x][y]);
8278 PlayLevelSoundAction(x, y, ACTION_MOVING);
8282 ContinueMoving(x, y);
8285 void ContinueMoving(int x, int y)
8287 int element = Feld[x][y];
8288 struct ElementInfo *ei = &element_info[element];
8289 int direction = MovDir[x][y];
8290 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8291 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8292 int newx = x + dx, newy = y + dy;
8293 int stored = Store[x][y];
8294 int stored_new = Store[newx][newy];
8295 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8296 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8297 boolean last_line = (newy == lev_fieldy - 1);
8299 MovPos[x][y] += getElementMoveStepsize(x, y);
8301 if (pushed_by_player) // special case: moving object pushed by player
8302 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8304 if (ABS(MovPos[x][y]) < TILEX)
8306 TEST_DrawLevelField(x, y);
8308 return; // element is still moving
8311 // element reached destination field
8313 Feld[x][y] = EL_EMPTY;
8314 Feld[newx][newy] = element;
8315 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8317 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8319 element = Feld[newx][newy] = EL_ACID;
8321 else if (element == EL_MOLE)
8323 Feld[x][y] = EL_SAND;
8325 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8327 else if (element == EL_QUICKSAND_FILLING)
8329 element = Feld[newx][newy] = get_next_element(element);
8330 Store[newx][newy] = Store[x][y];
8332 else if (element == EL_QUICKSAND_EMPTYING)
8334 Feld[x][y] = get_next_element(element);
8335 element = Feld[newx][newy] = Store[x][y];
8337 else if (element == EL_QUICKSAND_FAST_FILLING)
8339 element = Feld[newx][newy] = get_next_element(element);
8340 Store[newx][newy] = Store[x][y];
8342 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8344 Feld[x][y] = get_next_element(element);
8345 element = Feld[newx][newy] = Store[x][y];
8347 else if (element == EL_MAGIC_WALL_FILLING)
8349 element = Feld[newx][newy] = get_next_element(element);
8350 if (!game.magic_wall_active)
8351 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8352 Store[newx][newy] = Store[x][y];
8354 else if (element == EL_MAGIC_WALL_EMPTYING)
8356 Feld[x][y] = get_next_element(element);
8357 if (!game.magic_wall_active)
8358 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8359 element = Feld[newx][newy] = Store[x][y];
8361 InitField(newx, newy, FALSE);
8363 else if (element == EL_BD_MAGIC_WALL_FILLING)
8365 element = Feld[newx][newy] = get_next_element(element);
8366 if (!game.magic_wall_active)
8367 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8368 Store[newx][newy] = Store[x][y];
8370 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8372 Feld[x][y] = get_next_element(element);
8373 if (!game.magic_wall_active)
8374 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8375 element = Feld[newx][newy] = Store[x][y];
8377 InitField(newx, newy, FALSE);
8379 else if (element == EL_DC_MAGIC_WALL_FILLING)
8381 element = Feld[newx][newy] = get_next_element(element);
8382 if (!game.magic_wall_active)
8383 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8384 Store[newx][newy] = Store[x][y];
8386 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8388 Feld[x][y] = get_next_element(element);
8389 if (!game.magic_wall_active)
8390 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8391 element = Feld[newx][newy] = Store[x][y];
8393 InitField(newx, newy, FALSE);
8395 else if (element == EL_AMOEBA_DROPPING)
8397 Feld[x][y] = get_next_element(element);
8398 element = Feld[newx][newy] = Store[x][y];
8400 else if (element == EL_SOKOBAN_OBJECT)
8403 Feld[x][y] = Back[x][y];
8405 if (Back[newx][newy])
8406 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8408 Back[x][y] = Back[newx][newy] = 0;
8411 Store[x][y] = EL_EMPTY;
8416 MovDelay[newx][newy] = 0;
8418 if (CAN_CHANGE_OR_HAS_ACTION(element))
8420 // copy element change control values to new field
8421 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8422 ChangePage[newx][newy] = ChangePage[x][y];
8423 ChangeCount[newx][newy] = ChangeCount[x][y];
8424 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8427 CustomValue[newx][newy] = CustomValue[x][y];
8429 ChangeDelay[x][y] = 0;
8430 ChangePage[x][y] = -1;
8431 ChangeCount[x][y] = 0;
8432 ChangeEvent[x][y] = -1;
8434 CustomValue[x][y] = 0;
8436 // copy animation control values to new field
8437 GfxFrame[newx][newy] = GfxFrame[x][y];
8438 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8439 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8440 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8442 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8444 // some elements can leave other elements behind after moving
8445 if (ei->move_leave_element != EL_EMPTY &&
8446 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8447 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8449 int move_leave_element = ei->move_leave_element;
8451 // this makes it possible to leave the removed element again
8452 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8453 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8455 Feld[x][y] = move_leave_element;
8457 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8458 MovDir[x][y] = direction;
8460 InitField(x, y, FALSE);
8462 if (GFX_CRUMBLED(Feld[x][y]))
8463 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8465 if (ELEM_IS_PLAYER(move_leave_element))
8466 RelocatePlayer(x, y, move_leave_element);
8469 // do this after checking for left-behind element
8470 ResetGfxAnimation(x, y); // reset animation values for old field
8472 if (!CAN_MOVE(element) ||
8473 (CAN_FALL(element) && direction == MV_DOWN &&
8474 (element == EL_SPRING ||
8475 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8476 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8477 GfxDir[x][y] = MovDir[newx][newy] = 0;
8479 TEST_DrawLevelField(x, y);
8480 TEST_DrawLevelField(newx, newy);
8482 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8484 // prevent pushed element from moving on in pushed direction
8485 if (pushed_by_player && CAN_MOVE(element) &&
8486 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8487 !(element_info[element].move_pattern & direction))
8488 TurnRound(newx, newy);
8490 // prevent elements on conveyor belt from moving on in last direction
8491 if (pushed_by_conveyor && CAN_FALL(element) &&
8492 direction & MV_HORIZONTAL)
8493 MovDir[newx][newy] = 0;
8495 if (!pushed_by_player)
8497 int nextx = newx + dx, nexty = newy + dy;
8498 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8500 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8502 if (CAN_FALL(element) && direction == MV_DOWN)
8503 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8505 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8506 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8508 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8509 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8512 if (DONT_TOUCH(element)) // object may be nasty to player or others
8514 TestIfBadThingTouchesPlayer(newx, newy);
8515 TestIfBadThingTouchesFriend(newx, newy);
8517 if (!IS_CUSTOM_ELEMENT(element))
8518 TestIfBadThingTouchesOtherBadThing(newx, newy);
8520 else if (element == EL_PENGUIN)
8521 TestIfFriendTouchesBadThing(newx, newy);
8523 if (DONT_GET_HIT_BY(element))
8525 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8528 // give the player one last chance (one more frame) to move away
8529 if (CAN_FALL(element) && direction == MV_DOWN &&
8530 (last_line || (!IS_FREE(x, newy + 1) &&
8531 (!IS_PLAYER(x, newy + 1) ||
8532 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8535 if (pushed_by_player && !game.use_change_when_pushing_bug)
8537 int push_side = MV_DIR_OPPOSITE(direction);
8538 struct PlayerInfo *player = PLAYERINFO(x, y);
8540 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8541 player->index_bit, push_side);
8542 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8543 player->index_bit, push_side);
8546 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8547 MovDelay[newx][newy] = 1;
8549 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8551 TestIfElementTouchesCustomElement(x, y); // empty or new element
8552 TestIfElementHitsCustomElement(newx, newy, direction);
8553 TestIfPlayerTouchesCustomElement(newx, newy);
8554 TestIfElementTouchesCustomElement(newx, newy);
8556 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8557 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8558 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8559 MV_DIR_OPPOSITE(direction));
8562 int AmoebeNachbarNr(int ax, int ay)
8565 int element = Feld[ax][ay];
8567 static int xy[4][2] =
8575 for (i = 0; i < NUM_DIRECTIONS; i++)
8577 int x = ax + xy[i][0];
8578 int y = ay + xy[i][1];
8580 if (!IN_LEV_FIELD(x, y))
8583 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8584 group_nr = AmoebaNr[x][y];
8590 static void AmoebenVereinigen(int ax, int ay)
8592 int i, x, y, xx, yy;
8593 int new_group_nr = AmoebaNr[ax][ay];
8594 static int xy[4][2] =
8602 if (new_group_nr == 0)
8605 for (i = 0; i < NUM_DIRECTIONS; i++)
8610 if (!IN_LEV_FIELD(x, y))
8613 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8614 Feld[x][y] == EL_BD_AMOEBA ||
8615 Feld[x][y] == EL_AMOEBA_DEAD) &&
8616 AmoebaNr[x][y] != new_group_nr)
8618 int old_group_nr = AmoebaNr[x][y];
8620 if (old_group_nr == 0)
8623 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8624 AmoebaCnt[old_group_nr] = 0;
8625 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8626 AmoebaCnt2[old_group_nr] = 0;
8628 SCAN_PLAYFIELD(xx, yy)
8630 if (AmoebaNr[xx][yy] == old_group_nr)
8631 AmoebaNr[xx][yy] = new_group_nr;
8637 void AmoebeUmwandeln(int ax, int ay)
8641 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8643 int group_nr = AmoebaNr[ax][ay];
8648 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8649 printf("AmoebeUmwandeln(): This should never happen!\n");
8654 SCAN_PLAYFIELD(x, y)
8656 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8659 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8663 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8664 SND_AMOEBA_TURNING_TO_GEM :
8665 SND_AMOEBA_TURNING_TO_ROCK));
8670 static int xy[4][2] =
8678 for (i = 0; i < NUM_DIRECTIONS; i++)
8683 if (!IN_LEV_FIELD(x, y))
8686 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8688 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8689 SND_AMOEBA_TURNING_TO_GEM :
8690 SND_AMOEBA_TURNING_TO_ROCK));
8697 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8700 int group_nr = AmoebaNr[ax][ay];
8701 boolean done = FALSE;
8706 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8707 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8712 SCAN_PLAYFIELD(x, y)
8714 if (AmoebaNr[x][y] == group_nr &&
8715 (Feld[x][y] == EL_AMOEBA_DEAD ||
8716 Feld[x][y] == EL_BD_AMOEBA ||
8717 Feld[x][y] == EL_AMOEBA_GROWING))
8720 Feld[x][y] = new_element;
8721 InitField(x, y, FALSE);
8722 TEST_DrawLevelField(x, y);
8728 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8729 SND_BD_AMOEBA_TURNING_TO_ROCK :
8730 SND_BD_AMOEBA_TURNING_TO_GEM));
8733 static void AmoebeWaechst(int x, int y)
8735 static unsigned int sound_delay = 0;
8736 static unsigned int sound_delay_value = 0;
8738 if (!MovDelay[x][y]) // start new growing cycle
8742 if (DelayReached(&sound_delay, sound_delay_value))
8744 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8745 sound_delay_value = 30;
8749 if (MovDelay[x][y]) // wait some time before growing bigger
8752 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8754 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8755 6 - MovDelay[x][y]);
8757 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8760 if (!MovDelay[x][y])
8762 Feld[x][y] = Store[x][y];
8764 TEST_DrawLevelField(x, y);
8769 static void AmoebaDisappearing(int x, int y)
8771 static unsigned int sound_delay = 0;
8772 static unsigned int sound_delay_value = 0;
8774 if (!MovDelay[x][y]) // start new shrinking cycle
8778 if (DelayReached(&sound_delay, sound_delay_value))
8779 sound_delay_value = 30;
8782 if (MovDelay[x][y]) // wait some time before shrinking
8785 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8787 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8788 6 - MovDelay[x][y]);
8790 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8793 if (!MovDelay[x][y])
8795 Feld[x][y] = EL_EMPTY;
8796 TEST_DrawLevelField(x, y);
8798 // don't let mole enter this field in this cycle;
8799 // (give priority to objects falling to this field from above)
8805 static void AmoebeAbleger(int ax, int ay)
8808 int element = Feld[ax][ay];
8809 int graphic = el2img(element);
8810 int newax = ax, neway = ay;
8811 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8812 static int xy[4][2] =
8820 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8822 Feld[ax][ay] = EL_AMOEBA_DEAD;
8823 TEST_DrawLevelField(ax, ay);
8827 if (IS_ANIMATED(graphic))
8828 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8830 if (!MovDelay[ax][ay]) // start making new amoeba field
8831 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8833 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8836 if (MovDelay[ax][ay])
8840 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8843 int x = ax + xy[start][0];
8844 int y = ay + xy[start][1];
8846 if (!IN_LEV_FIELD(x, y))
8849 if (IS_FREE(x, y) ||
8850 CAN_GROW_INTO(Feld[x][y]) ||
8851 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8852 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8858 if (newax == ax && neway == ay)
8861 else // normal or "filled" (BD style) amoeba
8864 boolean waiting_for_player = FALSE;
8866 for (i = 0; i < NUM_DIRECTIONS; i++)
8868 int j = (start + i) % 4;
8869 int x = ax + xy[j][0];
8870 int y = ay + xy[j][1];
8872 if (!IN_LEV_FIELD(x, y))
8875 if (IS_FREE(x, y) ||
8876 CAN_GROW_INTO(Feld[x][y]) ||
8877 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8878 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8884 else if (IS_PLAYER(x, y))
8885 waiting_for_player = TRUE;
8888 if (newax == ax && neway == ay) // amoeba cannot grow
8890 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
8892 Feld[ax][ay] = EL_AMOEBA_DEAD;
8893 TEST_DrawLevelField(ax, ay);
8894 AmoebaCnt[AmoebaNr[ax][ay]]--;
8896 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
8898 if (element == EL_AMOEBA_FULL)
8899 AmoebeUmwandeln(ax, ay);
8900 else if (element == EL_BD_AMOEBA)
8901 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
8906 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
8908 // amoeba gets larger by growing in some direction
8910 int new_group_nr = AmoebaNr[ax][ay];
8913 if (new_group_nr == 0)
8915 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
8916 printf("AmoebeAbleger(): This should never happen!\n");
8921 AmoebaNr[newax][neway] = new_group_nr;
8922 AmoebaCnt[new_group_nr]++;
8923 AmoebaCnt2[new_group_nr]++;
8925 // if amoeba touches other amoeba(s) after growing, unify them
8926 AmoebenVereinigen(newax, neway);
8928 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
8930 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
8936 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
8937 (neway == lev_fieldy - 1 && newax != ax))
8939 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
8940 Store[newax][neway] = element;
8942 else if (neway == ay || element == EL_EMC_DRIPPER)
8944 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
8946 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
8950 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
8951 Feld[ax][ay] = EL_AMOEBA_DROPPING;
8952 Store[ax][ay] = EL_AMOEBA_DROP;
8953 ContinueMoving(ax, ay);
8957 TEST_DrawLevelField(newax, neway);
8960 static void Life(int ax, int ay)
8964 int element = Feld[ax][ay];
8965 int graphic = el2img(element);
8966 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
8968 boolean changed = FALSE;
8970 if (IS_ANIMATED(graphic))
8971 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8976 if (!MovDelay[ax][ay]) // start new "game of life" cycle
8977 MovDelay[ax][ay] = life_time;
8979 if (MovDelay[ax][ay]) // wait some time before next cycle
8982 if (MovDelay[ax][ay])
8986 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
8988 int xx = ax+x1, yy = ay+y1;
8989 int old_element = Feld[xx][yy];
8990 int num_neighbours = 0;
8992 if (!IN_LEV_FIELD(xx, yy))
8995 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
8997 int x = xx+x2, y = yy+y2;
8999 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9002 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9003 boolean is_neighbour = FALSE;
9005 if (level.use_life_bugs)
9007 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9008 (IS_FREE(x, y) && Stop[x][y]));
9011 (Last[x][y] == element || is_player_cell);
9017 boolean is_free = FALSE;
9019 if (level.use_life_bugs)
9020 is_free = (IS_FREE(xx, yy));
9022 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9024 if (xx == ax && yy == ay) // field in the middle
9026 if (num_neighbours < life_parameter[0] ||
9027 num_neighbours > life_parameter[1])
9029 Feld[xx][yy] = EL_EMPTY;
9030 if (Feld[xx][yy] != old_element)
9031 TEST_DrawLevelField(xx, yy);
9032 Stop[xx][yy] = TRUE;
9036 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9037 { // free border field
9038 if (num_neighbours >= life_parameter[2] &&
9039 num_neighbours <= life_parameter[3])
9041 Feld[xx][yy] = element;
9042 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9043 if (Feld[xx][yy] != old_element)
9044 TEST_DrawLevelField(xx, yy);
9045 Stop[xx][yy] = TRUE;
9052 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9053 SND_GAME_OF_LIFE_GROWING);
9056 static void InitRobotWheel(int x, int y)
9058 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9061 static void RunRobotWheel(int x, int y)
9063 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9066 static void StopRobotWheel(int x, int y)
9068 if (game.robot_wheel_x == x &&
9069 game.robot_wheel_y == y)
9071 game.robot_wheel_x = -1;
9072 game.robot_wheel_y = -1;
9073 game.robot_wheel_active = FALSE;
9077 static void InitTimegateWheel(int x, int y)
9079 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9082 static void RunTimegateWheel(int x, int y)
9084 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9087 static void InitMagicBallDelay(int x, int y)
9089 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9092 static void ActivateMagicBall(int bx, int by)
9096 if (level.ball_random)
9098 int pos_border = RND(8); // select one of the eight border elements
9099 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9100 int xx = pos_content % 3;
9101 int yy = pos_content / 3;
9106 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9107 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9111 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9113 int xx = x - bx + 1;
9114 int yy = y - by + 1;
9116 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9117 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9121 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9124 static void CheckExit(int x, int y)
9126 if (game.gems_still_needed > 0 ||
9127 game.sokoban_fields_still_needed > 0 ||
9128 game.sokoban_objects_still_needed > 0 ||
9129 game.lights_still_needed > 0)
9131 int element = Feld[x][y];
9132 int graphic = el2img(element);
9134 if (IS_ANIMATED(graphic))
9135 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9140 // do not re-open exit door closed after last player
9141 if (game.all_players_gone)
9144 Feld[x][y] = EL_EXIT_OPENING;
9146 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9149 static void CheckExitEM(int x, int y)
9151 if (game.gems_still_needed > 0 ||
9152 game.sokoban_fields_still_needed > 0 ||
9153 game.sokoban_objects_still_needed > 0 ||
9154 game.lights_still_needed > 0)
9156 int element = Feld[x][y];
9157 int graphic = el2img(element);
9159 if (IS_ANIMATED(graphic))
9160 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9165 // do not re-open exit door closed after last player
9166 if (game.all_players_gone)
9169 Feld[x][y] = EL_EM_EXIT_OPENING;
9171 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9174 static void CheckExitSteel(int x, int y)
9176 if (game.gems_still_needed > 0 ||
9177 game.sokoban_fields_still_needed > 0 ||
9178 game.sokoban_objects_still_needed > 0 ||
9179 game.lights_still_needed > 0)
9181 int element = Feld[x][y];
9182 int graphic = el2img(element);
9184 if (IS_ANIMATED(graphic))
9185 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9190 // do not re-open exit door closed after last player
9191 if (game.all_players_gone)
9194 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9196 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9199 static void CheckExitSteelEM(int x, int y)
9201 if (game.gems_still_needed > 0 ||
9202 game.sokoban_fields_still_needed > 0 ||
9203 game.sokoban_objects_still_needed > 0 ||
9204 game.lights_still_needed > 0)
9206 int element = Feld[x][y];
9207 int graphic = el2img(element);
9209 if (IS_ANIMATED(graphic))
9210 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9215 // do not re-open exit door closed after last player
9216 if (game.all_players_gone)
9219 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9221 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9224 static void CheckExitSP(int x, int y)
9226 if (game.gems_still_needed > 0)
9228 int element = Feld[x][y];
9229 int graphic = el2img(element);
9231 if (IS_ANIMATED(graphic))
9232 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9237 // do not re-open exit door closed after last player
9238 if (game.all_players_gone)
9241 Feld[x][y] = EL_SP_EXIT_OPENING;
9243 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9246 static void CloseAllOpenTimegates(void)
9250 SCAN_PLAYFIELD(x, y)
9252 int element = Feld[x][y];
9254 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9256 Feld[x][y] = EL_TIMEGATE_CLOSING;
9258 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9263 static void DrawTwinkleOnField(int x, int y)
9265 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9268 if (Feld[x][y] == EL_BD_DIAMOND)
9271 if (MovDelay[x][y] == 0) // next animation frame
9272 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9274 if (MovDelay[x][y] != 0) // wait some time before next frame
9278 DrawLevelElementAnimation(x, y, Feld[x][y]);
9280 if (MovDelay[x][y] != 0)
9282 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9283 10 - MovDelay[x][y]);
9285 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9290 static void MauerWaechst(int x, int y)
9294 if (!MovDelay[x][y]) // next animation frame
9295 MovDelay[x][y] = 3 * delay;
9297 if (MovDelay[x][y]) // wait some time before next frame
9301 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9303 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9304 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9306 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9309 if (!MovDelay[x][y])
9311 if (MovDir[x][y] == MV_LEFT)
9313 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9314 TEST_DrawLevelField(x - 1, y);
9316 else if (MovDir[x][y] == MV_RIGHT)
9318 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9319 TEST_DrawLevelField(x + 1, y);
9321 else if (MovDir[x][y] == MV_UP)
9323 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9324 TEST_DrawLevelField(x, y - 1);
9328 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9329 TEST_DrawLevelField(x, y + 1);
9332 Feld[x][y] = Store[x][y];
9334 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9335 TEST_DrawLevelField(x, y);
9340 static void MauerAbleger(int ax, int ay)
9342 int element = Feld[ax][ay];
9343 int graphic = el2img(element);
9344 boolean oben_frei = FALSE, unten_frei = FALSE;
9345 boolean links_frei = FALSE, rechts_frei = FALSE;
9346 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9347 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9348 boolean new_wall = FALSE;
9350 if (IS_ANIMATED(graphic))
9351 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9353 if (!MovDelay[ax][ay]) // start building new wall
9354 MovDelay[ax][ay] = 6;
9356 if (MovDelay[ax][ay]) // wait some time before building new wall
9359 if (MovDelay[ax][ay])
9363 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9365 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9367 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9369 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9372 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9373 element == EL_EXPANDABLE_WALL_ANY)
9377 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9378 Store[ax][ay-1] = element;
9379 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9380 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9381 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9382 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9387 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9388 Store[ax][ay+1] = element;
9389 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9390 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9391 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9392 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9397 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9398 element == EL_EXPANDABLE_WALL_ANY ||
9399 element == EL_EXPANDABLE_WALL ||
9400 element == EL_BD_EXPANDABLE_WALL)
9404 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9405 Store[ax-1][ay] = element;
9406 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9407 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9408 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9409 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9415 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9416 Store[ax+1][ay] = element;
9417 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9418 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9419 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9420 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9425 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9426 TEST_DrawLevelField(ax, ay);
9428 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9430 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9431 unten_massiv = TRUE;
9432 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9433 links_massiv = TRUE;
9434 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9435 rechts_massiv = TRUE;
9437 if (((oben_massiv && unten_massiv) ||
9438 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9439 element == EL_EXPANDABLE_WALL) &&
9440 ((links_massiv && rechts_massiv) ||
9441 element == EL_EXPANDABLE_WALL_VERTICAL))
9442 Feld[ax][ay] = EL_WALL;
9445 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9448 static void MauerAblegerStahl(int ax, int ay)
9450 int element = Feld[ax][ay];
9451 int graphic = el2img(element);
9452 boolean oben_frei = FALSE, unten_frei = FALSE;
9453 boolean links_frei = FALSE, rechts_frei = FALSE;
9454 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9455 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9456 boolean new_wall = FALSE;
9458 if (IS_ANIMATED(graphic))
9459 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9461 if (!MovDelay[ax][ay]) // start building new wall
9462 MovDelay[ax][ay] = 6;
9464 if (MovDelay[ax][ay]) // wait some time before building new wall
9467 if (MovDelay[ax][ay])
9471 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9473 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9475 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9477 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9480 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9481 element == EL_EXPANDABLE_STEELWALL_ANY)
9485 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9486 Store[ax][ay-1] = element;
9487 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9488 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9489 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9490 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9495 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9496 Store[ax][ay+1] = element;
9497 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9498 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9499 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9500 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9505 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9506 element == EL_EXPANDABLE_STEELWALL_ANY)
9510 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9511 Store[ax-1][ay] = element;
9512 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9513 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9514 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9515 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9521 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9522 Store[ax+1][ay] = element;
9523 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9524 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9525 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9526 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9531 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9533 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9534 unten_massiv = TRUE;
9535 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9536 links_massiv = TRUE;
9537 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9538 rechts_massiv = TRUE;
9540 if (((oben_massiv && unten_massiv) ||
9541 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9542 ((links_massiv && rechts_massiv) ||
9543 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9544 Feld[ax][ay] = EL_STEELWALL;
9547 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9550 static void CheckForDragon(int x, int y)
9553 boolean dragon_found = FALSE;
9554 static int xy[4][2] =
9562 for (i = 0; i < NUM_DIRECTIONS; i++)
9564 for (j = 0; j < 4; j++)
9566 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9568 if (IN_LEV_FIELD(xx, yy) &&
9569 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9571 if (Feld[xx][yy] == EL_DRAGON)
9572 dragon_found = TRUE;
9581 for (i = 0; i < NUM_DIRECTIONS; i++)
9583 for (j = 0; j < 3; j++)
9585 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9587 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9589 Feld[xx][yy] = EL_EMPTY;
9590 TEST_DrawLevelField(xx, yy);
9599 static void InitBuggyBase(int x, int y)
9601 int element = Feld[x][y];
9602 int activating_delay = FRAMES_PER_SECOND / 4;
9605 (element == EL_SP_BUGGY_BASE ?
9606 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9607 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9609 element == EL_SP_BUGGY_BASE_ACTIVE ?
9610 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9613 static void WarnBuggyBase(int x, int y)
9616 static int xy[4][2] =
9624 for (i = 0; i < NUM_DIRECTIONS; i++)
9626 int xx = x + xy[i][0];
9627 int yy = y + xy[i][1];
9629 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9631 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9638 static void InitTrap(int x, int y)
9640 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9643 static void ActivateTrap(int x, int y)
9645 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9648 static void ChangeActiveTrap(int x, int y)
9650 int graphic = IMG_TRAP_ACTIVE;
9652 // if new animation frame was drawn, correct crumbled sand border
9653 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9654 TEST_DrawLevelFieldCrumbled(x, y);
9657 static int getSpecialActionElement(int element, int number, int base_element)
9659 return (element != EL_EMPTY ? element :
9660 number != -1 ? base_element + number - 1 :
9664 static int getModifiedActionNumber(int value_old, int operator, int operand,
9665 int value_min, int value_max)
9667 int value_new = (operator == CA_MODE_SET ? operand :
9668 operator == CA_MODE_ADD ? value_old + operand :
9669 operator == CA_MODE_SUBTRACT ? value_old - operand :
9670 operator == CA_MODE_MULTIPLY ? value_old * operand :
9671 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9672 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9675 return (value_new < value_min ? value_min :
9676 value_new > value_max ? value_max :
9680 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9682 struct ElementInfo *ei = &element_info[element];
9683 struct ElementChangeInfo *change = &ei->change_page[page];
9684 int target_element = change->target_element;
9685 int action_type = change->action_type;
9686 int action_mode = change->action_mode;
9687 int action_arg = change->action_arg;
9688 int action_element = change->action_element;
9691 if (!change->has_action)
9694 // ---------- determine action paramater values -----------------------------
9696 int level_time_value =
9697 (level.time > 0 ? TimeLeft :
9700 int action_arg_element_raw =
9701 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9702 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9703 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9704 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9705 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9706 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9707 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9709 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9711 int action_arg_direction =
9712 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9713 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9714 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9715 change->actual_trigger_side :
9716 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9717 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9720 int action_arg_number_min =
9721 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9724 int action_arg_number_max =
9725 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9726 action_type == CA_SET_LEVEL_GEMS ? 999 :
9727 action_type == CA_SET_LEVEL_TIME ? 9999 :
9728 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9729 action_type == CA_SET_CE_VALUE ? 9999 :
9730 action_type == CA_SET_CE_SCORE ? 9999 :
9733 int action_arg_number_reset =
9734 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9735 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9736 action_type == CA_SET_LEVEL_TIME ? level.time :
9737 action_type == CA_SET_LEVEL_SCORE ? 0 :
9738 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9739 action_type == CA_SET_CE_SCORE ? 0 :
9742 int action_arg_number =
9743 (action_arg <= CA_ARG_MAX ? action_arg :
9744 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9745 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9746 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9747 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9748 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9749 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9750 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9751 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9752 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9753 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9754 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9755 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9756 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9757 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9758 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9759 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9760 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9761 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9762 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9763 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9764 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9767 int action_arg_number_old =
9768 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9769 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9770 action_type == CA_SET_LEVEL_SCORE ? game.score :
9771 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9772 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9775 int action_arg_number_new =
9776 getModifiedActionNumber(action_arg_number_old,
9777 action_mode, action_arg_number,
9778 action_arg_number_min, action_arg_number_max);
9780 int trigger_player_bits =
9781 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9782 change->actual_trigger_player_bits : change->trigger_player);
9784 int action_arg_player_bits =
9785 (action_arg >= CA_ARG_PLAYER_1 &&
9786 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9787 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9788 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9791 // ---------- execute action -----------------------------------------------
9793 switch (action_type)
9800 // ---------- level actions ----------------------------------------------
9802 case CA_RESTART_LEVEL:
9804 game.restart_level = TRUE;
9809 case CA_SHOW_ENVELOPE:
9811 int element = getSpecialActionElement(action_arg_element,
9812 action_arg_number, EL_ENVELOPE_1);
9814 if (IS_ENVELOPE(element))
9815 local_player->show_envelope = element;
9820 case CA_SET_LEVEL_TIME:
9822 if (level.time > 0) // only modify limited time value
9824 TimeLeft = action_arg_number_new;
9826 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9828 DisplayGameControlValues();
9830 if (!TimeLeft && setup.time_limit)
9831 for (i = 0; i < MAX_PLAYERS; i++)
9832 KillPlayer(&stored_player[i]);
9838 case CA_SET_LEVEL_SCORE:
9840 game.score = action_arg_number_new;
9842 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9844 DisplayGameControlValues();
9849 case CA_SET_LEVEL_GEMS:
9851 game.gems_still_needed = action_arg_number_new;
9853 game.snapshot.collected_item = TRUE;
9855 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9857 DisplayGameControlValues();
9862 case CA_SET_LEVEL_WIND:
9864 game.wind_direction = action_arg_direction;
9869 case CA_SET_LEVEL_RANDOM_SEED:
9871 // ensure that setting a new random seed while playing is predictable
9872 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9877 // ---------- player actions ---------------------------------------------
9879 case CA_MOVE_PLAYER:
9881 // automatically move to the next field in specified direction
9882 for (i = 0; i < MAX_PLAYERS; i++)
9883 if (trigger_player_bits & (1 << i))
9884 stored_player[i].programmed_action = action_arg_direction;
9889 case CA_EXIT_PLAYER:
9891 for (i = 0; i < MAX_PLAYERS; i++)
9892 if (action_arg_player_bits & (1 << i))
9893 ExitPlayer(&stored_player[i]);
9895 if (game.players_still_needed == 0)
9901 case CA_KILL_PLAYER:
9903 for (i = 0; i < MAX_PLAYERS; i++)
9904 if (action_arg_player_bits & (1 << i))
9905 KillPlayer(&stored_player[i]);
9910 case CA_SET_PLAYER_KEYS:
9912 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
9913 int element = getSpecialActionElement(action_arg_element,
9914 action_arg_number, EL_KEY_1);
9916 if (IS_KEY(element))
9918 for (i = 0; i < MAX_PLAYERS; i++)
9920 if (trigger_player_bits & (1 << i))
9922 stored_player[i].key[KEY_NR(element)] = key_state;
9924 DrawGameDoorValues();
9932 case CA_SET_PLAYER_SPEED:
9934 for (i = 0; i < MAX_PLAYERS; i++)
9936 if (trigger_player_bits & (1 << i))
9938 int move_stepsize = TILEX / stored_player[i].move_delay_value;
9940 if (action_arg == CA_ARG_SPEED_FASTER &&
9941 stored_player[i].cannot_move)
9943 action_arg_number = STEPSIZE_VERY_SLOW;
9945 else if (action_arg == CA_ARG_SPEED_SLOWER ||
9946 action_arg == CA_ARG_SPEED_FASTER)
9948 action_arg_number = 2;
9949 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
9952 else if (action_arg == CA_ARG_NUMBER_RESET)
9954 action_arg_number = level.initial_player_stepsize[i];
9958 getModifiedActionNumber(move_stepsize,
9961 action_arg_number_min,
9962 action_arg_number_max);
9964 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
9971 case CA_SET_PLAYER_SHIELD:
9973 for (i = 0; i < MAX_PLAYERS; i++)
9975 if (trigger_player_bits & (1 << i))
9977 if (action_arg == CA_ARG_SHIELD_OFF)
9979 stored_player[i].shield_normal_time_left = 0;
9980 stored_player[i].shield_deadly_time_left = 0;
9982 else if (action_arg == CA_ARG_SHIELD_NORMAL)
9984 stored_player[i].shield_normal_time_left = 999999;
9986 else if (action_arg == CA_ARG_SHIELD_DEADLY)
9988 stored_player[i].shield_normal_time_left = 999999;
9989 stored_player[i].shield_deadly_time_left = 999999;
9997 case CA_SET_PLAYER_GRAVITY:
9999 for (i = 0; i < MAX_PLAYERS; i++)
10001 if (trigger_player_bits & (1 << i))
10003 stored_player[i].gravity =
10004 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10005 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10006 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10007 stored_player[i].gravity);
10014 case CA_SET_PLAYER_ARTWORK:
10016 for (i = 0; i < MAX_PLAYERS; i++)
10018 if (trigger_player_bits & (1 << i))
10020 int artwork_element = action_arg_element;
10022 if (action_arg == CA_ARG_ELEMENT_RESET)
10024 (level.use_artwork_element[i] ? level.artwork_element[i] :
10025 stored_player[i].element_nr);
10027 if (stored_player[i].artwork_element != artwork_element)
10028 stored_player[i].Frame = 0;
10030 stored_player[i].artwork_element = artwork_element;
10032 SetPlayerWaiting(&stored_player[i], FALSE);
10034 // set number of special actions for bored and sleeping animation
10035 stored_player[i].num_special_action_bored =
10036 get_num_special_action(artwork_element,
10037 ACTION_BORING_1, ACTION_BORING_LAST);
10038 stored_player[i].num_special_action_sleeping =
10039 get_num_special_action(artwork_element,
10040 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10047 case CA_SET_PLAYER_INVENTORY:
10049 for (i = 0; i < MAX_PLAYERS; i++)
10051 struct PlayerInfo *player = &stored_player[i];
10054 if (trigger_player_bits & (1 << i))
10056 int inventory_element = action_arg_element;
10058 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10059 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10060 action_arg == CA_ARG_ELEMENT_ACTION)
10062 int element = inventory_element;
10063 int collect_count = element_info[element].collect_count_initial;
10065 if (!IS_CUSTOM_ELEMENT(element))
10068 if (collect_count == 0)
10069 player->inventory_infinite_element = element;
10071 for (k = 0; k < collect_count; k++)
10072 if (player->inventory_size < MAX_INVENTORY_SIZE)
10073 player->inventory_element[player->inventory_size++] =
10076 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10077 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10078 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10080 if (player->inventory_infinite_element != EL_UNDEFINED &&
10081 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10082 action_arg_element_raw))
10083 player->inventory_infinite_element = EL_UNDEFINED;
10085 for (k = 0, j = 0; j < player->inventory_size; j++)
10087 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10088 action_arg_element_raw))
10089 player->inventory_element[k++] = player->inventory_element[j];
10092 player->inventory_size = k;
10094 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10096 if (player->inventory_size > 0)
10098 for (j = 0; j < player->inventory_size - 1; j++)
10099 player->inventory_element[j] = player->inventory_element[j + 1];
10101 player->inventory_size--;
10104 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10106 if (player->inventory_size > 0)
10107 player->inventory_size--;
10109 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10111 player->inventory_infinite_element = EL_UNDEFINED;
10112 player->inventory_size = 0;
10114 else if (action_arg == CA_ARG_INVENTORY_RESET)
10116 player->inventory_infinite_element = EL_UNDEFINED;
10117 player->inventory_size = 0;
10119 if (level.use_initial_inventory[i])
10121 for (j = 0; j < level.initial_inventory_size[i]; j++)
10123 int element = level.initial_inventory_content[i][j];
10124 int collect_count = element_info[element].collect_count_initial;
10126 if (!IS_CUSTOM_ELEMENT(element))
10129 if (collect_count == 0)
10130 player->inventory_infinite_element = element;
10132 for (k = 0; k < collect_count; k++)
10133 if (player->inventory_size < MAX_INVENTORY_SIZE)
10134 player->inventory_element[player->inventory_size++] =
10145 // ---------- CE actions -------------------------------------------------
10147 case CA_SET_CE_VALUE:
10149 int last_ce_value = CustomValue[x][y];
10151 CustomValue[x][y] = action_arg_number_new;
10153 if (CustomValue[x][y] != last_ce_value)
10155 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10156 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10158 if (CustomValue[x][y] == 0)
10160 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10161 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10168 case CA_SET_CE_SCORE:
10170 int last_ce_score = ei->collect_score;
10172 ei->collect_score = action_arg_number_new;
10174 if (ei->collect_score != last_ce_score)
10176 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10177 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10179 if (ei->collect_score == 0)
10183 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10184 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10187 This is a very special case that seems to be a mixture between
10188 CheckElementChange() and CheckTriggeredElementChange(): while
10189 the first one only affects single elements that are triggered
10190 directly, the second one affects multiple elements in the playfield
10191 that are triggered indirectly by another element. This is a third
10192 case: Changing the CE score always affects multiple identical CEs,
10193 so every affected CE must be checked, not only the single CE for
10194 which the CE score was changed in the first place (as every instance
10195 of that CE shares the same CE score, and therefore also can change)!
10197 SCAN_PLAYFIELD(xx, yy)
10199 if (Feld[xx][yy] == element)
10200 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10201 CE_SCORE_GETS_ZERO);
10209 case CA_SET_CE_ARTWORK:
10211 int artwork_element = action_arg_element;
10212 boolean reset_frame = FALSE;
10215 if (action_arg == CA_ARG_ELEMENT_RESET)
10216 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10219 if (ei->gfx_element != artwork_element)
10220 reset_frame = TRUE;
10222 ei->gfx_element = artwork_element;
10224 SCAN_PLAYFIELD(xx, yy)
10226 if (Feld[xx][yy] == element)
10230 ResetGfxAnimation(xx, yy);
10231 ResetRandomAnimationValue(xx, yy);
10234 TEST_DrawLevelField(xx, yy);
10241 // ---------- engine actions ---------------------------------------------
10243 case CA_SET_ENGINE_SCAN_MODE:
10245 InitPlayfieldScanMode(action_arg);
10255 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10257 int old_element = Feld[x][y];
10258 int new_element = GetElementFromGroupElement(element);
10259 int previous_move_direction = MovDir[x][y];
10260 int last_ce_value = CustomValue[x][y];
10261 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10262 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10263 boolean add_player_onto_element = (new_element_is_player &&
10264 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10265 IS_WALKABLE(old_element));
10267 if (!add_player_onto_element)
10269 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10270 RemoveMovingField(x, y);
10274 Feld[x][y] = new_element;
10276 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10277 MovDir[x][y] = previous_move_direction;
10279 if (element_info[new_element].use_last_ce_value)
10280 CustomValue[x][y] = last_ce_value;
10282 InitField_WithBug1(x, y, FALSE);
10284 new_element = Feld[x][y]; // element may have changed
10286 ResetGfxAnimation(x, y);
10287 ResetRandomAnimationValue(x, y);
10289 TEST_DrawLevelField(x, y);
10291 if (GFX_CRUMBLED(new_element))
10292 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10295 // check if element under the player changes from accessible to unaccessible
10296 // (needed for special case of dropping element which then changes)
10297 // (must be checked after creating new element for walkable group elements)
10298 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10299 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10306 // "ChangeCount" not set yet to allow "entered by player" change one time
10307 if (new_element_is_player)
10308 RelocatePlayer(x, y, new_element);
10311 ChangeCount[x][y]++; // count number of changes in the same frame
10313 TestIfBadThingTouchesPlayer(x, y);
10314 TestIfPlayerTouchesCustomElement(x, y);
10315 TestIfElementTouchesCustomElement(x, y);
10318 static void CreateField(int x, int y, int element)
10320 CreateFieldExt(x, y, element, FALSE);
10323 static void CreateElementFromChange(int x, int y, int element)
10325 element = GET_VALID_RUNTIME_ELEMENT(element);
10327 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10329 int old_element = Feld[x][y];
10331 // prevent changed element from moving in same engine frame
10332 // unless both old and new element can either fall or move
10333 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10334 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10338 CreateFieldExt(x, y, element, TRUE);
10341 static boolean ChangeElement(int x, int y, int element, int page)
10343 struct ElementInfo *ei = &element_info[element];
10344 struct ElementChangeInfo *change = &ei->change_page[page];
10345 int ce_value = CustomValue[x][y];
10346 int ce_score = ei->collect_score;
10347 int target_element;
10348 int old_element = Feld[x][y];
10350 // always use default change event to prevent running into a loop
10351 if (ChangeEvent[x][y] == -1)
10352 ChangeEvent[x][y] = CE_DELAY;
10354 if (ChangeEvent[x][y] == CE_DELAY)
10356 // reset actual trigger element, trigger player and action element
10357 change->actual_trigger_element = EL_EMPTY;
10358 change->actual_trigger_player = EL_EMPTY;
10359 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10360 change->actual_trigger_side = CH_SIDE_NONE;
10361 change->actual_trigger_ce_value = 0;
10362 change->actual_trigger_ce_score = 0;
10365 // do not change elements more than a specified maximum number of changes
10366 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10369 ChangeCount[x][y]++; // count number of changes in the same frame
10371 if (change->explode)
10378 if (change->use_target_content)
10380 boolean complete_replace = TRUE;
10381 boolean can_replace[3][3];
10384 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10387 boolean is_walkable;
10388 boolean is_diggable;
10389 boolean is_collectible;
10390 boolean is_removable;
10391 boolean is_destructible;
10392 int ex = x + xx - 1;
10393 int ey = y + yy - 1;
10394 int content_element = change->target_content.e[xx][yy];
10397 can_replace[xx][yy] = TRUE;
10399 if (ex == x && ey == y) // do not check changing element itself
10402 if (content_element == EL_EMPTY_SPACE)
10404 can_replace[xx][yy] = FALSE; // do not replace border with space
10409 if (!IN_LEV_FIELD(ex, ey))
10411 can_replace[xx][yy] = FALSE;
10412 complete_replace = FALSE;
10419 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10420 e = MovingOrBlocked2Element(ex, ey);
10422 is_empty = (IS_FREE(ex, ey) ||
10423 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10425 is_walkable = (is_empty || IS_WALKABLE(e));
10426 is_diggable = (is_empty || IS_DIGGABLE(e));
10427 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10428 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10429 is_removable = (is_diggable || is_collectible);
10431 can_replace[xx][yy] =
10432 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10433 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10434 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10435 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10436 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10437 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10438 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10440 if (!can_replace[xx][yy])
10441 complete_replace = FALSE;
10444 if (!change->only_if_complete || complete_replace)
10446 boolean something_has_changed = FALSE;
10448 if (change->only_if_complete && change->use_random_replace &&
10449 RND(100) < change->random_percentage)
10452 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10454 int ex = x + xx - 1;
10455 int ey = y + yy - 1;
10456 int content_element;
10458 if (can_replace[xx][yy] && (!change->use_random_replace ||
10459 RND(100) < change->random_percentage))
10461 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10462 RemoveMovingField(ex, ey);
10464 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10466 content_element = change->target_content.e[xx][yy];
10467 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10468 ce_value, ce_score);
10470 CreateElementFromChange(ex, ey, target_element);
10472 something_has_changed = TRUE;
10474 // for symmetry reasons, freeze newly created border elements
10475 if (ex != x || ey != y)
10476 Stop[ex][ey] = TRUE; // no more moving in this frame
10480 if (something_has_changed)
10482 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10483 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10489 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10490 ce_value, ce_score);
10492 if (element == EL_DIAGONAL_GROWING ||
10493 element == EL_DIAGONAL_SHRINKING)
10495 target_element = Store[x][y];
10497 Store[x][y] = EL_EMPTY;
10500 CreateElementFromChange(x, y, target_element);
10502 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10503 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10506 // this uses direct change before indirect change
10507 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10512 static void HandleElementChange(int x, int y, int page)
10514 int element = MovingOrBlocked2Element(x, y);
10515 struct ElementInfo *ei = &element_info[element];
10516 struct ElementChangeInfo *change = &ei->change_page[page];
10517 boolean handle_action_before_change = FALSE;
10520 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10521 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10524 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10525 x, y, element, element_info[element].token_name);
10526 printf("HandleElementChange(): This should never happen!\n");
10531 // this can happen with classic bombs on walkable, changing elements
10532 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10537 if (ChangeDelay[x][y] == 0) // initialize element change
10539 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10541 if (change->can_change)
10543 // !!! not clear why graphic animation should be reset at all here !!!
10544 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10545 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10548 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10550 When using an animation frame delay of 1 (this only happens with
10551 "sp_zonk.moving.left/right" in the classic graphics), the default
10552 (non-moving) animation shows wrong animation frames (while the
10553 moving animation, like "sp_zonk.moving.left/right", is correct,
10554 so this graphical bug never shows up with the classic graphics).
10555 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10556 be drawn instead of the correct frames 0,1,2,3. This is caused by
10557 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10558 an element change: First when the change delay ("ChangeDelay[][]")
10559 counter has reached zero after decrementing, then a second time in
10560 the next frame (after "GfxFrame[][]" was already incremented) when
10561 "ChangeDelay[][]" is reset to the initial delay value again.
10563 This causes frame 0 to be drawn twice, while the last frame won't
10564 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10566 As some animations may already be cleverly designed around this bug
10567 (at least the "Snake Bite" snake tail animation does this), it cannot
10568 simply be fixed here without breaking such existing animations.
10569 Unfortunately, it cannot easily be detected if a graphics set was
10570 designed "before" or "after" the bug was fixed. As a workaround,
10571 a new graphics set option "game.graphics_engine_version" was added
10572 to be able to specify the game's major release version for which the
10573 graphics set was designed, which can then be used to decide if the
10574 bugfix should be used (version 4 and above) or not (version 3 or
10575 below, or if no version was specified at all, as with old sets).
10577 (The wrong/fixed animation frames can be tested with the test level set
10578 "test_gfxframe" and level "000", which contains a specially prepared
10579 custom element at level position (x/y) == (11/9) which uses the zonk
10580 animation mentioned above. Using "game.graphics_engine_version: 4"
10581 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10582 This can also be seen from the debug output for this test element.)
10585 // when a custom element is about to change (for example by change delay),
10586 // do not reset graphic animation when the custom element is moving
10587 if (game.graphics_engine_version < 4 &&
10590 ResetGfxAnimation(x, y);
10591 ResetRandomAnimationValue(x, y);
10594 if (change->pre_change_function)
10595 change->pre_change_function(x, y);
10599 ChangeDelay[x][y]--;
10601 if (ChangeDelay[x][y] != 0) // continue element change
10603 if (change->can_change)
10605 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10607 if (IS_ANIMATED(graphic))
10608 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10610 if (change->change_function)
10611 change->change_function(x, y);
10614 else // finish element change
10616 if (ChangePage[x][y] != -1) // remember page from delayed change
10618 page = ChangePage[x][y];
10619 ChangePage[x][y] = -1;
10621 change = &ei->change_page[page];
10624 if (IS_MOVING(x, y)) // never change a running system ;-)
10626 ChangeDelay[x][y] = 1; // try change after next move step
10627 ChangePage[x][y] = page; // remember page to use for change
10632 // special case: set new level random seed before changing element
10633 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10634 handle_action_before_change = TRUE;
10636 if (change->has_action && handle_action_before_change)
10637 ExecuteCustomElementAction(x, y, element, page);
10639 if (change->can_change)
10641 if (ChangeElement(x, y, element, page))
10643 if (change->post_change_function)
10644 change->post_change_function(x, y);
10648 if (change->has_action && !handle_action_before_change)
10649 ExecuteCustomElementAction(x, y, element, page);
10653 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10654 int trigger_element,
10656 int trigger_player,
10660 boolean change_done_any = FALSE;
10661 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10664 if (!(trigger_events[trigger_element][trigger_event]))
10667 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10669 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10671 int element = EL_CUSTOM_START + i;
10672 boolean change_done = FALSE;
10675 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10676 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10679 for (p = 0; p < element_info[element].num_change_pages; p++)
10681 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10683 if (change->can_change_or_has_action &&
10684 change->has_event[trigger_event] &&
10685 change->trigger_side & trigger_side &&
10686 change->trigger_player & trigger_player &&
10687 change->trigger_page & trigger_page_bits &&
10688 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10690 change->actual_trigger_element = trigger_element;
10691 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10692 change->actual_trigger_player_bits = trigger_player;
10693 change->actual_trigger_side = trigger_side;
10694 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10695 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10697 if ((change->can_change && !change_done) || change->has_action)
10701 SCAN_PLAYFIELD(x, y)
10703 if (Feld[x][y] == element)
10705 if (change->can_change && !change_done)
10707 // if element already changed in this frame, not only prevent
10708 // another element change (checked in ChangeElement()), but
10709 // also prevent additional element actions for this element
10711 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10712 !level.use_action_after_change_bug)
10715 ChangeDelay[x][y] = 1;
10716 ChangeEvent[x][y] = trigger_event;
10718 HandleElementChange(x, y, p);
10720 else if (change->has_action)
10722 // if element already changed in this frame, not only prevent
10723 // another element change (checked in ChangeElement()), but
10724 // also prevent additional element actions for this element
10726 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10727 !level.use_action_after_change_bug)
10730 ExecuteCustomElementAction(x, y, element, p);
10731 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10736 if (change->can_change)
10738 change_done = TRUE;
10739 change_done_any = TRUE;
10746 RECURSION_LOOP_DETECTION_END();
10748 return change_done_any;
10751 static boolean CheckElementChangeExt(int x, int y,
10753 int trigger_element,
10755 int trigger_player,
10758 boolean change_done = FALSE;
10761 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10762 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10765 if (Feld[x][y] == EL_BLOCKED)
10767 Blocked2Moving(x, y, &x, &y);
10768 element = Feld[x][y];
10771 // check if element has already changed or is about to change after moving
10772 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10773 Feld[x][y] != element) ||
10775 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10776 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10777 ChangePage[x][y] != -1)))
10780 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10782 for (p = 0; p < element_info[element].num_change_pages; p++)
10784 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10786 /* check trigger element for all events where the element that is checked
10787 for changing interacts with a directly adjacent element -- this is
10788 different to element changes that affect other elements to change on the
10789 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10790 boolean check_trigger_element =
10791 (trigger_event == CE_TOUCHING_X ||
10792 trigger_event == CE_HITTING_X ||
10793 trigger_event == CE_HIT_BY_X ||
10794 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10796 if (change->can_change_or_has_action &&
10797 change->has_event[trigger_event] &&
10798 change->trigger_side & trigger_side &&
10799 change->trigger_player & trigger_player &&
10800 (!check_trigger_element ||
10801 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10803 change->actual_trigger_element = trigger_element;
10804 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10805 change->actual_trigger_player_bits = trigger_player;
10806 change->actual_trigger_side = trigger_side;
10807 change->actual_trigger_ce_value = CustomValue[x][y];
10808 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10810 // special case: trigger element not at (x,y) position for some events
10811 if (check_trigger_element)
10823 { 0, 0 }, { 0, 0 }, { 0, 0 },
10827 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10828 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10830 change->actual_trigger_ce_value = CustomValue[xx][yy];
10831 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10834 if (change->can_change && !change_done)
10836 ChangeDelay[x][y] = 1;
10837 ChangeEvent[x][y] = trigger_event;
10839 HandleElementChange(x, y, p);
10841 change_done = TRUE;
10843 else if (change->has_action)
10845 ExecuteCustomElementAction(x, y, element, p);
10846 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10851 RECURSION_LOOP_DETECTION_END();
10853 return change_done;
10856 static void PlayPlayerSound(struct PlayerInfo *player)
10858 int jx = player->jx, jy = player->jy;
10859 int sound_element = player->artwork_element;
10860 int last_action = player->last_action_waiting;
10861 int action = player->action_waiting;
10863 if (player->is_waiting)
10865 if (action != last_action)
10866 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10868 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10872 if (action != last_action)
10873 StopSound(element_info[sound_element].sound[last_action]);
10875 if (last_action == ACTION_SLEEPING)
10876 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
10880 static void PlayAllPlayersSound(void)
10884 for (i = 0; i < MAX_PLAYERS; i++)
10885 if (stored_player[i].active)
10886 PlayPlayerSound(&stored_player[i]);
10889 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
10891 boolean last_waiting = player->is_waiting;
10892 int move_dir = player->MovDir;
10894 player->dir_waiting = move_dir;
10895 player->last_action_waiting = player->action_waiting;
10899 if (!last_waiting) // not waiting -> waiting
10901 player->is_waiting = TRUE;
10903 player->frame_counter_bored =
10905 game.player_boring_delay_fixed +
10906 GetSimpleRandom(game.player_boring_delay_random);
10907 player->frame_counter_sleeping =
10909 game.player_sleeping_delay_fixed +
10910 GetSimpleRandom(game.player_sleeping_delay_random);
10912 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
10915 if (game.player_sleeping_delay_fixed +
10916 game.player_sleeping_delay_random > 0 &&
10917 player->anim_delay_counter == 0 &&
10918 player->post_delay_counter == 0 &&
10919 FrameCounter >= player->frame_counter_sleeping)
10920 player->is_sleeping = TRUE;
10921 else if (game.player_boring_delay_fixed +
10922 game.player_boring_delay_random > 0 &&
10923 FrameCounter >= player->frame_counter_bored)
10924 player->is_bored = TRUE;
10926 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
10927 player->is_bored ? ACTION_BORING :
10930 if (player->is_sleeping && player->use_murphy)
10932 // special case for sleeping Murphy when leaning against non-free tile
10934 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
10935 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
10936 !IS_MOVING(player->jx - 1, player->jy)))
10937 move_dir = MV_LEFT;
10938 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
10939 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
10940 !IS_MOVING(player->jx + 1, player->jy)))
10941 move_dir = MV_RIGHT;
10943 player->is_sleeping = FALSE;
10945 player->dir_waiting = move_dir;
10948 if (player->is_sleeping)
10950 if (player->num_special_action_sleeping > 0)
10952 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10954 int last_special_action = player->special_action_sleeping;
10955 int num_special_action = player->num_special_action_sleeping;
10956 int special_action =
10957 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
10958 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
10959 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
10960 last_special_action + 1 : ACTION_SLEEPING);
10961 int special_graphic =
10962 el_act_dir2img(player->artwork_element, special_action, move_dir);
10964 player->anim_delay_counter =
10965 graphic_info[special_graphic].anim_delay_fixed +
10966 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10967 player->post_delay_counter =
10968 graphic_info[special_graphic].post_delay_fixed +
10969 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10971 player->special_action_sleeping = special_action;
10974 if (player->anim_delay_counter > 0)
10976 player->action_waiting = player->special_action_sleeping;
10977 player->anim_delay_counter--;
10979 else if (player->post_delay_counter > 0)
10981 player->post_delay_counter--;
10985 else if (player->is_bored)
10987 if (player->num_special_action_bored > 0)
10989 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10991 int special_action =
10992 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
10993 int special_graphic =
10994 el_act_dir2img(player->artwork_element, special_action, move_dir);
10996 player->anim_delay_counter =
10997 graphic_info[special_graphic].anim_delay_fixed +
10998 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10999 player->post_delay_counter =
11000 graphic_info[special_graphic].post_delay_fixed +
11001 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11003 player->special_action_bored = special_action;
11006 if (player->anim_delay_counter > 0)
11008 player->action_waiting = player->special_action_bored;
11009 player->anim_delay_counter--;
11011 else if (player->post_delay_counter > 0)
11013 player->post_delay_counter--;
11018 else if (last_waiting) // waiting -> not waiting
11020 player->is_waiting = FALSE;
11021 player->is_bored = FALSE;
11022 player->is_sleeping = FALSE;
11024 player->frame_counter_bored = -1;
11025 player->frame_counter_sleeping = -1;
11027 player->anim_delay_counter = 0;
11028 player->post_delay_counter = 0;
11030 player->dir_waiting = player->MovDir;
11031 player->action_waiting = ACTION_DEFAULT;
11033 player->special_action_bored = ACTION_DEFAULT;
11034 player->special_action_sleeping = ACTION_DEFAULT;
11038 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11040 if ((!player->is_moving && player->was_moving) ||
11041 (player->MovPos == 0 && player->was_moving) ||
11042 (player->is_snapping && !player->was_snapping) ||
11043 (player->is_dropping && !player->was_dropping))
11045 if (!CheckSaveEngineSnapshotToList())
11048 player->was_moving = FALSE;
11049 player->was_snapping = TRUE;
11050 player->was_dropping = TRUE;
11054 if (player->is_moving)
11055 player->was_moving = TRUE;
11057 if (!player->is_snapping)
11058 player->was_snapping = FALSE;
11060 if (!player->is_dropping)
11061 player->was_dropping = FALSE;
11065 static void CheckSingleStepMode(struct PlayerInfo *player)
11067 if (tape.single_step && tape.recording && !tape.pausing)
11069 /* as it is called "single step mode", just return to pause mode when the
11070 player stopped moving after one tile (or never starts moving at all) */
11071 if (!player->is_moving &&
11072 !player->is_pushing &&
11073 !player->is_dropping_pressed)
11074 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11077 CheckSaveEngineSnapshot(player);
11080 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11082 int left = player_action & JOY_LEFT;
11083 int right = player_action & JOY_RIGHT;
11084 int up = player_action & JOY_UP;
11085 int down = player_action & JOY_DOWN;
11086 int button1 = player_action & JOY_BUTTON_1;
11087 int button2 = player_action & JOY_BUTTON_2;
11088 int dx = (left ? -1 : right ? 1 : 0);
11089 int dy = (up ? -1 : down ? 1 : 0);
11091 if (!player->active || tape.pausing)
11097 SnapField(player, dx, dy);
11101 DropElement(player);
11103 MovePlayer(player, dx, dy);
11106 CheckSingleStepMode(player);
11108 SetPlayerWaiting(player, FALSE);
11110 return player_action;
11114 // no actions for this player (no input at player's configured device)
11116 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11117 SnapField(player, 0, 0);
11118 CheckGravityMovementWhenNotMoving(player);
11120 if (player->MovPos == 0)
11121 SetPlayerWaiting(player, TRUE);
11123 if (player->MovPos == 0) // needed for tape.playing
11124 player->is_moving = FALSE;
11126 player->is_dropping = FALSE;
11127 player->is_dropping_pressed = FALSE;
11128 player->drop_pressed_delay = 0;
11130 CheckSingleStepMode(player);
11136 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11139 if (!tape.use_mouse)
11142 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11143 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11144 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11147 static void SetTapeActionFromMouseAction(byte *tape_action,
11148 struct MouseActionInfo *mouse_action)
11150 if (!tape.use_mouse)
11153 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11154 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11155 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11158 static void CheckLevelSolved(void)
11160 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11162 if (game_em.level_solved &&
11163 !game_em.game_over) // game won
11167 game_em.game_over = TRUE;
11169 game.all_players_gone = TRUE;
11172 if (game_em.game_over) // game lost
11173 game.all_players_gone = TRUE;
11175 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11177 if (game_sp.level_solved &&
11178 !game_sp.game_over) // game won
11182 game_sp.game_over = TRUE;
11184 game.all_players_gone = TRUE;
11187 if (game_sp.game_over) // game lost
11188 game.all_players_gone = TRUE;
11190 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11192 if (game_mm.level_solved &&
11193 !game_mm.game_over) // game won
11197 game_mm.game_over = TRUE;
11199 game.all_players_gone = TRUE;
11202 if (game_mm.game_over) // game lost
11203 game.all_players_gone = TRUE;
11207 static void CheckLevelTime(void)
11211 if (TimeFrames >= FRAMES_PER_SECOND)
11216 for (i = 0; i < MAX_PLAYERS; i++)
11218 struct PlayerInfo *player = &stored_player[i];
11220 if (SHIELD_ON(player))
11222 player->shield_normal_time_left--;
11224 if (player->shield_deadly_time_left > 0)
11225 player->shield_deadly_time_left--;
11229 if (!game.LevelSolved && !level.use_step_counter)
11237 if (TimeLeft <= 10 && setup.time_limit)
11238 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11240 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11241 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11243 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11245 if (!TimeLeft && setup.time_limit)
11247 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11248 game_em.lev->killed_out_of_time = TRUE;
11250 for (i = 0; i < MAX_PLAYERS; i++)
11251 KillPlayer(&stored_player[i]);
11254 else if (game.no_time_limit && !game.all_players_gone)
11256 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11259 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11262 if (tape.recording || tape.playing)
11263 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11266 if (tape.recording || tape.playing)
11267 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11269 UpdateAndDisplayGameControlValues();
11272 void AdvanceFrameAndPlayerCounters(int player_nr)
11276 // advance frame counters (global frame counter and time frame counter)
11280 // advance player counters (counters for move delay, move animation etc.)
11281 for (i = 0; i < MAX_PLAYERS; i++)
11283 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11284 int move_delay_value = stored_player[i].move_delay_value;
11285 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11287 if (!advance_player_counters) // not all players may be affected
11290 if (move_frames == 0) // less than one move per game frame
11292 int stepsize = TILEX / move_delay_value;
11293 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11294 int count = (stored_player[i].is_moving ?
11295 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11297 if (count % delay == 0)
11301 stored_player[i].Frame += move_frames;
11303 if (stored_player[i].MovPos != 0)
11304 stored_player[i].StepFrame += move_frames;
11306 if (stored_player[i].move_delay > 0)
11307 stored_player[i].move_delay--;
11309 // due to bugs in previous versions, counter must count up, not down
11310 if (stored_player[i].push_delay != -1)
11311 stored_player[i].push_delay++;
11313 if (stored_player[i].drop_delay > 0)
11314 stored_player[i].drop_delay--;
11316 if (stored_player[i].is_dropping_pressed)
11317 stored_player[i].drop_pressed_delay++;
11321 void StartGameActions(boolean init_network_game, boolean record_tape,
11324 unsigned int new_random_seed = InitRND(random_seed);
11327 TapeStartRecording(new_random_seed);
11329 if (init_network_game)
11331 SendToServer_LevelFile();
11332 SendToServer_StartPlaying();
11340 static void GameActionsExt(void)
11343 static unsigned int game_frame_delay = 0;
11345 unsigned int game_frame_delay_value;
11346 byte *recorded_player_action;
11347 byte summarized_player_action = 0;
11348 byte tape_action[MAX_PLAYERS];
11351 // detect endless loops, caused by custom element programming
11352 if (recursion_loop_detected && recursion_loop_depth == 0)
11354 char *message = getStringCat3("Internal Error! Element ",
11355 EL_NAME(recursion_loop_element),
11356 " caused endless loop! Quit the game?");
11358 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11359 EL_NAME(recursion_loop_element));
11361 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11363 recursion_loop_detected = FALSE; // if game should be continued
11370 if (game.restart_level)
11371 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11373 CheckLevelSolved();
11375 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11378 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11381 if (game_status != GAME_MODE_PLAYING) // status might have changed
11384 game_frame_delay_value =
11385 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11387 if (tape.playing && tape.warp_forward && !tape.pausing)
11388 game_frame_delay_value = 0;
11390 SetVideoFrameDelay(game_frame_delay_value);
11392 // (de)activate virtual buttons depending on current game status
11393 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11395 if (game.all_players_gone) // if no players there to be controlled anymore
11396 SetOverlayActive(FALSE);
11397 else if (!tape.playing) // if game continues after tape stopped playing
11398 SetOverlayActive(TRUE);
11403 // ---------- main game synchronization point ----------
11405 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11407 printf("::: skip == %d\n", skip);
11410 // ---------- main game synchronization point ----------
11412 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11416 if (network_playing && !network_player_action_received)
11418 // try to get network player actions in time
11420 // last chance to get network player actions without main loop delay
11421 HandleNetworking();
11423 // game was quit by network peer
11424 if (game_status != GAME_MODE_PLAYING)
11427 // check if network player actions still missing and game still running
11428 if (!network_player_action_received && !checkGameEnded())
11429 return; // failed to get network player actions in time
11431 // do not yet reset "network_player_action_received" (for tape.pausing)
11437 // at this point we know that we really continue executing the game
11439 network_player_action_received = FALSE;
11441 // when playing tape, read previously recorded player input from tape data
11442 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11444 local_player->effective_mouse_action = local_player->mouse_action;
11446 if (recorded_player_action != NULL)
11447 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11448 recorded_player_action);
11450 // TapePlayAction() may return NULL when toggling to "pause before death"
11454 if (tape.set_centered_player)
11456 game.centered_player_nr_next = tape.centered_player_nr_next;
11457 game.set_centered_player = TRUE;
11460 for (i = 0; i < MAX_PLAYERS; i++)
11462 summarized_player_action |= stored_player[i].action;
11464 if (!network_playing && (game.team_mode || tape.playing))
11465 stored_player[i].effective_action = stored_player[i].action;
11468 if (network_playing && !checkGameEnded())
11469 SendToServer_MovePlayer(summarized_player_action);
11471 // summarize all actions at local players mapped input device position
11472 // (this allows using different input devices in single player mode)
11473 if (!network.enabled && !game.team_mode)
11474 stored_player[map_player_action[local_player->index_nr]].effective_action =
11475 summarized_player_action;
11477 // summarize all actions at centered player in local team mode
11478 if (tape.recording &&
11479 setup.team_mode && !network.enabled &&
11480 setup.input_on_focus &&
11481 game.centered_player_nr != -1)
11483 for (i = 0; i < MAX_PLAYERS; i++)
11484 stored_player[map_player_action[i]].effective_action =
11485 (i == game.centered_player_nr ? summarized_player_action : 0);
11488 if (recorded_player_action != NULL)
11489 for (i = 0; i < MAX_PLAYERS; i++)
11490 stored_player[i].effective_action = recorded_player_action[i];
11492 for (i = 0; i < MAX_PLAYERS; i++)
11494 tape_action[i] = stored_player[i].effective_action;
11496 /* (this may happen in the RND game engine if a player was not present on
11497 the playfield on level start, but appeared later from a custom element */
11498 if (setup.team_mode &&
11501 !tape.player_participates[i])
11502 tape.player_participates[i] = TRUE;
11505 SetTapeActionFromMouseAction(tape_action,
11506 &local_player->effective_mouse_action);
11508 // only record actions from input devices, but not programmed actions
11509 if (tape.recording)
11510 TapeRecordAction(tape_action);
11512 // remember if game was played (especially after tape stopped playing)
11513 if (!tape.playing && summarized_player_action)
11514 game.GamePlayed = TRUE;
11516 #if USE_NEW_PLAYER_ASSIGNMENTS
11517 // !!! also map player actions in single player mode !!!
11518 // if (game.team_mode)
11521 byte mapped_action[MAX_PLAYERS];
11523 #if DEBUG_PLAYER_ACTIONS
11525 for (i = 0; i < MAX_PLAYERS; i++)
11526 printf(" %d, ", stored_player[i].effective_action);
11529 for (i = 0; i < MAX_PLAYERS; i++)
11530 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11532 for (i = 0; i < MAX_PLAYERS; i++)
11533 stored_player[i].effective_action = mapped_action[i];
11535 #if DEBUG_PLAYER_ACTIONS
11537 for (i = 0; i < MAX_PLAYERS; i++)
11538 printf(" %d, ", stored_player[i].effective_action);
11542 #if DEBUG_PLAYER_ACTIONS
11546 for (i = 0; i < MAX_PLAYERS; i++)
11547 printf(" %d, ", stored_player[i].effective_action);
11553 for (i = 0; i < MAX_PLAYERS; i++)
11555 // allow engine snapshot in case of changed movement attempt
11556 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11557 (stored_player[i].effective_action & KEY_MOTION))
11558 game.snapshot.changed_action = TRUE;
11560 // allow engine snapshot in case of snapping/dropping attempt
11561 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11562 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11563 game.snapshot.changed_action = TRUE;
11565 game.snapshot.last_action[i] = stored_player[i].effective_action;
11568 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11570 GameActions_EM_Main();
11572 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11574 GameActions_SP_Main();
11576 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11578 GameActions_MM_Main();
11582 GameActions_RND_Main();
11585 BlitScreenToBitmap(backbuffer);
11587 CheckLevelSolved();
11590 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11592 if (global.show_frames_per_second)
11594 static unsigned int fps_counter = 0;
11595 static int fps_frames = 0;
11596 unsigned int fps_delay_ms = Counter() - fps_counter;
11600 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11602 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11605 fps_counter = Counter();
11607 // always draw FPS to screen after FPS value was updated
11608 redraw_mask |= REDRAW_FPS;
11611 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11612 if (GetDrawDeactivationMask() == REDRAW_NONE)
11613 redraw_mask |= REDRAW_FPS;
11617 static void GameActions_CheckSaveEngineSnapshot(void)
11619 if (!game.snapshot.save_snapshot)
11622 // clear flag for saving snapshot _before_ saving snapshot
11623 game.snapshot.save_snapshot = FALSE;
11625 SaveEngineSnapshotToList();
11628 void GameActions(void)
11632 GameActions_CheckSaveEngineSnapshot();
11635 void GameActions_EM_Main(void)
11637 byte effective_action[MAX_PLAYERS];
11638 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11641 for (i = 0; i < MAX_PLAYERS; i++)
11642 effective_action[i] = stored_player[i].effective_action;
11644 GameActions_EM(effective_action, warp_mode);
11647 void GameActions_SP_Main(void)
11649 byte effective_action[MAX_PLAYERS];
11650 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11653 for (i = 0; i < MAX_PLAYERS; i++)
11654 effective_action[i] = stored_player[i].effective_action;
11656 GameActions_SP(effective_action, warp_mode);
11658 for (i = 0; i < MAX_PLAYERS; i++)
11660 if (stored_player[i].force_dropping)
11661 stored_player[i].action |= KEY_BUTTON_DROP;
11663 stored_player[i].force_dropping = FALSE;
11667 void GameActions_MM_Main(void)
11669 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11671 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11674 void GameActions_RND_Main(void)
11679 void GameActions_RND(void)
11681 int magic_wall_x = 0, magic_wall_y = 0;
11682 int i, x, y, element, graphic, last_gfx_frame;
11684 InitPlayfieldScanModeVars();
11686 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11688 SCAN_PLAYFIELD(x, y)
11690 ChangeCount[x][y] = 0;
11691 ChangeEvent[x][y] = -1;
11695 if (game.set_centered_player)
11697 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11699 // switching to "all players" only possible if all players fit to screen
11700 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11702 game.centered_player_nr_next = game.centered_player_nr;
11703 game.set_centered_player = FALSE;
11706 // do not switch focus to non-existing (or non-active) player
11707 if (game.centered_player_nr_next >= 0 &&
11708 !stored_player[game.centered_player_nr_next].active)
11710 game.centered_player_nr_next = game.centered_player_nr;
11711 game.set_centered_player = FALSE;
11715 if (game.set_centered_player &&
11716 ScreenMovPos == 0) // screen currently aligned at tile position
11720 if (game.centered_player_nr_next == -1)
11722 setScreenCenteredToAllPlayers(&sx, &sy);
11726 sx = stored_player[game.centered_player_nr_next].jx;
11727 sy = stored_player[game.centered_player_nr_next].jy;
11730 game.centered_player_nr = game.centered_player_nr_next;
11731 game.set_centered_player = FALSE;
11733 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11734 DrawGameDoorValues();
11737 for (i = 0; i < MAX_PLAYERS; i++)
11739 int actual_player_action = stored_player[i].effective_action;
11742 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11743 - rnd_equinox_tetrachloride 048
11744 - rnd_equinox_tetrachloride_ii 096
11745 - rnd_emanuel_schmieg 002
11746 - doctor_sloan_ww 001, 020
11748 if (stored_player[i].MovPos == 0)
11749 CheckGravityMovement(&stored_player[i]);
11752 // overwrite programmed action with tape action
11753 if (stored_player[i].programmed_action)
11754 actual_player_action = stored_player[i].programmed_action;
11756 PlayerActions(&stored_player[i], actual_player_action);
11758 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11761 ScrollScreen(NULL, SCROLL_GO_ON);
11763 /* for backwards compatibility, the following code emulates a fixed bug that
11764 occured when pushing elements (causing elements that just made their last
11765 pushing step to already (if possible) make their first falling step in the
11766 same game frame, which is bad); this code is also needed to use the famous
11767 "spring push bug" which is used in older levels and might be wanted to be
11768 used also in newer levels, but in this case the buggy pushing code is only
11769 affecting the "spring" element and no other elements */
11771 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11773 for (i = 0; i < MAX_PLAYERS; i++)
11775 struct PlayerInfo *player = &stored_player[i];
11776 int x = player->jx;
11777 int y = player->jy;
11779 if (player->active && player->is_pushing && player->is_moving &&
11781 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11782 Feld[x][y] == EL_SPRING))
11784 ContinueMoving(x, y);
11786 // continue moving after pushing (this is actually a bug)
11787 if (!IS_MOVING(x, y))
11788 Stop[x][y] = FALSE;
11793 SCAN_PLAYFIELD(x, y)
11795 Last[x][y] = Feld[x][y];
11797 ChangeCount[x][y] = 0;
11798 ChangeEvent[x][y] = -1;
11800 // this must be handled before main playfield loop
11801 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11804 if (MovDelay[x][y] <= 0)
11808 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11811 if (MovDelay[x][y] <= 0)
11814 TEST_DrawLevelField(x, y);
11816 TestIfElementTouchesCustomElement(x, y); // for empty space
11821 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11823 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11824 printf("GameActions(): This should never happen!\n");
11826 ChangePage[x][y] = -1;
11830 Stop[x][y] = FALSE;
11831 if (WasJustMoving[x][y] > 0)
11832 WasJustMoving[x][y]--;
11833 if (WasJustFalling[x][y] > 0)
11834 WasJustFalling[x][y]--;
11835 if (CheckCollision[x][y] > 0)
11836 CheckCollision[x][y]--;
11837 if (CheckImpact[x][y] > 0)
11838 CheckImpact[x][y]--;
11842 /* reset finished pushing action (not done in ContinueMoving() to allow
11843 continuous pushing animation for elements with zero push delay) */
11844 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11846 ResetGfxAnimation(x, y);
11847 TEST_DrawLevelField(x, y);
11851 if (IS_BLOCKED(x, y))
11855 Blocked2Moving(x, y, &oldx, &oldy);
11856 if (!IS_MOVING(oldx, oldy))
11858 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11859 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11860 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11861 printf("GameActions(): This should never happen!\n");
11867 SCAN_PLAYFIELD(x, y)
11869 element = Feld[x][y];
11870 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11871 last_gfx_frame = GfxFrame[x][y];
11873 ResetGfxFrame(x, y);
11875 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
11876 DrawLevelGraphicAnimation(x, y, graphic);
11878 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
11879 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
11880 ResetRandomAnimationValue(x, y);
11882 SetRandomAnimationValue(x, y);
11884 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
11886 if (IS_INACTIVE(element))
11888 if (IS_ANIMATED(graphic))
11889 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11894 // this may take place after moving, so 'element' may have changed
11895 if (IS_CHANGING(x, y) &&
11896 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
11898 int page = element_info[element].event_page_nr[CE_DELAY];
11900 HandleElementChange(x, y, page);
11902 element = Feld[x][y];
11903 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11906 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
11910 element = Feld[x][y];
11911 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11913 if (IS_ANIMATED(graphic) &&
11914 !IS_MOVING(x, y) &&
11916 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11918 if (IS_GEM(element) || element == EL_SP_INFOTRON)
11919 TEST_DrawTwinkleOnField(x, y);
11921 else if (element == EL_ACID)
11924 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11926 else if ((element == EL_EXIT_OPEN ||
11927 element == EL_EM_EXIT_OPEN ||
11928 element == EL_SP_EXIT_OPEN ||
11929 element == EL_STEEL_EXIT_OPEN ||
11930 element == EL_EM_STEEL_EXIT_OPEN ||
11931 element == EL_SP_TERMINAL ||
11932 element == EL_SP_TERMINAL_ACTIVE ||
11933 element == EL_EXTRA_TIME ||
11934 element == EL_SHIELD_NORMAL ||
11935 element == EL_SHIELD_DEADLY) &&
11936 IS_ANIMATED(graphic))
11937 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11938 else if (IS_MOVING(x, y))
11939 ContinueMoving(x, y);
11940 else if (IS_ACTIVE_BOMB(element))
11941 CheckDynamite(x, y);
11942 else if (element == EL_AMOEBA_GROWING)
11943 AmoebeWaechst(x, y);
11944 else if (element == EL_AMOEBA_SHRINKING)
11945 AmoebaDisappearing(x, y);
11947 #if !USE_NEW_AMOEBA_CODE
11948 else if (IS_AMOEBALIVE(element))
11949 AmoebeAbleger(x, y);
11952 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
11954 else if (element == EL_EXIT_CLOSED)
11956 else if (element == EL_EM_EXIT_CLOSED)
11958 else if (element == EL_STEEL_EXIT_CLOSED)
11959 CheckExitSteel(x, y);
11960 else if (element == EL_EM_STEEL_EXIT_CLOSED)
11961 CheckExitSteelEM(x, y);
11962 else if (element == EL_SP_EXIT_CLOSED)
11964 else if (element == EL_EXPANDABLE_WALL_GROWING ||
11965 element == EL_EXPANDABLE_STEELWALL_GROWING)
11966 MauerWaechst(x, y);
11967 else if (element == EL_EXPANDABLE_WALL ||
11968 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
11969 element == EL_EXPANDABLE_WALL_VERTICAL ||
11970 element == EL_EXPANDABLE_WALL_ANY ||
11971 element == EL_BD_EXPANDABLE_WALL)
11972 MauerAbleger(x, y);
11973 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
11974 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
11975 element == EL_EXPANDABLE_STEELWALL_ANY)
11976 MauerAblegerStahl(x, y);
11977 else if (element == EL_FLAMES)
11978 CheckForDragon(x, y);
11979 else if (element == EL_EXPLOSION)
11980 ; // drawing of correct explosion animation is handled separately
11981 else if (element == EL_ELEMENT_SNAPPING ||
11982 element == EL_DIAGONAL_SHRINKING ||
11983 element == EL_DIAGONAL_GROWING)
11985 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
11987 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11989 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
11990 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11992 if (IS_BELT_ACTIVE(element))
11993 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
11995 if (game.magic_wall_active)
11997 int jx = local_player->jx, jy = local_player->jy;
11999 // play the element sound at the position nearest to the player
12000 if ((element == EL_MAGIC_WALL_FULL ||
12001 element == EL_MAGIC_WALL_ACTIVE ||
12002 element == EL_MAGIC_WALL_EMPTYING ||
12003 element == EL_BD_MAGIC_WALL_FULL ||
12004 element == EL_BD_MAGIC_WALL_ACTIVE ||
12005 element == EL_BD_MAGIC_WALL_EMPTYING ||
12006 element == EL_DC_MAGIC_WALL_FULL ||
12007 element == EL_DC_MAGIC_WALL_ACTIVE ||
12008 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12009 ABS(x - jx) + ABS(y - jy) <
12010 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12018 #if USE_NEW_AMOEBA_CODE
12019 // new experimental amoeba growth stuff
12020 if (!(FrameCounter % 8))
12022 static unsigned int random = 1684108901;
12024 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12026 x = RND(lev_fieldx);
12027 y = RND(lev_fieldy);
12028 element = Feld[x][y];
12030 if (!IS_PLAYER(x,y) &&
12031 (element == EL_EMPTY ||
12032 CAN_GROW_INTO(element) ||
12033 element == EL_QUICKSAND_EMPTY ||
12034 element == EL_QUICKSAND_FAST_EMPTY ||
12035 element == EL_ACID_SPLASH_LEFT ||
12036 element == EL_ACID_SPLASH_RIGHT))
12038 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12039 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12040 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12041 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12042 Feld[x][y] = EL_AMOEBA_DROP;
12045 random = random * 129 + 1;
12050 game.explosions_delayed = FALSE;
12052 SCAN_PLAYFIELD(x, y)
12054 element = Feld[x][y];
12056 if (ExplodeField[x][y])
12057 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12058 else if (element == EL_EXPLOSION)
12059 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12061 ExplodeField[x][y] = EX_TYPE_NONE;
12064 game.explosions_delayed = TRUE;
12066 if (game.magic_wall_active)
12068 if (!(game.magic_wall_time_left % 4))
12070 int element = Feld[magic_wall_x][magic_wall_y];
12072 if (element == EL_BD_MAGIC_WALL_FULL ||
12073 element == EL_BD_MAGIC_WALL_ACTIVE ||
12074 element == EL_BD_MAGIC_WALL_EMPTYING)
12075 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12076 else if (element == EL_DC_MAGIC_WALL_FULL ||
12077 element == EL_DC_MAGIC_WALL_ACTIVE ||
12078 element == EL_DC_MAGIC_WALL_EMPTYING)
12079 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12081 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12084 if (game.magic_wall_time_left > 0)
12086 game.magic_wall_time_left--;
12088 if (!game.magic_wall_time_left)
12090 SCAN_PLAYFIELD(x, y)
12092 element = Feld[x][y];
12094 if (element == EL_MAGIC_WALL_ACTIVE ||
12095 element == EL_MAGIC_WALL_FULL)
12097 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12098 TEST_DrawLevelField(x, y);
12100 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12101 element == EL_BD_MAGIC_WALL_FULL)
12103 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12104 TEST_DrawLevelField(x, y);
12106 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12107 element == EL_DC_MAGIC_WALL_FULL)
12109 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12110 TEST_DrawLevelField(x, y);
12114 game.magic_wall_active = FALSE;
12119 if (game.light_time_left > 0)
12121 game.light_time_left--;
12123 if (game.light_time_left == 0)
12124 RedrawAllLightSwitchesAndInvisibleElements();
12127 if (game.timegate_time_left > 0)
12129 game.timegate_time_left--;
12131 if (game.timegate_time_left == 0)
12132 CloseAllOpenTimegates();
12135 if (game.lenses_time_left > 0)
12137 game.lenses_time_left--;
12139 if (game.lenses_time_left == 0)
12140 RedrawAllInvisibleElementsForLenses();
12143 if (game.magnify_time_left > 0)
12145 game.magnify_time_left--;
12147 if (game.magnify_time_left == 0)
12148 RedrawAllInvisibleElementsForMagnifier();
12151 for (i = 0; i < MAX_PLAYERS; i++)
12153 struct PlayerInfo *player = &stored_player[i];
12155 if (SHIELD_ON(player))
12157 if (player->shield_deadly_time_left)
12158 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12159 else if (player->shield_normal_time_left)
12160 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12164 #if USE_DELAYED_GFX_REDRAW
12165 SCAN_PLAYFIELD(x, y)
12167 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12169 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12170 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12172 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12173 DrawLevelField(x, y);
12175 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12176 DrawLevelFieldCrumbled(x, y);
12178 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12179 DrawLevelFieldCrumbledNeighbours(x, y);
12181 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12182 DrawTwinkleOnField(x, y);
12185 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12190 PlayAllPlayersSound();
12192 for (i = 0; i < MAX_PLAYERS; i++)
12194 struct PlayerInfo *player = &stored_player[i];
12196 if (player->show_envelope != 0 && (!player->active ||
12197 player->MovPos == 0))
12199 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12201 player->show_envelope = 0;
12205 // use random number generator in every frame to make it less predictable
12206 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12210 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12212 int min_x = x, min_y = y, max_x = x, max_y = y;
12215 for (i = 0; i < MAX_PLAYERS; i++)
12217 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12219 if (!stored_player[i].active || &stored_player[i] == player)
12222 min_x = MIN(min_x, jx);
12223 min_y = MIN(min_y, jy);
12224 max_x = MAX(max_x, jx);
12225 max_y = MAX(max_y, jy);
12228 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12231 static boolean AllPlayersInVisibleScreen(void)
12235 for (i = 0; i < MAX_PLAYERS; i++)
12237 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12239 if (!stored_player[i].active)
12242 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12249 void ScrollLevel(int dx, int dy)
12251 int scroll_offset = 2 * TILEX_VAR;
12254 BlitBitmap(drawto_field, drawto_field,
12255 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12256 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12257 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12258 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12259 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12260 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12264 x = (dx == 1 ? BX1 : BX2);
12265 for (y = BY1; y <= BY2; y++)
12266 DrawScreenField(x, y);
12271 y = (dy == 1 ? BY1 : BY2);
12272 for (x = BX1; x <= BX2; x++)
12273 DrawScreenField(x, y);
12276 redraw_mask |= REDRAW_FIELD;
12279 static boolean canFallDown(struct PlayerInfo *player)
12281 int jx = player->jx, jy = player->jy;
12283 return (IN_LEV_FIELD(jx, jy + 1) &&
12284 (IS_FREE(jx, jy + 1) ||
12285 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12286 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12287 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12290 static boolean canPassField(int x, int y, int move_dir)
12292 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12293 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12294 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12295 int nextx = x + dx;
12296 int nexty = y + dy;
12297 int element = Feld[x][y];
12299 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12300 !CAN_MOVE(element) &&
12301 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12302 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12303 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12306 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12308 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12309 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12310 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12314 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12315 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12316 (IS_DIGGABLE(Feld[newx][newy]) ||
12317 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12318 canPassField(newx, newy, move_dir)));
12321 static void CheckGravityMovement(struct PlayerInfo *player)
12323 if (player->gravity && !player->programmed_action)
12325 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12326 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12327 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12328 int jx = player->jx, jy = player->jy;
12329 boolean player_is_moving_to_valid_field =
12330 (!player_is_snapping &&
12331 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12332 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12333 boolean player_can_fall_down = canFallDown(player);
12335 if (player_can_fall_down &&
12336 !player_is_moving_to_valid_field)
12337 player->programmed_action = MV_DOWN;
12341 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12343 return CheckGravityMovement(player);
12345 if (player->gravity && !player->programmed_action)
12347 int jx = player->jx, jy = player->jy;
12348 boolean field_under_player_is_free =
12349 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12350 boolean player_is_standing_on_valid_field =
12351 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12352 (IS_WALKABLE(Feld[jx][jy]) &&
12353 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12355 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12356 player->programmed_action = MV_DOWN;
12361 MovePlayerOneStep()
12362 -----------------------------------------------------------------------------
12363 dx, dy: direction (non-diagonal) to try to move the player to
12364 real_dx, real_dy: direction as read from input device (can be diagonal)
12367 boolean MovePlayerOneStep(struct PlayerInfo *player,
12368 int dx, int dy, int real_dx, int real_dy)
12370 int jx = player->jx, jy = player->jy;
12371 int new_jx = jx + dx, new_jy = jy + dy;
12373 boolean player_can_move = !player->cannot_move;
12375 if (!player->active || (!dx && !dy))
12376 return MP_NO_ACTION;
12378 player->MovDir = (dx < 0 ? MV_LEFT :
12379 dx > 0 ? MV_RIGHT :
12381 dy > 0 ? MV_DOWN : MV_NONE);
12383 if (!IN_LEV_FIELD(new_jx, new_jy))
12384 return MP_NO_ACTION;
12386 if (!player_can_move)
12388 if (player->MovPos == 0)
12390 player->is_moving = FALSE;
12391 player->is_digging = FALSE;
12392 player->is_collecting = FALSE;
12393 player->is_snapping = FALSE;
12394 player->is_pushing = FALSE;
12398 if (!network.enabled && game.centered_player_nr == -1 &&
12399 !AllPlayersInSight(player, new_jx, new_jy))
12400 return MP_NO_ACTION;
12402 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12403 if (can_move != MP_MOVING)
12406 // check if DigField() has caused relocation of the player
12407 if (player->jx != jx || player->jy != jy)
12408 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12410 StorePlayer[jx][jy] = 0;
12411 player->last_jx = jx;
12412 player->last_jy = jy;
12413 player->jx = new_jx;
12414 player->jy = new_jy;
12415 StorePlayer[new_jx][new_jy] = player->element_nr;
12417 if (player->move_delay_value_next != -1)
12419 player->move_delay_value = player->move_delay_value_next;
12420 player->move_delay_value_next = -1;
12424 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12426 player->step_counter++;
12428 PlayerVisit[jx][jy] = FrameCounter;
12430 player->is_moving = TRUE;
12433 // should better be called in MovePlayer(), but this breaks some tapes
12434 ScrollPlayer(player, SCROLL_INIT);
12440 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12442 int jx = player->jx, jy = player->jy;
12443 int old_jx = jx, old_jy = jy;
12444 int moved = MP_NO_ACTION;
12446 if (!player->active)
12451 if (player->MovPos == 0)
12453 player->is_moving = FALSE;
12454 player->is_digging = FALSE;
12455 player->is_collecting = FALSE;
12456 player->is_snapping = FALSE;
12457 player->is_pushing = FALSE;
12463 if (player->move_delay > 0)
12466 player->move_delay = -1; // set to "uninitialized" value
12468 // store if player is automatically moved to next field
12469 player->is_auto_moving = (player->programmed_action != MV_NONE);
12471 // remove the last programmed player action
12472 player->programmed_action = 0;
12474 if (player->MovPos)
12476 // should only happen if pre-1.2 tape recordings are played
12477 // this is only for backward compatibility
12479 int original_move_delay_value = player->move_delay_value;
12482 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12486 // scroll remaining steps with finest movement resolution
12487 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12489 while (player->MovPos)
12491 ScrollPlayer(player, SCROLL_GO_ON);
12492 ScrollScreen(NULL, SCROLL_GO_ON);
12494 AdvanceFrameAndPlayerCounters(player->index_nr);
12497 BackToFront_WithFrameDelay(0);
12500 player->move_delay_value = original_move_delay_value;
12503 player->is_active = FALSE;
12505 if (player->last_move_dir & MV_HORIZONTAL)
12507 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12508 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12512 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12513 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12516 if (!moved && !player->is_active)
12518 player->is_moving = FALSE;
12519 player->is_digging = FALSE;
12520 player->is_collecting = FALSE;
12521 player->is_snapping = FALSE;
12522 player->is_pushing = FALSE;
12528 if (moved & MP_MOVING && !ScreenMovPos &&
12529 (player->index_nr == game.centered_player_nr ||
12530 game.centered_player_nr == -1))
12532 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12534 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12536 // actual player has left the screen -- scroll in that direction
12537 if (jx != old_jx) // player has moved horizontally
12538 scroll_x += (jx - old_jx);
12539 else // player has moved vertically
12540 scroll_y += (jy - old_jy);
12544 int offset_raw = game.scroll_delay_value;
12546 if (jx != old_jx) // player has moved horizontally
12548 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12549 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12550 int new_scroll_x = jx - MIDPOSX + offset_x;
12552 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12553 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12554 scroll_x = new_scroll_x;
12556 // don't scroll over playfield boundaries
12557 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12559 // don't scroll more than one field at a time
12560 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12562 // don't scroll against the player's moving direction
12563 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12564 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12565 scroll_x = old_scroll_x;
12567 else // player has moved vertically
12569 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12570 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12571 int new_scroll_y = jy - MIDPOSY + offset_y;
12573 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12574 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12575 scroll_y = new_scroll_y;
12577 // don't scroll over playfield boundaries
12578 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12580 // don't scroll more than one field at a time
12581 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12583 // don't scroll against the player's moving direction
12584 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12585 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12586 scroll_y = old_scroll_y;
12590 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12592 if (!network.enabled && game.centered_player_nr == -1 &&
12593 !AllPlayersInVisibleScreen())
12595 scroll_x = old_scroll_x;
12596 scroll_y = old_scroll_y;
12600 ScrollScreen(player, SCROLL_INIT);
12601 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12606 player->StepFrame = 0;
12608 if (moved & MP_MOVING)
12610 if (old_jx != jx && old_jy == jy)
12611 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12612 else if (old_jx == jx && old_jy != jy)
12613 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12615 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12617 player->last_move_dir = player->MovDir;
12618 player->is_moving = TRUE;
12619 player->is_snapping = FALSE;
12620 player->is_switching = FALSE;
12621 player->is_dropping = FALSE;
12622 player->is_dropping_pressed = FALSE;
12623 player->drop_pressed_delay = 0;
12626 // should better be called here than above, but this breaks some tapes
12627 ScrollPlayer(player, SCROLL_INIT);
12632 CheckGravityMovementWhenNotMoving(player);
12634 player->is_moving = FALSE;
12636 /* at this point, the player is allowed to move, but cannot move right now
12637 (e.g. because of something blocking the way) -- ensure that the player
12638 is also allowed to move in the next frame (in old versions before 3.1.1,
12639 the player was forced to wait again for eight frames before next try) */
12641 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12642 player->move_delay = 0; // allow direct movement in the next frame
12645 if (player->move_delay == -1) // not yet initialized by DigField()
12646 player->move_delay = player->move_delay_value;
12648 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12650 TestIfPlayerTouchesBadThing(jx, jy);
12651 TestIfPlayerTouchesCustomElement(jx, jy);
12654 if (!player->active)
12655 RemovePlayer(player);
12660 void ScrollPlayer(struct PlayerInfo *player, int mode)
12662 int jx = player->jx, jy = player->jy;
12663 int last_jx = player->last_jx, last_jy = player->last_jy;
12664 int move_stepsize = TILEX / player->move_delay_value;
12666 if (!player->active)
12669 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12672 if (mode == SCROLL_INIT)
12674 player->actual_frame_counter = FrameCounter;
12675 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12677 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12678 Feld[last_jx][last_jy] == EL_EMPTY)
12680 int last_field_block_delay = 0; // start with no blocking at all
12681 int block_delay_adjustment = player->block_delay_adjustment;
12683 // if player blocks last field, add delay for exactly one move
12684 if (player->block_last_field)
12686 last_field_block_delay += player->move_delay_value;
12688 // when blocking enabled, prevent moving up despite gravity
12689 if (player->gravity && player->MovDir == MV_UP)
12690 block_delay_adjustment = -1;
12693 // add block delay adjustment (also possible when not blocking)
12694 last_field_block_delay += block_delay_adjustment;
12696 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12697 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12700 if (player->MovPos != 0) // player has not yet reached destination
12703 else if (!FrameReached(&player->actual_frame_counter, 1))
12706 if (player->MovPos != 0)
12708 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12709 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12711 // before DrawPlayer() to draw correct player graphic for this case
12712 if (player->MovPos == 0)
12713 CheckGravityMovement(player);
12716 if (player->MovPos == 0) // player reached destination field
12718 if (player->move_delay_reset_counter > 0)
12720 player->move_delay_reset_counter--;
12722 if (player->move_delay_reset_counter == 0)
12724 // continue with normal speed after quickly moving through gate
12725 HALVE_PLAYER_SPEED(player);
12727 // be able to make the next move without delay
12728 player->move_delay = 0;
12732 player->last_jx = jx;
12733 player->last_jy = jy;
12735 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12736 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12737 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12738 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12739 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12740 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12741 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12742 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12744 ExitPlayer(player);
12746 if (game.players_still_needed == 0 &&
12747 (game.friends_still_needed == 0 ||
12748 IS_SP_ELEMENT(Feld[jx][jy])))
12752 // this breaks one level: "machine", level 000
12754 int move_direction = player->MovDir;
12755 int enter_side = MV_DIR_OPPOSITE(move_direction);
12756 int leave_side = move_direction;
12757 int old_jx = last_jx;
12758 int old_jy = last_jy;
12759 int old_element = Feld[old_jx][old_jy];
12760 int new_element = Feld[jx][jy];
12762 if (IS_CUSTOM_ELEMENT(old_element))
12763 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12765 player->index_bit, leave_side);
12767 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12768 CE_PLAYER_LEAVES_X,
12769 player->index_bit, leave_side);
12771 if (IS_CUSTOM_ELEMENT(new_element))
12772 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12773 player->index_bit, enter_side);
12775 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12776 CE_PLAYER_ENTERS_X,
12777 player->index_bit, enter_side);
12779 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12780 CE_MOVE_OF_X, move_direction);
12783 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12785 TestIfPlayerTouchesBadThing(jx, jy);
12786 TestIfPlayerTouchesCustomElement(jx, jy);
12788 /* needed because pushed element has not yet reached its destination,
12789 so it would trigger a change event at its previous field location */
12790 if (!player->is_pushing)
12791 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12793 if (!player->active)
12794 RemovePlayer(player);
12797 if (!game.LevelSolved && level.use_step_counter)
12807 if (TimeLeft <= 10 && setup.time_limit)
12808 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12810 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12812 DisplayGameControlValues();
12814 if (!TimeLeft && setup.time_limit)
12815 for (i = 0; i < MAX_PLAYERS; i++)
12816 KillPlayer(&stored_player[i]);
12818 else if (game.no_time_limit && !game.all_players_gone)
12820 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12822 DisplayGameControlValues();
12826 if (tape.single_step && tape.recording && !tape.pausing &&
12827 !player->programmed_action)
12828 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12830 if (!player->programmed_action)
12831 CheckSaveEngineSnapshot(player);
12835 void ScrollScreen(struct PlayerInfo *player, int mode)
12837 static unsigned int screen_frame_counter = 0;
12839 if (mode == SCROLL_INIT)
12841 // set scrolling step size according to actual player's moving speed
12842 ScrollStepSize = TILEX / player->move_delay_value;
12844 screen_frame_counter = FrameCounter;
12845 ScreenMovDir = player->MovDir;
12846 ScreenMovPos = player->MovPos;
12847 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12850 else if (!FrameReached(&screen_frame_counter, 1))
12855 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
12856 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12857 redraw_mask |= REDRAW_FIELD;
12860 ScreenMovDir = MV_NONE;
12863 void TestIfPlayerTouchesCustomElement(int x, int y)
12865 static int xy[4][2] =
12872 static int trigger_sides[4][2] =
12874 // center side border side
12875 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12876 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12877 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12878 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12880 static int touch_dir[4] =
12882 MV_LEFT | MV_RIGHT,
12887 int center_element = Feld[x][y]; // should always be non-moving!
12890 for (i = 0; i < NUM_DIRECTIONS; i++)
12892 int xx = x + xy[i][0];
12893 int yy = y + xy[i][1];
12894 int center_side = trigger_sides[i][0];
12895 int border_side = trigger_sides[i][1];
12896 int border_element;
12898 if (!IN_LEV_FIELD(xx, yy))
12901 if (IS_PLAYER(x, y)) // player found at center element
12903 struct PlayerInfo *player = PLAYERINFO(x, y);
12905 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12906 border_element = Feld[xx][yy]; // may be moving!
12907 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12908 border_element = Feld[xx][yy];
12909 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
12910 border_element = MovingOrBlocked2Element(xx, yy);
12912 continue; // center and border element do not touch
12914 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
12915 player->index_bit, border_side);
12916 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
12917 CE_PLAYER_TOUCHES_X,
12918 player->index_bit, border_side);
12921 /* use player element that is initially defined in the level playfield,
12922 not the player element that corresponds to the runtime player number
12923 (example: a level that contains EL_PLAYER_3 as the only player would
12924 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12925 int player_element = PLAYERINFO(x, y)->initial_element;
12927 CheckElementChangeBySide(xx, yy, border_element, player_element,
12928 CE_TOUCHING_X, border_side);
12931 else if (IS_PLAYER(xx, yy)) // player found at border element
12933 struct PlayerInfo *player = PLAYERINFO(xx, yy);
12935 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12937 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
12938 continue; // center and border element do not touch
12941 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
12942 player->index_bit, center_side);
12943 CheckTriggeredElementChangeByPlayer(x, y, center_element,
12944 CE_PLAYER_TOUCHES_X,
12945 player->index_bit, center_side);
12948 /* use player element that is initially defined in the level playfield,
12949 not the player element that corresponds to the runtime player number
12950 (example: a level that contains EL_PLAYER_3 as the only player would
12951 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12952 int player_element = PLAYERINFO(xx, yy)->initial_element;
12954 CheckElementChangeBySide(x, y, center_element, player_element,
12955 CE_TOUCHING_X, center_side);
12963 void TestIfElementTouchesCustomElement(int x, int y)
12965 static int xy[4][2] =
12972 static int trigger_sides[4][2] =
12974 // center side border side
12975 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12976 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12977 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12978 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12980 static int touch_dir[4] =
12982 MV_LEFT | MV_RIGHT,
12987 boolean change_center_element = FALSE;
12988 int center_element = Feld[x][y]; // should always be non-moving!
12989 int border_element_old[NUM_DIRECTIONS];
12992 for (i = 0; i < NUM_DIRECTIONS; i++)
12994 int xx = x + xy[i][0];
12995 int yy = y + xy[i][1];
12996 int border_element;
12998 border_element_old[i] = -1;
13000 if (!IN_LEV_FIELD(xx, yy))
13003 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13004 border_element = Feld[xx][yy]; // may be moving!
13005 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13006 border_element = Feld[xx][yy];
13007 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13008 border_element = MovingOrBlocked2Element(xx, yy);
13010 continue; // center and border element do not touch
13012 border_element_old[i] = border_element;
13015 for (i = 0; i < NUM_DIRECTIONS; i++)
13017 int xx = x + xy[i][0];
13018 int yy = y + xy[i][1];
13019 int center_side = trigger_sides[i][0];
13020 int border_element = border_element_old[i];
13022 if (border_element == -1)
13025 // check for change of border element
13026 CheckElementChangeBySide(xx, yy, border_element, center_element,
13027 CE_TOUCHING_X, center_side);
13029 // (center element cannot be player, so we dont have to check this here)
13032 for (i = 0; i < NUM_DIRECTIONS; i++)
13034 int xx = x + xy[i][0];
13035 int yy = y + xy[i][1];
13036 int border_side = trigger_sides[i][1];
13037 int border_element = border_element_old[i];
13039 if (border_element == -1)
13042 // check for change of center element (but change it only once)
13043 if (!change_center_element)
13044 change_center_element =
13045 CheckElementChangeBySide(x, y, center_element, border_element,
13046 CE_TOUCHING_X, border_side);
13048 if (IS_PLAYER(xx, yy))
13050 /* use player element that is initially defined in the level playfield,
13051 not the player element that corresponds to the runtime player number
13052 (example: a level that contains EL_PLAYER_3 as the only player would
13053 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13054 int player_element = PLAYERINFO(xx, yy)->initial_element;
13056 CheckElementChangeBySide(x, y, center_element, player_element,
13057 CE_TOUCHING_X, border_side);
13062 void TestIfElementHitsCustomElement(int x, int y, int direction)
13064 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13065 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13066 int hitx = x + dx, hity = y + dy;
13067 int hitting_element = Feld[x][y];
13068 int touched_element;
13070 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13073 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13074 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13076 if (IN_LEV_FIELD(hitx, hity))
13078 int opposite_direction = MV_DIR_OPPOSITE(direction);
13079 int hitting_side = direction;
13080 int touched_side = opposite_direction;
13081 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13082 MovDir[hitx][hity] != direction ||
13083 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13089 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13090 CE_HITTING_X, touched_side);
13092 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13093 CE_HIT_BY_X, hitting_side);
13095 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13096 CE_HIT_BY_SOMETHING, opposite_direction);
13098 if (IS_PLAYER(hitx, hity))
13100 /* use player element that is initially defined in the level playfield,
13101 not the player element that corresponds to the runtime player number
13102 (example: a level that contains EL_PLAYER_3 as the only player would
13103 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13104 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13106 CheckElementChangeBySide(x, y, hitting_element, player_element,
13107 CE_HITTING_X, touched_side);
13112 // "hitting something" is also true when hitting the playfield border
13113 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13114 CE_HITTING_SOMETHING, direction);
13117 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13119 int i, kill_x = -1, kill_y = -1;
13121 int bad_element = -1;
13122 static int test_xy[4][2] =
13129 static int test_dir[4] =
13137 for (i = 0; i < NUM_DIRECTIONS; i++)
13139 int test_x, test_y, test_move_dir, test_element;
13141 test_x = good_x + test_xy[i][0];
13142 test_y = good_y + test_xy[i][1];
13144 if (!IN_LEV_FIELD(test_x, test_y))
13148 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13150 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13152 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13153 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13155 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13156 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13160 bad_element = test_element;
13166 if (kill_x != -1 || kill_y != -1)
13168 if (IS_PLAYER(good_x, good_y))
13170 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13172 if (player->shield_deadly_time_left > 0 &&
13173 !IS_INDESTRUCTIBLE(bad_element))
13174 Bang(kill_x, kill_y);
13175 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13176 KillPlayer(player);
13179 Bang(good_x, good_y);
13183 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13185 int i, kill_x = -1, kill_y = -1;
13186 int bad_element = Feld[bad_x][bad_y];
13187 static int test_xy[4][2] =
13194 static int touch_dir[4] =
13196 MV_LEFT | MV_RIGHT,
13201 static int test_dir[4] =
13209 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13212 for (i = 0; i < NUM_DIRECTIONS; i++)
13214 int test_x, test_y, test_move_dir, test_element;
13216 test_x = bad_x + test_xy[i][0];
13217 test_y = bad_y + test_xy[i][1];
13219 if (!IN_LEV_FIELD(test_x, test_y))
13223 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13225 test_element = Feld[test_x][test_y];
13227 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13228 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13230 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13231 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13233 // good thing is player or penguin that does not move away
13234 if (IS_PLAYER(test_x, test_y))
13236 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13238 if (bad_element == EL_ROBOT && player->is_moving)
13239 continue; // robot does not kill player if he is moving
13241 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13243 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13244 continue; // center and border element do not touch
13252 else if (test_element == EL_PENGUIN)
13262 if (kill_x != -1 || kill_y != -1)
13264 if (IS_PLAYER(kill_x, kill_y))
13266 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13268 if (player->shield_deadly_time_left > 0 &&
13269 !IS_INDESTRUCTIBLE(bad_element))
13270 Bang(bad_x, bad_y);
13271 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13272 KillPlayer(player);
13275 Bang(kill_x, kill_y);
13279 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13281 int bad_element = Feld[bad_x][bad_y];
13282 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13283 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13284 int test_x = bad_x + dx, test_y = bad_y + dy;
13285 int test_move_dir, test_element;
13286 int kill_x = -1, kill_y = -1;
13288 if (!IN_LEV_FIELD(test_x, test_y))
13292 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13294 test_element = Feld[test_x][test_y];
13296 if (test_move_dir != bad_move_dir)
13298 // good thing can be player or penguin that does not move away
13299 if (IS_PLAYER(test_x, test_y))
13301 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13303 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13304 player as being hit when he is moving towards the bad thing, because
13305 the "get hit by" condition would be lost after the player stops) */
13306 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13307 return; // player moves away from bad thing
13312 else if (test_element == EL_PENGUIN)
13319 if (kill_x != -1 || kill_y != -1)
13321 if (IS_PLAYER(kill_x, kill_y))
13323 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13325 if (player->shield_deadly_time_left > 0 &&
13326 !IS_INDESTRUCTIBLE(bad_element))
13327 Bang(bad_x, bad_y);
13328 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13329 KillPlayer(player);
13332 Bang(kill_x, kill_y);
13336 void TestIfPlayerTouchesBadThing(int x, int y)
13338 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13341 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13343 TestIfGoodThingHitsBadThing(x, y, move_dir);
13346 void TestIfBadThingTouchesPlayer(int x, int y)
13348 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13351 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13353 TestIfBadThingHitsGoodThing(x, y, move_dir);
13356 void TestIfFriendTouchesBadThing(int x, int y)
13358 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13361 void TestIfBadThingTouchesFriend(int x, int y)
13363 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13366 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13368 int i, kill_x = bad_x, kill_y = bad_y;
13369 static int xy[4][2] =
13377 for (i = 0; i < NUM_DIRECTIONS; i++)
13381 x = bad_x + xy[i][0];
13382 y = bad_y + xy[i][1];
13383 if (!IN_LEV_FIELD(x, y))
13386 element = Feld[x][y];
13387 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13388 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13396 if (kill_x != bad_x || kill_y != bad_y)
13397 Bang(bad_x, bad_y);
13400 void KillPlayer(struct PlayerInfo *player)
13402 int jx = player->jx, jy = player->jy;
13404 if (!player->active)
13408 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13409 player->killed, player->active, player->reanimated);
13412 /* the following code was introduced to prevent an infinite loop when calling
13414 -> CheckTriggeredElementChangeExt()
13415 -> ExecuteCustomElementAction()
13417 -> (infinitely repeating the above sequence of function calls)
13418 which occurs when killing the player while having a CE with the setting
13419 "kill player X when explosion of <player X>"; the solution using a new
13420 field "player->killed" was chosen for backwards compatibility, although
13421 clever use of the fields "player->active" etc. would probably also work */
13423 if (player->killed)
13427 player->killed = TRUE;
13429 // remove accessible field at the player's position
13430 Feld[jx][jy] = EL_EMPTY;
13432 // deactivate shield (else Bang()/Explode() would not work right)
13433 player->shield_normal_time_left = 0;
13434 player->shield_deadly_time_left = 0;
13437 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13438 player->killed, player->active, player->reanimated);
13444 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13445 player->killed, player->active, player->reanimated);
13448 if (player->reanimated) // killed player may have been reanimated
13449 player->killed = player->reanimated = FALSE;
13451 BuryPlayer(player);
13454 static void KillPlayerUnlessEnemyProtected(int x, int y)
13456 if (!PLAYER_ENEMY_PROTECTED(x, y))
13457 KillPlayer(PLAYERINFO(x, y));
13460 static void KillPlayerUnlessExplosionProtected(int x, int y)
13462 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13463 KillPlayer(PLAYERINFO(x, y));
13466 void BuryPlayer(struct PlayerInfo *player)
13468 int jx = player->jx, jy = player->jy;
13470 if (!player->active)
13473 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13474 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13476 RemovePlayer(player);
13478 player->buried = TRUE;
13480 if (game.all_players_gone)
13481 game.GameOver = TRUE;
13484 void RemovePlayer(struct PlayerInfo *player)
13486 int jx = player->jx, jy = player->jy;
13487 int i, found = FALSE;
13489 player->present = FALSE;
13490 player->active = FALSE;
13492 // required for some CE actions (even if the player is not active anymore)
13493 player->MovPos = 0;
13495 if (!ExplodeField[jx][jy])
13496 StorePlayer[jx][jy] = 0;
13498 if (player->is_moving)
13499 TEST_DrawLevelField(player->last_jx, player->last_jy);
13501 for (i = 0; i < MAX_PLAYERS; i++)
13502 if (stored_player[i].active)
13507 game.all_players_gone = TRUE;
13508 game.GameOver = TRUE;
13511 game.exit_x = game.robot_wheel_x = jx;
13512 game.exit_y = game.robot_wheel_y = jy;
13515 void ExitPlayer(struct PlayerInfo *player)
13517 DrawPlayer(player); // needed here only to cleanup last field
13518 RemovePlayer(player);
13520 if (game.players_still_needed > 0)
13521 game.players_still_needed--;
13524 static void setFieldForSnapping(int x, int y, int element, int direction)
13526 struct ElementInfo *ei = &element_info[element];
13527 int direction_bit = MV_DIR_TO_BIT(direction);
13528 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13529 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13530 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13532 Feld[x][y] = EL_ELEMENT_SNAPPING;
13533 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13535 ResetGfxAnimation(x, y);
13537 GfxElement[x][y] = element;
13538 GfxAction[x][y] = action;
13539 GfxDir[x][y] = direction;
13540 GfxFrame[x][y] = -1;
13544 =============================================================================
13545 checkDiagonalPushing()
13546 -----------------------------------------------------------------------------
13547 check if diagonal input device direction results in pushing of object
13548 (by checking if the alternative direction is walkable, diggable, ...)
13549 =============================================================================
13552 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13553 int x, int y, int real_dx, int real_dy)
13555 int jx, jy, dx, dy, xx, yy;
13557 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13560 // diagonal direction: check alternative direction
13565 xx = jx + (dx == 0 ? real_dx : 0);
13566 yy = jy + (dy == 0 ? real_dy : 0);
13568 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13572 =============================================================================
13574 -----------------------------------------------------------------------------
13575 x, y: field next to player (non-diagonal) to try to dig to
13576 real_dx, real_dy: direction as read from input device (can be diagonal)
13577 =============================================================================
13580 static int DigField(struct PlayerInfo *player,
13581 int oldx, int oldy, int x, int y,
13582 int real_dx, int real_dy, int mode)
13584 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13585 boolean player_was_pushing = player->is_pushing;
13586 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13587 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13588 int jx = oldx, jy = oldy;
13589 int dx = x - jx, dy = y - jy;
13590 int nextx = x + dx, nexty = y + dy;
13591 int move_direction = (dx == -1 ? MV_LEFT :
13592 dx == +1 ? MV_RIGHT :
13594 dy == +1 ? MV_DOWN : MV_NONE);
13595 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13596 int dig_side = MV_DIR_OPPOSITE(move_direction);
13597 int old_element = Feld[jx][jy];
13598 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13601 if (is_player) // function can also be called by EL_PENGUIN
13603 if (player->MovPos == 0)
13605 player->is_digging = FALSE;
13606 player->is_collecting = FALSE;
13609 if (player->MovPos == 0) // last pushing move finished
13610 player->is_pushing = FALSE;
13612 if (mode == DF_NO_PUSH) // player just stopped pushing
13614 player->is_switching = FALSE;
13615 player->push_delay = -1;
13617 return MP_NO_ACTION;
13621 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13622 old_element = Back[jx][jy];
13624 // in case of element dropped at player position, check background
13625 else if (Back[jx][jy] != EL_EMPTY &&
13626 game.engine_version >= VERSION_IDENT(2,2,0,0))
13627 old_element = Back[jx][jy];
13629 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13630 return MP_NO_ACTION; // field has no opening in this direction
13632 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13633 return MP_NO_ACTION; // field has no opening in this direction
13635 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13639 Feld[jx][jy] = player->artwork_element;
13640 InitMovingField(jx, jy, MV_DOWN);
13641 Store[jx][jy] = EL_ACID;
13642 ContinueMoving(jx, jy);
13643 BuryPlayer(player);
13645 return MP_DONT_RUN_INTO;
13648 if (player_can_move && DONT_RUN_INTO(element))
13650 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13652 return MP_DONT_RUN_INTO;
13655 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13656 return MP_NO_ACTION;
13658 collect_count = element_info[element].collect_count_initial;
13660 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13661 return MP_NO_ACTION;
13663 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13664 player_can_move = player_can_move_or_snap;
13666 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13667 game.engine_version >= VERSION_IDENT(2,2,0,0))
13669 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13670 player->index_bit, dig_side);
13671 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13672 player->index_bit, dig_side);
13674 if (element == EL_DC_LANDMINE)
13677 if (Feld[x][y] != element) // field changed by snapping
13680 return MP_NO_ACTION;
13683 if (player->gravity && is_player && !player->is_auto_moving &&
13684 canFallDown(player) && move_direction != MV_DOWN &&
13685 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13686 return MP_NO_ACTION; // player cannot walk here due to gravity
13688 if (player_can_move &&
13689 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13691 int sound_element = SND_ELEMENT(element);
13692 int sound_action = ACTION_WALKING;
13694 if (IS_RND_GATE(element))
13696 if (!player->key[RND_GATE_NR(element)])
13697 return MP_NO_ACTION;
13699 else if (IS_RND_GATE_GRAY(element))
13701 if (!player->key[RND_GATE_GRAY_NR(element)])
13702 return MP_NO_ACTION;
13704 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13706 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13707 return MP_NO_ACTION;
13709 else if (element == EL_EXIT_OPEN ||
13710 element == EL_EM_EXIT_OPEN ||
13711 element == EL_EM_EXIT_OPENING ||
13712 element == EL_STEEL_EXIT_OPEN ||
13713 element == EL_EM_STEEL_EXIT_OPEN ||
13714 element == EL_EM_STEEL_EXIT_OPENING ||
13715 element == EL_SP_EXIT_OPEN ||
13716 element == EL_SP_EXIT_OPENING)
13718 sound_action = ACTION_PASSING; // player is passing exit
13720 else if (element == EL_EMPTY)
13722 sound_action = ACTION_MOVING; // nothing to walk on
13725 // play sound from background or player, whatever is available
13726 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13727 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13729 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13731 else if (player_can_move &&
13732 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13734 if (!ACCESS_FROM(element, opposite_direction))
13735 return MP_NO_ACTION; // field not accessible from this direction
13737 if (CAN_MOVE(element)) // only fixed elements can be passed!
13738 return MP_NO_ACTION;
13740 if (IS_EM_GATE(element))
13742 if (!player->key[EM_GATE_NR(element)])
13743 return MP_NO_ACTION;
13745 else if (IS_EM_GATE_GRAY(element))
13747 if (!player->key[EM_GATE_GRAY_NR(element)])
13748 return MP_NO_ACTION;
13750 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13752 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13753 return MP_NO_ACTION;
13755 else if (IS_EMC_GATE(element))
13757 if (!player->key[EMC_GATE_NR(element)])
13758 return MP_NO_ACTION;
13760 else if (IS_EMC_GATE_GRAY(element))
13762 if (!player->key[EMC_GATE_GRAY_NR(element)])
13763 return MP_NO_ACTION;
13765 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13767 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13768 return MP_NO_ACTION;
13770 else if (element == EL_DC_GATE_WHITE ||
13771 element == EL_DC_GATE_WHITE_GRAY ||
13772 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13774 if (player->num_white_keys == 0)
13775 return MP_NO_ACTION;
13777 player->num_white_keys--;
13779 else if (IS_SP_PORT(element))
13781 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13782 element == EL_SP_GRAVITY_PORT_RIGHT ||
13783 element == EL_SP_GRAVITY_PORT_UP ||
13784 element == EL_SP_GRAVITY_PORT_DOWN)
13785 player->gravity = !player->gravity;
13786 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13787 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13788 element == EL_SP_GRAVITY_ON_PORT_UP ||
13789 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13790 player->gravity = TRUE;
13791 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13792 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13793 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13794 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13795 player->gravity = FALSE;
13798 // automatically move to the next field with double speed
13799 player->programmed_action = move_direction;
13801 if (player->move_delay_reset_counter == 0)
13803 player->move_delay_reset_counter = 2; // two double speed steps
13805 DOUBLE_PLAYER_SPEED(player);
13808 PlayLevelSoundAction(x, y, ACTION_PASSING);
13810 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13814 if (mode != DF_SNAP)
13816 GfxElement[x][y] = GFX_ELEMENT(element);
13817 player->is_digging = TRUE;
13820 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13822 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13823 player->index_bit, dig_side);
13825 if (mode == DF_SNAP)
13827 if (level.block_snap_field)
13828 setFieldForSnapping(x, y, element, move_direction);
13830 TestIfElementTouchesCustomElement(x, y); // for empty space
13832 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13833 player->index_bit, dig_side);
13836 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13840 if (is_player && mode != DF_SNAP)
13842 GfxElement[x][y] = element;
13843 player->is_collecting = TRUE;
13846 if (element == EL_SPEED_PILL)
13848 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13850 else if (element == EL_EXTRA_TIME && level.time > 0)
13852 TimeLeft += level.extra_time;
13854 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13856 DisplayGameControlValues();
13858 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
13860 player->shield_normal_time_left += level.shield_normal_time;
13861 if (element == EL_SHIELD_DEADLY)
13862 player->shield_deadly_time_left += level.shield_deadly_time;
13864 else if (element == EL_DYNAMITE ||
13865 element == EL_EM_DYNAMITE ||
13866 element == EL_SP_DISK_RED)
13868 if (player->inventory_size < MAX_INVENTORY_SIZE)
13869 player->inventory_element[player->inventory_size++] = element;
13871 DrawGameDoorValues();
13873 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
13875 player->dynabomb_count++;
13876 player->dynabombs_left++;
13878 else if (element == EL_DYNABOMB_INCREASE_SIZE)
13880 player->dynabomb_size++;
13882 else if (element == EL_DYNABOMB_INCREASE_POWER)
13884 player->dynabomb_xl = TRUE;
13886 else if (IS_KEY(element))
13888 player->key[KEY_NR(element)] = TRUE;
13890 DrawGameDoorValues();
13892 else if (element == EL_DC_KEY_WHITE)
13894 player->num_white_keys++;
13896 // display white keys?
13897 // DrawGameDoorValues();
13899 else if (IS_ENVELOPE(element))
13901 player->show_envelope = element;
13903 else if (element == EL_EMC_LENSES)
13905 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
13907 RedrawAllInvisibleElementsForLenses();
13909 else if (element == EL_EMC_MAGNIFIER)
13911 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
13913 RedrawAllInvisibleElementsForMagnifier();
13915 else if (IS_DROPPABLE(element) ||
13916 IS_THROWABLE(element)) // can be collected and dropped
13920 if (collect_count == 0)
13921 player->inventory_infinite_element = element;
13923 for (i = 0; i < collect_count; i++)
13924 if (player->inventory_size < MAX_INVENTORY_SIZE)
13925 player->inventory_element[player->inventory_size++] = element;
13927 DrawGameDoorValues();
13929 else if (collect_count > 0)
13931 game.gems_still_needed -= collect_count;
13932 if (game.gems_still_needed < 0)
13933 game.gems_still_needed = 0;
13935 game.snapshot.collected_item = TRUE;
13937 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
13939 DisplayGameControlValues();
13942 RaiseScoreElement(element);
13943 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
13946 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
13947 player->index_bit, dig_side);
13949 if (mode == DF_SNAP)
13951 if (level.block_snap_field)
13952 setFieldForSnapping(x, y, element, move_direction);
13954 TestIfElementTouchesCustomElement(x, y); // for empty space
13956 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13957 player->index_bit, dig_side);
13960 else if (player_can_move_or_snap && IS_PUSHABLE(element))
13962 if (mode == DF_SNAP && element != EL_BD_ROCK)
13963 return MP_NO_ACTION;
13965 if (CAN_FALL(element) && dy)
13966 return MP_NO_ACTION;
13968 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
13969 !(element == EL_SPRING && level.use_spring_bug))
13970 return MP_NO_ACTION;
13972 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
13973 ((move_direction & MV_VERTICAL &&
13974 ((element_info[element].move_pattern & MV_LEFT &&
13975 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
13976 (element_info[element].move_pattern & MV_RIGHT &&
13977 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
13978 (move_direction & MV_HORIZONTAL &&
13979 ((element_info[element].move_pattern & MV_UP &&
13980 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
13981 (element_info[element].move_pattern & MV_DOWN &&
13982 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
13983 return MP_NO_ACTION;
13985 // do not push elements already moving away faster than player
13986 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
13987 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
13988 return MP_NO_ACTION;
13990 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
13992 if (player->push_delay_value == -1 || !player_was_pushing)
13993 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13995 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13997 if (player->push_delay_value == -1)
13998 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14000 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14002 if (!player->is_pushing)
14003 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14006 player->is_pushing = TRUE;
14007 player->is_active = TRUE;
14009 if (!(IN_LEV_FIELD(nextx, nexty) &&
14010 (IS_FREE(nextx, nexty) ||
14011 (IS_SB_ELEMENT(element) &&
14012 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14013 (IS_CUSTOM_ELEMENT(element) &&
14014 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14015 return MP_NO_ACTION;
14017 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14018 return MP_NO_ACTION;
14020 if (player->push_delay == -1) // new pushing; restart delay
14021 player->push_delay = 0;
14023 if (player->push_delay < player->push_delay_value &&
14024 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14025 element != EL_SPRING && element != EL_BALLOON)
14027 // make sure that there is no move delay before next try to push
14028 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14029 player->move_delay = 0;
14031 return MP_NO_ACTION;
14034 if (IS_CUSTOM_ELEMENT(element) &&
14035 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14037 if (!DigFieldByCE(nextx, nexty, element))
14038 return MP_NO_ACTION;
14041 if (IS_SB_ELEMENT(element))
14043 boolean sokoban_task_solved = FALSE;
14045 if (element == EL_SOKOBAN_FIELD_FULL)
14047 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14049 IncrementSokobanFieldsNeeded();
14050 IncrementSokobanObjectsNeeded();
14053 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14055 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14057 DecrementSokobanFieldsNeeded();
14058 DecrementSokobanObjectsNeeded();
14060 // sokoban object was pushed from empty field to sokoban field
14061 if (Back[x][y] == EL_EMPTY)
14062 sokoban_task_solved = TRUE;
14065 Feld[x][y] = EL_SOKOBAN_OBJECT;
14067 if (Back[x][y] == Back[nextx][nexty])
14068 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14069 else if (Back[x][y] != 0)
14070 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14073 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14076 if (sokoban_task_solved &&
14077 game.sokoban_fields_still_needed == 0 &&
14078 game.sokoban_objects_still_needed == 0 &&
14079 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14081 game.players_still_needed = 0;
14085 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14089 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14091 InitMovingField(x, y, move_direction);
14092 GfxAction[x][y] = ACTION_PUSHING;
14094 if (mode == DF_SNAP)
14095 ContinueMoving(x, y);
14097 MovPos[x][y] = (dx != 0 ? dx : dy);
14099 Pushed[x][y] = TRUE;
14100 Pushed[nextx][nexty] = TRUE;
14102 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14103 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14105 player->push_delay_value = -1; // get new value later
14107 // check for element change _after_ element has been pushed
14108 if (game.use_change_when_pushing_bug)
14110 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14111 player->index_bit, dig_side);
14112 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14113 player->index_bit, dig_side);
14116 else if (IS_SWITCHABLE(element))
14118 if (PLAYER_SWITCHING(player, x, y))
14120 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14121 player->index_bit, dig_side);
14126 player->is_switching = TRUE;
14127 player->switch_x = x;
14128 player->switch_y = y;
14130 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14132 if (element == EL_ROBOT_WHEEL)
14134 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14136 game.robot_wheel_x = x;
14137 game.robot_wheel_y = y;
14138 game.robot_wheel_active = TRUE;
14140 TEST_DrawLevelField(x, y);
14142 else if (element == EL_SP_TERMINAL)
14146 SCAN_PLAYFIELD(xx, yy)
14148 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14152 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14154 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14156 ResetGfxAnimation(xx, yy);
14157 TEST_DrawLevelField(xx, yy);
14161 else if (IS_BELT_SWITCH(element))
14163 ToggleBeltSwitch(x, y);
14165 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14166 element == EL_SWITCHGATE_SWITCH_DOWN ||
14167 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14168 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14170 ToggleSwitchgateSwitch(x, y);
14172 else if (element == EL_LIGHT_SWITCH ||
14173 element == EL_LIGHT_SWITCH_ACTIVE)
14175 ToggleLightSwitch(x, y);
14177 else if (element == EL_TIMEGATE_SWITCH ||
14178 element == EL_DC_TIMEGATE_SWITCH)
14180 ActivateTimegateSwitch(x, y);
14182 else if (element == EL_BALLOON_SWITCH_LEFT ||
14183 element == EL_BALLOON_SWITCH_RIGHT ||
14184 element == EL_BALLOON_SWITCH_UP ||
14185 element == EL_BALLOON_SWITCH_DOWN ||
14186 element == EL_BALLOON_SWITCH_NONE ||
14187 element == EL_BALLOON_SWITCH_ANY)
14189 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14190 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14191 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14192 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14193 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14196 else if (element == EL_LAMP)
14198 Feld[x][y] = EL_LAMP_ACTIVE;
14199 game.lights_still_needed--;
14201 ResetGfxAnimation(x, y);
14202 TEST_DrawLevelField(x, y);
14204 else if (element == EL_TIME_ORB_FULL)
14206 Feld[x][y] = EL_TIME_ORB_EMPTY;
14208 if (level.time > 0 || level.use_time_orb_bug)
14210 TimeLeft += level.time_orb_time;
14211 game.no_time_limit = FALSE;
14213 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14215 DisplayGameControlValues();
14218 ResetGfxAnimation(x, y);
14219 TEST_DrawLevelField(x, y);
14221 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14222 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14226 game.ball_state = !game.ball_state;
14228 SCAN_PLAYFIELD(xx, yy)
14230 int e = Feld[xx][yy];
14232 if (game.ball_state)
14234 if (e == EL_EMC_MAGIC_BALL)
14235 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14236 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14237 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14241 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14242 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14243 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14244 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14249 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14250 player->index_bit, dig_side);
14252 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14253 player->index_bit, dig_side);
14255 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14256 player->index_bit, dig_side);
14262 if (!PLAYER_SWITCHING(player, x, y))
14264 player->is_switching = TRUE;
14265 player->switch_x = x;
14266 player->switch_y = y;
14268 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14269 player->index_bit, dig_side);
14270 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14271 player->index_bit, dig_side);
14273 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14274 player->index_bit, dig_side);
14275 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14276 player->index_bit, dig_side);
14279 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14280 player->index_bit, dig_side);
14281 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14282 player->index_bit, dig_side);
14284 return MP_NO_ACTION;
14287 player->push_delay = -1;
14289 if (is_player) // function can also be called by EL_PENGUIN
14291 if (Feld[x][y] != element) // really digged/collected something
14293 player->is_collecting = !player->is_digging;
14294 player->is_active = TRUE;
14301 static boolean DigFieldByCE(int x, int y, int digging_element)
14303 int element = Feld[x][y];
14305 if (!IS_FREE(x, y))
14307 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14308 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14311 // no element can dig solid indestructible elements
14312 if (IS_INDESTRUCTIBLE(element) &&
14313 !IS_DIGGABLE(element) &&
14314 !IS_COLLECTIBLE(element))
14317 if (AmoebaNr[x][y] &&
14318 (element == EL_AMOEBA_FULL ||
14319 element == EL_BD_AMOEBA ||
14320 element == EL_AMOEBA_GROWING))
14322 AmoebaCnt[AmoebaNr[x][y]]--;
14323 AmoebaCnt2[AmoebaNr[x][y]]--;
14326 if (IS_MOVING(x, y))
14327 RemoveMovingField(x, y);
14331 TEST_DrawLevelField(x, y);
14334 // if digged element was about to explode, prevent the explosion
14335 ExplodeField[x][y] = EX_TYPE_NONE;
14337 PlayLevelSoundAction(x, y, action);
14340 Store[x][y] = EL_EMPTY;
14342 // this makes it possible to leave the removed element again
14343 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14344 Store[x][y] = element;
14349 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14351 int jx = player->jx, jy = player->jy;
14352 int x = jx + dx, y = jy + dy;
14353 int snap_direction = (dx == -1 ? MV_LEFT :
14354 dx == +1 ? MV_RIGHT :
14356 dy == +1 ? MV_DOWN : MV_NONE);
14357 boolean can_continue_snapping = (level.continuous_snapping &&
14358 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14360 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14363 if (!player->active || !IN_LEV_FIELD(x, y))
14371 if (player->MovPos == 0)
14372 player->is_pushing = FALSE;
14374 player->is_snapping = FALSE;
14376 if (player->MovPos == 0)
14378 player->is_moving = FALSE;
14379 player->is_digging = FALSE;
14380 player->is_collecting = FALSE;
14386 // prevent snapping with already pressed snap key when not allowed
14387 if (player->is_snapping && !can_continue_snapping)
14390 player->MovDir = snap_direction;
14392 if (player->MovPos == 0)
14394 player->is_moving = FALSE;
14395 player->is_digging = FALSE;
14396 player->is_collecting = FALSE;
14399 player->is_dropping = FALSE;
14400 player->is_dropping_pressed = FALSE;
14401 player->drop_pressed_delay = 0;
14403 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14406 player->is_snapping = TRUE;
14407 player->is_active = TRUE;
14409 if (player->MovPos == 0)
14411 player->is_moving = FALSE;
14412 player->is_digging = FALSE;
14413 player->is_collecting = FALSE;
14416 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14417 TEST_DrawLevelField(player->last_jx, player->last_jy);
14419 TEST_DrawLevelField(x, y);
14424 static boolean DropElement(struct PlayerInfo *player)
14426 int old_element, new_element;
14427 int dropx = player->jx, dropy = player->jy;
14428 int drop_direction = player->MovDir;
14429 int drop_side = drop_direction;
14430 int drop_element = get_next_dropped_element(player);
14432 /* do not drop an element on top of another element; when holding drop key
14433 pressed without moving, dropped element must move away before the next
14434 element can be dropped (this is especially important if the next element
14435 is dynamite, which can be placed on background for historical reasons) */
14436 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14439 if (IS_THROWABLE(drop_element))
14441 dropx += GET_DX_FROM_DIR(drop_direction);
14442 dropy += GET_DY_FROM_DIR(drop_direction);
14444 if (!IN_LEV_FIELD(dropx, dropy))
14448 old_element = Feld[dropx][dropy]; // old element at dropping position
14449 new_element = drop_element; // default: no change when dropping
14451 // check if player is active, not moving and ready to drop
14452 if (!player->active || player->MovPos || player->drop_delay > 0)
14455 // check if player has anything that can be dropped
14456 if (new_element == EL_UNDEFINED)
14459 // only set if player has anything that can be dropped
14460 player->is_dropping_pressed = TRUE;
14462 // check if drop key was pressed long enough for EM style dynamite
14463 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14466 // check if anything can be dropped at the current position
14467 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14470 // collected custom elements can only be dropped on empty fields
14471 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14474 if (old_element != EL_EMPTY)
14475 Back[dropx][dropy] = old_element; // store old element on this field
14477 ResetGfxAnimation(dropx, dropy);
14478 ResetRandomAnimationValue(dropx, dropy);
14480 if (player->inventory_size > 0 ||
14481 player->inventory_infinite_element != EL_UNDEFINED)
14483 if (player->inventory_size > 0)
14485 player->inventory_size--;
14487 DrawGameDoorValues();
14489 if (new_element == EL_DYNAMITE)
14490 new_element = EL_DYNAMITE_ACTIVE;
14491 else if (new_element == EL_EM_DYNAMITE)
14492 new_element = EL_EM_DYNAMITE_ACTIVE;
14493 else if (new_element == EL_SP_DISK_RED)
14494 new_element = EL_SP_DISK_RED_ACTIVE;
14497 Feld[dropx][dropy] = new_element;
14499 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14500 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14501 el2img(Feld[dropx][dropy]), 0);
14503 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14505 // needed if previous element just changed to "empty" in the last frame
14506 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14508 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14509 player->index_bit, drop_side);
14510 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14512 player->index_bit, drop_side);
14514 TestIfElementTouchesCustomElement(dropx, dropy);
14516 else // player is dropping a dyna bomb
14518 player->dynabombs_left--;
14520 Feld[dropx][dropy] = new_element;
14522 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14523 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14524 el2img(Feld[dropx][dropy]), 0);
14526 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14529 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14530 InitField_WithBug1(dropx, dropy, FALSE);
14532 new_element = Feld[dropx][dropy]; // element might have changed
14534 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14535 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14537 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14538 MovDir[dropx][dropy] = drop_direction;
14540 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14542 // do not cause impact style collision by dropping elements that can fall
14543 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14546 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14547 player->is_dropping = TRUE;
14549 player->drop_pressed_delay = 0;
14550 player->is_dropping_pressed = FALSE;
14552 player->drop_x = dropx;
14553 player->drop_y = dropy;
14558 // ----------------------------------------------------------------------------
14559 // game sound playing functions
14560 // ----------------------------------------------------------------------------
14562 static int *loop_sound_frame = NULL;
14563 static int *loop_sound_volume = NULL;
14565 void InitPlayLevelSound(void)
14567 int num_sounds = getSoundListSize();
14569 checked_free(loop_sound_frame);
14570 checked_free(loop_sound_volume);
14572 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14573 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14576 static void PlayLevelSound(int x, int y, int nr)
14578 int sx = SCREENX(x), sy = SCREENY(y);
14579 int volume, stereo_position;
14580 int max_distance = 8;
14581 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14583 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14584 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14587 if (!IN_LEV_FIELD(x, y) ||
14588 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14589 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14592 volume = SOUND_MAX_VOLUME;
14594 if (!IN_SCR_FIELD(sx, sy))
14596 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14597 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14599 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14602 stereo_position = (SOUND_MAX_LEFT +
14603 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14604 (SCR_FIELDX + 2 * max_distance));
14606 if (IS_LOOP_SOUND(nr))
14608 /* This assures that quieter loop sounds do not overwrite louder ones,
14609 while restarting sound volume comparison with each new game frame. */
14611 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14614 loop_sound_volume[nr] = volume;
14615 loop_sound_frame[nr] = FrameCounter;
14618 PlaySoundExt(nr, volume, stereo_position, type);
14621 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14623 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14624 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14625 y < LEVELY(BY1) ? LEVELY(BY1) :
14626 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14630 static void PlayLevelSoundAction(int x, int y, int action)
14632 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14635 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14637 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14639 if (sound_effect != SND_UNDEFINED)
14640 PlayLevelSound(x, y, sound_effect);
14643 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14646 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14648 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14649 PlayLevelSound(x, y, sound_effect);
14652 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14654 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14656 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14657 PlayLevelSound(x, y, sound_effect);
14660 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14662 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14664 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14665 StopSound(sound_effect);
14668 static int getLevelMusicNr(void)
14670 if (levelset.music[level_nr] != MUS_UNDEFINED)
14671 return levelset.music[level_nr]; // from config file
14673 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14676 static void FadeLevelSounds(void)
14681 static void FadeLevelMusic(void)
14683 int music_nr = getLevelMusicNr();
14684 char *curr_music = getCurrentlyPlayingMusicFilename();
14685 char *next_music = getMusicInfoEntryFilename(music_nr);
14687 if (!strEqual(curr_music, next_music))
14691 void FadeLevelSoundsAndMusic(void)
14697 static void PlayLevelMusic(void)
14699 int music_nr = getLevelMusicNr();
14700 char *curr_music = getCurrentlyPlayingMusicFilename();
14701 char *next_music = getMusicInfoEntryFilename(music_nr);
14703 if (!strEqual(curr_music, next_music))
14704 PlayMusicLoop(music_nr);
14707 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14709 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14710 int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
14711 int x = xx - 1 - offset;
14712 int y = yy - 1 - offset;
14717 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14721 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14725 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14729 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14733 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14737 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14741 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14744 case SOUND_android_clone:
14745 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14748 case SOUND_android_move:
14749 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14753 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14757 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14761 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14764 case SOUND_eater_eat:
14765 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14769 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14772 case SOUND_collect:
14773 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14776 case SOUND_diamond:
14777 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14781 // !!! CHECK THIS !!!
14783 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14785 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14789 case SOUND_wonderfall:
14790 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14794 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14798 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14802 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14806 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14810 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14814 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14818 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14822 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14825 case SOUND_exit_open:
14826 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14829 case SOUND_exit_leave:
14830 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14833 case SOUND_dynamite:
14834 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14838 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14842 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14846 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14850 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14854 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
14858 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
14862 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
14867 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
14869 int element = map_element_SP_to_RND(element_sp);
14870 int action = map_action_SP_to_RND(action_sp);
14871 int offset = (setup.sp_show_border_elements ? 0 : 1);
14872 int x = xx - offset;
14873 int y = yy - offset;
14875 PlayLevelSoundElementAction(x, y, element, action);
14878 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
14880 int element = map_element_MM_to_RND(element_mm);
14881 int action = map_action_MM_to_RND(action_mm);
14883 int x = xx - offset;
14884 int y = yy - offset;
14886 if (!IS_MM_ELEMENT(element))
14887 element = EL_MM_DEFAULT;
14889 PlayLevelSoundElementAction(x, y, element, action);
14892 void PlaySound_MM(int sound_mm)
14894 int sound = map_sound_MM_to_RND(sound_mm);
14896 if (sound == SND_UNDEFINED)
14902 void PlaySoundLoop_MM(int sound_mm)
14904 int sound = map_sound_MM_to_RND(sound_mm);
14906 if (sound == SND_UNDEFINED)
14909 PlaySoundLoop(sound);
14912 void StopSound_MM(int sound_mm)
14914 int sound = map_sound_MM_to_RND(sound_mm);
14916 if (sound == SND_UNDEFINED)
14922 void RaiseScore(int value)
14924 game.score += value;
14926 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
14928 DisplayGameControlValues();
14931 void RaiseScoreElement(int element)
14936 case EL_BD_DIAMOND:
14937 case EL_EMERALD_YELLOW:
14938 case EL_EMERALD_RED:
14939 case EL_EMERALD_PURPLE:
14940 case EL_SP_INFOTRON:
14941 RaiseScore(level.score[SC_EMERALD]);
14944 RaiseScore(level.score[SC_DIAMOND]);
14947 RaiseScore(level.score[SC_CRYSTAL]);
14950 RaiseScore(level.score[SC_PEARL]);
14953 case EL_BD_BUTTERFLY:
14954 case EL_SP_ELECTRON:
14955 RaiseScore(level.score[SC_BUG]);
14958 case EL_BD_FIREFLY:
14959 case EL_SP_SNIKSNAK:
14960 RaiseScore(level.score[SC_SPACESHIP]);
14963 case EL_DARK_YAMYAM:
14964 RaiseScore(level.score[SC_YAMYAM]);
14967 RaiseScore(level.score[SC_ROBOT]);
14970 RaiseScore(level.score[SC_PACMAN]);
14973 RaiseScore(level.score[SC_NUT]);
14976 case EL_EM_DYNAMITE:
14977 case EL_SP_DISK_RED:
14978 case EL_DYNABOMB_INCREASE_NUMBER:
14979 case EL_DYNABOMB_INCREASE_SIZE:
14980 case EL_DYNABOMB_INCREASE_POWER:
14981 RaiseScore(level.score[SC_DYNAMITE]);
14983 case EL_SHIELD_NORMAL:
14984 case EL_SHIELD_DEADLY:
14985 RaiseScore(level.score[SC_SHIELD]);
14987 case EL_EXTRA_TIME:
14988 RaiseScore(level.extra_time_score);
15002 case EL_DC_KEY_WHITE:
15003 RaiseScore(level.score[SC_KEY]);
15006 RaiseScore(element_info[element].collect_score);
15011 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15013 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15015 // closing door required in case of envelope style request dialogs
15018 // prevent short reactivation of overlay buttons while closing door
15019 SetOverlayActive(FALSE);
15021 CloseDoor(DOOR_CLOSE_1);
15024 if (network.enabled)
15025 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15029 FadeSkipNextFadeIn();
15031 SetGameStatus(GAME_MODE_MAIN);
15036 else // continue playing the game
15038 if (tape.playing && tape.deactivate_display)
15039 TapeDeactivateDisplayOff(TRUE);
15041 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15043 if (tape.playing && tape.deactivate_display)
15044 TapeDeactivateDisplayOn();
15048 void RequestQuitGame(boolean ask_if_really_quit)
15050 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15051 boolean skip_request = game.all_players_gone || quick_quit;
15053 RequestQuitGameExt(skip_request, quick_quit,
15054 "Do you really want to quit the game?");
15057 void RequestRestartGame(char *message)
15059 game.restart_game_message = NULL;
15061 boolean has_started_game = hasStartedNetworkGame();
15062 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15064 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15066 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15070 SetGameStatus(GAME_MODE_MAIN);
15076 void CheckGameOver(void)
15078 static boolean last_game_over = FALSE;
15079 static int game_over_delay = 0;
15080 int game_over_delay_value = 50;
15081 boolean game_over = checkGameFailed();
15083 // do not handle game over if request dialog is already active
15084 if (game.request_active)
15087 // do not ask to play again if game was never actually played
15088 if (!game.GamePlayed)
15093 last_game_over = FALSE;
15094 game_over_delay = game_over_delay_value;
15099 if (game_over_delay > 0)
15106 if (last_game_over != game_over)
15107 game.restart_game_message = (hasStartedNetworkGame() ?
15108 "Game over! Play it again?" :
15111 last_game_over = game_over;
15114 boolean checkGameSolved(void)
15116 // set for all game engines if level was solved
15117 return game.LevelSolved_GameEnd;
15120 boolean checkGameFailed(void)
15122 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15123 return (game_em.game_over && !game_em.level_solved);
15124 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15125 return (game_sp.game_over && !game_sp.level_solved);
15126 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15127 return (game_mm.game_over && !game_mm.level_solved);
15128 else // GAME_ENGINE_TYPE_RND
15129 return (game.GameOver && !game.LevelSolved);
15132 boolean checkGameEnded(void)
15134 return (checkGameSolved() || checkGameFailed());
15138 // ----------------------------------------------------------------------------
15139 // random generator functions
15140 // ----------------------------------------------------------------------------
15142 unsigned int InitEngineRandom_RND(int seed)
15144 game.num_random_calls = 0;
15146 return InitEngineRandom(seed);
15149 unsigned int RND(int max)
15153 game.num_random_calls++;
15155 return GetEngineRandom(max);
15162 // ----------------------------------------------------------------------------
15163 // game engine snapshot handling functions
15164 // ----------------------------------------------------------------------------
15166 struct EngineSnapshotInfo
15168 // runtime values for custom element collect score
15169 int collect_score[NUM_CUSTOM_ELEMENTS];
15171 // runtime values for group element choice position
15172 int choice_pos[NUM_GROUP_ELEMENTS];
15174 // runtime values for belt position animations
15175 int belt_graphic[4][NUM_BELT_PARTS];
15176 int belt_anim_mode[4][NUM_BELT_PARTS];
15179 static struct EngineSnapshotInfo engine_snapshot_rnd;
15180 static char *snapshot_level_identifier = NULL;
15181 static int snapshot_level_nr = -1;
15183 static void SaveEngineSnapshotValues_RND(void)
15185 static int belt_base_active_element[4] =
15187 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15188 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15189 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15190 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15194 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15196 int element = EL_CUSTOM_START + i;
15198 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15201 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15203 int element = EL_GROUP_START + i;
15205 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15208 for (i = 0; i < 4; i++)
15210 for (j = 0; j < NUM_BELT_PARTS; j++)
15212 int element = belt_base_active_element[i] + j;
15213 int graphic = el2img(element);
15214 int anim_mode = graphic_info[graphic].anim_mode;
15216 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15217 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15222 static void LoadEngineSnapshotValues_RND(void)
15224 unsigned int num_random_calls = game.num_random_calls;
15227 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15229 int element = EL_CUSTOM_START + i;
15231 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15234 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15236 int element = EL_GROUP_START + i;
15238 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15241 for (i = 0; i < 4; i++)
15243 for (j = 0; j < NUM_BELT_PARTS; j++)
15245 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15246 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15248 graphic_info[graphic].anim_mode = anim_mode;
15252 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15254 InitRND(tape.random_seed);
15255 for (i = 0; i < num_random_calls; i++)
15259 if (game.num_random_calls != num_random_calls)
15261 Error(ERR_INFO, "number of random calls out of sync");
15262 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15263 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15264 Error(ERR_EXIT, "this should not happen -- please debug");
15268 void FreeEngineSnapshotSingle(void)
15270 FreeSnapshotSingle();
15272 setString(&snapshot_level_identifier, NULL);
15273 snapshot_level_nr = -1;
15276 void FreeEngineSnapshotList(void)
15278 FreeSnapshotList();
15281 static ListNode *SaveEngineSnapshotBuffers(void)
15283 ListNode *buffers = NULL;
15285 // copy some special values to a structure better suited for the snapshot
15287 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15288 SaveEngineSnapshotValues_RND();
15289 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15290 SaveEngineSnapshotValues_EM();
15291 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15292 SaveEngineSnapshotValues_SP(&buffers);
15293 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15294 SaveEngineSnapshotValues_MM(&buffers);
15296 // save values stored in special snapshot structure
15298 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15299 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15300 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15301 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15302 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15303 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15304 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15305 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15307 // save further RND engine values
15309 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15310 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15311 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15313 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15314 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15315 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15316 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15317 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15319 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15320 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15321 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15323 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15325 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15326 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15328 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15329 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15330 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15331 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15332 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15333 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15334 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15335 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15336 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15337 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15338 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15339 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15340 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15341 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15342 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15343 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15344 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15345 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15347 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15348 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15350 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15351 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15352 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15354 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15355 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15357 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15358 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15359 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15360 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15361 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15363 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15364 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15367 ListNode *node = engine_snapshot_list_rnd;
15370 while (node != NULL)
15372 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15377 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15383 void SaveEngineSnapshotSingle(void)
15385 ListNode *buffers = SaveEngineSnapshotBuffers();
15387 // finally save all snapshot buffers to single snapshot
15388 SaveSnapshotSingle(buffers);
15390 // save level identification information
15391 setString(&snapshot_level_identifier, leveldir_current->identifier);
15392 snapshot_level_nr = level_nr;
15395 boolean CheckSaveEngineSnapshotToList(void)
15397 boolean save_snapshot =
15398 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15399 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15400 game.snapshot.changed_action) ||
15401 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15402 game.snapshot.collected_item));
15404 game.snapshot.changed_action = FALSE;
15405 game.snapshot.collected_item = FALSE;
15406 game.snapshot.save_snapshot = save_snapshot;
15408 return save_snapshot;
15411 void SaveEngineSnapshotToList(void)
15413 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15417 ListNode *buffers = SaveEngineSnapshotBuffers();
15419 // finally save all snapshot buffers to snapshot list
15420 SaveSnapshotToList(buffers);
15423 void SaveEngineSnapshotToListInitial(void)
15425 FreeEngineSnapshotList();
15427 SaveEngineSnapshotToList();
15430 static void LoadEngineSnapshotValues(void)
15432 // restore special values from snapshot structure
15434 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15435 LoadEngineSnapshotValues_RND();
15436 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15437 LoadEngineSnapshotValues_EM();
15438 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15439 LoadEngineSnapshotValues_SP();
15440 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15441 LoadEngineSnapshotValues_MM();
15444 void LoadEngineSnapshotSingle(void)
15446 LoadSnapshotSingle();
15448 LoadEngineSnapshotValues();
15451 static void LoadEngineSnapshot_Undo(int steps)
15453 LoadSnapshotFromList_Older(steps);
15455 LoadEngineSnapshotValues();
15458 static void LoadEngineSnapshot_Redo(int steps)
15460 LoadSnapshotFromList_Newer(steps);
15462 LoadEngineSnapshotValues();
15465 boolean CheckEngineSnapshotSingle(void)
15467 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15468 snapshot_level_nr == level_nr);
15471 boolean CheckEngineSnapshotList(void)
15473 return CheckSnapshotList();
15477 // ---------- new game button stuff -------------------------------------------
15484 boolean *setup_value;
15485 boolean allowed_on_tape;
15486 boolean is_touch_button;
15488 } gamebutton_info[NUM_GAME_BUTTONS] =
15491 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15492 GAME_CTRL_ID_STOP, NULL,
15493 TRUE, FALSE, "stop game"
15496 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15497 GAME_CTRL_ID_PAUSE, NULL,
15498 TRUE, FALSE, "pause game"
15501 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15502 GAME_CTRL_ID_PLAY, NULL,
15503 TRUE, FALSE, "play game"
15506 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15507 GAME_CTRL_ID_UNDO, NULL,
15508 TRUE, FALSE, "undo step"
15511 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15512 GAME_CTRL_ID_REDO, NULL,
15513 TRUE, FALSE, "redo step"
15516 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15517 GAME_CTRL_ID_SAVE, NULL,
15518 TRUE, FALSE, "save game"
15521 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15522 GAME_CTRL_ID_PAUSE2, NULL,
15523 TRUE, FALSE, "pause game"
15526 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15527 GAME_CTRL_ID_LOAD, NULL,
15528 TRUE, FALSE, "load game"
15531 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15532 GAME_CTRL_ID_PANEL_STOP, NULL,
15533 FALSE, FALSE, "stop game"
15536 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15537 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15538 FALSE, FALSE, "pause game"
15541 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15542 GAME_CTRL_ID_PANEL_PLAY, NULL,
15543 FALSE, FALSE, "play game"
15546 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15547 GAME_CTRL_ID_TOUCH_STOP, NULL,
15548 FALSE, TRUE, "stop game"
15551 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15552 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15553 FALSE, TRUE, "pause game"
15556 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15557 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15558 TRUE, FALSE, "background music on/off"
15561 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15562 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15563 TRUE, FALSE, "sound loops on/off"
15566 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15567 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15568 TRUE, FALSE, "normal sounds on/off"
15571 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15572 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15573 FALSE, FALSE, "background music on/off"
15576 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15577 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15578 FALSE, FALSE, "sound loops on/off"
15581 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15582 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15583 FALSE, FALSE, "normal sounds on/off"
15587 void CreateGameButtons(void)
15591 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15593 int graphic = gamebutton_info[i].graphic;
15594 struct GraphicInfo *gfx = &graphic_info[graphic];
15595 struct XY *pos = gamebutton_info[i].pos;
15596 struct GadgetInfo *gi;
15599 unsigned int event_mask;
15600 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15601 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15602 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15603 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15604 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15605 int gd_x = gfx->src_x;
15606 int gd_y = gfx->src_y;
15607 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15608 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15609 int gd_xa = gfx->src_x + gfx->active_xoffset;
15610 int gd_ya = gfx->src_y + gfx->active_yoffset;
15611 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15612 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15613 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15614 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15617 if (gfx->bitmap == NULL)
15619 game_gadget[id] = NULL;
15624 if (id == GAME_CTRL_ID_STOP ||
15625 id == GAME_CTRL_ID_PANEL_STOP ||
15626 id == GAME_CTRL_ID_TOUCH_STOP ||
15627 id == GAME_CTRL_ID_PLAY ||
15628 id == GAME_CTRL_ID_PANEL_PLAY ||
15629 id == GAME_CTRL_ID_SAVE ||
15630 id == GAME_CTRL_ID_LOAD)
15632 button_type = GD_TYPE_NORMAL_BUTTON;
15634 event_mask = GD_EVENT_RELEASED;
15636 else if (id == GAME_CTRL_ID_UNDO ||
15637 id == GAME_CTRL_ID_REDO)
15639 button_type = GD_TYPE_NORMAL_BUTTON;
15641 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15645 button_type = GD_TYPE_CHECK_BUTTON;
15646 checked = (gamebutton_info[i].setup_value != NULL ?
15647 *gamebutton_info[i].setup_value : FALSE);
15648 event_mask = GD_EVENT_PRESSED;
15651 gi = CreateGadget(GDI_CUSTOM_ID, id,
15652 GDI_IMAGE_ID, graphic,
15653 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15656 GDI_WIDTH, gfx->width,
15657 GDI_HEIGHT, gfx->height,
15658 GDI_TYPE, button_type,
15659 GDI_STATE, GD_BUTTON_UNPRESSED,
15660 GDI_CHECKED, checked,
15661 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15662 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15663 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15664 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15665 GDI_DIRECT_DRAW, FALSE,
15666 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15667 GDI_EVENT_MASK, event_mask,
15668 GDI_CALLBACK_ACTION, HandleGameButtons,
15672 Error(ERR_EXIT, "cannot create gadget");
15674 game_gadget[id] = gi;
15678 void FreeGameButtons(void)
15682 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15683 FreeGadget(game_gadget[i]);
15686 static void UnmapGameButtonsAtSamePosition(int id)
15690 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15692 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15693 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15694 UnmapGadget(game_gadget[i]);
15697 static void UnmapGameButtonsAtSamePosition_All(void)
15699 if (setup.show_snapshot_buttons)
15701 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15702 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15703 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15707 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15708 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15709 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15711 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15712 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15713 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15717 static void MapGameButtonsAtSamePosition(int id)
15721 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15723 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15724 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15725 MapGadget(game_gadget[i]);
15727 UnmapGameButtonsAtSamePosition_All();
15730 void MapUndoRedoButtons(void)
15732 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15733 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15735 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15736 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15739 void UnmapUndoRedoButtons(void)
15741 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15742 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15744 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15745 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15748 void ModifyPauseButtons(void)
15752 GAME_CTRL_ID_PAUSE,
15753 GAME_CTRL_ID_PAUSE2,
15754 GAME_CTRL_ID_PANEL_PAUSE,
15755 GAME_CTRL_ID_TOUCH_PAUSE,
15760 for (i = 0; ids[i] > -1; i++)
15761 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15764 static void MapGameButtonsExt(boolean on_tape)
15768 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15769 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15770 i != GAME_CTRL_ID_UNDO &&
15771 i != GAME_CTRL_ID_REDO)
15772 MapGadget(game_gadget[i]);
15774 UnmapGameButtonsAtSamePosition_All();
15776 RedrawGameButtons();
15779 static void UnmapGameButtonsExt(boolean on_tape)
15783 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15784 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15785 UnmapGadget(game_gadget[i]);
15788 static void RedrawGameButtonsExt(boolean on_tape)
15792 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15793 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15794 RedrawGadget(game_gadget[i]);
15797 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15802 gi->checked = state;
15805 static void RedrawSoundButtonGadget(int id)
15807 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15808 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15809 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15810 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15811 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15812 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15815 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15816 RedrawGadget(game_gadget[id2]);
15819 void MapGameButtons(void)
15821 MapGameButtonsExt(FALSE);
15824 void UnmapGameButtons(void)
15826 UnmapGameButtonsExt(FALSE);
15829 void RedrawGameButtons(void)
15831 RedrawGameButtonsExt(FALSE);
15834 void MapGameButtonsOnTape(void)
15836 MapGameButtonsExt(TRUE);
15839 void UnmapGameButtonsOnTape(void)
15841 UnmapGameButtonsExt(TRUE);
15844 void RedrawGameButtonsOnTape(void)
15846 RedrawGameButtonsExt(TRUE);
15849 static void GameUndoRedoExt(void)
15851 ClearPlayerAction();
15853 tape.pausing = TRUE;
15856 UpdateAndDisplayGameControlValues();
15858 DrawCompleteVideoDisplay();
15859 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
15860 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
15861 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
15866 static void GameUndo(int steps)
15868 if (!CheckEngineSnapshotList())
15871 LoadEngineSnapshot_Undo(steps);
15876 static void GameRedo(int steps)
15878 if (!CheckEngineSnapshotList())
15881 LoadEngineSnapshot_Redo(steps);
15886 static void HandleGameButtonsExt(int id, int button)
15888 static boolean game_undo_executed = FALSE;
15889 int steps = BUTTON_STEPSIZE(button);
15890 boolean handle_game_buttons =
15891 (game_status == GAME_MODE_PLAYING ||
15892 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
15894 if (!handle_game_buttons)
15899 case GAME_CTRL_ID_STOP:
15900 case GAME_CTRL_ID_PANEL_STOP:
15901 case GAME_CTRL_ID_TOUCH_STOP:
15902 if (game_status == GAME_MODE_MAIN)
15908 RequestQuitGame(TRUE);
15912 case GAME_CTRL_ID_PAUSE:
15913 case GAME_CTRL_ID_PAUSE2:
15914 case GAME_CTRL_ID_PANEL_PAUSE:
15915 case GAME_CTRL_ID_TOUCH_PAUSE:
15916 if (network.enabled && game_status == GAME_MODE_PLAYING)
15919 SendToServer_ContinuePlaying();
15921 SendToServer_PausePlaying();
15924 TapeTogglePause(TAPE_TOGGLE_MANUAL);
15926 game_undo_executed = FALSE;
15930 case GAME_CTRL_ID_PLAY:
15931 case GAME_CTRL_ID_PANEL_PLAY:
15932 if (game_status == GAME_MODE_MAIN)
15934 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15936 else if (tape.pausing)
15938 if (network.enabled)
15939 SendToServer_ContinuePlaying();
15941 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
15945 case GAME_CTRL_ID_UNDO:
15946 // Important: When using "save snapshot when collecting an item" mode,
15947 // load last (current) snapshot for first "undo" after pressing "pause"
15948 // (else the last-but-one snapshot would be loaded, because the snapshot
15949 // pointer already points to the last snapshot when pressing "pause",
15950 // which is fine for "every step/move" mode, but not for "every collect")
15951 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15952 !game_undo_executed)
15955 game_undo_executed = TRUE;
15960 case GAME_CTRL_ID_REDO:
15964 case GAME_CTRL_ID_SAVE:
15968 case GAME_CTRL_ID_LOAD:
15972 case SOUND_CTRL_ID_MUSIC:
15973 case SOUND_CTRL_ID_PANEL_MUSIC:
15974 if (setup.sound_music)
15976 setup.sound_music = FALSE;
15980 else if (audio.music_available)
15982 setup.sound = setup.sound_music = TRUE;
15984 SetAudioMode(setup.sound);
15986 if (game_status == GAME_MODE_PLAYING)
15990 RedrawSoundButtonGadget(id);
15994 case SOUND_CTRL_ID_LOOPS:
15995 case SOUND_CTRL_ID_PANEL_LOOPS:
15996 if (setup.sound_loops)
15997 setup.sound_loops = FALSE;
15998 else if (audio.loops_available)
16000 setup.sound = setup.sound_loops = TRUE;
16002 SetAudioMode(setup.sound);
16005 RedrawSoundButtonGadget(id);
16009 case SOUND_CTRL_ID_SIMPLE:
16010 case SOUND_CTRL_ID_PANEL_SIMPLE:
16011 if (setup.sound_simple)
16012 setup.sound_simple = FALSE;
16013 else if (audio.sound_available)
16015 setup.sound = setup.sound_simple = TRUE;
16017 SetAudioMode(setup.sound);
16020 RedrawSoundButtonGadget(id);
16029 static void HandleGameButtons(struct GadgetInfo *gi)
16031 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16034 void HandleSoundButtonKeys(Key key)
16036 if (key == setup.shortcut.sound_simple)
16037 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16038 else if (key == setup.shortcut.sound_loops)
16039 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16040 else if (key == setup.shortcut.sound_music)
16041 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);