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 level.native_em_level->ply[player_nr]->dynamite;
2130 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2131 return level.native_sp_level->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 ?
2215 level.native_em_level->lev->time :
2216 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2217 level.native_sp_level->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 level.native_em_level->lev->score :
2225 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2226 level.native_sp_level->game_sp->score :
2227 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2230 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2231 level.native_em_level->lev->required :
2232 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2233 level.native_sp_level->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 level.native_em_level->lev->required > 0 :
2239 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2240 level.native_sp_level->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 (level.native_em_level->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 (level.native_em_level->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 game_em.use_single_button =
2910 (game.engine_version > VERSION_IDENT(4,0,0,2));
2912 game_em.use_snap_key_bug =
2913 (game.engine_version < VERSION_IDENT(4,0,1,0));
2915 // --------------------------------------------------------------------------
2917 // set maximal allowed number of custom element changes per game frame
2918 game.max_num_changes_per_frame = 1;
2920 // default scan direction: scan playfield from top/left to bottom/right
2921 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
2923 // dynamically adjust element properties according to game engine version
2924 InitElementPropertiesEngine(game.engine_version);
2927 printf("level %d: level version == %06d\n", level_nr, level.game_version);
2928 printf(" tape version == %06d [%s] [file: %06d]\n",
2929 tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
2931 printf(" => game.engine_version == %06d\n", game.engine_version);
2934 // ---------- initialize player's initial move delay ------------------------
2936 // dynamically adjust player properties according to level information
2937 for (i = 0; i < MAX_PLAYERS; i++)
2938 game.initial_move_delay_value[i] =
2939 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
2941 // dynamically adjust player properties according to game engine version
2942 for (i = 0; i < MAX_PLAYERS; i++)
2943 game.initial_move_delay[i] =
2944 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
2945 game.initial_move_delay_value[i] : 0);
2947 // ---------- initialize player's initial push delay ------------------------
2949 // dynamically adjust player properties according to game engine version
2950 game.initial_push_delay_value =
2951 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
2953 // ---------- initialize changing elements ----------------------------------
2955 // initialize changing elements information
2956 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2958 struct ElementInfo *ei = &element_info[i];
2960 // this pointer might have been changed in the level editor
2961 ei->change = &ei->change_page[0];
2963 if (!IS_CUSTOM_ELEMENT(i))
2965 ei->change->target_element = EL_EMPTY_SPACE;
2966 ei->change->delay_fixed = 0;
2967 ei->change->delay_random = 0;
2968 ei->change->delay_frames = 1;
2971 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
2973 ei->has_change_event[j] = FALSE;
2975 ei->event_page_nr[j] = 0;
2976 ei->event_page[j] = &ei->change_page[0];
2980 // add changing elements from pre-defined list
2981 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
2983 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
2984 struct ElementInfo *ei = &element_info[ch_delay->element];
2986 ei->change->target_element = ch_delay->target_element;
2987 ei->change->delay_fixed = ch_delay->change_delay;
2989 ei->change->pre_change_function = ch_delay->pre_change_function;
2990 ei->change->change_function = ch_delay->change_function;
2991 ei->change->post_change_function = ch_delay->post_change_function;
2993 ei->change->can_change = TRUE;
2994 ei->change->can_change_or_has_action = TRUE;
2996 ei->has_change_event[CE_DELAY] = TRUE;
2998 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
2999 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3002 // ---------- initialize internal run-time variables ------------------------
3004 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3006 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3008 for (j = 0; j < ei->num_change_pages; j++)
3010 ei->change_page[j].can_change_or_has_action =
3011 (ei->change_page[j].can_change |
3012 ei->change_page[j].has_action);
3016 // add change events from custom element configuration
3017 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3019 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3021 for (j = 0; j < ei->num_change_pages; j++)
3023 if (!ei->change_page[j].can_change_or_has_action)
3026 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3028 // only add event page for the first page found with this event
3029 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3031 ei->has_change_event[k] = TRUE;
3033 ei->event_page_nr[k] = j;
3034 ei->event_page[k] = &ei->change_page[j];
3040 // ---------- initialize reference elements in change conditions ------------
3042 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3044 int element = EL_CUSTOM_START + i;
3045 struct ElementInfo *ei = &element_info[element];
3047 for (j = 0; j < ei->num_change_pages; j++)
3049 int trigger_element = ei->change_page[j].initial_trigger_element;
3051 if (trigger_element >= EL_PREV_CE_8 &&
3052 trigger_element <= EL_NEXT_CE_8)
3053 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3055 ei->change_page[j].trigger_element = trigger_element;
3059 // ---------- initialize run-time trigger player and element ----------------
3061 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3063 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3065 for (j = 0; j < ei->num_change_pages; j++)
3067 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3068 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3069 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3070 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3071 ei->change_page[j].actual_trigger_ce_value = 0;
3072 ei->change_page[j].actual_trigger_ce_score = 0;
3076 // ---------- initialize trigger events -------------------------------------
3078 // initialize trigger events information
3079 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3080 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3081 trigger_events[i][j] = FALSE;
3083 // add trigger events from element change event properties
3084 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3086 struct ElementInfo *ei = &element_info[i];
3088 for (j = 0; j < ei->num_change_pages; j++)
3090 if (!ei->change_page[j].can_change_or_has_action)
3093 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3095 int trigger_element = ei->change_page[j].trigger_element;
3097 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3099 if (ei->change_page[j].has_event[k])
3101 if (IS_GROUP_ELEMENT(trigger_element))
3103 struct ElementGroupInfo *group =
3104 element_info[trigger_element].group;
3106 for (l = 0; l < group->num_elements_resolved; l++)
3107 trigger_events[group->element_resolved[l]][k] = TRUE;
3109 else if (trigger_element == EL_ANY_ELEMENT)
3110 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3111 trigger_events[l][k] = TRUE;
3113 trigger_events[trigger_element][k] = TRUE;
3120 // ---------- initialize push delay -----------------------------------------
3122 // initialize push delay values to default
3123 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3125 if (!IS_CUSTOM_ELEMENT(i))
3127 // set default push delay values (corrected since version 3.0.7-1)
3128 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3130 element_info[i].push_delay_fixed = 2;
3131 element_info[i].push_delay_random = 8;
3135 element_info[i].push_delay_fixed = 8;
3136 element_info[i].push_delay_random = 8;
3141 // set push delay value for certain elements from pre-defined list
3142 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3144 int e = push_delay_list[i].element;
3146 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3147 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3150 // set push delay value for Supaplex elements for newer engine versions
3151 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3153 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3155 if (IS_SP_ELEMENT(i))
3157 // set SP push delay to just enough to push under a falling zonk
3158 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3160 element_info[i].push_delay_fixed = delay;
3161 element_info[i].push_delay_random = 0;
3166 // ---------- initialize move stepsize --------------------------------------
3168 // initialize move stepsize values to default
3169 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3170 if (!IS_CUSTOM_ELEMENT(i))
3171 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3173 // set move stepsize value for certain elements from pre-defined list
3174 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3176 int e = move_stepsize_list[i].element;
3178 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3181 // ---------- initialize collect score --------------------------------------
3183 // initialize collect score values for custom elements from initial value
3184 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3185 if (IS_CUSTOM_ELEMENT(i))
3186 element_info[i].collect_score = element_info[i].collect_score_initial;
3188 // ---------- initialize collect count --------------------------------------
3190 // initialize collect count values for non-custom elements
3191 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3192 if (!IS_CUSTOM_ELEMENT(i))
3193 element_info[i].collect_count_initial = 0;
3195 // add collect count values for all elements from pre-defined list
3196 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3197 element_info[collect_count_list[i].element].collect_count_initial =
3198 collect_count_list[i].count;
3200 // ---------- initialize access direction -----------------------------------
3202 // initialize access direction values to default (access from every side)
3203 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3204 if (!IS_CUSTOM_ELEMENT(i))
3205 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3207 // set access direction value for certain elements from pre-defined list
3208 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3209 element_info[access_direction_list[i].element].access_direction =
3210 access_direction_list[i].direction;
3212 // ---------- initialize explosion content ----------------------------------
3213 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3215 if (IS_CUSTOM_ELEMENT(i))
3218 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3220 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3222 element_info[i].content.e[x][y] =
3223 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3224 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3225 i == EL_PLAYER_3 ? EL_EMERALD :
3226 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3227 i == EL_MOLE ? EL_EMERALD_RED :
3228 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3229 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3230 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3231 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3232 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3233 i == EL_WALL_EMERALD ? EL_EMERALD :
3234 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3235 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3236 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3237 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3238 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3239 i == EL_WALL_PEARL ? EL_PEARL :
3240 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3245 // ---------- initialize recursion detection --------------------------------
3246 recursion_loop_depth = 0;
3247 recursion_loop_detected = FALSE;
3248 recursion_loop_element = EL_UNDEFINED;
3250 // ---------- initialize graphics engine ------------------------------------
3251 game.scroll_delay_value =
3252 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3253 setup.scroll_delay ? setup.scroll_delay_value : 0);
3254 game.scroll_delay_value =
3255 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3257 // ---------- initialize game engine snapshots ------------------------------
3258 for (i = 0; i < MAX_PLAYERS; i++)
3259 game.snapshot.last_action[i] = 0;
3260 game.snapshot.changed_action = FALSE;
3261 game.snapshot.collected_item = FALSE;
3262 game.snapshot.mode =
3263 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3264 SNAPSHOT_MODE_EVERY_STEP :
3265 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3266 SNAPSHOT_MODE_EVERY_MOVE :
3267 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3268 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3269 game.snapshot.save_snapshot = FALSE;
3271 // ---------- initialize level time for Supaplex engine ---------------------
3272 // Supaplex levels with time limit currently unsupported -- should be added
3273 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3277 static int get_num_special_action(int element, int action_first,
3280 int num_special_action = 0;
3283 for (i = action_first; i <= action_last; i++)
3285 boolean found = FALSE;
3287 for (j = 0; j < NUM_DIRECTIONS; j++)
3288 if (el_act_dir2img(element, i, j) !=
3289 el_act_dir2img(element, ACTION_DEFAULT, j))
3293 num_special_action++;
3298 return num_special_action;
3302 // ============================================================================
3304 // ----------------------------------------------------------------------------
3305 // initialize and start new game
3306 // ============================================================================
3308 #if DEBUG_INIT_PLAYER
3309 static void DebugPrintPlayerStatus(char *message)
3316 printf("%s:\n", message);
3318 for (i = 0; i < MAX_PLAYERS; i++)
3320 struct PlayerInfo *player = &stored_player[i];
3322 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3326 player->connected_locally,
3327 player->connected_network,
3330 if (local_player == player)
3331 printf(" (local player)");
3340 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3341 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3342 int fade_mask = REDRAW_FIELD;
3344 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3345 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3346 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3347 int initial_move_dir = MV_DOWN;
3350 // required here to update video display before fading (FIX THIS)
3351 DrawMaskedBorder(REDRAW_DOOR_2);
3353 if (!game.restart_level)
3354 CloseDoor(DOOR_CLOSE_1);
3356 SetGameStatus(GAME_MODE_PLAYING);
3358 if (level_editor_test_game)
3359 FadeSkipNextFadeOut();
3361 FadeSetEnterScreen();
3364 fade_mask = REDRAW_ALL;
3366 FadeLevelSoundsAndMusic();
3368 ExpireSoundLoops(TRUE);
3372 if (level_editor_test_game)
3373 FadeSkipNextFadeIn();
3375 // needed if different viewport properties defined for playing
3376 ChangeViewportPropertiesIfNeeded();
3380 DrawCompleteVideoDisplay();
3382 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3385 InitGameControlValues();
3387 // don't play tapes over network
3388 network_playing = (network.enabled && !tape.playing);
3390 for (i = 0; i < MAX_PLAYERS; i++)
3392 struct PlayerInfo *player = &stored_player[i];
3394 player->index_nr = i;
3395 player->index_bit = (1 << i);
3396 player->element_nr = EL_PLAYER_1 + i;
3398 player->present = FALSE;
3399 player->active = FALSE;
3400 player->mapped = FALSE;
3402 player->killed = FALSE;
3403 player->reanimated = FALSE;
3404 player->buried = FALSE;
3407 player->effective_action = 0;
3408 player->programmed_action = 0;
3409 player->snap_action = 0;
3411 player->mouse_action.lx = 0;
3412 player->mouse_action.ly = 0;
3413 player->mouse_action.button = 0;
3414 player->mouse_action.button_hint = 0;
3416 player->effective_mouse_action.lx = 0;
3417 player->effective_mouse_action.ly = 0;
3418 player->effective_mouse_action.button = 0;
3419 player->effective_mouse_action.button_hint = 0;
3421 for (j = 0; j < MAX_NUM_KEYS; j++)
3422 player->key[j] = FALSE;
3424 player->num_white_keys = 0;
3426 player->dynabomb_count = 0;
3427 player->dynabomb_size = 1;
3428 player->dynabombs_left = 0;
3429 player->dynabomb_xl = FALSE;
3431 player->MovDir = initial_move_dir;
3434 player->GfxDir = initial_move_dir;
3435 player->GfxAction = ACTION_DEFAULT;
3437 player->StepFrame = 0;
3439 player->initial_element = player->element_nr;
3440 player->artwork_element =
3441 (level.use_artwork_element[i] ? level.artwork_element[i] :
3442 player->element_nr);
3443 player->use_murphy = FALSE;
3445 player->block_last_field = FALSE; // initialized in InitPlayerField()
3446 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3448 player->gravity = level.initial_player_gravity[i];
3450 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3452 player->actual_frame_counter = 0;
3454 player->step_counter = 0;
3456 player->last_move_dir = initial_move_dir;
3458 player->is_active = FALSE;
3460 player->is_waiting = FALSE;
3461 player->is_moving = FALSE;
3462 player->is_auto_moving = FALSE;
3463 player->is_digging = FALSE;
3464 player->is_snapping = FALSE;
3465 player->is_collecting = FALSE;
3466 player->is_pushing = FALSE;
3467 player->is_switching = FALSE;
3468 player->is_dropping = FALSE;
3469 player->is_dropping_pressed = FALSE;
3471 player->is_bored = FALSE;
3472 player->is_sleeping = FALSE;
3474 player->was_waiting = TRUE;
3475 player->was_moving = FALSE;
3476 player->was_snapping = FALSE;
3477 player->was_dropping = FALSE;
3479 player->force_dropping = FALSE;
3481 player->frame_counter_bored = -1;
3482 player->frame_counter_sleeping = -1;
3484 player->anim_delay_counter = 0;
3485 player->post_delay_counter = 0;
3487 player->dir_waiting = initial_move_dir;
3488 player->action_waiting = ACTION_DEFAULT;
3489 player->last_action_waiting = ACTION_DEFAULT;
3490 player->special_action_bored = ACTION_DEFAULT;
3491 player->special_action_sleeping = ACTION_DEFAULT;
3493 player->switch_x = -1;
3494 player->switch_y = -1;
3496 player->drop_x = -1;
3497 player->drop_y = -1;
3499 player->show_envelope = 0;
3501 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3503 player->push_delay = -1; // initialized when pushing starts
3504 player->push_delay_value = game.initial_push_delay_value;
3506 player->drop_delay = 0;
3507 player->drop_pressed_delay = 0;
3509 player->last_jx = -1;
3510 player->last_jy = -1;
3514 player->shield_normal_time_left = 0;
3515 player->shield_deadly_time_left = 0;
3517 player->inventory_infinite_element = EL_UNDEFINED;
3518 player->inventory_size = 0;
3520 if (level.use_initial_inventory[i])
3522 for (j = 0; j < level.initial_inventory_size[i]; j++)
3524 int element = level.initial_inventory_content[i][j];
3525 int collect_count = element_info[element].collect_count_initial;
3528 if (!IS_CUSTOM_ELEMENT(element))
3531 if (collect_count == 0)
3532 player->inventory_infinite_element = element;
3534 for (k = 0; k < collect_count; k++)
3535 if (player->inventory_size < MAX_INVENTORY_SIZE)
3536 player->inventory_element[player->inventory_size++] = element;
3540 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3541 SnapField(player, 0, 0);
3543 map_player_action[i] = i;
3546 network_player_action_received = FALSE;
3548 // initial null action
3549 if (network_playing)
3550 SendToServer_MovePlayer(MV_NONE);
3555 TimeLeft = level.time;
3558 ScreenMovDir = MV_NONE;
3562 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3564 game.robot_wheel_x = -1;
3565 game.robot_wheel_y = -1;
3570 game.all_players_gone = FALSE;
3572 game.LevelSolved = FALSE;
3573 game.GameOver = FALSE;
3575 game.GamePlayed = !tape.playing;
3577 game.LevelSolved_GameWon = FALSE;
3578 game.LevelSolved_GameEnd = FALSE;
3579 game.LevelSolved_SaveTape = FALSE;
3580 game.LevelSolved_SaveScore = FALSE;
3582 game.LevelSolved_CountingTime = 0;
3583 game.LevelSolved_CountingScore = 0;
3584 game.LevelSolved_CountingHealth = 0;
3586 game.panel.active = TRUE;
3588 game.no_time_limit = (level.time == 0);
3590 game.yamyam_content_nr = 0;
3591 game.robot_wheel_active = FALSE;
3592 game.magic_wall_active = FALSE;
3593 game.magic_wall_time_left = 0;
3594 game.light_time_left = 0;
3595 game.timegate_time_left = 0;
3596 game.switchgate_pos = 0;
3597 game.wind_direction = level.wind_direction_initial;
3600 game.score_final = 0;
3602 game.health = MAX_HEALTH;
3603 game.health_final = MAX_HEALTH;
3605 game.gems_still_needed = level.gems_needed;
3606 game.sokoban_fields_still_needed = 0;
3607 game.sokoban_objects_still_needed = 0;
3608 game.lights_still_needed = 0;
3609 game.players_still_needed = 0;
3610 game.friends_still_needed = 0;
3612 game.lenses_time_left = 0;
3613 game.magnify_time_left = 0;
3615 game.ball_state = level.ball_state_initial;
3616 game.ball_content_nr = 0;
3618 game.explosions_delayed = TRUE;
3620 game.envelope_active = FALSE;
3622 for (i = 0; i < NUM_BELTS; i++)
3624 game.belt_dir[i] = MV_NONE;
3625 game.belt_dir_nr[i] = 3; // not moving, next moving left
3628 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3629 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3631 #if DEBUG_INIT_PLAYER
3632 DebugPrintPlayerStatus("Player status at level initialization");
3635 SCAN_PLAYFIELD(x, y)
3637 Feld[x][y] = Last[x][y] = level.field[x][y];
3638 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3639 ChangeDelay[x][y] = 0;
3640 ChangePage[x][y] = -1;
3641 CustomValue[x][y] = 0; // initialized in InitField()
3642 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3644 WasJustMoving[x][y] = 0;
3645 WasJustFalling[x][y] = 0;
3646 CheckCollision[x][y] = 0;
3647 CheckImpact[x][y] = 0;
3649 Pushed[x][y] = FALSE;
3651 ChangeCount[x][y] = 0;
3652 ChangeEvent[x][y] = -1;
3654 ExplodePhase[x][y] = 0;
3655 ExplodeDelay[x][y] = 0;
3656 ExplodeField[x][y] = EX_TYPE_NONE;
3658 RunnerVisit[x][y] = 0;
3659 PlayerVisit[x][y] = 0;
3662 GfxRandom[x][y] = INIT_GFX_RANDOM();
3663 GfxElement[x][y] = EL_UNDEFINED;
3664 GfxAction[x][y] = ACTION_DEFAULT;
3665 GfxDir[x][y] = MV_NONE;
3666 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3669 SCAN_PLAYFIELD(x, y)
3671 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3673 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3675 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3678 InitField(x, y, TRUE);
3680 ResetGfxAnimation(x, y);
3685 for (i = 0; i < MAX_PLAYERS; i++)
3687 struct PlayerInfo *player = &stored_player[i];
3689 // set number of special actions for bored and sleeping animation
3690 player->num_special_action_bored =
3691 get_num_special_action(player->artwork_element,
3692 ACTION_BORING_1, ACTION_BORING_LAST);
3693 player->num_special_action_sleeping =
3694 get_num_special_action(player->artwork_element,
3695 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3698 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3699 emulate_sb ? EMU_SOKOBAN :
3700 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3702 // initialize type of slippery elements
3703 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3705 if (!IS_CUSTOM_ELEMENT(i))
3707 // default: elements slip down either to the left or right randomly
3708 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3710 // SP style elements prefer to slip down on the left side
3711 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3712 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3714 // BD style elements prefer to slip down on the left side
3715 if (game.emulation == EMU_BOULDERDASH)
3716 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3720 // initialize explosion and ignition delay
3721 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3723 if (!IS_CUSTOM_ELEMENT(i))
3726 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3727 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3728 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3729 int last_phase = (num_phase + 1) * delay;
3730 int half_phase = (num_phase / 2) * delay;
3732 element_info[i].explosion_delay = last_phase - 1;
3733 element_info[i].ignition_delay = half_phase;
3735 if (i == EL_BLACK_ORB)
3736 element_info[i].ignition_delay = 1;
3740 // correct non-moving belts to start moving left
3741 for (i = 0; i < NUM_BELTS; i++)
3742 if (game.belt_dir[i] == MV_NONE)
3743 game.belt_dir_nr[i] = 3; // not moving, next moving left
3745 #if USE_NEW_PLAYER_ASSIGNMENTS
3746 // use preferred player also in local single-player mode
3747 if (!network.enabled && !game.team_mode)
3749 int old_index_nr = local_player->index_nr;
3750 int new_index_nr = setup.network_player_nr;
3752 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3754 stored_player[old_index_nr].connected_locally = FALSE;
3755 stored_player[new_index_nr].connected_locally = TRUE;
3759 for (i = 0; i < MAX_PLAYERS; i++)
3761 stored_player[i].connected = FALSE;
3763 // in network game mode, the local player might not be the first player
3764 if (stored_player[i].connected_locally)
3765 local_player = &stored_player[i];
3768 if (!network.enabled)
3769 local_player->connected = TRUE;
3773 for (i = 0; i < MAX_PLAYERS; i++)
3774 stored_player[i].connected = tape.player_participates[i];
3776 else if (network.enabled)
3778 // add team mode players connected over the network (needed for correct
3779 // assignment of player figures from level to locally playing players)
3781 for (i = 0; i < MAX_PLAYERS; i++)
3782 if (stored_player[i].connected_network)
3783 stored_player[i].connected = TRUE;
3785 else if (game.team_mode)
3787 // try to guess locally connected team mode players (needed for correct
3788 // assignment of player figures from level to locally playing players)
3790 for (i = 0; i < MAX_PLAYERS; i++)
3791 if (setup.input[i].use_joystick ||
3792 setup.input[i].key.left != KSYM_UNDEFINED)
3793 stored_player[i].connected = TRUE;
3796 #if DEBUG_INIT_PLAYER
3797 DebugPrintPlayerStatus("Player status after level initialization");
3800 #if DEBUG_INIT_PLAYER
3802 printf("Reassigning players ...\n");
3805 // check if any connected player was not found in playfield
3806 for (i = 0; i < MAX_PLAYERS; i++)
3808 struct PlayerInfo *player = &stored_player[i];
3810 if (player->connected && !player->present)
3812 struct PlayerInfo *field_player = NULL;
3814 #if DEBUG_INIT_PLAYER
3816 printf("- looking for field player for player %d ...\n", i + 1);
3819 // assign first free player found that is present in the playfield
3821 // first try: look for unmapped playfield player that is not connected
3822 for (j = 0; j < MAX_PLAYERS; j++)
3823 if (field_player == NULL &&
3824 stored_player[j].present &&
3825 !stored_player[j].mapped &&
3826 !stored_player[j].connected)
3827 field_player = &stored_player[j];
3829 // second try: look for *any* unmapped playfield player
3830 for (j = 0; j < MAX_PLAYERS; j++)
3831 if (field_player == NULL &&
3832 stored_player[j].present &&
3833 !stored_player[j].mapped)
3834 field_player = &stored_player[j];
3836 if (field_player != NULL)
3838 int jx = field_player->jx, jy = field_player->jy;
3840 #if DEBUG_INIT_PLAYER
3842 printf("- found player %d\n", field_player->index_nr + 1);
3845 player->present = FALSE;
3846 player->active = FALSE;
3848 field_player->present = TRUE;
3849 field_player->active = TRUE;
3852 player->initial_element = field_player->initial_element;
3853 player->artwork_element = field_player->artwork_element;
3855 player->block_last_field = field_player->block_last_field;
3856 player->block_delay_adjustment = field_player->block_delay_adjustment;
3859 StorePlayer[jx][jy] = field_player->element_nr;
3861 field_player->jx = field_player->last_jx = jx;
3862 field_player->jy = field_player->last_jy = jy;
3864 if (local_player == player)
3865 local_player = field_player;
3867 map_player_action[field_player->index_nr] = i;
3869 field_player->mapped = TRUE;
3871 #if DEBUG_INIT_PLAYER
3873 printf("- map_player_action[%d] == %d\n",
3874 field_player->index_nr + 1, i + 1);
3879 if (player->connected && player->present)
3880 player->mapped = TRUE;
3883 #if DEBUG_INIT_PLAYER
3884 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
3889 // check if any connected player was not found in playfield
3890 for (i = 0; i < MAX_PLAYERS; i++)
3892 struct PlayerInfo *player = &stored_player[i];
3894 if (player->connected && !player->present)
3896 for (j = 0; j < MAX_PLAYERS; j++)
3898 struct PlayerInfo *field_player = &stored_player[j];
3899 int jx = field_player->jx, jy = field_player->jy;
3901 // assign first free player found that is present in the playfield
3902 if (field_player->present && !field_player->connected)
3904 player->present = TRUE;
3905 player->active = TRUE;
3907 field_player->present = FALSE;
3908 field_player->active = FALSE;
3910 player->initial_element = field_player->initial_element;
3911 player->artwork_element = field_player->artwork_element;
3913 player->block_last_field = field_player->block_last_field;
3914 player->block_delay_adjustment = field_player->block_delay_adjustment;
3916 StorePlayer[jx][jy] = player->element_nr;
3918 player->jx = player->last_jx = jx;
3919 player->jy = player->last_jy = jy;
3929 printf("::: local_player->present == %d\n", local_player->present);
3932 // set focus to local player for network games, else to all players
3933 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
3934 game.centered_player_nr_next = game.centered_player_nr;
3935 game.set_centered_player = FALSE;
3937 if (network_playing && tape.recording)
3939 // store client dependent player focus when recording network games
3940 tape.centered_player_nr_next = game.centered_player_nr_next;
3941 tape.set_centered_player = TRUE;
3946 // when playing a tape, eliminate all players who do not participate
3948 #if USE_NEW_PLAYER_ASSIGNMENTS
3950 if (!game.team_mode)
3952 for (i = 0; i < MAX_PLAYERS; i++)
3954 if (stored_player[i].active &&
3955 !tape.player_participates[map_player_action[i]])
3957 struct PlayerInfo *player = &stored_player[i];
3958 int jx = player->jx, jy = player->jy;
3960 #if DEBUG_INIT_PLAYER
3962 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
3965 player->active = FALSE;
3966 StorePlayer[jx][jy] = 0;
3967 Feld[jx][jy] = EL_EMPTY;
3974 for (i = 0; i < MAX_PLAYERS; i++)
3976 if (stored_player[i].active &&
3977 !tape.player_participates[i])
3979 struct PlayerInfo *player = &stored_player[i];
3980 int jx = player->jx, jy = player->jy;
3982 player->active = FALSE;
3983 StorePlayer[jx][jy] = 0;
3984 Feld[jx][jy] = EL_EMPTY;
3989 else if (!network.enabled && !game.team_mode) // && !tape.playing
3991 // when in single player mode, eliminate all but the local player
3993 for (i = 0; i < MAX_PLAYERS; i++)
3995 struct PlayerInfo *player = &stored_player[i];
3997 if (player->active && player != local_player)
3999 int jx = player->jx, jy = player->jy;
4001 player->active = FALSE;
4002 player->present = FALSE;
4004 StorePlayer[jx][jy] = 0;
4005 Feld[jx][jy] = EL_EMPTY;
4010 for (i = 0; i < MAX_PLAYERS; i++)
4011 if (stored_player[i].active)
4012 game.players_still_needed++;
4014 if (level.solved_by_one_player)
4015 game.players_still_needed = 1;
4017 // when recording the game, store which players take part in the game
4020 #if USE_NEW_PLAYER_ASSIGNMENTS
4021 for (i = 0; i < MAX_PLAYERS; i++)
4022 if (stored_player[i].connected)
4023 tape.player_participates[i] = TRUE;
4025 for (i = 0; i < MAX_PLAYERS; i++)
4026 if (stored_player[i].active)
4027 tape.player_participates[i] = TRUE;
4031 #if DEBUG_INIT_PLAYER
4032 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4035 if (BorderElement == EL_EMPTY)
4038 SBX_Right = lev_fieldx - SCR_FIELDX;
4040 SBY_Lower = lev_fieldy - SCR_FIELDY;
4045 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4047 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4050 if (full_lev_fieldx <= SCR_FIELDX)
4051 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4052 if (full_lev_fieldy <= SCR_FIELDY)
4053 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4055 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4057 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4060 // if local player not found, look for custom element that might create
4061 // the player (make some assumptions about the right custom element)
4062 if (!local_player->present)
4064 int start_x = 0, start_y = 0;
4065 int found_rating = 0;
4066 int found_element = EL_UNDEFINED;
4067 int player_nr = local_player->index_nr;
4069 SCAN_PLAYFIELD(x, y)
4071 int element = Feld[x][y];
4076 if (level.use_start_element[player_nr] &&
4077 level.start_element[player_nr] == element &&
4084 found_element = element;
4087 if (!IS_CUSTOM_ELEMENT(element))
4090 if (CAN_CHANGE(element))
4092 for (i = 0; i < element_info[element].num_change_pages; i++)
4094 // check for player created from custom element as single target
4095 content = element_info[element].change_page[i].target_element;
4096 is_player = ELEM_IS_PLAYER(content);
4098 if (is_player && (found_rating < 3 ||
4099 (found_rating == 3 && element < found_element)))
4105 found_element = element;
4110 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4112 // check for player created from custom element as explosion content
4113 content = element_info[element].content.e[xx][yy];
4114 is_player = ELEM_IS_PLAYER(content);
4116 if (is_player && (found_rating < 2 ||
4117 (found_rating == 2 && element < found_element)))
4119 start_x = x + xx - 1;
4120 start_y = y + yy - 1;
4123 found_element = element;
4126 if (!CAN_CHANGE(element))
4129 for (i = 0; i < element_info[element].num_change_pages; i++)
4131 // check for player created from custom element as extended target
4133 element_info[element].change_page[i].target_content.e[xx][yy];
4135 is_player = ELEM_IS_PLAYER(content);
4137 if (is_player && (found_rating < 1 ||
4138 (found_rating == 1 && element < found_element)))
4140 start_x = x + xx - 1;
4141 start_y = y + yy - 1;
4144 found_element = element;
4150 scroll_x = SCROLL_POSITION_X(start_x);
4151 scroll_y = SCROLL_POSITION_Y(start_y);
4155 scroll_x = SCROLL_POSITION_X(local_player->jx);
4156 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4159 // !!! FIX THIS (START) !!!
4160 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4162 InitGameEngine_EM();
4164 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4166 InitGameEngine_SP();
4168 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4170 InitGameEngine_MM();
4174 DrawLevel(REDRAW_FIELD);
4177 // after drawing the level, correct some elements
4178 if (game.timegate_time_left == 0)
4179 CloseAllOpenTimegates();
4182 // blit playfield from scroll buffer to normal back buffer for fading in
4183 BlitScreenToBitmap(backbuffer);
4184 // !!! FIX THIS (END) !!!
4186 DrawMaskedBorder(fade_mask);
4191 // full screen redraw is required at this point in the following cases:
4192 // - special editor door undrawn when game was started from level editor
4193 // - drawing area (playfield) was changed and has to be removed completely
4194 redraw_mask = REDRAW_ALL;
4198 if (!game.restart_level)
4200 // copy default game door content to main double buffer
4202 // !!! CHECK AGAIN !!!
4203 SetPanelBackground();
4204 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4205 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4208 SetPanelBackground();
4209 SetDrawBackgroundMask(REDRAW_DOOR_1);
4211 UpdateAndDisplayGameControlValues();
4213 if (!game.restart_level)
4219 CreateGameButtons();
4224 // copy actual game door content to door double buffer for OpenDoor()
4225 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4227 OpenDoor(DOOR_OPEN_ALL);
4229 KeyboardAutoRepeatOffUnlessAutoplay();
4231 #if DEBUG_INIT_PLAYER
4232 DebugPrintPlayerStatus("Player status (final)");
4241 if (!game.restart_level && !tape.playing)
4243 LevelStats_incPlayed(level_nr);
4245 SaveLevelSetup_SeriesInfo();
4248 game.restart_level = FALSE;
4249 game.restart_game_message = NULL;
4250 game.request_active = FALSE;
4252 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4253 InitGameActions_MM();
4255 SaveEngineSnapshotToListInitial();
4257 if (!game.restart_level)
4259 PlaySound(SND_GAME_STARTING);
4261 if (setup.sound_music)
4266 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4267 int actual_player_x, int actual_player_y)
4269 // this is used for non-R'n'D game engines to update certain engine values
4271 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4273 actual_player_x = correctLevelPosX_EM(actual_player_x);
4274 actual_player_y = correctLevelPosY_EM(actual_player_y);
4277 // needed to determine if sounds are played within the visible screen area
4278 scroll_x = actual_scroll_x;
4279 scroll_y = actual_scroll_y;
4281 // needed to get player position for "follow finger" playing input method
4282 local_player->jx = actual_player_x;
4283 local_player->jy = actual_player_y;
4286 void InitMovDir(int x, int y)
4288 int i, element = Feld[x][y];
4289 static int xy[4][2] =
4296 static int direction[3][4] =
4298 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4299 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4300 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4309 Feld[x][y] = EL_BUG;
4310 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4313 case EL_SPACESHIP_RIGHT:
4314 case EL_SPACESHIP_UP:
4315 case EL_SPACESHIP_LEFT:
4316 case EL_SPACESHIP_DOWN:
4317 Feld[x][y] = EL_SPACESHIP;
4318 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4321 case EL_BD_BUTTERFLY_RIGHT:
4322 case EL_BD_BUTTERFLY_UP:
4323 case EL_BD_BUTTERFLY_LEFT:
4324 case EL_BD_BUTTERFLY_DOWN:
4325 Feld[x][y] = EL_BD_BUTTERFLY;
4326 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4329 case EL_BD_FIREFLY_RIGHT:
4330 case EL_BD_FIREFLY_UP:
4331 case EL_BD_FIREFLY_LEFT:
4332 case EL_BD_FIREFLY_DOWN:
4333 Feld[x][y] = EL_BD_FIREFLY;
4334 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4337 case EL_PACMAN_RIGHT:
4339 case EL_PACMAN_LEFT:
4340 case EL_PACMAN_DOWN:
4341 Feld[x][y] = EL_PACMAN;
4342 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4345 case EL_YAMYAM_LEFT:
4346 case EL_YAMYAM_RIGHT:
4348 case EL_YAMYAM_DOWN:
4349 Feld[x][y] = EL_YAMYAM;
4350 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4353 case EL_SP_SNIKSNAK:
4354 MovDir[x][y] = MV_UP;
4357 case EL_SP_ELECTRON:
4358 MovDir[x][y] = MV_LEFT;
4365 Feld[x][y] = EL_MOLE;
4366 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4370 if (IS_CUSTOM_ELEMENT(element))
4372 struct ElementInfo *ei = &element_info[element];
4373 int move_direction_initial = ei->move_direction_initial;
4374 int move_pattern = ei->move_pattern;
4376 if (move_direction_initial == MV_START_PREVIOUS)
4378 if (MovDir[x][y] != MV_NONE)
4381 move_direction_initial = MV_START_AUTOMATIC;
4384 if (move_direction_initial == MV_START_RANDOM)
4385 MovDir[x][y] = 1 << RND(4);
4386 else if (move_direction_initial & MV_ANY_DIRECTION)
4387 MovDir[x][y] = move_direction_initial;
4388 else if (move_pattern == MV_ALL_DIRECTIONS ||
4389 move_pattern == MV_TURNING_LEFT ||
4390 move_pattern == MV_TURNING_RIGHT ||
4391 move_pattern == MV_TURNING_LEFT_RIGHT ||
4392 move_pattern == MV_TURNING_RIGHT_LEFT ||
4393 move_pattern == MV_TURNING_RANDOM)
4394 MovDir[x][y] = 1 << RND(4);
4395 else if (move_pattern == MV_HORIZONTAL)
4396 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4397 else if (move_pattern == MV_VERTICAL)
4398 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4399 else if (move_pattern & MV_ANY_DIRECTION)
4400 MovDir[x][y] = element_info[element].move_pattern;
4401 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4402 move_pattern == MV_ALONG_RIGHT_SIDE)
4404 // use random direction as default start direction
4405 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4406 MovDir[x][y] = 1 << RND(4);
4408 for (i = 0; i < NUM_DIRECTIONS; i++)
4410 int x1 = x + xy[i][0];
4411 int y1 = y + xy[i][1];
4413 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4415 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4416 MovDir[x][y] = direction[0][i];
4418 MovDir[x][y] = direction[1][i];
4427 MovDir[x][y] = 1 << RND(4);
4429 if (element != EL_BUG &&
4430 element != EL_SPACESHIP &&
4431 element != EL_BD_BUTTERFLY &&
4432 element != EL_BD_FIREFLY)
4435 for (i = 0; i < NUM_DIRECTIONS; i++)
4437 int x1 = x + xy[i][0];
4438 int y1 = y + xy[i][1];
4440 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4442 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4444 MovDir[x][y] = direction[0][i];
4447 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4448 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4450 MovDir[x][y] = direction[1][i];
4459 GfxDir[x][y] = MovDir[x][y];
4462 void InitAmoebaNr(int x, int y)
4465 int group_nr = AmoebeNachbarNr(x, y);
4469 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4471 if (AmoebaCnt[i] == 0)
4479 AmoebaNr[x][y] = group_nr;
4480 AmoebaCnt[group_nr]++;
4481 AmoebaCnt2[group_nr]++;
4484 static void LevelSolved(void)
4486 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4487 game.players_still_needed > 0)
4490 game.LevelSolved = TRUE;
4491 game.GameOver = TRUE;
4493 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4494 level.native_em_level->lev->score :
4495 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4498 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4499 MM_HEALTH(game_mm.laser_overload_value) :
4502 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4503 game.LevelSolved_CountingScore = game.score_final;
4504 game.LevelSolved_CountingHealth = game.health_final;
4509 static int time_count_steps;
4510 static int time, time_final;
4511 static int score, score_final;
4512 static int health, health_final;
4513 static int game_over_delay_1 = 0;
4514 static int game_over_delay_2 = 0;
4515 static int game_over_delay_3 = 0;
4516 int game_over_delay_value_1 = 50;
4517 int game_over_delay_value_2 = 25;
4518 int game_over_delay_value_3 = 50;
4520 if (!game.LevelSolved_GameWon)
4524 // do not start end game actions before the player stops moving (to exit)
4525 if (local_player->active && local_player->MovPos)
4528 game.LevelSolved_GameWon = TRUE;
4529 game.LevelSolved_SaveTape = tape.recording;
4530 game.LevelSolved_SaveScore = !tape.playing;
4534 LevelStats_incSolved(level_nr);
4536 SaveLevelSetup_SeriesInfo();
4539 if (tape.auto_play) // tape might already be stopped here
4540 tape.auto_play_level_solved = TRUE;
4544 game_over_delay_1 = 0;
4545 game_over_delay_2 = 0;
4546 game_over_delay_3 = game_over_delay_value_3;
4548 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4549 score = score_final = game.score_final;
4550 health = health_final = game.health_final;
4552 if (level.score[SC_TIME_BONUS] > 0)
4557 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4559 else if (game.no_time_limit && TimePlayed < 999)
4562 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4565 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4567 game_over_delay_1 = game_over_delay_value_1;
4569 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4572 score_final += health * level.score[SC_TIME_BONUS];
4574 game_over_delay_2 = game_over_delay_value_2;
4577 game.score_final = score_final;
4578 game.health_final = health_final;
4581 if (level_editor_test_game)
4584 score = score_final;
4586 game.LevelSolved_CountingTime = time;
4587 game.LevelSolved_CountingScore = score;
4589 game_panel_controls[GAME_PANEL_TIME].value = time;
4590 game_panel_controls[GAME_PANEL_SCORE].value = score;
4592 DisplayGameControlValues();
4595 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4597 // check if last player has left the level
4598 if (game.exit_x >= 0 &&
4601 int x = game.exit_x;
4602 int y = game.exit_y;
4603 int element = Feld[x][y];
4605 // close exit door after last player
4606 if ((game.all_players_gone &&
4607 (element == EL_EXIT_OPEN ||
4608 element == EL_SP_EXIT_OPEN ||
4609 element == EL_STEEL_EXIT_OPEN)) ||
4610 element == EL_EM_EXIT_OPEN ||
4611 element == EL_EM_STEEL_EXIT_OPEN)
4615 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4616 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4617 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4618 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4619 EL_EM_STEEL_EXIT_CLOSING);
4621 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4624 // player disappears
4625 DrawLevelField(x, y);
4628 for (i = 0; i < MAX_PLAYERS; i++)
4630 struct PlayerInfo *player = &stored_player[i];
4632 if (player->present)
4634 RemovePlayer(player);
4636 // player disappears
4637 DrawLevelField(player->jx, player->jy);
4642 PlaySound(SND_GAME_WINNING);
4645 if (game_over_delay_1 > 0)
4647 game_over_delay_1--;
4652 if (time != time_final)
4654 int time_to_go = ABS(time_final - time);
4655 int time_count_dir = (time < time_final ? +1 : -1);
4657 if (time_to_go < time_count_steps)
4658 time_count_steps = 1;
4660 time += time_count_steps * time_count_dir;
4661 score += time_count_steps * level.score[SC_TIME_BONUS];
4663 game.LevelSolved_CountingTime = time;
4664 game.LevelSolved_CountingScore = score;
4666 game_panel_controls[GAME_PANEL_TIME].value = time;
4667 game_panel_controls[GAME_PANEL_SCORE].value = score;
4669 DisplayGameControlValues();
4671 if (time == time_final)
4672 StopSound(SND_GAME_LEVELTIME_BONUS);
4673 else if (setup.sound_loops)
4674 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4676 PlaySound(SND_GAME_LEVELTIME_BONUS);
4681 if (game_over_delay_2 > 0)
4683 game_over_delay_2--;
4688 if (health != health_final)
4690 int health_count_dir = (health < health_final ? +1 : -1);
4692 health += health_count_dir;
4693 score += level.score[SC_TIME_BONUS];
4695 game.LevelSolved_CountingHealth = health;
4696 game.LevelSolved_CountingScore = score;
4698 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4699 game_panel_controls[GAME_PANEL_SCORE].value = score;
4701 DisplayGameControlValues();
4703 if (health == health_final)
4704 StopSound(SND_GAME_LEVELTIME_BONUS);
4705 else if (setup.sound_loops)
4706 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4708 PlaySound(SND_GAME_LEVELTIME_BONUS);
4713 game.panel.active = FALSE;
4715 if (game_over_delay_3 > 0)
4717 game_over_delay_3--;
4727 // used instead of "level_nr" (needed for network games)
4728 int last_level_nr = levelset.level_nr;
4731 game.LevelSolved_GameEnd = TRUE;
4733 if (game.LevelSolved_SaveTape)
4735 // make sure that request dialog to save tape does not open door again
4736 if (!global.use_envelope_request)
4737 CloseDoor(DOOR_CLOSE_1);
4739 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4742 // if no tape is to be saved, close both doors simultaneously
4743 CloseDoor(DOOR_CLOSE_ALL);
4745 if (level_editor_test_game)
4747 SetGameStatus(GAME_MODE_MAIN);
4754 if (!game.LevelSolved_SaveScore)
4756 SetGameStatus(GAME_MODE_MAIN);
4763 if (level_nr == leveldir_current->handicap_level)
4765 leveldir_current->handicap_level++;
4767 SaveLevelSetup_SeriesInfo();
4770 if (setup.increment_levels &&
4771 level_nr < leveldir_current->last_level &&
4774 level_nr++; // advance to next level
4775 TapeErase(); // start with empty tape
4777 if (setup.auto_play_next_level)
4779 LoadLevel(level_nr);
4781 SaveLevelSetup_SeriesInfo();
4785 hi_pos = NewHiScore(last_level_nr);
4787 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4789 SetGameStatus(GAME_MODE_SCORES);
4791 DrawHallOfFame(last_level_nr, hi_pos);
4793 else if (setup.auto_play_next_level && setup.increment_levels &&
4794 last_level_nr < leveldir_current->last_level &&
4797 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4801 SetGameStatus(GAME_MODE_MAIN);
4807 int NewHiScore(int level_nr)
4811 boolean one_score_entry_per_name = !program.many_scores_per_name;
4813 LoadScore(level_nr);
4815 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4816 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4819 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4821 if (game.score_final > highscore[k].Score)
4823 // player has made it to the hall of fame
4825 if (k < MAX_SCORE_ENTRIES - 1)
4827 int m = MAX_SCORE_ENTRIES - 1;
4829 if (one_score_entry_per_name)
4831 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4832 if (strEqual(setup.player_name, highscore[l].Name))
4835 if (m == k) // player's new highscore overwrites his old one
4839 for (l = m; l > k; l--)
4841 strcpy(highscore[l].Name, highscore[l - 1].Name);
4842 highscore[l].Score = highscore[l - 1].Score;
4848 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4849 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4850 highscore[k].Score = game.score_final;
4855 else if (one_score_entry_per_name &&
4856 !strncmp(setup.player_name, highscore[k].Name,
4857 MAX_PLAYER_NAME_LEN))
4858 break; // player already there with a higher score
4862 SaveScore(level_nr);
4867 static int getElementMoveStepsizeExt(int x, int y, int direction)
4869 int element = Feld[x][y];
4870 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4871 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4872 int horiz_move = (dx != 0);
4873 int sign = (horiz_move ? dx : dy);
4874 int step = sign * element_info[element].move_stepsize;
4876 // special values for move stepsize for spring and things on conveyor belt
4879 if (CAN_FALL(element) &&
4880 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
4881 step = sign * MOVE_STEPSIZE_NORMAL / 2;
4882 else if (element == EL_SPRING)
4883 step = sign * MOVE_STEPSIZE_NORMAL * 2;
4889 static int getElementMoveStepsize(int x, int y)
4891 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
4894 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
4896 if (player->GfxAction != action || player->GfxDir != dir)
4898 player->GfxAction = action;
4899 player->GfxDir = dir;
4901 player->StepFrame = 0;
4905 static void ResetGfxFrame(int x, int y)
4907 // profiling showed that "autotest" spends 10~20% of its time in this function
4908 if (DrawingDeactivatedField())
4911 int element = Feld[x][y];
4912 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
4914 if (graphic_info[graphic].anim_global_sync)
4915 GfxFrame[x][y] = FrameCounter;
4916 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
4917 GfxFrame[x][y] = CustomValue[x][y];
4918 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
4919 GfxFrame[x][y] = element_info[element].collect_score;
4920 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
4921 GfxFrame[x][y] = ChangeDelay[x][y];
4924 static void ResetGfxAnimation(int x, int y)
4926 GfxAction[x][y] = ACTION_DEFAULT;
4927 GfxDir[x][y] = MovDir[x][y];
4930 ResetGfxFrame(x, y);
4933 static void ResetRandomAnimationValue(int x, int y)
4935 GfxRandom[x][y] = INIT_GFX_RANDOM();
4938 static void InitMovingField(int x, int y, int direction)
4940 int element = Feld[x][y];
4941 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4942 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4945 boolean is_moving_before, is_moving_after;
4947 // check if element was/is moving or being moved before/after mode change
4948 is_moving_before = (WasJustMoving[x][y] != 0);
4949 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
4951 // reset animation only for moving elements which change direction of moving
4952 // or which just started or stopped moving
4953 // (else CEs with property "can move" / "not moving" are reset each frame)
4954 if (is_moving_before != is_moving_after ||
4955 direction != MovDir[x][y])
4956 ResetGfxAnimation(x, y);
4958 MovDir[x][y] = direction;
4959 GfxDir[x][y] = direction;
4961 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
4962 direction == MV_DOWN && CAN_FALL(element) ?
4963 ACTION_FALLING : ACTION_MOVING);
4965 // this is needed for CEs with property "can move" / "not moving"
4967 if (is_moving_after)
4969 if (Feld[newx][newy] == EL_EMPTY)
4970 Feld[newx][newy] = EL_BLOCKED;
4972 MovDir[newx][newy] = MovDir[x][y];
4974 CustomValue[newx][newy] = CustomValue[x][y];
4976 GfxFrame[newx][newy] = GfxFrame[x][y];
4977 GfxRandom[newx][newy] = GfxRandom[x][y];
4978 GfxAction[newx][newy] = GfxAction[x][y];
4979 GfxDir[newx][newy] = GfxDir[x][y];
4983 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
4985 int direction = MovDir[x][y];
4986 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
4987 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
4993 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
4995 int oldx = x, oldy = y;
4996 int direction = MovDir[x][y];
4998 if (direction == MV_LEFT)
5000 else if (direction == MV_RIGHT)
5002 else if (direction == MV_UP)
5004 else if (direction == MV_DOWN)
5007 *comes_from_x = oldx;
5008 *comes_from_y = oldy;
5011 static int MovingOrBlocked2Element(int x, int y)
5013 int element = Feld[x][y];
5015 if (element == EL_BLOCKED)
5019 Blocked2Moving(x, y, &oldx, &oldy);
5020 return Feld[oldx][oldy];
5026 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5028 // like MovingOrBlocked2Element(), but if element is moving
5029 // and (x,y) is the field the moving element is just leaving,
5030 // return EL_BLOCKED instead of the element value
5031 int element = Feld[x][y];
5033 if (IS_MOVING(x, y))
5035 if (element == EL_BLOCKED)
5039 Blocked2Moving(x, y, &oldx, &oldy);
5040 return Feld[oldx][oldy];
5049 static void RemoveField(int x, int y)
5051 Feld[x][y] = EL_EMPTY;
5057 CustomValue[x][y] = 0;
5060 ChangeDelay[x][y] = 0;
5061 ChangePage[x][y] = -1;
5062 Pushed[x][y] = FALSE;
5064 GfxElement[x][y] = EL_UNDEFINED;
5065 GfxAction[x][y] = ACTION_DEFAULT;
5066 GfxDir[x][y] = MV_NONE;
5069 static void RemoveMovingField(int x, int y)
5071 int oldx = x, oldy = y, newx = x, newy = y;
5072 int element = Feld[x][y];
5073 int next_element = EL_UNDEFINED;
5075 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5078 if (IS_MOVING(x, y))
5080 Moving2Blocked(x, y, &newx, &newy);
5082 if (Feld[newx][newy] != EL_BLOCKED)
5084 // element is moving, but target field is not free (blocked), but
5085 // already occupied by something different (example: acid pool);
5086 // in this case, only remove the moving field, but not the target
5088 RemoveField(oldx, oldy);
5090 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5092 TEST_DrawLevelField(oldx, oldy);
5097 else if (element == EL_BLOCKED)
5099 Blocked2Moving(x, y, &oldx, &oldy);
5100 if (!IS_MOVING(oldx, oldy))
5104 if (element == EL_BLOCKED &&
5105 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5106 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5107 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5108 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5109 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5110 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5111 next_element = get_next_element(Feld[oldx][oldy]);
5113 RemoveField(oldx, oldy);
5114 RemoveField(newx, newy);
5116 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5118 if (next_element != EL_UNDEFINED)
5119 Feld[oldx][oldy] = next_element;
5121 TEST_DrawLevelField(oldx, oldy);
5122 TEST_DrawLevelField(newx, newy);
5125 void DrawDynamite(int x, int y)
5127 int sx = SCREENX(x), sy = SCREENY(y);
5128 int graphic = el2img(Feld[x][y]);
5131 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5134 if (IS_WALKABLE_INSIDE(Back[x][y]))
5138 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5139 else if (Store[x][y])
5140 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5142 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5144 if (Back[x][y] || Store[x][y])
5145 DrawGraphicThruMask(sx, sy, graphic, frame);
5147 DrawGraphic(sx, sy, graphic, frame);
5150 static void CheckDynamite(int x, int y)
5152 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5156 if (MovDelay[x][y] != 0)
5159 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5165 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5170 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5172 boolean num_checked_players = 0;
5175 for (i = 0; i < MAX_PLAYERS; i++)
5177 if (stored_player[i].active)
5179 int sx = stored_player[i].jx;
5180 int sy = stored_player[i].jy;
5182 if (num_checked_players == 0)
5189 *sx1 = MIN(*sx1, sx);
5190 *sy1 = MIN(*sy1, sy);
5191 *sx2 = MAX(*sx2, sx);
5192 *sy2 = MAX(*sy2, sy);
5195 num_checked_players++;
5200 static boolean checkIfAllPlayersFitToScreen_RND(void)
5202 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5204 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5206 return (sx2 - sx1 < SCR_FIELDX &&
5207 sy2 - sy1 < SCR_FIELDY);
5210 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5212 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5214 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5216 *sx = (sx1 + sx2) / 2;
5217 *sy = (sy1 + sy2) / 2;
5220 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5221 boolean center_screen, boolean quick_relocation)
5223 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5224 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5225 boolean no_delay = (tape.warp_forward);
5226 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5227 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5228 int new_scroll_x, new_scroll_y;
5230 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5232 // case 1: quick relocation inside visible screen (without scrolling)
5239 if (!level.shifted_relocation || center_screen)
5241 // relocation _with_ centering of screen
5243 new_scroll_x = SCROLL_POSITION_X(x);
5244 new_scroll_y = SCROLL_POSITION_Y(y);
5248 // relocation _without_ centering of screen
5250 int center_scroll_x = SCROLL_POSITION_X(old_x);
5251 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5252 int offset_x = x + (scroll_x - center_scroll_x);
5253 int offset_y = y + (scroll_y - center_scroll_y);
5255 // for new screen position, apply previous offset to center position
5256 new_scroll_x = SCROLL_POSITION_X(offset_x);
5257 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5260 if (quick_relocation)
5262 // case 2: quick relocation (redraw without visible scrolling)
5264 scroll_x = new_scroll_x;
5265 scroll_y = new_scroll_y;
5272 // case 3: visible relocation (with scrolling to new position)
5274 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5276 SetVideoFrameDelay(wait_delay_value);
5278 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5280 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5281 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5283 if (dx == 0 && dy == 0) // no scrolling needed at all
5289 // set values for horizontal/vertical screen scrolling (half tile size)
5290 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5291 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5292 int pos_x = dx * TILEX / 2;
5293 int pos_y = dy * TILEY / 2;
5294 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5295 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5297 ScrollLevel(dx, dy);
5300 // scroll in two steps of half tile size to make things smoother
5301 BlitScreenToBitmapExt_RND(window, fx, fy);
5303 // scroll second step to align at full tile size
5304 BlitScreenToBitmap(window);
5310 SetVideoFrameDelay(frame_delay_value_old);
5313 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5315 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5316 int player_nr = GET_PLAYER_NR(el_player);
5317 struct PlayerInfo *player = &stored_player[player_nr];
5318 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5319 boolean no_delay = (tape.warp_forward);
5320 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5321 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5322 int old_jx = player->jx;
5323 int old_jy = player->jy;
5324 int old_element = Feld[old_jx][old_jy];
5325 int element = Feld[jx][jy];
5326 boolean player_relocated = (old_jx != jx || old_jy != jy);
5328 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5329 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5330 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5331 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5332 int leave_side_horiz = move_dir_horiz;
5333 int leave_side_vert = move_dir_vert;
5334 int enter_side = enter_side_horiz | enter_side_vert;
5335 int leave_side = leave_side_horiz | leave_side_vert;
5337 if (player->buried) // do not reanimate dead player
5340 if (!player_relocated) // no need to relocate the player
5343 if (IS_PLAYER(jx, jy)) // player already placed at new position
5345 RemoveField(jx, jy); // temporarily remove newly placed player
5346 DrawLevelField(jx, jy);
5349 if (player->present)
5351 while (player->MovPos)
5353 ScrollPlayer(player, SCROLL_GO_ON);
5354 ScrollScreen(NULL, SCROLL_GO_ON);
5356 AdvanceFrameAndPlayerCounters(player->index_nr);
5360 BackToFront_WithFrameDelay(wait_delay_value);
5363 DrawPlayer(player); // needed here only to cleanup last field
5364 DrawLevelField(player->jx, player->jy); // remove player graphic
5366 player->is_moving = FALSE;
5369 if (IS_CUSTOM_ELEMENT(old_element))
5370 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5372 player->index_bit, leave_side);
5374 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5376 player->index_bit, leave_side);
5378 Feld[jx][jy] = el_player;
5379 InitPlayerField(jx, jy, el_player, TRUE);
5381 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5382 possible that the relocation target field did not contain a player element,
5383 but a walkable element, to which the new player was relocated -- in this
5384 case, restore that (already initialized!) element on the player field */
5385 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5387 Feld[jx][jy] = element; // restore previously existing element
5390 // only visually relocate centered player
5391 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5392 FALSE, level.instant_relocation);
5394 TestIfPlayerTouchesBadThing(jx, jy);
5395 TestIfPlayerTouchesCustomElement(jx, jy);
5397 if (IS_CUSTOM_ELEMENT(element))
5398 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5399 player->index_bit, enter_side);
5401 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5402 player->index_bit, enter_side);
5404 if (player->is_switching)
5406 /* ensure that relocation while still switching an element does not cause
5407 a new element to be treated as also switched directly after relocation
5408 (this is important for teleporter switches that teleport the player to
5409 a place where another teleporter switch is in the same direction, which
5410 would then incorrectly be treated as immediately switched before the
5411 direction key that caused the switch was released) */
5413 player->switch_x += jx - old_jx;
5414 player->switch_y += jy - old_jy;
5418 static void Explode(int ex, int ey, int phase, int mode)
5424 // !!! eliminate this variable !!!
5425 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5427 if (game.explosions_delayed)
5429 ExplodeField[ex][ey] = mode;
5433 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5435 int center_element = Feld[ex][ey];
5436 int artwork_element, explosion_element; // set these values later
5438 // remove things displayed in background while burning dynamite
5439 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5442 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5444 // put moving element to center field (and let it explode there)
5445 center_element = MovingOrBlocked2Element(ex, ey);
5446 RemoveMovingField(ex, ey);
5447 Feld[ex][ey] = center_element;
5450 // now "center_element" is finally determined -- set related values now
5451 artwork_element = center_element; // for custom player artwork
5452 explosion_element = center_element; // for custom player artwork
5454 if (IS_PLAYER(ex, ey))
5456 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5458 artwork_element = stored_player[player_nr].artwork_element;
5460 if (level.use_explosion_element[player_nr])
5462 explosion_element = level.explosion_element[player_nr];
5463 artwork_element = explosion_element;
5467 if (mode == EX_TYPE_NORMAL ||
5468 mode == EX_TYPE_CENTER ||
5469 mode == EX_TYPE_CROSS)
5470 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5472 last_phase = element_info[explosion_element].explosion_delay + 1;
5474 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5476 int xx = x - ex + 1;
5477 int yy = y - ey + 1;
5480 if (!IN_LEV_FIELD(x, y) ||
5481 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5482 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5485 element = Feld[x][y];
5487 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5489 element = MovingOrBlocked2Element(x, y);
5491 if (!IS_EXPLOSION_PROOF(element))
5492 RemoveMovingField(x, y);
5495 // indestructible elements can only explode in center (but not flames)
5496 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5497 mode == EX_TYPE_BORDER)) ||
5498 element == EL_FLAMES)
5501 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5502 behaviour, for example when touching a yamyam that explodes to rocks
5503 with active deadly shield, a rock is created under the player !!! */
5504 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5506 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5507 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5508 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5510 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5513 if (IS_ACTIVE_BOMB(element))
5515 // re-activate things under the bomb like gate or penguin
5516 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5523 // save walkable background elements while explosion on same tile
5524 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5525 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5526 Back[x][y] = element;
5528 // ignite explodable elements reached by other explosion
5529 if (element == EL_EXPLOSION)
5530 element = Store2[x][y];
5532 if (AmoebaNr[x][y] &&
5533 (element == EL_AMOEBA_FULL ||
5534 element == EL_BD_AMOEBA ||
5535 element == EL_AMOEBA_GROWING))
5537 AmoebaCnt[AmoebaNr[x][y]]--;
5538 AmoebaCnt2[AmoebaNr[x][y]]--;
5543 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5545 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5547 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5549 if (PLAYERINFO(ex, ey)->use_murphy)
5550 Store[x][y] = EL_EMPTY;
5553 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5554 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5555 else if (ELEM_IS_PLAYER(center_element))
5556 Store[x][y] = EL_EMPTY;
5557 else if (center_element == EL_YAMYAM)
5558 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5559 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5560 Store[x][y] = element_info[center_element].content.e[xx][yy];
5562 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5563 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5564 // otherwise) -- FIX THIS !!!
5565 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5566 Store[x][y] = element_info[element].content.e[1][1];
5568 else if (!CAN_EXPLODE(element))
5569 Store[x][y] = element_info[element].content.e[1][1];
5572 Store[x][y] = EL_EMPTY;
5574 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5575 center_element == EL_AMOEBA_TO_DIAMOND)
5576 Store2[x][y] = element;
5578 Feld[x][y] = EL_EXPLOSION;
5579 GfxElement[x][y] = artwork_element;
5581 ExplodePhase[x][y] = 1;
5582 ExplodeDelay[x][y] = last_phase;
5587 if (center_element == EL_YAMYAM)
5588 game.yamyam_content_nr =
5589 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5601 GfxFrame[x][y] = 0; // restart explosion animation
5603 last_phase = ExplodeDelay[x][y];
5605 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5607 // this can happen if the player leaves an explosion just in time
5608 if (GfxElement[x][y] == EL_UNDEFINED)
5609 GfxElement[x][y] = EL_EMPTY;
5611 border_element = Store2[x][y];
5612 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5613 border_element = StorePlayer[x][y];
5615 if (phase == element_info[border_element].ignition_delay ||
5616 phase == last_phase)
5618 boolean border_explosion = FALSE;
5620 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5621 !PLAYER_EXPLOSION_PROTECTED(x, y))
5623 KillPlayerUnlessExplosionProtected(x, y);
5624 border_explosion = TRUE;
5626 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5628 Feld[x][y] = Store2[x][y];
5631 border_explosion = TRUE;
5633 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5635 AmoebeUmwandeln(x, y);
5637 border_explosion = TRUE;
5640 // if an element just explodes due to another explosion (chain-reaction),
5641 // do not immediately end the new explosion when it was the last frame of
5642 // the explosion (as it would be done in the following "if"-statement!)
5643 if (border_explosion && phase == last_phase)
5647 if (phase == last_phase)
5651 element = Feld[x][y] = Store[x][y];
5652 Store[x][y] = Store2[x][y] = 0;
5653 GfxElement[x][y] = EL_UNDEFINED;
5655 // player can escape from explosions and might therefore be still alive
5656 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5657 element <= EL_PLAYER_IS_EXPLODING_4)
5659 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5660 int explosion_element = EL_PLAYER_1 + player_nr;
5661 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5662 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5664 if (level.use_explosion_element[player_nr])
5665 explosion_element = level.explosion_element[player_nr];
5667 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5668 element_info[explosion_element].content.e[xx][yy]);
5671 // restore probably existing indestructible background element
5672 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5673 element = Feld[x][y] = Back[x][y];
5676 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5677 GfxDir[x][y] = MV_NONE;
5678 ChangeDelay[x][y] = 0;
5679 ChangePage[x][y] = -1;
5681 CustomValue[x][y] = 0;
5683 InitField_WithBug2(x, y, FALSE);
5685 TEST_DrawLevelField(x, y);
5687 TestIfElementTouchesCustomElement(x, y);
5689 if (GFX_CRUMBLED(element))
5690 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5692 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5693 StorePlayer[x][y] = 0;
5695 if (ELEM_IS_PLAYER(element))
5696 RelocatePlayer(x, y, element);
5698 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5700 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5701 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5704 TEST_DrawLevelFieldCrumbled(x, y);
5706 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5708 DrawLevelElement(x, y, Back[x][y]);
5709 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5711 else if (IS_WALKABLE_UNDER(Back[x][y]))
5713 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5714 DrawLevelElementThruMask(x, y, Back[x][y]);
5716 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5717 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5721 static void DynaExplode(int ex, int ey)
5724 int dynabomb_element = Feld[ex][ey];
5725 int dynabomb_size = 1;
5726 boolean dynabomb_xl = FALSE;
5727 struct PlayerInfo *player;
5728 static int xy[4][2] =
5736 if (IS_ACTIVE_BOMB(dynabomb_element))
5738 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5739 dynabomb_size = player->dynabomb_size;
5740 dynabomb_xl = player->dynabomb_xl;
5741 player->dynabombs_left++;
5744 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5746 for (i = 0; i < NUM_DIRECTIONS; i++)
5748 for (j = 1; j <= dynabomb_size; j++)
5750 int x = ex + j * xy[i][0];
5751 int y = ey + j * xy[i][1];
5754 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5757 element = Feld[x][y];
5759 // do not restart explosions of fields with active bombs
5760 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5763 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5765 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5766 !IS_DIGGABLE(element) && !dynabomb_xl)
5772 void Bang(int x, int y)
5774 int element = MovingOrBlocked2Element(x, y);
5775 int explosion_type = EX_TYPE_NORMAL;
5777 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5779 struct PlayerInfo *player = PLAYERINFO(x, y);
5781 element = Feld[x][y] = player->initial_element;
5783 if (level.use_explosion_element[player->index_nr])
5785 int explosion_element = level.explosion_element[player->index_nr];
5787 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5788 explosion_type = EX_TYPE_CROSS;
5789 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5790 explosion_type = EX_TYPE_CENTER;
5798 case EL_BD_BUTTERFLY:
5801 case EL_DARK_YAMYAM:
5805 RaiseScoreElement(element);
5808 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5809 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5810 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5811 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5812 case EL_DYNABOMB_INCREASE_NUMBER:
5813 case EL_DYNABOMB_INCREASE_SIZE:
5814 case EL_DYNABOMB_INCREASE_POWER:
5815 explosion_type = EX_TYPE_DYNA;
5818 case EL_DC_LANDMINE:
5819 explosion_type = EX_TYPE_CENTER;
5824 case EL_LAMP_ACTIVE:
5825 case EL_AMOEBA_TO_DIAMOND:
5826 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5827 explosion_type = EX_TYPE_CENTER;
5831 if (element_info[element].explosion_type == EXPLODES_CROSS)
5832 explosion_type = EX_TYPE_CROSS;
5833 else if (element_info[element].explosion_type == EXPLODES_1X1)
5834 explosion_type = EX_TYPE_CENTER;
5838 if (explosion_type == EX_TYPE_DYNA)
5841 Explode(x, y, EX_PHASE_START, explosion_type);
5843 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5846 static void SplashAcid(int x, int y)
5848 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5849 (!IN_LEV_FIELD(x - 1, y - 2) ||
5850 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5851 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5853 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5854 (!IN_LEV_FIELD(x + 1, y - 2) ||
5855 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5856 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5858 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5861 static void InitBeltMovement(void)
5863 static int belt_base_element[4] =
5865 EL_CONVEYOR_BELT_1_LEFT,
5866 EL_CONVEYOR_BELT_2_LEFT,
5867 EL_CONVEYOR_BELT_3_LEFT,
5868 EL_CONVEYOR_BELT_4_LEFT
5870 static int belt_base_active_element[4] =
5872 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5873 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5874 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5875 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5880 // set frame order for belt animation graphic according to belt direction
5881 for (i = 0; i < NUM_BELTS; i++)
5885 for (j = 0; j < NUM_BELT_PARTS; j++)
5887 int element = belt_base_active_element[belt_nr] + j;
5888 int graphic_1 = el2img(element);
5889 int graphic_2 = el2panelimg(element);
5891 if (game.belt_dir[i] == MV_LEFT)
5893 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5894 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5898 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5899 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5904 SCAN_PLAYFIELD(x, y)
5906 int element = Feld[x][y];
5908 for (i = 0; i < NUM_BELTS; i++)
5910 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
5912 int e_belt_nr = getBeltNrFromBeltElement(element);
5915 if (e_belt_nr == belt_nr)
5917 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
5919 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
5926 static void ToggleBeltSwitch(int x, int y)
5928 static int belt_base_element[4] =
5930 EL_CONVEYOR_BELT_1_LEFT,
5931 EL_CONVEYOR_BELT_2_LEFT,
5932 EL_CONVEYOR_BELT_3_LEFT,
5933 EL_CONVEYOR_BELT_4_LEFT
5935 static int belt_base_active_element[4] =
5937 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5938 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5939 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5940 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5942 static int belt_base_switch_element[4] =
5944 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
5945 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
5946 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
5947 EL_CONVEYOR_BELT_4_SWITCH_LEFT
5949 static int belt_move_dir[4] =
5957 int element = Feld[x][y];
5958 int belt_nr = getBeltNrFromBeltSwitchElement(element);
5959 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
5960 int belt_dir = belt_move_dir[belt_dir_nr];
5963 if (!IS_BELT_SWITCH(element))
5966 game.belt_dir_nr[belt_nr] = belt_dir_nr;
5967 game.belt_dir[belt_nr] = belt_dir;
5969 if (belt_dir_nr == 3)
5972 // set frame order for belt animation graphic according to belt direction
5973 for (i = 0; i < NUM_BELT_PARTS; i++)
5975 int element = belt_base_active_element[belt_nr] + i;
5976 int graphic_1 = el2img(element);
5977 int graphic_2 = el2panelimg(element);
5979 if (belt_dir == MV_LEFT)
5981 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5982 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5986 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5987 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5991 SCAN_PLAYFIELD(xx, yy)
5993 int element = Feld[xx][yy];
5995 if (IS_BELT_SWITCH(element))
5997 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
5999 if (e_belt_nr == belt_nr)
6001 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6002 TEST_DrawLevelField(xx, yy);
6005 else if (IS_BELT(element) && belt_dir != MV_NONE)
6007 int e_belt_nr = getBeltNrFromBeltElement(element);
6009 if (e_belt_nr == belt_nr)
6011 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
6013 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6014 TEST_DrawLevelField(xx, yy);
6017 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6019 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6021 if (e_belt_nr == belt_nr)
6023 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6025 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6026 TEST_DrawLevelField(xx, yy);
6032 static void ToggleSwitchgateSwitch(int x, int y)
6036 game.switchgate_pos = !game.switchgate_pos;
6038 SCAN_PLAYFIELD(xx, yy)
6040 int element = Feld[xx][yy];
6042 if (element == EL_SWITCHGATE_SWITCH_UP)
6044 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6045 TEST_DrawLevelField(xx, yy);
6047 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6049 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6050 TEST_DrawLevelField(xx, yy);
6052 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6054 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6055 TEST_DrawLevelField(xx, yy);
6057 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6059 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6060 TEST_DrawLevelField(xx, yy);
6062 else if (element == EL_SWITCHGATE_OPEN ||
6063 element == EL_SWITCHGATE_OPENING)
6065 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6067 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6069 else if (element == EL_SWITCHGATE_CLOSED ||
6070 element == EL_SWITCHGATE_CLOSING)
6072 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6074 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6079 static int getInvisibleActiveFromInvisibleElement(int element)
6081 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6082 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6083 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6087 static int getInvisibleFromInvisibleActiveElement(int element)
6089 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6090 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6091 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6095 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6099 SCAN_PLAYFIELD(x, y)
6101 int element = Feld[x][y];
6103 if (element == EL_LIGHT_SWITCH &&
6104 game.light_time_left > 0)
6106 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6107 TEST_DrawLevelField(x, y);
6109 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6110 game.light_time_left == 0)
6112 Feld[x][y] = EL_LIGHT_SWITCH;
6113 TEST_DrawLevelField(x, y);
6115 else if (element == EL_EMC_DRIPPER &&
6116 game.light_time_left > 0)
6118 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6119 TEST_DrawLevelField(x, y);
6121 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6122 game.light_time_left == 0)
6124 Feld[x][y] = EL_EMC_DRIPPER;
6125 TEST_DrawLevelField(x, y);
6127 else if (element == EL_INVISIBLE_STEELWALL ||
6128 element == EL_INVISIBLE_WALL ||
6129 element == EL_INVISIBLE_SAND)
6131 if (game.light_time_left > 0)
6132 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6134 TEST_DrawLevelField(x, y);
6136 // uncrumble neighbour fields, if needed
6137 if (element == EL_INVISIBLE_SAND)
6138 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6140 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6141 element == EL_INVISIBLE_WALL_ACTIVE ||
6142 element == EL_INVISIBLE_SAND_ACTIVE)
6144 if (game.light_time_left == 0)
6145 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6147 TEST_DrawLevelField(x, y);
6149 // re-crumble neighbour fields, if needed
6150 if (element == EL_INVISIBLE_SAND)
6151 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6156 static void RedrawAllInvisibleElementsForLenses(void)
6160 SCAN_PLAYFIELD(x, y)
6162 int element = Feld[x][y];
6164 if (element == EL_EMC_DRIPPER &&
6165 game.lenses_time_left > 0)
6167 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6168 TEST_DrawLevelField(x, y);
6170 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6171 game.lenses_time_left == 0)
6173 Feld[x][y] = EL_EMC_DRIPPER;
6174 TEST_DrawLevelField(x, y);
6176 else if (element == EL_INVISIBLE_STEELWALL ||
6177 element == EL_INVISIBLE_WALL ||
6178 element == EL_INVISIBLE_SAND)
6180 if (game.lenses_time_left > 0)
6181 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6183 TEST_DrawLevelField(x, y);
6185 // uncrumble neighbour fields, if needed
6186 if (element == EL_INVISIBLE_SAND)
6187 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6189 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6190 element == EL_INVISIBLE_WALL_ACTIVE ||
6191 element == EL_INVISIBLE_SAND_ACTIVE)
6193 if (game.lenses_time_left == 0)
6194 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6196 TEST_DrawLevelField(x, y);
6198 // re-crumble neighbour fields, if needed
6199 if (element == EL_INVISIBLE_SAND)
6200 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6205 static void RedrawAllInvisibleElementsForMagnifier(void)
6209 SCAN_PLAYFIELD(x, y)
6211 int element = Feld[x][y];
6213 if (element == EL_EMC_FAKE_GRASS &&
6214 game.magnify_time_left > 0)
6216 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6217 TEST_DrawLevelField(x, y);
6219 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6220 game.magnify_time_left == 0)
6222 Feld[x][y] = EL_EMC_FAKE_GRASS;
6223 TEST_DrawLevelField(x, y);
6225 else if (IS_GATE_GRAY(element) &&
6226 game.magnify_time_left > 0)
6228 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6229 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6230 IS_EM_GATE_GRAY(element) ?
6231 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6232 IS_EMC_GATE_GRAY(element) ?
6233 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6234 IS_DC_GATE_GRAY(element) ?
6235 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6237 TEST_DrawLevelField(x, y);
6239 else if (IS_GATE_GRAY_ACTIVE(element) &&
6240 game.magnify_time_left == 0)
6242 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6243 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6244 IS_EM_GATE_GRAY_ACTIVE(element) ?
6245 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6246 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6247 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6248 IS_DC_GATE_GRAY_ACTIVE(element) ?
6249 EL_DC_GATE_WHITE_GRAY :
6251 TEST_DrawLevelField(x, y);
6256 static void ToggleLightSwitch(int x, int y)
6258 int element = Feld[x][y];
6260 game.light_time_left =
6261 (element == EL_LIGHT_SWITCH ?
6262 level.time_light * FRAMES_PER_SECOND : 0);
6264 RedrawAllLightSwitchesAndInvisibleElements();
6267 static void ActivateTimegateSwitch(int x, int y)
6271 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6273 SCAN_PLAYFIELD(xx, yy)
6275 int element = Feld[xx][yy];
6277 if (element == EL_TIMEGATE_CLOSED ||
6278 element == EL_TIMEGATE_CLOSING)
6280 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6281 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6285 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6287 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6288 TEST_DrawLevelField(xx, yy);
6294 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6295 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6298 static void Impact(int x, int y)
6300 boolean last_line = (y == lev_fieldy - 1);
6301 boolean object_hit = FALSE;
6302 boolean impact = (last_line || object_hit);
6303 int element = Feld[x][y];
6304 int smashed = EL_STEELWALL;
6306 if (!last_line) // check if element below was hit
6308 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6311 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6312 MovDir[x][y + 1] != MV_DOWN ||
6313 MovPos[x][y + 1] <= TILEY / 2));
6315 // do not smash moving elements that left the smashed field in time
6316 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6317 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6320 #if USE_QUICKSAND_IMPACT_BUGFIX
6321 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6323 RemoveMovingField(x, y + 1);
6324 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6325 Feld[x][y + 2] = EL_ROCK;
6326 TEST_DrawLevelField(x, y + 2);
6331 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6333 RemoveMovingField(x, y + 1);
6334 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6335 Feld[x][y + 2] = EL_ROCK;
6336 TEST_DrawLevelField(x, y + 2);
6343 smashed = MovingOrBlocked2Element(x, y + 1);
6345 impact = (last_line || object_hit);
6348 if (!last_line && smashed == EL_ACID) // element falls into acid
6350 SplashAcid(x, y + 1);
6354 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6355 // only reset graphic animation if graphic really changes after impact
6357 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6359 ResetGfxAnimation(x, y);
6360 TEST_DrawLevelField(x, y);
6363 if (impact && CAN_EXPLODE_IMPACT(element))
6368 else if (impact && element == EL_PEARL &&
6369 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6371 ResetGfxAnimation(x, y);
6373 Feld[x][y] = EL_PEARL_BREAKING;
6374 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6377 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6379 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6384 if (impact && element == EL_AMOEBA_DROP)
6386 if (object_hit && IS_PLAYER(x, y + 1))
6387 KillPlayerUnlessEnemyProtected(x, y + 1);
6388 else if (object_hit && smashed == EL_PENGUIN)
6392 Feld[x][y] = EL_AMOEBA_GROWING;
6393 Store[x][y] = EL_AMOEBA_WET;
6395 ResetRandomAnimationValue(x, y);
6400 if (object_hit) // check which object was hit
6402 if ((CAN_PASS_MAGIC_WALL(element) &&
6403 (smashed == EL_MAGIC_WALL ||
6404 smashed == EL_BD_MAGIC_WALL)) ||
6405 (CAN_PASS_DC_MAGIC_WALL(element) &&
6406 smashed == EL_DC_MAGIC_WALL))
6409 int activated_magic_wall =
6410 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6411 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6412 EL_DC_MAGIC_WALL_ACTIVE);
6414 // activate magic wall / mill
6415 SCAN_PLAYFIELD(xx, yy)
6417 if (Feld[xx][yy] == smashed)
6418 Feld[xx][yy] = activated_magic_wall;
6421 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6422 game.magic_wall_active = TRUE;
6424 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6425 SND_MAGIC_WALL_ACTIVATING :
6426 smashed == EL_BD_MAGIC_WALL ?
6427 SND_BD_MAGIC_WALL_ACTIVATING :
6428 SND_DC_MAGIC_WALL_ACTIVATING));
6431 if (IS_PLAYER(x, y + 1))
6433 if (CAN_SMASH_PLAYER(element))
6435 KillPlayerUnlessEnemyProtected(x, y + 1);
6439 else if (smashed == EL_PENGUIN)
6441 if (CAN_SMASH_PLAYER(element))
6447 else if (element == EL_BD_DIAMOND)
6449 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6455 else if (((element == EL_SP_INFOTRON ||
6456 element == EL_SP_ZONK) &&
6457 (smashed == EL_SP_SNIKSNAK ||
6458 smashed == EL_SP_ELECTRON ||
6459 smashed == EL_SP_DISK_ORANGE)) ||
6460 (element == EL_SP_INFOTRON &&
6461 smashed == EL_SP_DISK_YELLOW))
6466 else if (CAN_SMASH_EVERYTHING(element))
6468 if (IS_CLASSIC_ENEMY(smashed) ||
6469 CAN_EXPLODE_SMASHED(smashed))
6474 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6476 if (smashed == EL_LAMP ||
6477 smashed == EL_LAMP_ACTIVE)
6482 else if (smashed == EL_NUT)
6484 Feld[x][y + 1] = EL_NUT_BREAKING;
6485 PlayLevelSound(x, y, SND_NUT_BREAKING);
6486 RaiseScoreElement(EL_NUT);
6489 else if (smashed == EL_PEARL)
6491 ResetGfxAnimation(x, y);
6493 Feld[x][y + 1] = EL_PEARL_BREAKING;
6494 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6497 else if (smashed == EL_DIAMOND)
6499 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6500 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6503 else if (IS_BELT_SWITCH(smashed))
6505 ToggleBeltSwitch(x, y + 1);
6507 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6508 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6509 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6510 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6512 ToggleSwitchgateSwitch(x, y + 1);
6514 else if (smashed == EL_LIGHT_SWITCH ||
6515 smashed == EL_LIGHT_SWITCH_ACTIVE)
6517 ToggleLightSwitch(x, y + 1);
6521 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6523 CheckElementChangeBySide(x, y + 1, smashed, element,
6524 CE_SWITCHED, CH_SIDE_TOP);
6525 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6531 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6536 // play sound of magic wall / mill
6538 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6539 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6540 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6542 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6543 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6544 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6545 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6546 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6547 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6552 // play sound of object that hits the ground
6553 if (last_line || object_hit)
6554 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6557 static void TurnRoundExt(int x, int y)
6569 { 0, 0 }, { 0, 0 }, { 0, 0 },
6574 int left, right, back;
6578 { MV_DOWN, MV_UP, MV_RIGHT },
6579 { MV_UP, MV_DOWN, MV_LEFT },
6581 { MV_LEFT, MV_RIGHT, MV_DOWN },
6585 { MV_RIGHT, MV_LEFT, MV_UP }
6588 int element = Feld[x][y];
6589 int move_pattern = element_info[element].move_pattern;
6591 int old_move_dir = MovDir[x][y];
6592 int left_dir = turn[old_move_dir].left;
6593 int right_dir = turn[old_move_dir].right;
6594 int back_dir = turn[old_move_dir].back;
6596 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6597 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6598 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6599 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6601 int left_x = x + left_dx, left_y = y + left_dy;
6602 int right_x = x + right_dx, right_y = y + right_dy;
6603 int move_x = x + move_dx, move_y = y + move_dy;
6607 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6609 TestIfBadThingTouchesOtherBadThing(x, y);
6611 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6612 MovDir[x][y] = right_dir;
6613 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6614 MovDir[x][y] = left_dir;
6616 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6618 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6621 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6623 TestIfBadThingTouchesOtherBadThing(x, y);
6625 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6626 MovDir[x][y] = left_dir;
6627 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6628 MovDir[x][y] = right_dir;
6630 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6632 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6635 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6637 TestIfBadThingTouchesOtherBadThing(x, y);
6639 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6640 MovDir[x][y] = left_dir;
6641 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6642 MovDir[x][y] = right_dir;
6644 if (MovDir[x][y] != old_move_dir)
6647 else if (element == EL_YAMYAM)
6649 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6650 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6652 if (can_turn_left && can_turn_right)
6653 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6654 else if (can_turn_left)
6655 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6656 else if (can_turn_right)
6657 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6659 MovDir[x][y] = back_dir;
6661 MovDelay[x][y] = 16 + 16 * RND(3);
6663 else if (element == EL_DARK_YAMYAM)
6665 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6667 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6670 if (can_turn_left && can_turn_right)
6671 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6672 else if (can_turn_left)
6673 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6674 else if (can_turn_right)
6675 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6677 MovDir[x][y] = back_dir;
6679 MovDelay[x][y] = 16 + 16 * RND(3);
6681 else if (element == EL_PACMAN)
6683 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6684 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6686 if (can_turn_left && can_turn_right)
6687 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6688 else if (can_turn_left)
6689 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6690 else if (can_turn_right)
6691 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6693 MovDir[x][y] = back_dir;
6695 MovDelay[x][y] = 6 + RND(40);
6697 else if (element == EL_PIG)
6699 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6700 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6701 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6702 boolean should_turn_left, should_turn_right, should_move_on;
6704 int rnd = RND(rnd_value);
6706 should_turn_left = (can_turn_left &&
6708 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6709 y + back_dy + left_dy)));
6710 should_turn_right = (can_turn_right &&
6712 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6713 y + back_dy + right_dy)));
6714 should_move_on = (can_move_on &&
6717 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6718 y + move_dy + left_dy) ||
6719 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6720 y + move_dy + right_dy)));
6722 if (should_turn_left || should_turn_right || should_move_on)
6724 if (should_turn_left && should_turn_right && should_move_on)
6725 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6726 rnd < 2 * rnd_value / 3 ? right_dir :
6728 else if (should_turn_left && should_turn_right)
6729 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6730 else if (should_turn_left && should_move_on)
6731 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6732 else if (should_turn_right && should_move_on)
6733 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6734 else if (should_turn_left)
6735 MovDir[x][y] = left_dir;
6736 else if (should_turn_right)
6737 MovDir[x][y] = right_dir;
6738 else if (should_move_on)
6739 MovDir[x][y] = old_move_dir;
6741 else if (can_move_on && rnd > rnd_value / 8)
6742 MovDir[x][y] = old_move_dir;
6743 else if (can_turn_left && can_turn_right)
6744 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6745 else if (can_turn_left && rnd > rnd_value / 8)
6746 MovDir[x][y] = left_dir;
6747 else if (can_turn_right && rnd > rnd_value/8)
6748 MovDir[x][y] = right_dir;
6750 MovDir[x][y] = back_dir;
6752 xx = x + move_xy[MovDir[x][y]].dx;
6753 yy = y + move_xy[MovDir[x][y]].dy;
6755 if (!IN_LEV_FIELD(xx, yy) ||
6756 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6757 MovDir[x][y] = old_move_dir;
6761 else if (element == EL_DRAGON)
6763 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6764 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6765 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6767 int rnd = RND(rnd_value);
6769 if (can_move_on && rnd > rnd_value / 8)
6770 MovDir[x][y] = old_move_dir;
6771 else if (can_turn_left && can_turn_right)
6772 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6773 else if (can_turn_left && rnd > rnd_value / 8)
6774 MovDir[x][y] = left_dir;
6775 else if (can_turn_right && rnd > rnd_value / 8)
6776 MovDir[x][y] = right_dir;
6778 MovDir[x][y] = back_dir;
6780 xx = x + move_xy[MovDir[x][y]].dx;
6781 yy = y + move_xy[MovDir[x][y]].dy;
6783 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6784 MovDir[x][y] = old_move_dir;
6788 else if (element == EL_MOLE)
6790 boolean can_move_on =
6791 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6792 IS_AMOEBOID(Feld[move_x][move_y]) ||
6793 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6796 boolean can_turn_left =
6797 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6798 IS_AMOEBOID(Feld[left_x][left_y])));
6800 boolean can_turn_right =
6801 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6802 IS_AMOEBOID(Feld[right_x][right_y])));
6804 if (can_turn_left && can_turn_right)
6805 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6806 else if (can_turn_left)
6807 MovDir[x][y] = left_dir;
6809 MovDir[x][y] = right_dir;
6812 if (MovDir[x][y] != old_move_dir)
6815 else if (element == EL_BALLOON)
6817 MovDir[x][y] = game.wind_direction;
6820 else if (element == EL_SPRING)
6822 if (MovDir[x][y] & MV_HORIZONTAL)
6824 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6825 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6827 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6828 ResetGfxAnimation(move_x, move_y);
6829 TEST_DrawLevelField(move_x, move_y);
6831 MovDir[x][y] = back_dir;
6833 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6834 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6835 MovDir[x][y] = MV_NONE;
6840 else if (element == EL_ROBOT ||
6841 element == EL_SATELLITE ||
6842 element == EL_PENGUIN ||
6843 element == EL_EMC_ANDROID)
6845 int attr_x = -1, attr_y = -1;
6847 if (game.all_players_gone)
6849 attr_x = game.exit_x;
6850 attr_y = game.exit_y;
6856 for (i = 0; i < MAX_PLAYERS; i++)
6858 struct PlayerInfo *player = &stored_player[i];
6859 int jx = player->jx, jy = player->jy;
6861 if (!player->active)
6865 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6873 if (element == EL_ROBOT &&
6874 game.robot_wheel_x >= 0 &&
6875 game.robot_wheel_y >= 0 &&
6876 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
6877 game.engine_version < VERSION_IDENT(3,1,0,0)))
6879 attr_x = game.robot_wheel_x;
6880 attr_y = game.robot_wheel_y;
6883 if (element == EL_PENGUIN)
6886 static int xy[4][2] =
6894 for (i = 0; i < NUM_DIRECTIONS; i++)
6896 int ex = x + xy[i][0];
6897 int ey = y + xy[i][1];
6899 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
6900 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
6901 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
6902 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
6911 MovDir[x][y] = MV_NONE;
6913 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
6914 else if (attr_x > x)
6915 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
6917 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
6918 else if (attr_y > y)
6919 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
6921 if (element == EL_ROBOT)
6925 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6926 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
6927 Moving2Blocked(x, y, &newx, &newy);
6929 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
6930 MovDelay[x][y] = 8 + 8 * !RND(3);
6932 MovDelay[x][y] = 16;
6934 else if (element == EL_PENGUIN)
6940 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6942 boolean first_horiz = RND(2);
6943 int new_move_dir = MovDir[x][y];
6946 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6947 Moving2Blocked(x, y, &newx, &newy);
6949 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6953 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6954 Moving2Blocked(x, y, &newx, &newy);
6956 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6959 MovDir[x][y] = old_move_dir;
6963 else if (element == EL_SATELLITE)
6969 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6971 boolean first_horiz = RND(2);
6972 int new_move_dir = MovDir[x][y];
6975 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6976 Moving2Blocked(x, y, &newx, &newy);
6978 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6982 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6983 Moving2Blocked(x, y, &newx, &newy);
6985 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6988 MovDir[x][y] = old_move_dir;
6992 else if (element == EL_EMC_ANDROID)
6994 static int check_pos[16] =
6996 -1, // 0 => (invalid)
6999 -1, // 3 => (invalid)
7001 0, // 5 => MV_LEFT | MV_UP
7002 2, // 6 => MV_RIGHT | MV_UP
7003 -1, // 7 => (invalid)
7005 6, // 9 => MV_LEFT | MV_DOWN
7006 4, // 10 => MV_RIGHT | MV_DOWN
7007 -1, // 11 => (invalid)
7008 -1, // 12 => (invalid)
7009 -1, // 13 => (invalid)
7010 -1, // 14 => (invalid)
7011 -1, // 15 => (invalid)
7019 { -1, -1, MV_LEFT | MV_UP },
7021 { +1, -1, MV_RIGHT | MV_UP },
7022 { +1, 0, MV_RIGHT },
7023 { +1, +1, MV_RIGHT | MV_DOWN },
7025 { -1, +1, MV_LEFT | MV_DOWN },
7028 int start_pos, check_order;
7029 boolean can_clone = FALSE;
7032 // check if there is any free field around current position
7033 for (i = 0; i < 8; i++)
7035 int newx = x + check_xy[i].dx;
7036 int newy = y + check_xy[i].dy;
7038 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7046 if (can_clone) // randomly find an element to clone
7050 start_pos = check_pos[RND(8)];
7051 check_order = (RND(2) ? -1 : +1);
7053 for (i = 0; i < 8; i++)
7055 int pos_raw = start_pos + i * check_order;
7056 int pos = (pos_raw + 8) % 8;
7057 int newx = x + check_xy[pos].dx;
7058 int newy = y + check_xy[pos].dy;
7060 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7062 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7063 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7065 Store[x][y] = Feld[newx][newy];
7074 if (can_clone) // randomly find a direction to move
7078 start_pos = check_pos[RND(8)];
7079 check_order = (RND(2) ? -1 : +1);
7081 for (i = 0; i < 8; i++)
7083 int pos_raw = start_pos + i * check_order;
7084 int pos = (pos_raw + 8) % 8;
7085 int newx = x + check_xy[pos].dx;
7086 int newy = y + check_xy[pos].dy;
7087 int new_move_dir = check_xy[pos].dir;
7089 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7091 MovDir[x][y] = new_move_dir;
7092 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7101 if (can_clone) // cloning and moving successful
7104 // cannot clone -- try to move towards player
7106 start_pos = check_pos[MovDir[x][y] & 0x0f];
7107 check_order = (RND(2) ? -1 : +1);
7109 for (i = 0; i < 3; i++)
7111 // first check start_pos, then previous/next or (next/previous) pos
7112 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7113 int pos = (pos_raw + 8) % 8;
7114 int newx = x + check_xy[pos].dx;
7115 int newy = y + check_xy[pos].dy;
7116 int new_move_dir = check_xy[pos].dir;
7118 if (IS_PLAYER(newx, newy))
7121 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7123 MovDir[x][y] = new_move_dir;
7124 MovDelay[x][y] = level.android_move_time * 8 + 1;
7131 else if (move_pattern == MV_TURNING_LEFT ||
7132 move_pattern == MV_TURNING_RIGHT ||
7133 move_pattern == MV_TURNING_LEFT_RIGHT ||
7134 move_pattern == MV_TURNING_RIGHT_LEFT ||
7135 move_pattern == MV_TURNING_RANDOM ||
7136 move_pattern == MV_ALL_DIRECTIONS)
7138 boolean can_turn_left =
7139 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7140 boolean can_turn_right =
7141 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7143 if (element_info[element].move_stepsize == 0) // "not moving"
7146 if (move_pattern == MV_TURNING_LEFT)
7147 MovDir[x][y] = left_dir;
7148 else if (move_pattern == MV_TURNING_RIGHT)
7149 MovDir[x][y] = right_dir;
7150 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7151 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7152 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7153 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7154 else if (move_pattern == MV_TURNING_RANDOM)
7155 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7156 can_turn_right && !can_turn_left ? right_dir :
7157 RND(2) ? left_dir : right_dir);
7158 else if (can_turn_left && can_turn_right)
7159 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7160 else if (can_turn_left)
7161 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7162 else if (can_turn_right)
7163 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7165 MovDir[x][y] = back_dir;
7167 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7169 else if (move_pattern == MV_HORIZONTAL ||
7170 move_pattern == MV_VERTICAL)
7172 if (move_pattern & old_move_dir)
7173 MovDir[x][y] = back_dir;
7174 else if (move_pattern == MV_HORIZONTAL)
7175 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7176 else if (move_pattern == MV_VERTICAL)
7177 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7179 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7181 else if (move_pattern & MV_ANY_DIRECTION)
7183 MovDir[x][y] = move_pattern;
7184 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7186 else if (move_pattern & MV_WIND_DIRECTION)
7188 MovDir[x][y] = game.wind_direction;
7189 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7191 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7193 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7194 MovDir[x][y] = left_dir;
7195 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7196 MovDir[x][y] = right_dir;
7198 if (MovDir[x][y] != old_move_dir)
7199 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7201 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7203 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7204 MovDir[x][y] = right_dir;
7205 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7206 MovDir[x][y] = left_dir;
7208 if (MovDir[x][y] != old_move_dir)
7209 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7211 else if (move_pattern == MV_TOWARDS_PLAYER ||
7212 move_pattern == MV_AWAY_FROM_PLAYER)
7214 int attr_x = -1, attr_y = -1;
7216 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7218 if (game.all_players_gone)
7220 attr_x = game.exit_x;
7221 attr_y = game.exit_y;
7227 for (i = 0; i < MAX_PLAYERS; i++)
7229 struct PlayerInfo *player = &stored_player[i];
7230 int jx = player->jx, jy = player->jy;
7232 if (!player->active)
7236 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7244 MovDir[x][y] = MV_NONE;
7246 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7247 else if (attr_x > x)
7248 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7250 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7251 else if (attr_y > y)
7252 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7254 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7256 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7258 boolean first_horiz = RND(2);
7259 int new_move_dir = MovDir[x][y];
7261 if (element_info[element].move_stepsize == 0) // "not moving"
7263 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7264 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7270 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7271 Moving2Blocked(x, y, &newx, &newy);
7273 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7277 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7278 Moving2Blocked(x, y, &newx, &newy);
7280 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7283 MovDir[x][y] = old_move_dir;
7286 else if (move_pattern == MV_WHEN_PUSHED ||
7287 move_pattern == MV_WHEN_DROPPED)
7289 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7290 MovDir[x][y] = MV_NONE;
7294 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7296 static int test_xy[7][2] =
7306 static int test_dir[7] =
7316 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7317 int move_preference = -1000000; // start with very low preference
7318 int new_move_dir = MV_NONE;
7319 int start_test = RND(4);
7322 for (i = 0; i < NUM_DIRECTIONS; i++)
7324 int move_dir = test_dir[start_test + i];
7325 int move_dir_preference;
7327 xx = x + test_xy[start_test + i][0];
7328 yy = y + test_xy[start_test + i][1];
7330 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7331 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7333 new_move_dir = move_dir;
7338 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7341 move_dir_preference = -1 * RunnerVisit[xx][yy];
7342 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7343 move_dir_preference = PlayerVisit[xx][yy];
7345 if (move_dir_preference > move_preference)
7347 // prefer field that has not been visited for the longest time
7348 move_preference = move_dir_preference;
7349 new_move_dir = move_dir;
7351 else if (move_dir_preference == move_preference &&
7352 move_dir == old_move_dir)
7354 // prefer last direction when all directions are preferred equally
7355 move_preference = move_dir_preference;
7356 new_move_dir = move_dir;
7360 MovDir[x][y] = new_move_dir;
7361 if (old_move_dir != new_move_dir)
7362 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7366 static void TurnRound(int x, int y)
7368 int direction = MovDir[x][y];
7372 GfxDir[x][y] = MovDir[x][y];
7374 if (direction != MovDir[x][y])
7378 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7380 ResetGfxFrame(x, y);
7383 static boolean JustBeingPushed(int x, int y)
7387 for (i = 0; i < MAX_PLAYERS; i++)
7389 struct PlayerInfo *player = &stored_player[i];
7391 if (player->active && player->is_pushing && player->MovPos)
7393 int next_jx = player->jx + (player->jx - player->last_jx);
7394 int next_jy = player->jy + (player->jy - player->last_jy);
7396 if (x == next_jx && y == next_jy)
7404 static void StartMoving(int x, int y)
7406 boolean started_moving = FALSE; // some elements can fall _and_ move
7407 int element = Feld[x][y];
7412 if (MovDelay[x][y] == 0)
7413 GfxAction[x][y] = ACTION_DEFAULT;
7415 if (CAN_FALL(element) && y < lev_fieldy - 1)
7417 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7418 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7419 if (JustBeingPushed(x, y))
7422 if (element == EL_QUICKSAND_FULL)
7424 if (IS_FREE(x, y + 1))
7426 InitMovingField(x, y, MV_DOWN);
7427 started_moving = TRUE;
7429 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7430 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7431 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7432 Store[x][y] = EL_ROCK;
7434 Store[x][y] = EL_ROCK;
7437 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7439 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7441 if (!MovDelay[x][y])
7443 MovDelay[x][y] = TILEY + 1;
7445 ResetGfxAnimation(x, y);
7446 ResetGfxAnimation(x, y + 1);
7451 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7452 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7459 Feld[x][y] = EL_QUICKSAND_EMPTY;
7460 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7461 Store[x][y + 1] = Store[x][y];
7464 PlayLevelSoundAction(x, y, ACTION_FILLING);
7466 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7468 if (!MovDelay[x][y])
7470 MovDelay[x][y] = TILEY + 1;
7472 ResetGfxAnimation(x, y);
7473 ResetGfxAnimation(x, y + 1);
7478 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7479 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7486 Feld[x][y] = EL_QUICKSAND_EMPTY;
7487 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7488 Store[x][y + 1] = Store[x][y];
7491 PlayLevelSoundAction(x, y, ACTION_FILLING);
7494 else if (element == EL_QUICKSAND_FAST_FULL)
7496 if (IS_FREE(x, y + 1))
7498 InitMovingField(x, y, MV_DOWN);
7499 started_moving = TRUE;
7501 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7502 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7503 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7504 Store[x][y] = EL_ROCK;
7506 Store[x][y] = EL_ROCK;
7509 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7511 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7513 if (!MovDelay[x][y])
7515 MovDelay[x][y] = TILEY + 1;
7517 ResetGfxAnimation(x, y);
7518 ResetGfxAnimation(x, y + 1);
7523 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7524 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7531 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7532 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7533 Store[x][y + 1] = Store[x][y];
7536 PlayLevelSoundAction(x, y, ACTION_FILLING);
7538 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7540 if (!MovDelay[x][y])
7542 MovDelay[x][y] = TILEY + 1;
7544 ResetGfxAnimation(x, y);
7545 ResetGfxAnimation(x, y + 1);
7550 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7551 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7558 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7559 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7560 Store[x][y + 1] = Store[x][y];
7563 PlayLevelSoundAction(x, y, ACTION_FILLING);
7566 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7567 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7569 InitMovingField(x, y, MV_DOWN);
7570 started_moving = TRUE;
7572 Feld[x][y] = EL_QUICKSAND_FILLING;
7573 Store[x][y] = element;
7575 PlayLevelSoundAction(x, y, ACTION_FILLING);
7577 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7578 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7580 InitMovingField(x, y, MV_DOWN);
7581 started_moving = TRUE;
7583 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7584 Store[x][y] = element;
7586 PlayLevelSoundAction(x, y, ACTION_FILLING);
7588 else if (element == EL_MAGIC_WALL_FULL)
7590 if (IS_FREE(x, y + 1))
7592 InitMovingField(x, y, MV_DOWN);
7593 started_moving = TRUE;
7595 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7596 Store[x][y] = EL_CHANGED(Store[x][y]);
7598 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7600 if (!MovDelay[x][y])
7601 MovDelay[x][y] = TILEY / 4 + 1;
7610 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7611 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7612 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7616 else if (element == EL_BD_MAGIC_WALL_FULL)
7618 if (IS_FREE(x, y + 1))
7620 InitMovingField(x, y, MV_DOWN);
7621 started_moving = TRUE;
7623 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7624 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7626 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7628 if (!MovDelay[x][y])
7629 MovDelay[x][y] = TILEY / 4 + 1;
7638 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7639 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7640 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7644 else if (element == EL_DC_MAGIC_WALL_FULL)
7646 if (IS_FREE(x, y + 1))
7648 InitMovingField(x, y, MV_DOWN);
7649 started_moving = TRUE;
7651 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7652 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7654 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7656 if (!MovDelay[x][y])
7657 MovDelay[x][y] = TILEY / 4 + 1;
7666 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7667 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7668 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7672 else if ((CAN_PASS_MAGIC_WALL(element) &&
7673 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7674 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7675 (CAN_PASS_DC_MAGIC_WALL(element) &&
7676 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7679 InitMovingField(x, y, MV_DOWN);
7680 started_moving = TRUE;
7683 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7684 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7685 EL_DC_MAGIC_WALL_FILLING);
7686 Store[x][y] = element;
7688 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7690 SplashAcid(x, y + 1);
7692 InitMovingField(x, y, MV_DOWN);
7693 started_moving = TRUE;
7695 Store[x][y] = EL_ACID;
7698 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7699 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7700 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7701 CAN_FALL(element) && WasJustFalling[x][y] &&
7702 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7704 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7705 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7706 (Feld[x][y + 1] == EL_BLOCKED)))
7708 /* this is needed for a special case not covered by calling "Impact()"
7709 from "ContinueMoving()": if an element moves to a tile directly below
7710 another element which was just falling on that tile (which was empty
7711 in the previous frame), the falling element above would just stop
7712 instead of smashing the element below (in previous version, the above
7713 element was just checked for "moving" instead of "falling", resulting
7714 in incorrect smashes caused by horizontal movement of the above
7715 element; also, the case of the player being the element to smash was
7716 simply not covered here... :-/ ) */
7718 CheckCollision[x][y] = 0;
7719 CheckImpact[x][y] = 0;
7723 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7725 if (MovDir[x][y] == MV_NONE)
7727 InitMovingField(x, y, MV_DOWN);
7728 started_moving = TRUE;
7731 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7733 if (WasJustFalling[x][y]) // prevent animation from being restarted
7734 MovDir[x][y] = MV_DOWN;
7736 InitMovingField(x, y, MV_DOWN);
7737 started_moving = TRUE;
7739 else if (element == EL_AMOEBA_DROP)
7741 Feld[x][y] = EL_AMOEBA_GROWING;
7742 Store[x][y] = EL_AMOEBA_WET;
7744 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7745 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7746 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7747 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7749 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7750 (IS_FREE(x - 1, y + 1) ||
7751 Feld[x - 1][y + 1] == EL_ACID));
7752 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7753 (IS_FREE(x + 1, y + 1) ||
7754 Feld[x + 1][y + 1] == EL_ACID));
7755 boolean can_fall_any = (can_fall_left || can_fall_right);
7756 boolean can_fall_both = (can_fall_left && can_fall_right);
7757 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7759 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7761 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7762 can_fall_right = FALSE;
7763 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7764 can_fall_left = FALSE;
7765 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7766 can_fall_right = FALSE;
7767 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7768 can_fall_left = FALSE;
7770 can_fall_any = (can_fall_left || can_fall_right);
7771 can_fall_both = FALSE;
7776 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7777 can_fall_right = FALSE; // slip down on left side
7779 can_fall_left = !(can_fall_right = RND(2));
7781 can_fall_both = FALSE;
7786 // if not determined otherwise, prefer left side for slipping down
7787 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7788 started_moving = TRUE;
7791 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7793 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7794 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7795 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7796 int belt_dir = game.belt_dir[belt_nr];
7798 if ((belt_dir == MV_LEFT && left_is_free) ||
7799 (belt_dir == MV_RIGHT && right_is_free))
7801 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7803 InitMovingField(x, y, belt_dir);
7804 started_moving = TRUE;
7806 Pushed[x][y] = TRUE;
7807 Pushed[nextx][y] = TRUE;
7809 GfxAction[x][y] = ACTION_DEFAULT;
7813 MovDir[x][y] = 0; // if element was moving, stop it
7818 // not "else if" because of elements that can fall and move (EL_SPRING)
7819 if (CAN_MOVE(element) && !started_moving)
7821 int move_pattern = element_info[element].move_pattern;
7824 Moving2Blocked(x, y, &newx, &newy);
7826 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7829 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7830 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7832 WasJustMoving[x][y] = 0;
7833 CheckCollision[x][y] = 0;
7835 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7837 if (Feld[x][y] != element) // element has changed
7841 if (!MovDelay[x][y]) // start new movement phase
7843 // all objects that can change their move direction after each step
7844 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7846 if (element != EL_YAMYAM &&
7847 element != EL_DARK_YAMYAM &&
7848 element != EL_PACMAN &&
7849 !(move_pattern & MV_ANY_DIRECTION) &&
7850 move_pattern != MV_TURNING_LEFT &&
7851 move_pattern != MV_TURNING_RIGHT &&
7852 move_pattern != MV_TURNING_LEFT_RIGHT &&
7853 move_pattern != MV_TURNING_RIGHT_LEFT &&
7854 move_pattern != MV_TURNING_RANDOM)
7858 if (MovDelay[x][y] && (element == EL_BUG ||
7859 element == EL_SPACESHIP ||
7860 element == EL_SP_SNIKSNAK ||
7861 element == EL_SP_ELECTRON ||
7862 element == EL_MOLE))
7863 TEST_DrawLevelField(x, y);
7867 if (MovDelay[x][y]) // wait some time before next movement
7871 if (element == EL_ROBOT ||
7872 element == EL_YAMYAM ||
7873 element == EL_DARK_YAMYAM)
7875 DrawLevelElementAnimationIfNeeded(x, y, element);
7876 PlayLevelSoundAction(x, y, ACTION_WAITING);
7878 else if (element == EL_SP_ELECTRON)
7879 DrawLevelElementAnimationIfNeeded(x, y, element);
7880 else if (element == EL_DRAGON)
7883 int dir = MovDir[x][y];
7884 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
7885 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
7886 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
7887 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
7888 dir == MV_UP ? IMG_FLAMES_1_UP :
7889 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
7890 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
7892 GfxAction[x][y] = ACTION_ATTACKING;
7894 if (IS_PLAYER(x, y))
7895 DrawPlayerField(x, y);
7897 TEST_DrawLevelField(x, y);
7899 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
7901 for (i = 1; i <= 3; i++)
7903 int xx = x + i * dx;
7904 int yy = y + i * dy;
7905 int sx = SCREENX(xx);
7906 int sy = SCREENY(yy);
7907 int flame_graphic = graphic + (i - 1);
7909 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
7914 int flamed = MovingOrBlocked2Element(xx, yy);
7916 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
7919 RemoveMovingField(xx, yy);
7921 ChangeDelay[xx][yy] = 0;
7923 Feld[xx][yy] = EL_FLAMES;
7925 if (IN_SCR_FIELD(sx, sy))
7927 TEST_DrawLevelFieldCrumbled(xx, yy);
7928 DrawGraphic(sx, sy, flame_graphic, frame);
7933 if (Feld[xx][yy] == EL_FLAMES)
7934 Feld[xx][yy] = EL_EMPTY;
7935 TEST_DrawLevelField(xx, yy);
7940 if (MovDelay[x][y]) // element still has to wait some time
7942 PlayLevelSoundAction(x, y, ACTION_WAITING);
7948 // now make next step
7950 Moving2Blocked(x, y, &newx, &newy); // get next screen position
7952 if (DONT_COLLIDE_WITH(element) &&
7953 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
7954 !PLAYER_ENEMY_PROTECTED(newx, newy))
7956 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
7961 else if (CAN_MOVE_INTO_ACID(element) &&
7962 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
7963 !IS_MV_DIAGONAL(MovDir[x][y]) &&
7964 (MovDir[x][y] == MV_DOWN ||
7965 game.engine_version >= VERSION_IDENT(3,1,0,0)))
7967 SplashAcid(newx, newy);
7968 Store[x][y] = EL_ACID;
7970 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
7972 if (Feld[newx][newy] == EL_EXIT_OPEN ||
7973 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
7974 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
7975 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
7978 TEST_DrawLevelField(x, y);
7980 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
7981 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
7982 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
7984 game.friends_still_needed--;
7985 if (!game.friends_still_needed &&
7987 game.all_players_gone)
7992 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
7994 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
7995 TEST_DrawLevelField(newx, newy);
7997 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
7999 else if (!IS_FREE(newx, newy))
8001 GfxAction[x][y] = ACTION_WAITING;
8003 if (IS_PLAYER(x, y))
8004 DrawPlayerField(x, y);
8006 TEST_DrawLevelField(x, y);
8011 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8013 if (IS_FOOD_PIG(Feld[newx][newy]))
8015 if (IS_MOVING(newx, newy))
8016 RemoveMovingField(newx, newy);
8019 Feld[newx][newy] = EL_EMPTY;
8020 TEST_DrawLevelField(newx, newy);
8023 PlayLevelSound(x, y, SND_PIG_DIGGING);
8025 else if (!IS_FREE(newx, newy))
8027 if (IS_PLAYER(x, y))
8028 DrawPlayerField(x, y);
8030 TEST_DrawLevelField(x, y);
8035 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8037 if (Store[x][y] != EL_EMPTY)
8039 boolean can_clone = FALSE;
8042 // check if element to clone is still there
8043 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8045 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8053 // cannot clone or target field not free anymore -- do not clone
8054 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8055 Store[x][y] = EL_EMPTY;
8058 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8060 if (IS_MV_DIAGONAL(MovDir[x][y]))
8062 int diagonal_move_dir = MovDir[x][y];
8063 int stored = Store[x][y];
8064 int change_delay = 8;
8067 // android is moving diagonally
8069 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8071 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8072 GfxElement[x][y] = EL_EMC_ANDROID;
8073 GfxAction[x][y] = ACTION_SHRINKING;
8074 GfxDir[x][y] = diagonal_move_dir;
8075 ChangeDelay[x][y] = change_delay;
8077 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8080 DrawLevelGraphicAnimation(x, y, graphic);
8081 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8083 if (Feld[newx][newy] == EL_ACID)
8085 SplashAcid(newx, newy);
8090 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8092 Store[newx][newy] = EL_EMC_ANDROID;
8093 GfxElement[newx][newy] = EL_EMC_ANDROID;
8094 GfxAction[newx][newy] = ACTION_GROWING;
8095 GfxDir[newx][newy] = diagonal_move_dir;
8096 ChangeDelay[newx][newy] = change_delay;
8098 graphic = el_act_dir2img(GfxElement[newx][newy],
8099 GfxAction[newx][newy], GfxDir[newx][newy]);
8101 DrawLevelGraphicAnimation(newx, newy, graphic);
8102 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8108 Feld[newx][newy] = EL_EMPTY;
8109 TEST_DrawLevelField(newx, newy);
8111 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8114 else if (!IS_FREE(newx, newy))
8119 else if (IS_CUSTOM_ELEMENT(element) &&
8120 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8122 if (!DigFieldByCE(newx, newy, element))
8125 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8127 RunnerVisit[x][y] = FrameCounter;
8128 PlayerVisit[x][y] /= 8; // expire player visit path
8131 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8133 if (!IS_FREE(newx, newy))
8135 if (IS_PLAYER(x, y))
8136 DrawPlayerField(x, y);
8138 TEST_DrawLevelField(x, y);
8144 boolean wanna_flame = !RND(10);
8145 int dx = newx - x, dy = newy - y;
8146 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8147 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8148 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8149 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8150 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8151 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8154 IS_CLASSIC_ENEMY(element1) ||
8155 IS_CLASSIC_ENEMY(element2)) &&
8156 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8157 element1 != EL_FLAMES && element2 != EL_FLAMES)
8159 ResetGfxAnimation(x, y);
8160 GfxAction[x][y] = ACTION_ATTACKING;
8162 if (IS_PLAYER(x, y))
8163 DrawPlayerField(x, y);
8165 TEST_DrawLevelField(x, y);
8167 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8169 MovDelay[x][y] = 50;
8171 Feld[newx][newy] = EL_FLAMES;
8172 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8173 Feld[newx1][newy1] = EL_FLAMES;
8174 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8175 Feld[newx2][newy2] = EL_FLAMES;
8181 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8182 Feld[newx][newy] == EL_DIAMOND)
8184 if (IS_MOVING(newx, newy))
8185 RemoveMovingField(newx, newy);
8188 Feld[newx][newy] = EL_EMPTY;
8189 TEST_DrawLevelField(newx, newy);
8192 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8194 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8195 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8197 if (AmoebaNr[newx][newy])
8199 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8200 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8201 Feld[newx][newy] == EL_BD_AMOEBA)
8202 AmoebaCnt[AmoebaNr[newx][newy]]--;
8205 if (IS_MOVING(newx, newy))
8207 RemoveMovingField(newx, newy);
8211 Feld[newx][newy] = EL_EMPTY;
8212 TEST_DrawLevelField(newx, newy);
8215 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8217 else if ((element == EL_PACMAN || element == EL_MOLE)
8218 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8220 if (AmoebaNr[newx][newy])
8222 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8223 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8224 Feld[newx][newy] == EL_BD_AMOEBA)
8225 AmoebaCnt[AmoebaNr[newx][newy]]--;
8228 if (element == EL_MOLE)
8230 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8231 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8233 ResetGfxAnimation(x, y);
8234 GfxAction[x][y] = ACTION_DIGGING;
8235 TEST_DrawLevelField(x, y);
8237 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8239 return; // wait for shrinking amoeba
8241 else // element == EL_PACMAN
8243 Feld[newx][newy] = EL_EMPTY;
8244 TEST_DrawLevelField(newx, newy);
8245 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8248 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8249 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8250 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8252 // wait for shrinking amoeba to completely disappear
8255 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8257 // object was running against a wall
8261 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8262 DrawLevelElementAnimation(x, y, element);
8264 if (DONT_TOUCH(element))
8265 TestIfBadThingTouchesPlayer(x, y);
8270 InitMovingField(x, y, MovDir[x][y]);
8272 PlayLevelSoundAction(x, y, ACTION_MOVING);
8276 ContinueMoving(x, y);
8279 void ContinueMoving(int x, int y)
8281 int element = Feld[x][y];
8282 struct ElementInfo *ei = &element_info[element];
8283 int direction = MovDir[x][y];
8284 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8285 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8286 int newx = x + dx, newy = y + dy;
8287 int stored = Store[x][y];
8288 int stored_new = Store[newx][newy];
8289 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8290 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8291 boolean last_line = (newy == lev_fieldy - 1);
8293 MovPos[x][y] += getElementMoveStepsize(x, y);
8295 if (pushed_by_player) // special case: moving object pushed by player
8296 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8298 if (ABS(MovPos[x][y]) < TILEX)
8300 TEST_DrawLevelField(x, y);
8302 return; // element is still moving
8305 // element reached destination field
8307 Feld[x][y] = EL_EMPTY;
8308 Feld[newx][newy] = element;
8309 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8311 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8313 element = Feld[newx][newy] = EL_ACID;
8315 else if (element == EL_MOLE)
8317 Feld[x][y] = EL_SAND;
8319 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8321 else if (element == EL_QUICKSAND_FILLING)
8323 element = Feld[newx][newy] = get_next_element(element);
8324 Store[newx][newy] = Store[x][y];
8326 else if (element == EL_QUICKSAND_EMPTYING)
8328 Feld[x][y] = get_next_element(element);
8329 element = Feld[newx][newy] = Store[x][y];
8331 else if (element == EL_QUICKSAND_FAST_FILLING)
8333 element = Feld[newx][newy] = get_next_element(element);
8334 Store[newx][newy] = Store[x][y];
8336 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8338 Feld[x][y] = get_next_element(element);
8339 element = Feld[newx][newy] = Store[x][y];
8341 else if (element == EL_MAGIC_WALL_FILLING)
8343 element = Feld[newx][newy] = get_next_element(element);
8344 if (!game.magic_wall_active)
8345 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8346 Store[newx][newy] = Store[x][y];
8348 else if (element == EL_MAGIC_WALL_EMPTYING)
8350 Feld[x][y] = get_next_element(element);
8351 if (!game.magic_wall_active)
8352 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8353 element = Feld[newx][newy] = Store[x][y];
8355 InitField(newx, newy, FALSE);
8357 else if (element == EL_BD_MAGIC_WALL_FILLING)
8359 element = Feld[newx][newy] = get_next_element(element);
8360 if (!game.magic_wall_active)
8361 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8362 Store[newx][newy] = Store[x][y];
8364 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8366 Feld[x][y] = get_next_element(element);
8367 if (!game.magic_wall_active)
8368 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8369 element = Feld[newx][newy] = Store[x][y];
8371 InitField(newx, newy, FALSE);
8373 else if (element == EL_DC_MAGIC_WALL_FILLING)
8375 element = Feld[newx][newy] = get_next_element(element);
8376 if (!game.magic_wall_active)
8377 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8378 Store[newx][newy] = Store[x][y];
8380 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8382 Feld[x][y] = get_next_element(element);
8383 if (!game.magic_wall_active)
8384 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8385 element = Feld[newx][newy] = Store[x][y];
8387 InitField(newx, newy, FALSE);
8389 else if (element == EL_AMOEBA_DROPPING)
8391 Feld[x][y] = get_next_element(element);
8392 element = Feld[newx][newy] = Store[x][y];
8394 else if (element == EL_SOKOBAN_OBJECT)
8397 Feld[x][y] = Back[x][y];
8399 if (Back[newx][newy])
8400 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8402 Back[x][y] = Back[newx][newy] = 0;
8405 Store[x][y] = EL_EMPTY;
8410 MovDelay[newx][newy] = 0;
8412 if (CAN_CHANGE_OR_HAS_ACTION(element))
8414 // copy element change control values to new field
8415 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8416 ChangePage[newx][newy] = ChangePage[x][y];
8417 ChangeCount[newx][newy] = ChangeCount[x][y];
8418 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8421 CustomValue[newx][newy] = CustomValue[x][y];
8423 ChangeDelay[x][y] = 0;
8424 ChangePage[x][y] = -1;
8425 ChangeCount[x][y] = 0;
8426 ChangeEvent[x][y] = -1;
8428 CustomValue[x][y] = 0;
8430 // copy animation control values to new field
8431 GfxFrame[newx][newy] = GfxFrame[x][y];
8432 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8433 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8434 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8436 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8438 // some elements can leave other elements behind after moving
8439 if (ei->move_leave_element != EL_EMPTY &&
8440 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8441 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8443 int move_leave_element = ei->move_leave_element;
8445 // this makes it possible to leave the removed element again
8446 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8447 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8449 Feld[x][y] = move_leave_element;
8451 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8452 MovDir[x][y] = direction;
8454 InitField(x, y, FALSE);
8456 if (GFX_CRUMBLED(Feld[x][y]))
8457 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8459 if (ELEM_IS_PLAYER(move_leave_element))
8460 RelocatePlayer(x, y, move_leave_element);
8463 // do this after checking for left-behind element
8464 ResetGfxAnimation(x, y); // reset animation values for old field
8466 if (!CAN_MOVE(element) ||
8467 (CAN_FALL(element) && direction == MV_DOWN &&
8468 (element == EL_SPRING ||
8469 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8470 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8471 GfxDir[x][y] = MovDir[newx][newy] = 0;
8473 TEST_DrawLevelField(x, y);
8474 TEST_DrawLevelField(newx, newy);
8476 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8478 // prevent pushed element from moving on in pushed direction
8479 if (pushed_by_player && CAN_MOVE(element) &&
8480 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8481 !(element_info[element].move_pattern & direction))
8482 TurnRound(newx, newy);
8484 // prevent elements on conveyor belt from moving on in last direction
8485 if (pushed_by_conveyor && CAN_FALL(element) &&
8486 direction & MV_HORIZONTAL)
8487 MovDir[newx][newy] = 0;
8489 if (!pushed_by_player)
8491 int nextx = newx + dx, nexty = newy + dy;
8492 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8494 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8496 if (CAN_FALL(element) && direction == MV_DOWN)
8497 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8499 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8500 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8502 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8503 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8506 if (DONT_TOUCH(element)) // object may be nasty to player or others
8508 TestIfBadThingTouchesPlayer(newx, newy);
8509 TestIfBadThingTouchesFriend(newx, newy);
8511 if (!IS_CUSTOM_ELEMENT(element))
8512 TestIfBadThingTouchesOtherBadThing(newx, newy);
8514 else if (element == EL_PENGUIN)
8515 TestIfFriendTouchesBadThing(newx, newy);
8517 if (DONT_GET_HIT_BY(element))
8519 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8522 // give the player one last chance (one more frame) to move away
8523 if (CAN_FALL(element) && direction == MV_DOWN &&
8524 (last_line || (!IS_FREE(x, newy + 1) &&
8525 (!IS_PLAYER(x, newy + 1) ||
8526 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8529 if (pushed_by_player && !game.use_change_when_pushing_bug)
8531 int push_side = MV_DIR_OPPOSITE(direction);
8532 struct PlayerInfo *player = PLAYERINFO(x, y);
8534 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8535 player->index_bit, push_side);
8536 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8537 player->index_bit, push_side);
8540 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8541 MovDelay[newx][newy] = 1;
8543 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8545 TestIfElementTouchesCustomElement(x, y); // empty or new element
8546 TestIfElementHitsCustomElement(newx, newy, direction);
8547 TestIfPlayerTouchesCustomElement(newx, newy);
8548 TestIfElementTouchesCustomElement(newx, newy);
8550 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8551 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8552 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8553 MV_DIR_OPPOSITE(direction));
8556 int AmoebeNachbarNr(int ax, int ay)
8559 int element = Feld[ax][ay];
8561 static int xy[4][2] =
8569 for (i = 0; i < NUM_DIRECTIONS; i++)
8571 int x = ax + xy[i][0];
8572 int y = ay + xy[i][1];
8574 if (!IN_LEV_FIELD(x, y))
8577 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8578 group_nr = AmoebaNr[x][y];
8584 static void AmoebenVereinigen(int ax, int ay)
8586 int i, x, y, xx, yy;
8587 int new_group_nr = AmoebaNr[ax][ay];
8588 static int xy[4][2] =
8596 if (new_group_nr == 0)
8599 for (i = 0; i < NUM_DIRECTIONS; i++)
8604 if (!IN_LEV_FIELD(x, y))
8607 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8608 Feld[x][y] == EL_BD_AMOEBA ||
8609 Feld[x][y] == EL_AMOEBA_DEAD) &&
8610 AmoebaNr[x][y] != new_group_nr)
8612 int old_group_nr = AmoebaNr[x][y];
8614 if (old_group_nr == 0)
8617 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8618 AmoebaCnt[old_group_nr] = 0;
8619 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8620 AmoebaCnt2[old_group_nr] = 0;
8622 SCAN_PLAYFIELD(xx, yy)
8624 if (AmoebaNr[xx][yy] == old_group_nr)
8625 AmoebaNr[xx][yy] = new_group_nr;
8631 void AmoebeUmwandeln(int ax, int ay)
8635 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8637 int group_nr = AmoebaNr[ax][ay];
8642 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8643 printf("AmoebeUmwandeln(): This should never happen!\n");
8648 SCAN_PLAYFIELD(x, y)
8650 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8653 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8657 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8658 SND_AMOEBA_TURNING_TO_GEM :
8659 SND_AMOEBA_TURNING_TO_ROCK));
8664 static int xy[4][2] =
8672 for (i = 0; i < NUM_DIRECTIONS; i++)
8677 if (!IN_LEV_FIELD(x, y))
8680 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8682 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8683 SND_AMOEBA_TURNING_TO_GEM :
8684 SND_AMOEBA_TURNING_TO_ROCK));
8691 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8694 int group_nr = AmoebaNr[ax][ay];
8695 boolean done = FALSE;
8700 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8701 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8706 SCAN_PLAYFIELD(x, y)
8708 if (AmoebaNr[x][y] == group_nr &&
8709 (Feld[x][y] == EL_AMOEBA_DEAD ||
8710 Feld[x][y] == EL_BD_AMOEBA ||
8711 Feld[x][y] == EL_AMOEBA_GROWING))
8714 Feld[x][y] = new_element;
8715 InitField(x, y, FALSE);
8716 TEST_DrawLevelField(x, y);
8722 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8723 SND_BD_AMOEBA_TURNING_TO_ROCK :
8724 SND_BD_AMOEBA_TURNING_TO_GEM));
8727 static void AmoebeWaechst(int x, int y)
8729 static unsigned int sound_delay = 0;
8730 static unsigned int sound_delay_value = 0;
8732 if (!MovDelay[x][y]) // start new growing cycle
8736 if (DelayReached(&sound_delay, sound_delay_value))
8738 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8739 sound_delay_value = 30;
8743 if (MovDelay[x][y]) // wait some time before growing bigger
8746 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8748 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8749 6 - MovDelay[x][y]);
8751 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8754 if (!MovDelay[x][y])
8756 Feld[x][y] = Store[x][y];
8758 TEST_DrawLevelField(x, y);
8763 static void AmoebaDisappearing(int x, int y)
8765 static unsigned int sound_delay = 0;
8766 static unsigned int sound_delay_value = 0;
8768 if (!MovDelay[x][y]) // start new shrinking cycle
8772 if (DelayReached(&sound_delay, sound_delay_value))
8773 sound_delay_value = 30;
8776 if (MovDelay[x][y]) // wait some time before shrinking
8779 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8781 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8782 6 - MovDelay[x][y]);
8784 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8787 if (!MovDelay[x][y])
8789 Feld[x][y] = EL_EMPTY;
8790 TEST_DrawLevelField(x, y);
8792 // don't let mole enter this field in this cycle;
8793 // (give priority to objects falling to this field from above)
8799 static void AmoebeAbleger(int ax, int ay)
8802 int element = Feld[ax][ay];
8803 int graphic = el2img(element);
8804 int newax = ax, neway = ay;
8805 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8806 static int xy[4][2] =
8814 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8816 Feld[ax][ay] = EL_AMOEBA_DEAD;
8817 TEST_DrawLevelField(ax, ay);
8821 if (IS_ANIMATED(graphic))
8822 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8824 if (!MovDelay[ax][ay]) // start making new amoeba field
8825 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8827 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8830 if (MovDelay[ax][ay])
8834 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8837 int x = ax + xy[start][0];
8838 int y = ay + xy[start][1];
8840 if (!IN_LEV_FIELD(x, y))
8843 if (IS_FREE(x, y) ||
8844 CAN_GROW_INTO(Feld[x][y]) ||
8845 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8846 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8852 if (newax == ax && neway == ay)
8855 else // normal or "filled" (BD style) amoeba
8858 boolean waiting_for_player = FALSE;
8860 for (i = 0; i < NUM_DIRECTIONS; i++)
8862 int j = (start + i) % 4;
8863 int x = ax + xy[j][0];
8864 int y = ay + xy[j][1];
8866 if (!IN_LEV_FIELD(x, y))
8869 if (IS_FREE(x, y) ||
8870 CAN_GROW_INTO(Feld[x][y]) ||
8871 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8872 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8878 else if (IS_PLAYER(x, y))
8879 waiting_for_player = TRUE;
8882 if (newax == ax && neway == ay) // amoeba cannot grow
8884 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
8886 Feld[ax][ay] = EL_AMOEBA_DEAD;
8887 TEST_DrawLevelField(ax, ay);
8888 AmoebaCnt[AmoebaNr[ax][ay]]--;
8890 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
8892 if (element == EL_AMOEBA_FULL)
8893 AmoebeUmwandeln(ax, ay);
8894 else if (element == EL_BD_AMOEBA)
8895 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
8900 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
8902 // amoeba gets larger by growing in some direction
8904 int new_group_nr = AmoebaNr[ax][ay];
8907 if (new_group_nr == 0)
8909 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
8910 printf("AmoebeAbleger(): This should never happen!\n");
8915 AmoebaNr[newax][neway] = new_group_nr;
8916 AmoebaCnt[new_group_nr]++;
8917 AmoebaCnt2[new_group_nr]++;
8919 // if amoeba touches other amoeba(s) after growing, unify them
8920 AmoebenVereinigen(newax, neway);
8922 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
8924 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
8930 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
8931 (neway == lev_fieldy - 1 && newax != ax))
8933 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
8934 Store[newax][neway] = element;
8936 else if (neway == ay || element == EL_EMC_DRIPPER)
8938 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
8940 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
8944 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
8945 Feld[ax][ay] = EL_AMOEBA_DROPPING;
8946 Store[ax][ay] = EL_AMOEBA_DROP;
8947 ContinueMoving(ax, ay);
8951 TEST_DrawLevelField(newax, neway);
8954 static void Life(int ax, int ay)
8958 int element = Feld[ax][ay];
8959 int graphic = el2img(element);
8960 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
8962 boolean changed = FALSE;
8964 if (IS_ANIMATED(graphic))
8965 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8970 if (!MovDelay[ax][ay]) // start new "game of life" cycle
8971 MovDelay[ax][ay] = life_time;
8973 if (MovDelay[ax][ay]) // wait some time before next cycle
8976 if (MovDelay[ax][ay])
8980 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
8982 int xx = ax+x1, yy = ay+y1;
8983 int old_element = Feld[xx][yy];
8984 int num_neighbours = 0;
8986 if (!IN_LEV_FIELD(xx, yy))
8989 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
8991 int x = xx+x2, y = yy+y2;
8993 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
8996 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
8997 boolean is_neighbour = FALSE;
8999 if (level.use_life_bugs)
9001 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9002 (IS_FREE(x, y) && Stop[x][y]));
9005 (Last[x][y] == element || is_player_cell);
9011 boolean is_free = FALSE;
9013 if (level.use_life_bugs)
9014 is_free = (IS_FREE(xx, yy));
9016 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9018 if (xx == ax && yy == ay) // field in the middle
9020 if (num_neighbours < life_parameter[0] ||
9021 num_neighbours > life_parameter[1])
9023 Feld[xx][yy] = EL_EMPTY;
9024 if (Feld[xx][yy] != old_element)
9025 TEST_DrawLevelField(xx, yy);
9026 Stop[xx][yy] = TRUE;
9030 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9031 { // free border field
9032 if (num_neighbours >= life_parameter[2] &&
9033 num_neighbours <= life_parameter[3])
9035 Feld[xx][yy] = element;
9036 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9037 if (Feld[xx][yy] != old_element)
9038 TEST_DrawLevelField(xx, yy);
9039 Stop[xx][yy] = TRUE;
9046 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9047 SND_GAME_OF_LIFE_GROWING);
9050 static void InitRobotWheel(int x, int y)
9052 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9055 static void RunRobotWheel(int x, int y)
9057 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9060 static void StopRobotWheel(int x, int y)
9062 if (game.robot_wheel_x == x &&
9063 game.robot_wheel_y == y)
9065 game.robot_wheel_x = -1;
9066 game.robot_wheel_y = -1;
9067 game.robot_wheel_active = FALSE;
9071 static void InitTimegateWheel(int x, int y)
9073 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9076 static void RunTimegateWheel(int x, int y)
9078 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9081 static void InitMagicBallDelay(int x, int y)
9083 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9086 static void ActivateMagicBall(int bx, int by)
9090 if (level.ball_random)
9092 int pos_border = RND(8); // select one of the eight border elements
9093 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9094 int xx = pos_content % 3;
9095 int yy = pos_content / 3;
9100 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9101 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9105 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9107 int xx = x - bx + 1;
9108 int yy = y - by + 1;
9110 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9111 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9115 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9118 static void CheckExit(int x, int y)
9120 if (game.gems_still_needed > 0 ||
9121 game.sokoban_fields_still_needed > 0 ||
9122 game.sokoban_objects_still_needed > 0 ||
9123 game.lights_still_needed > 0)
9125 int element = Feld[x][y];
9126 int graphic = el2img(element);
9128 if (IS_ANIMATED(graphic))
9129 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9134 // do not re-open exit door closed after last player
9135 if (game.all_players_gone)
9138 Feld[x][y] = EL_EXIT_OPENING;
9140 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9143 static void CheckExitEM(int x, int y)
9145 if (game.gems_still_needed > 0 ||
9146 game.sokoban_fields_still_needed > 0 ||
9147 game.sokoban_objects_still_needed > 0 ||
9148 game.lights_still_needed > 0)
9150 int element = Feld[x][y];
9151 int graphic = el2img(element);
9153 if (IS_ANIMATED(graphic))
9154 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9159 // do not re-open exit door closed after last player
9160 if (game.all_players_gone)
9163 Feld[x][y] = EL_EM_EXIT_OPENING;
9165 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9168 static void CheckExitSteel(int x, int y)
9170 if (game.gems_still_needed > 0 ||
9171 game.sokoban_fields_still_needed > 0 ||
9172 game.sokoban_objects_still_needed > 0 ||
9173 game.lights_still_needed > 0)
9175 int element = Feld[x][y];
9176 int graphic = el2img(element);
9178 if (IS_ANIMATED(graphic))
9179 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9184 // do not re-open exit door closed after last player
9185 if (game.all_players_gone)
9188 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9190 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9193 static void CheckExitSteelEM(int x, int y)
9195 if (game.gems_still_needed > 0 ||
9196 game.sokoban_fields_still_needed > 0 ||
9197 game.sokoban_objects_still_needed > 0 ||
9198 game.lights_still_needed > 0)
9200 int element = Feld[x][y];
9201 int graphic = el2img(element);
9203 if (IS_ANIMATED(graphic))
9204 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9209 // do not re-open exit door closed after last player
9210 if (game.all_players_gone)
9213 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9215 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9218 static void CheckExitSP(int x, int y)
9220 if (game.gems_still_needed > 0)
9222 int element = Feld[x][y];
9223 int graphic = el2img(element);
9225 if (IS_ANIMATED(graphic))
9226 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9231 // do not re-open exit door closed after last player
9232 if (game.all_players_gone)
9235 Feld[x][y] = EL_SP_EXIT_OPENING;
9237 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9240 static void CloseAllOpenTimegates(void)
9244 SCAN_PLAYFIELD(x, y)
9246 int element = Feld[x][y];
9248 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9250 Feld[x][y] = EL_TIMEGATE_CLOSING;
9252 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9257 static void DrawTwinkleOnField(int x, int y)
9259 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9262 if (Feld[x][y] == EL_BD_DIAMOND)
9265 if (MovDelay[x][y] == 0) // next animation frame
9266 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9268 if (MovDelay[x][y] != 0) // wait some time before next frame
9272 DrawLevelElementAnimation(x, y, Feld[x][y]);
9274 if (MovDelay[x][y] != 0)
9276 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9277 10 - MovDelay[x][y]);
9279 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9284 static void MauerWaechst(int x, int y)
9288 if (!MovDelay[x][y]) // next animation frame
9289 MovDelay[x][y] = 3 * delay;
9291 if (MovDelay[x][y]) // wait some time before next frame
9295 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9297 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9298 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9300 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9303 if (!MovDelay[x][y])
9305 if (MovDir[x][y] == MV_LEFT)
9307 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9308 TEST_DrawLevelField(x - 1, y);
9310 else if (MovDir[x][y] == MV_RIGHT)
9312 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9313 TEST_DrawLevelField(x + 1, y);
9315 else if (MovDir[x][y] == MV_UP)
9317 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9318 TEST_DrawLevelField(x, y - 1);
9322 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9323 TEST_DrawLevelField(x, y + 1);
9326 Feld[x][y] = Store[x][y];
9328 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9329 TEST_DrawLevelField(x, y);
9334 static void MauerAbleger(int ax, int ay)
9336 int element = Feld[ax][ay];
9337 int graphic = el2img(element);
9338 boolean oben_frei = FALSE, unten_frei = FALSE;
9339 boolean links_frei = FALSE, rechts_frei = FALSE;
9340 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9341 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9342 boolean new_wall = FALSE;
9344 if (IS_ANIMATED(graphic))
9345 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9347 if (!MovDelay[ax][ay]) // start building new wall
9348 MovDelay[ax][ay] = 6;
9350 if (MovDelay[ax][ay]) // wait some time before building new wall
9353 if (MovDelay[ax][ay])
9357 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9359 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9361 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9363 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9366 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9367 element == EL_EXPANDABLE_WALL_ANY)
9371 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9372 Store[ax][ay-1] = element;
9373 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9374 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9375 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9376 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9381 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9382 Store[ax][ay+1] = element;
9383 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9384 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9385 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9386 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9391 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9392 element == EL_EXPANDABLE_WALL_ANY ||
9393 element == EL_EXPANDABLE_WALL ||
9394 element == EL_BD_EXPANDABLE_WALL)
9398 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9399 Store[ax-1][ay] = element;
9400 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9401 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9402 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9403 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9409 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9410 Store[ax+1][ay] = element;
9411 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9412 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9413 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9414 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9419 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9420 TEST_DrawLevelField(ax, ay);
9422 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9424 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9425 unten_massiv = TRUE;
9426 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9427 links_massiv = TRUE;
9428 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9429 rechts_massiv = TRUE;
9431 if (((oben_massiv && unten_massiv) ||
9432 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9433 element == EL_EXPANDABLE_WALL) &&
9434 ((links_massiv && rechts_massiv) ||
9435 element == EL_EXPANDABLE_WALL_VERTICAL))
9436 Feld[ax][ay] = EL_WALL;
9439 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9442 static void MauerAblegerStahl(int ax, int ay)
9444 int element = Feld[ax][ay];
9445 int graphic = el2img(element);
9446 boolean oben_frei = FALSE, unten_frei = FALSE;
9447 boolean links_frei = FALSE, rechts_frei = FALSE;
9448 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9449 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9450 boolean new_wall = FALSE;
9452 if (IS_ANIMATED(graphic))
9453 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9455 if (!MovDelay[ax][ay]) // start building new wall
9456 MovDelay[ax][ay] = 6;
9458 if (MovDelay[ax][ay]) // wait some time before building new wall
9461 if (MovDelay[ax][ay])
9465 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9467 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9469 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9471 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9474 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9475 element == EL_EXPANDABLE_STEELWALL_ANY)
9479 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9480 Store[ax][ay-1] = element;
9481 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9482 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9483 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9484 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9489 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9490 Store[ax][ay+1] = element;
9491 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9492 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9493 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9494 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9499 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9500 element == EL_EXPANDABLE_STEELWALL_ANY)
9504 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9505 Store[ax-1][ay] = element;
9506 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9507 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9508 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9509 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9515 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9516 Store[ax+1][ay] = element;
9517 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9518 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9519 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9520 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9525 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9527 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9528 unten_massiv = TRUE;
9529 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9530 links_massiv = TRUE;
9531 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9532 rechts_massiv = TRUE;
9534 if (((oben_massiv && unten_massiv) ||
9535 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9536 ((links_massiv && rechts_massiv) ||
9537 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9538 Feld[ax][ay] = EL_STEELWALL;
9541 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9544 static void CheckForDragon(int x, int y)
9547 boolean dragon_found = FALSE;
9548 static int xy[4][2] =
9556 for (i = 0; i < NUM_DIRECTIONS; i++)
9558 for (j = 0; j < 4; j++)
9560 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9562 if (IN_LEV_FIELD(xx, yy) &&
9563 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9565 if (Feld[xx][yy] == EL_DRAGON)
9566 dragon_found = TRUE;
9575 for (i = 0; i < NUM_DIRECTIONS; i++)
9577 for (j = 0; j < 3; j++)
9579 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9581 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9583 Feld[xx][yy] = EL_EMPTY;
9584 TEST_DrawLevelField(xx, yy);
9593 static void InitBuggyBase(int x, int y)
9595 int element = Feld[x][y];
9596 int activating_delay = FRAMES_PER_SECOND / 4;
9599 (element == EL_SP_BUGGY_BASE ?
9600 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9601 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9603 element == EL_SP_BUGGY_BASE_ACTIVE ?
9604 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9607 static void WarnBuggyBase(int x, int y)
9610 static int xy[4][2] =
9618 for (i = 0; i < NUM_DIRECTIONS; i++)
9620 int xx = x + xy[i][0];
9621 int yy = y + xy[i][1];
9623 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9625 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9632 static void InitTrap(int x, int y)
9634 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9637 static void ActivateTrap(int x, int y)
9639 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9642 static void ChangeActiveTrap(int x, int y)
9644 int graphic = IMG_TRAP_ACTIVE;
9646 // if new animation frame was drawn, correct crumbled sand border
9647 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9648 TEST_DrawLevelFieldCrumbled(x, y);
9651 static int getSpecialActionElement(int element, int number, int base_element)
9653 return (element != EL_EMPTY ? element :
9654 number != -1 ? base_element + number - 1 :
9658 static int getModifiedActionNumber(int value_old, int operator, int operand,
9659 int value_min, int value_max)
9661 int value_new = (operator == CA_MODE_SET ? operand :
9662 operator == CA_MODE_ADD ? value_old + operand :
9663 operator == CA_MODE_SUBTRACT ? value_old - operand :
9664 operator == CA_MODE_MULTIPLY ? value_old * operand :
9665 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9666 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9669 return (value_new < value_min ? value_min :
9670 value_new > value_max ? value_max :
9674 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9676 struct ElementInfo *ei = &element_info[element];
9677 struct ElementChangeInfo *change = &ei->change_page[page];
9678 int target_element = change->target_element;
9679 int action_type = change->action_type;
9680 int action_mode = change->action_mode;
9681 int action_arg = change->action_arg;
9682 int action_element = change->action_element;
9685 if (!change->has_action)
9688 // ---------- determine action paramater values -----------------------------
9690 int level_time_value =
9691 (level.time > 0 ? TimeLeft :
9694 int action_arg_element_raw =
9695 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9696 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9697 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9698 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9699 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9700 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9701 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9703 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9705 int action_arg_direction =
9706 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9707 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9708 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9709 change->actual_trigger_side :
9710 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9711 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9714 int action_arg_number_min =
9715 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9718 int action_arg_number_max =
9719 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9720 action_type == CA_SET_LEVEL_GEMS ? 999 :
9721 action_type == CA_SET_LEVEL_TIME ? 9999 :
9722 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9723 action_type == CA_SET_CE_VALUE ? 9999 :
9724 action_type == CA_SET_CE_SCORE ? 9999 :
9727 int action_arg_number_reset =
9728 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9729 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9730 action_type == CA_SET_LEVEL_TIME ? level.time :
9731 action_type == CA_SET_LEVEL_SCORE ? 0 :
9732 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9733 action_type == CA_SET_CE_SCORE ? 0 :
9736 int action_arg_number =
9737 (action_arg <= CA_ARG_MAX ? action_arg :
9738 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9739 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9740 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9741 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9742 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9743 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9744 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9745 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9746 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9747 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9748 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9749 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9750 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9751 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9752 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9753 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9754 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9755 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9756 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9757 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9758 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9761 int action_arg_number_old =
9762 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9763 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9764 action_type == CA_SET_LEVEL_SCORE ? game.score :
9765 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9766 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9769 int action_arg_number_new =
9770 getModifiedActionNumber(action_arg_number_old,
9771 action_mode, action_arg_number,
9772 action_arg_number_min, action_arg_number_max);
9774 int trigger_player_bits =
9775 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9776 change->actual_trigger_player_bits : change->trigger_player);
9778 int action_arg_player_bits =
9779 (action_arg >= CA_ARG_PLAYER_1 &&
9780 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9781 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9782 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9785 // ---------- execute action -----------------------------------------------
9787 switch (action_type)
9794 // ---------- level actions ----------------------------------------------
9796 case CA_RESTART_LEVEL:
9798 game.restart_level = TRUE;
9803 case CA_SHOW_ENVELOPE:
9805 int element = getSpecialActionElement(action_arg_element,
9806 action_arg_number, EL_ENVELOPE_1);
9808 if (IS_ENVELOPE(element))
9809 local_player->show_envelope = element;
9814 case CA_SET_LEVEL_TIME:
9816 if (level.time > 0) // only modify limited time value
9818 TimeLeft = action_arg_number_new;
9820 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9822 DisplayGameControlValues();
9824 if (!TimeLeft && setup.time_limit)
9825 for (i = 0; i < MAX_PLAYERS; i++)
9826 KillPlayer(&stored_player[i]);
9832 case CA_SET_LEVEL_SCORE:
9834 game.score = action_arg_number_new;
9836 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9838 DisplayGameControlValues();
9843 case CA_SET_LEVEL_GEMS:
9845 game.gems_still_needed = action_arg_number_new;
9847 game.snapshot.collected_item = TRUE;
9849 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9851 DisplayGameControlValues();
9856 case CA_SET_LEVEL_WIND:
9858 game.wind_direction = action_arg_direction;
9863 case CA_SET_LEVEL_RANDOM_SEED:
9865 // ensure that setting a new random seed while playing is predictable
9866 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9871 // ---------- player actions ---------------------------------------------
9873 case CA_MOVE_PLAYER:
9875 // automatically move to the next field in specified direction
9876 for (i = 0; i < MAX_PLAYERS; i++)
9877 if (trigger_player_bits & (1 << i))
9878 stored_player[i].programmed_action = action_arg_direction;
9883 case CA_EXIT_PLAYER:
9885 for (i = 0; i < MAX_PLAYERS; i++)
9886 if (action_arg_player_bits & (1 << i))
9887 ExitPlayer(&stored_player[i]);
9889 if (game.players_still_needed == 0)
9895 case CA_KILL_PLAYER:
9897 for (i = 0; i < MAX_PLAYERS; i++)
9898 if (action_arg_player_bits & (1 << i))
9899 KillPlayer(&stored_player[i]);
9904 case CA_SET_PLAYER_KEYS:
9906 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
9907 int element = getSpecialActionElement(action_arg_element,
9908 action_arg_number, EL_KEY_1);
9910 if (IS_KEY(element))
9912 for (i = 0; i < MAX_PLAYERS; i++)
9914 if (trigger_player_bits & (1 << i))
9916 stored_player[i].key[KEY_NR(element)] = key_state;
9918 DrawGameDoorValues();
9926 case CA_SET_PLAYER_SPEED:
9928 for (i = 0; i < MAX_PLAYERS; i++)
9930 if (trigger_player_bits & (1 << i))
9932 int move_stepsize = TILEX / stored_player[i].move_delay_value;
9934 if (action_arg == CA_ARG_SPEED_FASTER &&
9935 stored_player[i].cannot_move)
9937 action_arg_number = STEPSIZE_VERY_SLOW;
9939 else if (action_arg == CA_ARG_SPEED_SLOWER ||
9940 action_arg == CA_ARG_SPEED_FASTER)
9942 action_arg_number = 2;
9943 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
9946 else if (action_arg == CA_ARG_NUMBER_RESET)
9948 action_arg_number = level.initial_player_stepsize[i];
9952 getModifiedActionNumber(move_stepsize,
9955 action_arg_number_min,
9956 action_arg_number_max);
9958 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
9965 case CA_SET_PLAYER_SHIELD:
9967 for (i = 0; i < MAX_PLAYERS; i++)
9969 if (trigger_player_bits & (1 << i))
9971 if (action_arg == CA_ARG_SHIELD_OFF)
9973 stored_player[i].shield_normal_time_left = 0;
9974 stored_player[i].shield_deadly_time_left = 0;
9976 else if (action_arg == CA_ARG_SHIELD_NORMAL)
9978 stored_player[i].shield_normal_time_left = 999999;
9980 else if (action_arg == CA_ARG_SHIELD_DEADLY)
9982 stored_player[i].shield_normal_time_left = 999999;
9983 stored_player[i].shield_deadly_time_left = 999999;
9991 case CA_SET_PLAYER_GRAVITY:
9993 for (i = 0; i < MAX_PLAYERS; i++)
9995 if (trigger_player_bits & (1 << i))
9997 stored_player[i].gravity =
9998 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
9999 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10000 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10001 stored_player[i].gravity);
10008 case CA_SET_PLAYER_ARTWORK:
10010 for (i = 0; i < MAX_PLAYERS; i++)
10012 if (trigger_player_bits & (1 << i))
10014 int artwork_element = action_arg_element;
10016 if (action_arg == CA_ARG_ELEMENT_RESET)
10018 (level.use_artwork_element[i] ? level.artwork_element[i] :
10019 stored_player[i].element_nr);
10021 if (stored_player[i].artwork_element != artwork_element)
10022 stored_player[i].Frame = 0;
10024 stored_player[i].artwork_element = artwork_element;
10026 SetPlayerWaiting(&stored_player[i], FALSE);
10028 // set number of special actions for bored and sleeping animation
10029 stored_player[i].num_special_action_bored =
10030 get_num_special_action(artwork_element,
10031 ACTION_BORING_1, ACTION_BORING_LAST);
10032 stored_player[i].num_special_action_sleeping =
10033 get_num_special_action(artwork_element,
10034 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10041 case CA_SET_PLAYER_INVENTORY:
10043 for (i = 0; i < MAX_PLAYERS; i++)
10045 struct PlayerInfo *player = &stored_player[i];
10048 if (trigger_player_bits & (1 << i))
10050 int inventory_element = action_arg_element;
10052 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10053 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10054 action_arg == CA_ARG_ELEMENT_ACTION)
10056 int element = inventory_element;
10057 int collect_count = element_info[element].collect_count_initial;
10059 if (!IS_CUSTOM_ELEMENT(element))
10062 if (collect_count == 0)
10063 player->inventory_infinite_element = element;
10065 for (k = 0; k < collect_count; k++)
10066 if (player->inventory_size < MAX_INVENTORY_SIZE)
10067 player->inventory_element[player->inventory_size++] =
10070 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10071 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10072 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10074 if (player->inventory_infinite_element != EL_UNDEFINED &&
10075 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10076 action_arg_element_raw))
10077 player->inventory_infinite_element = EL_UNDEFINED;
10079 for (k = 0, j = 0; j < player->inventory_size; j++)
10081 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10082 action_arg_element_raw))
10083 player->inventory_element[k++] = player->inventory_element[j];
10086 player->inventory_size = k;
10088 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10090 if (player->inventory_size > 0)
10092 for (j = 0; j < player->inventory_size - 1; j++)
10093 player->inventory_element[j] = player->inventory_element[j + 1];
10095 player->inventory_size--;
10098 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10100 if (player->inventory_size > 0)
10101 player->inventory_size--;
10103 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10105 player->inventory_infinite_element = EL_UNDEFINED;
10106 player->inventory_size = 0;
10108 else if (action_arg == CA_ARG_INVENTORY_RESET)
10110 player->inventory_infinite_element = EL_UNDEFINED;
10111 player->inventory_size = 0;
10113 if (level.use_initial_inventory[i])
10115 for (j = 0; j < level.initial_inventory_size[i]; j++)
10117 int element = level.initial_inventory_content[i][j];
10118 int collect_count = element_info[element].collect_count_initial;
10120 if (!IS_CUSTOM_ELEMENT(element))
10123 if (collect_count == 0)
10124 player->inventory_infinite_element = element;
10126 for (k = 0; k < collect_count; k++)
10127 if (player->inventory_size < MAX_INVENTORY_SIZE)
10128 player->inventory_element[player->inventory_size++] =
10139 // ---------- CE actions -------------------------------------------------
10141 case CA_SET_CE_VALUE:
10143 int last_ce_value = CustomValue[x][y];
10145 CustomValue[x][y] = action_arg_number_new;
10147 if (CustomValue[x][y] != last_ce_value)
10149 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10150 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10152 if (CustomValue[x][y] == 0)
10154 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10155 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10162 case CA_SET_CE_SCORE:
10164 int last_ce_score = ei->collect_score;
10166 ei->collect_score = action_arg_number_new;
10168 if (ei->collect_score != last_ce_score)
10170 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10171 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10173 if (ei->collect_score == 0)
10177 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10178 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10181 This is a very special case that seems to be a mixture between
10182 CheckElementChange() and CheckTriggeredElementChange(): while
10183 the first one only affects single elements that are triggered
10184 directly, the second one affects multiple elements in the playfield
10185 that are triggered indirectly by another element. This is a third
10186 case: Changing the CE score always affects multiple identical CEs,
10187 so every affected CE must be checked, not only the single CE for
10188 which the CE score was changed in the first place (as every instance
10189 of that CE shares the same CE score, and therefore also can change)!
10191 SCAN_PLAYFIELD(xx, yy)
10193 if (Feld[xx][yy] == element)
10194 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10195 CE_SCORE_GETS_ZERO);
10203 case CA_SET_CE_ARTWORK:
10205 int artwork_element = action_arg_element;
10206 boolean reset_frame = FALSE;
10209 if (action_arg == CA_ARG_ELEMENT_RESET)
10210 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10213 if (ei->gfx_element != artwork_element)
10214 reset_frame = TRUE;
10216 ei->gfx_element = artwork_element;
10218 SCAN_PLAYFIELD(xx, yy)
10220 if (Feld[xx][yy] == element)
10224 ResetGfxAnimation(xx, yy);
10225 ResetRandomAnimationValue(xx, yy);
10228 TEST_DrawLevelField(xx, yy);
10235 // ---------- engine actions ---------------------------------------------
10237 case CA_SET_ENGINE_SCAN_MODE:
10239 InitPlayfieldScanMode(action_arg);
10249 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10251 int old_element = Feld[x][y];
10252 int new_element = GetElementFromGroupElement(element);
10253 int previous_move_direction = MovDir[x][y];
10254 int last_ce_value = CustomValue[x][y];
10255 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10256 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10257 boolean add_player_onto_element = (new_element_is_player &&
10258 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10259 IS_WALKABLE(old_element));
10261 if (!add_player_onto_element)
10263 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10264 RemoveMovingField(x, y);
10268 Feld[x][y] = new_element;
10270 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10271 MovDir[x][y] = previous_move_direction;
10273 if (element_info[new_element].use_last_ce_value)
10274 CustomValue[x][y] = last_ce_value;
10276 InitField_WithBug1(x, y, FALSE);
10278 new_element = Feld[x][y]; // element may have changed
10280 ResetGfxAnimation(x, y);
10281 ResetRandomAnimationValue(x, y);
10283 TEST_DrawLevelField(x, y);
10285 if (GFX_CRUMBLED(new_element))
10286 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10289 // check if element under the player changes from accessible to unaccessible
10290 // (needed for special case of dropping element which then changes)
10291 // (must be checked after creating new element for walkable group elements)
10292 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10293 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10300 // "ChangeCount" not set yet to allow "entered by player" change one time
10301 if (new_element_is_player)
10302 RelocatePlayer(x, y, new_element);
10305 ChangeCount[x][y]++; // count number of changes in the same frame
10307 TestIfBadThingTouchesPlayer(x, y);
10308 TestIfPlayerTouchesCustomElement(x, y);
10309 TestIfElementTouchesCustomElement(x, y);
10312 static void CreateField(int x, int y, int element)
10314 CreateFieldExt(x, y, element, FALSE);
10317 static void CreateElementFromChange(int x, int y, int element)
10319 element = GET_VALID_RUNTIME_ELEMENT(element);
10321 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10323 int old_element = Feld[x][y];
10325 // prevent changed element from moving in same engine frame
10326 // unless both old and new element can either fall or move
10327 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10328 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10332 CreateFieldExt(x, y, element, TRUE);
10335 static boolean ChangeElement(int x, int y, int element, int page)
10337 struct ElementInfo *ei = &element_info[element];
10338 struct ElementChangeInfo *change = &ei->change_page[page];
10339 int ce_value = CustomValue[x][y];
10340 int ce_score = ei->collect_score;
10341 int target_element;
10342 int old_element = Feld[x][y];
10344 // always use default change event to prevent running into a loop
10345 if (ChangeEvent[x][y] == -1)
10346 ChangeEvent[x][y] = CE_DELAY;
10348 if (ChangeEvent[x][y] == CE_DELAY)
10350 // reset actual trigger element, trigger player and action element
10351 change->actual_trigger_element = EL_EMPTY;
10352 change->actual_trigger_player = EL_EMPTY;
10353 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10354 change->actual_trigger_side = CH_SIDE_NONE;
10355 change->actual_trigger_ce_value = 0;
10356 change->actual_trigger_ce_score = 0;
10359 // do not change elements more than a specified maximum number of changes
10360 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10363 ChangeCount[x][y]++; // count number of changes in the same frame
10365 if (change->explode)
10372 if (change->use_target_content)
10374 boolean complete_replace = TRUE;
10375 boolean can_replace[3][3];
10378 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10381 boolean is_walkable;
10382 boolean is_diggable;
10383 boolean is_collectible;
10384 boolean is_removable;
10385 boolean is_destructible;
10386 int ex = x + xx - 1;
10387 int ey = y + yy - 1;
10388 int content_element = change->target_content.e[xx][yy];
10391 can_replace[xx][yy] = TRUE;
10393 if (ex == x && ey == y) // do not check changing element itself
10396 if (content_element == EL_EMPTY_SPACE)
10398 can_replace[xx][yy] = FALSE; // do not replace border with space
10403 if (!IN_LEV_FIELD(ex, ey))
10405 can_replace[xx][yy] = FALSE;
10406 complete_replace = FALSE;
10413 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10414 e = MovingOrBlocked2Element(ex, ey);
10416 is_empty = (IS_FREE(ex, ey) ||
10417 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10419 is_walkable = (is_empty || IS_WALKABLE(e));
10420 is_diggable = (is_empty || IS_DIGGABLE(e));
10421 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10422 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10423 is_removable = (is_diggable || is_collectible);
10425 can_replace[xx][yy] =
10426 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10427 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10428 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10429 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10430 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10431 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10432 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10434 if (!can_replace[xx][yy])
10435 complete_replace = FALSE;
10438 if (!change->only_if_complete || complete_replace)
10440 boolean something_has_changed = FALSE;
10442 if (change->only_if_complete && change->use_random_replace &&
10443 RND(100) < change->random_percentage)
10446 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10448 int ex = x + xx - 1;
10449 int ey = y + yy - 1;
10450 int content_element;
10452 if (can_replace[xx][yy] && (!change->use_random_replace ||
10453 RND(100) < change->random_percentage))
10455 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10456 RemoveMovingField(ex, ey);
10458 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10460 content_element = change->target_content.e[xx][yy];
10461 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10462 ce_value, ce_score);
10464 CreateElementFromChange(ex, ey, target_element);
10466 something_has_changed = TRUE;
10468 // for symmetry reasons, freeze newly created border elements
10469 if (ex != x || ey != y)
10470 Stop[ex][ey] = TRUE; // no more moving in this frame
10474 if (something_has_changed)
10476 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10477 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10483 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10484 ce_value, ce_score);
10486 if (element == EL_DIAGONAL_GROWING ||
10487 element == EL_DIAGONAL_SHRINKING)
10489 target_element = Store[x][y];
10491 Store[x][y] = EL_EMPTY;
10494 CreateElementFromChange(x, y, target_element);
10496 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10497 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10500 // this uses direct change before indirect change
10501 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10506 static void HandleElementChange(int x, int y, int page)
10508 int element = MovingOrBlocked2Element(x, y);
10509 struct ElementInfo *ei = &element_info[element];
10510 struct ElementChangeInfo *change = &ei->change_page[page];
10511 boolean handle_action_before_change = FALSE;
10514 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10515 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10518 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10519 x, y, element, element_info[element].token_name);
10520 printf("HandleElementChange(): This should never happen!\n");
10525 // this can happen with classic bombs on walkable, changing elements
10526 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10531 if (ChangeDelay[x][y] == 0) // initialize element change
10533 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10535 if (change->can_change)
10537 // !!! not clear why graphic animation should be reset at all here !!!
10538 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10539 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10542 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10544 When using an animation frame delay of 1 (this only happens with
10545 "sp_zonk.moving.left/right" in the classic graphics), the default
10546 (non-moving) animation shows wrong animation frames (while the
10547 moving animation, like "sp_zonk.moving.left/right", is correct,
10548 so this graphical bug never shows up with the classic graphics).
10549 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10550 be drawn instead of the correct frames 0,1,2,3. This is caused by
10551 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10552 an element change: First when the change delay ("ChangeDelay[][]")
10553 counter has reached zero after decrementing, then a second time in
10554 the next frame (after "GfxFrame[][]" was already incremented) when
10555 "ChangeDelay[][]" is reset to the initial delay value again.
10557 This causes frame 0 to be drawn twice, while the last frame won't
10558 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10560 As some animations may already be cleverly designed around this bug
10561 (at least the "Snake Bite" snake tail animation does this), it cannot
10562 simply be fixed here without breaking such existing animations.
10563 Unfortunately, it cannot easily be detected if a graphics set was
10564 designed "before" or "after" the bug was fixed. As a workaround,
10565 a new graphics set option "game.graphics_engine_version" was added
10566 to be able to specify the game's major release version for which the
10567 graphics set was designed, which can then be used to decide if the
10568 bugfix should be used (version 4 and above) or not (version 3 or
10569 below, or if no version was specified at all, as with old sets).
10571 (The wrong/fixed animation frames can be tested with the test level set
10572 "test_gfxframe" and level "000", which contains a specially prepared
10573 custom element at level position (x/y) == (11/9) which uses the zonk
10574 animation mentioned above. Using "game.graphics_engine_version: 4"
10575 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10576 This can also be seen from the debug output for this test element.)
10579 // when a custom element is about to change (for example by change delay),
10580 // do not reset graphic animation when the custom element is moving
10581 if (game.graphics_engine_version < 4 &&
10584 ResetGfxAnimation(x, y);
10585 ResetRandomAnimationValue(x, y);
10588 if (change->pre_change_function)
10589 change->pre_change_function(x, y);
10593 ChangeDelay[x][y]--;
10595 if (ChangeDelay[x][y] != 0) // continue element change
10597 if (change->can_change)
10599 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10601 if (IS_ANIMATED(graphic))
10602 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10604 if (change->change_function)
10605 change->change_function(x, y);
10608 else // finish element change
10610 if (ChangePage[x][y] != -1) // remember page from delayed change
10612 page = ChangePage[x][y];
10613 ChangePage[x][y] = -1;
10615 change = &ei->change_page[page];
10618 if (IS_MOVING(x, y)) // never change a running system ;-)
10620 ChangeDelay[x][y] = 1; // try change after next move step
10621 ChangePage[x][y] = page; // remember page to use for change
10626 // special case: set new level random seed before changing element
10627 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10628 handle_action_before_change = TRUE;
10630 if (change->has_action && handle_action_before_change)
10631 ExecuteCustomElementAction(x, y, element, page);
10633 if (change->can_change)
10635 if (ChangeElement(x, y, element, page))
10637 if (change->post_change_function)
10638 change->post_change_function(x, y);
10642 if (change->has_action && !handle_action_before_change)
10643 ExecuteCustomElementAction(x, y, element, page);
10647 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10648 int trigger_element,
10650 int trigger_player,
10654 boolean change_done_any = FALSE;
10655 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10658 if (!(trigger_events[trigger_element][trigger_event]))
10661 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10663 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10665 int element = EL_CUSTOM_START + i;
10666 boolean change_done = FALSE;
10669 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10670 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10673 for (p = 0; p < element_info[element].num_change_pages; p++)
10675 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10677 if (change->can_change_or_has_action &&
10678 change->has_event[trigger_event] &&
10679 change->trigger_side & trigger_side &&
10680 change->trigger_player & trigger_player &&
10681 change->trigger_page & trigger_page_bits &&
10682 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10684 change->actual_trigger_element = trigger_element;
10685 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10686 change->actual_trigger_player_bits = trigger_player;
10687 change->actual_trigger_side = trigger_side;
10688 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10689 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10691 if ((change->can_change && !change_done) || change->has_action)
10695 SCAN_PLAYFIELD(x, y)
10697 if (Feld[x][y] == element)
10699 if (change->can_change && !change_done)
10701 // if element already changed in this frame, not only prevent
10702 // another element change (checked in ChangeElement()), but
10703 // also prevent additional element actions for this element
10705 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10706 !level.use_action_after_change_bug)
10709 ChangeDelay[x][y] = 1;
10710 ChangeEvent[x][y] = trigger_event;
10712 HandleElementChange(x, y, p);
10714 else if (change->has_action)
10716 // if element already changed in this frame, not only prevent
10717 // another element change (checked in ChangeElement()), but
10718 // also prevent additional element actions for this element
10720 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10721 !level.use_action_after_change_bug)
10724 ExecuteCustomElementAction(x, y, element, p);
10725 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10730 if (change->can_change)
10732 change_done = TRUE;
10733 change_done_any = TRUE;
10740 RECURSION_LOOP_DETECTION_END();
10742 return change_done_any;
10745 static boolean CheckElementChangeExt(int x, int y,
10747 int trigger_element,
10749 int trigger_player,
10752 boolean change_done = FALSE;
10755 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10756 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10759 if (Feld[x][y] == EL_BLOCKED)
10761 Blocked2Moving(x, y, &x, &y);
10762 element = Feld[x][y];
10765 // check if element has already changed or is about to change after moving
10766 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10767 Feld[x][y] != element) ||
10769 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10770 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10771 ChangePage[x][y] != -1)))
10774 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10776 for (p = 0; p < element_info[element].num_change_pages; p++)
10778 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10780 /* check trigger element for all events where the element that is checked
10781 for changing interacts with a directly adjacent element -- this is
10782 different to element changes that affect other elements to change on the
10783 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10784 boolean check_trigger_element =
10785 (trigger_event == CE_TOUCHING_X ||
10786 trigger_event == CE_HITTING_X ||
10787 trigger_event == CE_HIT_BY_X ||
10788 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10790 if (change->can_change_or_has_action &&
10791 change->has_event[trigger_event] &&
10792 change->trigger_side & trigger_side &&
10793 change->trigger_player & trigger_player &&
10794 (!check_trigger_element ||
10795 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10797 change->actual_trigger_element = trigger_element;
10798 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10799 change->actual_trigger_player_bits = trigger_player;
10800 change->actual_trigger_side = trigger_side;
10801 change->actual_trigger_ce_value = CustomValue[x][y];
10802 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10804 // special case: trigger element not at (x,y) position for some events
10805 if (check_trigger_element)
10817 { 0, 0 }, { 0, 0 }, { 0, 0 },
10821 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10822 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10824 change->actual_trigger_ce_value = CustomValue[xx][yy];
10825 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10828 if (change->can_change && !change_done)
10830 ChangeDelay[x][y] = 1;
10831 ChangeEvent[x][y] = trigger_event;
10833 HandleElementChange(x, y, p);
10835 change_done = TRUE;
10837 else if (change->has_action)
10839 ExecuteCustomElementAction(x, y, element, p);
10840 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10845 RECURSION_LOOP_DETECTION_END();
10847 return change_done;
10850 static void PlayPlayerSound(struct PlayerInfo *player)
10852 int jx = player->jx, jy = player->jy;
10853 int sound_element = player->artwork_element;
10854 int last_action = player->last_action_waiting;
10855 int action = player->action_waiting;
10857 if (player->is_waiting)
10859 if (action != last_action)
10860 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10862 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10866 if (action != last_action)
10867 StopSound(element_info[sound_element].sound[last_action]);
10869 if (last_action == ACTION_SLEEPING)
10870 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
10874 static void PlayAllPlayersSound(void)
10878 for (i = 0; i < MAX_PLAYERS; i++)
10879 if (stored_player[i].active)
10880 PlayPlayerSound(&stored_player[i]);
10883 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
10885 boolean last_waiting = player->is_waiting;
10886 int move_dir = player->MovDir;
10888 player->dir_waiting = move_dir;
10889 player->last_action_waiting = player->action_waiting;
10893 if (!last_waiting) // not waiting -> waiting
10895 player->is_waiting = TRUE;
10897 player->frame_counter_bored =
10899 game.player_boring_delay_fixed +
10900 GetSimpleRandom(game.player_boring_delay_random);
10901 player->frame_counter_sleeping =
10903 game.player_sleeping_delay_fixed +
10904 GetSimpleRandom(game.player_sleeping_delay_random);
10906 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
10909 if (game.player_sleeping_delay_fixed +
10910 game.player_sleeping_delay_random > 0 &&
10911 player->anim_delay_counter == 0 &&
10912 player->post_delay_counter == 0 &&
10913 FrameCounter >= player->frame_counter_sleeping)
10914 player->is_sleeping = TRUE;
10915 else if (game.player_boring_delay_fixed +
10916 game.player_boring_delay_random > 0 &&
10917 FrameCounter >= player->frame_counter_bored)
10918 player->is_bored = TRUE;
10920 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
10921 player->is_bored ? ACTION_BORING :
10924 if (player->is_sleeping && player->use_murphy)
10926 // special case for sleeping Murphy when leaning against non-free tile
10928 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
10929 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
10930 !IS_MOVING(player->jx - 1, player->jy)))
10931 move_dir = MV_LEFT;
10932 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
10933 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
10934 !IS_MOVING(player->jx + 1, player->jy)))
10935 move_dir = MV_RIGHT;
10937 player->is_sleeping = FALSE;
10939 player->dir_waiting = move_dir;
10942 if (player->is_sleeping)
10944 if (player->num_special_action_sleeping > 0)
10946 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10948 int last_special_action = player->special_action_sleeping;
10949 int num_special_action = player->num_special_action_sleeping;
10950 int special_action =
10951 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
10952 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
10953 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
10954 last_special_action + 1 : ACTION_SLEEPING);
10955 int special_graphic =
10956 el_act_dir2img(player->artwork_element, special_action, move_dir);
10958 player->anim_delay_counter =
10959 graphic_info[special_graphic].anim_delay_fixed +
10960 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10961 player->post_delay_counter =
10962 graphic_info[special_graphic].post_delay_fixed +
10963 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10965 player->special_action_sleeping = special_action;
10968 if (player->anim_delay_counter > 0)
10970 player->action_waiting = player->special_action_sleeping;
10971 player->anim_delay_counter--;
10973 else if (player->post_delay_counter > 0)
10975 player->post_delay_counter--;
10979 else if (player->is_bored)
10981 if (player->num_special_action_bored > 0)
10983 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10985 int special_action =
10986 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
10987 int special_graphic =
10988 el_act_dir2img(player->artwork_element, special_action, move_dir);
10990 player->anim_delay_counter =
10991 graphic_info[special_graphic].anim_delay_fixed +
10992 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10993 player->post_delay_counter =
10994 graphic_info[special_graphic].post_delay_fixed +
10995 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10997 player->special_action_bored = special_action;
11000 if (player->anim_delay_counter > 0)
11002 player->action_waiting = player->special_action_bored;
11003 player->anim_delay_counter--;
11005 else if (player->post_delay_counter > 0)
11007 player->post_delay_counter--;
11012 else if (last_waiting) // waiting -> not waiting
11014 player->is_waiting = FALSE;
11015 player->is_bored = FALSE;
11016 player->is_sleeping = FALSE;
11018 player->frame_counter_bored = -1;
11019 player->frame_counter_sleeping = -1;
11021 player->anim_delay_counter = 0;
11022 player->post_delay_counter = 0;
11024 player->dir_waiting = player->MovDir;
11025 player->action_waiting = ACTION_DEFAULT;
11027 player->special_action_bored = ACTION_DEFAULT;
11028 player->special_action_sleeping = ACTION_DEFAULT;
11032 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11034 if ((!player->is_moving && player->was_moving) ||
11035 (player->MovPos == 0 && player->was_moving) ||
11036 (player->is_snapping && !player->was_snapping) ||
11037 (player->is_dropping && !player->was_dropping))
11039 if (!CheckSaveEngineSnapshotToList())
11042 player->was_moving = FALSE;
11043 player->was_snapping = TRUE;
11044 player->was_dropping = TRUE;
11048 if (player->is_moving)
11049 player->was_moving = TRUE;
11051 if (!player->is_snapping)
11052 player->was_snapping = FALSE;
11054 if (!player->is_dropping)
11055 player->was_dropping = FALSE;
11059 static void CheckSingleStepMode(struct PlayerInfo *player)
11061 if (tape.single_step && tape.recording && !tape.pausing)
11063 /* as it is called "single step mode", just return to pause mode when the
11064 player stopped moving after one tile (or never starts moving at all) */
11065 if (!player->is_moving &&
11066 !player->is_pushing &&
11067 !player->is_dropping_pressed)
11068 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11071 CheckSaveEngineSnapshot(player);
11074 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11076 int left = player_action & JOY_LEFT;
11077 int right = player_action & JOY_RIGHT;
11078 int up = player_action & JOY_UP;
11079 int down = player_action & JOY_DOWN;
11080 int button1 = player_action & JOY_BUTTON_1;
11081 int button2 = player_action & JOY_BUTTON_2;
11082 int dx = (left ? -1 : right ? 1 : 0);
11083 int dy = (up ? -1 : down ? 1 : 0);
11085 if (!player->active || tape.pausing)
11091 SnapField(player, dx, dy);
11095 DropElement(player);
11097 MovePlayer(player, dx, dy);
11100 CheckSingleStepMode(player);
11102 SetPlayerWaiting(player, FALSE);
11104 return player_action;
11108 // no actions for this player (no input at player's configured device)
11110 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11111 SnapField(player, 0, 0);
11112 CheckGravityMovementWhenNotMoving(player);
11114 if (player->MovPos == 0)
11115 SetPlayerWaiting(player, TRUE);
11117 if (player->MovPos == 0) // needed for tape.playing
11118 player->is_moving = FALSE;
11120 player->is_dropping = FALSE;
11121 player->is_dropping_pressed = FALSE;
11122 player->drop_pressed_delay = 0;
11124 CheckSingleStepMode(player);
11130 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11133 if (!tape.use_mouse)
11136 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11137 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11138 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11141 static void SetTapeActionFromMouseAction(byte *tape_action,
11142 struct MouseActionInfo *mouse_action)
11144 if (!tape.use_mouse)
11147 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11148 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11149 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11152 static void CheckLevelSolved(void)
11154 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11156 if (game_em.level_solved &&
11157 !game_em.game_over) // game won
11161 game_em.game_over = TRUE;
11163 game.all_players_gone = TRUE;
11166 if (game_em.game_over) // game lost
11167 game.all_players_gone = TRUE;
11169 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11171 if (game_sp.level_solved &&
11172 !game_sp.game_over) // game won
11176 game_sp.game_over = TRUE;
11178 game.all_players_gone = TRUE;
11181 if (game_sp.game_over) // game lost
11182 game.all_players_gone = TRUE;
11184 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11186 if (game_mm.level_solved &&
11187 !game_mm.game_over) // game won
11191 game_mm.game_over = TRUE;
11193 game.all_players_gone = TRUE;
11196 if (game_mm.game_over) // game lost
11197 game.all_players_gone = TRUE;
11201 static void CheckLevelTime(void)
11205 if (TimeFrames >= FRAMES_PER_SECOND)
11210 for (i = 0; i < MAX_PLAYERS; i++)
11212 struct PlayerInfo *player = &stored_player[i];
11214 if (SHIELD_ON(player))
11216 player->shield_normal_time_left--;
11218 if (player->shield_deadly_time_left > 0)
11219 player->shield_deadly_time_left--;
11223 if (!game.LevelSolved && !level.use_step_counter)
11231 if (TimeLeft <= 10 && setup.time_limit)
11232 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11234 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11235 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11237 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11239 if (!TimeLeft && setup.time_limit)
11241 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11242 level.native_em_level->lev->killed_out_of_time = TRUE;
11244 for (i = 0; i < MAX_PLAYERS; i++)
11245 KillPlayer(&stored_player[i]);
11248 else if (game.no_time_limit && !game.all_players_gone)
11250 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11253 level.native_em_level->lev->time =
11254 (game.no_time_limit ? TimePlayed : TimeLeft);
11257 if (tape.recording || tape.playing)
11258 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11261 if (tape.recording || tape.playing)
11262 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11264 UpdateAndDisplayGameControlValues();
11267 void AdvanceFrameAndPlayerCounters(int player_nr)
11271 // advance frame counters (global frame counter and time frame counter)
11275 // advance player counters (counters for move delay, move animation etc.)
11276 for (i = 0; i < MAX_PLAYERS; i++)
11278 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11279 int move_delay_value = stored_player[i].move_delay_value;
11280 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11282 if (!advance_player_counters) // not all players may be affected
11285 if (move_frames == 0) // less than one move per game frame
11287 int stepsize = TILEX / move_delay_value;
11288 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11289 int count = (stored_player[i].is_moving ?
11290 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11292 if (count % delay == 0)
11296 stored_player[i].Frame += move_frames;
11298 if (stored_player[i].MovPos != 0)
11299 stored_player[i].StepFrame += move_frames;
11301 if (stored_player[i].move_delay > 0)
11302 stored_player[i].move_delay--;
11304 // due to bugs in previous versions, counter must count up, not down
11305 if (stored_player[i].push_delay != -1)
11306 stored_player[i].push_delay++;
11308 if (stored_player[i].drop_delay > 0)
11309 stored_player[i].drop_delay--;
11311 if (stored_player[i].is_dropping_pressed)
11312 stored_player[i].drop_pressed_delay++;
11316 void StartGameActions(boolean init_network_game, boolean record_tape,
11319 unsigned int new_random_seed = InitRND(random_seed);
11322 TapeStartRecording(new_random_seed);
11324 if (init_network_game)
11326 SendToServer_LevelFile();
11327 SendToServer_StartPlaying();
11335 static void GameActionsExt(void)
11338 static unsigned int game_frame_delay = 0;
11340 unsigned int game_frame_delay_value;
11341 byte *recorded_player_action;
11342 byte summarized_player_action = 0;
11343 byte tape_action[MAX_PLAYERS];
11346 // detect endless loops, caused by custom element programming
11347 if (recursion_loop_detected && recursion_loop_depth == 0)
11349 char *message = getStringCat3("Internal Error! Element ",
11350 EL_NAME(recursion_loop_element),
11351 " caused endless loop! Quit the game?");
11353 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11354 EL_NAME(recursion_loop_element));
11356 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11358 recursion_loop_detected = FALSE; // if game should be continued
11365 if (game.restart_level)
11366 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11368 CheckLevelSolved();
11370 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11373 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11376 if (game_status != GAME_MODE_PLAYING) // status might have changed
11379 game_frame_delay_value =
11380 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11382 if (tape.playing && tape.warp_forward && !tape.pausing)
11383 game_frame_delay_value = 0;
11385 SetVideoFrameDelay(game_frame_delay_value);
11387 // (de)activate virtual buttons depending on current game status
11388 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11390 if (game.all_players_gone) // if no players there to be controlled anymore
11391 SetOverlayActive(FALSE);
11392 else if (!tape.playing) // if game continues after tape stopped playing
11393 SetOverlayActive(TRUE);
11398 // ---------- main game synchronization point ----------
11400 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11402 printf("::: skip == %d\n", skip);
11405 // ---------- main game synchronization point ----------
11407 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11411 if (network_playing && !network_player_action_received)
11413 // try to get network player actions in time
11415 // last chance to get network player actions without main loop delay
11416 HandleNetworking();
11418 // game was quit by network peer
11419 if (game_status != GAME_MODE_PLAYING)
11422 // check if network player actions still missing and game still running
11423 if (!network_player_action_received && !checkGameEnded())
11424 return; // failed to get network player actions in time
11426 // do not yet reset "network_player_action_received" (for tape.pausing)
11432 // at this point we know that we really continue executing the game
11434 network_player_action_received = FALSE;
11436 // when playing tape, read previously recorded player input from tape data
11437 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11439 local_player->effective_mouse_action = local_player->mouse_action;
11441 if (recorded_player_action != NULL)
11442 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11443 recorded_player_action);
11445 // TapePlayAction() may return NULL when toggling to "pause before death"
11449 if (tape.set_centered_player)
11451 game.centered_player_nr_next = tape.centered_player_nr_next;
11452 game.set_centered_player = TRUE;
11455 for (i = 0; i < MAX_PLAYERS; i++)
11457 summarized_player_action |= stored_player[i].action;
11459 if (!network_playing && (game.team_mode || tape.playing))
11460 stored_player[i].effective_action = stored_player[i].action;
11463 if (network_playing && !checkGameEnded())
11464 SendToServer_MovePlayer(summarized_player_action);
11466 // summarize all actions at local players mapped input device position
11467 // (this allows using different input devices in single player mode)
11468 if (!network.enabled && !game.team_mode)
11469 stored_player[map_player_action[local_player->index_nr]].effective_action =
11470 summarized_player_action;
11472 // summarize all actions at centered player in local team mode
11473 if (tape.recording &&
11474 setup.team_mode && !network.enabled &&
11475 setup.input_on_focus &&
11476 game.centered_player_nr != -1)
11478 for (i = 0; i < MAX_PLAYERS; i++)
11479 stored_player[map_player_action[i]].effective_action =
11480 (i == game.centered_player_nr ? summarized_player_action : 0);
11483 if (recorded_player_action != NULL)
11484 for (i = 0; i < MAX_PLAYERS; i++)
11485 stored_player[i].effective_action = recorded_player_action[i];
11487 for (i = 0; i < MAX_PLAYERS; i++)
11489 tape_action[i] = stored_player[i].effective_action;
11491 /* (this may happen in the RND game engine if a player was not present on
11492 the playfield on level start, but appeared later from a custom element */
11493 if (setup.team_mode &&
11496 !tape.player_participates[i])
11497 tape.player_participates[i] = TRUE;
11500 SetTapeActionFromMouseAction(tape_action,
11501 &local_player->effective_mouse_action);
11503 // only record actions from input devices, but not programmed actions
11504 if (tape.recording)
11505 TapeRecordAction(tape_action);
11507 // remember if game was played (especially after tape stopped playing)
11508 if (!tape.playing && summarized_player_action)
11509 game.GamePlayed = TRUE;
11511 #if USE_NEW_PLAYER_ASSIGNMENTS
11512 // !!! also map player actions in single player mode !!!
11513 // if (game.team_mode)
11516 byte mapped_action[MAX_PLAYERS];
11518 #if DEBUG_PLAYER_ACTIONS
11520 for (i = 0; i < MAX_PLAYERS; i++)
11521 printf(" %d, ", stored_player[i].effective_action);
11524 for (i = 0; i < MAX_PLAYERS; i++)
11525 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11527 for (i = 0; i < MAX_PLAYERS; i++)
11528 stored_player[i].effective_action = mapped_action[i];
11530 #if DEBUG_PLAYER_ACTIONS
11532 for (i = 0; i < MAX_PLAYERS; i++)
11533 printf(" %d, ", stored_player[i].effective_action);
11537 #if DEBUG_PLAYER_ACTIONS
11541 for (i = 0; i < MAX_PLAYERS; i++)
11542 printf(" %d, ", stored_player[i].effective_action);
11548 for (i = 0; i < MAX_PLAYERS; i++)
11550 // allow engine snapshot in case of changed movement attempt
11551 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11552 (stored_player[i].effective_action & KEY_MOTION))
11553 game.snapshot.changed_action = TRUE;
11555 // allow engine snapshot in case of snapping/dropping attempt
11556 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11557 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11558 game.snapshot.changed_action = TRUE;
11560 game.snapshot.last_action[i] = stored_player[i].effective_action;
11563 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11565 GameActions_EM_Main();
11567 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11569 GameActions_SP_Main();
11571 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11573 GameActions_MM_Main();
11577 GameActions_RND_Main();
11580 BlitScreenToBitmap(backbuffer);
11582 CheckLevelSolved();
11585 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11587 if (global.show_frames_per_second)
11589 static unsigned int fps_counter = 0;
11590 static int fps_frames = 0;
11591 unsigned int fps_delay_ms = Counter() - fps_counter;
11595 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11597 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11600 fps_counter = Counter();
11602 // always draw FPS to screen after FPS value was updated
11603 redraw_mask |= REDRAW_FPS;
11606 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11607 if (GetDrawDeactivationMask() == REDRAW_NONE)
11608 redraw_mask |= REDRAW_FPS;
11612 static void GameActions_CheckSaveEngineSnapshot(void)
11614 if (!game.snapshot.save_snapshot)
11617 // clear flag for saving snapshot _before_ saving snapshot
11618 game.snapshot.save_snapshot = FALSE;
11620 SaveEngineSnapshotToList();
11623 void GameActions(void)
11627 GameActions_CheckSaveEngineSnapshot();
11630 void GameActions_EM_Main(void)
11632 byte effective_action[MAX_PLAYERS];
11633 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11636 for (i = 0; i < MAX_PLAYERS; i++)
11637 effective_action[i] = stored_player[i].effective_action;
11639 GameActions_EM(effective_action, warp_mode);
11642 void GameActions_SP_Main(void)
11644 byte effective_action[MAX_PLAYERS];
11645 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11648 for (i = 0; i < MAX_PLAYERS; i++)
11649 effective_action[i] = stored_player[i].effective_action;
11651 GameActions_SP(effective_action, warp_mode);
11653 for (i = 0; i < MAX_PLAYERS; i++)
11655 if (stored_player[i].force_dropping)
11656 stored_player[i].action |= KEY_BUTTON_DROP;
11658 stored_player[i].force_dropping = FALSE;
11662 void GameActions_MM_Main(void)
11664 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11666 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11669 void GameActions_RND_Main(void)
11674 void GameActions_RND(void)
11676 int magic_wall_x = 0, magic_wall_y = 0;
11677 int i, x, y, element, graphic, last_gfx_frame;
11679 InitPlayfieldScanModeVars();
11681 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11683 SCAN_PLAYFIELD(x, y)
11685 ChangeCount[x][y] = 0;
11686 ChangeEvent[x][y] = -1;
11690 if (game.set_centered_player)
11692 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11694 // switching to "all players" only possible if all players fit to screen
11695 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11697 game.centered_player_nr_next = game.centered_player_nr;
11698 game.set_centered_player = FALSE;
11701 // do not switch focus to non-existing (or non-active) player
11702 if (game.centered_player_nr_next >= 0 &&
11703 !stored_player[game.centered_player_nr_next].active)
11705 game.centered_player_nr_next = game.centered_player_nr;
11706 game.set_centered_player = FALSE;
11710 if (game.set_centered_player &&
11711 ScreenMovPos == 0) // screen currently aligned at tile position
11715 if (game.centered_player_nr_next == -1)
11717 setScreenCenteredToAllPlayers(&sx, &sy);
11721 sx = stored_player[game.centered_player_nr_next].jx;
11722 sy = stored_player[game.centered_player_nr_next].jy;
11725 game.centered_player_nr = game.centered_player_nr_next;
11726 game.set_centered_player = FALSE;
11728 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11729 DrawGameDoorValues();
11732 for (i = 0; i < MAX_PLAYERS; i++)
11734 int actual_player_action = stored_player[i].effective_action;
11737 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11738 - rnd_equinox_tetrachloride 048
11739 - rnd_equinox_tetrachloride_ii 096
11740 - rnd_emanuel_schmieg 002
11741 - doctor_sloan_ww 001, 020
11743 if (stored_player[i].MovPos == 0)
11744 CheckGravityMovement(&stored_player[i]);
11747 // overwrite programmed action with tape action
11748 if (stored_player[i].programmed_action)
11749 actual_player_action = stored_player[i].programmed_action;
11751 PlayerActions(&stored_player[i], actual_player_action);
11753 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11756 ScrollScreen(NULL, SCROLL_GO_ON);
11758 /* for backwards compatibility, the following code emulates a fixed bug that
11759 occured when pushing elements (causing elements that just made their last
11760 pushing step to already (if possible) make their first falling step in the
11761 same game frame, which is bad); this code is also needed to use the famous
11762 "spring push bug" which is used in older levels and might be wanted to be
11763 used also in newer levels, but in this case the buggy pushing code is only
11764 affecting the "spring" element and no other elements */
11766 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11768 for (i = 0; i < MAX_PLAYERS; i++)
11770 struct PlayerInfo *player = &stored_player[i];
11771 int x = player->jx;
11772 int y = player->jy;
11774 if (player->active && player->is_pushing && player->is_moving &&
11776 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11777 Feld[x][y] == EL_SPRING))
11779 ContinueMoving(x, y);
11781 // continue moving after pushing (this is actually a bug)
11782 if (!IS_MOVING(x, y))
11783 Stop[x][y] = FALSE;
11788 SCAN_PLAYFIELD(x, y)
11790 Last[x][y] = Feld[x][y];
11792 ChangeCount[x][y] = 0;
11793 ChangeEvent[x][y] = -1;
11795 // this must be handled before main playfield loop
11796 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11799 if (MovDelay[x][y] <= 0)
11803 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11806 if (MovDelay[x][y] <= 0)
11809 TEST_DrawLevelField(x, y);
11811 TestIfElementTouchesCustomElement(x, y); // for empty space
11816 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11818 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11819 printf("GameActions(): This should never happen!\n");
11821 ChangePage[x][y] = -1;
11825 Stop[x][y] = FALSE;
11826 if (WasJustMoving[x][y] > 0)
11827 WasJustMoving[x][y]--;
11828 if (WasJustFalling[x][y] > 0)
11829 WasJustFalling[x][y]--;
11830 if (CheckCollision[x][y] > 0)
11831 CheckCollision[x][y]--;
11832 if (CheckImpact[x][y] > 0)
11833 CheckImpact[x][y]--;
11837 /* reset finished pushing action (not done in ContinueMoving() to allow
11838 continuous pushing animation for elements with zero push delay) */
11839 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11841 ResetGfxAnimation(x, y);
11842 TEST_DrawLevelField(x, y);
11846 if (IS_BLOCKED(x, y))
11850 Blocked2Moving(x, y, &oldx, &oldy);
11851 if (!IS_MOVING(oldx, oldy))
11853 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11854 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11855 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11856 printf("GameActions(): This should never happen!\n");
11862 SCAN_PLAYFIELD(x, y)
11864 element = Feld[x][y];
11865 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11866 last_gfx_frame = GfxFrame[x][y];
11868 ResetGfxFrame(x, y);
11870 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
11871 DrawLevelGraphicAnimation(x, y, graphic);
11873 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
11874 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
11875 ResetRandomAnimationValue(x, y);
11877 SetRandomAnimationValue(x, y);
11879 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
11881 if (IS_INACTIVE(element))
11883 if (IS_ANIMATED(graphic))
11884 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11889 // this may take place after moving, so 'element' may have changed
11890 if (IS_CHANGING(x, y) &&
11891 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
11893 int page = element_info[element].event_page_nr[CE_DELAY];
11895 HandleElementChange(x, y, page);
11897 element = Feld[x][y];
11898 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11901 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
11905 element = Feld[x][y];
11906 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11908 if (IS_ANIMATED(graphic) &&
11909 !IS_MOVING(x, y) &&
11911 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11913 if (IS_GEM(element) || element == EL_SP_INFOTRON)
11914 TEST_DrawTwinkleOnField(x, y);
11916 else if (element == EL_ACID)
11919 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11921 else if ((element == EL_EXIT_OPEN ||
11922 element == EL_EM_EXIT_OPEN ||
11923 element == EL_SP_EXIT_OPEN ||
11924 element == EL_STEEL_EXIT_OPEN ||
11925 element == EL_EM_STEEL_EXIT_OPEN ||
11926 element == EL_SP_TERMINAL ||
11927 element == EL_SP_TERMINAL_ACTIVE ||
11928 element == EL_EXTRA_TIME ||
11929 element == EL_SHIELD_NORMAL ||
11930 element == EL_SHIELD_DEADLY) &&
11931 IS_ANIMATED(graphic))
11932 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11933 else if (IS_MOVING(x, y))
11934 ContinueMoving(x, y);
11935 else if (IS_ACTIVE_BOMB(element))
11936 CheckDynamite(x, y);
11937 else if (element == EL_AMOEBA_GROWING)
11938 AmoebeWaechst(x, y);
11939 else if (element == EL_AMOEBA_SHRINKING)
11940 AmoebaDisappearing(x, y);
11942 #if !USE_NEW_AMOEBA_CODE
11943 else if (IS_AMOEBALIVE(element))
11944 AmoebeAbleger(x, y);
11947 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
11949 else if (element == EL_EXIT_CLOSED)
11951 else if (element == EL_EM_EXIT_CLOSED)
11953 else if (element == EL_STEEL_EXIT_CLOSED)
11954 CheckExitSteel(x, y);
11955 else if (element == EL_EM_STEEL_EXIT_CLOSED)
11956 CheckExitSteelEM(x, y);
11957 else if (element == EL_SP_EXIT_CLOSED)
11959 else if (element == EL_EXPANDABLE_WALL_GROWING ||
11960 element == EL_EXPANDABLE_STEELWALL_GROWING)
11961 MauerWaechst(x, y);
11962 else if (element == EL_EXPANDABLE_WALL ||
11963 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
11964 element == EL_EXPANDABLE_WALL_VERTICAL ||
11965 element == EL_EXPANDABLE_WALL_ANY ||
11966 element == EL_BD_EXPANDABLE_WALL)
11967 MauerAbleger(x, y);
11968 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
11969 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
11970 element == EL_EXPANDABLE_STEELWALL_ANY)
11971 MauerAblegerStahl(x, y);
11972 else if (element == EL_FLAMES)
11973 CheckForDragon(x, y);
11974 else if (element == EL_EXPLOSION)
11975 ; // drawing of correct explosion animation is handled separately
11976 else if (element == EL_ELEMENT_SNAPPING ||
11977 element == EL_DIAGONAL_SHRINKING ||
11978 element == EL_DIAGONAL_GROWING)
11980 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
11982 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11984 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
11985 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11987 if (IS_BELT_ACTIVE(element))
11988 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
11990 if (game.magic_wall_active)
11992 int jx = local_player->jx, jy = local_player->jy;
11994 // play the element sound at the position nearest to the player
11995 if ((element == EL_MAGIC_WALL_FULL ||
11996 element == EL_MAGIC_WALL_ACTIVE ||
11997 element == EL_MAGIC_WALL_EMPTYING ||
11998 element == EL_BD_MAGIC_WALL_FULL ||
11999 element == EL_BD_MAGIC_WALL_ACTIVE ||
12000 element == EL_BD_MAGIC_WALL_EMPTYING ||
12001 element == EL_DC_MAGIC_WALL_FULL ||
12002 element == EL_DC_MAGIC_WALL_ACTIVE ||
12003 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12004 ABS(x - jx) + ABS(y - jy) <
12005 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12013 #if USE_NEW_AMOEBA_CODE
12014 // new experimental amoeba growth stuff
12015 if (!(FrameCounter % 8))
12017 static unsigned int random = 1684108901;
12019 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12021 x = RND(lev_fieldx);
12022 y = RND(lev_fieldy);
12023 element = Feld[x][y];
12025 if (!IS_PLAYER(x,y) &&
12026 (element == EL_EMPTY ||
12027 CAN_GROW_INTO(element) ||
12028 element == EL_QUICKSAND_EMPTY ||
12029 element == EL_QUICKSAND_FAST_EMPTY ||
12030 element == EL_ACID_SPLASH_LEFT ||
12031 element == EL_ACID_SPLASH_RIGHT))
12033 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12034 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12035 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12036 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12037 Feld[x][y] = EL_AMOEBA_DROP;
12040 random = random * 129 + 1;
12045 game.explosions_delayed = FALSE;
12047 SCAN_PLAYFIELD(x, y)
12049 element = Feld[x][y];
12051 if (ExplodeField[x][y])
12052 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12053 else if (element == EL_EXPLOSION)
12054 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12056 ExplodeField[x][y] = EX_TYPE_NONE;
12059 game.explosions_delayed = TRUE;
12061 if (game.magic_wall_active)
12063 if (!(game.magic_wall_time_left % 4))
12065 int element = Feld[magic_wall_x][magic_wall_y];
12067 if (element == EL_BD_MAGIC_WALL_FULL ||
12068 element == EL_BD_MAGIC_WALL_ACTIVE ||
12069 element == EL_BD_MAGIC_WALL_EMPTYING)
12070 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12071 else if (element == EL_DC_MAGIC_WALL_FULL ||
12072 element == EL_DC_MAGIC_WALL_ACTIVE ||
12073 element == EL_DC_MAGIC_WALL_EMPTYING)
12074 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12076 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12079 if (game.magic_wall_time_left > 0)
12081 game.magic_wall_time_left--;
12083 if (!game.magic_wall_time_left)
12085 SCAN_PLAYFIELD(x, y)
12087 element = Feld[x][y];
12089 if (element == EL_MAGIC_WALL_ACTIVE ||
12090 element == EL_MAGIC_WALL_FULL)
12092 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12093 TEST_DrawLevelField(x, y);
12095 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12096 element == EL_BD_MAGIC_WALL_FULL)
12098 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12099 TEST_DrawLevelField(x, y);
12101 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12102 element == EL_DC_MAGIC_WALL_FULL)
12104 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12105 TEST_DrawLevelField(x, y);
12109 game.magic_wall_active = FALSE;
12114 if (game.light_time_left > 0)
12116 game.light_time_left--;
12118 if (game.light_time_left == 0)
12119 RedrawAllLightSwitchesAndInvisibleElements();
12122 if (game.timegate_time_left > 0)
12124 game.timegate_time_left--;
12126 if (game.timegate_time_left == 0)
12127 CloseAllOpenTimegates();
12130 if (game.lenses_time_left > 0)
12132 game.lenses_time_left--;
12134 if (game.lenses_time_left == 0)
12135 RedrawAllInvisibleElementsForLenses();
12138 if (game.magnify_time_left > 0)
12140 game.magnify_time_left--;
12142 if (game.magnify_time_left == 0)
12143 RedrawAllInvisibleElementsForMagnifier();
12146 for (i = 0; i < MAX_PLAYERS; i++)
12148 struct PlayerInfo *player = &stored_player[i];
12150 if (SHIELD_ON(player))
12152 if (player->shield_deadly_time_left)
12153 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12154 else if (player->shield_normal_time_left)
12155 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12159 #if USE_DELAYED_GFX_REDRAW
12160 SCAN_PLAYFIELD(x, y)
12162 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12164 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12165 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12167 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12168 DrawLevelField(x, y);
12170 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12171 DrawLevelFieldCrumbled(x, y);
12173 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12174 DrawLevelFieldCrumbledNeighbours(x, y);
12176 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12177 DrawTwinkleOnField(x, y);
12180 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12185 PlayAllPlayersSound();
12187 for (i = 0; i < MAX_PLAYERS; i++)
12189 struct PlayerInfo *player = &stored_player[i];
12191 if (player->show_envelope != 0 && (!player->active ||
12192 player->MovPos == 0))
12194 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12196 player->show_envelope = 0;
12200 // use random number generator in every frame to make it less predictable
12201 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12205 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12207 int min_x = x, min_y = y, max_x = x, max_y = y;
12210 for (i = 0; i < MAX_PLAYERS; i++)
12212 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12214 if (!stored_player[i].active || &stored_player[i] == player)
12217 min_x = MIN(min_x, jx);
12218 min_y = MIN(min_y, jy);
12219 max_x = MAX(max_x, jx);
12220 max_y = MAX(max_y, jy);
12223 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12226 static boolean AllPlayersInVisibleScreen(void)
12230 for (i = 0; i < MAX_PLAYERS; i++)
12232 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12234 if (!stored_player[i].active)
12237 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12244 void ScrollLevel(int dx, int dy)
12246 int scroll_offset = 2 * TILEX_VAR;
12249 BlitBitmap(drawto_field, drawto_field,
12250 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12251 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12252 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12253 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12254 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12255 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12259 x = (dx == 1 ? BX1 : BX2);
12260 for (y = BY1; y <= BY2; y++)
12261 DrawScreenField(x, y);
12266 y = (dy == 1 ? BY1 : BY2);
12267 for (x = BX1; x <= BX2; x++)
12268 DrawScreenField(x, y);
12271 redraw_mask |= REDRAW_FIELD;
12274 static boolean canFallDown(struct PlayerInfo *player)
12276 int jx = player->jx, jy = player->jy;
12278 return (IN_LEV_FIELD(jx, jy + 1) &&
12279 (IS_FREE(jx, jy + 1) ||
12280 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12281 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12282 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12285 static boolean canPassField(int x, int y, int move_dir)
12287 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12288 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12289 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12290 int nextx = x + dx;
12291 int nexty = y + dy;
12292 int element = Feld[x][y];
12294 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12295 !CAN_MOVE(element) &&
12296 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12297 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12298 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12301 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12303 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12304 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12305 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12309 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12310 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12311 (IS_DIGGABLE(Feld[newx][newy]) ||
12312 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12313 canPassField(newx, newy, move_dir)));
12316 static void CheckGravityMovement(struct PlayerInfo *player)
12318 if (player->gravity && !player->programmed_action)
12320 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12321 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12322 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12323 int jx = player->jx, jy = player->jy;
12324 boolean player_is_moving_to_valid_field =
12325 (!player_is_snapping &&
12326 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12327 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12328 boolean player_can_fall_down = canFallDown(player);
12330 if (player_can_fall_down &&
12331 !player_is_moving_to_valid_field)
12332 player->programmed_action = MV_DOWN;
12336 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12338 return CheckGravityMovement(player);
12340 if (player->gravity && !player->programmed_action)
12342 int jx = player->jx, jy = player->jy;
12343 boolean field_under_player_is_free =
12344 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12345 boolean player_is_standing_on_valid_field =
12346 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12347 (IS_WALKABLE(Feld[jx][jy]) &&
12348 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12350 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12351 player->programmed_action = MV_DOWN;
12356 MovePlayerOneStep()
12357 -----------------------------------------------------------------------------
12358 dx, dy: direction (non-diagonal) to try to move the player to
12359 real_dx, real_dy: direction as read from input device (can be diagonal)
12362 boolean MovePlayerOneStep(struct PlayerInfo *player,
12363 int dx, int dy, int real_dx, int real_dy)
12365 int jx = player->jx, jy = player->jy;
12366 int new_jx = jx + dx, new_jy = jy + dy;
12368 boolean player_can_move = !player->cannot_move;
12370 if (!player->active || (!dx && !dy))
12371 return MP_NO_ACTION;
12373 player->MovDir = (dx < 0 ? MV_LEFT :
12374 dx > 0 ? MV_RIGHT :
12376 dy > 0 ? MV_DOWN : MV_NONE);
12378 if (!IN_LEV_FIELD(new_jx, new_jy))
12379 return MP_NO_ACTION;
12381 if (!player_can_move)
12383 if (player->MovPos == 0)
12385 player->is_moving = FALSE;
12386 player->is_digging = FALSE;
12387 player->is_collecting = FALSE;
12388 player->is_snapping = FALSE;
12389 player->is_pushing = FALSE;
12393 if (!network.enabled && game.centered_player_nr == -1 &&
12394 !AllPlayersInSight(player, new_jx, new_jy))
12395 return MP_NO_ACTION;
12397 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12398 if (can_move != MP_MOVING)
12401 // check if DigField() has caused relocation of the player
12402 if (player->jx != jx || player->jy != jy)
12403 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12405 StorePlayer[jx][jy] = 0;
12406 player->last_jx = jx;
12407 player->last_jy = jy;
12408 player->jx = new_jx;
12409 player->jy = new_jy;
12410 StorePlayer[new_jx][new_jy] = player->element_nr;
12412 if (player->move_delay_value_next != -1)
12414 player->move_delay_value = player->move_delay_value_next;
12415 player->move_delay_value_next = -1;
12419 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12421 player->step_counter++;
12423 PlayerVisit[jx][jy] = FrameCounter;
12425 player->is_moving = TRUE;
12428 // should better be called in MovePlayer(), but this breaks some tapes
12429 ScrollPlayer(player, SCROLL_INIT);
12435 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12437 int jx = player->jx, jy = player->jy;
12438 int old_jx = jx, old_jy = jy;
12439 int moved = MP_NO_ACTION;
12441 if (!player->active)
12446 if (player->MovPos == 0)
12448 player->is_moving = FALSE;
12449 player->is_digging = FALSE;
12450 player->is_collecting = FALSE;
12451 player->is_snapping = FALSE;
12452 player->is_pushing = FALSE;
12458 if (player->move_delay > 0)
12461 player->move_delay = -1; // set to "uninitialized" value
12463 // store if player is automatically moved to next field
12464 player->is_auto_moving = (player->programmed_action != MV_NONE);
12466 // remove the last programmed player action
12467 player->programmed_action = 0;
12469 if (player->MovPos)
12471 // should only happen if pre-1.2 tape recordings are played
12472 // this is only for backward compatibility
12474 int original_move_delay_value = player->move_delay_value;
12477 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12481 // scroll remaining steps with finest movement resolution
12482 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12484 while (player->MovPos)
12486 ScrollPlayer(player, SCROLL_GO_ON);
12487 ScrollScreen(NULL, SCROLL_GO_ON);
12489 AdvanceFrameAndPlayerCounters(player->index_nr);
12492 BackToFront_WithFrameDelay(0);
12495 player->move_delay_value = original_move_delay_value;
12498 player->is_active = FALSE;
12500 if (player->last_move_dir & MV_HORIZONTAL)
12502 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12503 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12507 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12508 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12511 if (!moved && !player->is_active)
12513 player->is_moving = FALSE;
12514 player->is_digging = FALSE;
12515 player->is_collecting = FALSE;
12516 player->is_snapping = FALSE;
12517 player->is_pushing = FALSE;
12523 if (moved & MP_MOVING && !ScreenMovPos &&
12524 (player->index_nr == game.centered_player_nr ||
12525 game.centered_player_nr == -1))
12527 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12529 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12531 // actual player has left the screen -- scroll in that direction
12532 if (jx != old_jx) // player has moved horizontally
12533 scroll_x += (jx - old_jx);
12534 else // player has moved vertically
12535 scroll_y += (jy - old_jy);
12539 int offset_raw = game.scroll_delay_value;
12541 if (jx != old_jx) // player has moved horizontally
12543 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12544 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12545 int new_scroll_x = jx - MIDPOSX + offset_x;
12547 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12548 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12549 scroll_x = new_scroll_x;
12551 // don't scroll over playfield boundaries
12552 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12554 // don't scroll more than one field at a time
12555 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12557 // don't scroll against the player's moving direction
12558 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12559 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12560 scroll_x = old_scroll_x;
12562 else // player has moved vertically
12564 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12565 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12566 int new_scroll_y = jy - MIDPOSY + offset_y;
12568 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12569 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12570 scroll_y = new_scroll_y;
12572 // don't scroll over playfield boundaries
12573 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12575 // don't scroll more than one field at a time
12576 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12578 // don't scroll against the player's moving direction
12579 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12580 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12581 scroll_y = old_scroll_y;
12585 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12587 if (!network.enabled && game.centered_player_nr == -1 &&
12588 !AllPlayersInVisibleScreen())
12590 scroll_x = old_scroll_x;
12591 scroll_y = old_scroll_y;
12595 ScrollScreen(player, SCROLL_INIT);
12596 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12601 player->StepFrame = 0;
12603 if (moved & MP_MOVING)
12605 if (old_jx != jx && old_jy == jy)
12606 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12607 else if (old_jx == jx && old_jy != jy)
12608 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12610 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12612 player->last_move_dir = player->MovDir;
12613 player->is_moving = TRUE;
12614 player->is_snapping = FALSE;
12615 player->is_switching = FALSE;
12616 player->is_dropping = FALSE;
12617 player->is_dropping_pressed = FALSE;
12618 player->drop_pressed_delay = 0;
12621 // should better be called here than above, but this breaks some tapes
12622 ScrollPlayer(player, SCROLL_INIT);
12627 CheckGravityMovementWhenNotMoving(player);
12629 player->is_moving = FALSE;
12631 /* at this point, the player is allowed to move, but cannot move right now
12632 (e.g. because of something blocking the way) -- ensure that the player
12633 is also allowed to move in the next frame (in old versions before 3.1.1,
12634 the player was forced to wait again for eight frames before next try) */
12636 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12637 player->move_delay = 0; // allow direct movement in the next frame
12640 if (player->move_delay == -1) // not yet initialized by DigField()
12641 player->move_delay = player->move_delay_value;
12643 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12645 TestIfPlayerTouchesBadThing(jx, jy);
12646 TestIfPlayerTouchesCustomElement(jx, jy);
12649 if (!player->active)
12650 RemovePlayer(player);
12655 void ScrollPlayer(struct PlayerInfo *player, int mode)
12657 int jx = player->jx, jy = player->jy;
12658 int last_jx = player->last_jx, last_jy = player->last_jy;
12659 int move_stepsize = TILEX / player->move_delay_value;
12661 if (!player->active)
12664 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12667 if (mode == SCROLL_INIT)
12669 player->actual_frame_counter = FrameCounter;
12670 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12672 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12673 Feld[last_jx][last_jy] == EL_EMPTY)
12675 int last_field_block_delay = 0; // start with no blocking at all
12676 int block_delay_adjustment = player->block_delay_adjustment;
12678 // if player blocks last field, add delay for exactly one move
12679 if (player->block_last_field)
12681 last_field_block_delay += player->move_delay_value;
12683 // when blocking enabled, prevent moving up despite gravity
12684 if (player->gravity && player->MovDir == MV_UP)
12685 block_delay_adjustment = -1;
12688 // add block delay adjustment (also possible when not blocking)
12689 last_field_block_delay += block_delay_adjustment;
12691 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12692 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12695 if (player->MovPos != 0) // player has not yet reached destination
12698 else if (!FrameReached(&player->actual_frame_counter, 1))
12701 if (player->MovPos != 0)
12703 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12704 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12706 // before DrawPlayer() to draw correct player graphic for this case
12707 if (player->MovPos == 0)
12708 CheckGravityMovement(player);
12711 if (player->MovPos == 0) // player reached destination field
12713 if (player->move_delay_reset_counter > 0)
12715 player->move_delay_reset_counter--;
12717 if (player->move_delay_reset_counter == 0)
12719 // continue with normal speed after quickly moving through gate
12720 HALVE_PLAYER_SPEED(player);
12722 // be able to make the next move without delay
12723 player->move_delay = 0;
12727 player->last_jx = jx;
12728 player->last_jy = jy;
12730 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12731 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12732 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12733 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12734 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12735 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12736 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12737 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12739 ExitPlayer(player);
12741 if (game.players_still_needed == 0 &&
12742 (game.friends_still_needed == 0 ||
12743 IS_SP_ELEMENT(Feld[jx][jy])))
12747 // this breaks one level: "machine", level 000
12749 int move_direction = player->MovDir;
12750 int enter_side = MV_DIR_OPPOSITE(move_direction);
12751 int leave_side = move_direction;
12752 int old_jx = last_jx;
12753 int old_jy = last_jy;
12754 int old_element = Feld[old_jx][old_jy];
12755 int new_element = Feld[jx][jy];
12757 if (IS_CUSTOM_ELEMENT(old_element))
12758 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12760 player->index_bit, leave_side);
12762 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12763 CE_PLAYER_LEAVES_X,
12764 player->index_bit, leave_side);
12766 if (IS_CUSTOM_ELEMENT(new_element))
12767 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12768 player->index_bit, enter_side);
12770 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12771 CE_PLAYER_ENTERS_X,
12772 player->index_bit, enter_side);
12774 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12775 CE_MOVE_OF_X, move_direction);
12778 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12780 TestIfPlayerTouchesBadThing(jx, jy);
12781 TestIfPlayerTouchesCustomElement(jx, jy);
12783 /* needed because pushed element has not yet reached its destination,
12784 so it would trigger a change event at its previous field location */
12785 if (!player->is_pushing)
12786 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12788 if (!player->active)
12789 RemovePlayer(player);
12792 if (!game.LevelSolved && level.use_step_counter)
12802 if (TimeLeft <= 10 && setup.time_limit)
12803 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12805 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12807 DisplayGameControlValues();
12809 if (!TimeLeft && setup.time_limit)
12810 for (i = 0; i < MAX_PLAYERS; i++)
12811 KillPlayer(&stored_player[i]);
12813 else if (game.no_time_limit && !game.all_players_gone)
12815 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12817 DisplayGameControlValues();
12821 if (tape.single_step && tape.recording && !tape.pausing &&
12822 !player->programmed_action)
12823 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12825 if (!player->programmed_action)
12826 CheckSaveEngineSnapshot(player);
12830 void ScrollScreen(struct PlayerInfo *player, int mode)
12832 static unsigned int screen_frame_counter = 0;
12834 if (mode == SCROLL_INIT)
12836 // set scrolling step size according to actual player's moving speed
12837 ScrollStepSize = TILEX / player->move_delay_value;
12839 screen_frame_counter = FrameCounter;
12840 ScreenMovDir = player->MovDir;
12841 ScreenMovPos = player->MovPos;
12842 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12845 else if (!FrameReached(&screen_frame_counter, 1))
12850 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
12851 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12852 redraw_mask |= REDRAW_FIELD;
12855 ScreenMovDir = MV_NONE;
12858 void TestIfPlayerTouchesCustomElement(int x, int y)
12860 static int xy[4][2] =
12867 static int trigger_sides[4][2] =
12869 // center side border side
12870 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12871 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12872 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12873 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12875 static int touch_dir[4] =
12877 MV_LEFT | MV_RIGHT,
12882 int center_element = Feld[x][y]; // should always be non-moving!
12885 for (i = 0; i < NUM_DIRECTIONS; i++)
12887 int xx = x + xy[i][0];
12888 int yy = y + xy[i][1];
12889 int center_side = trigger_sides[i][0];
12890 int border_side = trigger_sides[i][1];
12891 int border_element;
12893 if (!IN_LEV_FIELD(xx, yy))
12896 if (IS_PLAYER(x, y)) // player found at center element
12898 struct PlayerInfo *player = PLAYERINFO(x, y);
12900 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12901 border_element = Feld[xx][yy]; // may be moving!
12902 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12903 border_element = Feld[xx][yy];
12904 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
12905 border_element = MovingOrBlocked2Element(xx, yy);
12907 continue; // center and border element do not touch
12909 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
12910 player->index_bit, border_side);
12911 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
12912 CE_PLAYER_TOUCHES_X,
12913 player->index_bit, border_side);
12916 /* use player element that is initially defined in the level playfield,
12917 not the player element that corresponds to the runtime player number
12918 (example: a level that contains EL_PLAYER_3 as the only player would
12919 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12920 int player_element = PLAYERINFO(x, y)->initial_element;
12922 CheckElementChangeBySide(xx, yy, border_element, player_element,
12923 CE_TOUCHING_X, border_side);
12926 else if (IS_PLAYER(xx, yy)) // player found at border element
12928 struct PlayerInfo *player = PLAYERINFO(xx, yy);
12930 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12932 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
12933 continue; // center and border element do not touch
12936 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
12937 player->index_bit, center_side);
12938 CheckTriggeredElementChangeByPlayer(x, y, center_element,
12939 CE_PLAYER_TOUCHES_X,
12940 player->index_bit, center_side);
12943 /* use player element that is initially defined in the level playfield,
12944 not the player element that corresponds to the runtime player number
12945 (example: a level that contains EL_PLAYER_3 as the only player would
12946 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12947 int player_element = PLAYERINFO(xx, yy)->initial_element;
12949 CheckElementChangeBySide(x, y, center_element, player_element,
12950 CE_TOUCHING_X, center_side);
12958 void TestIfElementTouchesCustomElement(int x, int y)
12960 static int xy[4][2] =
12967 static int trigger_sides[4][2] =
12969 // center side border side
12970 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12971 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12972 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12973 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12975 static int touch_dir[4] =
12977 MV_LEFT | MV_RIGHT,
12982 boolean change_center_element = FALSE;
12983 int center_element = Feld[x][y]; // should always be non-moving!
12984 int border_element_old[NUM_DIRECTIONS];
12987 for (i = 0; i < NUM_DIRECTIONS; i++)
12989 int xx = x + xy[i][0];
12990 int yy = y + xy[i][1];
12991 int border_element;
12993 border_element_old[i] = -1;
12995 if (!IN_LEV_FIELD(xx, yy))
12998 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12999 border_element = Feld[xx][yy]; // may be moving!
13000 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13001 border_element = Feld[xx][yy];
13002 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13003 border_element = MovingOrBlocked2Element(xx, yy);
13005 continue; // center and border element do not touch
13007 border_element_old[i] = border_element;
13010 for (i = 0; i < NUM_DIRECTIONS; i++)
13012 int xx = x + xy[i][0];
13013 int yy = y + xy[i][1];
13014 int center_side = trigger_sides[i][0];
13015 int border_element = border_element_old[i];
13017 if (border_element == -1)
13020 // check for change of border element
13021 CheckElementChangeBySide(xx, yy, border_element, center_element,
13022 CE_TOUCHING_X, center_side);
13024 // (center element cannot be player, so we dont have to check this here)
13027 for (i = 0; i < NUM_DIRECTIONS; i++)
13029 int xx = x + xy[i][0];
13030 int yy = y + xy[i][1];
13031 int border_side = trigger_sides[i][1];
13032 int border_element = border_element_old[i];
13034 if (border_element == -1)
13037 // check for change of center element (but change it only once)
13038 if (!change_center_element)
13039 change_center_element =
13040 CheckElementChangeBySide(x, y, center_element, border_element,
13041 CE_TOUCHING_X, border_side);
13043 if (IS_PLAYER(xx, yy))
13045 /* use player element that is initially defined in the level playfield,
13046 not the player element that corresponds to the runtime player number
13047 (example: a level that contains EL_PLAYER_3 as the only player would
13048 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13049 int player_element = PLAYERINFO(xx, yy)->initial_element;
13051 CheckElementChangeBySide(x, y, center_element, player_element,
13052 CE_TOUCHING_X, border_side);
13057 void TestIfElementHitsCustomElement(int x, int y, int direction)
13059 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13060 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13061 int hitx = x + dx, hity = y + dy;
13062 int hitting_element = Feld[x][y];
13063 int touched_element;
13065 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13068 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13069 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13071 if (IN_LEV_FIELD(hitx, hity))
13073 int opposite_direction = MV_DIR_OPPOSITE(direction);
13074 int hitting_side = direction;
13075 int touched_side = opposite_direction;
13076 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13077 MovDir[hitx][hity] != direction ||
13078 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13084 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13085 CE_HITTING_X, touched_side);
13087 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13088 CE_HIT_BY_X, hitting_side);
13090 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13091 CE_HIT_BY_SOMETHING, opposite_direction);
13093 if (IS_PLAYER(hitx, hity))
13095 /* use player element that is initially defined in the level playfield,
13096 not the player element that corresponds to the runtime player number
13097 (example: a level that contains EL_PLAYER_3 as the only player would
13098 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13099 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13101 CheckElementChangeBySide(x, y, hitting_element, player_element,
13102 CE_HITTING_X, touched_side);
13107 // "hitting something" is also true when hitting the playfield border
13108 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13109 CE_HITTING_SOMETHING, direction);
13112 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13114 int i, kill_x = -1, kill_y = -1;
13116 int bad_element = -1;
13117 static int test_xy[4][2] =
13124 static int test_dir[4] =
13132 for (i = 0; i < NUM_DIRECTIONS; i++)
13134 int test_x, test_y, test_move_dir, test_element;
13136 test_x = good_x + test_xy[i][0];
13137 test_y = good_y + test_xy[i][1];
13139 if (!IN_LEV_FIELD(test_x, test_y))
13143 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13145 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13147 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13148 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13150 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13151 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13155 bad_element = test_element;
13161 if (kill_x != -1 || kill_y != -1)
13163 if (IS_PLAYER(good_x, good_y))
13165 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13167 if (player->shield_deadly_time_left > 0 &&
13168 !IS_INDESTRUCTIBLE(bad_element))
13169 Bang(kill_x, kill_y);
13170 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13171 KillPlayer(player);
13174 Bang(good_x, good_y);
13178 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13180 int i, kill_x = -1, kill_y = -1;
13181 int bad_element = Feld[bad_x][bad_y];
13182 static int test_xy[4][2] =
13189 static int touch_dir[4] =
13191 MV_LEFT | MV_RIGHT,
13196 static int test_dir[4] =
13204 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13207 for (i = 0; i < NUM_DIRECTIONS; i++)
13209 int test_x, test_y, test_move_dir, test_element;
13211 test_x = bad_x + test_xy[i][0];
13212 test_y = bad_y + test_xy[i][1];
13214 if (!IN_LEV_FIELD(test_x, test_y))
13218 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13220 test_element = Feld[test_x][test_y];
13222 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13223 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13225 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13226 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13228 // good thing is player or penguin that does not move away
13229 if (IS_PLAYER(test_x, test_y))
13231 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13233 if (bad_element == EL_ROBOT && player->is_moving)
13234 continue; // robot does not kill player if he is moving
13236 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13238 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13239 continue; // center and border element do not touch
13247 else if (test_element == EL_PENGUIN)
13257 if (kill_x != -1 || kill_y != -1)
13259 if (IS_PLAYER(kill_x, kill_y))
13261 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13263 if (player->shield_deadly_time_left > 0 &&
13264 !IS_INDESTRUCTIBLE(bad_element))
13265 Bang(bad_x, bad_y);
13266 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13267 KillPlayer(player);
13270 Bang(kill_x, kill_y);
13274 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13276 int bad_element = Feld[bad_x][bad_y];
13277 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13278 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13279 int test_x = bad_x + dx, test_y = bad_y + dy;
13280 int test_move_dir, test_element;
13281 int kill_x = -1, kill_y = -1;
13283 if (!IN_LEV_FIELD(test_x, test_y))
13287 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13289 test_element = Feld[test_x][test_y];
13291 if (test_move_dir != bad_move_dir)
13293 // good thing can be player or penguin that does not move away
13294 if (IS_PLAYER(test_x, test_y))
13296 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13298 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13299 player as being hit when he is moving towards the bad thing, because
13300 the "get hit by" condition would be lost after the player stops) */
13301 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13302 return; // player moves away from bad thing
13307 else if (test_element == EL_PENGUIN)
13314 if (kill_x != -1 || kill_y != -1)
13316 if (IS_PLAYER(kill_x, kill_y))
13318 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13320 if (player->shield_deadly_time_left > 0 &&
13321 !IS_INDESTRUCTIBLE(bad_element))
13322 Bang(bad_x, bad_y);
13323 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13324 KillPlayer(player);
13327 Bang(kill_x, kill_y);
13331 void TestIfPlayerTouchesBadThing(int x, int y)
13333 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13336 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13338 TestIfGoodThingHitsBadThing(x, y, move_dir);
13341 void TestIfBadThingTouchesPlayer(int x, int y)
13343 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13346 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13348 TestIfBadThingHitsGoodThing(x, y, move_dir);
13351 void TestIfFriendTouchesBadThing(int x, int y)
13353 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13356 void TestIfBadThingTouchesFriend(int x, int y)
13358 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13361 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13363 int i, kill_x = bad_x, kill_y = bad_y;
13364 static int xy[4][2] =
13372 for (i = 0; i < NUM_DIRECTIONS; i++)
13376 x = bad_x + xy[i][0];
13377 y = bad_y + xy[i][1];
13378 if (!IN_LEV_FIELD(x, y))
13381 element = Feld[x][y];
13382 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13383 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13391 if (kill_x != bad_x || kill_y != bad_y)
13392 Bang(bad_x, bad_y);
13395 void KillPlayer(struct PlayerInfo *player)
13397 int jx = player->jx, jy = player->jy;
13399 if (!player->active)
13403 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13404 player->killed, player->active, player->reanimated);
13407 /* the following code was introduced to prevent an infinite loop when calling
13409 -> CheckTriggeredElementChangeExt()
13410 -> ExecuteCustomElementAction()
13412 -> (infinitely repeating the above sequence of function calls)
13413 which occurs when killing the player while having a CE with the setting
13414 "kill player X when explosion of <player X>"; the solution using a new
13415 field "player->killed" was chosen for backwards compatibility, although
13416 clever use of the fields "player->active" etc. would probably also work */
13418 if (player->killed)
13422 player->killed = TRUE;
13424 // remove accessible field at the player's position
13425 Feld[jx][jy] = EL_EMPTY;
13427 // deactivate shield (else Bang()/Explode() would not work right)
13428 player->shield_normal_time_left = 0;
13429 player->shield_deadly_time_left = 0;
13432 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13433 player->killed, player->active, player->reanimated);
13439 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13440 player->killed, player->active, player->reanimated);
13443 if (player->reanimated) // killed player may have been reanimated
13444 player->killed = player->reanimated = FALSE;
13446 BuryPlayer(player);
13449 static void KillPlayerUnlessEnemyProtected(int x, int y)
13451 if (!PLAYER_ENEMY_PROTECTED(x, y))
13452 KillPlayer(PLAYERINFO(x, y));
13455 static void KillPlayerUnlessExplosionProtected(int x, int y)
13457 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13458 KillPlayer(PLAYERINFO(x, y));
13461 void BuryPlayer(struct PlayerInfo *player)
13463 int jx = player->jx, jy = player->jy;
13465 if (!player->active)
13468 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13469 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13471 RemovePlayer(player);
13473 player->buried = TRUE;
13475 if (game.all_players_gone)
13476 game.GameOver = TRUE;
13479 void RemovePlayer(struct PlayerInfo *player)
13481 int jx = player->jx, jy = player->jy;
13482 int i, found = FALSE;
13484 player->present = FALSE;
13485 player->active = FALSE;
13487 // required for some CE actions (even if the player is not active anymore)
13488 player->MovPos = 0;
13490 if (!ExplodeField[jx][jy])
13491 StorePlayer[jx][jy] = 0;
13493 if (player->is_moving)
13494 TEST_DrawLevelField(player->last_jx, player->last_jy);
13496 for (i = 0; i < MAX_PLAYERS; i++)
13497 if (stored_player[i].active)
13502 game.all_players_gone = TRUE;
13503 game.GameOver = TRUE;
13506 game.exit_x = game.robot_wheel_x = jx;
13507 game.exit_y = game.robot_wheel_y = jy;
13510 void ExitPlayer(struct PlayerInfo *player)
13512 DrawPlayer(player); // needed here only to cleanup last field
13513 RemovePlayer(player);
13515 if (game.players_still_needed > 0)
13516 game.players_still_needed--;
13519 static void setFieldForSnapping(int x, int y, int element, int direction)
13521 struct ElementInfo *ei = &element_info[element];
13522 int direction_bit = MV_DIR_TO_BIT(direction);
13523 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13524 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13525 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13527 Feld[x][y] = EL_ELEMENT_SNAPPING;
13528 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13530 ResetGfxAnimation(x, y);
13532 GfxElement[x][y] = element;
13533 GfxAction[x][y] = action;
13534 GfxDir[x][y] = direction;
13535 GfxFrame[x][y] = -1;
13539 =============================================================================
13540 checkDiagonalPushing()
13541 -----------------------------------------------------------------------------
13542 check if diagonal input device direction results in pushing of object
13543 (by checking if the alternative direction is walkable, diggable, ...)
13544 =============================================================================
13547 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13548 int x, int y, int real_dx, int real_dy)
13550 int jx, jy, dx, dy, xx, yy;
13552 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13555 // diagonal direction: check alternative direction
13560 xx = jx + (dx == 0 ? real_dx : 0);
13561 yy = jy + (dy == 0 ? real_dy : 0);
13563 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13567 =============================================================================
13569 -----------------------------------------------------------------------------
13570 x, y: field next to player (non-diagonal) to try to dig to
13571 real_dx, real_dy: direction as read from input device (can be diagonal)
13572 =============================================================================
13575 static int DigField(struct PlayerInfo *player,
13576 int oldx, int oldy, int x, int y,
13577 int real_dx, int real_dy, int mode)
13579 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13580 boolean player_was_pushing = player->is_pushing;
13581 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13582 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13583 int jx = oldx, jy = oldy;
13584 int dx = x - jx, dy = y - jy;
13585 int nextx = x + dx, nexty = y + dy;
13586 int move_direction = (dx == -1 ? MV_LEFT :
13587 dx == +1 ? MV_RIGHT :
13589 dy == +1 ? MV_DOWN : MV_NONE);
13590 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13591 int dig_side = MV_DIR_OPPOSITE(move_direction);
13592 int old_element = Feld[jx][jy];
13593 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13596 if (is_player) // function can also be called by EL_PENGUIN
13598 if (player->MovPos == 0)
13600 player->is_digging = FALSE;
13601 player->is_collecting = FALSE;
13604 if (player->MovPos == 0) // last pushing move finished
13605 player->is_pushing = FALSE;
13607 if (mode == DF_NO_PUSH) // player just stopped pushing
13609 player->is_switching = FALSE;
13610 player->push_delay = -1;
13612 return MP_NO_ACTION;
13616 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13617 old_element = Back[jx][jy];
13619 // in case of element dropped at player position, check background
13620 else if (Back[jx][jy] != EL_EMPTY &&
13621 game.engine_version >= VERSION_IDENT(2,2,0,0))
13622 old_element = Back[jx][jy];
13624 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13625 return MP_NO_ACTION; // field has no opening in this direction
13627 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13628 return MP_NO_ACTION; // field has no opening in this direction
13630 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13634 Feld[jx][jy] = player->artwork_element;
13635 InitMovingField(jx, jy, MV_DOWN);
13636 Store[jx][jy] = EL_ACID;
13637 ContinueMoving(jx, jy);
13638 BuryPlayer(player);
13640 return MP_DONT_RUN_INTO;
13643 if (player_can_move && DONT_RUN_INTO(element))
13645 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13647 return MP_DONT_RUN_INTO;
13650 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13651 return MP_NO_ACTION;
13653 collect_count = element_info[element].collect_count_initial;
13655 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13656 return MP_NO_ACTION;
13658 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13659 player_can_move = player_can_move_or_snap;
13661 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13662 game.engine_version >= VERSION_IDENT(2,2,0,0))
13664 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13665 player->index_bit, dig_side);
13666 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13667 player->index_bit, dig_side);
13669 if (element == EL_DC_LANDMINE)
13672 if (Feld[x][y] != element) // field changed by snapping
13675 return MP_NO_ACTION;
13678 if (player->gravity && is_player && !player->is_auto_moving &&
13679 canFallDown(player) && move_direction != MV_DOWN &&
13680 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13681 return MP_NO_ACTION; // player cannot walk here due to gravity
13683 if (player_can_move &&
13684 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13686 int sound_element = SND_ELEMENT(element);
13687 int sound_action = ACTION_WALKING;
13689 if (IS_RND_GATE(element))
13691 if (!player->key[RND_GATE_NR(element)])
13692 return MP_NO_ACTION;
13694 else if (IS_RND_GATE_GRAY(element))
13696 if (!player->key[RND_GATE_GRAY_NR(element)])
13697 return MP_NO_ACTION;
13699 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13701 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13702 return MP_NO_ACTION;
13704 else if (element == EL_EXIT_OPEN ||
13705 element == EL_EM_EXIT_OPEN ||
13706 element == EL_EM_EXIT_OPENING ||
13707 element == EL_STEEL_EXIT_OPEN ||
13708 element == EL_EM_STEEL_EXIT_OPEN ||
13709 element == EL_EM_STEEL_EXIT_OPENING ||
13710 element == EL_SP_EXIT_OPEN ||
13711 element == EL_SP_EXIT_OPENING)
13713 sound_action = ACTION_PASSING; // player is passing exit
13715 else if (element == EL_EMPTY)
13717 sound_action = ACTION_MOVING; // nothing to walk on
13720 // play sound from background or player, whatever is available
13721 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13722 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13724 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13726 else if (player_can_move &&
13727 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13729 if (!ACCESS_FROM(element, opposite_direction))
13730 return MP_NO_ACTION; // field not accessible from this direction
13732 if (CAN_MOVE(element)) // only fixed elements can be passed!
13733 return MP_NO_ACTION;
13735 if (IS_EM_GATE(element))
13737 if (!player->key[EM_GATE_NR(element)])
13738 return MP_NO_ACTION;
13740 else if (IS_EM_GATE_GRAY(element))
13742 if (!player->key[EM_GATE_GRAY_NR(element)])
13743 return MP_NO_ACTION;
13745 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13747 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13748 return MP_NO_ACTION;
13750 else if (IS_EMC_GATE(element))
13752 if (!player->key[EMC_GATE_NR(element)])
13753 return MP_NO_ACTION;
13755 else if (IS_EMC_GATE_GRAY(element))
13757 if (!player->key[EMC_GATE_GRAY_NR(element)])
13758 return MP_NO_ACTION;
13760 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13762 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13763 return MP_NO_ACTION;
13765 else if (element == EL_DC_GATE_WHITE ||
13766 element == EL_DC_GATE_WHITE_GRAY ||
13767 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13769 if (player->num_white_keys == 0)
13770 return MP_NO_ACTION;
13772 player->num_white_keys--;
13774 else if (IS_SP_PORT(element))
13776 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13777 element == EL_SP_GRAVITY_PORT_RIGHT ||
13778 element == EL_SP_GRAVITY_PORT_UP ||
13779 element == EL_SP_GRAVITY_PORT_DOWN)
13780 player->gravity = !player->gravity;
13781 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13782 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13783 element == EL_SP_GRAVITY_ON_PORT_UP ||
13784 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13785 player->gravity = TRUE;
13786 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13787 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13788 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13789 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13790 player->gravity = FALSE;
13793 // automatically move to the next field with double speed
13794 player->programmed_action = move_direction;
13796 if (player->move_delay_reset_counter == 0)
13798 player->move_delay_reset_counter = 2; // two double speed steps
13800 DOUBLE_PLAYER_SPEED(player);
13803 PlayLevelSoundAction(x, y, ACTION_PASSING);
13805 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13809 if (mode != DF_SNAP)
13811 GfxElement[x][y] = GFX_ELEMENT(element);
13812 player->is_digging = TRUE;
13815 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13817 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13818 player->index_bit, dig_side);
13820 if (mode == DF_SNAP)
13822 if (level.block_snap_field)
13823 setFieldForSnapping(x, y, element, move_direction);
13825 TestIfElementTouchesCustomElement(x, y); // for empty space
13827 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13828 player->index_bit, dig_side);
13831 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13835 if (is_player && mode != DF_SNAP)
13837 GfxElement[x][y] = element;
13838 player->is_collecting = TRUE;
13841 if (element == EL_SPEED_PILL)
13843 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13845 else if (element == EL_EXTRA_TIME && level.time > 0)
13847 TimeLeft += level.extra_time;
13849 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13851 DisplayGameControlValues();
13853 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
13855 player->shield_normal_time_left += level.shield_normal_time;
13856 if (element == EL_SHIELD_DEADLY)
13857 player->shield_deadly_time_left += level.shield_deadly_time;
13859 else if (element == EL_DYNAMITE ||
13860 element == EL_EM_DYNAMITE ||
13861 element == EL_SP_DISK_RED)
13863 if (player->inventory_size < MAX_INVENTORY_SIZE)
13864 player->inventory_element[player->inventory_size++] = element;
13866 DrawGameDoorValues();
13868 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
13870 player->dynabomb_count++;
13871 player->dynabombs_left++;
13873 else if (element == EL_DYNABOMB_INCREASE_SIZE)
13875 player->dynabomb_size++;
13877 else if (element == EL_DYNABOMB_INCREASE_POWER)
13879 player->dynabomb_xl = TRUE;
13881 else if (IS_KEY(element))
13883 player->key[KEY_NR(element)] = TRUE;
13885 DrawGameDoorValues();
13887 else if (element == EL_DC_KEY_WHITE)
13889 player->num_white_keys++;
13891 // display white keys?
13892 // DrawGameDoorValues();
13894 else if (IS_ENVELOPE(element))
13896 player->show_envelope = element;
13898 else if (element == EL_EMC_LENSES)
13900 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
13902 RedrawAllInvisibleElementsForLenses();
13904 else if (element == EL_EMC_MAGNIFIER)
13906 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
13908 RedrawAllInvisibleElementsForMagnifier();
13910 else if (IS_DROPPABLE(element) ||
13911 IS_THROWABLE(element)) // can be collected and dropped
13915 if (collect_count == 0)
13916 player->inventory_infinite_element = element;
13918 for (i = 0; i < collect_count; i++)
13919 if (player->inventory_size < MAX_INVENTORY_SIZE)
13920 player->inventory_element[player->inventory_size++] = element;
13922 DrawGameDoorValues();
13924 else if (collect_count > 0)
13926 game.gems_still_needed -= collect_count;
13927 if (game.gems_still_needed < 0)
13928 game.gems_still_needed = 0;
13930 game.snapshot.collected_item = TRUE;
13932 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
13934 DisplayGameControlValues();
13937 RaiseScoreElement(element);
13938 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
13941 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
13942 player->index_bit, dig_side);
13944 if (mode == DF_SNAP)
13946 if (level.block_snap_field)
13947 setFieldForSnapping(x, y, element, move_direction);
13949 TestIfElementTouchesCustomElement(x, y); // for empty space
13951 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13952 player->index_bit, dig_side);
13955 else if (player_can_move_or_snap && IS_PUSHABLE(element))
13957 if (mode == DF_SNAP && element != EL_BD_ROCK)
13958 return MP_NO_ACTION;
13960 if (CAN_FALL(element) && dy)
13961 return MP_NO_ACTION;
13963 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
13964 !(element == EL_SPRING && level.use_spring_bug))
13965 return MP_NO_ACTION;
13967 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
13968 ((move_direction & MV_VERTICAL &&
13969 ((element_info[element].move_pattern & MV_LEFT &&
13970 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
13971 (element_info[element].move_pattern & MV_RIGHT &&
13972 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
13973 (move_direction & MV_HORIZONTAL &&
13974 ((element_info[element].move_pattern & MV_UP &&
13975 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
13976 (element_info[element].move_pattern & MV_DOWN &&
13977 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
13978 return MP_NO_ACTION;
13980 // do not push elements already moving away faster than player
13981 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
13982 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
13983 return MP_NO_ACTION;
13985 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
13987 if (player->push_delay_value == -1 || !player_was_pushing)
13988 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13990 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13992 if (player->push_delay_value == -1)
13993 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13995 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
13997 if (!player->is_pushing)
13998 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14001 player->is_pushing = TRUE;
14002 player->is_active = TRUE;
14004 if (!(IN_LEV_FIELD(nextx, nexty) &&
14005 (IS_FREE(nextx, nexty) ||
14006 (IS_SB_ELEMENT(element) &&
14007 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14008 (IS_CUSTOM_ELEMENT(element) &&
14009 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14010 return MP_NO_ACTION;
14012 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14013 return MP_NO_ACTION;
14015 if (player->push_delay == -1) // new pushing; restart delay
14016 player->push_delay = 0;
14018 if (player->push_delay < player->push_delay_value &&
14019 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14020 element != EL_SPRING && element != EL_BALLOON)
14022 // make sure that there is no move delay before next try to push
14023 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14024 player->move_delay = 0;
14026 return MP_NO_ACTION;
14029 if (IS_CUSTOM_ELEMENT(element) &&
14030 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14032 if (!DigFieldByCE(nextx, nexty, element))
14033 return MP_NO_ACTION;
14036 if (IS_SB_ELEMENT(element))
14038 boolean sokoban_task_solved = FALSE;
14040 if (element == EL_SOKOBAN_FIELD_FULL)
14042 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14044 IncrementSokobanFieldsNeeded();
14045 IncrementSokobanObjectsNeeded();
14048 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14050 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14052 DecrementSokobanFieldsNeeded();
14053 DecrementSokobanObjectsNeeded();
14055 // sokoban object was pushed from empty field to sokoban field
14056 if (Back[x][y] == EL_EMPTY)
14057 sokoban_task_solved = TRUE;
14060 Feld[x][y] = EL_SOKOBAN_OBJECT;
14062 if (Back[x][y] == Back[nextx][nexty])
14063 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14064 else if (Back[x][y] != 0)
14065 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14068 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14071 if (sokoban_task_solved &&
14072 game.sokoban_fields_still_needed == 0 &&
14073 game.sokoban_objects_still_needed == 0 &&
14074 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14076 game.players_still_needed = 0;
14080 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14084 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14086 InitMovingField(x, y, move_direction);
14087 GfxAction[x][y] = ACTION_PUSHING;
14089 if (mode == DF_SNAP)
14090 ContinueMoving(x, y);
14092 MovPos[x][y] = (dx != 0 ? dx : dy);
14094 Pushed[x][y] = TRUE;
14095 Pushed[nextx][nexty] = TRUE;
14097 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14098 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14100 player->push_delay_value = -1; // get new value later
14102 // check for element change _after_ element has been pushed
14103 if (game.use_change_when_pushing_bug)
14105 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14106 player->index_bit, dig_side);
14107 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14108 player->index_bit, dig_side);
14111 else if (IS_SWITCHABLE(element))
14113 if (PLAYER_SWITCHING(player, x, y))
14115 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14116 player->index_bit, dig_side);
14121 player->is_switching = TRUE;
14122 player->switch_x = x;
14123 player->switch_y = y;
14125 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14127 if (element == EL_ROBOT_WHEEL)
14129 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14131 game.robot_wheel_x = x;
14132 game.robot_wheel_y = y;
14133 game.robot_wheel_active = TRUE;
14135 TEST_DrawLevelField(x, y);
14137 else if (element == EL_SP_TERMINAL)
14141 SCAN_PLAYFIELD(xx, yy)
14143 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14147 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14149 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14151 ResetGfxAnimation(xx, yy);
14152 TEST_DrawLevelField(xx, yy);
14156 else if (IS_BELT_SWITCH(element))
14158 ToggleBeltSwitch(x, y);
14160 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14161 element == EL_SWITCHGATE_SWITCH_DOWN ||
14162 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14163 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14165 ToggleSwitchgateSwitch(x, y);
14167 else if (element == EL_LIGHT_SWITCH ||
14168 element == EL_LIGHT_SWITCH_ACTIVE)
14170 ToggleLightSwitch(x, y);
14172 else if (element == EL_TIMEGATE_SWITCH ||
14173 element == EL_DC_TIMEGATE_SWITCH)
14175 ActivateTimegateSwitch(x, y);
14177 else if (element == EL_BALLOON_SWITCH_LEFT ||
14178 element == EL_BALLOON_SWITCH_RIGHT ||
14179 element == EL_BALLOON_SWITCH_UP ||
14180 element == EL_BALLOON_SWITCH_DOWN ||
14181 element == EL_BALLOON_SWITCH_NONE ||
14182 element == EL_BALLOON_SWITCH_ANY)
14184 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14185 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14186 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14187 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14188 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14191 else if (element == EL_LAMP)
14193 Feld[x][y] = EL_LAMP_ACTIVE;
14194 game.lights_still_needed--;
14196 ResetGfxAnimation(x, y);
14197 TEST_DrawLevelField(x, y);
14199 else if (element == EL_TIME_ORB_FULL)
14201 Feld[x][y] = EL_TIME_ORB_EMPTY;
14203 if (level.time > 0 || level.use_time_orb_bug)
14205 TimeLeft += level.time_orb_time;
14206 game.no_time_limit = FALSE;
14208 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14210 DisplayGameControlValues();
14213 ResetGfxAnimation(x, y);
14214 TEST_DrawLevelField(x, y);
14216 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14217 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14221 game.ball_state = !game.ball_state;
14223 SCAN_PLAYFIELD(xx, yy)
14225 int e = Feld[xx][yy];
14227 if (game.ball_state)
14229 if (e == EL_EMC_MAGIC_BALL)
14230 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14231 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14232 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14236 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14237 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14238 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14239 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14244 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14245 player->index_bit, dig_side);
14247 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14248 player->index_bit, dig_side);
14250 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14251 player->index_bit, dig_side);
14257 if (!PLAYER_SWITCHING(player, x, y))
14259 player->is_switching = TRUE;
14260 player->switch_x = x;
14261 player->switch_y = y;
14263 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14264 player->index_bit, dig_side);
14265 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14266 player->index_bit, dig_side);
14268 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14269 player->index_bit, dig_side);
14270 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14271 player->index_bit, dig_side);
14274 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14275 player->index_bit, dig_side);
14276 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14277 player->index_bit, dig_side);
14279 return MP_NO_ACTION;
14282 player->push_delay = -1;
14284 if (is_player) // function can also be called by EL_PENGUIN
14286 if (Feld[x][y] != element) // really digged/collected something
14288 player->is_collecting = !player->is_digging;
14289 player->is_active = TRUE;
14296 static boolean DigFieldByCE(int x, int y, int digging_element)
14298 int element = Feld[x][y];
14300 if (!IS_FREE(x, y))
14302 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14303 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14306 // no element can dig solid indestructible elements
14307 if (IS_INDESTRUCTIBLE(element) &&
14308 !IS_DIGGABLE(element) &&
14309 !IS_COLLECTIBLE(element))
14312 if (AmoebaNr[x][y] &&
14313 (element == EL_AMOEBA_FULL ||
14314 element == EL_BD_AMOEBA ||
14315 element == EL_AMOEBA_GROWING))
14317 AmoebaCnt[AmoebaNr[x][y]]--;
14318 AmoebaCnt2[AmoebaNr[x][y]]--;
14321 if (IS_MOVING(x, y))
14322 RemoveMovingField(x, y);
14326 TEST_DrawLevelField(x, y);
14329 // if digged element was about to explode, prevent the explosion
14330 ExplodeField[x][y] = EX_TYPE_NONE;
14332 PlayLevelSoundAction(x, y, action);
14335 Store[x][y] = EL_EMPTY;
14337 // this makes it possible to leave the removed element again
14338 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14339 Store[x][y] = element;
14344 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14346 int jx = player->jx, jy = player->jy;
14347 int x = jx + dx, y = jy + dy;
14348 int snap_direction = (dx == -1 ? MV_LEFT :
14349 dx == +1 ? MV_RIGHT :
14351 dy == +1 ? MV_DOWN : MV_NONE);
14352 boolean can_continue_snapping = (level.continuous_snapping &&
14353 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14355 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14358 if (!player->active || !IN_LEV_FIELD(x, y))
14366 if (player->MovPos == 0)
14367 player->is_pushing = FALSE;
14369 player->is_snapping = FALSE;
14371 if (player->MovPos == 0)
14373 player->is_moving = FALSE;
14374 player->is_digging = FALSE;
14375 player->is_collecting = FALSE;
14381 // prevent snapping with already pressed snap key when not allowed
14382 if (player->is_snapping && !can_continue_snapping)
14385 player->MovDir = snap_direction;
14387 if (player->MovPos == 0)
14389 player->is_moving = FALSE;
14390 player->is_digging = FALSE;
14391 player->is_collecting = FALSE;
14394 player->is_dropping = FALSE;
14395 player->is_dropping_pressed = FALSE;
14396 player->drop_pressed_delay = 0;
14398 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14401 player->is_snapping = TRUE;
14402 player->is_active = TRUE;
14404 if (player->MovPos == 0)
14406 player->is_moving = FALSE;
14407 player->is_digging = FALSE;
14408 player->is_collecting = FALSE;
14411 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14412 TEST_DrawLevelField(player->last_jx, player->last_jy);
14414 TEST_DrawLevelField(x, y);
14419 static boolean DropElement(struct PlayerInfo *player)
14421 int old_element, new_element;
14422 int dropx = player->jx, dropy = player->jy;
14423 int drop_direction = player->MovDir;
14424 int drop_side = drop_direction;
14425 int drop_element = get_next_dropped_element(player);
14427 /* do not drop an element on top of another element; when holding drop key
14428 pressed without moving, dropped element must move away before the next
14429 element can be dropped (this is especially important if the next element
14430 is dynamite, which can be placed on background for historical reasons) */
14431 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14434 if (IS_THROWABLE(drop_element))
14436 dropx += GET_DX_FROM_DIR(drop_direction);
14437 dropy += GET_DY_FROM_DIR(drop_direction);
14439 if (!IN_LEV_FIELD(dropx, dropy))
14443 old_element = Feld[dropx][dropy]; // old element at dropping position
14444 new_element = drop_element; // default: no change when dropping
14446 // check if player is active, not moving and ready to drop
14447 if (!player->active || player->MovPos || player->drop_delay > 0)
14450 // check if player has anything that can be dropped
14451 if (new_element == EL_UNDEFINED)
14454 // only set if player has anything that can be dropped
14455 player->is_dropping_pressed = TRUE;
14457 // check if drop key was pressed long enough for EM style dynamite
14458 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14461 // check if anything can be dropped at the current position
14462 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14465 // collected custom elements can only be dropped on empty fields
14466 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14469 if (old_element != EL_EMPTY)
14470 Back[dropx][dropy] = old_element; // store old element on this field
14472 ResetGfxAnimation(dropx, dropy);
14473 ResetRandomAnimationValue(dropx, dropy);
14475 if (player->inventory_size > 0 ||
14476 player->inventory_infinite_element != EL_UNDEFINED)
14478 if (player->inventory_size > 0)
14480 player->inventory_size--;
14482 DrawGameDoorValues();
14484 if (new_element == EL_DYNAMITE)
14485 new_element = EL_DYNAMITE_ACTIVE;
14486 else if (new_element == EL_EM_DYNAMITE)
14487 new_element = EL_EM_DYNAMITE_ACTIVE;
14488 else if (new_element == EL_SP_DISK_RED)
14489 new_element = EL_SP_DISK_RED_ACTIVE;
14492 Feld[dropx][dropy] = new_element;
14494 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14495 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14496 el2img(Feld[dropx][dropy]), 0);
14498 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14500 // needed if previous element just changed to "empty" in the last frame
14501 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14503 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14504 player->index_bit, drop_side);
14505 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14507 player->index_bit, drop_side);
14509 TestIfElementTouchesCustomElement(dropx, dropy);
14511 else // player is dropping a dyna bomb
14513 player->dynabombs_left--;
14515 Feld[dropx][dropy] = new_element;
14517 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14518 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14519 el2img(Feld[dropx][dropy]), 0);
14521 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14524 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14525 InitField_WithBug1(dropx, dropy, FALSE);
14527 new_element = Feld[dropx][dropy]; // element might have changed
14529 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14530 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14532 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14533 MovDir[dropx][dropy] = drop_direction;
14535 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14537 // do not cause impact style collision by dropping elements that can fall
14538 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14541 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14542 player->is_dropping = TRUE;
14544 player->drop_pressed_delay = 0;
14545 player->is_dropping_pressed = FALSE;
14547 player->drop_x = dropx;
14548 player->drop_y = dropy;
14553 // ----------------------------------------------------------------------------
14554 // game sound playing functions
14555 // ----------------------------------------------------------------------------
14557 static int *loop_sound_frame = NULL;
14558 static int *loop_sound_volume = NULL;
14560 void InitPlayLevelSound(void)
14562 int num_sounds = getSoundListSize();
14564 checked_free(loop_sound_frame);
14565 checked_free(loop_sound_volume);
14567 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14568 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14571 static void PlayLevelSound(int x, int y, int nr)
14573 int sx = SCREENX(x), sy = SCREENY(y);
14574 int volume, stereo_position;
14575 int max_distance = 8;
14576 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14578 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14579 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14582 if (!IN_LEV_FIELD(x, y) ||
14583 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14584 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14587 volume = SOUND_MAX_VOLUME;
14589 if (!IN_SCR_FIELD(sx, sy))
14591 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14592 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14594 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14597 stereo_position = (SOUND_MAX_LEFT +
14598 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14599 (SCR_FIELDX + 2 * max_distance));
14601 if (IS_LOOP_SOUND(nr))
14603 /* This assures that quieter loop sounds do not overwrite louder ones,
14604 while restarting sound volume comparison with each new game frame. */
14606 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14609 loop_sound_volume[nr] = volume;
14610 loop_sound_frame[nr] = FrameCounter;
14613 PlaySoundExt(nr, volume, stereo_position, type);
14616 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14618 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14619 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14620 y < LEVELY(BY1) ? LEVELY(BY1) :
14621 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14625 static void PlayLevelSoundAction(int x, int y, int action)
14627 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14630 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14632 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14634 if (sound_effect != SND_UNDEFINED)
14635 PlayLevelSound(x, y, sound_effect);
14638 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14641 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14643 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14644 PlayLevelSound(x, y, sound_effect);
14647 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14649 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14651 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14652 PlayLevelSound(x, y, sound_effect);
14655 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14657 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14659 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14660 StopSound(sound_effect);
14663 static int getLevelMusicNr(void)
14665 if (levelset.music[level_nr] != MUS_UNDEFINED)
14666 return levelset.music[level_nr]; // from config file
14668 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14671 static void FadeLevelSounds(void)
14676 static void FadeLevelMusic(void)
14678 int music_nr = getLevelMusicNr();
14679 char *curr_music = getCurrentlyPlayingMusicFilename();
14680 char *next_music = getMusicInfoEntryFilename(music_nr);
14682 if (!strEqual(curr_music, next_music))
14686 void FadeLevelSoundsAndMusic(void)
14692 static void PlayLevelMusic(void)
14694 int music_nr = getLevelMusicNr();
14695 char *curr_music = getCurrentlyPlayingMusicFilename();
14696 char *next_music = getMusicInfoEntryFilename(music_nr);
14698 if (!strEqual(curr_music, next_music))
14699 PlayMusicLoop(music_nr);
14702 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14704 int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0);
14705 int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
14706 int x = xx - 1 - offset;
14707 int y = yy - 1 - offset;
14712 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14716 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14720 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14724 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14728 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14732 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14736 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14739 case SAMPLE_android_clone:
14740 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14743 case SAMPLE_android_move:
14744 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14747 case SAMPLE_spring:
14748 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14752 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14756 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14759 case SAMPLE_eater_eat:
14760 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14764 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14767 case SAMPLE_collect:
14768 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14771 case SAMPLE_diamond:
14772 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14775 case SAMPLE_squash:
14776 // !!! CHECK THIS !!!
14778 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14780 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14784 case SAMPLE_wonderfall:
14785 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14789 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14793 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14797 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14801 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14805 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14809 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14812 case SAMPLE_wonder:
14813 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14817 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14820 case SAMPLE_exit_open:
14821 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14824 case SAMPLE_exit_leave:
14825 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14828 case SAMPLE_dynamite:
14829 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14833 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14837 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14841 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14845 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14849 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
14853 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
14857 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
14862 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
14864 int element = map_element_SP_to_RND(element_sp);
14865 int action = map_action_SP_to_RND(action_sp);
14866 int offset = (setup.sp_show_border_elements ? 0 : 1);
14867 int x = xx - offset;
14868 int y = yy - offset;
14870 PlayLevelSoundElementAction(x, y, element, action);
14873 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
14875 int element = map_element_MM_to_RND(element_mm);
14876 int action = map_action_MM_to_RND(action_mm);
14878 int x = xx - offset;
14879 int y = yy - offset;
14881 if (!IS_MM_ELEMENT(element))
14882 element = EL_MM_DEFAULT;
14884 PlayLevelSoundElementAction(x, y, element, action);
14887 void PlaySound_MM(int sound_mm)
14889 int sound = map_sound_MM_to_RND(sound_mm);
14891 if (sound == SND_UNDEFINED)
14897 void PlaySoundLoop_MM(int sound_mm)
14899 int sound = map_sound_MM_to_RND(sound_mm);
14901 if (sound == SND_UNDEFINED)
14904 PlaySoundLoop(sound);
14907 void StopSound_MM(int sound_mm)
14909 int sound = map_sound_MM_to_RND(sound_mm);
14911 if (sound == SND_UNDEFINED)
14917 void RaiseScore(int value)
14919 game.score += value;
14921 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
14923 DisplayGameControlValues();
14926 void RaiseScoreElement(int element)
14931 case EL_BD_DIAMOND:
14932 case EL_EMERALD_YELLOW:
14933 case EL_EMERALD_RED:
14934 case EL_EMERALD_PURPLE:
14935 case EL_SP_INFOTRON:
14936 RaiseScore(level.score[SC_EMERALD]);
14939 RaiseScore(level.score[SC_DIAMOND]);
14942 RaiseScore(level.score[SC_CRYSTAL]);
14945 RaiseScore(level.score[SC_PEARL]);
14948 case EL_BD_BUTTERFLY:
14949 case EL_SP_ELECTRON:
14950 RaiseScore(level.score[SC_BUG]);
14953 case EL_BD_FIREFLY:
14954 case EL_SP_SNIKSNAK:
14955 RaiseScore(level.score[SC_SPACESHIP]);
14958 case EL_DARK_YAMYAM:
14959 RaiseScore(level.score[SC_YAMYAM]);
14962 RaiseScore(level.score[SC_ROBOT]);
14965 RaiseScore(level.score[SC_PACMAN]);
14968 RaiseScore(level.score[SC_NUT]);
14971 case EL_EM_DYNAMITE:
14972 case EL_SP_DISK_RED:
14973 case EL_DYNABOMB_INCREASE_NUMBER:
14974 case EL_DYNABOMB_INCREASE_SIZE:
14975 case EL_DYNABOMB_INCREASE_POWER:
14976 RaiseScore(level.score[SC_DYNAMITE]);
14978 case EL_SHIELD_NORMAL:
14979 case EL_SHIELD_DEADLY:
14980 RaiseScore(level.score[SC_SHIELD]);
14982 case EL_EXTRA_TIME:
14983 RaiseScore(level.extra_time_score);
14997 case EL_DC_KEY_WHITE:
14998 RaiseScore(level.score[SC_KEY]);
15001 RaiseScore(element_info[element].collect_score);
15006 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15008 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15010 // closing door required in case of envelope style request dialogs
15013 // prevent short reactivation of overlay buttons while closing door
15014 SetOverlayActive(FALSE);
15016 CloseDoor(DOOR_CLOSE_1);
15019 if (network.enabled)
15020 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15024 FadeSkipNextFadeIn();
15026 SetGameStatus(GAME_MODE_MAIN);
15031 else // continue playing the game
15033 if (tape.playing && tape.deactivate_display)
15034 TapeDeactivateDisplayOff(TRUE);
15036 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15038 if (tape.playing && tape.deactivate_display)
15039 TapeDeactivateDisplayOn();
15043 void RequestQuitGame(boolean ask_if_really_quit)
15045 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15046 boolean skip_request = game.all_players_gone || quick_quit;
15048 RequestQuitGameExt(skip_request, quick_quit,
15049 "Do you really want to quit the game?");
15052 void RequestRestartGame(char *message)
15054 game.restart_game_message = NULL;
15056 boolean has_started_game = hasStartedNetworkGame();
15057 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15059 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15061 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15065 SetGameStatus(GAME_MODE_MAIN);
15071 void CheckGameOver(void)
15073 static boolean last_game_over = FALSE;
15074 static int game_over_delay = 0;
15075 int game_over_delay_value = 50;
15076 boolean game_over = checkGameFailed();
15078 // do not handle game over if request dialog is already active
15079 if (game.request_active)
15082 // do not ask to play again if game was never actually played
15083 if (!game.GamePlayed)
15088 last_game_over = FALSE;
15089 game_over_delay = game_over_delay_value;
15094 if (game_over_delay > 0)
15101 if (last_game_over != game_over)
15102 game.restart_game_message = (hasStartedNetworkGame() ?
15103 "Game over! Play it again?" :
15106 last_game_over = game_over;
15109 boolean checkGameSolved(void)
15111 // set for all game engines if level was solved
15112 return game.LevelSolved_GameEnd;
15115 boolean checkGameFailed(void)
15117 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15118 return (game_em.game_over && !game_em.level_solved);
15119 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15120 return (game_sp.game_over && !game_sp.level_solved);
15121 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15122 return (game_mm.game_over && !game_mm.level_solved);
15123 else // GAME_ENGINE_TYPE_RND
15124 return (game.GameOver && !game.LevelSolved);
15127 boolean checkGameEnded(void)
15129 return (checkGameSolved() || checkGameFailed());
15133 // ----------------------------------------------------------------------------
15134 // random generator functions
15135 // ----------------------------------------------------------------------------
15137 unsigned int InitEngineRandom_RND(int seed)
15139 game.num_random_calls = 0;
15141 return InitEngineRandom(seed);
15144 unsigned int RND(int max)
15148 game.num_random_calls++;
15150 return GetEngineRandom(max);
15157 // ----------------------------------------------------------------------------
15158 // game engine snapshot handling functions
15159 // ----------------------------------------------------------------------------
15161 struct EngineSnapshotInfo
15163 // runtime values for custom element collect score
15164 int collect_score[NUM_CUSTOM_ELEMENTS];
15166 // runtime values for group element choice position
15167 int choice_pos[NUM_GROUP_ELEMENTS];
15169 // runtime values for belt position animations
15170 int belt_graphic[4][NUM_BELT_PARTS];
15171 int belt_anim_mode[4][NUM_BELT_PARTS];
15174 static struct EngineSnapshotInfo engine_snapshot_rnd;
15175 static char *snapshot_level_identifier = NULL;
15176 static int snapshot_level_nr = -1;
15178 static void SaveEngineSnapshotValues_RND(void)
15180 static int belt_base_active_element[4] =
15182 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15183 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15184 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15185 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15189 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15191 int element = EL_CUSTOM_START + i;
15193 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15196 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15198 int element = EL_GROUP_START + i;
15200 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15203 for (i = 0; i < 4; i++)
15205 for (j = 0; j < NUM_BELT_PARTS; j++)
15207 int element = belt_base_active_element[i] + j;
15208 int graphic = el2img(element);
15209 int anim_mode = graphic_info[graphic].anim_mode;
15211 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15212 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15217 static void LoadEngineSnapshotValues_RND(void)
15219 unsigned int num_random_calls = game.num_random_calls;
15222 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15224 int element = EL_CUSTOM_START + i;
15226 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15229 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15231 int element = EL_GROUP_START + i;
15233 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15236 for (i = 0; i < 4; i++)
15238 for (j = 0; j < NUM_BELT_PARTS; j++)
15240 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15241 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15243 graphic_info[graphic].anim_mode = anim_mode;
15247 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15249 InitRND(tape.random_seed);
15250 for (i = 0; i < num_random_calls; i++)
15254 if (game.num_random_calls != num_random_calls)
15256 Error(ERR_INFO, "number of random calls out of sync");
15257 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15258 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15259 Error(ERR_EXIT, "this should not happen -- please debug");
15263 void FreeEngineSnapshotSingle(void)
15265 FreeSnapshotSingle();
15267 setString(&snapshot_level_identifier, NULL);
15268 snapshot_level_nr = -1;
15271 void FreeEngineSnapshotList(void)
15273 FreeSnapshotList();
15276 static ListNode *SaveEngineSnapshotBuffers(void)
15278 ListNode *buffers = NULL;
15280 // copy some special values to a structure better suited for the snapshot
15282 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15283 SaveEngineSnapshotValues_RND();
15284 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15285 SaveEngineSnapshotValues_EM();
15286 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15287 SaveEngineSnapshotValues_SP(&buffers);
15288 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15289 SaveEngineSnapshotValues_MM(&buffers);
15291 // save values stored in special snapshot structure
15293 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15294 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15295 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15296 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15297 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15298 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15299 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15300 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15302 // save further RND engine values
15304 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15305 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15306 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15308 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15309 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15310 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15311 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15312 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15314 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15315 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15316 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15318 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15320 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15321 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15323 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15324 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15325 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15326 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15327 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15328 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15329 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15330 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15331 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15332 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15333 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15334 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15335 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15336 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15337 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15338 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15339 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15340 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15342 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15343 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15345 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15346 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15347 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15349 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15350 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15352 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15353 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15354 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15355 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15356 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15358 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15359 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15362 ListNode *node = engine_snapshot_list_rnd;
15365 while (node != NULL)
15367 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15372 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15378 void SaveEngineSnapshotSingle(void)
15380 ListNode *buffers = SaveEngineSnapshotBuffers();
15382 // finally save all snapshot buffers to single snapshot
15383 SaveSnapshotSingle(buffers);
15385 // save level identification information
15386 setString(&snapshot_level_identifier, leveldir_current->identifier);
15387 snapshot_level_nr = level_nr;
15390 boolean CheckSaveEngineSnapshotToList(void)
15392 boolean save_snapshot =
15393 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15394 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15395 game.snapshot.changed_action) ||
15396 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15397 game.snapshot.collected_item));
15399 game.snapshot.changed_action = FALSE;
15400 game.snapshot.collected_item = FALSE;
15401 game.snapshot.save_snapshot = save_snapshot;
15403 return save_snapshot;
15406 void SaveEngineSnapshotToList(void)
15408 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15412 ListNode *buffers = SaveEngineSnapshotBuffers();
15414 // finally save all snapshot buffers to snapshot list
15415 SaveSnapshotToList(buffers);
15418 void SaveEngineSnapshotToListInitial(void)
15420 FreeEngineSnapshotList();
15422 SaveEngineSnapshotToList();
15425 static void LoadEngineSnapshotValues(void)
15427 // restore special values from snapshot structure
15429 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15430 LoadEngineSnapshotValues_RND();
15431 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15432 LoadEngineSnapshotValues_EM();
15433 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15434 LoadEngineSnapshotValues_SP();
15435 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15436 LoadEngineSnapshotValues_MM();
15439 void LoadEngineSnapshotSingle(void)
15441 LoadSnapshotSingle();
15443 LoadEngineSnapshotValues();
15446 static void LoadEngineSnapshot_Undo(int steps)
15448 LoadSnapshotFromList_Older(steps);
15450 LoadEngineSnapshotValues();
15453 static void LoadEngineSnapshot_Redo(int steps)
15455 LoadSnapshotFromList_Newer(steps);
15457 LoadEngineSnapshotValues();
15460 boolean CheckEngineSnapshotSingle(void)
15462 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15463 snapshot_level_nr == level_nr);
15466 boolean CheckEngineSnapshotList(void)
15468 return CheckSnapshotList();
15472 // ---------- new game button stuff -------------------------------------------
15479 boolean *setup_value;
15480 boolean allowed_on_tape;
15481 boolean is_touch_button;
15483 } gamebutton_info[NUM_GAME_BUTTONS] =
15486 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15487 GAME_CTRL_ID_STOP, NULL,
15488 TRUE, FALSE, "stop game"
15491 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15492 GAME_CTRL_ID_PAUSE, NULL,
15493 TRUE, FALSE, "pause game"
15496 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15497 GAME_CTRL_ID_PLAY, NULL,
15498 TRUE, FALSE, "play game"
15501 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15502 GAME_CTRL_ID_UNDO, NULL,
15503 TRUE, FALSE, "undo step"
15506 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15507 GAME_CTRL_ID_REDO, NULL,
15508 TRUE, FALSE, "redo step"
15511 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15512 GAME_CTRL_ID_SAVE, NULL,
15513 TRUE, FALSE, "save game"
15516 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15517 GAME_CTRL_ID_PAUSE2, NULL,
15518 TRUE, FALSE, "pause game"
15521 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15522 GAME_CTRL_ID_LOAD, NULL,
15523 TRUE, FALSE, "load game"
15526 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15527 GAME_CTRL_ID_PANEL_STOP, NULL,
15528 FALSE, FALSE, "stop game"
15531 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15532 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15533 FALSE, FALSE, "pause game"
15536 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15537 GAME_CTRL_ID_PANEL_PLAY, NULL,
15538 FALSE, FALSE, "play game"
15541 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15542 GAME_CTRL_ID_TOUCH_STOP, NULL,
15543 FALSE, TRUE, "stop game"
15546 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15547 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15548 FALSE, TRUE, "pause game"
15551 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15552 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15553 TRUE, FALSE, "background music on/off"
15556 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15557 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15558 TRUE, FALSE, "sound loops on/off"
15561 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15562 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15563 TRUE, FALSE, "normal sounds on/off"
15566 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15567 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15568 FALSE, FALSE, "background music on/off"
15571 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15572 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15573 FALSE, FALSE, "sound loops on/off"
15576 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15577 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15578 FALSE, FALSE, "normal sounds on/off"
15582 void CreateGameButtons(void)
15586 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15588 int graphic = gamebutton_info[i].graphic;
15589 struct GraphicInfo *gfx = &graphic_info[graphic];
15590 struct XY *pos = gamebutton_info[i].pos;
15591 struct GadgetInfo *gi;
15594 unsigned int event_mask;
15595 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15596 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15597 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15598 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15599 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15600 int gd_x = gfx->src_x;
15601 int gd_y = gfx->src_y;
15602 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15603 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15604 int gd_xa = gfx->src_x + gfx->active_xoffset;
15605 int gd_ya = gfx->src_y + gfx->active_yoffset;
15606 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15607 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15608 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15609 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15612 if (gfx->bitmap == NULL)
15614 game_gadget[id] = NULL;
15619 if (id == GAME_CTRL_ID_STOP ||
15620 id == GAME_CTRL_ID_PANEL_STOP ||
15621 id == GAME_CTRL_ID_TOUCH_STOP ||
15622 id == GAME_CTRL_ID_PLAY ||
15623 id == GAME_CTRL_ID_PANEL_PLAY ||
15624 id == GAME_CTRL_ID_SAVE ||
15625 id == GAME_CTRL_ID_LOAD)
15627 button_type = GD_TYPE_NORMAL_BUTTON;
15629 event_mask = GD_EVENT_RELEASED;
15631 else if (id == GAME_CTRL_ID_UNDO ||
15632 id == GAME_CTRL_ID_REDO)
15634 button_type = GD_TYPE_NORMAL_BUTTON;
15636 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15640 button_type = GD_TYPE_CHECK_BUTTON;
15641 checked = (gamebutton_info[i].setup_value != NULL ?
15642 *gamebutton_info[i].setup_value : FALSE);
15643 event_mask = GD_EVENT_PRESSED;
15646 gi = CreateGadget(GDI_CUSTOM_ID, id,
15647 GDI_IMAGE_ID, graphic,
15648 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15651 GDI_WIDTH, gfx->width,
15652 GDI_HEIGHT, gfx->height,
15653 GDI_TYPE, button_type,
15654 GDI_STATE, GD_BUTTON_UNPRESSED,
15655 GDI_CHECKED, checked,
15656 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15657 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15658 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15659 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15660 GDI_DIRECT_DRAW, FALSE,
15661 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15662 GDI_EVENT_MASK, event_mask,
15663 GDI_CALLBACK_ACTION, HandleGameButtons,
15667 Error(ERR_EXIT, "cannot create gadget");
15669 game_gadget[id] = gi;
15673 void FreeGameButtons(void)
15677 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15678 FreeGadget(game_gadget[i]);
15681 static void UnmapGameButtonsAtSamePosition(int id)
15685 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15687 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15688 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15689 UnmapGadget(game_gadget[i]);
15692 static void UnmapGameButtonsAtSamePosition_All(void)
15694 if (setup.show_snapshot_buttons)
15696 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15697 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15698 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15702 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15703 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15704 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15706 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15707 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15708 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15712 static void MapGameButtonsAtSamePosition(int id)
15716 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15718 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15719 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15720 MapGadget(game_gadget[i]);
15722 UnmapGameButtonsAtSamePosition_All();
15725 void MapUndoRedoButtons(void)
15727 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15728 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15730 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15731 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15734 void UnmapUndoRedoButtons(void)
15736 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15737 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15739 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15740 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15743 void ModifyPauseButtons(void)
15747 GAME_CTRL_ID_PAUSE,
15748 GAME_CTRL_ID_PAUSE2,
15749 GAME_CTRL_ID_PANEL_PAUSE,
15750 GAME_CTRL_ID_TOUCH_PAUSE,
15755 for (i = 0; ids[i] > -1; i++)
15756 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15759 static void MapGameButtonsExt(boolean on_tape)
15763 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15764 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15765 i != GAME_CTRL_ID_UNDO &&
15766 i != GAME_CTRL_ID_REDO)
15767 MapGadget(game_gadget[i]);
15769 UnmapGameButtonsAtSamePosition_All();
15771 RedrawGameButtons();
15774 static void UnmapGameButtonsExt(boolean on_tape)
15778 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15779 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15780 UnmapGadget(game_gadget[i]);
15783 static void RedrawGameButtonsExt(boolean on_tape)
15787 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15788 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15789 RedrawGadget(game_gadget[i]);
15792 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15797 gi->checked = state;
15800 static void RedrawSoundButtonGadget(int id)
15802 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15803 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15804 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15805 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15806 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15807 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15810 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15811 RedrawGadget(game_gadget[id2]);
15814 void MapGameButtons(void)
15816 MapGameButtonsExt(FALSE);
15819 void UnmapGameButtons(void)
15821 UnmapGameButtonsExt(FALSE);
15824 void RedrawGameButtons(void)
15826 RedrawGameButtonsExt(FALSE);
15829 void MapGameButtonsOnTape(void)
15831 MapGameButtonsExt(TRUE);
15834 void UnmapGameButtonsOnTape(void)
15836 UnmapGameButtonsExt(TRUE);
15839 void RedrawGameButtonsOnTape(void)
15841 RedrawGameButtonsExt(TRUE);
15844 static void GameUndoRedoExt(void)
15846 ClearPlayerAction();
15848 tape.pausing = TRUE;
15851 UpdateAndDisplayGameControlValues();
15853 DrawCompleteVideoDisplay();
15854 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
15855 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
15856 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
15861 static void GameUndo(int steps)
15863 if (!CheckEngineSnapshotList())
15866 LoadEngineSnapshot_Undo(steps);
15871 static void GameRedo(int steps)
15873 if (!CheckEngineSnapshotList())
15876 LoadEngineSnapshot_Redo(steps);
15881 static void HandleGameButtonsExt(int id, int button)
15883 static boolean game_undo_executed = FALSE;
15884 int steps = BUTTON_STEPSIZE(button);
15885 boolean handle_game_buttons =
15886 (game_status == GAME_MODE_PLAYING ||
15887 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
15889 if (!handle_game_buttons)
15894 case GAME_CTRL_ID_STOP:
15895 case GAME_CTRL_ID_PANEL_STOP:
15896 case GAME_CTRL_ID_TOUCH_STOP:
15897 if (game_status == GAME_MODE_MAIN)
15903 RequestQuitGame(TRUE);
15907 case GAME_CTRL_ID_PAUSE:
15908 case GAME_CTRL_ID_PAUSE2:
15909 case GAME_CTRL_ID_PANEL_PAUSE:
15910 case GAME_CTRL_ID_TOUCH_PAUSE:
15911 if (network.enabled && game_status == GAME_MODE_PLAYING)
15914 SendToServer_ContinuePlaying();
15916 SendToServer_PausePlaying();
15919 TapeTogglePause(TAPE_TOGGLE_MANUAL);
15921 game_undo_executed = FALSE;
15925 case GAME_CTRL_ID_PLAY:
15926 case GAME_CTRL_ID_PANEL_PLAY:
15927 if (game_status == GAME_MODE_MAIN)
15929 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15931 else if (tape.pausing)
15933 if (network.enabled)
15934 SendToServer_ContinuePlaying();
15936 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
15940 case GAME_CTRL_ID_UNDO:
15941 // Important: When using "save snapshot when collecting an item" mode,
15942 // load last (current) snapshot for first "undo" after pressing "pause"
15943 // (else the last-but-one snapshot would be loaded, because the snapshot
15944 // pointer already points to the last snapshot when pressing "pause",
15945 // which is fine for "every step/move" mode, but not for "every collect")
15946 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15947 !game_undo_executed)
15950 game_undo_executed = TRUE;
15955 case GAME_CTRL_ID_REDO:
15959 case GAME_CTRL_ID_SAVE:
15963 case GAME_CTRL_ID_LOAD:
15967 case SOUND_CTRL_ID_MUSIC:
15968 case SOUND_CTRL_ID_PANEL_MUSIC:
15969 if (setup.sound_music)
15971 setup.sound_music = FALSE;
15975 else if (audio.music_available)
15977 setup.sound = setup.sound_music = TRUE;
15979 SetAudioMode(setup.sound);
15981 if (game_status == GAME_MODE_PLAYING)
15985 RedrawSoundButtonGadget(id);
15989 case SOUND_CTRL_ID_LOOPS:
15990 case SOUND_CTRL_ID_PANEL_LOOPS:
15991 if (setup.sound_loops)
15992 setup.sound_loops = FALSE;
15993 else if (audio.loops_available)
15995 setup.sound = setup.sound_loops = TRUE;
15997 SetAudioMode(setup.sound);
16000 RedrawSoundButtonGadget(id);
16004 case SOUND_CTRL_ID_SIMPLE:
16005 case SOUND_CTRL_ID_PANEL_SIMPLE:
16006 if (setup.sound_simple)
16007 setup.sound_simple = FALSE;
16008 else if (audio.sound_available)
16010 setup.sound = setup.sound_simple = TRUE;
16012 SetAudioMode(setup.sound);
16015 RedrawSoundButtonGadget(id);
16024 static void HandleGameButtons(struct GadgetInfo *gi)
16026 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16029 void HandleSoundButtonKeys(Key key)
16031 if (key == setup.shortcut.sound_simple)
16032 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16033 else if (key == setup.shortcut.sound_loops)
16034 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16035 else if (key == setup.shortcut.sound_music)
16036 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);