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;
3936 game.set_centered_player_fast = FALSE;
3938 if (network_playing && tape.recording)
3940 // store client dependent player focus when recording network games
3941 tape.centered_player_nr_next = game.centered_player_nr_next;
3942 tape.set_centered_player = TRUE;
3947 // when playing a tape, eliminate all players who do not participate
3949 #if USE_NEW_PLAYER_ASSIGNMENTS
3951 if (!game.team_mode)
3953 for (i = 0; i < MAX_PLAYERS; i++)
3955 if (stored_player[i].active &&
3956 !tape.player_participates[map_player_action[i]])
3958 struct PlayerInfo *player = &stored_player[i];
3959 int jx = player->jx, jy = player->jy;
3961 #if DEBUG_INIT_PLAYER
3963 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
3966 player->active = FALSE;
3967 StorePlayer[jx][jy] = 0;
3968 Feld[jx][jy] = EL_EMPTY;
3975 for (i = 0; i < MAX_PLAYERS; i++)
3977 if (stored_player[i].active &&
3978 !tape.player_participates[i])
3980 struct PlayerInfo *player = &stored_player[i];
3981 int jx = player->jx, jy = player->jy;
3983 player->active = FALSE;
3984 StorePlayer[jx][jy] = 0;
3985 Feld[jx][jy] = EL_EMPTY;
3990 else if (!network.enabled && !game.team_mode) // && !tape.playing
3992 // when in single player mode, eliminate all but the local player
3994 for (i = 0; i < MAX_PLAYERS; i++)
3996 struct PlayerInfo *player = &stored_player[i];
3998 if (player->active && player != local_player)
4000 int jx = player->jx, jy = player->jy;
4002 player->active = FALSE;
4003 player->present = FALSE;
4005 StorePlayer[jx][jy] = 0;
4006 Feld[jx][jy] = EL_EMPTY;
4011 for (i = 0; i < MAX_PLAYERS; i++)
4012 if (stored_player[i].active)
4013 game.players_still_needed++;
4015 if (level.solved_by_one_player)
4016 game.players_still_needed = 1;
4018 // when recording the game, store which players take part in the game
4021 #if USE_NEW_PLAYER_ASSIGNMENTS
4022 for (i = 0; i < MAX_PLAYERS; i++)
4023 if (stored_player[i].connected)
4024 tape.player_participates[i] = TRUE;
4026 for (i = 0; i < MAX_PLAYERS; i++)
4027 if (stored_player[i].active)
4028 tape.player_participates[i] = TRUE;
4032 #if DEBUG_INIT_PLAYER
4033 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4036 if (BorderElement == EL_EMPTY)
4039 SBX_Right = lev_fieldx - SCR_FIELDX;
4041 SBY_Lower = lev_fieldy - SCR_FIELDY;
4046 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4048 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4051 if (full_lev_fieldx <= SCR_FIELDX)
4052 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4053 if (full_lev_fieldy <= SCR_FIELDY)
4054 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4056 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4058 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4061 // if local player not found, look for custom element that might create
4062 // the player (make some assumptions about the right custom element)
4063 if (!local_player->present)
4065 int start_x = 0, start_y = 0;
4066 int found_rating = 0;
4067 int found_element = EL_UNDEFINED;
4068 int player_nr = local_player->index_nr;
4070 SCAN_PLAYFIELD(x, y)
4072 int element = Feld[x][y];
4077 if (level.use_start_element[player_nr] &&
4078 level.start_element[player_nr] == element &&
4085 found_element = element;
4088 if (!IS_CUSTOM_ELEMENT(element))
4091 if (CAN_CHANGE(element))
4093 for (i = 0; i < element_info[element].num_change_pages; i++)
4095 // check for player created from custom element as single target
4096 content = element_info[element].change_page[i].target_element;
4097 is_player = ELEM_IS_PLAYER(content);
4099 if (is_player && (found_rating < 3 ||
4100 (found_rating == 3 && element < found_element)))
4106 found_element = element;
4111 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4113 // check for player created from custom element as explosion content
4114 content = element_info[element].content.e[xx][yy];
4115 is_player = ELEM_IS_PLAYER(content);
4117 if (is_player && (found_rating < 2 ||
4118 (found_rating == 2 && element < found_element)))
4120 start_x = x + xx - 1;
4121 start_y = y + yy - 1;
4124 found_element = element;
4127 if (!CAN_CHANGE(element))
4130 for (i = 0; i < element_info[element].num_change_pages; i++)
4132 // check for player created from custom element as extended target
4134 element_info[element].change_page[i].target_content.e[xx][yy];
4136 is_player = ELEM_IS_PLAYER(content);
4138 if (is_player && (found_rating < 1 ||
4139 (found_rating == 1 && element < found_element)))
4141 start_x = x + xx - 1;
4142 start_y = y + yy - 1;
4145 found_element = element;
4151 scroll_x = SCROLL_POSITION_X(start_x);
4152 scroll_y = SCROLL_POSITION_Y(start_y);
4156 scroll_x = SCROLL_POSITION_X(local_player->jx);
4157 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4160 // !!! FIX THIS (START) !!!
4161 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4163 InitGameEngine_EM();
4165 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4167 InitGameEngine_SP();
4169 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4171 InitGameEngine_MM();
4175 DrawLevel(REDRAW_FIELD);
4178 // after drawing the level, correct some elements
4179 if (game.timegate_time_left == 0)
4180 CloseAllOpenTimegates();
4183 // blit playfield from scroll buffer to normal back buffer for fading in
4184 BlitScreenToBitmap(backbuffer);
4185 // !!! FIX THIS (END) !!!
4187 DrawMaskedBorder(fade_mask);
4192 // full screen redraw is required at this point in the following cases:
4193 // - special editor door undrawn when game was started from level editor
4194 // - drawing area (playfield) was changed and has to be removed completely
4195 redraw_mask = REDRAW_ALL;
4199 if (!game.restart_level)
4201 // copy default game door content to main double buffer
4203 // !!! CHECK AGAIN !!!
4204 SetPanelBackground();
4205 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4206 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4209 SetPanelBackground();
4210 SetDrawBackgroundMask(REDRAW_DOOR_1);
4212 UpdateAndDisplayGameControlValues();
4214 if (!game.restart_level)
4220 CreateGameButtons();
4225 // copy actual game door content to door double buffer for OpenDoor()
4226 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4228 OpenDoor(DOOR_OPEN_ALL);
4230 KeyboardAutoRepeatOffUnlessAutoplay();
4232 #if DEBUG_INIT_PLAYER
4233 DebugPrintPlayerStatus("Player status (final)");
4242 if (!game.restart_level && !tape.playing)
4244 LevelStats_incPlayed(level_nr);
4246 SaveLevelSetup_SeriesInfo();
4249 game.restart_level = FALSE;
4250 game.restart_game_message = NULL;
4251 game.request_active = FALSE;
4253 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4254 InitGameActions_MM();
4256 SaveEngineSnapshotToListInitial();
4258 if (!game.restart_level)
4260 PlaySound(SND_GAME_STARTING);
4262 if (setup.sound_music)
4267 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4268 int actual_player_x, int actual_player_y)
4270 // this is used for non-R'n'D game engines to update certain engine values
4272 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4274 actual_player_x = correctLevelPosX_EM(actual_player_x);
4275 actual_player_y = correctLevelPosY_EM(actual_player_y);
4278 // needed to determine if sounds are played within the visible screen area
4279 scroll_x = actual_scroll_x;
4280 scroll_y = actual_scroll_y;
4282 // needed to get player position for "follow finger" playing input method
4283 local_player->jx = actual_player_x;
4284 local_player->jy = actual_player_y;
4287 void InitMovDir(int x, int y)
4289 int i, element = Feld[x][y];
4290 static int xy[4][2] =
4297 static int direction[3][4] =
4299 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4300 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4301 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4310 Feld[x][y] = EL_BUG;
4311 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4314 case EL_SPACESHIP_RIGHT:
4315 case EL_SPACESHIP_UP:
4316 case EL_SPACESHIP_LEFT:
4317 case EL_SPACESHIP_DOWN:
4318 Feld[x][y] = EL_SPACESHIP;
4319 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4322 case EL_BD_BUTTERFLY_RIGHT:
4323 case EL_BD_BUTTERFLY_UP:
4324 case EL_BD_BUTTERFLY_LEFT:
4325 case EL_BD_BUTTERFLY_DOWN:
4326 Feld[x][y] = EL_BD_BUTTERFLY;
4327 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4330 case EL_BD_FIREFLY_RIGHT:
4331 case EL_BD_FIREFLY_UP:
4332 case EL_BD_FIREFLY_LEFT:
4333 case EL_BD_FIREFLY_DOWN:
4334 Feld[x][y] = EL_BD_FIREFLY;
4335 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4338 case EL_PACMAN_RIGHT:
4340 case EL_PACMAN_LEFT:
4341 case EL_PACMAN_DOWN:
4342 Feld[x][y] = EL_PACMAN;
4343 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4346 case EL_YAMYAM_LEFT:
4347 case EL_YAMYAM_RIGHT:
4349 case EL_YAMYAM_DOWN:
4350 Feld[x][y] = EL_YAMYAM;
4351 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4354 case EL_SP_SNIKSNAK:
4355 MovDir[x][y] = MV_UP;
4358 case EL_SP_ELECTRON:
4359 MovDir[x][y] = MV_LEFT;
4366 Feld[x][y] = EL_MOLE;
4367 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4371 if (IS_CUSTOM_ELEMENT(element))
4373 struct ElementInfo *ei = &element_info[element];
4374 int move_direction_initial = ei->move_direction_initial;
4375 int move_pattern = ei->move_pattern;
4377 if (move_direction_initial == MV_START_PREVIOUS)
4379 if (MovDir[x][y] != MV_NONE)
4382 move_direction_initial = MV_START_AUTOMATIC;
4385 if (move_direction_initial == MV_START_RANDOM)
4386 MovDir[x][y] = 1 << RND(4);
4387 else if (move_direction_initial & MV_ANY_DIRECTION)
4388 MovDir[x][y] = move_direction_initial;
4389 else if (move_pattern == MV_ALL_DIRECTIONS ||
4390 move_pattern == MV_TURNING_LEFT ||
4391 move_pattern == MV_TURNING_RIGHT ||
4392 move_pattern == MV_TURNING_LEFT_RIGHT ||
4393 move_pattern == MV_TURNING_RIGHT_LEFT ||
4394 move_pattern == MV_TURNING_RANDOM)
4395 MovDir[x][y] = 1 << RND(4);
4396 else if (move_pattern == MV_HORIZONTAL)
4397 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4398 else if (move_pattern == MV_VERTICAL)
4399 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4400 else if (move_pattern & MV_ANY_DIRECTION)
4401 MovDir[x][y] = element_info[element].move_pattern;
4402 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4403 move_pattern == MV_ALONG_RIGHT_SIDE)
4405 // use random direction as default start direction
4406 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4407 MovDir[x][y] = 1 << RND(4);
4409 for (i = 0; i < NUM_DIRECTIONS; i++)
4411 int x1 = x + xy[i][0];
4412 int y1 = y + xy[i][1];
4414 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4416 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4417 MovDir[x][y] = direction[0][i];
4419 MovDir[x][y] = direction[1][i];
4428 MovDir[x][y] = 1 << RND(4);
4430 if (element != EL_BUG &&
4431 element != EL_SPACESHIP &&
4432 element != EL_BD_BUTTERFLY &&
4433 element != EL_BD_FIREFLY)
4436 for (i = 0; i < NUM_DIRECTIONS; i++)
4438 int x1 = x + xy[i][0];
4439 int y1 = y + xy[i][1];
4441 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4443 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4445 MovDir[x][y] = direction[0][i];
4448 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4449 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4451 MovDir[x][y] = direction[1][i];
4460 GfxDir[x][y] = MovDir[x][y];
4463 void InitAmoebaNr(int x, int y)
4466 int group_nr = AmoebeNachbarNr(x, y);
4470 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4472 if (AmoebaCnt[i] == 0)
4480 AmoebaNr[x][y] = group_nr;
4481 AmoebaCnt[group_nr]++;
4482 AmoebaCnt2[group_nr]++;
4485 static void LevelSolved(void)
4487 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4488 game.players_still_needed > 0)
4491 game.LevelSolved = TRUE;
4492 game.GameOver = TRUE;
4494 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4495 level.native_em_level->lev->score :
4496 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4499 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4500 MM_HEALTH(game_mm.laser_overload_value) :
4503 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4504 game.LevelSolved_CountingScore = game.score_final;
4505 game.LevelSolved_CountingHealth = game.health_final;
4510 static int time_count_steps;
4511 static int time, time_final;
4512 static int score, score_final;
4513 static int health, health_final;
4514 static int game_over_delay_1 = 0;
4515 static int game_over_delay_2 = 0;
4516 static int game_over_delay_3 = 0;
4517 int game_over_delay_value_1 = 50;
4518 int game_over_delay_value_2 = 25;
4519 int game_over_delay_value_3 = 50;
4521 if (!game.LevelSolved_GameWon)
4525 // do not start end game actions before the player stops moving (to exit)
4526 if (local_player->active && local_player->MovPos)
4529 game.LevelSolved_GameWon = TRUE;
4530 game.LevelSolved_SaveTape = tape.recording;
4531 game.LevelSolved_SaveScore = !tape.playing;
4535 LevelStats_incSolved(level_nr);
4537 SaveLevelSetup_SeriesInfo();
4540 if (tape.auto_play) // tape might already be stopped here
4541 tape.auto_play_level_solved = TRUE;
4545 game_over_delay_1 = 0;
4546 game_over_delay_2 = 0;
4547 game_over_delay_3 = game_over_delay_value_3;
4549 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4550 score = score_final = game.score_final;
4551 health = health_final = game.health_final;
4553 if (level.score[SC_TIME_BONUS] > 0)
4558 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4560 else if (game.no_time_limit && TimePlayed < 999)
4563 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4566 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4568 game_over_delay_1 = game_over_delay_value_1;
4570 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4573 score_final += health * level.score[SC_TIME_BONUS];
4575 game_over_delay_2 = game_over_delay_value_2;
4578 game.score_final = score_final;
4579 game.health_final = health_final;
4582 if (level_editor_test_game)
4585 score = score_final;
4587 game.LevelSolved_CountingTime = time;
4588 game.LevelSolved_CountingScore = score;
4590 game_panel_controls[GAME_PANEL_TIME].value = time;
4591 game_panel_controls[GAME_PANEL_SCORE].value = score;
4593 DisplayGameControlValues();
4596 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4598 // check if last player has left the level
4599 if (game.exit_x >= 0 &&
4602 int x = game.exit_x;
4603 int y = game.exit_y;
4604 int element = Feld[x][y];
4606 // close exit door after last player
4607 if ((game.all_players_gone &&
4608 (element == EL_EXIT_OPEN ||
4609 element == EL_SP_EXIT_OPEN ||
4610 element == EL_STEEL_EXIT_OPEN)) ||
4611 element == EL_EM_EXIT_OPEN ||
4612 element == EL_EM_STEEL_EXIT_OPEN)
4616 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4617 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4618 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4619 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4620 EL_EM_STEEL_EXIT_CLOSING);
4622 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4625 // player disappears
4626 DrawLevelField(x, y);
4629 for (i = 0; i < MAX_PLAYERS; i++)
4631 struct PlayerInfo *player = &stored_player[i];
4633 if (player->present)
4635 RemovePlayer(player);
4637 // player disappears
4638 DrawLevelField(player->jx, player->jy);
4643 PlaySound(SND_GAME_WINNING);
4646 if (game_over_delay_1 > 0)
4648 game_over_delay_1--;
4653 if (time != time_final)
4655 int time_to_go = ABS(time_final - time);
4656 int time_count_dir = (time < time_final ? +1 : -1);
4658 if (time_to_go < time_count_steps)
4659 time_count_steps = 1;
4661 time += time_count_steps * time_count_dir;
4662 score += time_count_steps * level.score[SC_TIME_BONUS];
4664 game.LevelSolved_CountingTime = time;
4665 game.LevelSolved_CountingScore = score;
4667 game_panel_controls[GAME_PANEL_TIME].value = time;
4668 game_panel_controls[GAME_PANEL_SCORE].value = score;
4670 DisplayGameControlValues();
4672 if (time == time_final)
4673 StopSound(SND_GAME_LEVELTIME_BONUS);
4674 else if (setup.sound_loops)
4675 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4677 PlaySound(SND_GAME_LEVELTIME_BONUS);
4682 if (game_over_delay_2 > 0)
4684 game_over_delay_2--;
4689 if (health != health_final)
4691 int health_count_dir = (health < health_final ? +1 : -1);
4693 health += health_count_dir;
4694 score += level.score[SC_TIME_BONUS];
4696 game.LevelSolved_CountingHealth = health;
4697 game.LevelSolved_CountingScore = score;
4699 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4700 game_panel_controls[GAME_PANEL_SCORE].value = score;
4702 DisplayGameControlValues();
4704 if (health == health_final)
4705 StopSound(SND_GAME_LEVELTIME_BONUS);
4706 else if (setup.sound_loops)
4707 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4709 PlaySound(SND_GAME_LEVELTIME_BONUS);
4714 game.panel.active = FALSE;
4716 if (game_over_delay_3 > 0)
4718 game_over_delay_3--;
4728 // used instead of "level_nr" (needed for network games)
4729 int last_level_nr = levelset.level_nr;
4732 game.LevelSolved_GameEnd = TRUE;
4734 if (game.LevelSolved_SaveTape)
4736 // make sure that request dialog to save tape does not open door again
4737 if (!global.use_envelope_request)
4738 CloseDoor(DOOR_CLOSE_1);
4740 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4743 // if no tape is to be saved, close both doors simultaneously
4744 CloseDoor(DOOR_CLOSE_ALL);
4746 if (level_editor_test_game)
4748 SetGameStatus(GAME_MODE_MAIN);
4755 if (!game.LevelSolved_SaveScore)
4757 SetGameStatus(GAME_MODE_MAIN);
4764 if (level_nr == leveldir_current->handicap_level)
4766 leveldir_current->handicap_level++;
4768 SaveLevelSetup_SeriesInfo();
4771 if (setup.increment_levels &&
4772 level_nr < leveldir_current->last_level &&
4775 level_nr++; // advance to next level
4776 TapeErase(); // start with empty tape
4778 if (setup.auto_play_next_level)
4780 LoadLevel(level_nr);
4782 SaveLevelSetup_SeriesInfo();
4786 hi_pos = NewHiScore(last_level_nr);
4788 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4790 SetGameStatus(GAME_MODE_SCORES);
4792 DrawHallOfFame(last_level_nr, hi_pos);
4794 else if (setup.auto_play_next_level && setup.increment_levels &&
4795 last_level_nr < leveldir_current->last_level &&
4798 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4802 SetGameStatus(GAME_MODE_MAIN);
4808 int NewHiScore(int level_nr)
4812 boolean one_score_entry_per_name = !program.many_scores_per_name;
4814 LoadScore(level_nr);
4816 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4817 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4820 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4822 if (game.score_final > highscore[k].Score)
4824 // player has made it to the hall of fame
4826 if (k < MAX_SCORE_ENTRIES - 1)
4828 int m = MAX_SCORE_ENTRIES - 1;
4830 if (one_score_entry_per_name)
4832 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4833 if (strEqual(setup.player_name, highscore[l].Name))
4836 if (m == k) // player's new highscore overwrites his old one
4840 for (l = m; l > k; l--)
4842 strcpy(highscore[l].Name, highscore[l - 1].Name);
4843 highscore[l].Score = highscore[l - 1].Score;
4849 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4850 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4851 highscore[k].Score = game.score_final;
4856 else if (one_score_entry_per_name &&
4857 !strncmp(setup.player_name, highscore[k].Name,
4858 MAX_PLAYER_NAME_LEN))
4859 break; // player already there with a higher score
4863 SaveScore(level_nr);
4868 static int getElementMoveStepsizeExt(int x, int y, int direction)
4870 int element = Feld[x][y];
4871 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4872 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4873 int horiz_move = (dx != 0);
4874 int sign = (horiz_move ? dx : dy);
4875 int step = sign * element_info[element].move_stepsize;
4877 // special values for move stepsize for spring and things on conveyor belt
4880 if (CAN_FALL(element) &&
4881 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
4882 step = sign * MOVE_STEPSIZE_NORMAL / 2;
4883 else if (element == EL_SPRING)
4884 step = sign * MOVE_STEPSIZE_NORMAL * 2;
4890 static int getElementMoveStepsize(int x, int y)
4892 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
4895 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
4897 if (player->GfxAction != action || player->GfxDir != dir)
4899 player->GfxAction = action;
4900 player->GfxDir = dir;
4902 player->StepFrame = 0;
4906 static void ResetGfxFrame(int x, int y)
4908 // profiling showed that "autotest" spends 10~20% of its time in this function
4909 if (DrawingDeactivatedField())
4912 int element = Feld[x][y];
4913 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
4915 if (graphic_info[graphic].anim_global_sync)
4916 GfxFrame[x][y] = FrameCounter;
4917 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
4918 GfxFrame[x][y] = CustomValue[x][y];
4919 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
4920 GfxFrame[x][y] = element_info[element].collect_score;
4921 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
4922 GfxFrame[x][y] = ChangeDelay[x][y];
4925 static void ResetGfxAnimation(int x, int y)
4927 GfxAction[x][y] = ACTION_DEFAULT;
4928 GfxDir[x][y] = MovDir[x][y];
4931 ResetGfxFrame(x, y);
4934 static void ResetRandomAnimationValue(int x, int y)
4936 GfxRandom[x][y] = INIT_GFX_RANDOM();
4939 static void InitMovingField(int x, int y, int direction)
4941 int element = Feld[x][y];
4942 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4943 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4946 boolean is_moving_before, is_moving_after;
4948 // check if element was/is moving or being moved before/after mode change
4949 is_moving_before = (WasJustMoving[x][y] != 0);
4950 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
4952 // reset animation only for moving elements which change direction of moving
4953 // or which just started or stopped moving
4954 // (else CEs with property "can move" / "not moving" are reset each frame)
4955 if (is_moving_before != is_moving_after ||
4956 direction != MovDir[x][y])
4957 ResetGfxAnimation(x, y);
4959 MovDir[x][y] = direction;
4960 GfxDir[x][y] = direction;
4962 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
4963 direction == MV_DOWN && CAN_FALL(element) ?
4964 ACTION_FALLING : ACTION_MOVING);
4966 // this is needed for CEs with property "can move" / "not moving"
4968 if (is_moving_after)
4970 if (Feld[newx][newy] == EL_EMPTY)
4971 Feld[newx][newy] = EL_BLOCKED;
4973 MovDir[newx][newy] = MovDir[x][y];
4975 CustomValue[newx][newy] = CustomValue[x][y];
4977 GfxFrame[newx][newy] = GfxFrame[x][y];
4978 GfxRandom[newx][newy] = GfxRandom[x][y];
4979 GfxAction[newx][newy] = GfxAction[x][y];
4980 GfxDir[newx][newy] = GfxDir[x][y];
4984 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
4986 int direction = MovDir[x][y];
4987 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
4988 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
4994 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
4996 int oldx = x, oldy = y;
4997 int direction = MovDir[x][y];
4999 if (direction == MV_LEFT)
5001 else if (direction == MV_RIGHT)
5003 else if (direction == MV_UP)
5005 else if (direction == MV_DOWN)
5008 *comes_from_x = oldx;
5009 *comes_from_y = oldy;
5012 static int MovingOrBlocked2Element(int x, int y)
5014 int element = Feld[x][y];
5016 if (element == EL_BLOCKED)
5020 Blocked2Moving(x, y, &oldx, &oldy);
5021 return Feld[oldx][oldy];
5027 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5029 // like MovingOrBlocked2Element(), but if element is moving
5030 // and (x,y) is the field the moving element is just leaving,
5031 // return EL_BLOCKED instead of the element value
5032 int element = Feld[x][y];
5034 if (IS_MOVING(x, y))
5036 if (element == EL_BLOCKED)
5040 Blocked2Moving(x, y, &oldx, &oldy);
5041 return Feld[oldx][oldy];
5050 static void RemoveField(int x, int y)
5052 Feld[x][y] = EL_EMPTY;
5058 CustomValue[x][y] = 0;
5061 ChangeDelay[x][y] = 0;
5062 ChangePage[x][y] = -1;
5063 Pushed[x][y] = FALSE;
5065 GfxElement[x][y] = EL_UNDEFINED;
5066 GfxAction[x][y] = ACTION_DEFAULT;
5067 GfxDir[x][y] = MV_NONE;
5070 static void RemoveMovingField(int x, int y)
5072 int oldx = x, oldy = y, newx = x, newy = y;
5073 int element = Feld[x][y];
5074 int next_element = EL_UNDEFINED;
5076 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5079 if (IS_MOVING(x, y))
5081 Moving2Blocked(x, y, &newx, &newy);
5083 if (Feld[newx][newy] != EL_BLOCKED)
5085 // element is moving, but target field is not free (blocked), but
5086 // already occupied by something different (example: acid pool);
5087 // in this case, only remove the moving field, but not the target
5089 RemoveField(oldx, oldy);
5091 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5093 TEST_DrawLevelField(oldx, oldy);
5098 else if (element == EL_BLOCKED)
5100 Blocked2Moving(x, y, &oldx, &oldy);
5101 if (!IS_MOVING(oldx, oldy))
5105 if (element == EL_BLOCKED &&
5106 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5107 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5108 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5109 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5110 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5111 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5112 next_element = get_next_element(Feld[oldx][oldy]);
5114 RemoveField(oldx, oldy);
5115 RemoveField(newx, newy);
5117 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5119 if (next_element != EL_UNDEFINED)
5120 Feld[oldx][oldy] = next_element;
5122 TEST_DrawLevelField(oldx, oldy);
5123 TEST_DrawLevelField(newx, newy);
5126 void DrawDynamite(int x, int y)
5128 int sx = SCREENX(x), sy = SCREENY(y);
5129 int graphic = el2img(Feld[x][y]);
5132 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5135 if (IS_WALKABLE_INSIDE(Back[x][y]))
5139 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5140 else if (Store[x][y])
5141 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5143 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5145 if (Back[x][y] || Store[x][y])
5146 DrawGraphicThruMask(sx, sy, graphic, frame);
5148 DrawGraphic(sx, sy, graphic, frame);
5151 static void CheckDynamite(int x, int y)
5153 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5157 if (MovDelay[x][y] != 0)
5160 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5166 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5171 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5173 boolean num_checked_players = 0;
5176 for (i = 0; i < MAX_PLAYERS; i++)
5178 if (stored_player[i].active)
5180 int sx = stored_player[i].jx;
5181 int sy = stored_player[i].jy;
5183 if (num_checked_players == 0)
5190 *sx1 = MIN(*sx1, sx);
5191 *sy1 = MIN(*sy1, sy);
5192 *sx2 = MAX(*sx2, sx);
5193 *sy2 = MAX(*sy2, sy);
5196 num_checked_players++;
5201 static boolean checkIfAllPlayersFitToScreen_RND(void)
5203 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5205 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5207 return (sx2 - sx1 < SCR_FIELDX &&
5208 sy2 - sy1 < SCR_FIELDY);
5211 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5213 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5215 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5217 *sx = (sx1 + sx2) / 2;
5218 *sy = (sy1 + sy2) / 2;
5221 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5222 boolean center_screen, boolean quick_relocation)
5224 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5225 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5226 boolean no_delay = (tape.warp_forward);
5227 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5228 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5229 int new_scroll_x, new_scroll_y;
5231 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5233 // case 1: quick relocation inside visible screen (without scrolling)
5240 if (!level.shifted_relocation || center_screen)
5242 // relocation _with_ centering of screen
5244 new_scroll_x = SCROLL_POSITION_X(x);
5245 new_scroll_y = SCROLL_POSITION_Y(y);
5249 // relocation _without_ centering of screen
5251 int center_scroll_x = SCROLL_POSITION_X(old_x);
5252 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5253 int offset_x = x + (scroll_x - center_scroll_x);
5254 int offset_y = y + (scroll_y - center_scroll_y);
5256 // for new screen position, apply previous offset to center position
5257 new_scroll_x = SCROLL_POSITION_X(offset_x);
5258 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5261 if (quick_relocation)
5263 // case 2: quick relocation (redraw without visible scrolling)
5265 scroll_x = new_scroll_x;
5266 scroll_y = new_scroll_y;
5273 // case 3: visible relocation (with scrolling to new position)
5275 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5277 SetVideoFrameDelay(wait_delay_value);
5279 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5281 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5282 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5284 if (dx == 0 && dy == 0) // no scrolling needed at all
5290 // set values for horizontal/vertical screen scrolling (half tile size)
5291 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5292 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5293 int pos_x = dx * TILEX / 2;
5294 int pos_y = dy * TILEY / 2;
5295 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5296 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5298 ScrollLevel(dx, dy);
5301 // scroll in two steps of half tile size to make things smoother
5302 BlitScreenToBitmapExt_RND(window, fx, fy);
5304 // scroll second step to align at full tile size
5305 BlitScreenToBitmap(window);
5311 SetVideoFrameDelay(frame_delay_value_old);
5314 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5316 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5317 int player_nr = GET_PLAYER_NR(el_player);
5318 struct PlayerInfo *player = &stored_player[player_nr];
5319 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5320 boolean no_delay = (tape.warp_forward);
5321 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5322 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5323 int old_jx = player->jx;
5324 int old_jy = player->jy;
5325 int old_element = Feld[old_jx][old_jy];
5326 int element = Feld[jx][jy];
5327 boolean player_relocated = (old_jx != jx || old_jy != jy);
5329 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5330 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5331 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5332 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5333 int leave_side_horiz = move_dir_horiz;
5334 int leave_side_vert = move_dir_vert;
5335 int enter_side = enter_side_horiz | enter_side_vert;
5336 int leave_side = leave_side_horiz | leave_side_vert;
5338 if (player->buried) // do not reanimate dead player
5341 if (!player_relocated) // no need to relocate the player
5344 if (IS_PLAYER(jx, jy)) // player already placed at new position
5346 RemoveField(jx, jy); // temporarily remove newly placed player
5347 DrawLevelField(jx, jy);
5350 if (player->present)
5352 while (player->MovPos)
5354 ScrollPlayer(player, SCROLL_GO_ON);
5355 ScrollScreen(NULL, SCROLL_GO_ON);
5357 AdvanceFrameAndPlayerCounters(player->index_nr);
5361 BackToFront_WithFrameDelay(wait_delay_value);
5364 DrawPlayer(player); // needed here only to cleanup last field
5365 DrawLevelField(player->jx, player->jy); // remove player graphic
5367 player->is_moving = FALSE;
5370 if (IS_CUSTOM_ELEMENT(old_element))
5371 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5373 player->index_bit, leave_side);
5375 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5377 player->index_bit, leave_side);
5379 Feld[jx][jy] = el_player;
5380 InitPlayerField(jx, jy, el_player, TRUE);
5382 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5383 possible that the relocation target field did not contain a player element,
5384 but a walkable element, to which the new player was relocated -- in this
5385 case, restore that (already initialized!) element on the player field */
5386 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5388 Feld[jx][jy] = element; // restore previously existing element
5391 // only visually relocate centered player
5392 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5393 FALSE, level.instant_relocation);
5395 TestIfPlayerTouchesBadThing(jx, jy);
5396 TestIfPlayerTouchesCustomElement(jx, jy);
5398 if (IS_CUSTOM_ELEMENT(element))
5399 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5400 player->index_bit, enter_side);
5402 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5403 player->index_bit, enter_side);
5405 if (player->is_switching)
5407 /* ensure that relocation while still switching an element does not cause
5408 a new element to be treated as also switched directly after relocation
5409 (this is important for teleporter switches that teleport the player to
5410 a place where another teleporter switch is in the same direction, which
5411 would then incorrectly be treated as immediately switched before the
5412 direction key that caused the switch was released) */
5414 player->switch_x += jx - old_jx;
5415 player->switch_y += jy - old_jy;
5419 static void Explode(int ex, int ey, int phase, int mode)
5425 // !!! eliminate this variable !!!
5426 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5428 if (game.explosions_delayed)
5430 ExplodeField[ex][ey] = mode;
5434 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5436 int center_element = Feld[ex][ey];
5437 int artwork_element, explosion_element; // set these values later
5439 // remove things displayed in background while burning dynamite
5440 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5443 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5445 // put moving element to center field (and let it explode there)
5446 center_element = MovingOrBlocked2Element(ex, ey);
5447 RemoveMovingField(ex, ey);
5448 Feld[ex][ey] = center_element;
5451 // now "center_element" is finally determined -- set related values now
5452 artwork_element = center_element; // for custom player artwork
5453 explosion_element = center_element; // for custom player artwork
5455 if (IS_PLAYER(ex, ey))
5457 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5459 artwork_element = stored_player[player_nr].artwork_element;
5461 if (level.use_explosion_element[player_nr])
5463 explosion_element = level.explosion_element[player_nr];
5464 artwork_element = explosion_element;
5468 if (mode == EX_TYPE_NORMAL ||
5469 mode == EX_TYPE_CENTER ||
5470 mode == EX_TYPE_CROSS)
5471 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5473 last_phase = element_info[explosion_element].explosion_delay + 1;
5475 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5477 int xx = x - ex + 1;
5478 int yy = y - ey + 1;
5481 if (!IN_LEV_FIELD(x, y) ||
5482 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5483 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5486 element = Feld[x][y];
5488 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5490 element = MovingOrBlocked2Element(x, y);
5492 if (!IS_EXPLOSION_PROOF(element))
5493 RemoveMovingField(x, y);
5496 // indestructible elements can only explode in center (but not flames)
5497 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5498 mode == EX_TYPE_BORDER)) ||
5499 element == EL_FLAMES)
5502 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5503 behaviour, for example when touching a yamyam that explodes to rocks
5504 with active deadly shield, a rock is created under the player !!! */
5505 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5507 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5508 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5509 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5511 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5514 if (IS_ACTIVE_BOMB(element))
5516 // re-activate things under the bomb like gate or penguin
5517 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5524 // save walkable background elements while explosion on same tile
5525 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5526 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5527 Back[x][y] = element;
5529 // ignite explodable elements reached by other explosion
5530 if (element == EL_EXPLOSION)
5531 element = Store2[x][y];
5533 if (AmoebaNr[x][y] &&
5534 (element == EL_AMOEBA_FULL ||
5535 element == EL_BD_AMOEBA ||
5536 element == EL_AMOEBA_GROWING))
5538 AmoebaCnt[AmoebaNr[x][y]]--;
5539 AmoebaCnt2[AmoebaNr[x][y]]--;
5544 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5546 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5548 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5550 if (PLAYERINFO(ex, ey)->use_murphy)
5551 Store[x][y] = EL_EMPTY;
5554 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5555 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5556 else if (ELEM_IS_PLAYER(center_element))
5557 Store[x][y] = EL_EMPTY;
5558 else if (center_element == EL_YAMYAM)
5559 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5560 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5561 Store[x][y] = element_info[center_element].content.e[xx][yy];
5563 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5564 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5565 // otherwise) -- FIX THIS !!!
5566 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5567 Store[x][y] = element_info[element].content.e[1][1];
5569 else if (!CAN_EXPLODE(element))
5570 Store[x][y] = element_info[element].content.e[1][1];
5573 Store[x][y] = EL_EMPTY;
5575 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5576 center_element == EL_AMOEBA_TO_DIAMOND)
5577 Store2[x][y] = element;
5579 Feld[x][y] = EL_EXPLOSION;
5580 GfxElement[x][y] = artwork_element;
5582 ExplodePhase[x][y] = 1;
5583 ExplodeDelay[x][y] = last_phase;
5588 if (center_element == EL_YAMYAM)
5589 game.yamyam_content_nr =
5590 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5602 GfxFrame[x][y] = 0; // restart explosion animation
5604 last_phase = ExplodeDelay[x][y];
5606 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5608 // this can happen if the player leaves an explosion just in time
5609 if (GfxElement[x][y] == EL_UNDEFINED)
5610 GfxElement[x][y] = EL_EMPTY;
5612 border_element = Store2[x][y];
5613 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5614 border_element = StorePlayer[x][y];
5616 if (phase == element_info[border_element].ignition_delay ||
5617 phase == last_phase)
5619 boolean border_explosion = FALSE;
5621 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5622 !PLAYER_EXPLOSION_PROTECTED(x, y))
5624 KillPlayerUnlessExplosionProtected(x, y);
5625 border_explosion = TRUE;
5627 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5629 Feld[x][y] = Store2[x][y];
5632 border_explosion = TRUE;
5634 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5636 AmoebeUmwandeln(x, y);
5638 border_explosion = TRUE;
5641 // if an element just explodes due to another explosion (chain-reaction),
5642 // do not immediately end the new explosion when it was the last frame of
5643 // the explosion (as it would be done in the following "if"-statement!)
5644 if (border_explosion && phase == last_phase)
5648 if (phase == last_phase)
5652 element = Feld[x][y] = Store[x][y];
5653 Store[x][y] = Store2[x][y] = 0;
5654 GfxElement[x][y] = EL_UNDEFINED;
5656 // player can escape from explosions and might therefore be still alive
5657 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5658 element <= EL_PLAYER_IS_EXPLODING_4)
5660 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5661 int explosion_element = EL_PLAYER_1 + player_nr;
5662 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5663 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5665 if (level.use_explosion_element[player_nr])
5666 explosion_element = level.explosion_element[player_nr];
5668 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5669 element_info[explosion_element].content.e[xx][yy]);
5672 // restore probably existing indestructible background element
5673 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5674 element = Feld[x][y] = Back[x][y];
5677 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5678 GfxDir[x][y] = MV_NONE;
5679 ChangeDelay[x][y] = 0;
5680 ChangePage[x][y] = -1;
5682 CustomValue[x][y] = 0;
5684 InitField_WithBug2(x, y, FALSE);
5686 TEST_DrawLevelField(x, y);
5688 TestIfElementTouchesCustomElement(x, y);
5690 if (GFX_CRUMBLED(element))
5691 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5693 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5694 StorePlayer[x][y] = 0;
5696 if (ELEM_IS_PLAYER(element))
5697 RelocatePlayer(x, y, element);
5699 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5701 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5702 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5705 TEST_DrawLevelFieldCrumbled(x, y);
5707 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5709 DrawLevelElement(x, y, Back[x][y]);
5710 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5712 else if (IS_WALKABLE_UNDER(Back[x][y]))
5714 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5715 DrawLevelElementThruMask(x, y, Back[x][y]);
5717 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5718 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5722 static void DynaExplode(int ex, int ey)
5725 int dynabomb_element = Feld[ex][ey];
5726 int dynabomb_size = 1;
5727 boolean dynabomb_xl = FALSE;
5728 struct PlayerInfo *player;
5729 static int xy[4][2] =
5737 if (IS_ACTIVE_BOMB(dynabomb_element))
5739 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5740 dynabomb_size = player->dynabomb_size;
5741 dynabomb_xl = player->dynabomb_xl;
5742 player->dynabombs_left++;
5745 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5747 for (i = 0; i < NUM_DIRECTIONS; i++)
5749 for (j = 1; j <= dynabomb_size; j++)
5751 int x = ex + j * xy[i][0];
5752 int y = ey + j * xy[i][1];
5755 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5758 element = Feld[x][y];
5760 // do not restart explosions of fields with active bombs
5761 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5764 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5766 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5767 !IS_DIGGABLE(element) && !dynabomb_xl)
5773 void Bang(int x, int y)
5775 int element = MovingOrBlocked2Element(x, y);
5776 int explosion_type = EX_TYPE_NORMAL;
5778 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5780 struct PlayerInfo *player = PLAYERINFO(x, y);
5782 element = Feld[x][y] = player->initial_element;
5784 if (level.use_explosion_element[player->index_nr])
5786 int explosion_element = level.explosion_element[player->index_nr];
5788 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5789 explosion_type = EX_TYPE_CROSS;
5790 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5791 explosion_type = EX_TYPE_CENTER;
5799 case EL_BD_BUTTERFLY:
5802 case EL_DARK_YAMYAM:
5806 RaiseScoreElement(element);
5809 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5810 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5811 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5812 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5813 case EL_DYNABOMB_INCREASE_NUMBER:
5814 case EL_DYNABOMB_INCREASE_SIZE:
5815 case EL_DYNABOMB_INCREASE_POWER:
5816 explosion_type = EX_TYPE_DYNA;
5819 case EL_DC_LANDMINE:
5820 explosion_type = EX_TYPE_CENTER;
5825 case EL_LAMP_ACTIVE:
5826 case EL_AMOEBA_TO_DIAMOND:
5827 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5828 explosion_type = EX_TYPE_CENTER;
5832 if (element_info[element].explosion_type == EXPLODES_CROSS)
5833 explosion_type = EX_TYPE_CROSS;
5834 else if (element_info[element].explosion_type == EXPLODES_1X1)
5835 explosion_type = EX_TYPE_CENTER;
5839 if (explosion_type == EX_TYPE_DYNA)
5842 Explode(x, y, EX_PHASE_START, explosion_type);
5844 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5847 static void SplashAcid(int x, int y)
5849 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5850 (!IN_LEV_FIELD(x - 1, y - 2) ||
5851 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5852 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5854 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5855 (!IN_LEV_FIELD(x + 1, y - 2) ||
5856 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5857 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5859 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5862 static void InitBeltMovement(void)
5864 static int belt_base_element[4] =
5866 EL_CONVEYOR_BELT_1_LEFT,
5867 EL_CONVEYOR_BELT_2_LEFT,
5868 EL_CONVEYOR_BELT_3_LEFT,
5869 EL_CONVEYOR_BELT_4_LEFT
5871 static int belt_base_active_element[4] =
5873 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5874 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5875 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5876 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5881 // set frame order for belt animation graphic according to belt direction
5882 for (i = 0; i < NUM_BELTS; i++)
5886 for (j = 0; j < NUM_BELT_PARTS; j++)
5888 int element = belt_base_active_element[belt_nr] + j;
5889 int graphic_1 = el2img(element);
5890 int graphic_2 = el2panelimg(element);
5892 if (game.belt_dir[i] == MV_LEFT)
5894 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5895 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5899 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5900 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5905 SCAN_PLAYFIELD(x, y)
5907 int element = Feld[x][y];
5909 for (i = 0; i < NUM_BELTS; i++)
5911 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
5913 int e_belt_nr = getBeltNrFromBeltElement(element);
5916 if (e_belt_nr == belt_nr)
5918 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
5920 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
5927 static void ToggleBeltSwitch(int x, int y)
5929 static int belt_base_element[4] =
5931 EL_CONVEYOR_BELT_1_LEFT,
5932 EL_CONVEYOR_BELT_2_LEFT,
5933 EL_CONVEYOR_BELT_3_LEFT,
5934 EL_CONVEYOR_BELT_4_LEFT
5936 static int belt_base_active_element[4] =
5938 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5939 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5940 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5941 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5943 static int belt_base_switch_element[4] =
5945 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
5946 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
5947 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
5948 EL_CONVEYOR_BELT_4_SWITCH_LEFT
5950 static int belt_move_dir[4] =
5958 int element = Feld[x][y];
5959 int belt_nr = getBeltNrFromBeltSwitchElement(element);
5960 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
5961 int belt_dir = belt_move_dir[belt_dir_nr];
5964 if (!IS_BELT_SWITCH(element))
5967 game.belt_dir_nr[belt_nr] = belt_dir_nr;
5968 game.belt_dir[belt_nr] = belt_dir;
5970 if (belt_dir_nr == 3)
5973 // set frame order for belt animation graphic according to belt direction
5974 for (i = 0; i < NUM_BELT_PARTS; i++)
5976 int element = belt_base_active_element[belt_nr] + i;
5977 int graphic_1 = el2img(element);
5978 int graphic_2 = el2panelimg(element);
5980 if (belt_dir == MV_LEFT)
5982 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5983 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5987 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5988 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5992 SCAN_PLAYFIELD(xx, yy)
5994 int element = Feld[xx][yy];
5996 if (IS_BELT_SWITCH(element))
5998 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6000 if (e_belt_nr == belt_nr)
6002 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6003 TEST_DrawLevelField(xx, yy);
6006 else if (IS_BELT(element) && belt_dir != MV_NONE)
6008 int e_belt_nr = getBeltNrFromBeltElement(element);
6010 if (e_belt_nr == belt_nr)
6012 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
6014 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6015 TEST_DrawLevelField(xx, yy);
6018 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6020 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6022 if (e_belt_nr == belt_nr)
6024 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6026 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6027 TEST_DrawLevelField(xx, yy);
6033 static void ToggleSwitchgateSwitch(int x, int y)
6037 game.switchgate_pos = !game.switchgate_pos;
6039 SCAN_PLAYFIELD(xx, yy)
6041 int element = Feld[xx][yy];
6043 if (element == EL_SWITCHGATE_SWITCH_UP)
6045 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6046 TEST_DrawLevelField(xx, yy);
6048 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6050 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6051 TEST_DrawLevelField(xx, yy);
6053 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6055 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6056 TEST_DrawLevelField(xx, yy);
6058 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6060 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6061 TEST_DrawLevelField(xx, yy);
6063 else if (element == EL_SWITCHGATE_OPEN ||
6064 element == EL_SWITCHGATE_OPENING)
6066 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6068 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6070 else if (element == EL_SWITCHGATE_CLOSED ||
6071 element == EL_SWITCHGATE_CLOSING)
6073 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6075 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6080 static int getInvisibleActiveFromInvisibleElement(int element)
6082 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6083 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6084 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6088 static int getInvisibleFromInvisibleActiveElement(int element)
6090 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6091 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6092 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6096 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6100 SCAN_PLAYFIELD(x, y)
6102 int element = Feld[x][y];
6104 if (element == EL_LIGHT_SWITCH &&
6105 game.light_time_left > 0)
6107 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6108 TEST_DrawLevelField(x, y);
6110 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6111 game.light_time_left == 0)
6113 Feld[x][y] = EL_LIGHT_SWITCH;
6114 TEST_DrawLevelField(x, y);
6116 else if (element == EL_EMC_DRIPPER &&
6117 game.light_time_left > 0)
6119 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6120 TEST_DrawLevelField(x, y);
6122 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6123 game.light_time_left == 0)
6125 Feld[x][y] = EL_EMC_DRIPPER;
6126 TEST_DrawLevelField(x, y);
6128 else if (element == EL_INVISIBLE_STEELWALL ||
6129 element == EL_INVISIBLE_WALL ||
6130 element == EL_INVISIBLE_SAND)
6132 if (game.light_time_left > 0)
6133 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6135 TEST_DrawLevelField(x, y);
6137 // uncrumble neighbour fields, if needed
6138 if (element == EL_INVISIBLE_SAND)
6139 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6141 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6142 element == EL_INVISIBLE_WALL_ACTIVE ||
6143 element == EL_INVISIBLE_SAND_ACTIVE)
6145 if (game.light_time_left == 0)
6146 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6148 TEST_DrawLevelField(x, y);
6150 // re-crumble neighbour fields, if needed
6151 if (element == EL_INVISIBLE_SAND)
6152 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6157 static void RedrawAllInvisibleElementsForLenses(void)
6161 SCAN_PLAYFIELD(x, y)
6163 int element = Feld[x][y];
6165 if (element == EL_EMC_DRIPPER &&
6166 game.lenses_time_left > 0)
6168 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6169 TEST_DrawLevelField(x, y);
6171 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6172 game.lenses_time_left == 0)
6174 Feld[x][y] = EL_EMC_DRIPPER;
6175 TEST_DrawLevelField(x, y);
6177 else if (element == EL_INVISIBLE_STEELWALL ||
6178 element == EL_INVISIBLE_WALL ||
6179 element == EL_INVISIBLE_SAND)
6181 if (game.lenses_time_left > 0)
6182 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6184 TEST_DrawLevelField(x, y);
6186 // uncrumble neighbour fields, if needed
6187 if (element == EL_INVISIBLE_SAND)
6188 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6190 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6191 element == EL_INVISIBLE_WALL_ACTIVE ||
6192 element == EL_INVISIBLE_SAND_ACTIVE)
6194 if (game.lenses_time_left == 0)
6195 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6197 TEST_DrawLevelField(x, y);
6199 // re-crumble neighbour fields, if needed
6200 if (element == EL_INVISIBLE_SAND)
6201 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6206 static void RedrawAllInvisibleElementsForMagnifier(void)
6210 SCAN_PLAYFIELD(x, y)
6212 int element = Feld[x][y];
6214 if (element == EL_EMC_FAKE_GRASS &&
6215 game.magnify_time_left > 0)
6217 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6218 TEST_DrawLevelField(x, y);
6220 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6221 game.magnify_time_left == 0)
6223 Feld[x][y] = EL_EMC_FAKE_GRASS;
6224 TEST_DrawLevelField(x, y);
6226 else if (IS_GATE_GRAY(element) &&
6227 game.magnify_time_left > 0)
6229 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6230 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6231 IS_EM_GATE_GRAY(element) ?
6232 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6233 IS_EMC_GATE_GRAY(element) ?
6234 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6235 IS_DC_GATE_GRAY(element) ?
6236 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6238 TEST_DrawLevelField(x, y);
6240 else if (IS_GATE_GRAY_ACTIVE(element) &&
6241 game.magnify_time_left == 0)
6243 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6244 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6245 IS_EM_GATE_GRAY_ACTIVE(element) ?
6246 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6247 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6248 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6249 IS_DC_GATE_GRAY_ACTIVE(element) ?
6250 EL_DC_GATE_WHITE_GRAY :
6252 TEST_DrawLevelField(x, y);
6257 static void ToggleLightSwitch(int x, int y)
6259 int element = Feld[x][y];
6261 game.light_time_left =
6262 (element == EL_LIGHT_SWITCH ?
6263 level.time_light * FRAMES_PER_SECOND : 0);
6265 RedrawAllLightSwitchesAndInvisibleElements();
6268 static void ActivateTimegateSwitch(int x, int y)
6272 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6274 SCAN_PLAYFIELD(xx, yy)
6276 int element = Feld[xx][yy];
6278 if (element == EL_TIMEGATE_CLOSED ||
6279 element == EL_TIMEGATE_CLOSING)
6281 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6282 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6286 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6288 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6289 TEST_DrawLevelField(xx, yy);
6295 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6296 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6299 static void Impact(int x, int y)
6301 boolean last_line = (y == lev_fieldy - 1);
6302 boolean object_hit = FALSE;
6303 boolean impact = (last_line || object_hit);
6304 int element = Feld[x][y];
6305 int smashed = EL_STEELWALL;
6307 if (!last_line) // check if element below was hit
6309 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6312 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6313 MovDir[x][y + 1] != MV_DOWN ||
6314 MovPos[x][y + 1] <= TILEY / 2));
6316 // do not smash moving elements that left the smashed field in time
6317 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6318 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6321 #if USE_QUICKSAND_IMPACT_BUGFIX
6322 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6324 RemoveMovingField(x, y + 1);
6325 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6326 Feld[x][y + 2] = EL_ROCK;
6327 TEST_DrawLevelField(x, y + 2);
6332 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6334 RemoveMovingField(x, y + 1);
6335 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6336 Feld[x][y + 2] = EL_ROCK;
6337 TEST_DrawLevelField(x, y + 2);
6344 smashed = MovingOrBlocked2Element(x, y + 1);
6346 impact = (last_line || object_hit);
6349 if (!last_line && smashed == EL_ACID) // element falls into acid
6351 SplashAcid(x, y + 1);
6355 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6356 // only reset graphic animation if graphic really changes after impact
6358 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6360 ResetGfxAnimation(x, y);
6361 TEST_DrawLevelField(x, y);
6364 if (impact && CAN_EXPLODE_IMPACT(element))
6369 else if (impact && element == EL_PEARL &&
6370 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6372 ResetGfxAnimation(x, y);
6374 Feld[x][y] = EL_PEARL_BREAKING;
6375 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6378 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6380 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6385 if (impact && element == EL_AMOEBA_DROP)
6387 if (object_hit && IS_PLAYER(x, y + 1))
6388 KillPlayerUnlessEnemyProtected(x, y + 1);
6389 else if (object_hit && smashed == EL_PENGUIN)
6393 Feld[x][y] = EL_AMOEBA_GROWING;
6394 Store[x][y] = EL_AMOEBA_WET;
6396 ResetRandomAnimationValue(x, y);
6401 if (object_hit) // check which object was hit
6403 if ((CAN_PASS_MAGIC_WALL(element) &&
6404 (smashed == EL_MAGIC_WALL ||
6405 smashed == EL_BD_MAGIC_WALL)) ||
6406 (CAN_PASS_DC_MAGIC_WALL(element) &&
6407 smashed == EL_DC_MAGIC_WALL))
6410 int activated_magic_wall =
6411 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6412 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6413 EL_DC_MAGIC_WALL_ACTIVE);
6415 // activate magic wall / mill
6416 SCAN_PLAYFIELD(xx, yy)
6418 if (Feld[xx][yy] == smashed)
6419 Feld[xx][yy] = activated_magic_wall;
6422 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6423 game.magic_wall_active = TRUE;
6425 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6426 SND_MAGIC_WALL_ACTIVATING :
6427 smashed == EL_BD_MAGIC_WALL ?
6428 SND_BD_MAGIC_WALL_ACTIVATING :
6429 SND_DC_MAGIC_WALL_ACTIVATING));
6432 if (IS_PLAYER(x, y + 1))
6434 if (CAN_SMASH_PLAYER(element))
6436 KillPlayerUnlessEnemyProtected(x, y + 1);
6440 else if (smashed == EL_PENGUIN)
6442 if (CAN_SMASH_PLAYER(element))
6448 else if (element == EL_BD_DIAMOND)
6450 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6456 else if (((element == EL_SP_INFOTRON ||
6457 element == EL_SP_ZONK) &&
6458 (smashed == EL_SP_SNIKSNAK ||
6459 smashed == EL_SP_ELECTRON ||
6460 smashed == EL_SP_DISK_ORANGE)) ||
6461 (element == EL_SP_INFOTRON &&
6462 smashed == EL_SP_DISK_YELLOW))
6467 else if (CAN_SMASH_EVERYTHING(element))
6469 if (IS_CLASSIC_ENEMY(smashed) ||
6470 CAN_EXPLODE_SMASHED(smashed))
6475 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6477 if (smashed == EL_LAMP ||
6478 smashed == EL_LAMP_ACTIVE)
6483 else if (smashed == EL_NUT)
6485 Feld[x][y + 1] = EL_NUT_BREAKING;
6486 PlayLevelSound(x, y, SND_NUT_BREAKING);
6487 RaiseScoreElement(EL_NUT);
6490 else if (smashed == EL_PEARL)
6492 ResetGfxAnimation(x, y);
6494 Feld[x][y + 1] = EL_PEARL_BREAKING;
6495 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6498 else if (smashed == EL_DIAMOND)
6500 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6501 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6504 else if (IS_BELT_SWITCH(smashed))
6506 ToggleBeltSwitch(x, y + 1);
6508 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6509 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6510 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6511 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6513 ToggleSwitchgateSwitch(x, y + 1);
6515 else if (smashed == EL_LIGHT_SWITCH ||
6516 smashed == EL_LIGHT_SWITCH_ACTIVE)
6518 ToggleLightSwitch(x, y + 1);
6522 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6524 CheckElementChangeBySide(x, y + 1, smashed, element,
6525 CE_SWITCHED, CH_SIDE_TOP);
6526 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6532 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6537 // play sound of magic wall / mill
6539 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6540 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6541 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6543 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6544 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6545 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6546 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6547 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6548 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6553 // play sound of object that hits the ground
6554 if (last_line || object_hit)
6555 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6558 static void TurnRoundExt(int x, int y)
6570 { 0, 0 }, { 0, 0 }, { 0, 0 },
6575 int left, right, back;
6579 { MV_DOWN, MV_UP, MV_RIGHT },
6580 { MV_UP, MV_DOWN, MV_LEFT },
6582 { MV_LEFT, MV_RIGHT, MV_DOWN },
6586 { MV_RIGHT, MV_LEFT, MV_UP }
6589 int element = Feld[x][y];
6590 int move_pattern = element_info[element].move_pattern;
6592 int old_move_dir = MovDir[x][y];
6593 int left_dir = turn[old_move_dir].left;
6594 int right_dir = turn[old_move_dir].right;
6595 int back_dir = turn[old_move_dir].back;
6597 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6598 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6599 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6600 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6602 int left_x = x + left_dx, left_y = y + left_dy;
6603 int right_x = x + right_dx, right_y = y + right_dy;
6604 int move_x = x + move_dx, move_y = y + move_dy;
6608 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6610 TestIfBadThingTouchesOtherBadThing(x, y);
6612 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6613 MovDir[x][y] = right_dir;
6614 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6615 MovDir[x][y] = left_dir;
6617 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6619 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6622 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6624 TestIfBadThingTouchesOtherBadThing(x, y);
6626 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6627 MovDir[x][y] = left_dir;
6628 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6629 MovDir[x][y] = right_dir;
6631 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6633 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6636 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6638 TestIfBadThingTouchesOtherBadThing(x, y);
6640 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6641 MovDir[x][y] = left_dir;
6642 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6643 MovDir[x][y] = right_dir;
6645 if (MovDir[x][y] != old_move_dir)
6648 else if (element == EL_YAMYAM)
6650 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6651 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6653 if (can_turn_left && can_turn_right)
6654 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6655 else if (can_turn_left)
6656 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6657 else if (can_turn_right)
6658 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6660 MovDir[x][y] = back_dir;
6662 MovDelay[x][y] = 16 + 16 * RND(3);
6664 else if (element == EL_DARK_YAMYAM)
6666 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6668 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6671 if (can_turn_left && can_turn_right)
6672 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6673 else if (can_turn_left)
6674 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6675 else if (can_turn_right)
6676 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6678 MovDir[x][y] = back_dir;
6680 MovDelay[x][y] = 16 + 16 * RND(3);
6682 else if (element == EL_PACMAN)
6684 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6685 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6687 if (can_turn_left && can_turn_right)
6688 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6689 else if (can_turn_left)
6690 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6691 else if (can_turn_right)
6692 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6694 MovDir[x][y] = back_dir;
6696 MovDelay[x][y] = 6 + RND(40);
6698 else if (element == EL_PIG)
6700 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6701 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6702 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6703 boolean should_turn_left, should_turn_right, should_move_on;
6705 int rnd = RND(rnd_value);
6707 should_turn_left = (can_turn_left &&
6709 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6710 y + back_dy + left_dy)));
6711 should_turn_right = (can_turn_right &&
6713 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6714 y + back_dy + right_dy)));
6715 should_move_on = (can_move_on &&
6718 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6719 y + move_dy + left_dy) ||
6720 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6721 y + move_dy + right_dy)));
6723 if (should_turn_left || should_turn_right || should_move_on)
6725 if (should_turn_left && should_turn_right && should_move_on)
6726 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6727 rnd < 2 * rnd_value / 3 ? right_dir :
6729 else if (should_turn_left && should_turn_right)
6730 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6731 else if (should_turn_left && should_move_on)
6732 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6733 else if (should_turn_right && should_move_on)
6734 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6735 else if (should_turn_left)
6736 MovDir[x][y] = left_dir;
6737 else if (should_turn_right)
6738 MovDir[x][y] = right_dir;
6739 else if (should_move_on)
6740 MovDir[x][y] = old_move_dir;
6742 else if (can_move_on && rnd > rnd_value / 8)
6743 MovDir[x][y] = old_move_dir;
6744 else if (can_turn_left && can_turn_right)
6745 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6746 else if (can_turn_left && rnd > rnd_value / 8)
6747 MovDir[x][y] = left_dir;
6748 else if (can_turn_right && rnd > rnd_value/8)
6749 MovDir[x][y] = right_dir;
6751 MovDir[x][y] = back_dir;
6753 xx = x + move_xy[MovDir[x][y]].dx;
6754 yy = y + move_xy[MovDir[x][y]].dy;
6756 if (!IN_LEV_FIELD(xx, yy) ||
6757 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6758 MovDir[x][y] = old_move_dir;
6762 else if (element == EL_DRAGON)
6764 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6765 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6766 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6768 int rnd = RND(rnd_value);
6770 if (can_move_on && rnd > rnd_value / 8)
6771 MovDir[x][y] = old_move_dir;
6772 else if (can_turn_left && can_turn_right)
6773 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6774 else if (can_turn_left && rnd > rnd_value / 8)
6775 MovDir[x][y] = left_dir;
6776 else if (can_turn_right && rnd > rnd_value / 8)
6777 MovDir[x][y] = right_dir;
6779 MovDir[x][y] = back_dir;
6781 xx = x + move_xy[MovDir[x][y]].dx;
6782 yy = y + move_xy[MovDir[x][y]].dy;
6784 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6785 MovDir[x][y] = old_move_dir;
6789 else if (element == EL_MOLE)
6791 boolean can_move_on =
6792 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6793 IS_AMOEBOID(Feld[move_x][move_y]) ||
6794 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6797 boolean can_turn_left =
6798 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6799 IS_AMOEBOID(Feld[left_x][left_y])));
6801 boolean can_turn_right =
6802 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6803 IS_AMOEBOID(Feld[right_x][right_y])));
6805 if (can_turn_left && can_turn_right)
6806 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6807 else if (can_turn_left)
6808 MovDir[x][y] = left_dir;
6810 MovDir[x][y] = right_dir;
6813 if (MovDir[x][y] != old_move_dir)
6816 else if (element == EL_BALLOON)
6818 MovDir[x][y] = game.wind_direction;
6821 else if (element == EL_SPRING)
6823 if (MovDir[x][y] & MV_HORIZONTAL)
6825 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6826 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6828 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6829 ResetGfxAnimation(move_x, move_y);
6830 TEST_DrawLevelField(move_x, move_y);
6832 MovDir[x][y] = back_dir;
6834 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6835 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6836 MovDir[x][y] = MV_NONE;
6841 else if (element == EL_ROBOT ||
6842 element == EL_SATELLITE ||
6843 element == EL_PENGUIN ||
6844 element == EL_EMC_ANDROID)
6846 int attr_x = -1, attr_y = -1;
6848 if (game.all_players_gone)
6850 attr_x = game.exit_x;
6851 attr_y = game.exit_y;
6857 for (i = 0; i < MAX_PLAYERS; i++)
6859 struct PlayerInfo *player = &stored_player[i];
6860 int jx = player->jx, jy = player->jy;
6862 if (!player->active)
6866 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6874 if (element == EL_ROBOT &&
6875 game.robot_wheel_x >= 0 &&
6876 game.robot_wheel_y >= 0 &&
6877 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
6878 game.engine_version < VERSION_IDENT(3,1,0,0)))
6880 attr_x = game.robot_wheel_x;
6881 attr_y = game.robot_wheel_y;
6884 if (element == EL_PENGUIN)
6887 static int xy[4][2] =
6895 for (i = 0; i < NUM_DIRECTIONS; i++)
6897 int ex = x + xy[i][0];
6898 int ey = y + xy[i][1];
6900 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
6901 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
6902 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
6903 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
6912 MovDir[x][y] = MV_NONE;
6914 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
6915 else if (attr_x > x)
6916 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
6918 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
6919 else if (attr_y > y)
6920 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
6922 if (element == EL_ROBOT)
6926 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6927 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
6928 Moving2Blocked(x, y, &newx, &newy);
6930 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
6931 MovDelay[x][y] = 8 + 8 * !RND(3);
6933 MovDelay[x][y] = 16;
6935 else if (element == EL_PENGUIN)
6941 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6943 boolean first_horiz = RND(2);
6944 int new_move_dir = MovDir[x][y];
6947 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6948 Moving2Blocked(x, y, &newx, &newy);
6950 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6954 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6955 Moving2Blocked(x, y, &newx, &newy);
6957 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6960 MovDir[x][y] = old_move_dir;
6964 else if (element == EL_SATELLITE)
6970 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6972 boolean first_horiz = RND(2);
6973 int new_move_dir = MovDir[x][y];
6976 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6977 Moving2Blocked(x, y, &newx, &newy);
6979 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6983 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6984 Moving2Blocked(x, y, &newx, &newy);
6986 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
6989 MovDir[x][y] = old_move_dir;
6993 else if (element == EL_EMC_ANDROID)
6995 static int check_pos[16] =
6997 -1, // 0 => (invalid)
7000 -1, // 3 => (invalid)
7002 0, // 5 => MV_LEFT | MV_UP
7003 2, // 6 => MV_RIGHT | MV_UP
7004 -1, // 7 => (invalid)
7006 6, // 9 => MV_LEFT | MV_DOWN
7007 4, // 10 => MV_RIGHT | MV_DOWN
7008 -1, // 11 => (invalid)
7009 -1, // 12 => (invalid)
7010 -1, // 13 => (invalid)
7011 -1, // 14 => (invalid)
7012 -1, // 15 => (invalid)
7020 { -1, -1, MV_LEFT | MV_UP },
7022 { +1, -1, MV_RIGHT | MV_UP },
7023 { +1, 0, MV_RIGHT },
7024 { +1, +1, MV_RIGHT | MV_DOWN },
7026 { -1, +1, MV_LEFT | MV_DOWN },
7029 int start_pos, check_order;
7030 boolean can_clone = FALSE;
7033 // check if there is any free field around current position
7034 for (i = 0; i < 8; i++)
7036 int newx = x + check_xy[i].dx;
7037 int newy = y + check_xy[i].dy;
7039 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7047 if (can_clone) // randomly find an element to clone
7051 start_pos = check_pos[RND(8)];
7052 check_order = (RND(2) ? -1 : +1);
7054 for (i = 0; i < 8; i++)
7056 int pos_raw = start_pos + i * check_order;
7057 int pos = (pos_raw + 8) % 8;
7058 int newx = x + check_xy[pos].dx;
7059 int newy = y + check_xy[pos].dy;
7061 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7063 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7064 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7066 Store[x][y] = Feld[newx][newy];
7075 if (can_clone) // randomly find a direction to move
7079 start_pos = check_pos[RND(8)];
7080 check_order = (RND(2) ? -1 : +1);
7082 for (i = 0; i < 8; i++)
7084 int pos_raw = start_pos + i * check_order;
7085 int pos = (pos_raw + 8) % 8;
7086 int newx = x + check_xy[pos].dx;
7087 int newy = y + check_xy[pos].dy;
7088 int new_move_dir = check_xy[pos].dir;
7090 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7092 MovDir[x][y] = new_move_dir;
7093 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7102 if (can_clone) // cloning and moving successful
7105 // cannot clone -- try to move towards player
7107 start_pos = check_pos[MovDir[x][y] & 0x0f];
7108 check_order = (RND(2) ? -1 : +1);
7110 for (i = 0; i < 3; i++)
7112 // first check start_pos, then previous/next or (next/previous) pos
7113 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7114 int pos = (pos_raw + 8) % 8;
7115 int newx = x + check_xy[pos].dx;
7116 int newy = y + check_xy[pos].dy;
7117 int new_move_dir = check_xy[pos].dir;
7119 if (IS_PLAYER(newx, newy))
7122 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7124 MovDir[x][y] = new_move_dir;
7125 MovDelay[x][y] = level.android_move_time * 8 + 1;
7132 else if (move_pattern == MV_TURNING_LEFT ||
7133 move_pattern == MV_TURNING_RIGHT ||
7134 move_pattern == MV_TURNING_LEFT_RIGHT ||
7135 move_pattern == MV_TURNING_RIGHT_LEFT ||
7136 move_pattern == MV_TURNING_RANDOM ||
7137 move_pattern == MV_ALL_DIRECTIONS)
7139 boolean can_turn_left =
7140 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7141 boolean can_turn_right =
7142 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7144 if (element_info[element].move_stepsize == 0) // "not moving"
7147 if (move_pattern == MV_TURNING_LEFT)
7148 MovDir[x][y] = left_dir;
7149 else if (move_pattern == MV_TURNING_RIGHT)
7150 MovDir[x][y] = right_dir;
7151 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7152 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7153 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7154 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7155 else if (move_pattern == MV_TURNING_RANDOM)
7156 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7157 can_turn_right && !can_turn_left ? right_dir :
7158 RND(2) ? left_dir : right_dir);
7159 else if (can_turn_left && can_turn_right)
7160 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7161 else if (can_turn_left)
7162 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7163 else if (can_turn_right)
7164 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7166 MovDir[x][y] = back_dir;
7168 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7170 else if (move_pattern == MV_HORIZONTAL ||
7171 move_pattern == MV_VERTICAL)
7173 if (move_pattern & old_move_dir)
7174 MovDir[x][y] = back_dir;
7175 else if (move_pattern == MV_HORIZONTAL)
7176 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7177 else if (move_pattern == MV_VERTICAL)
7178 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7180 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7182 else if (move_pattern & MV_ANY_DIRECTION)
7184 MovDir[x][y] = move_pattern;
7185 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7187 else if (move_pattern & MV_WIND_DIRECTION)
7189 MovDir[x][y] = game.wind_direction;
7190 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7192 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7194 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7195 MovDir[x][y] = left_dir;
7196 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7197 MovDir[x][y] = right_dir;
7199 if (MovDir[x][y] != old_move_dir)
7200 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7202 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7204 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7205 MovDir[x][y] = right_dir;
7206 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7207 MovDir[x][y] = left_dir;
7209 if (MovDir[x][y] != old_move_dir)
7210 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7212 else if (move_pattern == MV_TOWARDS_PLAYER ||
7213 move_pattern == MV_AWAY_FROM_PLAYER)
7215 int attr_x = -1, attr_y = -1;
7217 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7219 if (game.all_players_gone)
7221 attr_x = game.exit_x;
7222 attr_y = game.exit_y;
7228 for (i = 0; i < MAX_PLAYERS; i++)
7230 struct PlayerInfo *player = &stored_player[i];
7231 int jx = player->jx, jy = player->jy;
7233 if (!player->active)
7237 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7245 MovDir[x][y] = MV_NONE;
7247 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7248 else if (attr_x > x)
7249 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7251 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7252 else if (attr_y > y)
7253 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7255 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7257 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7259 boolean first_horiz = RND(2);
7260 int new_move_dir = MovDir[x][y];
7262 if (element_info[element].move_stepsize == 0) // "not moving"
7264 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7265 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7271 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7272 Moving2Blocked(x, y, &newx, &newy);
7274 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7278 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7279 Moving2Blocked(x, y, &newx, &newy);
7281 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7284 MovDir[x][y] = old_move_dir;
7287 else if (move_pattern == MV_WHEN_PUSHED ||
7288 move_pattern == MV_WHEN_DROPPED)
7290 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7291 MovDir[x][y] = MV_NONE;
7295 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7297 static int test_xy[7][2] =
7307 static int test_dir[7] =
7317 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7318 int move_preference = -1000000; // start with very low preference
7319 int new_move_dir = MV_NONE;
7320 int start_test = RND(4);
7323 for (i = 0; i < NUM_DIRECTIONS; i++)
7325 int move_dir = test_dir[start_test + i];
7326 int move_dir_preference;
7328 xx = x + test_xy[start_test + i][0];
7329 yy = y + test_xy[start_test + i][1];
7331 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7332 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7334 new_move_dir = move_dir;
7339 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7342 move_dir_preference = -1 * RunnerVisit[xx][yy];
7343 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7344 move_dir_preference = PlayerVisit[xx][yy];
7346 if (move_dir_preference > move_preference)
7348 // prefer field that has not been visited for the longest time
7349 move_preference = move_dir_preference;
7350 new_move_dir = move_dir;
7352 else if (move_dir_preference == move_preference &&
7353 move_dir == old_move_dir)
7355 // prefer last direction when all directions are preferred equally
7356 move_preference = move_dir_preference;
7357 new_move_dir = move_dir;
7361 MovDir[x][y] = new_move_dir;
7362 if (old_move_dir != new_move_dir)
7363 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7367 static void TurnRound(int x, int y)
7369 int direction = MovDir[x][y];
7373 GfxDir[x][y] = MovDir[x][y];
7375 if (direction != MovDir[x][y])
7379 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7381 ResetGfxFrame(x, y);
7384 static boolean JustBeingPushed(int x, int y)
7388 for (i = 0; i < MAX_PLAYERS; i++)
7390 struct PlayerInfo *player = &stored_player[i];
7392 if (player->active && player->is_pushing && player->MovPos)
7394 int next_jx = player->jx + (player->jx - player->last_jx);
7395 int next_jy = player->jy + (player->jy - player->last_jy);
7397 if (x == next_jx && y == next_jy)
7405 static void StartMoving(int x, int y)
7407 boolean started_moving = FALSE; // some elements can fall _and_ move
7408 int element = Feld[x][y];
7413 if (MovDelay[x][y] == 0)
7414 GfxAction[x][y] = ACTION_DEFAULT;
7416 if (CAN_FALL(element) && y < lev_fieldy - 1)
7418 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7419 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7420 if (JustBeingPushed(x, y))
7423 if (element == EL_QUICKSAND_FULL)
7425 if (IS_FREE(x, y + 1))
7427 InitMovingField(x, y, MV_DOWN);
7428 started_moving = TRUE;
7430 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7431 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7432 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7433 Store[x][y] = EL_ROCK;
7435 Store[x][y] = EL_ROCK;
7438 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7440 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7442 if (!MovDelay[x][y])
7444 MovDelay[x][y] = TILEY + 1;
7446 ResetGfxAnimation(x, y);
7447 ResetGfxAnimation(x, y + 1);
7452 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7453 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7460 Feld[x][y] = EL_QUICKSAND_EMPTY;
7461 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7462 Store[x][y + 1] = Store[x][y];
7465 PlayLevelSoundAction(x, y, ACTION_FILLING);
7467 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7469 if (!MovDelay[x][y])
7471 MovDelay[x][y] = TILEY + 1;
7473 ResetGfxAnimation(x, y);
7474 ResetGfxAnimation(x, y + 1);
7479 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7480 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7487 Feld[x][y] = EL_QUICKSAND_EMPTY;
7488 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7489 Store[x][y + 1] = Store[x][y];
7492 PlayLevelSoundAction(x, y, ACTION_FILLING);
7495 else if (element == EL_QUICKSAND_FAST_FULL)
7497 if (IS_FREE(x, y + 1))
7499 InitMovingField(x, y, MV_DOWN);
7500 started_moving = TRUE;
7502 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7503 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7504 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7505 Store[x][y] = EL_ROCK;
7507 Store[x][y] = EL_ROCK;
7510 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7512 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7514 if (!MovDelay[x][y])
7516 MovDelay[x][y] = TILEY + 1;
7518 ResetGfxAnimation(x, y);
7519 ResetGfxAnimation(x, y + 1);
7524 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7525 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7532 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7533 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7534 Store[x][y + 1] = Store[x][y];
7537 PlayLevelSoundAction(x, y, ACTION_FILLING);
7539 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7541 if (!MovDelay[x][y])
7543 MovDelay[x][y] = TILEY + 1;
7545 ResetGfxAnimation(x, y);
7546 ResetGfxAnimation(x, y + 1);
7551 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7552 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7559 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7560 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7561 Store[x][y + 1] = Store[x][y];
7564 PlayLevelSoundAction(x, y, ACTION_FILLING);
7567 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7568 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7570 InitMovingField(x, y, MV_DOWN);
7571 started_moving = TRUE;
7573 Feld[x][y] = EL_QUICKSAND_FILLING;
7574 Store[x][y] = element;
7576 PlayLevelSoundAction(x, y, ACTION_FILLING);
7578 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7579 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7581 InitMovingField(x, y, MV_DOWN);
7582 started_moving = TRUE;
7584 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7585 Store[x][y] = element;
7587 PlayLevelSoundAction(x, y, ACTION_FILLING);
7589 else if (element == EL_MAGIC_WALL_FULL)
7591 if (IS_FREE(x, y + 1))
7593 InitMovingField(x, y, MV_DOWN);
7594 started_moving = TRUE;
7596 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7597 Store[x][y] = EL_CHANGED(Store[x][y]);
7599 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7601 if (!MovDelay[x][y])
7602 MovDelay[x][y] = TILEY / 4 + 1;
7611 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7612 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7613 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7617 else if (element == EL_BD_MAGIC_WALL_FULL)
7619 if (IS_FREE(x, y + 1))
7621 InitMovingField(x, y, MV_DOWN);
7622 started_moving = TRUE;
7624 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7625 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7627 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7629 if (!MovDelay[x][y])
7630 MovDelay[x][y] = TILEY / 4 + 1;
7639 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7640 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7641 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7645 else if (element == EL_DC_MAGIC_WALL_FULL)
7647 if (IS_FREE(x, y + 1))
7649 InitMovingField(x, y, MV_DOWN);
7650 started_moving = TRUE;
7652 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7653 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7655 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7657 if (!MovDelay[x][y])
7658 MovDelay[x][y] = TILEY / 4 + 1;
7667 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7668 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7669 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7673 else if ((CAN_PASS_MAGIC_WALL(element) &&
7674 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7675 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7676 (CAN_PASS_DC_MAGIC_WALL(element) &&
7677 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7680 InitMovingField(x, y, MV_DOWN);
7681 started_moving = TRUE;
7684 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7685 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7686 EL_DC_MAGIC_WALL_FILLING);
7687 Store[x][y] = element;
7689 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7691 SplashAcid(x, y + 1);
7693 InitMovingField(x, y, MV_DOWN);
7694 started_moving = TRUE;
7696 Store[x][y] = EL_ACID;
7699 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7700 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7701 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7702 CAN_FALL(element) && WasJustFalling[x][y] &&
7703 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7705 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7706 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7707 (Feld[x][y + 1] == EL_BLOCKED)))
7709 /* this is needed for a special case not covered by calling "Impact()"
7710 from "ContinueMoving()": if an element moves to a tile directly below
7711 another element which was just falling on that tile (which was empty
7712 in the previous frame), the falling element above would just stop
7713 instead of smashing the element below (in previous version, the above
7714 element was just checked for "moving" instead of "falling", resulting
7715 in incorrect smashes caused by horizontal movement of the above
7716 element; also, the case of the player being the element to smash was
7717 simply not covered here... :-/ ) */
7719 CheckCollision[x][y] = 0;
7720 CheckImpact[x][y] = 0;
7724 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7726 if (MovDir[x][y] == MV_NONE)
7728 InitMovingField(x, y, MV_DOWN);
7729 started_moving = TRUE;
7732 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7734 if (WasJustFalling[x][y]) // prevent animation from being restarted
7735 MovDir[x][y] = MV_DOWN;
7737 InitMovingField(x, y, MV_DOWN);
7738 started_moving = TRUE;
7740 else if (element == EL_AMOEBA_DROP)
7742 Feld[x][y] = EL_AMOEBA_GROWING;
7743 Store[x][y] = EL_AMOEBA_WET;
7745 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7746 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7747 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7748 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7750 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7751 (IS_FREE(x - 1, y + 1) ||
7752 Feld[x - 1][y + 1] == EL_ACID));
7753 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7754 (IS_FREE(x + 1, y + 1) ||
7755 Feld[x + 1][y + 1] == EL_ACID));
7756 boolean can_fall_any = (can_fall_left || can_fall_right);
7757 boolean can_fall_both = (can_fall_left && can_fall_right);
7758 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7760 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7762 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7763 can_fall_right = FALSE;
7764 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7765 can_fall_left = FALSE;
7766 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7767 can_fall_right = FALSE;
7768 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7769 can_fall_left = FALSE;
7771 can_fall_any = (can_fall_left || can_fall_right);
7772 can_fall_both = FALSE;
7777 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7778 can_fall_right = FALSE; // slip down on left side
7780 can_fall_left = !(can_fall_right = RND(2));
7782 can_fall_both = FALSE;
7787 // if not determined otherwise, prefer left side for slipping down
7788 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7789 started_moving = TRUE;
7792 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7794 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7795 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7796 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7797 int belt_dir = game.belt_dir[belt_nr];
7799 if ((belt_dir == MV_LEFT && left_is_free) ||
7800 (belt_dir == MV_RIGHT && right_is_free))
7802 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7804 InitMovingField(x, y, belt_dir);
7805 started_moving = TRUE;
7807 Pushed[x][y] = TRUE;
7808 Pushed[nextx][y] = TRUE;
7810 GfxAction[x][y] = ACTION_DEFAULT;
7814 MovDir[x][y] = 0; // if element was moving, stop it
7819 // not "else if" because of elements that can fall and move (EL_SPRING)
7820 if (CAN_MOVE(element) && !started_moving)
7822 int move_pattern = element_info[element].move_pattern;
7825 Moving2Blocked(x, y, &newx, &newy);
7827 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7830 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7831 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7833 WasJustMoving[x][y] = 0;
7834 CheckCollision[x][y] = 0;
7836 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7838 if (Feld[x][y] != element) // element has changed
7842 if (!MovDelay[x][y]) // start new movement phase
7844 // all objects that can change their move direction after each step
7845 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7847 if (element != EL_YAMYAM &&
7848 element != EL_DARK_YAMYAM &&
7849 element != EL_PACMAN &&
7850 !(move_pattern & MV_ANY_DIRECTION) &&
7851 move_pattern != MV_TURNING_LEFT &&
7852 move_pattern != MV_TURNING_RIGHT &&
7853 move_pattern != MV_TURNING_LEFT_RIGHT &&
7854 move_pattern != MV_TURNING_RIGHT_LEFT &&
7855 move_pattern != MV_TURNING_RANDOM)
7859 if (MovDelay[x][y] && (element == EL_BUG ||
7860 element == EL_SPACESHIP ||
7861 element == EL_SP_SNIKSNAK ||
7862 element == EL_SP_ELECTRON ||
7863 element == EL_MOLE))
7864 TEST_DrawLevelField(x, y);
7868 if (MovDelay[x][y]) // wait some time before next movement
7872 if (element == EL_ROBOT ||
7873 element == EL_YAMYAM ||
7874 element == EL_DARK_YAMYAM)
7876 DrawLevelElementAnimationIfNeeded(x, y, element);
7877 PlayLevelSoundAction(x, y, ACTION_WAITING);
7879 else if (element == EL_SP_ELECTRON)
7880 DrawLevelElementAnimationIfNeeded(x, y, element);
7881 else if (element == EL_DRAGON)
7884 int dir = MovDir[x][y];
7885 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
7886 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
7887 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
7888 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
7889 dir == MV_UP ? IMG_FLAMES_1_UP :
7890 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
7891 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
7893 GfxAction[x][y] = ACTION_ATTACKING;
7895 if (IS_PLAYER(x, y))
7896 DrawPlayerField(x, y);
7898 TEST_DrawLevelField(x, y);
7900 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
7902 for (i = 1; i <= 3; i++)
7904 int xx = x + i * dx;
7905 int yy = y + i * dy;
7906 int sx = SCREENX(xx);
7907 int sy = SCREENY(yy);
7908 int flame_graphic = graphic + (i - 1);
7910 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
7915 int flamed = MovingOrBlocked2Element(xx, yy);
7917 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
7920 RemoveMovingField(xx, yy);
7922 ChangeDelay[xx][yy] = 0;
7924 Feld[xx][yy] = EL_FLAMES;
7926 if (IN_SCR_FIELD(sx, sy))
7928 TEST_DrawLevelFieldCrumbled(xx, yy);
7929 DrawGraphic(sx, sy, flame_graphic, frame);
7934 if (Feld[xx][yy] == EL_FLAMES)
7935 Feld[xx][yy] = EL_EMPTY;
7936 TEST_DrawLevelField(xx, yy);
7941 if (MovDelay[x][y]) // element still has to wait some time
7943 PlayLevelSoundAction(x, y, ACTION_WAITING);
7949 // now make next step
7951 Moving2Blocked(x, y, &newx, &newy); // get next screen position
7953 if (DONT_COLLIDE_WITH(element) &&
7954 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
7955 !PLAYER_ENEMY_PROTECTED(newx, newy))
7957 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
7962 else if (CAN_MOVE_INTO_ACID(element) &&
7963 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
7964 !IS_MV_DIAGONAL(MovDir[x][y]) &&
7965 (MovDir[x][y] == MV_DOWN ||
7966 game.engine_version >= VERSION_IDENT(3,1,0,0)))
7968 SplashAcid(newx, newy);
7969 Store[x][y] = EL_ACID;
7971 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
7973 if (Feld[newx][newy] == EL_EXIT_OPEN ||
7974 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
7975 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
7976 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
7979 TEST_DrawLevelField(x, y);
7981 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
7982 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
7983 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
7985 game.friends_still_needed--;
7986 if (!game.friends_still_needed &&
7988 game.all_players_gone)
7993 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
7995 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
7996 TEST_DrawLevelField(newx, newy);
7998 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8000 else if (!IS_FREE(newx, newy))
8002 GfxAction[x][y] = ACTION_WAITING;
8004 if (IS_PLAYER(x, y))
8005 DrawPlayerField(x, y);
8007 TEST_DrawLevelField(x, y);
8012 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8014 if (IS_FOOD_PIG(Feld[newx][newy]))
8016 if (IS_MOVING(newx, newy))
8017 RemoveMovingField(newx, newy);
8020 Feld[newx][newy] = EL_EMPTY;
8021 TEST_DrawLevelField(newx, newy);
8024 PlayLevelSound(x, y, SND_PIG_DIGGING);
8026 else if (!IS_FREE(newx, newy))
8028 if (IS_PLAYER(x, y))
8029 DrawPlayerField(x, y);
8031 TEST_DrawLevelField(x, y);
8036 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8038 if (Store[x][y] != EL_EMPTY)
8040 boolean can_clone = FALSE;
8043 // check if element to clone is still there
8044 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8046 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8054 // cannot clone or target field not free anymore -- do not clone
8055 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8056 Store[x][y] = EL_EMPTY;
8059 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8061 if (IS_MV_DIAGONAL(MovDir[x][y]))
8063 int diagonal_move_dir = MovDir[x][y];
8064 int stored = Store[x][y];
8065 int change_delay = 8;
8068 // android is moving diagonally
8070 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8072 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8073 GfxElement[x][y] = EL_EMC_ANDROID;
8074 GfxAction[x][y] = ACTION_SHRINKING;
8075 GfxDir[x][y] = diagonal_move_dir;
8076 ChangeDelay[x][y] = change_delay;
8078 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8081 DrawLevelGraphicAnimation(x, y, graphic);
8082 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8084 if (Feld[newx][newy] == EL_ACID)
8086 SplashAcid(newx, newy);
8091 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8093 Store[newx][newy] = EL_EMC_ANDROID;
8094 GfxElement[newx][newy] = EL_EMC_ANDROID;
8095 GfxAction[newx][newy] = ACTION_GROWING;
8096 GfxDir[newx][newy] = diagonal_move_dir;
8097 ChangeDelay[newx][newy] = change_delay;
8099 graphic = el_act_dir2img(GfxElement[newx][newy],
8100 GfxAction[newx][newy], GfxDir[newx][newy]);
8102 DrawLevelGraphicAnimation(newx, newy, graphic);
8103 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8109 Feld[newx][newy] = EL_EMPTY;
8110 TEST_DrawLevelField(newx, newy);
8112 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8115 else if (!IS_FREE(newx, newy))
8120 else if (IS_CUSTOM_ELEMENT(element) &&
8121 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8123 if (!DigFieldByCE(newx, newy, element))
8126 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8128 RunnerVisit[x][y] = FrameCounter;
8129 PlayerVisit[x][y] /= 8; // expire player visit path
8132 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8134 if (!IS_FREE(newx, newy))
8136 if (IS_PLAYER(x, y))
8137 DrawPlayerField(x, y);
8139 TEST_DrawLevelField(x, y);
8145 boolean wanna_flame = !RND(10);
8146 int dx = newx - x, dy = newy - y;
8147 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8148 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8149 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8150 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8151 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8152 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8155 IS_CLASSIC_ENEMY(element1) ||
8156 IS_CLASSIC_ENEMY(element2)) &&
8157 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8158 element1 != EL_FLAMES && element2 != EL_FLAMES)
8160 ResetGfxAnimation(x, y);
8161 GfxAction[x][y] = ACTION_ATTACKING;
8163 if (IS_PLAYER(x, y))
8164 DrawPlayerField(x, y);
8166 TEST_DrawLevelField(x, y);
8168 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8170 MovDelay[x][y] = 50;
8172 Feld[newx][newy] = EL_FLAMES;
8173 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8174 Feld[newx1][newy1] = EL_FLAMES;
8175 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8176 Feld[newx2][newy2] = EL_FLAMES;
8182 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8183 Feld[newx][newy] == EL_DIAMOND)
8185 if (IS_MOVING(newx, newy))
8186 RemoveMovingField(newx, newy);
8189 Feld[newx][newy] = EL_EMPTY;
8190 TEST_DrawLevelField(newx, newy);
8193 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8195 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8196 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8198 if (AmoebaNr[newx][newy])
8200 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8201 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8202 Feld[newx][newy] == EL_BD_AMOEBA)
8203 AmoebaCnt[AmoebaNr[newx][newy]]--;
8206 if (IS_MOVING(newx, newy))
8208 RemoveMovingField(newx, newy);
8212 Feld[newx][newy] = EL_EMPTY;
8213 TEST_DrawLevelField(newx, newy);
8216 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8218 else if ((element == EL_PACMAN || element == EL_MOLE)
8219 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8221 if (AmoebaNr[newx][newy])
8223 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8224 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8225 Feld[newx][newy] == EL_BD_AMOEBA)
8226 AmoebaCnt[AmoebaNr[newx][newy]]--;
8229 if (element == EL_MOLE)
8231 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8232 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8234 ResetGfxAnimation(x, y);
8235 GfxAction[x][y] = ACTION_DIGGING;
8236 TEST_DrawLevelField(x, y);
8238 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8240 return; // wait for shrinking amoeba
8242 else // element == EL_PACMAN
8244 Feld[newx][newy] = EL_EMPTY;
8245 TEST_DrawLevelField(newx, newy);
8246 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8249 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8250 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8251 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8253 // wait for shrinking amoeba to completely disappear
8256 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8258 // object was running against a wall
8262 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8263 DrawLevelElementAnimation(x, y, element);
8265 if (DONT_TOUCH(element))
8266 TestIfBadThingTouchesPlayer(x, y);
8271 InitMovingField(x, y, MovDir[x][y]);
8273 PlayLevelSoundAction(x, y, ACTION_MOVING);
8277 ContinueMoving(x, y);
8280 void ContinueMoving(int x, int y)
8282 int element = Feld[x][y];
8283 struct ElementInfo *ei = &element_info[element];
8284 int direction = MovDir[x][y];
8285 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8286 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8287 int newx = x + dx, newy = y + dy;
8288 int stored = Store[x][y];
8289 int stored_new = Store[newx][newy];
8290 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8291 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8292 boolean last_line = (newy == lev_fieldy - 1);
8294 MovPos[x][y] += getElementMoveStepsize(x, y);
8296 if (pushed_by_player) // special case: moving object pushed by player
8297 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8299 if (ABS(MovPos[x][y]) < TILEX)
8301 TEST_DrawLevelField(x, y);
8303 return; // element is still moving
8306 // element reached destination field
8308 Feld[x][y] = EL_EMPTY;
8309 Feld[newx][newy] = element;
8310 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8312 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8314 element = Feld[newx][newy] = EL_ACID;
8316 else if (element == EL_MOLE)
8318 Feld[x][y] = EL_SAND;
8320 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8322 else if (element == EL_QUICKSAND_FILLING)
8324 element = Feld[newx][newy] = get_next_element(element);
8325 Store[newx][newy] = Store[x][y];
8327 else if (element == EL_QUICKSAND_EMPTYING)
8329 Feld[x][y] = get_next_element(element);
8330 element = Feld[newx][newy] = Store[x][y];
8332 else if (element == EL_QUICKSAND_FAST_FILLING)
8334 element = Feld[newx][newy] = get_next_element(element);
8335 Store[newx][newy] = Store[x][y];
8337 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8339 Feld[x][y] = get_next_element(element);
8340 element = Feld[newx][newy] = Store[x][y];
8342 else if (element == EL_MAGIC_WALL_FILLING)
8344 element = Feld[newx][newy] = get_next_element(element);
8345 if (!game.magic_wall_active)
8346 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8347 Store[newx][newy] = Store[x][y];
8349 else if (element == EL_MAGIC_WALL_EMPTYING)
8351 Feld[x][y] = get_next_element(element);
8352 if (!game.magic_wall_active)
8353 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8354 element = Feld[newx][newy] = Store[x][y];
8356 InitField(newx, newy, FALSE);
8358 else if (element == EL_BD_MAGIC_WALL_FILLING)
8360 element = Feld[newx][newy] = get_next_element(element);
8361 if (!game.magic_wall_active)
8362 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8363 Store[newx][newy] = Store[x][y];
8365 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8367 Feld[x][y] = get_next_element(element);
8368 if (!game.magic_wall_active)
8369 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8370 element = Feld[newx][newy] = Store[x][y];
8372 InitField(newx, newy, FALSE);
8374 else if (element == EL_DC_MAGIC_WALL_FILLING)
8376 element = Feld[newx][newy] = get_next_element(element);
8377 if (!game.magic_wall_active)
8378 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8379 Store[newx][newy] = Store[x][y];
8381 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8383 Feld[x][y] = get_next_element(element);
8384 if (!game.magic_wall_active)
8385 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8386 element = Feld[newx][newy] = Store[x][y];
8388 InitField(newx, newy, FALSE);
8390 else if (element == EL_AMOEBA_DROPPING)
8392 Feld[x][y] = get_next_element(element);
8393 element = Feld[newx][newy] = Store[x][y];
8395 else if (element == EL_SOKOBAN_OBJECT)
8398 Feld[x][y] = Back[x][y];
8400 if (Back[newx][newy])
8401 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8403 Back[x][y] = Back[newx][newy] = 0;
8406 Store[x][y] = EL_EMPTY;
8411 MovDelay[newx][newy] = 0;
8413 if (CAN_CHANGE_OR_HAS_ACTION(element))
8415 // copy element change control values to new field
8416 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8417 ChangePage[newx][newy] = ChangePage[x][y];
8418 ChangeCount[newx][newy] = ChangeCount[x][y];
8419 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8422 CustomValue[newx][newy] = CustomValue[x][y];
8424 ChangeDelay[x][y] = 0;
8425 ChangePage[x][y] = -1;
8426 ChangeCount[x][y] = 0;
8427 ChangeEvent[x][y] = -1;
8429 CustomValue[x][y] = 0;
8431 // copy animation control values to new field
8432 GfxFrame[newx][newy] = GfxFrame[x][y];
8433 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8434 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8435 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8437 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8439 // some elements can leave other elements behind after moving
8440 if (ei->move_leave_element != EL_EMPTY &&
8441 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8442 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8444 int move_leave_element = ei->move_leave_element;
8446 // this makes it possible to leave the removed element again
8447 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8448 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8450 Feld[x][y] = move_leave_element;
8452 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8453 MovDir[x][y] = direction;
8455 InitField(x, y, FALSE);
8457 if (GFX_CRUMBLED(Feld[x][y]))
8458 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8460 if (ELEM_IS_PLAYER(move_leave_element))
8461 RelocatePlayer(x, y, move_leave_element);
8464 // do this after checking for left-behind element
8465 ResetGfxAnimation(x, y); // reset animation values for old field
8467 if (!CAN_MOVE(element) ||
8468 (CAN_FALL(element) && direction == MV_DOWN &&
8469 (element == EL_SPRING ||
8470 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8471 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8472 GfxDir[x][y] = MovDir[newx][newy] = 0;
8474 TEST_DrawLevelField(x, y);
8475 TEST_DrawLevelField(newx, newy);
8477 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8479 // prevent pushed element from moving on in pushed direction
8480 if (pushed_by_player && CAN_MOVE(element) &&
8481 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8482 !(element_info[element].move_pattern & direction))
8483 TurnRound(newx, newy);
8485 // prevent elements on conveyor belt from moving on in last direction
8486 if (pushed_by_conveyor && CAN_FALL(element) &&
8487 direction & MV_HORIZONTAL)
8488 MovDir[newx][newy] = 0;
8490 if (!pushed_by_player)
8492 int nextx = newx + dx, nexty = newy + dy;
8493 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8495 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8497 if (CAN_FALL(element) && direction == MV_DOWN)
8498 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8500 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8501 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8503 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8504 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8507 if (DONT_TOUCH(element)) // object may be nasty to player or others
8509 TestIfBadThingTouchesPlayer(newx, newy);
8510 TestIfBadThingTouchesFriend(newx, newy);
8512 if (!IS_CUSTOM_ELEMENT(element))
8513 TestIfBadThingTouchesOtherBadThing(newx, newy);
8515 else if (element == EL_PENGUIN)
8516 TestIfFriendTouchesBadThing(newx, newy);
8518 if (DONT_GET_HIT_BY(element))
8520 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8523 // give the player one last chance (one more frame) to move away
8524 if (CAN_FALL(element) && direction == MV_DOWN &&
8525 (last_line || (!IS_FREE(x, newy + 1) &&
8526 (!IS_PLAYER(x, newy + 1) ||
8527 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8530 if (pushed_by_player && !game.use_change_when_pushing_bug)
8532 int push_side = MV_DIR_OPPOSITE(direction);
8533 struct PlayerInfo *player = PLAYERINFO(x, y);
8535 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8536 player->index_bit, push_side);
8537 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8538 player->index_bit, push_side);
8541 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8542 MovDelay[newx][newy] = 1;
8544 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8546 TestIfElementTouchesCustomElement(x, y); // empty or new element
8547 TestIfElementHitsCustomElement(newx, newy, direction);
8548 TestIfPlayerTouchesCustomElement(newx, newy);
8549 TestIfElementTouchesCustomElement(newx, newy);
8551 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8552 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8553 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8554 MV_DIR_OPPOSITE(direction));
8557 int AmoebeNachbarNr(int ax, int ay)
8560 int element = Feld[ax][ay];
8562 static int xy[4][2] =
8570 for (i = 0; i < NUM_DIRECTIONS; i++)
8572 int x = ax + xy[i][0];
8573 int y = ay + xy[i][1];
8575 if (!IN_LEV_FIELD(x, y))
8578 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8579 group_nr = AmoebaNr[x][y];
8585 static void AmoebenVereinigen(int ax, int ay)
8587 int i, x, y, xx, yy;
8588 int new_group_nr = AmoebaNr[ax][ay];
8589 static int xy[4][2] =
8597 if (new_group_nr == 0)
8600 for (i = 0; i < NUM_DIRECTIONS; i++)
8605 if (!IN_LEV_FIELD(x, y))
8608 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8609 Feld[x][y] == EL_BD_AMOEBA ||
8610 Feld[x][y] == EL_AMOEBA_DEAD) &&
8611 AmoebaNr[x][y] != new_group_nr)
8613 int old_group_nr = AmoebaNr[x][y];
8615 if (old_group_nr == 0)
8618 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8619 AmoebaCnt[old_group_nr] = 0;
8620 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8621 AmoebaCnt2[old_group_nr] = 0;
8623 SCAN_PLAYFIELD(xx, yy)
8625 if (AmoebaNr[xx][yy] == old_group_nr)
8626 AmoebaNr[xx][yy] = new_group_nr;
8632 void AmoebeUmwandeln(int ax, int ay)
8636 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8638 int group_nr = AmoebaNr[ax][ay];
8643 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8644 printf("AmoebeUmwandeln(): This should never happen!\n");
8649 SCAN_PLAYFIELD(x, y)
8651 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8654 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8658 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8659 SND_AMOEBA_TURNING_TO_GEM :
8660 SND_AMOEBA_TURNING_TO_ROCK));
8665 static int xy[4][2] =
8673 for (i = 0; i < NUM_DIRECTIONS; i++)
8678 if (!IN_LEV_FIELD(x, y))
8681 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8683 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8684 SND_AMOEBA_TURNING_TO_GEM :
8685 SND_AMOEBA_TURNING_TO_ROCK));
8692 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8695 int group_nr = AmoebaNr[ax][ay];
8696 boolean done = FALSE;
8701 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8702 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8707 SCAN_PLAYFIELD(x, y)
8709 if (AmoebaNr[x][y] == group_nr &&
8710 (Feld[x][y] == EL_AMOEBA_DEAD ||
8711 Feld[x][y] == EL_BD_AMOEBA ||
8712 Feld[x][y] == EL_AMOEBA_GROWING))
8715 Feld[x][y] = new_element;
8716 InitField(x, y, FALSE);
8717 TEST_DrawLevelField(x, y);
8723 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8724 SND_BD_AMOEBA_TURNING_TO_ROCK :
8725 SND_BD_AMOEBA_TURNING_TO_GEM));
8728 static void AmoebeWaechst(int x, int y)
8730 static unsigned int sound_delay = 0;
8731 static unsigned int sound_delay_value = 0;
8733 if (!MovDelay[x][y]) // start new growing cycle
8737 if (DelayReached(&sound_delay, sound_delay_value))
8739 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8740 sound_delay_value = 30;
8744 if (MovDelay[x][y]) // wait some time before growing bigger
8747 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8749 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8750 6 - MovDelay[x][y]);
8752 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8755 if (!MovDelay[x][y])
8757 Feld[x][y] = Store[x][y];
8759 TEST_DrawLevelField(x, y);
8764 static void AmoebaDisappearing(int x, int y)
8766 static unsigned int sound_delay = 0;
8767 static unsigned int sound_delay_value = 0;
8769 if (!MovDelay[x][y]) // start new shrinking cycle
8773 if (DelayReached(&sound_delay, sound_delay_value))
8774 sound_delay_value = 30;
8777 if (MovDelay[x][y]) // wait some time before shrinking
8780 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8782 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8783 6 - MovDelay[x][y]);
8785 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8788 if (!MovDelay[x][y])
8790 Feld[x][y] = EL_EMPTY;
8791 TEST_DrawLevelField(x, y);
8793 // don't let mole enter this field in this cycle;
8794 // (give priority to objects falling to this field from above)
8800 static void AmoebeAbleger(int ax, int ay)
8803 int element = Feld[ax][ay];
8804 int graphic = el2img(element);
8805 int newax = ax, neway = ay;
8806 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8807 static int xy[4][2] =
8815 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8817 Feld[ax][ay] = EL_AMOEBA_DEAD;
8818 TEST_DrawLevelField(ax, ay);
8822 if (IS_ANIMATED(graphic))
8823 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8825 if (!MovDelay[ax][ay]) // start making new amoeba field
8826 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8828 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8831 if (MovDelay[ax][ay])
8835 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8838 int x = ax + xy[start][0];
8839 int y = ay + xy[start][1];
8841 if (!IN_LEV_FIELD(x, y))
8844 if (IS_FREE(x, y) ||
8845 CAN_GROW_INTO(Feld[x][y]) ||
8846 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8847 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8853 if (newax == ax && neway == ay)
8856 else // normal or "filled" (BD style) amoeba
8859 boolean waiting_for_player = FALSE;
8861 for (i = 0; i < NUM_DIRECTIONS; i++)
8863 int j = (start + i) % 4;
8864 int x = ax + xy[j][0];
8865 int y = ay + xy[j][1];
8867 if (!IN_LEV_FIELD(x, y))
8870 if (IS_FREE(x, y) ||
8871 CAN_GROW_INTO(Feld[x][y]) ||
8872 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8873 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8879 else if (IS_PLAYER(x, y))
8880 waiting_for_player = TRUE;
8883 if (newax == ax && neway == ay) // amoeba cannot grow
8885 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
8887 Feld[ax][ay] = EL_AMOEBA_DEAD;
8888 TEST_DrawLevelField(ax, ay);
8889 AmoebaCnt[AmoebaNr[ax][ay]]--;
8891 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
8893 if (element == EL_AMOEBA_FULL)
8894 AmoebeUmwandeln(ax, ay);
8895 else if (element == EL_BD_AMOEBA)
8896 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
8901 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
8903 // amoeba gets larger by growing in some direction
8905 int new_group_nr = AmoebaNr[ax][ay];
8908 if (new_group_nr == 0)
8910 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
8911 printf("AmoebeAbleger(): This should never happen!\n");
8916 AmoebaNr[newax][neway] = new_group_nr;
8917 AmoebaCnt[new_group_nr]++;
8918 AmoebaCnt2[new_group_nr]++;
8920 // if amoeba touches other amoeba(s) after growing, unify them
8921 AmoebenVereinigen(newax, neway);
8923 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
8925 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
8931 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
8932 (neway == lev_fieldy - 1 && newax != ax))
8934 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
8935 Store[newax][neway] = element;
8937 else if (neway == ay || element == EL_EMC_DRIPPER)
8939 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
8941 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
8945 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
8946 Feld[ax][ay] = EL_AMOEBA_DROPPING;
8947 Store[ax][ay] = EL_AMOEBA_DROP;
8948 ContinueMoving(ax, ay);
8952 TEST_DrawLevelField(newax, neway);
8955 static void Life(int ax, int ay)
8959 int element = Feld[ax][ay];
8960 int graphic = el2img(element);
8961 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
8963 boolean changed = FALSE;
8965 if (IS_ANIMATED(graphic))
8966 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8971 if (!MovDelay[ax][ay]) // start new "game of life" cycle
8972 MovDelay[ax][ay] = life_time;
8974 if (MovDelay[ax][ay]) // wait some time before next cycle
8977 if (MovDelay[ax][ay])
8981 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
8983 int xx = ax+x1, yy = ay+y1;
8984 int old_element = Feld[xx][yy];
8985 int num_neighbours = 0;
8987 if (!IN_LEV_FIELD(xx, yy))
8990 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
8992 int x = xx+x2, y = yy+y2;
8994 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
8997 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
8998 boolean is_neighbour = FALSE;
9000 if (level.use_life_bugs)
9002 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9003 (IS_FREE(x, y) && Stop[x][y]));
9006 (Last[x][y] == element || is_player_cell);
9012 boolean is_free = FALSE;
9014 if (level.use_life_bugs)
9015 is_free = (IS_FREE(xx, yy));
9017 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9019 if (xx == ax && yy == ay) // field in the middle
9021 if (num_neighbours < life_parameter[0] ||
9022 num_neighbours > life_parameter[1])
9024 Feld[xx][yy] = EL_EMPTY;
9025 if (Feld[xx][yy] != old_element)
9026 TEST_DrawLevelField(xx, yy);
9027 Stop[xx][yy] = TRUE;
9031 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9032 { // free border field
9033 if (num_neighbours >= life_parameter[2] &&
9034 num_neighbours <= life_parameter[3])
9036 Feld[xx][yy] = element;
9037 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9038 if (Feld[xx][yy] != old_element)
9039 TEST_DrawLevelField(xx, yy);
9040 Stop[xx][yy] = TRUE;
9047 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9048 SND_GAME_OF_LIFE_GROWING);
9051 static void InitRobotWheel(int x, int y)
9053 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9056 static void RunRobotWheel(int x, int y)
9058 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9061 static void StopRobotWheel(int x, int y)
9063 if (game.robot_wheel_x == x &&
9064 game.robot_wheel_y == y)
9066 game.robot_wheel_x = -1;
9067 game.robot_wheel_y = -1;
9068 game.robot_wheel_active = FALSE;
9072 static void InitTimegateWheel(int x, int y)
9074 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9077 static void RunTimegateWheel(int x, int y)
9079 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9082 static void InitMagicBallDelay(int x, int y)
9084 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9087 static void ActivateMagicBall(int bx, int by)
9091 if (level.ball_random)
9093 int pos_border = RND(8); // select one of the eight border elements
9094 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9095 int xx = pos_content % 3;
9096 int yy = pos_content / 3;
9101 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9102 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9106 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9108 int xx = x - bx + 1;
9109 int yy = y - by + 1;
9111 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9112 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9116 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9119 static void CheckExit(int x, int y)
9121 if (game.gems_still_needed > 0 ||
9122 game.sokoban_fields_still_needed > 0 ||
9123 game.sokoban_objects_still_needed > 0 ||
9124 game.lights_still_needed > 0)
9126 int element = Feld[x][y];
9127 int graphic = el2img(element);
9129 if (IS_ANIMATED(graphic))
9130 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9135 // do not re-open exit door closed after last player
9136 if (game.all_players_gone)
9139 Feld[x][y] = EL_EXIT_OPENING;
9141 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9144 static void CheckExitEM(int x, int y)
9146 if (game.gems_still_needed > 0 ||
9147 game.sokoban_fields_still_needed > 0 ||
9148 game.sokoban_objects_still_needed > 0 ||
9149 game.lights_still_needed > 0)
9151 int element = Feld[x][y];
9152 int graphic = el2img(element);
9154 if (IS_ANIMATED(graphic))
9155 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9160 // do not re-open exit door closed after last player
9161 if (game.all_players_gone)
9164 Feld[x][y] = EL_EM_EXIT_OPENING;
9166 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9169 static void CheckExitSteel(int x, int y)
9171 if (game.gems_still_needed > 0 ||
9172 game.sokoban_fields_still_needed > 0 ||
9173 game.sokoban_objects_still_needed > 0 ||
9174 game.lights_still_needed > 0)
9176 int element = Feld[x][y];
9177 int graphic = el2img(element);
9179 if (IS_ANIMATED(graphic))
9180 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9185 // do not re-open exit door closed after last player
9186 if (game.all_players_gone)
9189 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9191 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9194 static void CheckExitSteelEM(int x, int y)
9196 if (game.gems_still_needed > 0 ||
9197 game.sokoban_fields_still_needed > 0 ||
9198 game.sokoban_objects_still_needed > 0 ||
9199 game.lights_still_needed > 0)
9201 int element = Feld[x][y];
9202 int graphic = el2img(element);
9204 if (IS_ANIMATED(graphic))
9205 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9210 // do not re-open exit door closed after last player
9211 if (game.all_players_gone)
9214 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9216 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9219 static void CheckExitSP(int x, int y)
9221 if (game.gems_still_needed > 0)
9223 int element = Feld[x][y];
9224 int graphic = el2img(element);
9226 if (IS_ANIMATED(graphic))
9227 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9232 // do not re-open exit door closed after last player
9233 if (game.all_players_gone)
9236 Feld[x][y] = EL_SP_EXIT_OPENING;
9238 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9241 static void CloseAllOpenTimegates(void)
9245 SCAN_PLAYFIELD(x, y)
9247 int element = Feld[x][y];
9249 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9251 Feld[x][y] = EL_TIMEGATE_CLOSING;
9253 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9258 static void DrawTwinkleOnField(int x, int y)
9260 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9263 if (Feld[x][y] == EL_BD_DIAMOND)
9266 if (MovDelay[x][y] == 0) // next animation frame
9267 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9269 if (MovDelay[x][y] != 0) // wait some time before next frame
9273 DrawLevelElementAnimation(x, y, Feld[x][y]);
9275 if (MovDelay[x][y] != 0)
9277 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9278 10 - MovDelay[x][y]);
9280 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9285 static void MauerWaechst(int x, int y)
9289 if (!MovDelay[x][y]) // next animation frame
9290 MovDelay[x][y] = 3 * delay;
9292 if (MovDelay[x][y]) // wait some time before next frame
9296 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9298 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9299 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9301 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9304 if (!MovDelay[x][y])
9306 if (MovDir[x][y] == MV_LEFT)
9308 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9309 TEST_DrawLevelField(x - 1, y);
9311 else if (MovDir[x][y] == MV_RIGHT)
9313 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9314 TEST_DrawLevelField(x + 1, y);
9316 else if (MovDir[x][y] == MV_UP)
9318 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9319 TEST_DrawLevelField(x, y - 1);
9323 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9324 TEST_DrawLevelField(x, y + 1);
9327 Feld[x][y] = Store[x][y];
9329 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9330 TEST_DrawLevelField(x, y);
9335 static void MauerAbleger(int ax, int ay)
9337 int element = Feld[ax][ay];
9338 int graphic = el2img(element);
9339 boolean oben_frei = FALSE, unten_frei = FALSE;
9340 boolean links_frei = FALSE, rechts_frei = FALSE;
9341 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9342 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9343 boolean new_wall = FALSE;
9345 if (IS_ANIMATED(graphic))
9346 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9348 if (!MovDelay[ax][ay]) // start building new wall
9349 MovDelay[ax][ay] = 6;
9351 if (MovDelay[ax][ay]) // wait some time before building new wall
9354 if (MovDelay[ax][ay])
9358 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9360 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9362 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9364 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9367 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9368 element == EL_EXPANDABLE_WALL_ANY)
9372 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9373 Store[ax][ay-1] = element;
9374 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9375 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9376 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9377 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9382 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9383 Store[ax][ay+1] = element;
9384 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9385 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9386 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9387 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9392 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9393 element == EL_EXPANDABLE_WALL_ANY ||
9394 element == EL_EXPANDABLE_WALL ||
9395 element == EL_BD_EXPANDABLE_WALL)
9399 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9400 Store[ax-1][ay] = element;
9401 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9402 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9403 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9404 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9410 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9411 Store[ax+1][ay] = element;
9412 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9413 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9414 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9415 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9420 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9421 TEST_DrawLevelField(ax, ay);
9423 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9425 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9426 unten_massiv = TRUE;
9427 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9428 links_massiv = TRUE;
9429 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9430 rechts_massiv = TRUE;
9432 if (((oben_massiv && unten_massiv) ||
9433 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9434 element == EL_EXPANDABLE_WALL) &&
9435 ((links_massiv && rechts_massiv) ||
9436 element == EL_EXPANDABLE_WALL_VERTICAL))
9437 Feld[ax][ay] = EL_WALL;
9440 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9443 static void MauerAblegerStahl(int ax, int ay)
9445 int element = Feld[ax][ay];
9446 int graphic = el2img(element);
9447 boolean oben_frei = FALSE, unten_frei = FALSE;
9448 boolean links_frei = FALSE, rechts_frei = FALSE;
9449 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9450 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9451 boolean new_wall = FALSE;
9453 if (IS_ANIMATED(graphic))
9454 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9456 if (!MovDelay[ax][ay]) // start building new wall
9457 MovDelay[ax][ay] = 6;
9459 if (MovDelay[ax][ay]) // wait some time before building new wall
9462 if (MovDelay[ax][ay])
9466 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9468 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9470 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9472 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9475 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9476 element == EL_EXPANDABLE_STEELWALL_ANY)
9480 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9481 Store[ax][ay-1] = element;
9482 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9483 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9484 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9485 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9490 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9491 Store[ax][ay+1] = element;
9492 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9493 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9494 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9495 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9500 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9501 element == EL_EXPANDABLE_STEELWALL_ANY)
9505 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9506 Store[ax-1][ay] = element;
9507 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9508 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9509 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9510 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9516 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9517 Store[ax+1][ay] = element;
9518 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9519 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9520 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9521 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9526 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9528 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9529 unten_massiv = TRUE;
9530 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9531 links_massiv = TRUE;
9532 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9533 rechts_massiv = TRUE;
9535 if (((oben_massiv && unten_massiv) ||
9536 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9537 ((links_massiv && rechts_massiv) ||
9538 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9539 Feld[ax][ay] = EL_STEELWALL;
9542 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9545 static void CheckForDragon(int x, int y)
9548 boolean dragon_found = FALSE;
9549 static int xy[4][2] =
9557 for (i = 0; i < NUM_DIRECTIONS; i++)
9559 for (j = 0; j < 4; j++)
9561 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9563 if (IN_LEV_FIELD(xx, yy) &&
9564 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9566 if (Feld[xx][yy] == EL_DRAGON)
9567 dragon_found = TRUE;
9576 for (i = 0; i < NUM_DIRECTIONS; i++)
9578 for (j = 0; j < 3; j++)
9580 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9582 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9584 Feld[xx][yy] = EL_EMPTY;
9585 TEST_DrawLevelField(xx, yy);
9594 static void InitBuggyBase(int x, int y)
9596 int element = Feld[x][y];
9597 int activating_delay = FRAMES_PER_SECOND / 4;
9600 (element == EL_SP_BUGGY_BASE ?
9601 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9602 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9604 element == EL_SP_BUGGY_BASE_ACTIVE ?
9605 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9608 static void WarnBuggyBase(int x, int y)
9611 static int xy[4][2] =
9619 for (i = 0; i < NUM_DIRECTIONS; i++)
9621 int xx = x + xy[i][0];
9622 int yy = y + xy[i][1];
9624 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9626 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9633 static void InitTrap(int x, int y)
9635 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9638 static void ActivateTrap(int x, int y)
9640 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9643 static void ChangeActiveTrap(int x, int y)
9645 int graphic = IMG_TRAP_ACTIVE;
9647 // if new animation frame was drawn, correct crumbled sand border
9648 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9649 TEST_DrawLevelFieldCrumbled(x, y);
9652 static int getSpecialActionElement(int element, int number, int base_element)
9654 return (element != EL_EMPTY ? element :
9655 number != -1 ? base_element + number - 1 :
9659 static int getModifiedActionNumber(int value_old, int operator, int operand,
9660 int value_min, int value_max)
9662 int value_new = (operator == CA_MODE_SET ? operand :
9663 operator == CA_MODE_ADD ? value_old + operand :
9664 operator == CA_MODE_SUBTRACT ? value_old - operand :
9665 operator == CA_MODE_MULTIPLY ? value_old * operand :
9666 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9667 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9670 return (value_new < value_min ? value_min :
9671 value_new > value_max ? value_max :
9675 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9677 struct ElementInfo *ei = &element_info[element];
9678 struct ElementChangeInfo *change = &ei->change_page[page];
9679 int target_element = change->target_element;
9680 int action_type = change->action_type;
9681 int action_mode = change->action_mode;
9682 int action_arg = change->action_arg;
9683 int action_element = change->action_element;
9686 if (!change->has_action)
9689 // ---------- determine action paramater values -----------------------------
9691 int level_time_value =
9692 (level.time > 0 ? TimeLeft :
9695 int action_arg_element_raw =
9696 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9697 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9698 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9699 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9700 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9701 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9702 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9704 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9706 int action_arg_direction =
9707 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9708 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9709 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9710 change->actual_trigger_side :
9711 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9712 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9715 int action_arg_number_min =
9716 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9719 int action_arg_number_max =
9720 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9721 action_type == CA_SET_LEVEL_GEMS ? 999 :
9722 action_type == CA_SET_LEVEL_TIME ? 9999 :
9723 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9724 action_type == CA_SET_CE_VALUE ? 9999 :
9725 action_type == CA_SET_CE_SCORE ? 9999 :
9728 int action_arg_number_reset =
9729 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9730 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9731 action_type == CA_SET_LEVEL_TIME ? level.time :
9732 action_type == CA_SET_LEVEL_SCORE ? 0 :
9733 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9734 action_type == CA_SET_CE_SCORE ? 0 :
9737 int action_arg_number =
9738 (action_arg <= CA_ARG_MAX ? action_arg :
9739 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9740 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9741 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9742 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9743 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9744 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9745 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9746 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9747 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9748 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9749 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9750 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9751 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9752 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9753 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9754 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9755 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9756 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9757 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9758 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9759 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9762 int action_arg_number_old =
9763 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9764 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9765 action_type == CA_SET_LEVEL_SCORE ? game.score :
9766 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9767 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9770 int action_arg_number_new =
9771 getModifiedActionNumber(action_arg_number_old,
9772 action_mode, action_arg_number,
9773 action_arg_number_min, action_arg_number_max);
9775 int trigger_player_bits =
9776 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9777 change->actual_trigger_player_bits : change->trigger_player);
9779 int action_arg_player_bits =
9780 (action_arg >= CA_ARG_PLAYER_1 &&
9781 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9782 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9783 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9786 // ---------- execute action -----------------------------------------------
9788 switch (action_type)
9795 // ---------- level actions ----------------------------------------------
9797 case CA_RESTART_LEVEL:
9799 game.restart_level = TRUE;
9804 case CA_SHOW_ENVELOPE:
9806 int element = getSpecialActionElement(action_arg_element,
9807 action_arg_number, EL_ENVELOPE_1);
9809 if (IS_ENVELOPE(element))
9810 local_player->show_envelope = element;
9815 case CA_SET_LEVEL_TIME:
9817 if (level.time > 0) // only modify limited time value
9819 TimeLeft = action_arg_number_new;
9821 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9823 DisplayGameControlValues();
9825 if (!TimeLeft && setup.time_limit)
9826 for (i = 0; i < MAX_PLAYERS; i++)
9827 KillPlayer(&stored_player[i]);
9833 case CA_SET_LEVEL_SCORE:
9835 game.score = action_arg_number_new;
9837 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9839 DisplayGameControlValues();
9844 case CA_SET_LEVEL_GEMS:
9846 game.gems_still_needed = action_arg_number_new;
9848 game.snapshot.collected_item = TRUE;
9850 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9852 DisplayGameControlValues();
9857 case CA_SET_LEVEL_WIND:
9859 game.wind_direction = action_arg_direction;
9864 case CA_SET_LEVEL_RANDOM_SEED:
9866 // ensure that setting a new random seed while playing is predictable
9867 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9872 // ---------- player actions ---------------------------------------------
9874 case CA_MOVE_PLAYER:
9876 // automatically move to the next field in specified direction
9877 for (i = 0; i < MAX_PLAYERS; i++)
9878 if (trigger_player_bits & (1 << i))
9879 stored_player[i].programmed_action = action_arg_direction;
9884 case CA_EXIT_PLAYER:
9886 for (i = 0; i < MAX_PLAYERS; i++)
9887 if (action_arg_player_bits & (1 << i))
9888 ExitPlayer(&stored_player[i]);
9890 if (game.players_still_needed == 0)
9896 case CA_KILL_PLAYER:
9898 for (i = 0; i < MAX_PLAYERS; i++)
9899 if (action_arg_player_bits & (1 << i))
9900 KillPlayer(&stored_player[i]);
9905 case CA_SET_PLAYER_KEYS:
9907 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
9908 int element = getSpecialActionElement(action_arg_element,
9909 action_arg_number, EL_KEY_1);
9911 if (IS_KEY(element))
9913 for (i = 0; i < MAX_PLAYERS; i++)
9915 if (trigger_player_bits & (1 << i))
9917 stored_player[i].key[KEY_NR(element)] = key_state;
9919 DrawGameDoorValues();
9927 case CA_SET_PLAYER_SPEED:
9929 for (i = 0; i < MAX_PLAYERS; i++)
9931 if (trigger_player_bits & (1 << i))
9933 int move_stepsize = TILEX / stored_player[i].move_delay_value;
9935 if (action_arg == CA_ARG_SPEED_FASTER &&
9936 stored_player[i].cannot_move)
9938 action_arg_number = STEPSIZE_VERY_SLOW;
9940 else if (action_arg == CA_ARG_SPEED_SLOWER ||
9941 action_arg == CA_ARG_SPEED_FASTER)
9943 action_arg_number = 2;
9944 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
9947 else if (action_arg == CA_ARG_NUMBER_RESET)
9949 action_arg_number = level.initial_player_stepsize[i];
9953 getModifiedActionNumber(move_stepsize,
9956 action_arg_number_min,
9957 action_arg_number_max);
9959 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
9966 case CA_SET_PLAYER_SHIELD:
9968 for (i = 0; i < MAX_PLAYERS; i++)
9970 if (trigger_player_bits & (1 << i))
9972 if (action_arg == CA_ARG_SHIELD_OFF)
9974 stored_player[i].shield_normal_time_left = 0;
9975 stored_player[i].shield_deadly_time_left = 0;
9977 else if (action_arg == CA_ARG_SHIELD_NORMAL)
9979 stored_player[i].shield_normal_time_left = 999999;
9981 else if (action_arg == CA_ARG_SHIELD_DEADLY)
9983 stored_player[i].shield_normal_time_left = 999999;
9984 stored_player[i].shield_deadly_time_left = 999999;
9992 case CA_SET_PLAYER_GRAVITY:
9994 for (i = 0; i < MAX_PLAYERS; i++)
9996 if (trigger_player_bits & (1 << i))
9998 stored_player[i].gravity =
9999 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10000 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10001 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10002 stored_player[i].gravity);
10009 case CA_SET_PLAYER_ARTWORK:
10011 for (i = 0; i < MAX_PLAYERS; i++)
10013 if (trigger_player_bits & (1 << i))
10015 int artwork_element = action_arg_element;
10017 if (action_arg == CA_ARG_ELEMENT_RESET)
10019 (level.use_artwork_element[i] ? level.artwork_element[i] :
10020 stored_player[i].element_nr);
10022 if (stored_player[i].artwork_element != artwork_element)
10023 stored_player[i].Frame = 0;
10025 stored_player[i].artwork_element = artwork_element;
10027 SetPlayerWaiting(&stored_player[i], FALSE);
10029 // set number of special actions for bored and sleeping animation
10030 stored_player[i].num_special_action_bored =
10031 get_num_special_action(artwork_element,
10032 ACTION_BORING_1, ACTION_BORING_LAST);
10033 stored_player[i].num_special_action_sleeping =
10034 get_num_special_action(artwork_element,
10035 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10042 case CA_SET_PLAYER_INVENTORY:
10044 for (i = 0; i < MAX_PLAYERS; i++)
10046 struct PlayerInfo *player = &stored_player[i];
10049 if (trigger_player_bits & (1 << i))
10051 int inventory_element = action_arg_element;
10053 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10054 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10055 action_arg == CA_ARG_ELEMENT_ACTION)
10057 int element = inventory_element;
10058 int collect_count = element_info[element].collect_count_initial;
10060 if (!IS_CUSTOM_ELEMENT(element))
10063 if (collect_count == 0)
10064 player->inventory_infinite_element = element;
10066 for (k = 0; k < collect_count; k++)
10067 if (player->inventory_size < MAX_INVENTORY_SIZE)
10068 player->inventory_element[player->inventory_size++] =
10071 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10072 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10073 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10075 if (player->inventory_infinite_element != EL_UNDEFINED &&
10076 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10077 action_arg_element_raw))
10078 player->inventory_infinite_element = EL_UNDEFINED;
10080 for (k = 0, j = 0; j < player->inventory_size; j++)
10082 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10083 action_arg_element_raw))
10084 player->inventory_element[k++] = player->inventory_element[j];
10087 player->inventory_size = k;
10089 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10091 if (player->inventory_size > 0)
10093 for (j = 0; j < player->inventory_size - 1; j++)
10094 player->inventory_element[j] = player->inventory_element[j + 1];
10096 player->inventory_size--;
10099 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10101 if (player->inventory_size > 0)
10102 player->inventory_size--;
10104 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10106 player->inventory_infinite_element = EL_UNDEFINED;
10107 player->inventory_size = 0;
10109 else if (action_arg == CA_ARG_INVENTORY_RESET)
10111 player->inventory_infinite_element = EL_UNDEFINED;
10112 player->inventory_size = 0;
10114 if (level.use_initial_inventory[i])
10116 for (j = 0; j < level.initial_inventory_size[i]; j++)
10118 int element = level.initial_inventory_content[i][j];
10119 int collect_count = element_info[element].collect_count_initial;
10121 if (!IS_CUSTOM_ELEMENT(element))
10124 if (collect_count == 0)
10125 player->inventory_infinite_element = element;
10127 for (k = 0; k < collect_count; k++)
10128 if (player->inventory_size < MAX_INVENTORY_SIZE)
10129 player->inventory_element[player->inventory_size++] =
10140 // ---------- CE actions -------------------------------------------------
10142 case CA_SET_CE_VALUE:
10144 int last_ce_value = CustomValue[x][y];
10146 CustomValue[x][y] = action_arg_number_new;
10148 if (CustomValue[x][y] != last_ce_value)
10150 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10151 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10153 if (CustomValue[x][y] == 0)
10155 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10156 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10163 case CA_SET_CE_SCORE:
10165 int last_ce_score = ei->collect_score;
10167 ei->collect_score = action_arg_number_new;
10169 if (ei->collect_score != last_ce_score)
10171 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10172 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10174 if (ei->collect_score == 0)
10178 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10179 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10182 This is a very special case that seems to be a mixture between
10183 CheckElementChange() and CheckTriggeredElementChange(): while
10184 the first one only affects single elements that are triggered
10185 directly, the second one affects multiple elements in the playfield
10186 that are triggered indirectly by another element. This is a third
10187 case: Changing the CE score always affects multiple identical CEs,
10188 so every affected CE must be checked, not only the single CE for
10189 which the CE score was changed in the first place (as every instance
10190 of that CE shares the same CE score, and therefore also can change)!
10192 SCAN_PLAYFIELD(xx, yy)
10194 if (Feld[xx][yy] == element)
10195 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10196 CE_SCORE_GETS_ZERO);
10204 case CA_SET_CE_ARTWORK:
10206 int artwork_element = action_arg_element;
10207 boolean reset_frame = FALSE;
10210 if (action_arg == CA_ARG_ELEMENT_RESET)
10211 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10214 if (ei->gfx_element != artwork_element)
10215 reset_frame = TRUE;
10217 ei->gfx_element = artwork_element;
10219 SCAN_PLAYFIELD(xx, yy)
10221 if (Feld[xx][yy] == element)
10225 ResetGfxAnimation(xx, yy);
10226 ResetRandomAnimationValue(xx, yy);
10229 TEST_DrawLevelField(xx, yy);
10236 // ---------- engine actions ---------------------------------------------
10238 case CA_SET_ENGINE_SCAN_MODE:
10240 InitPlayfieldScanMode(action_arg);
10250 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10252 int old_element = Feld[x][y];
10253 int new_element = GetElementFromGroupElement(element);
10254 int previous_move_direction = MovDir[x][y];
10255 int last_ce_value = CustomValue[x][y];
10256 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10257 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10258 boolean add_player_onto_element = (new_element_is_player &&
10259 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10260 IS_WALKABLE(old_element));
10262 if (!add_player_onto_element)
10264 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10265 RemoveMovingField(x, y);
10269 Feld[x][y] = new_element;
10271 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10272 MovDir[x][y] = previous_move_direction;
10274 if (element_info[new_element].use_last_ce_value)
10275 CustomValue[x][y] = last_ce_value;
10277 InitField_WithBug1(x, y, FALSE);
10279 new_element = Feld[x][y]; // element may have changed
10281 ResetGfxAnimation(x, y);
10282 ResetRandomAnimationValue(x, y);
10284 TEST_DrawLevelField(x, y);
10286 if (GFX_CRUMBLED(new_element))
10287 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10290 // check if element under the player changes from accessible to unaccessible
10291 // (needed for special case of dropping element which then changes)
10292 // (must be checked after creating new element for walkable group elements)
10293 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10294 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10301 // "ChangeCount" not set yet to allow "entered by player" change one time
10302 if (new_element_is_player)
10303 RelocatePlayer(x, y, new_element);
10306 ChangeCount[x][y]++; // count number of changes in the same frame
10308 TestIfBadThingTouchesPlayer(x, y);
10309 TestIfPlayerTouchesCustomElement(x, y);
10310 TestIfElementTouchesCustomElement(x, y);
10313 static void CreateField(int x, int y, int element)
10315 CreateFieldExt(x, y, element, FALSE);
10318 static void CreateElementFromChange(int x, int y, int element)
10320 element = GET_VALID_RUNTIME_ELEMENT(element);
10322 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10324 int old_element = Feld[x][y];
10326 // prevent changed element from moving in same engine frame
10327 // unless both old and new element can either fall or move
10328 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10329 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10333 CreateFieldExt(x, y, element, TRUE);
10336 static boolean ChangeElement(int x, int y, int element, int page)
10338 struct ElementInfo *ei = &element_info[element];
10339 struct ElementChangeInfo *change = &ei->change_page[page];
10340 int ce_value = CustomValue[x][y];
10341 int ce_score = ei->collect_score;
10342 int target_element;
10343 int old_element = Feld[x][y];
10345 // always use default change event to prevent running into a loop
10346 if (ChangeEvent[x][y] == -1)
10347 ChangeEvent[x][y] = CE_DELAY;
10349 if (ChangeEvent[x][y] == CE_DELAY)
10351 // reset actual trigger element, trigger player and action element
10352 change->actual_trigger_element = EL_EMPTY;
10353 change->actual_trigger_player = EL_EMPTY;
10354 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10355 change->actual_trigger_side = CH_SIDE_NONE;
10356 change->actual_trigger_ce_value = 0;
10357 change->actual_trigger_ce_score = 0;
10360 // do not change elements more than a specified maximum number of changes
10361 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10364 ChangeCount[x][y]++; // count number of changes in the same frame
10366 if (change->explode)
10373 if (change->use_target_content)
10375 boolean complete_replace = TRUE;
10376 boolean can_replace[3][3];
10379 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10382 boolean is_walkable;
10383 boolean is_diggable;
10384 boolean is_collectible;
10385 boolean is_removable;
10386 boolean is_destructible;
10387 int ex = x + xx - 1;
10388 int ey = y + yy - 1;
10389 int content_element = change->target_content.e[xx][yy];
10392 can_replace[xx][yy] = TRUE;
10394 if (ex == x && ey == y) // do not check changing element itself
10397 if (content_element == EL_EMPTY_SPACE)
10399 can_replace[xx][yy] = FALSE; // do not replace border with space
10404 if (!IN_LEV_FIELD(ex, ey))
10406 can_replace[xx][yy] = FALSE;
10407 complete_replace = FALSE;
10414 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10415 e = MovingOrBlocked2Element(ex, ey);
10417 is_empty = (IS_FREE(ex, ey) ||
10418 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10420 is_walkable = (is_empty || IS_WALKABLE(e));
10421 is_diggable = (is_empty || IS_DIGGABLE(e));
10422 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10423 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10424 is_removable = (is_diggable || is_collectible);
10426 can_replace[xx][yy] =
10427 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10428 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10429 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10430 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10431 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10432 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10433 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10435 if (!can_replace[xx][yy])
10436 complete_replace = FALSE;
10439 if (!change->only_if_complete || complete_replace)
10441 boolean something_has_changed = FALSE;
10443 if (change->only_if_complete && change->use_random_replace &&
10444 RND(100) < change->random_percentage)
10447 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10449 int ex = x + xx - 1;
10450 int ey = y + yy - 1;
10451 int content_element;
10453 if (can_replace[xx][yy] && (!change->use_random_replace ||
10454 RND(100) < change->random_percentage))
10456 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10457 RemoveMovingField(ex, ey);
10459 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10461 content_element = change->target_content.e[xx][yy];
10462 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10463 ce_value, ce_score);
10465 CreateElementFromChange(ex, ey, target_element);
10467 something_has_changed = TRUE;
10469 // for symmetry reasons, freeze newly created border elements
10470 if (ex != x || ey != y)
10471 Stop[ex][ey] = TRUE; // no more moving in this frame
10475 if (something_has_changed)
10477 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10478 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10484 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10485 ce_value, ce_score);
10487 if (element == EL_DIAGONAL_GROWING ||
10488 element == EL_DIAGONAL_SHRINKING)
10490 target_element = Store[x][y];
10492 Store[x][y] = EL_EMPTY;
10495 CreateElementFromChange(x, y, target_element);
10497 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10498 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10501 // this uses direct change before indirect change
10502 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10507 static void HandleElementChange(int x, int y, int page)
10509 int element = MovingOrBlocked2Element(x, y);
10510 struct ElementInfo *ei = &element_info[element];
10511 struct ElementChangeInfo *change = &ei->change_page[page];
10512 boolean handle_action_before_change = FALSE;
10515 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10516 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10519 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10520 x, y, element, element_info[element].token_name);
10521 printf("HandleElementChange(): This should never happen!\n");
10526 // this can happen with classic bombs on walkable, changing elements
10527 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10532 if (ChangeDelay[x][y] == 0) // initialize element change
10534 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10536 if (change->can_change)
10538 // !!! not clear why graphic animation should be reset at all here !!!
10539 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10540 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10543 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10545 When using an animation frame delay of 1 (this only happens with
10546 "sp_zonk.moving.left/right" in the classic graphics), the default
10547 (non-moving) animation shows wrong animation frames (while the
10548 moving animation, like "sp_zonk.moving.left/right", is correct,
10549 so this graphical bug never shows up with the classic graphics).
10550 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10551 be drawn instead of the correct frames 0,1,2,3. This is caused by
10552 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10553 an element change: First when the change delay ("ChangeDelay[][]")
10554 counter has reached zero after decrementing, then a second time in
10555 the next frame (after "GfxFrame[][]" was already incremented) when
10556 "ChangeDelay[][]" is reset to the initial delay value again.
10558 This causes frame 0 to be drawn twice, while the last frame won't
10559 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10561 As some animations may already be cleverly designed around this bug
10562 (at least the "Snake Bite" snake tail animation does this), it cannot
10563 simply be fixed here without breaking such existing animations.
10564 Unfortunately, it cannot easily be detected if a graphics set was
10565 designed "before" or "after" the bug was fixed. As a workaround,
10566 a new graphics set option "game.graphics_engine_version" was added
10567 to be able to specify the game's major release version for which the
10568 graphics set was designed, which can then be used to decide if the
10569 bugfix should be used (version 4 and above) or not (version 3 or
10570 below, or if no version was specified at all, as with old sets).
10572 (The wrong/fixed animation frames can be tested with the test level set
10573 "test_gfxframe" and level "000", which contains a specially prepared
10574 custom element at level position (x/y) == (11/9) which uses the zonk
10575 animation mentioned above. Using "game.graphics_engine_version: 4"
10576 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10577 This can also be seen from the debug output for this test element.)
10580 // when a custom element is about to change (for example by change delay),
10581 // do not reset graphic animation when the custom element is moving
10582 if (game.graphics_engine_version < 4 &&
10585 ResetGfxAnimation(x, y);
10586 ResetRandomAnimationValue(x, y);
10589 if (change->pre_change_function)
10590 change->pre_change_function(x, y);
10594 ChangeDelay[x][y]--;
10596 if (ChangeDelay[x][y] != 0) // continue element change
10598 if (change->can_change)
10600 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10602 if (IS_ANIMATED(graphic))
10603 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10605 if (change->change_function)
10606 change->change_function(x, y);
10609 else // finish element change
10611 if (ChangePage[x][y] != -1) // remember page from delayed change
10613 page = ChangePage[x][y];
10614 ChangePage[x][y] = -1;
10616 change = &ei->change_page[page];
10619 if (IS_MOVING(x, y)) // never change a running system ;-)
10621 ChangeDelay[x][y] = 1; // try change after next move step
10622 ChangePage[x][y] = page; // remember page to use for change
10627 // special case: set new level random seed before changing element
10628 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10629 handle_action_before_change = TRUE;
10631 if (change->has_action && handle_action_before_change)
10632 ExecuteCustomElementAction(x, y, element, page);
10634 if (change->can_change)
10636 if (ChangeElement(x, y, element, page))
10638 if (change->post_change_function)
10639 change->post_change_function(x, y);
10643 if (change->has_action && !handle_action_before_change)
10644 ExecuteCustomElementAction(x, y, element, page);
10648 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10649 int trigger_element,
10651 int trigger_player,
10655 boolean change_done_any = FALSE;
10656 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10659 if (!(trigger_events[trigger_element][trigger_event]))
10662 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10664 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10666 int element = EL_CUSTOM_START + i;
10667 boolean change_done = FALSE;
10670 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10671 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10674 for (p = 0; p < element_info[element].num_change_pages; p++)
10676 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10678 if (change->can_change_or_has_action &&
10679 change->has_event[trigger_event] &&
10680 change->trigger_side & trigger_side &&
10681 change->trigger_player & trigger_player &&
10682 change->trigger_page & trigger_page_bits &&
10683 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10685 change->actual_trigger_element = trigger_element;
10686 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10687 change->actual_trigger_player_bits = trigger_player;
10688 change->actual_trigger_side = trigger_side;
10689 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10690 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10692 if ((change->can_change && !change_done) || change->has_action)
10696 SCAN_PLAYFIELD(x, y)
10698 if (Feld[x][y] == element)
10700 if (change->can_change && !change_done)
10702 // if element already changed in this frame, not only prevent
10703 // another element change (checked in ChangeElement()), but
10704 // also prevent additional element actions for this element
10706 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10707 !level.use_action_after_change_bug)
10710 ChangeDelay[x][y] = 1;
10711 ChangeEvent[x][y] = trigger_event;
10713 HandleElementChange(x, y, p);
10715 else if (change->has_action)
10717 // if element already changed in this frame, not only prevent
10718 // another element change (checked in ChangeElement()), but
10719 // also prevent additional element actions for this element
10721 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10722 !level.use_action_after_change_bug)
10725 ExecuteCustomElementAction(x, y, element, p);
10726 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10731 if (change->can_change)
10733 change_done = TRUE;
10734 change_done_any = TRUE;
10741 RECURSION_LOOP_DETECTION_END();
10743 return change_done_any;
10746 static boolean CheckElementChangeExt(int x, int y,
10748 int trigger_element,
10750 int trigger_player,
10753 boolean change_done = FALSE;
10756 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10757 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10760 if (Feld[x][y] == EL_BLOCKED)
10762 Blocked2Moving(x, y, &x, &y);
10763 element = Feld[x][y];
10766 // check if element has already changed or is about to change after moving
10767 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10768 Feld[x][y] != element) ||
10770 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10771 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10772 ChangePage[x][y] != -1)))
10775 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10777 for (p = 0; p < element_info[element].num_change_pages; p++)
10779 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10781 /* check trigger element for all events where the element that is checked
10782 for changing interacts with a directly adjacent element -- this is
10783 different to element changes that affect other elements to change on the
10784 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10785 boolean check_trigger_element =
10786 (trigger_event == CE_TOUCHING_X ||
10787 trigger_event == CE_HITTING_X ||
10788 trigger_event == CE_HIT_BY_X ||
10789 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10791 if (change->can_change_or_has_action &&
10792 change->has_event[trigger_event] &&
10793 change->trigger_side & trigger_side &&
10794 change->trigger_player & trigger_player &&
10795 (!check_trigger_element ||
10796 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10798 change->actual_trigger_element = trigger_element;
10799 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10800 change->actual_trigger_player_bits = trigger_player;
10801 change->actual_trigger_side = trigger_side;
10802 change->actual_trigger_ce_value = CustomValue[x][y];
10803 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10805 // special case: trigger element not at (x,y) position for some events
10806 if (check_trigger_element)
10818 { 0, 0 }, { 0, 0 }, { 0, 0 },
10822 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10823 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10825 change->actual_trigger_ce_value = CustomValue[xx][yy];
10826 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10829 if (change->can_change && !change_done)
10831 ChangeDelay[x][y] = 1;
10832 ChangeEvent[x][y] = trigger_event;
10834 HandleElementChange(x, y, p);
10836 change_done = TRUE;
10838 else if (change->has_action)
10840 ExecuteCustomElementAction(x, y, element, p);
10841 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10846 RECURSION_LOOP_DETECTION_END();
10848 return change_done;
10851 static void PlayPlayerSound(struct PlayerInfo *player)
10853 int jx = player->jx, jy = player->jy;
10854 int sound_element = player->artwork_element;
10855 int last_action = player->last_action_waiting;
10856 int action = player->action_waiting;
10858 if (player->is_waiting)
10860 if (action != last_action)
10861 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10863 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10867 if (action != last_action)
10868 StopSound(element_info[sound_element].sound[last_action]);
10870 if (last_action == ACTION_SLEEPING)
10871 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
10875 static void PlayAllPlayersSound(void)
10879 for (i = 0; i < MAX_PLAYERS; i++)
10880 if (stored_player[i].active)
10881 PlayPlayerSound(&stored_player[i]);
10884 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
10886 boolean last_waiting = player->is_waiting;
10887 int move_dir = player->MovDir;
10889 player->dir_waiting = move_dir;
10890 player->last_action_waiting = player->action_waiting;
10894 if (!last_waiting) // not waiting -> waiting
10896 player->is_waiting = TRUE;
10898 player->frame_counter_bored =
10900 game.player_boring_delay_fixed +
10901 GetSimpleRandom(game.player_boring_delay_random);
10902 player->frame_counter_sleeping =
10904 game.player_sleeping_delay_fixed +
10905 GetSimpleRandom(game.player_sleeping_delay_random);
10907 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
10910 if (game.player_sleeping_delay_fixed +
10911 game.player_sleeping_delay_random > 0 &&
10912 player->anim_delay_counter == 0 &&
10913 player->post_delay_counter == 0 &&
10914 FrameCounter >= player->frame_counter_sleeping)
10915 player->is_sleeping = TRUE;
10916 else if (game.player_boring_delay_fixed +
10917 game.player_boring_delay_random > 0 &&
10918 FrameCounter >= player->frame_counter_bored)
10919 player->is_bored = TRUE;
10921 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
10922 player->is_bored ? ACTION_BORING :
10925 if (player->is_sleeping && player->use_murphy)
10927 // special case for sleeping Murphy when leaning against non-free tile
10929 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
10930 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
10931 !IS_MOVING(player->jx - 1, player->jy)))
10932 move_dir = MV_LEFT;
10933 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
10934 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
10935 !IS_MOVING(player->jx + 1, player->jy)))
10936 move_dir = MV_RIGHT;
10938 player->is_sleeping = FALSE;
10940 player->dir_waiting = move_dir;
10943 if (player->is_sleeping)
10945 if (player->num_special_action_sleeping > 0)
10947 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10949 int last_special_action = player->special_action_sleeping;
10950 int num_special_action = player->num_special_action_sleeping;
10951 int special_action =
10952 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
10953 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
10954 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
10955 last_special_action + 1 : ACTION_SLEEPING);
10956 int special_graphic =
10957 el_act_dir2img(player->artwork_element, special_action, move_dir);
10959 player->anim_delay_counter =
10960 graphic_info[special_graphic].anim_delay_fixed +
10961 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10962 player->post_delay_counter =
10963 graphic_info[special_graphic].post_delay_fixed +
10964 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10966 player->special_action_sleeping = special_action;
10969 if (player->anim_delay_counter > 0)
10971 player->action_waiting = player->special_action_sleeping;
10972 player->anim_delay_counter--;
10974 else if (player->post_delay_counter > 0)
10976 player->post_delay_counter--;
10980 else if (player->is_bored)
10982 if (player->num_special_action_bored > 0)
10984 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10986 int special_action =
10987 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
10988 int special_graphic =
10989 el_act_dir2img(player->artwork_element, special_action, move_dir);
10991 player->anim_delay_counter =
10992 graphic_info[special_graphic].anim_delay_fixed +
10993 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10994 player->post_delay_counter =
10995 graphic_info[special_graphic].post_delay_fixed +
10996 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
10998 player->special_action_bored = special_action;
11001 if (player->anim_delay_counter > 0)
11003 player->action_waiting = player->special_action_bored;
11004 player->anim_delay_counter--;
11006 else if (player->post_delay_counter > 0)
11008 player->post_delay_counter--;
11013 else if (last_waiting) // waiting -> not waiting
11015 player->is_waiting = FALSE;
11016 player->is_bored = FALSE;
11017 player->is_sleeping = FALSE;
11019 player->frame_counter_bored = -1;
11020 player->frame_counter_sleeping = -1;
11022 player->anim_delay_counter = 0;
11023 player->post_delay_counter = 0;
11025 player->dir_waiting = player->MovDir;
11026 player->action_waiting = ACTION_DEFAULT;
11028 player->special_action_bored = ACTION_DEFAULT;
11029 player->special_action_sleeping = ACTION_DEFAULT;
11033 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11035 if ((!player->is_moving && player->was_moving) ||
11036 (player->MovPos == 0 && player->was_moving) ||
11037 (player->is_snapping && !player->was_snapping) ||
11038 (player->is_dropping && !player->was_dropping))
11040 if (!CheckSaveEngineSnapshotToList())
11043 player->was_moving = FALSE;
11044 player->was_snapping = TRUE;
11045 player->was_dropping = TRUE;
11049 if (player->is_moving)
11050 player->was_moving = TRUE;
11052 if (!player->is_snapping)
11053 player->was_snapping = FALSE;
11055 if (!player->is_dropping)
11056 player->was_dropping = FALSE;
11060 static void CheckSingleStepMode(struct PlayerInfo *player)
11062 if (tape.single_step && tape.recording && !tape.pausing)
11064 /* as it is called "single step mode", just return to pause mode when the
11065 player stopped moving after one tile (or never starts moving at all) */
11066 if (!player->is_moving &&
11067 !player->is_pushing &&
11068 !player->is_dropping_pressed)
11069 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11072 CheckSaveEngineSnapshot(player);
11075 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11077 int left = player_action & JOY_LEFT;
11078 int right = player_action & JOY_RIGHT;
11079 int up = player_action & JOY_UP;
11080 int down = player_action & JOY_DOWN;
11081 int button1 = player_action & JOY_BUTTON_1;
11082 int button2 = player_action & JOY_BUTTON_2;
11083 int dx = (left ? -1 : right ? 1 : 0);
11084 int dy = (up ? -1 : down ? 1 : 0);
11086 if (!player->active || tape.pausing)
11092 SnapField(player, dx, dy);
11096 DropElement(player);
11098 MovePlayer(player, dx, dy);
11101 CheckSingleStepMode(player);
11103 SetPlayerWaiting(player, FALSE);
11105 return player_action;
11109 // no actions for this player (no input at player's configured device)
11111 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11112 SnapField(player, 0, 0);
11113 CheckGravityMovementWhenNotMoving(player);
11115 if (player->MovPos == 0)
11116 SetPlayerWaiting(player, TRUE);
11118 if (player->MovPos == 0) // needed for tape.playing
11119 player->is_moving = FALSE;
11121 player->is_dropping = FALSE;
11122 player->is_dropping_pressed = FALSE;
11123 player->drop_pressed_delay = 0;
11125 CheckSingleStepMode(player);
11131 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11134 if (!tape.use_mouse)
11137 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11138 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11139 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11142 static void SetTapeActionFromMouseAction(byte *tape_action,
11143 struct MouseActionInfo *mouse_action)
11145 if (!tape.use_mouse)
11148 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11149 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11150 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11153 static void CheckLevelSolved(void)
11155 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11157 if (game_em.level_solved &&
11158 !game_em.game_over) // game won
11162 game_em.game_over = TRUE;
11164 game.all_players_gone = TRUE;
11167 if (game_em.game_over) // game lost
11168 game.all_players_gone = TRUE;
11170 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11172 if (game_sp.level_solved &&
11173 !game_sp.game_over) // game won
11177 game_sp.game_over = TRUE;
11179 game.all_players_gone = TRUE;
11182 if (game_sp.game_over) // game lost
11183 game.all_players_gone = TRUE;
11185 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11187 if (game_mm.level_solved &&
11188 !game_mm.game_over) // game won
11192 game_mm.game_over = TRUE;
11194 game.all_players_gone = TRUE;
11197 if (game_mm.game_over) // game lost
11198 game.all_players_gone = TRUE;
11202 static void CheckLevelTime(void)
11206 if (TimeFrames >= FRAMES_PER_SECOND)
11211 for (i = 0; i < MAX_PLAYERS; i++)
11213 struct PlayerInfo *player = &stored_player[i];
11215 if (SHIELD_ON(player))
11217 player->shield_normal_time_left--;
11219 if (player->shield_deadly_time_left > 0)
11220 player->shield_deadly_time_left--;
11224 if (!game.LevelSolved && !level.use_step_counter)
11232 if (TimeLeft <= 10 && setup.time_limit)
11233 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11235 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11236 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11238 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11240 if (!TimeLeft && setup.time_limit)
11242 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11243 level.native_em_level->lev->killed_out_of_time = TRUE;
11245 for (i = 0; i < MAX_PLAYERS; i++)
11246 KillPlayer(&stored_player[i]);
11249 else if (game.no_time_limit && !game.all_players_gone)
11251 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11254 level.native_em_level->lev->time =
11255 (game.no_time_limit ? TimePlayed : TimeLeft);
11258 if (tape.recording || tape.playing)
11259 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11262 if (tape.recording || tape.playing)
11263 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11265 UpdateAndDisplayGameControlValues();
11268 void AdvanceFrameAndPlayerCounters(int player_nr)
11272 // advance frame counters (global frame counter and time frame counter)
11276 // advance player counters (counters for move delay, move animation etc.)
11277 for (i = 0; i < MAX_PLAYERS; i++)
11279 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11280 int move_delay_value = stored_player[i].move_delay_value;
11281 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11283 if (!advance_player_counters) // not all players may be affected
11286 if (move_frames == 0) // less than one move per game frame
11288 int stepsize = TILEX / move_delay_value;
11289 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11290 int count = (stored_player[i].is_moving ?
11291 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11293 if (count % delay == 0)
11297 stored_player[i].Frame += move_frames;
11299 if (stored_player[i].MovPos != 0)
11300 stored_player[i].StepFrame += move_frames;
11302 if (stored_player[i].move_delay > 0)
11303 stored_player[i].move_delay--;
11305 // due to bugs in previous versions, counter must count up, not down
11306 if (stored_player[i].push_delay != -1)
11307 stored_player[i].push_delay++;
11309 if (stored_player[i].drop_delay > 0)
11310 stored_player[i].drop_delay--;
11312 if (stored_player[i].is_dropping_pressed)
11313 stored_player[i].drop_pressed_delay++;
11317 void StartGameActions(boolean init_network_game, boolean record_tape,
11320 unsigned int new_random_seed = InitRND(random_seed);
11323 TapeStartRecording(new_random_seed);
11325 if (init_network_game)
11327 SendToServer_LevelFile();
11328 SendToServer_StartPlaying();
11336 static void GameActionsExt(void)
11339 static unsigned int game_frame_delay = 0;
11341 unsigned int game_frame_delay_value;
11342 byte *recorded_player_action;
11343 byte summarized_player_action = 0;
11344 byte tape_action[MAX_PLAYERS];
11347 // detect endless loops, caused by custom element programming
11348 if (recursion_loop_detected && recursion_loop_depth == 0)
11350 char *message = getStringCat3("Internal Error! Element ",
11351 EL_NAME(recursion_loop_element),
11352 " caused endless loop! Quit the game?");
11354 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11355 EL_NAME(recursion_loop_element));
11357 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11359 recursion_loop_detected = FALSE; // if game should be continued
11366 if (game.restart_level)
11367 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11369 CheckLevelSolved();
11371 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11374 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11377 if (game_status != GAME_MODE_PLAYING) // status might have changed
11380 game_frame_delay_value =
11381 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11383 if (tape.playing && tape.warp_forward && !tape.pausing)
11384 game_frame_delay_value = 0;
11386 SetVideoFrameDelay(game_frame_delay_value);
11388 // (de)activate virtual buttons depending on current game status
11389 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11391 if (game.all_players_gone) // if no players there to be controlled anymore
11392 SetOverlayActive(FALSE);
11393 else if (!tape.playing) // if game continues after tape stopped playing
11394 SetOverlayActive(TRUE);
11399 // ---------- main game synchronization point ----------
11401 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11403 printf("::: skip == %d\n", skip);
11406 // ---------- main game synchronization point ----------
11408 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11412 if (network_playing && !network_player_action_received)
11414 // try to get network player actions in time
11416 // last chance to get network player actions without main loop delay
11417 HandleNetworking();
11419 // game was quit by network peer
11420 if (game_status != GAME_MODE_PLAYING)
11423 // check if network player actions still missing and game still running
11424 if (!network_player_action_received && !checkGameEnded())
11425 return; // failed to get network player actions in time
11427 // do not yet reset "network_player_action_received" (for tape.pausing)
11433 // at this point we know that we really continue executing the game
11435 network_player_action_received = FALSE;
11437 // when playing tape, read previously recorded player input from tape data
11438 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11440 local_player->effective_mouse_action = local_player->mouse_action;
11442 if (recorded_player_action != NULL)
11443 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11444 recorded_player_action);
11446 // TapePlayAction() may return NULL when toggling to "pause before death"
11450 if (tape.set_centered_player)
11452 game.centered_player_nr_next = tape.centered_player_nr_next;
11453 game.set_centered_player = TRUE;
11456 for (i = 0; i < MAX_PLAYERS; i++)
11458 summarized_player_action |= stored_player[i].action;
11460 if (!network_playing && (game.team_mode || tape.playing))
11461 stored_player[i].effective_action = stored_player[i].action;
11464 if (network_playing && !checkGameEnded())
11465 SendToServer_MovePlayer(summarized_player_action);
11467 // summarize all actions at local players mapped input device position
11468 // (this allows using different input devices in single player mode)
11469 if (!network.enabled && !game.team_mode)
11470 stored_player[map_player_action[local_player->index_nr]].effective_action =
11471 summarized_player_action;
11473 // summarize all actions at centered player in local team mode
11474 if (tape.recording &&
11475 setup.team_mode && !network.enabled &&
11476 setup.input_on_focus &&
11477 game.centered_player_nr != -1)
11479 for (i = 0; i < MAX_PLAYERS; i++)
11480 stored_player[map_player_action[i]].effective_action =
11481 (i == game.centered_player_nr ? summarized_player_action : 0);
11484 if (recorded_player_action != NULL)
11485 for (i = 0; i < MAX_PLAYERS; i++)
11486 stored_player[i].effective_action = recorded_player_action[i];
11488 for (i = 0; i < MAX_PLAYERS; i++)
11490 tape_action[i] = stored_player[i].effective_action;
11492 /* (this may happen in the RND game engine if a player was not present on
11493 the playfield on level start, but appeared later from a custom element */
11494 if (setup.team_mode &&
11497 !tape.player_participates[i])
11498 tape.player_participates[i] = TRUE;
11501 SetTapeActionFromMouseAction(tape_action,
11502 &local_player->effective_mouse_action);
11504 // only record actions from input devices, but not programmed actions
11505 if (tape.recording)
11506 TapeRecordAction(tape_action);
11508 // remember if game was played (especially after tape stopped playing)
11509 if (!tape.playing && summarized_player_action)
11510 game.GamePlayed = TRUE;
11512 #if USE_NEW_PLAYER_ASSIGNMENTS
11513 // !!! also map player actions in single player mode !!!
11514 // if (game.team_mode)
11517 byte mapped_action[MAX_PLAYERS];
11519 #if DEBUG_PLAYER_ACTIONS
11521 for (i = 0; i < MAX_PLAYERS; i++)
11522 printf(" %d, ", stored_player[i].effective_action);
11525 for (i = 0; i < MAX_PLAYERS; i++)
11526 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11528 for (i = 0; i < MAX_PLAYERS; i++)
11529 stored_player[i].effective_action = mapped_action[i];
11531 #if DEBUG_PLAYER_ACTIONS
11533 for (i = 0; i < MAX_PLAYERS; i++)
11534 printf(" %d, ", stored_player[i].effective_action);
11538 #if DEBUG_PLAYER_ACTIONS
11542 for (i = 0; i < MAX_PLAYERS; i++)
11543 printf(" %d, ", stored_player[i].effective_action);
11549 for (i = 0; i < MAX_PLAYERS; i++)
11551 // allow engine snapshot in case of changed movement attempt
11552 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11553 (stored_player[i].effective_action & KEY_MOTION))
11554 game.snapshot.changed_action = TRUE;
11556 // allow engine snapshot in case of snapping/dropping attempt
11557 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11558 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11559 game.snapshot.changed_action = TRUE;
11561 game.snapshot.last_action[i] = stored_player[i].effective_action;
11564 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11566 GameActions_EM_Main();
11568 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11570 GameActions_SP_Main();
11572 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11574 GameActions_MM_Main();
11578 GameActions_RND_Main();
11581 BlitScreenToBitmap(backbuffer);
11583 CheckLevelSolved();
11586 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11588 if (global.show_frames_per_second)
11590 static unsigned int fps_counter = 0;
11591 static int fps_frames = 0;
11592 unsigned int fps_delay_ms = Counter() - fps_counter;
11596 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11598 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11601 fps_counter = Counter();
11603 // always draw FPS to screen after FPS value was updated
11604 redraw_mask |= REDRAW_FPS;
11607 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11608 if (GetDrawDeactivationMask() == REDRAW_NONE)
11609 redraw_mask |= REDRAW_FPS;
11613 static void GameActions_CheckSaveEngineSnapshot(void)
11615 if (!game.snapshot.save_snapshot)
11618 // clear flag for saving snapshot _before_ saving snapshot
11619 game.snapshot.save_snapshot = FALSE;
11621 SaveEngineSnapshotToList();
11624 void GameActions(void)
11628 GameActions_CheckSaveEngineSnapshot();
11631 void GameActions_EM_Main(void)
11633 byte effective_action[MAX_PLAYERS];
11634 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11637 for (i = 0; i < MAX_PLAYERS; i++)
11638 effective_action[i] = stored_player[i].effective_action;
11640 GameActions_EM(effective_action, warp_mode);
11643 void GameActions_SP_Main(void)
11645 byte effective_action[MAX_PLAYERS];
11646 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11649 for (i = 0; i < MAX_PLAYERS; i++)
11650 effective_action[i] = stored_player[i].effective_action;
11652 GameActions_SP(effective_action, warp_mode);
11654 for (i = 0; i < MAX_PLAYERS; i++)
11656 if (stored_player[i].force_dropping)
11657 stored_player[i].action |= KEY_BUTTON_DROP;
11659 stored_player[i].force_dropping = FALSE;
11663 void GameActions_MM_Main(void)
11665 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11667 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11670 void GameActions_RND_Main(void)
11675 void GameActions_RND(void)
11677 int magic_wall_x = 0, magic_wall_y = 0;
11678 int i, x, y, element, graphic, last_gfx_frame;
11680 InitPlayfieldScanModeVars();
11682 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11684 SCAN_PLAYFIELD(x, y)
11686 ChangeCount[x][y] = 0;
11687 ChangeEvent[x][y] = -1;
11691 if (game.set_centered_player)
11693 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11695 // switching to "all players" only possible if all players fit to screen
11696 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11698 game.centered_player_nr_next = game.centered_player_nr;
11699 game.set_centered_player = FALSE;
11702 // do not switch focus to non-existing (or non-active) player
11703 if (game.centered_player_nr_next >= 0 &&
11704 !stored_player[game.centered_player_nr_next].active)
11706 game.centered_player_nr_next = game.centered_player_nr;
11707 game.set_centered_player = FALSE;
11711 if (game.set_centered_player &&
11712 ScreenMovPos == 0) // screen currently aligned at tile position
11716 if (game.centered_player_nr_next == -1)
11718 setScreenCenteredToAllPlayers(&sx, &sy);
11722 sx = stored_player[game.centered_player_nr_next].jx;
11723 sy = stored_player[game.centered_player_nr_next].jy;
11726 game.centered_player_nr = game.centered_player_nr_next;
11727 game.set_centered_player = FALSE;
11729 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11730 DrawGameDoorValues();
11733 for (i = 0; i < MAX_PLAYERS; i++)
11735 int actual_player_action = stored_player[i].effective_action;
11738 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11739 - rnd_equinox_tetrachloride 048
11740 - rnd_equinox_tetrachloride_ii 096
11741 - rnd_emanuel_schmieg 002
11742 - doctor_sloan_ww 001, 020
11744 if (stored_player[i].MovPos == 0)
11745 CheckGravityMovement(&stored_player[i]);
11748 // overwrite programmed action with tape action
11749 if (stored_player[i].programmed_action)
11750 actual_player_action = stored_player[i].programmed_action;
11752 PlayerActions(&stored_player[i], actual_player_action);
11754 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11757 ScrollScreen(NULL, SCROLL_GO_ON);
11759 /* for backwards compatibility, the following code emulates a fixed bug that
11760 occured when pushing elements (causing elements that just made their last
11761 pushing step to already (if possible) make their first falling step in the
11762 same game frame, which is bad); this code is also needed to use the famous
11763 "spring push bug" which is used in older levels and might be wanted to be
11764 used also in newer levels, but in this case the buggy pushing code is only
11765 affecting the "spring" element and no other elements */
11767 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11769 for (i = 0; i < MAX_PLAYERS; i++)
11771 struct PlayerInfo *player = &stored_player[i];
11772 int x = player->jx;
11773 int y = player->jy;
11775 if (player->active && player->is_pushing && player->is_moving &&
11777 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11778 Feld[x][y] == EL_SPRING))
11780 ContinueMoving(x, y);
11782 // continue moving after pushing (this is actually a bug)
11783 if (!IS_MOVING(x, y))
11784 Stop[x][y] = FALSE;
11789 SCAN_PLAYFIELD(x, y)
11791 Last[x][y] = Feld[x][y];
11793 ChangeCount[x][y] = 0;
11794 ChangeEvent[x][y] = -1;
11796 // this must be handled before main playfield loop
11797 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11800 if (MovDelay[x][y] <= 0)
11804 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11807 if (MovDelay[x][y] <= 0)
11810 TEST_DrawLevelField(x, y);
11812 TestIfElementTouchesCustomElement(x, y); // for empty space
11817 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11819 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11820 printf("GameActions(): This should never happen!\n");
11822 ChangePage[x][y] = -1;
11826 Stop[x][y] = FALSE;
11827 if (WasJustMoving[x][y] > 0)
11828 WasJustMoving[x][y]--;
11829 if (WasJustFalling[x][y] > 0)
11830 WasJustFalling[x][y]--;
11831 if (CheckCollision[x][y] > 0)
11832 CheckCollision[x][y]--;
11833 if (CheckImpact[x][y] > 0)
11834 CheckImpact[x][y]--;
11838 /* reset finished pushing action (not done in ContinueMoving() to allow
11839 continuous pushing animation for elements with zero push delay) */
11840 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11842 ResetGfxAnimation(x, y);
11843 TEST_DrawLevelField(x, y);
11847 if (IS_BLOCKED(x, y))
11851 Blocked2Moving(x, y, &oldx, &oldy);
11852 if (!IS_MOVING(oldx, oldy))
11854 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11855 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11856 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11857 printf("GameActions(): This should never happen!\n");
11863 SCAN_PLAYFIELD(x, y)
11865 element = Feld[x][y];
11866 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11867 last_gfx_frame = GfxFrame[x][y];
11869 ResetGfxFrame(x, y);
11871 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
11872 DrawLevelGraphicAnimation(x, y, graphic);
11874 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
11875 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
11876 ResetRandomAnimationValue(x, y);
11878 SetRandomAnimationValue(x, y);
11880 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
11882 if (IS_INACTIVE(element))
11884 if (IS_ANIMATED(graphic))
11885 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11890 // this may take place after moving, so 'element' may have changed
11891 if (IS_CHANGING(x, y) &&
11892 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
11894 int page = element_info[element].event_page_nr[CE_DELAY];
11896 HandleElementChange(x, y, page);
11898 element = Feld[x][y];
11899 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11902 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
11906 element = Feld[x][y];
11907 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11909 if (IS_ANIMATED(graphic) &&
11910 !IS_MOVING(x, y) &&
11912 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11914 if (IS_GEM(element) || element == EL_SP_INFOTRON)
11915 TEST_DrawTwinkleOnField(x, y);
11917 else if (element == EL_ACID)
11920 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11922 else if ((element == EL_EXIT_OPEN ||
11923 element == EL_EM_EXIT_OPEN ||
11924 element == EL_SP_EXIT_OPEN ||
11925 element == EL_STEEL_EXIT_OPEN ||
11926 element == EL_EM_STEEL_EXIT_OPEN ||
11927 element == EL_SP_TERMINAL ||
11928 element == EL_SP_TERMINAL_ACTIVE ||
11929 element == EL_EXTRA_TIME ||
11930 element == EL_SHIELD_NORMAL ||
11931 element == EL_SHIELD_DEADLY) &&
11932 IS_ANIMATED(graphic))
11933 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11934 else if (IS_MOVING(x, y))
11935 ContinueMoving(x, y);
11936 else if (IS_ACTIVE_BOMB(element))
11937 CheckDynamite(x, y);
11938 else if (element == EL_AMOEBA_GROWING)
11939 AmoebeWaechst(x, y);
11940 else if (element == EL_AMOEBA_SHRINKING)
11941 AmoebaDisappearing(x, y);
11943 #if !USE_NEW_AMOEBA_CODE
11944 else if (IS_AMOEBALIVE(element))
11945 AmoebeAbleger(x, y);
11948 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
11950 else if (element == EL_EXIT_CLOSED)
11952 else if (element == EL_EM_EXIT_CLOSED)
11954 else if (element == EL_STEEL_EXIT_CLOSED)
11955 CheckExitSteel(x, y);
11956 else if (element == EL_EM_STEEL_EXIT_CLOSED)
11957 CheckExitSteelEM(x, y);
11958 else if (element == EL_SP_EXIT_CLOSED)
11960 else if (element == EL_EXPANDABLE_WALL_GROWING ||
11961 element == EL_EXPANDABLE_STEELWALL_GROWING)
11962 MauerWaechst(x, y);
11963 else if (element == EL_EXPANDABLE_WALL ||
11964 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
11965 element == EL_EXPANDABLE_WALL_VERTICAL ||
11966 element == EL_EXPANDABLE_WALL_ANY ||
11967 element == EL_BD_EXPANDABLE_WALL)
11968 MauerAbleger(x, y);
11969 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
11970 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
11971 element == EL_EXPANDABLE_STEELWALL_ANY)
11972 MauerAblegerStahl(x, y);
11973 else if (element == EL_FLAMES)
11974 CheckForDragon(x, y);
11975 else if (element == EL_EXPLOSION)
11976 ; // drawing of correct explosion animation is handled separately
11977 else if (element == EL_ELEMENT_SNAPPING ||
11978 element == EL_DIAGONAL_SHRINKING ||
11979 element == EL_DIAGONAL_GROWING)
11981 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
11983 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11985 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
11986 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11988 if (IS_BELT_ACTIVE(element))
11989 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
11991 if (game.magic_wall_active)
11993 int jx = local_player->jx, jy = local_player->jy;
11995 // play the element sound at the position nearest to the player
11996 if ((element == EL_MAGIC_WALL_FULL ||
11997 element == EL_MAGIC_WALL_ACTIVE ||
11998 element == EL_MAGIC_WALL_EMPTYING ||
11999 element == EL_BD_MAGIC_WALL_FULL ||
12000 element == EL_BD_MAGIC_WALL_ACTIVE ||
12001 element == EL_BD_MAGIC_WALL_EMPTYING ||
12002 element == EL_DC_MAGIC_WALL_FULL ||
12003 element == EL_DC_MAGIC_WALL_ACTIVE ||
12004 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12005 ABS(x - jx) + ABS(y - jy) <
12006 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12014 #if USE_NEW_AMOEBA_CODE
12015 // new experimental amoeba growth stuff
12016 if (!(FrameCounter % 8))
12018 static unsigned int random = 1684108901;
12020 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12022 x = RND(lev_fieldx);
12023 y = RND(lev_fieldy);
12024 element = Feld[x][y];
12026 if (!IS_PLAYER(x,y) &&
12027 (element == EL_EMPTY ||
12028 CAN_GROW_INTO(element) ||
12029 element == EL_QUICKSAND_EMPTY ||
12030 element == EL_QUICKSAND_FAST_EMPTY ||
12031 element == EL_ACID_SPLASH_LEFT ||
12032 element == EL_ACID_SPLASH_RIGHT))
12034 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12035 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12036 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12037 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12038 Feld[x][y] = EL_AMOEBA_DROP;
12041 random = random * 129 + 1;
12046 game.explosions_delayed = FALSE;
12048 SCAN_PLAYFIELD(x, y)
12050 element = Feld[x][y];
12052 if (ExplodeField[x][y])
12053 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12054 else if (element == EL_EXPLOSION)
12055 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12057 ExplodeField[x][y] = EX_TYPE_NONE;
12060 game.explosions_delayed = TRUE;
12062 if (game.magic_wall_active)
12064 if (!(game.magic_wall_time_left % 4))
12066 int element = Feld[magic_wall_x][magic_wall_y];
12068 if (element == EL_BD_MAGIC_WALL_FULL ||
12069 element == EL_BD_MAGIC_WALL_ACTIVE ||
12070 element == EL_BD_MAGIC_WALL_EMPTYING)
12071 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12072 else if (element == EL_DC_MAGIC_WALL_FULL ||
12073 element == EL_DC_MAGIC_WALL_ACTIVE ||
12074 element == EL_DC_MAGIC_WALL_EMPTYING)
12075 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12077 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12080 if (game.magic_wall_time_left > 0)
12082 game.magic_wall_time_left--;
12084 if (!game.magic_wall_time_left)
12086 SCAN_PLAYFIELD(x, y)
12088 element = Feld[x][y];
12090 if (element == EL_MAGIC_WALL_ACTIVE ||
12091 element == EL_MAGIC_WALL_FULL)
12093 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12094 TEST_DrawLevelField(x, y);
12096 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12097 element == EL_BD_MAGIC_WALL_FULL)
12099 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12100 TEST_DrawLevelField(x, y);
12102 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12103 element == EL_DC_MAGIC_WALL_FULL)
12105 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12106 TEST_DrawLevelField(x, y);
12110 game.magic_wall_active = FALSE;
12115 if (game.light_time_left > 0)
12117 game.light_time_left--;
12119 if (game.light_time_left == 0)
12120 RedrawAllLightSwitchesAndInvisibleElements();
12123 if (game.timegate_time_left > 0)
12125 game.timegate_time_left--;
12127 if (game.timegate_time_left == 0)
12128 CloseAllOpenTimegates();
12131 if (game.lenses_time_left > 0)
12133 game.lenses_time_left--;
12135 if (game.lenses_time_left == 0)
12136 RedrawAllInvisibleElementsForLenses();
12139 if (game.magnify_time_left > 0)
12141 game.magnify_time_left--;
12143 if (game.magnify_time_left == 0)
12144 RedrawAllInvisibleElementsForMagnifier();
12147 for (i = 0; i < MAX_PLAYERS; i++)
12149 struct PlayerInfo *player = &stored_player[i];
12151 if (SHIELD_ON(player))
12153 if (player->shield_deadly_time_left)
12154 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12155 else if (player->shield_normal_time_left)
12156 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12160 #if USE_DELAYED_GFX_REDRAW
12161 SCAN_PLAYFIELD(x, y)
12163 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12165 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12166 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12168 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12169 DrawLevelField(x, y);
12171 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12172 DrawLevelFieldCrumbled(x, y);
12174 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12175 DrawLevelFieldCrumbledNeighbours(x, y);
12177 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12178 DrawTwinkleOnField(x, y);
12181 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12186 PlayAllPlayersSound();
12188 for (i = 0; i < MAX_PLAYERS; i++)
12190 struct PlayerInfo *player = &stored_player[i];
12192 if (player->show_envelope != 0 && (!player->active ||
12193 player->MovPos == 0))
12195 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12197 player->show_envelope = 0;
12201 // use random number generator in every frame to make it less predictable
12202 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12206 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12208 int min_x = x, min_y = y, max_x = x, max_y = y;
12211 for (i = 0; i < MAX_PLAYERS; i++)
12213 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12215 if (!stored_player[i].active || &stored_player[i] == player)
12218 min_x = MIN(min_x, jx);
12219 min_y = MIN(min_y, jy);
12220 max_x = MAX(max_x, jx);
12221 max_y = MAX(max_y, jy);
12224 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12227 static boolean AllPlayersInVisibleScreen(void)
12231 for (i = 0; i < MAX_PLAYERS; i++)
12233 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12235 if (!stored_player[i].active)
12238 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12245 void ScrollLevel(int dx, int dy)
12247 int scroll_offset = 2 * TILEX_VAR;
12250 BlitBitmap(drawto_field, drawto_field,
12251 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12252 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12253 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12254 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12255 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12256 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12260 x = (dx == 1 ? BX1 : BX2);
12261 for (y = BY1; y <= BY2; y++)
12262 DrawScreenField(x, y);
12267 y = (dy == 1 ? BY1 : BY2);
12268 for (x = BX1; x <= BX2; x++)
12269 DrawScreenField(x, y);
12272 redraw_mask |= REDRAW_FIELD;
12275 static boolean canFallDown(struct PlayerInfo *player)
12277 int jx = player->jx, jy = player->jy;
12279 return (IN_LEV_FIELD(jx, jy + 1) &&
12280 (IS_FREE(jx, jy + 1) ||
12281 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12282 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12283 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12286 static boolean canPassField(int x, int y, int move_dir)
12288 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12289 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12290 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12291 int nextx = x + dx;
12292 int nexty = y + dy;
12293 int element = Feld[x][y];
12295 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12296 !CAN_MOVE(element) &&
12297 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12298 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12299 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12302 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12304 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12305 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12306 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12310 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12311 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12312 (IS_DIGGABLE(Feld[newx][newy]) ||
12313 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12314 canPassField(newx, newy, move_dir)));
12317 static void CheckGravityMovement(struct PlayerInfo *player)
12319 if (player->gravity && !player->programmed_action)
12321 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12322 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12323 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12324 int jx = player->jx, jy = player->jy;
12325 boolean player_is_moving_to_valid_field =
12326 (!player_is_snapping &&
12327 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12328 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12329 boolean player_can_fall_down = canFallDown(player);
12331 if (player_can_fall_down &&
12332 !player_is_moving_to_valid_field)
12333 player->programmed_action = MV_DOWN;
12337 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12339 return CheckGravityMovement(player);
12341 if (player->gravity && !player->programmed_action)
12343 int jx = player->jx, jy = player->jy;
12344 boolean field_under_player_is_free =
12345 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12346 boolean player_is_standing_on_valid_field =
12347 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12348 (IS_WALKABLE(Feld[jx][jy]) &&
12349 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12351 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12352 player->programmed_action = MV_DOWN;
12357 MovePlayerOneStep()
12358 -----------------------------------------------------------------------------
12359 dx, dy: direction (non-diagonal) to try to move the player to
12360 real_dx, real_dy: direction as read from input device (can be diagonal)
12363 boolean MovePlayerOneStep(struct PlayerInfo *player,
12364 int dx, int dy, int real_dx, int real_dy)
12366 int jx = player->jx, jy = player->jy;
12367 int new_jx = jx + dx, new_jy = jy + dy;
12369 boolean player_can_move = !player->cannot_move;
12371 if (!player->active || (!dx && !dy))
12372 return MP_NO_ACTION;
12374 player->MovDir = (dx < 0 ? MV_LEFT :
12375 dx > 0 ? MV_RIGHT :
12377 dy > 0 ? MV_DOWN : MV_NONE);
12379 if (!IN_LEV_FIELD(new_jx, new_jy))
12380 return MP_NO_ACTION;
12382 if (!player_can_move)
12384 if (player->MovPos == 0)
12386 player->is_moving = FALSE;
12387 player->is_digging = FALSE;
12388 player->is_collecting = FALSE;
12389 player->is_snapping = FALSE;
12390 player->is_pushing = FALSE;
12394 if (!network.enabled && game.centered_player_nr == -1 &&
12395 !AllPlayersInSight(player, new_jx, new_jy))
12396 return MP_NO_ACTION;
12398 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12399 if (can_move != MP_MOVING)
12402 // check if DigField() has caused relocation of the player
12403 if (player->jx != jx || player->jy != jy)
12404 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12406 StorePlayer[jx][jy] = 0;
12407 player->last_jx = jx;
12408 player->last_jy = jy;
12409 player->jx = new_jx;
12410 player->jy = new_jy;
12411 StorePlayer[new_jx][new_jy] = player->element_nr;
12413 if (player->move_delay_value_next != -1)
12415 player->move_delay_value = player->move_delay_value_next;
12416 player->move_delay_value_next = -1;
12420 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12422 player->step_counter++;
12424 PlayerVisit[jx][jy] = FrameCounter;
12426 player->is_moving = TRUE;
12429 // should better be called in MovePlayer(), but this breaks some tapes
12430 ScrollPlayer(player, SCROLL_INIT);
12436 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12438 int jx = player->jx, jy = player->jy;
12439 int old_jx = jx, old_jy = jy;
12440 int moved = MP_NO_ACTION;
12442 if (!player->active)
12447 if (player->MovPos == 0)
12449 player->is_moving = FALSE;
12450 player->is_digging = FALSE;
12451 player->is_collecting = FALSE;
12452 player->is_snapping = FALSE;
12453 player->is_pushing = FALSE;
12459 if (player->move_delay > 0)
12462 player->move_delay = -1; // set to "uninitialized" value
12464 // store if player is automatically moved to next field
12465 player->is_auto_moving = (player->programmed_action != MV_NONE);
12467 // remove the last programmed player action
12468 player->programmed_action = 0;
12470 if (player->MovPos)
12472 // should only happen if pre-1.2 tape recordings are played
12473 // this is only for backward compatibility
12475 int original_move_delay_value = player->move_delay_value;
12478 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12482 // scroll remaining steps with finest movement resolution
12483 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12485 while (player->MovPos)
12487 ScrollPlayer(player, SCROLL_GO_ON);
12488 ScrollScreen(NULL, SCROLL_GO_ON);
12490 AdvanceFrameAndPlayerCounters(player->index_nr);
12493 BackToFront_WithFrameDelay(0);
12496 player->move_delay_value = original_move_delay_value;
12499 player->is_active = FALSE;
12501 if (player->last_move_dir & MV_HORIZONTAL)
12503 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12504 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12508 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12509 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12512 if (!moved && !player->is_active)
12514 player->is_moving = FALSE;
12515 player->is_digging = FALSE;
12516 player->is_collecting = FALSE;
12517 player->is_snapping = FALSE;
12518 player->is_pushing = FALSE;
12524 if (moved & MP_MOVING && !ScreenMovPos &&
12525 (player->index_nr == game.centered_player_nr ||
12526 game.centered_player_nr == -1))
12528 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12530 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12532 // actual player has left the screen -- scroll in that direction
12533 if (jx != old_jx) // player has moved horizontally
12534 scroll_x += (jx - old_jx);
12535 else // player has moved vertically
12536 scroll_y += (jy - old_jy);
12540 int offset_raw = game.scroll_delay_value;
12542 if (jx != old_jx) // player has moved horizontally
12544 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12545 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12546 int new_scroll_x = jx - MIDPOSX + offset_x;
12548 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12549 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12550 scroll_x = new_scroll_x;
12552 // don't scroll over playfield boundaries
12553 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12555 // don't scroll more than one field at a time
12556 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12558 // don't scroll against the player's moving direction
12559 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12560 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12561 scroll_x = old_scroll_x;
12563 else // player has moved vertically
12565 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12566 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12567 int new_scroll_y = jy - MIDPOSY + offset_y;
12569 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12570 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12571 scroll_y = new_scroll_y;
12573 // don't scroll over playfield boundaries
12574 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12576 // don't scroll more than one field at a time
12577 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12579 // don't scroll against the player's moving direction
12580 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12581 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12582 scroll_y = old_scroll_y;
12586 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12588 if (!network.enabled && game.centered_player_nr == -1 &&
12589 !AllPlayersInVisibleScreen())
12591 scroll_x = old_scroll_x;
12592 scroll_y = old_scroll_y;
12596 ScrollScreen(player, SCROLL_INIT);
12597 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12602 player->StepFrame = 0;
12604 if (moved & MP_MOVING)
12606 if (old_jx != jx && old_jy == jy)
12607 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12608 else if (old_jx == jx && old_jy != jy)
12609 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12611 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12613 player->last_move_dir = player->MovDir;
12614 player->is_moving = TRUE;
12615 player->is_snapping = FALSE;
12616 player->is_switching = FALSE;
12617 player->is_dropping = FALSE;
12618 player->is_dropping_pressed = FALSE;
12619 player->drop_pressed_delay = 0;
12622 // should better be called here than above, but this breaks some tapes
12623 ScrollPlayer(player, SCROLL_INIT);
12628 CheckGravityMovementWhenNotMoving(player);
12630 player->is_moving = FALSE;
12632 /* at this point, the player is allowed to move, but cannot move right now
12633 (e.g. because of something blocking the way) -- ensure that the player
12634 is also allowed to move in the next frame (in old versions before 3.1.1,
12635 the player was forced to wait again for eight frames before next try) */
12637 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12638 player->move_delay = 0; // allow direct movement in the next frame
12641 if (player->move_delay == -1) // not yet initialized by DigField()
12642 player->move_delay = player->move_delay_value;
12644 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12646 TestIfPlayerTouchesBadThing(jx, jy);
12647 TestIfPlayerTouchesCustomElement(jx, jy);
12650 if (!player->active)
12651 RemovePlayer(player);
12656 void ScrollPlayer(struct PlayerInfo *player, int mode)
12658 int jx = player->jx, jy = player->jy;
12659 int last_jx = player->last_jx, last_jy = player->last_jy;
12660 int move_stepsize = TILEX / player->move_delay_value;
12662 if (!player->active)
12665 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12668 if (mode == SCROLL_INIT)
12670 player->actual_frame_counter = FrameCounter;
12671 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12673 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12674 Feld[last_jx][last_jy] == EL_EMPTY)
12676 int last_field_block_delay = 0; // start with no blocking at all
12677 int block_delay_adjustment = player->block_delay_adjustment;
12679 // if player blocks last field, add delay for exactly one move
12680 if (player->block_last_field)
12682 last_field_block_delay += player->move_delay_value;
12684 // when blocking enabled, prevent moving up despite gravity
12685 if (player->gravity && player->MovDir == MV_UP)
12686 block_delay_adjustment = -1;
12689 // add block delay adjustment (also possible when not blocking)
12690 last_field_block_delay += block_delay_adjustment;
12692 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12693 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12696 if (player->MovPos != 0) // player has not yet reached destination
12699 else if (!FrameReached(&player->actual_frame_counter, 1))
12702 if (player->MovPos != 0)
12704 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12705 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12707 // before DrawPlayer() to draw correct player graphic for this case
12708 if (player->MovPos == 0)
12709 CheckGravityMovement(player);
12712 if (player->MovPos == 0) // player reached destination field
12714 if (player->move_delay_reset_counter > 0)
12716 player->move_delay_reset_counter--;
12718 if (player->move_delay_reset_counter == 0)
12720 // continue with normal speed after quickly moving through gate
12721 HALVE_PLAYER_SPEED(player);
12723 // be able to make the next move without delay
12724 player->move_delay = 0;
12728 player->last_jx = jx;
12729 player->last_jy = jy;
12731 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12732 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12733 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12734 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12735 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12736 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12737 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12738 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12740 ExitPlayer(player);
12742 if (game.players_still_needed == 0 &&
12743 (game.friends_still_needed == 0 ||
12744 IS_SP_ELEMENT(Feld[jx][jy])))
12748 // this breaks one level: "machine", level 000
12750 int move_direction = player->MovDir;
12751 int enter_side = MV_DIR_OPPOSITE(move_direction);
12752 int leave_side = move_direction;
12753 int old_jx = last_jx;
12754 int old_jy = last_jy;
12755 int old_element = Feld[old_jx][old_jy];
12756 int new_element = Feld[jx][jy];
12758 if (IS_CUSTOM_ELEMENT(old_element))
12759 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12761 player->index_bit, leave_side);
12763 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12764 CE_PLAYER_LEAVES_X,
12765 player->index_bit, leave_side);
12767 if (IS_CUSTOM_ELEMENT(new_element))
12768 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12769 player->index_bit, enter_side);
12771 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12772 CE_PLAYER_ENTERS_X,
12773 player->index_bit, enter_side);
12775 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12776 CE_MOVE_OF_X, move_direction);
12779 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12781 TestIfPlayerTouchesBadThing(jx, jy);
12782 TestIfPlayerTouchesCustomElement(jx, jy);
12784 /* needed because pushed element has not yet reached its destination,
12785 so it would trigger a change event at its previous field location */
12786 if (!player->is_pushing)
12787 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12789 if (!player->active)
12790 RemovePlayer(player);
12793 if (!game.LevelSolved && level.use_step_counter)
12803 if (TimeLeft <= 10 && setup.time_limit)
12804 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12806 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12808 DisplayGameControlValues();
12810 if (!TimeLeft && setup.time_limit)
12811 for (i = 0; i < MAX_PLAYERS; i++)
12812 KillPlayer(&stored_player[i]);
12814 else if (game.no_time_limit && !game.all_players_gone)
12816 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12818 DisplayGameControlValues();
12822 if (tape.single_step && tape.recording && !tape.pausing &&
12823 !player->programmed_action)
12824 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12826 if (!player->programmed_action)
12827 CheckSaveEngineSnapshot(player);
12831 void ScrollScreen(struct PlayerInfo *player, int mode)
12833 static unsigned int screen_frame_counter = 0;
12835 if (mode == SCROLL_INIT)
12837 // set scrolling step size according to actual player's moving speed
12838 ScrollStepSize = TILEX / player->move_delay_value;
12840 screen_frame_counter = FrameCounter;
12841 ScreenMovDir = player->MovDir;
12842 ScreenMovPos = player->MovPos;
12843 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12846 else if (!FrameReached(&screen_frame_counter, 1))
12851 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
12852 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12853 redraw_mask |= REDRAW_FIELD;
12856 ScreenMovDir = MV_NONE;
12859 void TestIfPlayerTouchesCustomElement(int x, int y)
12861 static int xy[4][2] =
12868 static int trigger_sides[4][2] =
12870 // center side border side
12871 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12872 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12873 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12874 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12876 static int touch_dir[4] =
12878 MV_LEFT | MV_RIGHT,
12883 int center_element = Feld[x][y]; // should always be non-moving!
12886 for (i = 0; i < NUM_DIRECTIONS; i++)
12888 int xx = x + xy[i][0];
12889 int yy = y + xy[i][1];
12890 int center_side = trigger_sides[i][0];
12891 int border_side = trigger_sides[i][1];
12892 int border_element;
12894 if (!IN_LEV_FIELD(xx, yy))
12897 if (IS_PLAYER(x, y)) // player found at center element
12899 struct PlayerInfo *player = PLAYERINFO(x, y);
12901 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12902 border_element = Feld[xx][yy]; // may be moving!
12903 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12904 border_element = Feld[xx][yy];
12905 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
12906 border_element = MovingOrBlocked2Element(xx, yy);
12908 continue; // center and border element do not touch
12910 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
12911 player->index_bit, border_side);
12912 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
12913 CE_PLAYER_TOUCHES_X,
12914 player->index_bit, border_side);
12917 /* use player element that is initially defined in the level playfield,
12918 not the player element that corresponds to the runtime player number
12919 (example: a level that contains EL_PLAYER_3 as the only player would
12920 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12921 int player_element = PLAYERINFO(x, y)->initial_element;
12923 CheckElementChangeBySide(xx, yy, border_element, player_element,
12924 CE_TOUCHING_X, border_side);
12927 else if (IS_PLAYER(xx, yy)) // player found at border element
12929 struct PlayerInfo *player = PLAYERINFO(xx, yy);
12931 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12933 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
12934 continue; // center and border element do not touch
12937 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
12938 player->index_bit, center_side);
12939 CheckTriggeredElementChangeByPlayer(x, y, center_element,
12940 CE_PLAYER_TOUCHES_X,
12941 player->index_bit, center_side);
12944 /* use player element that is initially defined in the level playfield,
12945 not the player element that corresponds to the runtime player number
12946 (example: a level that contains EL_PLAYER_3 as the only player would
12947 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12948 int player_element = PLAYERINFO(xx, yy)->initial_element;
12950 CheckElementChangeBySide(x, y, center_element, player_element,
12951 CE_TOUCHING_X, center_side);
12959 void TestIfElementTouchesCustomElement(int x, int y)
12961 static int xy[4][2] =
12968 static int trigger_sides[4][2] =
12970 // center side border side
12971 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12972 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12973 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12974 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12976 static int touch_dir[4] =
12978 MV_LEFT | MV_RIGHT,
12983 boolean change_center_element = FALSE;
12984 int center_element = Feld[x][y]; // should always be non-moving!
12985 int border_element_old[NUM_DIRECTIONS];
12988 for (i = 0; i < NUM_DIRECTIONS; i++)
12990 int xx = x + xy[i][0];
12991 int yy = y + xy[i][1];
12992 int border_element;
12994 border_element_old[i] = -1;
12996 if (!IN_LEV_FIELD(xx, yy))
12999 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13000 border_element = Feld[xx][yy]; // may be moving!
13001 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13002 border_element = Feld[xx][yy];
13003 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13004 border_element = MovingOrBlocked2Element(xx, yy);
13006 continue; // center and border element do not touch
13008 border_element_old[i] = border_element;
13011 for (i = 0; i < NUM_DIRECTIONS; i++)
13013 int xx = x + xy[i][0];
13014 int yy = y + xy[i][1];
13015 int center_side = trigger_sides[i][0];
13016 int border_element = border_element_old[i];
13018 if (border_element == -1)
13021 // check for change of border element
13022 CheckElementChangeBySide(xx, yy, border_element, center_element,
13023 CE_TOUCHING_X, center_side);
13025 // (center element cannot be player, so we dont have to check this here)
13028 for (i = 0; i < NUM_DIRECTIONS; i++)
13030 int xx = x + xy[i][0];
13031 int yy = y + xy[i][1];
13032 int border_side = trigger_sides[i][1];
13033 int border_element = border_element_old[i];
13035 if (border_element == -1)
13038 // check for change of center element (but change it only once)
13039 if (!change_center_element)
13040 change_center_element =
13041 CheckElementChangeBySide(x, y, center_element, border_element,
13042 CE_TOUCHING_X, border_side);
13044 if (IS_PLAYER(xx, yy))
13046 /* use player element that is initially defined in the level playfield,
13047 not the player element that corresponds to the runtime player number
13048 (example: a level that contains EL_PLAYER_3 as the only player would
13049 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13050 int player_element = PLAYERINFO(xx, yy)->initial_element;
13052 CheckElementChangeBySide(x, y, center_element, player_element,
13053 CE_TOUCHING_X, border_side);
13058 void TestIfElementHitsCustomElement(int x, int y, int direction)
13060 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13061 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13062 int hitx = x + dx, hity = y + dy;
13063 int hitting_element = Feld[x][y];
13064 int touched_element;
13066 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13069 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13070 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13072 if (IN_LEV_FIELD(hitx, hity))
13074 int opposite_direction = MV_DIR_OPPOSITE(direction);
13075 int hitting_side = direction;
13076 int touched_side = opposite_direction;
13077 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13078 MovDir[hitx][hity] != direction ||
13079 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13085 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13086 CE_HITTING_X, touched_side);
13088 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13089 CE_HIT_BY_X, hitting_side);
13091 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13092 CE_HIT_BY_SOMETHING, opposite_direction);
13094 if (IS_PLAYER(hitx, hity))
13096 /* use player element that is initially defined in the level playfield,
13097 not the player element that corresponds to the runtime player number
13098 (example: a level that contains EL_PLAYER_3 as the only player would
13099 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13100 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13102 CheckElementChangeBySide(x, y, hitting_element, player_element,
13103 CE_HITTING_X, touched_side);
13108 // "hitting something" is also true when hitting the playfield border
13109 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13110 CE_HITTING_SOMETHING, direction);
13113 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13115 int i, kill_x = -1, kill_y = -1;
13117 int bad_element = -1;
13118 static int test_xy[4][2] =
13125 static int test_dir[4] =
13133 for (i = 0; i < NUM_DIRECTIONS; i++)
13135 int test_x, test_y, test_move_dir, test_element;
13137 test_x = good_x + test_xy[i][0];
13138 test_y = good_y + test_xy[i][1];
13140 if (!IN_LEV_FIELD(test_x, test_y))
13144 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13146 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13148 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13149 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13151 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13152 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13156 bad_element = test_element;
13162 if (kill_x != -1 || kill_y != -1)
13164 if (IS_PLAYER(good_x, good_y))
13166 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13168 if (player->shield_deadly_time_left > 0 &&
13169 !IS_INDESTRUCTIBLE(bad_element))
13170 Bang(kill_x, kill_y);
13171 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13172 KillPlayer(player);
13175 Bang(good_x, good_y);
13179 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13181 int i, kill_x = -1, kill_y = -1;
13182 int bad_element = Feld[bad_x][bad_y];
13183 static int test_xy[4][2] =
13190 static int touch_dir[4] =
13192 MV_LEFT | MV_RIGHT,
13197 static int test_dir[4] =
13205 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13208 for (i = 0; i < NUM_DIRECTIONS; i++)
13210 int test_x, test_y, test_move_dir, test_element;
13212 test_x = bad_x + test_xy[i][0];
13213 test_y = bad_y + test_xy[i][1];
13215 if (!IN_LEV_FIELD(test_x, test_y))
13219 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13221 test_element = Feld[test_x][test_y];
13223 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13224 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13226 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13227 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13229 // good thing is player or penguin that does not move away
13230 if (IS_PLAYER(test_x, test_y))
13232 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13234 if (bad_element == EL_ROBOT && player->is_moving)
13235 continue; // robot does not kill player if he is moving
13237 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13239 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13240 continue; // center and border element do not touch
13248 else if (test_element == EL_PENGUIN)
13258 if (kill_x != -1 || kill_y != -1)
13260 if (IS_PLAYER(kill_x, kill_y))
13262 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13264 if (player->shield_deadly_time_left > 0 &&
13265 !IS_INDESTRUCTIBLE(bad_element))
13266 Bang(bad_x, bad_y);
13267 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13268 KillPlayer(player);
13271 Bang(kill_x, kill_y);
13275 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13277 int bad_element = Feld[bad_x][bad_y];
13278 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13279 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13280 int test_x = bad_x + dx, test_y = bad_y + dy;
13281 int test_move_dir, test_element;
13282 int kill_x = -1, kill_y = -1;
13284 if (!IN_LEV_FIELD(test_x, test_y))
13288 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13290 test_element = Feld[test_x][test_y];
13292 if (test_move_dir != bad_move_dir)
13294 // good thing can be player or penguin that does not move away
13295 if (IS_PLAYER(test_x, test_y))
13297 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13299 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13300 player as being hit when he is moving towards the bad thing, because
13301 the "get hit by" condition would be lost after the player stops) */
13302 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13303 return; // player moves away from bad thing
13308 else if (test_element == EL_PENGUIN)
13315 if (kill_x != -1 || kill_y != -1)
13317 if (IS_PLAYER(kill_x, kill_y))
13319 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13321 if (player->shield_deadly_time_left > 0 &&
13322 !IS_INDESTRUCTIBLE(bad_element))
13323 Bang(bad_x, bad_y);
13324 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13325 KillPlayer(player);
13328 Bang(kill_x, kill_y);
13332 void TestIfPlayerTouchesBadThing(int x, int y)
13334 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13337 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13339 TestIfGoodThingHitsBadThing(x, y, move_dir);
13342 void TestIfBadThingTouchesPlayer(int x, int y)
13344 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13347 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13349 TestIfBadThingHitsGoodThing(x, y, move_dir);
13352 void TestIfFriendTouchesBadThing(int x, int y)
13354 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13357 void TestIfBadThingTouchesFriend(int x, int y)
13359 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13362 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13364 int i, kill_x = bad_x, kill_y = bad_y;
13365 static int xy[4][2] =
13373 for (i = 0; i < NUM_DIRECTIONS; i++)
13377 x = bad_x + xy[i][0];
13378 y = bad_y + xy[i][1];
13379 if (!IN_LEV_FIELD(x, y))
13382 element = Feld[x][y];
13383 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13384 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13392 if (kill_x != bad_x || kill_y != bad_y)
13393 Bang(bad_x, bad_y);
13396 void KillPlayer(struct PlayerInfo *player)
13398 int jx = player->jx, jy = player->jy;
13400 if (!player->active)
13404 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13405 player->killed, player->active, player->reanimated);
13408 /* the following code was introduced to prevent an infinite loop when calling
13410 -> CheckTriggeredElementChangeExt()
13411 -> ExecuteCustomElementAction()
13413 -> (infinitely repeating the above sequence of function calls)
13414 which occurs when killing the player while having a CE with the setting
13415 "kill player X when explosion of <player X>"; the solution using a new
13416 field "player->killed" was chosen for backwards compatibility, although
13417 clever use of the fields "player->active" etc. would probably also work */
13419 if (player->killed)
13423 player->killed = TRUE;
13425 // remove accessible field at the player's position
13426 Feld[jx][jy] = EL_EMPTY;
13428 // deactivate shield (else Bang()/Explode() would not work right)
13429 player->shield_normal_time_left = 0;
13430 player->shield_deadly_time_left = 0;
13433 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13434 player->killed, player->active, player->reanimated);
13440 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13441 player->killed, player->active, player->reanimated);
13444 if (player->reanimated) // killed player may have been reanimated
13445 player->killed = player->reanimated = FALSE;
13447 BuryPlayer(player);
13450 static void KillPlayerUnlessEnemyProtected(int x, int y)
13452 if (!PLAYER_ENEMY_PROTECTED(x, y))
13453 KillPlayer(PLAYERINFO(x, y));
13456 static void KillPlayerUnlessExplosionProtected(int x, int y)
13458 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13459 KillPlayer(PLAYERINFO(x, y));
13462 void BuryPlayer(struct PlayerInfo *player)
13464 int jx = player->jx, jy = player->jy;
13466 if (!player->active)
13469 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13470 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13472 RemovePlayer(player);
13474 player->buried = TRUE;
13476 if (game.all_players_gone)
13477 game.GameOver = TRUE;
13480 void RemovePlayer(struct PlayerInfo *player)
13482 int jx = player->jx, jy = player->jy;
13483 int i, found = FALSE;
13485 player->present = FALSE;
13486 player->active = FALSE;
13488 // required for some CE actions (even if the player is not active anymore)
13489 player->MovPos = 0;
13491 if (!ExplodeField[jx][jy])
13492 StorePlayer[jx][jy] = 0;
13494 if (player->is_moving)
13495 TEST_DrawLevelField(player->last_jx, player->last_jy);
13497 for (i = 0; i < MAX_PLAYERS; i++)
13498 if (stored_player[i].active)
13503 game.all_players_gone = TRUE;
13504 game.GameOver = TRUE;
13507 game.exit_x = game.robot_wheel_x = jx;
13508 game.exit_y = game.robot_wheel_y = jy;
13511 void ExitPlayer(struct PlayerInfo *player)
13513 DrawPlayer(player); // needed here only to cleanup last field
13514 RemovePlayer(player);
13516 if (game.players_still_needed > 0)
13517 game.players_still_needed--;
13520 static void setFieldForSnapping(int x, int y, int element, int direction)
13522 struct ElementInfo *ei = &element_info[element];
13523 int direction_bit = MV_DIR_TO_BIT(direction);
13524 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13525 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13526 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13528 Feld[x][y] = EL_ELEMENT_SNAPPING;
13529 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13531 ResetGfxAnimation(x, y);
13533 GfxElement[x][y] = element;
13534 GfxAction[x][y] = action;
13535 GfxDir[x][y] = direction;
13536 GfxFrame[x][y] = -1;
13540 =============================================================================
13541 checkDiagonalPushing()
13542 -----------------------------------------------------------------------------
13543 check if diagonal input device direction results in pushing of object
13544 (by checking if the alternative direction is walkable, diggable, ...)
13545 =============================================================================
13548 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13549 int x, int y, int real_dx, int real_dy)
13551 int jx, jy, dx, dy, xx, yy;
13553 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13556 // diagonal direction: check alternative direction
13561 xx = jx + (dx == 0 ? real_dx : 0);
13562 yy = jy + (dy == 0 ? real_dy : 0);
13564 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13568 =============================================================================
13570 -----------------------------------------------------------------------------
13571 x, y: field next to player (non-diagonal) to try to dig to
13572 real_dx, real_dy: direction as read from input device (can be diagonal)
13573 =============================================================================
13576 static int DigField(struct PlayerInfo *player,
13577 int oldx, int oldy, int x, int y,
13578 int real_dx, int real_dy, int mode)
13580 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13581 boolean player_was_pushing = player->is_pushing;
13582 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13583 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13584 int jx = oldx, jy = oldy;
13585 int dx = x - jx, dy = y - jy;
13586 int nextx = x + dx, nexty = y + dy;
13587 int move_direction = (dx == -1 ? MV_LEFT :
13588 dx == +1 ? MV_RIGHT :
13590 dy == +1 ? MV_DOWN : MV_NONE);
13591 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13592 int dig_side = MV_DIR_OPPOSITE(move_direction);
13593 int old_element = Feld[jx][jy];
13594 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13597 if (is_player) // function can also be called by EL_PENGUIN
13599 if (player->MovPos == 0)
13601 player->is_digging = FALSE;
13602 player->is_collecting = FALSE;
13605 if (player->MovPos == 0) // last pushing move finished
13606 player->is_pushing = FALSE;
13608 if (mode == DF_NO_PUSH) // player just stopped pushing
13610 player->is_switching = FALSE;
13611 player->push_delay = -1;
13613 return MP_NO_ACTION;
13617 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13618 old_element = Back[jx][jy];
13620 // in case of element dropped at player position, check background
13621 else if (Back[jx][jy] != EL_EMPTY &&
13622 game.engine_version >= VERSION_IDENT(2,2,0,0))
13623 old_element = Back[jx][jy];
13625 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13626 return MP_NO_ACTION; // field has no opening in this direction
13628 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13629 return MP_NO_ACTION; // field has no opening in this direction
13631 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13635 Feld[jx][jy] = player->artwork_element;
13636 InitMovingField(jx, jy, MV_DOWN);
13637 Store[jx][jy] = EL_ACID;
13638 ContinueMoving(jx, jy);
13639 BuryPlayer(player);
13641 return MP_DONT_RUN_INTO;
13644 if (player_can_move && DONT_RUN_INTO(element))
13646 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13648 return MP_DONT_RUN_INTO;
13651 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13652 return MP_NO_ACTION;
13654 collect_count = element_info[element].collect_count_initial;
13656 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13657 return MP_NO_ACTION;
13659 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13660 player_can_move = player_can_move_or_snap;
13662 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13663 game.engine_version >= VERSION_IDENT(2,2,0,0))
13665 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13666 player->index_bit, dig_side);
13667 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13668 player->index_bit, dig_side);
13670 if (element == EL_DC_LANDMINE)
13673 if (Feld[x][y] != element) // field changed by snapping
13676 return MP_NO_ACTION;
13679 if (player->gravity && is_player && !player->is_auto_moving &&
13680 canFallDown(player) && move_direction != MV_DOWN &&
13681 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13682 return MP_NO_ACTION; // player cannot walk here due to gravity
13684 if (player_can_move &&
13685 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13687 int sound_element = SND_ELEMENT(element);
13688 int sound_action = ACTION_WALKING;
13690 if (IS_RND_GATE(element))
13692 if (!player->key[RND_GATE_NR(element)])
13693 return MP_NO_ACTION;
13695 else if (IS_RND_GATE_GRAY(element))
13697 if (!player->key[RND_GATE_GRAY_NR(element)])
13698 return MP_NO_ACTION;
13700 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13702 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13703 return MP_NO_ACTION;
13705 else if (element == EL_EXIT_OPEN ||
13706 element == EL_EM_EXIT_OPEN ||
13707 element == EL_EM_EXIT_OPENING ||
13708 element == EL_STEEL_EXIT_OPEN ||
13709 element == EL_EM_STEEL_EXIT_OPEN ||
13710 element == EL_EM_STEEL_EXIT_OPENING ||
13711 element == EL_SP_EXIT_OPEN ||
13712 element == EL_SP_EXIT_OPENING)
13714 sound_action = ACTION_PASSING; // player is passing exit
13716 else if (element == EL_EMPTY)
13718 sound_action = ACTION_MOVING; // nothing to walk on
13721 // play sound from background or player, whatever is available
13722 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13723 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13725 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13727 else if (player_can_move &&
13728 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13730 if (!ACCESS_FROM(element, opposite_direction))
13731 return MP_NO_ACTION; // field not accessible from this direction
13733 if (CAN_MOVE(element)) // only fixed elements can be passed!
13734 return MP_NO_ACTION;
13736 if (IS_EM_GATE(element))
13738 if (!player->key[EM_GATE_NR(element)])
13739 return MP_NO_ACTION;
13741 else if (IS_EM_GATE_GRAY(element))
13743 if (!player->key[EM_GATE_GRAY_NR(element)])
13744 return MP_NO_ACTION;
13746 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13748 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13749 return MP_NO_ACTION;
13751 else if (IS_EMC_GATE(element))
13753 if (!player->key[EMC_GATE_NR(element)])
13754 return MP_NO_ACTION;
13756 else if (IS_EMC_GATE_GRAY(element))
13758 if (!player->key[EMC_GATE_GRAY_NR(element)])
13759 return MP_NO_ACTION;
13761 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13763 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13764 return MP_NO_ACTION;
13766 else if (element == EL_DC_GATE_WHITE ||
13767 element == EL_DC_GATE_WHITE_GRAY ||
13768 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13770 if (player->num_white_keys == 0)
13771 return MP_NO_ACTION;
13773 player->num_white_keys--;
13775 else if (IS_SP_PORT(element))
13777 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13778 element == EL_SP_GRAVITY_PORT_RIGHT ||
13779 element == EL_SP_GRAVITY_PORT_UP ||
13780 element == EL_SP_GRAVITY_PORT_DOWN)
13781 player->gravity = !player->gravity;
13782 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13783 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13784 element == EL_SP_GRAVITY_ON_PORT_UP ||
13785 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13786 player->gravity = TRUE;
13787 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13788 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13789 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13790 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13791 player->gravity = FALSE;
13794 // automatically move to the next field with double speed
13795 player->programmed_action = move_direction;
13797 if (player->move_delay_reset_counter == 0)
13799 player->move_delay_reset_counter = 2; // two double speed steps
13801 DOUBLE_PLAYER_SPEED(player);
13804 PlayLevelSoundAction(x, y, ACTION_PASSING);
13806 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13810 if (mode != DF_SNAP)
13812 GfxElement[x][y] = GFX_ELEMENT(element);
13813 player->is_digging = TRUE;
13816 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13818 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13819 player->index_bit, dig_side);
13821 if (mode == DF_SNAP)
13823 if (level.block_snap_field)
13824 setFieldForSnapping(x, y, element, move_direction);
13826 TestIfElementTouchesCustomElement(x, y); // for empty space
13828 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13829 player->index_bit, dig_side);
13832 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13836 if (is_player && mode != DF_SNAP)
13838 GfxElement[x][y] = element;
13839 player->is_collecting = TRUE;
13842 if (element == EL_SPEED_PILL)
13844 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13846 else if (element == EL_EXTRA_TIME && level.time > 0)
13848 TimeLeft += level.extra_time;
13850 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13852 DisplayGameControlValues();
13854 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
13856 player->shield_normal_time_left += level.shield_normal_time;
13857 if (element == EL_SHIELD_DEADLY)
13858 player->shield_deadly_time_left += level.shield_deadly_time;
13860 else if (element == EL_DYNAMITE ||
13861 element == EL_EM_DYNAMITE ||
13862 element == EL_SP_DISK_RED)
13864 if (player->inventory_size < MAX_INVENTORY_SIZE)
13865 player->inventory_element[player->inventory_size++] = element;
13867 DrawGameDoorValues();
13869 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
13871 player->dynabomb_count++;
13872 player->dynabombs_left++;
13874 else if (element == EL_DYNABOMB_INCREASE_SIZE)
13876 player->dynabomb_size++;
13878 else if (element == EL_DYNABOMB_INCREASE_POWER)
13880 player->dynabomb_xl = TRUE;
13882 else if (IS_KEY(element))
13884 player->key[KEY_NR(element)] = TRUE;
13886 DrawGameDoorValues();
13888 else if (element == EL_DC_KEY_WHITE)
13890 player->num_white_keys++;
13892 // display white keys?
13893 // DrawGameDoorValues();
13895 else if (IS_ENVELOPE(element))
13897 player->show_envelope = element;
13899 else if (element == EL_EMC_LENSES)
13901 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
13903 RedrawAllInvisibleElementsForLenses();
13905 else if (element == EL_EMC_MAGNIFIER)
13907 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
13909 RedrawAllInvisibleElementsForMagnifier();
13911 else if (IS_DROPPABLE(element) ||
13912 IS_THROWABLE(element)) // can be collected and dropped
13916 if (collect_count == 0)
13917 player->inventory_infinite_element = element;
13919 for (i = 0; i < collect_count; i++)
13920 if (player->inventory_size < MAX_INVENTORY_SIZE)
13921 player->inventory_element[player->inventory_size++] = element;
13923 DrawGameDoorValues();
13925 else if (collect_count > 0)
13927 game.gems_still_needed -= collect_count;
13928 if (game.gems_still_needed < 0)
13929 game.gems_still_needed = 0;
13931 game.snapshot.collected_item = TRUE;
13933 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
13935 DisplayGameControlValues();
13938 RaiseScoreElement(element);
13939 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
13942 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
13943 player->index_bit, dig_side);
13945 if (mode == DF_SNAP)
13947 if (level.block_snap_field)
13948 setFieldForSnapping(x, y, element, move_direction);
13950 TestIfElementTouchesCustomElement(x, y); // for empty space
13952 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13953 player->index_bit, dig_side);
13956 else if (player_can_move_or_snap && IS_PUSHABLE(element))
13958 if (mode == DF_SNAP && element != EL_BD_ROCK)
13959 return MP_NO_ACTION;
13961 if (CAN_FALL(element) && dy)
13962 return MP_NO_ACTION;
13964 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
13965 !(element == EL_SPRING && level.use_spring_bug))
13966 return MP_NO_ACTION;
13968 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
13969 ((move_direction & MV_VERTICAL &&
13970 ((element_info[element].move_pattern & MV_LEFT &&
13971 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
13972 (element_info[element].move_pattern & MV_RIGHT &&
13973 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
13974 (move_direction & MV_HORIZONTAL &&
13975 ((element_info[element].move_pattern & MV_UP &&
13976 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
13977 (element_info[element].move_pattern & MV_DOWN &&
13978 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
13979 return MP_NO_ACTION;
13981 // do not push elements already moving away faster than player
13982 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
13983 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
13984 return MP_NO_ACTION;
13986 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
13988 if (player->push_delay_value == -1 || !player_was_pushing)
13989 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13991 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
13993 if (player->push_delay_value == -1)
13994 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
13996 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
13998 if (!player->is_pushing)
13999 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14002 player->is_pushing = TRUE;
14003 player->is_active = TRUE;
14005 if (!(IN_LEV_FIELD(nextx, nexty) &&
14006 (IS_FREE(nextx, nexty) ||
14007 (IS_SB_ELEMENT(element) &&
14008 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14009 (IS_CUSTOM_ELEMENT(element) &&
14010 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14011 return MP_NO_ACTION;
14013 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14014 return MP_NO_ACTION;
14016 if (player->push_delay == -1) // new pushing; restart delay
14017 player->push_delay = 0;
14019 if (player->push_delay < player->push_delay_value &&
14020 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14021 element != EL_SPRING && element != EL_BALLOON)
14023 // make sure that there is no move delay before next try to push
14024 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14025 player->move_delay = 0;
14027 return MP_NO_ACTION;
14030 if (IS_CUSTOM_ELEMENT(element) &&
14031 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14033 if (!DigFieldByCE(nextx, nexty, element))
14034 return MP_NO_ACTION;
14037 if (IS_SB_ELEMENT(element))
14039 boolean sokoban_task_solved = FALSE;
14041 if (element == EL_SOKOBAN_FIELD_FULL)
14043 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14045 IncrementSokobanFieldsNeeded();
14046 IncrementSokobanObjectsNeeded();
14049 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14051 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14053 DecrementSokobanFieldsNeeded();
14054 DecrementSokobanObjectsNeeded();
14056 // sokoban object was pushed from empty field to sokoban field
14057 if (Back[x][y] == EL_EMPTY)
14058 sokoban_task_solved = TRUE;
14061 Feld[x][y] = EL_SOKOBAN_OBJECT;
14063 if (Back[x][y] == Back[nextx][nexty])
14064 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14065 else if (Back[x][y] != 0)
14066 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14069 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14072 if (sokoban_task_solved &&
14073 game.sokoban_fields_still_needed == 0 &&
14074 game.sokoban_objects_still_needed == 0 &&
14075 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14077 game.players_still_needed = 0;
14081 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14085 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14087 InitMovingField(x, y, move_direction);
14088 GfxAction[x][y] = ACTION_PUSHING;
14090 if (mode == DF_SNAP)
14091 ContinueMoving(x, y);
14093 MovPos[x][y] = (dx != 0 ? dx : dy);
14095 Pushed[x][y] = TRUE;
14096 Pushed[nextx][nexty] = TRUE;
14098 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14099 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14101 player->push_delay_value = -1; // get new value later
14103 // check for element change _after_ element has been pushed
14104 if (game.use_change_when_pushing_bug)
14106 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14107 player->index_bit, dig_side);
14108 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14109 player->index_bit, dig_side);
14112 else if (IS_SWITCHABLE(element))
14114 if (PLAYER_SWITCHING(player, x, y))
14116 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14117 player->index_bit, dig_side);
14122 player->is_switching = TRUE;
14123 player->switch_x = x;
14124 player->switch_y = y;
14126 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14128 if (element == EL_ROBOT_WHEEL)
14130 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14132 game.robot_wheel_x = x;
14133 game.robot_wheel_y = y;
14134 game.robot_wheel_active = TRUE;
14136 TEST_DrawLevelField(x, y);
14138 else if (element == EL_SP_TERMINAL)
14142 SCAN_PLAYFIELD(xx, yy)
14144 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14148 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14150 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14152 ResetGfxAnimation(xx, yy);
14153 TEST_DrawLevelField(xx, yy);
14157 else if (IS_BELT_SWITCH(element))
14159 ToggleBeltSwitch(x, y);
14161 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14162 element == EL_SWITCHGATE_SWITCH_DOWN ||
14163 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14164 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14166 ToggleSwitchgateSwitch(x, y);
14168 else if (element == EL_LIGHT_SWITCH ||
14169 element == EL_LIGHT_SWITCH_ACTIVE)
14171 ToggleLightSwitch(x, y);
14173 else if (element == EL_TIMEGATE_SWITCH ||
14174 element == EL_DC_TIMEGATE_SWITCH)
14176 ActivateTimegateSwitch(x, y);
14178 else if (element == EL_BALLOON_SWITCH_LEFT ||
14179 element == EL_BALLOON_SWITCH_RIGHT ||
14180 element == EL_BALLOON_SWITCH_UP ||
14181 element == EL_BALLOON_SWITCH_DOWN ||
14182 element == EL_BALLOON_SWITCH_NONE ||
14183 element == EL_BALLOON_SWITCH_ANY)
14185 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14186 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14187 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14188 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14189 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14192 else if (element == EL_LAMP)
14194 Feld[x][y] = EL_LAMP_ACTIVE;
14195 game.lights_still_needed--;
14197 ResetGfxAnimation(x, y);
14198 TEST_DrawLevelField(x, y);
14200 else if (element == EL_TIME_ORB_FULL)
14202 Feld[x][y] = EL_TIME_ORB_EMPTY;
14204 if (level.time > 0 || level.use_time_orb_bug)
14206 TimeLeft += level.time_orb_time;
14207 game.no_time_limit = FALSE;
14209 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14211 DisplayGameControlValues();
14214 ResetGfxAnimation(x, y);
14215 TEST_DrawLevelField(x, y);
14217 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14218 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14222 game.ball_state = !game.ball_state;
14224 SCAN_PLAYFIELD(xx, yy)
14226 int e = Feld[xx][yy];
14228 if (game.ball_state)
14230 if (e == EL_EMC_MAGIC_BALL)
14231 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14232 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14233 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14237 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14238 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14239 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14240 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14245 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14246 player->index_bit, dig_side);
14248 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14249 player->index_bit, dig_side);
14251 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14252 player->index_bit, dig_side);
14258 if (!PLAYER_SWITCHING(player, x, y))
14260 player->is_switching = TRUE;
14261 player->switch_x = x;
14262 player->switch_y = y;
14264 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14265 player->index_bit, dig_side);
14266 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14267 player->index_bit, dig_side);
14269 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14270 player->index_bit, dig_side);
14271 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14272 player->index_bit, dig_side);
14275 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14276 player->index_bit, dig_side);
14277 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14278 player->index_bit, dig_side);
14280 return MP_NO_ACTION;
14283 player->push_delay = -1;
14285 if (is_player) // function can also be called by EL_PENGUIN
14287 if (Feld[x][y] != element) // really digged/collected something
14289 player->is_collecting = !player->is_digging;
14290 player->is_active = TRUE;
14297 static boolean DigFieldByCE(int x, int y, int digging_element)
14299 int element = Feld[x][y];
14301 if (!IS_FREE(x, y))
14303 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14304 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14307 // no element can dig solid indestructible elements
14308 if (IS_INDESTRUCTIBLE(element) &&
14309 !IS_DIGGABLE(element) &&
14310 !IS_COLLECTIBLE(element))
14313 if (AmoebaNr[x][y] &&
14314 (element == EL_AMOEBA_FULL ||
14315 element == EL_BD_AMOEBA ||
14316 element == EL_AMOEBA_GROWING))
14318 AmoebaCnt[AmoebaNr[x][y]]--;
14319 AmoebaCnt2[AmoebaNr[x][y]]--;
14322 if (IS_MOVING(x, y))
14323 RemoveMovingField(x, y);
14327 TEST_DrawLevelField(x, y);
14330 // if digged element was about to explode, prevent the explosion
14331 ExplodeField[x][y] = EX_TYPE_NONE;
14333 PlayLevelSoundAction(x, y, action);
14336 Store[x][y] = EL_EMPTY;
14338 // this makes it possible to leave the removed element again
14339 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14340 Store[x][y] = element;
14345 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14347 int jx = player->jx, jy = player->jy;
14348 int x = jx + dx, y = jy + dy;
14349 int snap_direction = (dx == -1 ? MV_LEFT :
14350 dx == +1 ? MV_RIGHT :
14352 dy == +1 ? MV_DOWN : MV_NONE);
14353 boolean can_continue_snapping = (level.continuous_snapping &&
14354 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14356 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14359 if (!player->active || !IN_LEV_FIELD(x, y))
14367 if (player->MovPos == 0)
14368 player->is_pushing = FALSE;
14370 player->is_snapping = FALSE;
14372 if (player->MovPos == 0)
14374 player->is_moving = FALSE;
14375 player->is_digging = FALSE;
14376 player->is_collecting = FALSE;
14382 // prevent snapping with already pressed snap key when not allowed
14383 if (player->is_snapping && !can_continue_snapping)
14386 player->MovDir = snap_direction;
14388 if (player->MovPos == 0)
14390 player->is_moving = FALSE;
14391 player->is_digging = FALSE;
14392 player->is_collecting = FALSE;
14395 player->is_dropping = FALSE;
14396 player->is_dropping_pressed = FALSE;
14397 player->drop_pressed_delay = 0;
14399 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14402 player->is_snapping = TRUE;
14403 player->is_active = TRUE;
14405 if (player->MovPos == 0)
14407 player->is_moving = FALSE;
14408 player->is_digging = FALSE;
14409 player->is_collecting = FALSE;
14412 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14413 TEST_DrawLevelField(player->last_jx, player->last_jy);
14415 TEST_DrawLevelField(x, y);
14420 static boolean DropElement(struct PlayerInfo *player)
14422 int old_element, new_element;
14423 int dropx = player->jx, dropy = player->jy;
14424 int drop_direction = player->MovDir;
14425 int drop_side = drop_direction;
14426 int drop_element = get_next_dropped_element(player);
14428 /* do not drop an element on top of another element; when holding drop key
14429 pressed without moving, dropped element must move away before the next
14430 element can be dropped (this is especially important if the next element
14431 is dynamite, which can be placed on background for historical reasons) */
14432 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14435 if (IS_THROWABLE(drop_element))
14437 dropx += GET_DX_FROM_DIR(drop_direction);
14438 dropy += GET_DY_FROM_DIR(drop_direction);
14440 if (!IN_LEV_FIELD(dropx, dropy))
14444 old_element = Feld[dropx][dropy]; // old element at dropping position
14445 new_element = drop_element; // default: no change when dropping
14447 // check if player is active, not moving and ready to drop
14448 if (!player->active || player->MovPos || player->drop_delay > 0)
14451 // check if player has anything that can be dropped
14452 if (new_element == EL_UNDEFINED)
14455 // only set if player has anything that can be dropped
14456 player->is_dropping_pressed = TRUE;
14458 // check if drop key was pressed long enough for EM style dynamite
14459 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14462 // check if anything can be dropped at the current position
14463 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14466 // collected custom elements can only be dropped on empty fields
14467 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14470 if (old_element != EL_EMPTY)
14471 Back[dropx][dropy] = old_element; // store old element on this field
14473 ResetGfxAnimation(dropx, dropy);
14474 ResetRandomAnimationValue(dropx, dropy);
14476 if (player->inventory_size > 0 ||
14477 player->inventory_infinite_element != EL_UNDEFINED)
14479 if (player->inventory_size > 0)
14481 player->inventory_size--;
14483 DrawGameDoorValues();
14485 if (new_element == EL_DYNAMITE)
14486 new_element = EL_DYNAMITE_ACTIVE;
14487 else if (new_element == EL_EM_DYNAMITE)
14488 new_element = EL_EM_DYNAMITE_ACTIVE;
14489 else if (new_element == EL_SP_DISK_RED)
14490 new_element = EL_SP_DISK_RED_ACTIVE;
14493 Feld[dropx][dropy] = new_element;
14495 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14496 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14497 el2img(Feld[dropx][dropy]), 0);
14499 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14501 // needed if previous element just changed to "empty" in the last frame
14502 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14504 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14505 player->index_bit, drop_side);
14506 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14508 player->index_bit, drop_side);
14510 TestIfElementTouchesCustomElement(dropx, dropy);
14512 else // player is dropping a dyna bomb
14514 player->dynabombs_left--;
14516 Feld[dropx][dropy] = new_element;
14518 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14519 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14520 el2img(Feld[dropx][dropy]), 0);
14522 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14525 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14526 InitField_WithBug1(dropx, dropy, FALSE);
14528 new_element = Feld[dropx][dropy]; // element might have changed
14530 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14531 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14533 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14534 MovDir[dropx][dropy] = drop_direction;
14536 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14538 // do not cause impact style collision by dropping elements that can fall
14539 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14542 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14543 player->is_dropping = TRUE;
14545 player->drop_pressed_delay = 0;
14546 player->is_dropping_pressed = FALSE;
14548 player->drop_x = dropx;
14549 player->drop_y = dropy;
14554 // ----------------------------------------------------------------------------
14555 // game sound playing functions
14556 // ----------------------------------------------------------------------------
14558 static int *loop_sound_frame = NULL;
14559 static int *loop_sound_volume = NULL;
14561 void InitPlayLevelSound(void)
14563 int num_sounds = getSoundListSize();
14565 checked_free(loop_sound_frame);
14566 checked_free(loop_sound_volume);
14568 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14569 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14572 static void PlayLevelSound(int x, int y, int nr)
14574 int sx = SCREENX(x), sy = SCREENY(y);
14575 int volume, stereo_position;
14576 int max_distance = 8;
14577 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14579 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14580 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14583 if (!IN_LEV_FIELD(x, y) ||
14584 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14585 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14588 volume = SOUND_MAX_VOLUME;
14590 if (!IN_SCR_FIELD(sx, sy))
14592 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14593 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14595 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14598 stereo_position = (SOUND_MAX_LEFT +
14599 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14600 (SCR_FIELDX + 2 * max_distance));
14602 if (IS_LOOP_SOUND(nr))
14604 /* This assures that quieter loop sounds do not overwrite louder ones,
14605 while restarting sound volume comparison with each new game frame. */
14607 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14610 loop_sound_volume[nr] = volume;
14611 loop_sound_frame[nr] = FrameCounter;
14614 PlaySoundExt(nr, volume, stereo_position, type);
14617 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14619 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14620 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14621 y < LEVELY(BY1) ? LEVELY(BY1) :
14622 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14626 static void PlayLevelSoundAction(int x, int y, int action)
14628 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14631 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14633 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14635 if (sound_effect != SND_UNDEFINED)
14636 PlayLevelSound(x, y, sound_effect);
14639 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14642 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14644 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14645 PlayLevelSound(x, y, sound_effect);
14648 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14650 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14652 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14653 PlayLevelSound(x, y, sound_effect);
14656 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14658 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14660 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14661 StopSound(sound_effect);
14664 static int getLevelMusicNr(void)
14666 if (levelset.music[level_nr] != MUS_UNDEFINED)
14667 return levelset.music[level_nr]; // from config file
14669 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14672 static void FadeLevelSounds(void)
14677 static void FadeLevelMusic(void)
14679 int music_nr = getLevelMusicNr();
14680 char *curr_music = getCurrentlyPlayingMusicFilename();
14681 char *next_music = getMusicInfoEntryFilename(music_nr);
14683 if (!strEqual(curr_music, next_music))
14687 void FadeLevelSoundsAndMusic(void)
14693 static void PlayLevelMusic(void)
14695 int music_nr = getLevelMusicNr();
14696 char *curr_music = getCurrentlyPlayingMusicFilename();
14697 char *next_music = getMusicInfoEntryFilename(music_nr);
14699 if (!strEqual(curr_music, next_music))
14700 PlayMusicLoop(music_nr);
14703 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14705 int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0);
14706 int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
14707 int x = xx - 1 - offset;
14708 int y = yy - 1 - offset;
14713 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14717 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14721 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14725 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14729 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14733 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14737 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14740 case SOUND_android_clone:
14741 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14744 case SOUND_android_move:
14745 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14749 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14753 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14757 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14760 case SOUND_eater_eat:
14761 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14765 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14768 case SOUND_collect:
14769 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14772 case SOUND_diamond:
14773 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14777 // !!! CHECK THIS !!!
14779 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14781 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14785 case SOUND_wonderfall:
14786 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14790 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14794 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14798 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14802 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14806 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14810 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14814 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14818 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14821 case SOUND_exit_open:
14822 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14825 case SOUND_exit_leave:
14826 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14829 case SOUND_dynamite:
14830 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14834 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14838 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14842 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14846 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14850 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
14854 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
14858 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
14863 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
14865 int element = map_element_SP_to_RND(element_sp);
14866 int action = map_action_SP_to_RND(action_sp);
14867 int offset = (setup.sp_show_border_elements ? 0 : 1);
14868 int x = xx - offset;
14869 int y = yy - offset;
14871 PlayLevelSoundElementAction(x, y, element, action);
14874 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
14876 int element = map_element_MM_to_RND(element_mm);
14877 int action = map_action_MM_to_RND(action_mm);
14879 int x = xx - offset;
14880 int y = yy - offset;
14882 if (!IS_MM_ELEMENT(element))
14883 element = EL_MM_DEFAULT;
14885 PlayLevelSoundElementAction(x, y, element, action);
14888 void PlaySound_MM(int sound_mm)
14890 int sound = map_sound_MM_to_RND(sound_mm);
14892 if (sound == SND_UNDEFINED)
14898 void PlaySoundLoop_MM(int sound_mm)
14900 int sound = map_sound_MM_to_RND(sound_mm);
14902 if (sound == SND_UNDEFINED)
14905 PlaySoundLoop(sound);
14908 void StopSound_MM(int sound_mm)
14910 int sound = map_sound_MM_to_RND(sound_mm);
14912 if (sound == SND_UNDEFINED)
14918 void RaiseScore(int value)
14920 game.score += value;
14922 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
14924 DisplayGameControlValues();
14927 void RaiseScoreElement(int element)
14932 case EL_BD_DIAMOND:
14933 case EL_EMERALD_YELLOW:
14934 case EL_EMERALD_RED:
14935 case EL_EMERALD_PURPLE:
14936 case EL_SP_INFOTRON:
14937 RaiseScore(level.score[SC_EMERALD]);
14940 RaiseScore(level.score[SC_DIAMOND]);
14943 RaiseScore(level.score[SC_CRYSTAL]);
14946 RaiseScore(level.score[SC_PEARL]);
14949 case EL_BD_BUTTERFLY:
14950 case EL_SP_ELECTRON:
14951 RaiseScore(level.score[SC_BUG]);
14954 case EL_BD_FIREFLY:
14955 case EL_SP_SNIKSNAK:
14956 RaiseScore(level.score[SC_SPACESHIP]);
14959 case EL_DARK_YAMYAM:
14960 RaiseScore(level.score[SC_YAMYAM]);
14963 RaiseScore(level.score[SC_ROBOT]);
14966 RaiseScore(level.score[SC_PACMAN]);
14969 RaiseScore(level.score[SC_NUT]);
14972 case EL_EM_DYNAMITE:
14973 case EL_SP_DISK_RED:
14974 case EL_DYNABOMB_INCREASE_NUMBER:
14975 case EL_DYNABOMB_INCREASE_SIZE:
14976 case EL_DYNABOMB_INCREASE_POWER:
14977 RaiseScore(level.score[SC_DYNAMITE]);
14979 case EL_SHIELD_NORMAL:
14980 case EL_SHIELD_DEADLY:
14981 RaiseScore(level.score[SC_SHIELD]);
14983 case EL_EXTRA_TIME:
14984 RaiseScore(level.extra_time_score);
14998 case EL_DC_KEY_WHITE:
14999 RaiseScore(level.score[SC_KEY]);
15002 RaiseScore(element_info[element].collect_score);
15007 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15009 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15011 // closing door required in case of envelope style request dialogs
15014 // prevent short reactivation of overlay buttons while closing door
15015 SetOverlayActive(FALSE);
15017 CloseDoor(DOOR_CLOSE_1);
15020 if (network.enabled)
15021 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15025 FadeSkipNextFadeIn();
15027 SetGameStatus(GAME_MODE_MAIN);
15032 else // continue playing the game
15034 if (tape.playing && tape.deactivate_display)
15035 TapeDeactivateDisplayOff(TRUE);
15037 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15039 if (tape.playing && tape.deactivate_display)
15040 TapeDeactivateDisplayOn();
15044 void RequestQuitGame(boolean ask_if_really_quit)
15046 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15047 boolean skip_request = game.all_players_gone || quick_quit;
15049 RequestQuitGameExt(skip_request, quick_quit,
15050 "Do you really want to quit the game?");
15053 void RequestRestartGame(char *message)
15055 game.restart_game_message = NULL;
15057 boolean has_started_game = hasStartedNetworkGame();
15058 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15060 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15062 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15066 SetGameStatus(GAME_MODE_MAIN);
15072 void CheckGameOver(void)
15074 static boolean last_game_over = FALSE;
15075 static int game_over_delay = 0;
15076 int game_over_delay_value = 50;
15077 boolean game_over = checkGameFailed();
15079 // do not handle game over if request dialog is already active
15080 if (game.request_active)
15083 // do not ask to play again if game was never actually played
15084 if (!game.GamePlayed)
15089 last_game_over = FALSE;
15090 game_over_delay = game_over_delay_value;
15095 if (game_over_delay > 0)
15102 if (last_game_over != game_over)
15103 game.restart_game_message = (hasStartedNetworkGame() ?
15104 "Game over! Play it again?" :
15107 last_game_over = game_over;
15110 boolean checkGameSolved(void)
15112 // set for all game engines if level was solved
15113 return game.LevelSolved_GameEnd;
15116 boolean checkGameFailed(void)
15118 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15119 return (game_em.game_over && !game_em.level_solved);
15120 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15121 return (game_sp.game_over && !game_sp.level_solved);
15122 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15123 return (game_mm.game_over && !game_mm.level_solved);
15124 else // GAME_ENGINE_TYPE_RND
15125 return (game.GameOver && !game.LevelSolved);
15128 boolean checkGameEnded(void)
15130 return (checkGameSolved() || checkGameFailed());
15134 // ----------------------------------------------------------------------------
15135 // random generator functions
15136 // ----------------------------------------------------------------------------
15138 unsigned int InitEngineRandom_RND(int seed)
15140 game.num_random_calls = 0;
15142 return InitEngineRandom(seed);
15145 unsigned int RND(int max)
15149 game.num_random_calls++;
15151 return GetEngineRandom(max);
15158 // ----------------------------------------------------------------------------
15159 // game engine snapshot handling functions
15160 // ----------------------------------------------------------------------------
15162 struct EngineSnapshotInfo
15164 // runtime values for custom element collect score
15165 int collect_score[NUM_CUSTOM_ELEMENTS];
15167 // runtime values for group element choice position
15168 int choice_pos[NUM_GROUP_ELEMENTS];
15170 // runtime values for belt position animations
15171 int belt_graphic[4][NUM_BELT_PARTS];
15172 int belt_anim_mode[4][NUM_BELT_PARTS];
15175 static struct EngineSnapshotInfo engine_snapshot_rnd;
15176 static char *snapshot_level_identifier = NULL;
15177 static int snapshot_level_nr = -1;
15179 static void SaveEngineSnapshotValues_RND(void)
15181 static int belt_base_active_element[4] =
15183 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15184 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15185 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15186 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15190 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15192 int element = EL_CUSTOM_START + i;
15194 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15197 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15199 int element = EL_GROUP_START + i;
15201 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15204 for (i = 0; i < 4; i++)
15206 for (j = 0; j < NUM_BELT_PARTS; j++)
15208 int element = belt_base_active_element[i] + j;
15209 int graphic = el2img(element);
15210 int anim_mode = graphic_info[graphic].anim_mode;
15212 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15213 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15218 static void LoadEngineSnapshotValues_RND(void)
15220 unsigned int num_random_calls = game.num_random_calls;
15223 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15225 int element = EL_CUSTOM_START + i;
15227 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15230 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15232 int element = EL_GROUP_START + i;
15234 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15237 for (i = 0; i < 4; i++)
15239 for (j = 0; j < NUM_BELT_PARTS; j++)
15241 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15242 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15244 graphic_info[graphic].anim_mode = anim_mode;
15248 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15250 InitRND(tape.random_seed);
15251 for (i = 0; i < num_random_calls; i++)
15255 if (game.num_random_calls != num_random_calls)
15257 Error(ERR_INFO, "number of random calls out of sync");
15258 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15259 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15260 Error(ERR_EXIT, "this should not happen -- please debug");
15264 void FreeEngineSnapshotSingle(void)
15266 FreeSnapshotSingle();
15268 setString(&snapshot_level_identifier, NULL);
15269 snapshot_level_nr = -1;
15272 void FreeEngineSnapshotList(void)
15274 FreeSnapshotList();
15277 static ListNode *SaveEngineSnapshotBuffers(void)
15279 ListNode *buffers = NULL;
15281 // copy some special values to a structure better suited for the snapshot
15283 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15284 SaveEngineSnapshotValues_RND();
15285 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15286 SaveEngineSnapshotValues_EM();
15287 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15288 SaveEngineSnapshotValues_SP(&buffers);
15289 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15290 SaveEngineSnapshotValues_MM(&buffers);
15292 // save values stored in special snapshot structure
15294 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15295 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15296 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15297 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15298 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15299 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15300 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15301 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15303 // save further RND engine values
15305 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15306 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15307 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15309 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15310 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15311 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15312 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15313 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15315 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15316 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15317 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15319 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15321 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15322 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15324 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15325 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15326 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15327 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15328 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15329 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15330 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15331 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15332 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15333 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15334 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15335 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15336 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15337 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15338 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15339 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15340 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15341 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15343 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15344 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15346 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15347 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15348 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15350 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15351 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15353 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15354 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15355 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15356 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15357 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15359 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15360 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15363 ListNode *node = engine_snapshot_list_rnd;
15366 while (node != NULL)
15368 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15373 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15379 void SaveEngineSnapshotSingle(void)
15381 ListNode *buffers = SaveEngineSnapshotBuffers();
15383 // finally save all snapshot buffers to single snapshot
15384 SaveSnapshotSingle(buffers);
15386 // save level identification information
15387 setString(&snapshot_level_identifier, leveldir_current->identifier);
15388 snapshot_level_nr = level_nr;
15391 boolean CheckSaveEngineSnapshotToList(void)
15393 boolean save_snapshot =
15394 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15395 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15396 game.snapshot.changed_action) ||
15397 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15398 game.snapshot.collected_item));
15400 game.snapshot.changed_action = FALSE;
15401 game.snapshot.collected_item = FALSE;
15402 game.snapshot.save_snapshot = save_snapshot;
15404 return save_snapshot;
15407 void SaveEngineSnapshotToList(void)
15409 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15413 ListNode *buffers = SaveEngineSnapshotBuffers();
15415 // finally save all snapshot buffers to snapshot list
15416 SaveSnapshotToList(buffers);
15419 void SaveEngineSnapshotToListInitial(void)
15421 FreeEngineSnapshotList();
15423 SaveEngineSnapshotToList();
15426 static void LoadEngineSnapshotValues(void)
15428 // restore special values from snapshot structure
15430 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15431 LoadEngineSnapshotValues_RND();
15432 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15433 LoadEngineSnapshotValues_EM();
15434 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15435 LoadEngineSnapshotValues_SP();
15436 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15437 LoadEngineSnapshotValues_MM();
15440 void LoadEngineSnapshotSingle(void)
15442 LoadSnapshotSingle();
15444 LoadEngineSnapshotValues();
15447 static void LoadEngineSnapshot_Undo(int steps)
15449 LoadSnapshotFromList_Older(steps);
15451 LoadEngineSnapshotValues();
15454 static void LoadEngineSnapshot_Redo(int steps)
15456 LoadSnapshotFromList_Newer(steps);
15458 LoadEngineSnapshotValues();
15461 boolean CheckEngineSnapshotSingle(void)
15463 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15464 snapshot_level_nr == level_nr);
15467 boolean CheckEngineSnapshotList(void)
15469 return CheckSnapshotList();
15473 // ---------- new game button stuff -------------------------------------------
15480 boolean *setup_value;
15481 boolean allowed_on_tape;
15482 boolean is_touch_button;
15484 } gamebutton_info[NUM_GAME_BUTTONS] =
15487 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15488 GAME_CTRL_ID_STOP, NULL,
15489 TRUE, FALSE, "stop game"
15492 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15493 GAME_CTRL_ID_PAUSE, NULL,
15494 TRUE, FALSE, "pause game"
15497 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15498 GAME_CTRL_ID_PLAY, NULL,
15499 TRUE, FALSE, "play game"
15502 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15503 GAME_CTRL_ID_UNDO, NULL,
15504 TRUE, FALSE, "undo step"
15507 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15508 GAME_CTRL_ID_REDO, NULL,
15509 TRUE, FALSE, "redo step"
15512 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15513 GAME_CTRL_ID_SAVE, NULL,
15514 TRUE, FALSE, "save game"
15517 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15518 GAME_CTRL_ID_PAUSE2, NULL,
15519 TRUE, FALSE, "pause game"
15522 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15523 GAME_CTRL_ID_LOAD, NULL,
15524 TRUE, FALSE, "load game"
15527 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15528 GAME_CTRL_ID_PANEL_STOP, NULL,
15529 FALSE, FALSE, "stop game"
15532 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15533 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15534 FALSE, FALSE, "pause game"
15537 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15538 GAME_CTRL_ID_PANEL_PLAY, NULL,
15539 FALSE, FALSE, "play game"
15542 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15543 GAME_CTRL_ID_TOUCH_STOP, NULL,
15544 FALSE, TRUE, "stop game"
15547 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15548 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15549 FALSE, TRUE, "pause game"
15552 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15553 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15554 TRUE, FALSE, "background music on/off"
15557 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15558 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15559 TRUE, FALSE, "sound loops on/off"
15562 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15563 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15564 TRUE, FALSE, "normal sounds on/off"
15567 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15568 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15569 FALSE, FALSE, "background music on/off"
15572 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15573 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15574 FALSE, FALSE, "sound loops on/off"
15577 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15578 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15579 FALSE, FALSE, "normal sounds on/off"
15583 void CreateGameButtons(void)
15587 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15589 int graphic = gamebutton_info[i].graphic;
15590 struct GraphicInfo *gfx = &graphic_info[graphic];
15591 struct XY *pos = gamebutton_info[i].pos;
15592 struct GadgetInfo *gi;
15595 unsigned int event_mask;
15596 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15597 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15598 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15599 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15600 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15601 int gd_x = gfx->src_x;
15602 int gd_y = gfx->src_y;
15603 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15604 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15605 int gd_xa = gfx->src_x + gfx->active_xoffset;
15606 int gd_ya = gfx->src_y + gfx->active_yoffset;
15607 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15608 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15609 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15610 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15613 if (gfx->bitmap == NULL)
15615 game_gadget[id] = NULL;
15620 if (id == GAME_CTRL_ID_STOP ||
15621 id == GAME_CTRL_ID_PANEL_STOP ||
15622 id == GAME_CTRL_ID_TOUCH_STOP ||
15623 id == GAME_CTRL_ID_PLAY ||
15624 id == GAME_CTRL_ID_PANEL_PLAY ||
15625 id == GAME_CTRL_ID_SAVE ||
15626 id == GAME_CTRL_ID_LOAD)
15628 button_type = GD_TYPE_NORMAL_BUTTON;
15630 event_mask = GD_EVENT_RELEASED;
15632 else if (id == GAME_CTRL_ID_UNDO ||
15633 id == GAME_CTRL_ID_REDO)
15635 button_type = GD_TYPE_NORMAL_BUTTON;
15637 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15641 button_type = GD_TYPE_CHECK_BUTTON;
15642 checked = (gamebutton_info[i].setup_value != NULL ?
15643 *gamebutton_info[i].setup_value : FALSE);
15644 event_mask = GD_EVENT_PRESSED;
15647 gi = CreateGadget(GDI_CUSTOM_ID, id,
15648 GDI_IMAGE_ID, graphic,
15649 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15652 GDI_WIDTH, gfx->width,
15653 GDI_HEIGHT, gfx->height,
15654 GDI_TYPE, button_type,
15655 GDI_STATE, GD_BUTTON_UNPRESSED,
15656 GDI_CHECKED, checked,
15657 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15658 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15659 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15660 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15661 GDI_DIRECT_DRAW, FALSE,
15662 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15663 GDI_EVENT_MASK, event_mask,
15664 GDI_CALLBACK_ACTION, HandleGameButtons,
15668 Error(ERR_EXIT, "cannot create gadget");
15670 game_gadget[id] = gi;
15674 void FreeGameButtons(void)
15678 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15679 FreeGadget(game_gadget[i]);
15682 static void UnmapGameButtonsAtSamePosition(int id)
15686 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15688 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15689 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15690 UnmapGadget(game_gadget[i]);
15693 static void UnmapGameButtonsAtSamePosition_All(void)
15695 if (setup.show_snapshot_buttons)
15697 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15698 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15699 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15703 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15704 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15705 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15707 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15708 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15709 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15713 static void MapGameButtonsAtSamePosition(int id)
15717 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15719 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15720 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15721 MapGadget(game_gadget[i]);
15723 UnmapGameButtonsAtSamePosition_All();
15726 void MapUndoRedoButtons(void)
15728 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15729 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15731 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15732 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15735 void UnmapUndoRedoButtons(void)
15737 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15738 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15740 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15741 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15744 void ModifyPauseButtons(void)
15748 GAME_CTRL_ID_PAUSE,
15749 GAME_CTRL_ID_PAUSE2,
15750 GAME_CTRL_ID_PANEL_PAUSE,
15751 GAME_CTRL_ID_TOUCH_PAUSE,
15756 for (i = 0; ids[i] > -1; i++)
15757 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15760 static void MapGameButtonsExt(boolean on_tape)
15764 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15765 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15766 i != GAME_CTRL_ID_UNDO &&
15767 i != GAME_CTRL_ID_REDO)
15768 MapGadget(game_gadget[i]);
15770 UnmapGameButtonsAtSamePosition_All();
15772 RedrawGameButtons();
15775 static void UnmapGameButtonsExt(boolean on_tape)
15779 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15780 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15781 UnmapGadget(game_gadget[i]);
15784 static void RedrawGameButtonsExt(boolean on_tape)
15788 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15789 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15790 RedrawGadget(game_gadget[i]);
15793 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15798 gi->checked = state;
15801 static void RedrawSoundButtonGadget(int id)
15803 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15804 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15805 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15806 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15807 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15808 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15811 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15812 RedrawGadget(game_gadget[id2]);
15815 void MapGameButtons(void)
15817 MapGameButtonsExt(FALSE);
15820 void UnmapGameButtons(void)
15822 UnmapGameButtonsExt(FALSE);
15825 void RedrawGameButtons(void)
15827 RedrawGameButtonsExt(FALSE);
15830 void MapGameButtonsOnTape(void)
15832 MapGameButtonsExt(TRUE);
15835 void UnmapGameButtonsOnTape(void)
15837 UnmapGameButtonsExt(TRUE);
15840 void RedrawGameButtonsOnTape(void)
15842 RedrawGameButtonsExt(TRUE);
15845 static void GameUndoRedoExt(void)
15847 ClearPlayerAction();
15849 tape.pausing = TRUE;
15852 UpdateAndDisplayGameControlValues();
15854 DrawCompleteVideoDisplay();
15855 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
15856 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
15857 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
15862 static void GameUndo(int steps)
15864 if (!CheckEngineSnapshotList())
15867 LoadEngineSnapshot_Undo(steps);
15872 static void GameRedo(int steps)
15874 if (!CheckEngineSnapshotList())
15877 LoadEngineSnapshot_Redo(steps);
15882 static void HandleGameButtonsExt(int id, int button)
15884 static boolean game_undo_executed = FALSE;
15885 int steps = BUTTON_STEPSIZE(button);
15886 boolean handle_game_buttons =
15887 (game_status == GAME_MODE_PLAYING ||
15888 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
15890 if (!handle_game_buttons)
15895 case GAME_CTRL_ID_STOP:
15896 case GAME_CTRL_ID_PANEL_STOP:
15897 case GAME_CTRL_ID_TOUCH_STOP:
15898 if (game_status == GAME_MODE_MAIN)
15904 RequestQuitGame(TRUE);
15908 case GAME_CTRL_ID_PAUSE:
15909 case GAME_CTRL_ID_PAUSE2:
15910 case GAME_CTRL_ID_PANEL_PAUSE:
15911 case GAME_CTRL_ID_TOUCH_PAUSE:
15912 if (network.enabled && game_status == GAME_MODE_PLAYING)
15915 SendToServer_ContinuePlaying();
15917 SendToServer_PausePlaying();
15920 TapeTogglePause(TAPE_TOGGLE_MANUAL);
15922 game_undo_executed = FALSE;
15926 case GAME_CTRL_ID_PLAY:
15927 case GAME_CTRL_ID_PANEL_PLAY:
15928 if (game_status == GAME_MODE_MAIN)
15930 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15932 else if (tape.pausing)
15934 if (network.enabled)
15935 SendToServer_ContinuePlaying();
15937 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
15941 case GAME_CTRL_ID_UNDO:
15942 // Important: When using "save snapshot when collecting an item" mode,
15943 // load last (current) snapshot for first "undo" after pressing "pause"
15944 // (else the last-but-one snapshot would be loaded, because the snapshot
15945 // pointer already points to the last snapshot when pressing "pause",
15946 // which is fine for "every step/move" mode, but not for "every collect")
15947 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15948 !game_undo_executed)
15951 game_undo_executed = TRUE;
15956 case GAME_CTRL_ID_REDO:
15960 case GAME_CTRL_ID_SAVE:
15964 case GAME_CTRL_ID_LOAD:
15968 case SOUND_CTRL_ID_MUSIC:
15969 case SOUND_CTRL_ID_PANEL_MUSIC:
15970 if (setup.sound_music)
15972 setup.sound_music = FALSE;
15976 else if (audio.music_available)
15978 setup.sound = setup.sound_music = TRUE;
15980 SetAudioMode(setup.sound);
15982 if (game_status == GAME_MODE_PLAYING)
15986 RedrawSoundButtonGadget(id);
15990 case SOUND_CTRL_ID_LOOPS:
15991 case SOUND_CTRL_ID_PANEL_LOOPS:
15992 if (setup.sound_loops)
15993 setup.sound_loops = FALSE;
15994 else if (audio.loops_available)
15996 setup.sound = setup.sound_loops = TRUE;
15998 SetAudioMode(setup.sound);
16001 RedrawSoundButtonGadget(id);
16005 case SOUND_CTRL_ID_SIMPLE:
16006 case SOUND_CTRL_ID_PANEL_SIMPLE:
16007 if (setup.sound_simple)
16008 setup.sound_simple = FALSE;
16009 else if (audio.sound_available)
16011 setup.sound = setup.sound_simple = TRUE;
16013 SetAudioMode(setup.sound);
16016 RedrawSoundButtonGadget(id);
16025 static void HandleGameButtons(struct GadgetInfo *gi)
16027 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16030 void HandleSoundButtonKeys(Key key)
16032 if (key == setup.shortcut.sound_simple)
16033 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16034 else if (key == setup.shortcut.sound_loops)
16035 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16036 else if (key == setup.shortcut.sound_music)
16037 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);