1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_INVENTORY_COUNT 2
93 #define GAME_PANEL_INVENTORY_FIRST_1 3
94 #define GAME_PANEL_INVENTORY_FIRST_2 4
95 #define GAME_PANEL_INVENTORY_FIRST_3 5
96 #define GAME_PANEL_INVENTORY_FIRST_4 6
97 #define GAME_PANEL_INVENTORY_FIRST_5 7
98 #define GAME_PANEL_INVENTORY_FIRST_6 8
99 #define GAME_PANEL_INVENTORY_FIRST_7 9
100 #define GAME_PANEL_INVENTORY_FIRST_8 10
101 #define GAME_PANEL_INVENTORY_LAST_1 11
102 #define GAME_PANEL_INVENTORY_LAST_2 12
103 #define GAME_PANEL_INVENTORY_LAST_3 13
104 #define GAME_PANEL_INVENTORY_LAST_4 14
105 #define GAME_PANEL_INVENTORY_LAST_5 15
106 #define GAME_PANEL_INVENTORY_LAST_6 16
107 #define GAME_PANEL_INVENTORY_LAST_7 17
108 #define GAME_PANEL_INVENTORY_LAST_8 18
109 #define GAME_PANEL_KEY_1 19
110 #define GAME_PANEL_KEY_2 20
111 #define GAME_PANEL_KEY_3 21
112 #define GAME_PANEL_KEY_4 22
113 #define GAME_PANEL_KEY_5 23
114 #define GAME_PANEL_KEY_6 24
115 #define GAME_PANEL_KEY_7 25
116 #define GAME_PANEL_KEY_8 26
117 #define GAME_PANEL_KEY_WHITE 27
118 #define GAME_PANEL_KEY_WHITE_COUNT 28
119 #define GAME_PANEL_SCORE 29
120 #define GAME_PANEL_HIGHSCORE 30
121 #define GAME_PANEL_TIME 31
122 #define GAME_PANEL_TIME_HH 32
123 #define GAME_PANEL_TIME_MM 33
124 #define GAME_PANEL_TIME_SS 34
125 #define GAME_PANEL_TIME_ANIM 35
126 #define GAME_PANEL_HEALTH 36
127 #define GAME_PANEL_HEALTH_ANIM 37
128 #define GAME_PANEL_FRAME 38
129 #define GAME_PANEL_SHIELD_NORMAL 39
130 #define GAME_PANEL_SHIELD_NORMAL_TIME 40
131 #define GAME_PANEL_SHIELD_DEADLY 41
132 #define GAME_PANEL_SHIELD_DEADLY_TIME 42
133 #define GAME_PANEL_EXIT 43
134 #define GAME_PANEL_EMC_MAGIC_BALL 44
135 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 45
136 #define GAME_PANEL_LIGHT_SWITCH 46
137 #define GAME_PANEL_LIGHT_SWITCH_TIME 47
138 #define GAME_PANEL_TIMEGATE_SWITCH 48
139 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 49
140 #define GAME_PANEL_SWITCHGATE_SWITCH 50
141 #define GAME_PANEL_EMC_LENSES 51
142 #define GAME_PANEL_EMC_LENSES_TIME 52
143 #define GAME_PANEL_EMC_MAGNIFIER 53
144 #define GAME_PANEL_EMC_MAGNIFIER_TIME 54
145 #define GAME_PANEL_BALLOON_SWITCH 55
146 #define GAME_PANEL_DYNABOMB_NUMBER 56
147 #define GAME_PANEL_DYNABOMB_SIZE 57
148 #define GAME_PANEL_DYNABOMB_POWER 58
149 #define GAME_PANEL_PENGUINS 59
150 #define GAME_PANEL_SOKOBAN_OBJECTS 60
151 #define GAME_PANEL_SOKOBAN_FIELDS 61
152 #define GAME_PANEL_ROBOT_WHEEL 62
153 #define GAME_PANEL_CONVEYOR_BELT_1 63
154 #define GAME_PANEL_CONVEYOR_BELT_2 64
155 #define GAME_PANEL_CONVEYOR_BELT_3 65
156 #define GAME_PANEL_CONVEYOR_BELT_4 66
157 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 67
158 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 68
159 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 69
160 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 70
161 #define GAME_PANEL_MAGIC_WALL 71
162 #define GAME_PANEL_MAGIC_WALL_TIME 72
163 #define GAME_PANEL_GRAVITY_STATE 73
164 #define GAME_PANEL_GRAPHIC_1 74
165 #define GAME_PANEL_GRAPHIC_2 75
166 #define GAME_PANEL_GRAPHIC_3 76
167 #define GAME_PANEL_GRAPHIC_4 77
168 #define GAME_PANEL_GRAPHIC_5 78
169 #define GAME_PANEL_GRAPHIC_6 79
170 #define GAME_PANEL_GRAPHIC_7 80
171 #define GAME_PANEL_GRAPHIC_8 81
172 #define GAME_PANEL_ELEMENT_1 82
173 #define GAME_PANEL_ELEMENT_2 83
174 #define GAME_PANEL_ELEMENT_3 84
175 #define GAME_PANEL_ELEMENT_4 85
176 #define GAME_PANEL_ELEMENT_5 86
177 #define GAME_PANEL_ELEMENT_6 87
178 #define GAME_PANEL_ELEMENT_7 88
179 #define GAME_PANEL_ELEMENT_8 89
180 #define GAME_PANEL_ELEMENT_COUNT_1 90
181 #define GAME_PANEL_ELEMENT_COUNT_2 91
182 #define GAME_PANEL_ELEMENT_COUNT_3 92
183 #define GAME_PANEL_ELEMENT_COUNT_4 93
184 #define GAME_PANEL_ELEMENT_COUNT_5 94
185 #define GAME_PANEL_ELEMENT_COUNT_6 95
186 #define GAME_PANEL_ELEMENT_COUNT_7 96
187 #define GAME_PANEL_ELEMENT_COUNT_8 97
188 #define GAME_PANEL_CE_SCORE_1 98
189 #define GAME_PANEL_CE_SCORE_2 99
190 #define GAME_PANEL_CE_SCORE_3 100
191 #define GAME_PANEL_CE_SCORE_4 101
192 #define GAME_PANEL_CE_SCORE_5 102
193 #define GAME_PANEL_CE_SCORE_6 103
194 #define GAME_PANEL_CE_SCORE_7 104
195 #define GAME_PANEL_CE_SCORE_8 105
196 #define GAME_PANEL_CE_SCORE_1_ELEMENT 106
197 #define GAME_PANEL_CE_SCORE_2_ELEMENT 107
198 #define GAME_PANEL_CE_SCORE_3_ELEMENT 108
199 #define GAME_PANEL_CE_SCORE_4_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_5_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_6_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_7_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_8_ELEMENT 113
204 #define GAME_PANEL_PLAYER_NAME 114
205 #define GAME_PANEL_LEVEL_NAME 115
206 #define GAME_PANEL_LEVEL_AUTHOR 116
208 #define NUM_GAME_PANEL_CONTROLS 117
210 struct GamePanelOrderInfo
216 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
218 struct GamePanelControlInfo
222 struct TextPosInfo *pos;
225 int graphic, graphic_active;
227 int value, last_value;
228 int frame, last_frame;
233 static struct GamePanelControlInfo game_panel_controls[] =
236 GAME_PANEL_LEVEL_NUMBER,
237 &game.panel.level_number,
246 GAME_PANEL_INVENTORY_COUNT,
247 &game.panel.inventory_count,
251 GAME_PANEL_INVENTORY_FIRST_1,
252 &game.panel.inventory_first[0],
256 GAME_PANEL_INVENTORY_FIRST_2,
257 &game.panel.inventory_first[1],
261 GAME_PANEL_INVENTORY_FIRST_3,
262 &game.panel.inventory_first[2],
266 GAME_PANEL_INVENTORY_FIRST_4,
267 &game.panel.inventory_first[3],
271 GAME_PANEL_INVENTORY_FIRST_5,
272 &game.panel.inventory_first[4],
276 GAME_PANEL_INVENTORY_FIRST_6,
277 &game.panel.inventory_first[5],
281 GAME_PANEL_INVENTORY_FIRST_7,
282 &game.panel.inventory_first[6],
286 GAME_PANEL_INVENTORY_FIRST_8,
287 &game.panel.inventory_first[7],
291 GAME_PANEL_INVENTORY_LAST_1,
292 &game.panel.inventory_last[0],
296 GAME_PANEL_INVENTORY_LAST_2,
297 &game.panel.inventory_last[1],
301 GAME_PANEL_INVENTORY_LAST_3,
302 &game.panel.inventory_last[2],
306 GAME_PANEL_INVENTORY_LAST_4,
307 &game.panel.inventory_last[3],
311 GAME_PANEL_INVENTORY_LAST_5,
312 &game.panel.inventory_last[4],
316 GAME_PANEL_INVENTORY_LAST_6,
317 &game.panel.inventory_last[5],
321 GAME_PANEL_INVENTORY_LAST_7,
322 &game.panel.inventory_last[6],
326 GAME_PANEL_INVENTORY_LAST_8,
327 &game.panel.inventory_last[7],
371 GAME_PANEL_KEY_WHITE,
372 &game.panel.key_white,
376 GAME_PANEL_KEY_WHITE_COUNT,
377 &game.panel.key_white_count,
386 GAME_PANEL_HIGHSCORE,
387 &game.panel.highscore,
411 GAME_PANEL_TIME_ANIM,
412 &game.panel.time_anim,
415 IMG_GFX_GAME_PANEL_TIME_ANIM,
416 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
424 GAME_PANEL_HEALTH_ANIM,
425 &game.panel.health_anim,
428 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
429 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
437 GAME_PANEL_SHIELD_NORMAL,
438 &game.panel.shield_normal,
442 GAME_PANEL_SHIELD_NORMAL_TIME,
443 &game.panel.shield_normal_time,
447 GAME_PANEL_SHIELD_DEADLY,
448 &game.panel.shield_deadly,
452 GAME_PANEL_SHIELD_DEADLY_TIME,
453 &game.panel.shield_deadly_time,
462 GAME_PANEL_EMC_MAGIC_BALL,
463 &game.panel.emc_magic_ball,
467 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
468 &game.panel.emc_magic_ball_switch,
472 GAME_PANEL_LIGHT_SWITCH,
473 &game.panel.light_switch,
477 GAME_PANEL_LIGHT_SWITCH_TIME,
478 &game.panel.light_switch_time,
482 GAME_PANEL_TIMEGATE_SWITCH,
483 &game.panel.timegate_switch,
487 GAME_PANEL_TIMEGATE_SWITCH_TIME,
488 &game.panel.timegate_switch_time,
492 GAME_PANEL_SWITCHGATE_SWITCH,
493 &game.panel.switchgate_switch,
497 GAME_PANEL_EMC_LENSES,
498 &game.panel.emc_lenses,
502 GAME_PANEL_EMC_LENSES_TIME,
503 &game.panel.emc_lenses_time,
507 GAME_PANEL_EMC_MAGNIFIER,
508 &game.panel.emc_magnifier,
512 GAME_PANEL_EMC_MAGNIFIER_TIME,
513 &game.panel.emc_magnifier_time,
517 GAME_PANEL_BALLOON_SWITCH,
518 &game.panel.balloon_switch,
522 GAME_PANEL_DYNABOMB_NUMBER,
523 &game.panel.dynabomb_number,
527 GAME_PANEL_DYNABOMB_SIZE,
528 &game.panel.dynabomb_size,
532 GAME_PANEL_DYNABOMB_POWER,
533 &game.panel.dynabomb_power,
538 &game.panel.penguins,
542 GAME_PANEL_SOKOBAN_OBJECTS,
543 &game.panel.sokoban_objects,
547 GAME_PANEL_SOKOBAN_FIELDS,
548 &game.panel.sokoban_fields,
552 GAME_PANEL_ROBOT_WHEEL,
553 &game.panel.robot_wheel,
557 GAME_PANEL_CONVEYOR_BELT_1,
558 &game.panel.conveyor_belt[0],
562 GAME_PANEL_CONVEYOR_BELT_2,
563 &game.panel.conveyor_belt[1],
567 GAME_PANEL_CONVEYOR_BELT_3,
568 &game.panel.conveyor_belt[2],
572 GAME_PANEL_CONVEYOR_BELT_4,
573 &game.panel.conveyor_belt[3],
577 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
578 &game.panel.conveyor_belt_switch[0],
582 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
583 &game.panel.conveyor_belt_switch[1],
587 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
588 &game.panel.conveyor_belt_switch[2],
592 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
593 &game.panel.conveyor_belt_switch[3],
597 GAME_PANEL_MAGIC_WALL,
598 &game.panel.magic_wall,
602 GAME_PANEL_MAGIC_WALL_TIME,
603 &game.panel.magic_wall_time,
607 GAME_PANEL_GRAVITY_STATE,
608 &game.panel.gravity_state,
612 GAME_PANEL_GRAPHIC_1,
613 &game.panel.graphic[0],
617 GAME_PANEL_GRAPHIC_2,
618 &game.panel.graphic[1],
622 GAME_PANEL_GRAPHIC_3,
623 &game.panel.graphic[2],
627 GAME_PANEL_GRAPHIC_4,
628 &game.panel.graphic[3],
632 GAME_PANEL_GRAPHIC_5,
633 &game.panel.graphic[4],
637 GAME_PANEL_GRAPHIC_6,
638 &game.panel.graphic[5],
642 GAME_PANEL_GRAPHIC_7,
643 &game.panel.graphic[6],
647 GAME_PANEL_GRAPHIC_8,
648 &game.panel.graphic[7],
652 GAME_PANEL_ELEMENT_1,
653 &game.panel.element[0],
657 GAME_PANEL_ELEMENT_2,
658 &game.panel.element[1],
662 GAME_PANEL_ELEMENT_3,
663 &game.panel.element[2],
667 GAME_PANEL_ELEMENT_4,
668 &game.panel.element[3],
672 GAME_PANEL_ELEMENT_5,
673 &game.panel.element[4],
677 GAME_PANEL_ELEMENT_6,
678 &game.panel.element[5],
682 GAME_PANEL_ELEMENT_7,
683 &game.panel.element[6],
687 GAME_PANEL_ELEMENT_8,
688 &game.panel.element[7],
692 GAME_PANEL_ELEMENT_COUNT_1,
693 &game.panel.element_count[0],
697 GAME_PANEL_ELEMENT_COUNT_2,
698 &game.panel.element_count[1],
702 GAME_PANEL_ELEMENT_COUNT_3,
703 &game.panel.element_count[2],
707 GAME_PANEL_ELEMENT_COUNT_4,
708 &game.panel.element_count[3],
712 GAME_PANEL_ELEMENT_COUNT_5,
713 &game.panel.element_count[4],
717 GAME_PANEL_ELEMENT_COUNT_6,
718 &game.panel.element_count[5],
722 GAME_PANEL_ELEMENT_COUNT_7,
723 &game.panel.element_count[6],
727 GAME_PANEL_ELEMENT_COUNT_8,
728 &game.panel.element_count[7],
732 GAME_PANEL_CE_SCORE_1,
733 &game.panel.ce_score[0],
737 GAME_PANEL_CE_SCORE_2,
738 &game.panel.ce_score[1],
742 GAME_PANEL_CE_SCORE_3,
743 &game.panel.ce_score[2],
747 GAME_PANEL_CE_SCORE_4,
748 &game.panel.ce_score[3],
752 GAME_PANEL_CE_SCORE_5,
753 &game.panel.ce_score[4],
757 GAME_PANEL_CE_SCORE_6,
758 &game.panel.ce_score[5],
762 GAME_PANEL_CE_SCORE_7,
763 &game.panel.ce_score[6],
767 GAME_PANEL_CE_SCORE_8,
768 &game.panel.ce_score[7],
772 GAME_PANEL_CE_SCORE_1_ELEMENT,
773 &game.panel.ce_score_element[0],
777 GAME_PANEL_CE_SCORE_2_ELEMENT,
778 &game.panel.ce_score_element[1],
782 GAME_PANEL_CE_SCORE_3_ELEMENT,
783 &game.panel.ce_score_element[2],
787 GAME_PANEL_CE_SCORE_4_ELEMENT,
788 &game.panel.ce_score_element[3],
792 GAME_PANEL_CE_SCORE_5_ELEMENT,
793 &game.panel.ce_score_element[4],
797 GAME_PANEL_CE_SCORE_6_ELEMENT,
798 &game.panel.ce_score_element[5],
802 GAME_PANEL_CE_SCORE_7_ELEMENT,
803 &game.panel.ce_score_element[6],
807 GAME_PANEL_CE_SCORE_8_ELEMENT,
808 &game.panel.ce_score_element[7],
812 GAME_PANEL_PLAYER_NAME,
813 &game.panel.player_name,
817 GAME_PANEL_LEVEL_NAME,
818 &game.panel.level_name,
822 GAME_PANEL_LEVEL_AUTHOR,
823 &game.panel.level_author,
834 // values for delayed check of falling and moving elements and for collision
835 #define CHECK_DELAY_MOVING 3
836 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
837 #define CHECK_DELAY_COLLISION 2
838 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
840 // values for initial player move delay (initial delay counter value)
841 #define INITIAL_MOVE_DELAY_OFF -1
842 #define INITIAL_MOVE_DELAY_ON 0
844 // values for player movement speed (which is in fact a delay value)
845 #define MOVE_DELAY_MIN_SPEED 32
846 #define MOVE_DELAY_NORMAL_SPEED 8
847 #define MOVE_DELAY_HIGH_SPEED 4
848 #define MOVE_DELAY_MAX_SPEED 1
850 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
851 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
853 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
854 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
856 // values for scroll positions
857 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
858 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
860 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
861 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
864 // values for other actions
865 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
866 #define MOVE_STEPSIZE_MIN (1)
867 #define MOVE_STEPSIZE_MAX (TILEX)
869 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
870 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
872 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
874 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
875 RND(element_info[e].push_delay_random))
876 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
877 RND(element_info[e].drop_delay_random))
878 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
879 RND(element_info[e].move_delay_random))
880 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
881 (element_info[e].move_delay_random))
882 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
883 RND(element_info[e].ce_value_random_initial))
884 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
885 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
886 RND((c)->delay_random * (c)->delay_frames))
887 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
888 RND((c)->delay_random))
891 #define GET_VALID_RUNTIME_ELEMENT(e) \
892 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
894 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
895 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
896 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
897 (be) + (e) - EL_SELF)
899 #define GET_PLAYER_FROM_BITS(p) \
900 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
902 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
903 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
904 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
905 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
906 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
907 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
908 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
909 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
910 RESOLVED_REFERENCE_ELEMENT(be, e) : \
913 #define CAN_GROW_INTO(e) \
914 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
916 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
917 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
922 (CAN_MOVE_INTO_ACID(e) && \
923 Feld[x][y] == EL_ACID) || \
926 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
927 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
928 (CAN_MOVE_INTO_ACID(e) && \
929 Feld[x][y] == EL_ACID) || \
932 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
933 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
935 (CAN_MOVE_INTO_ACID(e) && \
936 Feld[x][y] == EL_ACID) || \
937 (DONT_COLLIDE_WITH(e) && \
939 !PLAYER_ENEMY_PROTECTED(x, y))))
941 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
942 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
944 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
945 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
947 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
948 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Feld[x][y] == EL_EMC_PLANT)
950 #define ANDROID_CAN_CLONE_FIELD(x, y) \
951 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Feld[x][y]) || \
952 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
954 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
955 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
957 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
958 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Feld[x][y] == EL_DIAMOND)
960 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
961 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Feld[x][y]))
963 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Feld[x][y]))
966 #define PIG_CAN_ENTER_FIELD(e, x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Feld[x][y]))
969 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Feld[x][y] == EL_EXIT_OPEN || \
971 Feld[x][y] == EL_EM_EXIT_OPEN || \
972 Feld[x][y] == EL_STEEL_EXIT_OPEN || \
973 Feld[x][y] == EL_EM_STEEL_EXIT_OPEN || \
974 IS_FOOD_PENGUIN(Feld[x][y])))
975 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
976 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
978 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
979 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
981 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
982 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
984 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
985 (IN_LEV_FIELD(x, y) && (Feld[x][y] == EL_EMC_SPRING_BUMPER || \
986 Feld[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
988 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
990 #define CE_ENTER_FIELD_COND(e, x, y) \
991 (!IS_PLAYER(x, y) && \
992 IS_EQUAL_OR_IN_GROUP(Feld[x][y], MOVE_ENTER_EL(e)))
994 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
995 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
997 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
998 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1000 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1001 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1002 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1003 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1005 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1007 // game button identifiers
1008 #define GAME_CTRL_ID_STOP 0
1009 #define GAME_CTRL_ID_PAUSE 1
1010 #define GAME_CTRL_ID_PLAY 2
1011 #define GAME_CTRL_ID_UNDO 3
1012 #define GAME_CTRL_ID_REDO 4
1013 #define GAME_CTRL_ID_SAVE 5
1014 #define GAME_CTRL_ID_PAUSE2 6
1015 #define GAME_CTRL_ID_LOAD 7
1016 #define GAME_CTRL_ID_PANEL_STOP 8
1017 #define GAME_CTRL_ID_PANEL_PAUSE 9
1018 #define GAME_CTRL_ID_PANEL_PLAY 10
1019 #define GAME_CTRL_ID_TOUCH_STOP 11
1020 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1021 #define SOUND_CTRL_ID_MUSIC 13
1022 #define SOUND_CTRL_ID_LOOPS 14
1023 #define SOUND_CTRL_ID_SIMPLE 15
1024 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1025 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1026 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1028 #define NUM_GAME_BUTTONS 19
1031 // forward declaration for internal use
1033 static void CreateField(int, int, int);
1035 static void ResetGfxAnimation(int, int);
1037 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1038 static void AdvanceFrameAndPlayerCounters(int);
1040 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1041 static boolean MovePlayer(struct PlayerInfo *, int, int);
1042 static void ScrollPlayer(struct PlayerInfo *, int);
1043 static void ScrollScreen(struct PlayerInfo *, int);
1045 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1046 static boolean DigFieldByCE(int, int, int);
1047 static boolean SnapField(struct PlayerInfo *, int, int);
1048 static boolean DropElement(struct PlayerInfo *);
1050 static void InitBeltMovement(void);
1051 static void CloseAllOpenTimegates(void);
1052 static void CheckGravityMovement(struct PlayerInfo *);
1053 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1054 static void KillPlayerUnlessEnemyProtected(int, int);
1055 static void KillPlayerUnlessExplosionProtected(int, int);
1057 static void TestIfPlayerTouchesCustomElement(int, int);
1058 static void TestIfElementTouchesCustomElement(int, int);
1059 static void TestIfElementHitsCustomElement(int, int, int);
1061 static void HandleElementChange(int, int, int);
1062 static void ExecuteCustomElementAction(int, int, int, int);
1063 static boolean ChangeElement(int, int, int, int);
1065 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1066 #define CheckTriggeredElementChange(x, y, e, ev) \
1067 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1068 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1069 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1070 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1071 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1072 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1073 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1075 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1076 #define CheckElementChange(x, y, e, te, ev) \
1077 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1078 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1079 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1080 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1081 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1083 static void PlayLevelSound(int, int, int);
1084 static void PlayLevelSoundNearest(int, int, int);
1085 static void PlayLevelSoundAction(int, int, int);
1086 static void PlayLevelSoundElementAction(int, int, int, int);
1087 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1088 static void PlayLevelSoundActionIfLoop(int, int, int);
1089 static void StopLevelSoundActionIfLoop(int, int, int);
1090 static void PlayLevelMusic(void);
1091 static void FadeLevelSoundsAndMusic(void);
1093 static void HandleGameButtons(struct GadgetInfo *);
1095 int AmoebeNachbarNr(int, int);
1096 void AmoebeUmwandeln(int, int);
1097 void ContinueMoving(int, int);
1098 void Bang(int, int);
1099 void InitMovDir(int, int);
1100 void InitAmoebaNr(int, int);
1101 int NewHiScore(int);
1103 void TestIfGoodThingHitsBadThing(int, int, int);
1104 void TestIfBadThingHitsGoodThing(int, int, int);
1105 void TestIfPlayerTouchesBadThing(int, int);
1106 void TestIfPlayerRunsIntoBadThing(int, int, int);
1107 void TestIfBadThingTouchesPlayer(int, int);
1108 void TestIfBadThingRunsIntoPlayer(int, int, int);
1109 void TestIfFriendTouchesBadThing(int, int);
1110 void TestIfBadThingTouchesFriend(int, int);
1111 void TestIfBadThingTouchesOtherBadThing(int, int);
1112 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1114 void KillPlayer(struct PlayerInfo *);
1115 void BuryPlayer(struct PlayerInfo *);
1116 void RemovePlayer(struct PlayerInfo *);
1117 void ExitPlayer(struct PlayerInfo *);
1119 static int getInvisibleActiveFromInvisibleElement(int);
1120 static int getInvisibleFromInvisibleActiveElement(int);
1122 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1124 // for detection of endless loops, caused by custom element programming
1125 // (using maximal playfield width x 10 is just a rough approximation)
1126 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1128 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1130 if (recursion_loop_detected) \
1133 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1135 recursion_loop_detected = TRUE; \
1136 recursion_loop_element = (e); \
1139 recursion_loop_depth++; \
1142 #define RECURSION_LOOP_DETECTION_END() \
1144 recursion_loop_depth--; \
1147 static int recursion_loop_depth;
1148 static boolean recursion_loop_detected;
1149 static boolean recursion_loop_element;
1151 static int map_player_action[MAX_PLAYERS];
1154 // ----------------------------------------------------------------------------
1155 // definition of elements that automatically change to other elements after
1156 // a specified time, eventually calling a function when changing
1157 // ----------------------------------------------------------------------------
1159 // forward declaration for changer functions
1160 static void InitBuggyBase(int, int);
1161 static void WarnBuggyBase(int, int);
1163 static void InitTrap(int, int);
1164 static void ActivateTrap(int, int);
1165 static void ChangeActiveTrap(int, int);
1167 static void InitRobotWheel(int, int);
1168 static void RunRobotWheel(int, int);
1169 static void StopRobotWheel(int, int);
1171 static void InitTimegateWheel(int, int);
1172 static void RunTimegateWheel(int, int);
1174 static void InitMagicBallDelay(int, int);
1175 static void ActivateMagicBall(int, int);
1177 struct ChangingElementInfo
1182 void (*pre_change_function)(int x, int y);
1183 void (*change_function)(int x, int y);
1184 void (*post_change_function)(int x, int y);
1187 static struct ChangingElementInfo change_delay_list[] =
1222 EL_STEEL_EXIT_OPENING,
1230 EL_STEEL_EXIT_CLOSING,
1231 EL_STEEL_EXIT_CLOSED,
1254 EL_EM_STEEL_EXIT_OPENING,
1255 EL_EM_STEEL_EXIT_OPEN,
1262 EL_EM_STEEL_EXIT_CLOSING,
1286 EL_SWITCHGATE_OPENING,
1294 EL_SWITCHGATE_CLOSING,
1295 EL_SWITCHGATE_CLOSED,
1302 EL_TIMEGATE_OPENING,
1310 EL_TIMEGATE_CLOSING,
1319 EL_ACID_SPLASH_LEFT,
1327 EL_ACID_SPLASH_RIGHT,
1336 EL_SP_BUGGY_BASE_ACTIVATING,
1343 EL_SP_BUGGY_BASE_ACTIVATING,
1344 EL_SP_BUGGY_BASE_ACTIVE,
1351 EL_SP_BUGGY_BASE_ACTIVE,
1375 EL_ROBOT_WHEEL_ACTIVE,
1383 EL_TIMEGATE_SWITCH_ACTIVE,
1391 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1392 EL_DC_TIMEGATE_SWITCH,
1399 EL_EMC_MAGIC_BALL_ACTIVE,
1400 EL_EMC_MAGIC_BALL_ACTIVE,
1407 EL_EMC_SPRING_BUMPER_ACTIVE,
1408 EL_EMC_SPRING_BUMPER,
1415 EL_DIAGONAL_SHRINKING,
1423 EL_DIAGONAL_GROWING,
1444 int push_delay_fixed, push_delay_random;
1448 { EL_SPRING, 0, 0 },
1449 { EL_BALLOON, 0, 0 },
1451 { EL_SOKOBAN_OBJECT, 2, 0 },
1452 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1453 { EL_SATELLITE, 2, 0 },
1454 { EL_SP_DISK_YELLOW, 2, 0 },
1456 { EL_UNDEFINED, 0, 0 },
1464 move_stepsize_list[] =
1466 { EL_AMOEBA_DROP, 2 },
1467 { EL_AMOEBA_DROPPING, 2 },
1468 { EL_QUICKSAND_FILLING, 1 },
1469 { EL_QUICKSAND_EMPTYING, 1 },
1470 { EL_QUICKSAND_FAST_FILLING, 2 },
1471 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1472 { EL_MAGIC_WALL_FILLING, 2 },
1473 { EL_MAGIC_WALL_EMPTYING, 2 },
1474 { EL_BD_MAGIC_WALL_FILLING, 2 },
1475 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1476 { EL_DC_MAGIC_WALL_FILLING, 2 },
1477 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1479 { EL_UNDEFINED, 0 },
1487 collect_count_list[] =
1490 { EL_BD_DIAMOND, 1 },
1491 { EL_EMERALD_YELLOW, 1 },
1492 { EL_EMERALD_RED, 1 },
1493 { EL_EMERALD_PURPLE, 1 },
1495 { EL_SP_INFOTRON, 1 },
1499 { EL_UNDEFINED, 0 },
1507 access_direction_list[] =
1509 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1510 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1511 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1512 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1513 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1514 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1515 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1516 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1517 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1518 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1519 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1521 { EL_SP_PORT_LEFT, MV_RIGHT },
1522 { EL_SP_PORT_RIGHT, MV_LEFT },
1523 { EL_SP_PORT_UP, MV_DOWN },
1524 { EL_SP_PORT_DOWN, MV_UP },
1525 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1526 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1527 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1528 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1529 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1530 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1531 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1532 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1533 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1534 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1535 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1536 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1537 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1538 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1539 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1541 { EL_UNDEFINED, MV_NONE }
1544 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1546 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1547 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1548 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Feld[x][y]) || \
1549 IS_JUST_CHANGING(x, y))
1551 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1553 // static variables for playfield scan mode (scanning forward or backward)
1554 static int playfield_scan_start_x = 0;
1555 static int playfield_scan_start_y = 0;
1556 static int playfield_scan_delta_x = 1;
1557 static int playfield_scan_delta_y = 1;
1559 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1560 (y) >= 0 && (y) <= lev_fieldy - 1; \
1561 (y) += playfield_scan_delta_y) \
1562 for ((x) = playfield_scan_start_x; \
1563 (x) >= 0 && (x) <= lev_fieldx - 1; \
1564 (x) += playfield_scan_delta_x)
1567 void DEBUG_SetMaximumDynamite(void)
1571 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1572 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1573 local_player->inventory_element[local_player->inventory_size++] =
1578 static void InitPlayfieldScanModeVars(void)
1580 if (game.use_reverse_scan_direction)
1582 playfield_scan_start_x = lev_fieldx - 1;
1583 playfield_scan_start_y = lev_fieldy - 1;
1585 playfield_scan_delta_x = -1;
1586 playfield_scan_delta_y = -1;
1590 playfield_scan_start_x = 0;
1591 playfield_scan_start_y = 0;
1593 playfield_scan_delta_x = 1;
1594 playfield_scan_delta_y = 1;
1598 static void InitPlayfieldScanMode(int mode)
1600 game.use_reverse_scan_direction =
1601 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1603 InitPlayfieldScanModeVars();
1606 static int get_move_delay_from_stepsize(int move_stepsize)
1609 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1611 // make sure that stepsize value is always a power of 2
1612 move_stepsize = (1 << log_2(move_stepsize));
1614 return TILEX / move_stepsize;
1617 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1620 int player_nr = player->index_nr;
1621 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1622 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1624 // do no immediately change move delay -- the player might just be moving
1625 player->move_delay_value_next = move_delay;
1627 // information if player can move must be set separately
1628 player->cannot_move = cannot_move;
1632 player->move_delay = game.initial_move_delay[player_nr];
1633 player->move_delay_value = game.initial_move_delay_value[player_nr];
1635 player->move_delay_value_next = -1;
1637 player->move_delay_reset_counter = 0;
1641 void GetPlayerConfig(void)
1643 GameFrameDelay = setup.game_frame_delay;
1645 if (!audio.sound_available)
1646 setup.sound_simple = FALSE;
1648 if (!audio.loops_available)
1649 setup.sound_loops = FALSE;
1651 if (!audio.music_available)
1652 setup.sound_music = FALSE;
1654 if (!video.fullscreen_available)
1655 setup.fullscreen = FALSE;
1657 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1659 SetAudioMode(setup.sound);
1662 int GetElementFromGroupElement(int element)
1664 if (IS_GROUP_ELEMENT(element))
1666 struct ElementGroupInfo *group = element_info[element].group;
1667 int last_anim_random_frame = gfx.anim_random_frame;
1670 if (group->choice_mode == ANIM_RANDOM)
1671 gfx.anim_random_frame = RND(group->num_elements_resolved);
1673 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1674 group->choice_mode, 0,
1677 if (group->choice_mode == ANIM_RANDOM)
1678 gfx.anim_random_frame = last_anim_random_frame;
1680 group->choice_pos++;
1682 element = group->element_resolved[element_pos];
1688 static void IncrementSokobanFieldsNeeded(void)
1690 if (level.sb_fields_needed)
1691 game.sokoban_fields_still_needed++;
1694 static void IncrementSokobanObjectsNeeded(void)
1696 if (level.sb_objects_needed)
1697 game.sokoban_objects_still_needed++;
1700 static void DecrementSokobanFieldsNeeded(void)
1702 if (game.sokoban_fields_still_needed > 0)
1703 game.sokoban_fields_still_needed--;
1706 static void DecrementSokobanObjectsNeeded(void)
1708 if (game.sokoban_objects_still_needed > 0)
1709 game.sokoban_objects_still_needed--;
1712 static void InitPlayerField(int x, int y, int element, boolean init_game)
1714 if (element == EL_SP_MURPHY)
1718 if (stored_player[0].present)
1720 Feld[x][y] = EL_SP_MURPHY_CLONE;
1726 stored_player[0].initial_element = element;
1727 stored_player[0].use_murphy = TRUE;
1729 if (!level.use_artwork_element[0])
1730 stored_player[0].artwork_element = EL_SP_MURPHY;
1733 Feld[x][y] = EL_PLAYER_1;
1739 struct PlayerInfo *player = &stored_player[Feld[x][y] - EL_PLAYER_1];
1740 int jx = player->jx, jy = player->jy;
1742 player->present = TRUE;
1744 player->block_last_field = (element == EL_SP_MURPHY ?
1745 level.sp_block_last_field :
1746 level.block_last_field);
1748 // ---------- initialize player's last field block delay ------------------
1750 // always start with reliable default value (no adjustment needed)
1751 player->block_delay_adjustment = 0;
1753 // special case 1: in Supaplex, Murphy blocks last field one more frame
1754 if (player->block_last_field && element == EL_SP_MURPHY)
1755 player->block_delay_adjustment = 1;
1757 // special case 2: in game engines before 3.1.1, blocking was different
1758 if (game.use_block_last_field_bug)
1759 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1761 if (!network.enabled || player->connected_network)
1763 player->active = TRUE;
1765 // remove potentially duplicate players
1766 if (StorePlayer[jx][jy] == Feld[x][y])
1767 StorePlayer[jx][jy] = 0;
1769 StorePlayer[x][y] = Feld[x][y];
1771 #if DEBUG_INIT_PLAYER
1774 printf("- player element %d activated", player->element_nr);
1775 printf(" (local player is %d and currently %s)\n",
1776 local_player->element_nr,
1777 local_player->active ? "active" : "not active");
1782 Feld[x][y] = EL_EMPTY;
1784 player->jx = player->last_jx = x;
1785 player->jy = player->last_jy = y;
1790 int player_nr = GET_PLAYER_NR(element);
1791 struct PlayerInfo *player = &stored_player[player_nr];
1793 if (player->active && player->killed)
1794 player->reanimated = TRUE; // if player was just killed, reanimate him
1798 static void InitField(int x, int y, boolean init_game)
1800 int element = Feld[x][y];
1809 InitPlayerField(x, y, element, init_game);
1812 case EL_SOKOBAN_FIELD_PLAYER:
1813 element = Feld[x][y] = EL_PLAYER_1;
1814 InitField(x, y, init_game);
1816 element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1817 InitField(x, y, init_game);
1820 case EL_SOKOBAN_FIELD_EMPTY:
1821 IncrementSokobanFieldsNeeded();
1824 case EL_SOKOBAN_OBJECT:
1825 IncrementSokobanObjectsNeeded();
1829 if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID)
1830 Feld[x][y] = EL_ACID_POOL_TOPLEFT;
1831 else if (x > 0 && Feld[x-1][y] == EL_ACID)
1832 Feld[x][y] = EL_ACID_POOL_TOPRIGHT;
1833 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPLEFT)
1834 Feld[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1835 else if (y > 0 && Feld[x][y-1] == EL_ACID)
1836 Feld[x][y] = EL_ACID_POOL_BOTTOM;
1837 else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1838 Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1847 case EL_SPACESHIP_RIGHT:
1848 case EL_SPACESHIP_UP:
1849 case EL_SPACESHIP_LEFT:
1850 case EL_SPACESHIP_DOWN:
1851 case EL_BD_BUTTERFLY:
1852 case EL_BD_BUTTERFLY_RIGHT:
1853 case EL_BD_BUTTERFLY_UP:
1854 case EL_BD_BUTTERFLY_LEFT:
1855 case EL_BD_BUTTERFLY_DOWN:
1857 case EL_BD_FIREFLY_RIGHT:
1858 case EL_BD_FIREFLY_UP:
1859 case EL_BD_FIREFLY_LEFT:
1860 case EL_BD_FIREFLY_DOWN:
1861 case EL_PACMAN_RIGHT:
1863 case EL_PACMAN_LEFT:
1864 case EL_PACMAN_DOWN:
1866 case EL_YAMYAM_LEFT:
1867 case EL_YAMYAM_RIGHT:
1869 case EL_YAMYAM_DOWN:
1870 case EL_DARK_YAMYAM:
1873 case EL_SP_SNIKSNAK:
1874 case EL_SP_ELECTRON:
1883 case EL_AMOEBA_FULL:
1888 case EL_AMOEBA_DROP:
1889 if (y == lev_fieldy - 1)
1891 Feld[x][y] = EL_AMOEBA_GROWING;
1892 Store[x][y] = EL_AMOEBA_WET;
1896 case EL_DYNAMITE_ACTIVE:
1897 case EL_SP_DISK_RED_ACTIVE:
1898 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1899 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1900 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1901 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1902 MovDelay[x][y] = 96;
1905 case EL_EM_DYNAMITE_ACTIVE:
1906 MovDelay[x][y] = 32;
1910 game.lights_still_needed++;
1914 game.friends_still_needed++;
1919 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1922 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1923 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1924 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1925 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1926 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1927 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1928 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1929 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1930 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1931 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1932 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1933 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1936 int belt_nr = getBeltNrFromBeltSwitchElement(Feld[x][y]);
1937 int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]);
1938 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[x][y]);
1940 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1942 game.belt_dir[belt_nr] = belt_dir;
1943 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1945 else // more than one switch -- set it like the first switch
1947 Feld[x][y] = Feld[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1952 case EL_LIGHT_SWITCH_ACTIVE:
1954 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1957 case EL_INVISIBLE_STEELWALL:
1958 case EL_INVISIBLE_WALL:
1959 case EL_INVISIBLE_SAND:
1960 if (game.light_time_left > 0 ||
1961 game.lenses_time_left > 0)
1962 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
1965 case EL_EMC_MAGIC_BALL:
1966 if (game.ball_active)
1967 Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1970 case EL_EMC_MAGIC_BALL_SWITCH:
1971 if (game.ball_active)
1972 Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1975 case EL_TRIGGER_PLAYER:
1976 case EL_TRIGGER_ELEMENT:
1977 case EL_TRIGGER_CE_VALUE:
1978 case EL_TRIGGER_CE_SCORE:
1980 case EL_ANY_ELEMENT:
1981 case EL_CURRENT_CE_VALUE:
1982 case EL_CURRENT_CE_SCORE:
1999 // reference elements should not be used on the playfield
2000 Feld[x][y] = EL_EMPTY;
2004 if (IS_CUSTOM_ELEMENT(element))
2006 if (CAN_MOVE(element))
2009 if (!element_info[element].use_last_ce_value || init_game)
2010 CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]);
2012 else if (IS_GROUP_ELEMENT(element))
2014 Feld[x][y] = GetElementFromGroupElement(element);
2016 InitField(x, y, init_game);
2023 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2026 static void InitField_WithBug1(int x, int y, boolean init_game)
2028 InitField(x, y, init_game);
2030 // not needed to call InitMovDir() -- already done by InitField()!
2031 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2032 CAN_MOVE(Feld[x][y]))
2036 static void InitField_WithBug2(int x, int y, boolean init_game)
2038 int old_element = Feld[x][y];
2040 InitField(x, y, init_game);
2042 // not needed to call InitMovDir() -- already done by InitField()!
2043 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2044 CAN_MOVE(old_element) &&
2045 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2048 /* this case is in fact a combination of not less than three bugs:
2049 first, it calls InitMovDir() for elements that can move, although this is
2050 already done by InitField(); then, it checks the element that was at this
2051 field _before_ the call to InitField() (which can change it); lastly, it
2052 was not called for "mole with direction" elements, which were treated as
2053 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2057 static int get_key_element_from_nr(int key_nr)
2059 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2060 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2061 EL_EM_KEY_1 : EL_KEY_1);
2063 return key_base_element + key_nr;
2066 static int get_next_dropped_element(struct PlayerInfo *player)
2068 return (player->inventory_size > 0 ?
2069 player->inventory_element[player->inventory_size - 1] :
2070 player->inventory_infinite_element != EL_UNDEFINED ?
2071 player->inventory_infinite_element :
2072 player->dynabombs_left > 0 ?
2073 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2077 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2079 // pos >= 0: get element from bottom of the stack;
2080 // pos < 0: get element from top of the stack
2084 int min_inventory_size = -pos;
2085 int inventory_pos = player->inventory_size - min_inventory_size;
2086 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2088 return (player->inventory_size >= min_inventory_size ?
2089 player->inventory_element[inventory_pos] :
2090 player->inventory_infinite_element != EL_UNDEFINED ?
2091 player->inventory_infinite_element :
2092 player->dynabombs_left >= min_dynabombs_left ?
2093 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2098 int min_dynabombs_left = pos + 1;
2099 int min_inventory_size = pos + 1 - player->dynabombs_left;
2100 int inventory_pos = pos - player->dynabombs_left;
2102 return (player->inventory_infinite_element != EL_UNDEFINED ?
2103 player->inventory_infinite_element :
2104 player->dynabombs_left >= min_dynabombs_left ?
2105 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2106 player->inventory_size >= min_inventory_size ?
2107 player->inventory_element[inventory_pos] :
2112 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2114 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2115 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2118 if (gpo1->sort_priority != gpo2->sort_priority)
2119 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2121 compare_result = gpo1->nr - gpo2->nr;
2123 return compare_result;
2126 int getPlayerInventorySize(int player_nr)
2128 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2129 return game_em.ply[player_nr]->dynamite;
2130 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2131 return game_sp.red_disk_count;
2133 return stored_player[player_nr].inventory_size;
2136 static void InitGameControlValues(void)
2140 for (i = 0; game_panel_controls[i].nr != -1; i++)
2142 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2143 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2144 struct TextPosInfo *pos = gpc->pos;
2146 int type = gpc->type;
2150 Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
2151 Error(ERR_EXIT, "this should not happen -- please debug");
2154 // force update of game controls after initialization
2155 gpc->value = gpc->last_value = -1;
2156 gpc->frame = gpc->last_frame = -1;
2157 gpc->gfx_frame = -1;
2159 // determine panel value width for later calculation of alignment
2160 if (type == TYPE_INTEGER || type == TYPE_STRING)
2162 pos->width = pos->size * getFontWidth(pos->font);
2163 pos->height = getFontHeight(pos->font);
2165 else if (type == TYPE_ELEMENT)
2167 pos->width = pos->size;
2168 pos->height = pos->size;
2171 // fill structure for game panel draw order
2173 gpo->sort_priority = pos->sort_priority;
2176 // sort game panel controls according to sort_priority and control number
2177 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2178 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2181 static void UpdatePlayfieldElementCount(void)
2183 boolean use_element_count = FALSE;
2186 // first check if it is needed at all to calculate playfield element count
2187 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2188 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2189 use_element_count = TRUE;
2191 if (!use_element_count)
2194 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2195 element_info[i].element_count = 0;
2197 SCAN_PLAYFIELD(x, y)
2199 element_info[Feld[x][y]].element_count++;
2202 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2203 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2204 if (IS_IN_GROUP(j, i))
2205 element_info[EL_GROUP_START + i].element_count +=
2206 element_info[j].element_count;
2209 static void UpdateGameControlValues(void)
2212 int time = (game.LevelSolved ?
2213 game.LevelSolved_CountingTime :
2214 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2216 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2217 game_sp.time_played :
2218 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2219 game_mm.energy_left :
2220 game.no_time_limit ? TimePlayed : TimeLeft);
2221 int score = (game.LevelSolved ?
2222 game.LevelSolved_CountingScore :
2223 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2224 game_em.lev->score :
2225 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2227 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2230 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2231 game_em.lev->gems_needed :
2232 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2233 game_sp.infotrons_still_needed :
2234 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2235 game_mm.kettles_still_needed :
2236 game.gems_still_needed);
2237 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2238 game_em.lev->gems_needed > 0 :
2239 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2240 game_sp.infotrons_still_needed > 0 :
2241 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2242 game_mm.kettles_still_needed > 0 ||
2243 game_mm.lights_still_needed > 0 :
2244 game.gems_still_needed > 0 ||
2245 game.sokoban_fields_still_needed > 0 ||
2246 game.sokoban_objects_still_needed > 0 ||
2247 game.lights_still_needed > 0);
2248 int health = (game.LevelSolved ?
2249 game.LevelSolved_CountingHealth :
2250 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2251 MM_HEALTH(game_mm.laser_overload_value) :
2254 UpdatePlayfieldElementCount();
2256 // update game panel control values
2258 // used instead of "level_nr" (for network games)
2259 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2260 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2262 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2263 for (i = 0; i < MAX_NUM_KEYS; i++)
2264 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2265 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2266 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2268 if (game.centered_player_nr == -1)
2270 for (i = 0; i < MAX_PLAYERS; i++)
2272 // only one player in Supaplex game engine
2273 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2276 for (k = 0; k < MAX_NUM_KEYS; k++)
2278 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2280 if (game_em.ply[i]->keys & (1 << k))
2281 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2282 get_key_element_from_nr(k);
2284 else if (stored_player[i].key[k])
2285 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2286 get_key_element_from_nr(k);
2289 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2290 getPlayerInventorySize(i);
2292 if (stored_player[i].num_white_keys > 0)
2293 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2296 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2297 stored_player[i].num_white_keys;
2302 int player_nr = game.centered_player_nr;
2304 for (k = 0; k < MAX_NUM_KEYS; k++)
2306 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2308 if (game_em.ply[player_nr]->keys & (1 << k))
2309 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2310 get_key_element_from_nr(k);
2312 else if (stored_player[player_nr].key[k])
2313 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2314 get_key_element_from_nr(k);
2317 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2318 getPlayerInventorySize(player_nr);
2320 if (stored_player[player_nr].num_white_keys > 0)
2321 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2323 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2324 stored_player[player_nr].num_white_keys;
2327 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2329 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2330 get_inventory_element_from_pos(local_player, i);
2331 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2332 get_inventory_element_from_pos(local_player, -i - 1);
2335 game_panel_controls[GAME_PANEL_SCORE].value = score;
2336 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2338 game_panel_controls[GAME_PANEL_TIME].value = time;
2340 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2341 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2342 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2344 if (level.time == 0)
2345 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2347 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2349 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2350 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2352 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2354 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2355 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2357 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2358 local_player->shield_normal_time_left;
2359 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2360 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2362 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2363 local_player->shield_deadly_time_left;
2365 game_panel_controls[GAME_PANEL_EXIT].value =
2366 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2368 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2369 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2370 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2371 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2372 EL_EMC_MAGIC_BALL_SWITCH);
2374 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2375 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2376 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2377 game.light_time_left;
2379 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2380 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2381 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2382 game.timegate_time_left;
2384 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2385 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2387 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2388 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2389 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2390 game.lenses_time_left;
2392 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2393 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2394 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2395 game.magnify_time_left;
2397 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2398 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2399 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2400 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2401 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2402 EL_BALLOON_SWITCH_NONE);
2404 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2405 local_player->dynabomb_count;
2406 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2407 local_player->dynabomb_size;
2408 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2409 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2411 game_panel_controls[GAME_PANEL_PENGUINS].value =
2412 game.friends_still_needed;
2414 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2415 game.sokoban_objects_still_needed;
2416 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2417 game.sokoban_fields_still_needed;
2419 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2420 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2422 for (i = 0; i < NUM_BELTS; i++)
2424 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2425 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2426 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2427 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2428 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2431 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2432 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2433 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2434 game.magic_wall_time_left;
2436 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2437 local_player->gravity;
2439 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2440 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2442 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2443 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2444 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2445 game.panel.element[i].id : EL_UNDEFINED);
2447 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2448 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2449 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2450 element_info[game.panel.element_count[i].id].element_count : 0);
2452 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2453 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2454 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2455 element_info[game.panel.ce_score[i].id].collect_score : 0);
2457 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2458 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2459 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2460 element_info[game.panel.ce_score_element[i].id].collect_score :
2463 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2464 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2465 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2467 // update game panel control frames
2469 for (i = 0; game_panel_controls[i].nr != -1; i++)
2471 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2473 if (gpc->type == TYPE_ELEMENT)
2475 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2477 int last_anim_random_frame = gfx.anim_random_frame;
2478 int element = gpc->value;
2479 int graphic = el2panelimg(element);
2481 if (gpc->value != gpc->last_value)
2484 gpc->gfx_random = INIT_GFX_RANDOM();
2490 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2491 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2492 gpc->gfx_random = INIT_GFX_RANDOM();
2495 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2496 gfx.anim_random_frame = gpc->gfx_random;
2498 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2499 gpc->gfx_frame = element_info[element].collect_score;
2501 gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
2504 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2505 gfx.anim_random_frame = last_anim_random_frame;
2508 else if (gpc->type == TYPE_GRAPHIC)
2510 if (gpc->graphic != IMG_UNDEFINED)
2512 int last_anim_random_frame = gfx.anim_random_frame;
2513 int graphic = gpc->graphic;
2515 if (gpc->value != gpc->last_value)
2518 gpc->gfx_random = INIT_GFX_RANDOM();
2524 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2525 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2526 gpc->gfx_random = INIT_GFX_RANDOM();
2529 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2530 gfx.anim_random_frame = gpc->gfx_random;
2532 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2534 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2535 gfx.anim_random_frame = last_anim_random_frame;
2541 static void DisplayGameControlValues(void)
2543 boolean redraw_panel = FALSE;
2546 for (i = 0; game_panel_controls[i].nr != -1; i++)
2548 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2550 if (PANEL_DEACTIVATED(gpc->pos))
2553 if (gpc->value == gpc->last_value &&
2554 gpc->frame == gpc->last_frame)
2557 redraw_panel = TRUE;
2563 // copy default game door content to main double buffer
2565 // !!! CHECK AGAIN !!!
2566 SetPanelBackground();
2567 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2568 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2570 // redraw game control buttons
2571 RedrawGameButtons();
2573 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2575 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2577 int nr = game_panel_order[i].nr;
2578 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2579 struct TextPosInfo *pos = gpc->pos;
2580 int type = gpc->type;
2581 int value = gpc->value;
2582 int frame = gpc->frame;
2583 int size = pos->size;
2584 int font = pos->font;
2585 boolean draw_masked = pos->draw_masked;
2586 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2588 if (PANEL_DEACTIVATED(pos))
2591 gpc->last_value = value;
2592 gpc->last_frame = frame;
2594 if (type == TYPE_INTEGER)
2596 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2597 nr == GAME_PANEL_TIME)
2599 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2601 if (use_dynamic_size) // use dynamic number of digits
2603 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2604 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2605 int size2 = size1 + 1;
2606 int font1 = pos->font;
2607 int font2 = pos->font_alt;
2609 size = (value < value_change ? size1 : size2);
2610 font = (value < value_change ? font1 : font2);
2614 // correct text size if "digits" is zero or less
2616 size = strlen(int2str(value, size));
2618 // dynamically correct text alignment
2619 pos->width = size * getFontWidth(font);
2621 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2622 int2str(value, size), font, mask_mode);
2624 else if (type == TYPE_ELEMENT)
2626 int element, graphic;
2630 int dst_x = PANEL_XPOS(pos);
2631 int dst_y = PANEL_YPOS(pos);
2633 if (value != EL_UNDEFINED && value != EL_EMPTY)
2636 graphic = el2panelimg(value);
2638 // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
2640 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2643 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2646 width = graphic_info[graphic].width * size / TILESIZE;
2647 height = graphic_info[graphic].height * size / TILESIZE;
2650 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2653 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2657 else if (type == TYPE_GRAPHIC)
2659 int graphic = gpc->graphic;
2660 int graphic_active = gpc->graphic_active;
2664 int dst_x = PANEL_XPOS(pos);
2665 int dst_y = PANEL_YPOS(pos);
2666 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2667 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2669 if (graphic != IMG_UNDEFINED && !skip)
2671 if (pos->style == STYLE_REVERSE)
2672 value = 100 - value;
2674 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2676 if (pos->direction & MV_HORIZONTAL)
2678 width = graphic_info[graphic_active].width * value / 100;
2679 height = graphic_info[graphic_active].height;
2681 if (pos->direction == MV_LEFT)
2683 src_x += graphic_info[graphic_active].width - width;
2684 dst_x += graphic_info[graphic_active].width - width;
2689 width = graphic_info[graphic_active].width;
2690 height = graphic_info[graphic_active].height * value / 100;
2692 if (pos->direction == MV_UP)
2694 src_y += graphic_info[graphic_active].height - height;
2695 dst_y += graphic_info[graphic_active].height - height;
2700 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2703 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2706 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2708 if (pos->direction & MV_HORIZONTAL)
2710 if (pos->direction == MV_RIGHT)
2717 dst_x = PANEL_XPOS(pos);
2720 width = graphic_info[graphic].width - width;
2724 if (pos->direction == MV_DOWN)
2731 dst_y = PANEL_YPOS(pos);
2734 height = graphic_info[graphic].height - height;
2738 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2741 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2745 else if (type == TYPE_STRING)
2747 boolean active = (value != 0);
2748 char *state_normal = "off";
2749 char *state_active = "on";
2750 char *state = (active ? state_active : state_normal);
2751 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2752 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2753 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2754 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2756 if (nr == GAME_PANEL_GRAVITY_STATE)
2758 int font1 = pos->font; // (used for normal state)
2759 int font2 = pos->font_alt; // (used for active state)
2761 font = (active ? font2 : font1);
2770 // don't truncate output if "chars" is zero or less
2773 // dynamically correct text alignment
2774 pos->width = size * getFontWidth(font);
2777 s_cut = getStringCopyN(s, size);
2779 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2780 s_cut, font, mask_mode);
2786 redraw_mask |= REDRAW_DOOR_1;
2789 SetGameStatus(GAME_MODE_PLAYING);
2792 void UpdateAndDisplayGameControlValues(void)
2794 if (tape.deactivate_display)
2797 UpdateGameControlValues();
2798 DisplayGameControlValues();
2802 static void UpdateGameDoorValues(void)
2804 UpdateGameControlValues();
2808 void DrawGameDoorValues(void)
2810 DisplayGameControlValues();
2814 // ============================================================================
2816 // ----------------------------------------------------------------------------
2817 // initialize game engine due to level / tape version number
2818 // ============================================================================
2820 static void InitGameEngine(void)
2822 int i, j, k, l, x, y;
2824 // set game engine from tape file when re-playing, else from level file
2825 game.engine_version = (tape.playing ? tape.engine_version :
2826 level.game_version);
2828 // set single or multi-player game mode (needed for re-playing tapes)
2829 game.team_mode = setup.team_mode;
2833 int num_players = 0;
2835 for (i = 0; i < MAX_PLAYERS; i++)
2836 if (tape.player_participates[i])
2839 // multi-player tapes contain input data for more than one player
2840 game.team_mode = (num_players > 1);
2843 // --------------------------------------------------------------------------
2844 // set flags for bugs and changes according to active game engine version
2845 // --------------------------------------------------------------------------
2848 Summary of bugfix/change:
2849 Fixed handling for custom elements that change when pushed by the player.
2851 Fixed/changed in version:
2855 Before 3.1.0, custom elements that "change when pushing" changed directly
2856 after the player started pushing them (until then handled in "DigField()").
2857 Since 3.1.0, these custom elements are not changed until the "pushing"
2858 move of the element is finished (now handled in "ContinueMoving()").
2860 Affected levels/tapes:
2861 The first condition is generally needed for all levels/tapes before version
2862 3.1.0, which might use the old behaviour before it was changed; known tapes
2863 that are affected are some tapes from the level set "Walpurgis Gardens" by
2865 The second condition is an exception from the above case and is needed for
2866 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2867 above (including some development versions of 3.1.0), but before it was
2868 known that this change would break tapes like the above and was fixed in
2869 3.1.1, so that the changed behaviour was active although the engine version
2870 while recording maybe was before 3.1.0. There is at least one tape that is
2871 affected by this exception, which is the tape for the one-level set "Bug
2872 Machine" by Juergen Bonhagen.
2875 game.use_change_when_pushing_bug =
2876 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2878 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2879 tape.game_version < VERSION_IDENT(3,1,1,0)));
2882 Summary of bugfix/change:
2883 Fixed handling for blocking the field the player leaves when moving.
2885 Fixed/changed in version:
2889 Before 3.1.1, when "block last field when moving" was enabled, the field
2890 the player is leaving when moving was blocked for the time of the move,
2891 and was directly unblocked afterwards. This resulted in the last field
2892 being blocked for exactly one less than the number of frames of one player
2893 move. Additionally, even when blocking was disabled, the last field was
2894 blocked for exactly one frame.
2895 Since 3.1.1, due to changes in player movement handling, the last field
2896 is not blocked at all when blocking is disabled. When blocking is enabled,
2897 the last field is blocked for exactly the number of frames of one player
2898 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2899 last field is blocked for exactly one more than the number of frames of
2902 Affected levels/tapes:
2903 (!!! yet to be determined -- probably many !!!)
2906 game.use_block_last_field_bug =
2907 (game.engine_version < VERSION_IDENT(3,1,1,0));
2909 /* various special flags and settings for native Emerald Mine game engine */
2911 game_em.use_single_button =
2912 (game.engine_version > VERSION_IDENT(4,0,0,2));
2914 game_em.use_snap_key_bug =
2915 (game.engine_version < VERSION_IDENT(4,0,1,0));
2917 game_em.use_old_explosions =
2918 (game.engine_version < VERSION_IDENT(4,1,4,2));
2920 // --------------------------------------------------------------------------
2922 // set maximal allowed number of custom element changes per game frame
2923 game.max_num_changes_per_frame = 1;
2925 // default scan direction: scan playfield from top/left to bottom/right
2926 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
2928 // dynamically adjust element properties according to game engine version
2929 InitElementPropertiesEngine(game.engine_version);
2932 printf("level %d: level version == %06d\n", level_nr, level.game_version);
2933 printf(" tape version == %06d [%s] [file: %06d]\n",
2934 tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
2936 printf(" => game.engine_version == %06d\n", game.engine_version);
2939 // ---------- initialize player's initial move delay ------------------------
2941 // dynamically adjust player properties according to level information
2942 for (i = 0; i < MAX_PLAYERS; i++)
2943 game.initial_move_delay_value[i] =
2944 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
2946 // dynamically adjust player properties according to game engine version
2947 for (i = 0; i < MAX_PLAYERS; i++)
2948 game.initial_move_delay[i] =
2949 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
2950 game.initial_move_delay_value[i] : 0);
2952 // ---------- initialize player's initial push delay ------------------------
2954 // dynamically adjust player properties according to game engine version
2955 game.initial_push_delay_value =
2956 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
2958 // ---------- initialize changing elements ----------------------------------
2960 // initialize changing elements information
2961 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2963 struct ElementInfo *ei = &element_info[i];
2965 // this pointer might have been changed in the level editor
2966 ei->change = &ei->change_page[0];
2968 if (!IS_CUSTOM_ELEMENT(i))
2970 ei->change->target_element = EL_EMPTY_SPACE;
2971 ei->change->delay_fixed = 0;
2972 ei->change->delay_random = 0;
2973 ei->change->delay_frames = 1;
2976 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
2978 ei->has_change_event[j] = FALSE;
2980 ei->event_page_nr[j] = 0;
2981 ei->event_page[j] = &ei->change_page[0];
2985 // add changing elements from pre-defined list
2986 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
2988 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
2989 struct ElementInfo *ei = &element_info[ch_delay->element];
2991 ei->change->target_element = ch_delay->target_element;
2992 ei->change->delay_fixed = ch_delay->change_delay;
2994 ei->change->pre_change_function = ch_delay->pre_change_function;
2995 ei->change->change_function = ch_delay->change_function;
2996 ei->change->post_change_function = ch_delay->post_change_function;
2998 ei->change->can_change = TRUE;
2999 ei->change->can_change_or_has_action = TRUE;
3001 ei->has_change_event[CE_DELAY] = TRUE;
3003 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3004 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3007 // ---------- initialize internal run-time variables ------------------------
3009 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3011 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3013 for (j = 0; j < ei->num_change_pages; j++)
3015 ei->change_page[j].can_change_or_has_action =
3016 (ei->change_page[j].can_change |
3017 ei->change_page[j].has_action);
3021 // add change events from custom element configuration
3022 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3024 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3026 for (j = 0; j < ei->num_change_pages; j++)
3028 if (!ei->change_page[j].can_change_or_has_action)
3031 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3033 // only add event page for the first page found with this event
3034 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3036 ei->has_change_event[k] = TRUE;
3038 ei->event_page_nr[k] = j;
3039 ei->event_page[k] = &ei->change_page[j];
3045 // ---------- initialize reference elements in change conditions ------------
3047 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3049 int element = EL_CUSTOM_START + i;
3050 struct ElementInfo *ei = &element_info[element];
3052 for (j = 0; j < ei->num_change_pages; j++)
3054 int trigger_element = ei->change_page[j].initial_trigger_element;
3056 if (trigger_element >= EL_PREV_CE_8 &&
3057 trigger_element <= EL_NEXT_CE_8)
3058 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3060 ei->change_page[j].trigger_element = trigger_element;
3064 // ---------- initialize run-time trigger player and element ----------------
3066 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3068 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3070 for (j = 0; j < ei->num_change_pages; j++)
3072 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3073 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3074 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3075 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3076 ei->change_page[j].actual_trigger_ce_value = 0;
3077 ei->change_page[j].actual_trigger_ce_score = 0;
3081 // ---------- initialize trigger events -------------------------------------
3083 // initialize trigger events information
3084 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3085 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3086 trigger_events[i][j] = FALSE;
3088 // add trigger events from element change event properties
3089 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3091 struct ElementInfo *ei = &element_info[i];
3093 for (j = 0; j < ei->num_change_pages; j++)
3095 if (!ei->change_page[j].can_change_or_has_action)
3098 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3100 int trigger_element = ei->change_page[j].trigger_element;
3102 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3104 if (ei->change_page[j].has_event[k])
3106 if (IS_GROUP_ELEMENT(trigger_element))
3108 struct ElementGroupInfo *group =
3109 element_info[trigger_element].group;
3111 for (l = 0; l < group->num_elements_resolved; l++)
3112 trigger_events[group->element_resolved[l]][k] = TRUE;
3114 else if (trigger_element == EL_ANY_ELEMENT)
3115 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3116 trigger_events[l][k] = TRUE;
3118 trigger_events[trigger_element][k] = TRUE;
3125 // ---------- initialize push delay -----------------------------------------
3127 // initialize push delay values to default
3128 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3130 if (!IS_CUSTOM_ELEMENT(i))
3132 // set default push delay values (corrected since version 3.0.7-1)
3133 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3135 element_info[i].push_delay_fixed = 2;
3136 element_info[i].push_delay_random = 8;
3140 element_info[i].push_delay_fixed = 8;
3141 element_info[i].push_delay_random = 8;
3146 // set push delay value for certain elements from pre-defined list
3147 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3149 int e = push_delay_list[i].element;
3151 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3152 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3155 // set push delay value for Supaplex elements for newer engine versions
3156 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3158 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3160 if (IS_SP_ELEMENT(i))
3162 // set SP push delay to just enough to push under a falling zonk
3163 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3165 element_info[i].push_delay_fixed = delay;
3166 element_info[i].push_delay_random = 0;
3171 // ---------- initialize move stepsize --------------------------------------
3173 // initialize move stepsize values to default
3174 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3175 if (!IS_CUSTOM_ELEMENT(i))
3176 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3178 // set move stepsize value for certain elements from pre-defined list
3179 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3181 int e = move_stepsize_list[i].element;
3183 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3186 // ---------- initialize collect score --------------------------------------
3188 // initialize collect score values for custom elements from initial value
3189 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3190 if (IS_CUSTOM_ELEMENT(i))
3191 element_info[i].collect_score = element_info[i].collect_score_initial;
3193 // ---------- initialize collect count --------------------------------------
3195 // initialize collect count values for non-custom elements
3196 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3197 if (!IS_CUSTOM_ELEMENT(i))
3198 element_info[i].collect_count_initial = 0;
3200 // add collect count values for all elements from pre-defined list
3201 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3202 element_info[collect_count_list[i].element].collect_count_initial =
3203 collect_count_list[i].count;
3205 // ---------- initialize access direction -----------------------------------
3207 // initialize access direction values to default (access from every side)
3208 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3209 if (!IS_CUSTOM_ELEMENT(i))
3210 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3212 // set access direction value for certain elements from pre-defined list
3213 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3214 element_info[access_direction_list[i].element].access_direction =
3215 access_direction_list[i].direction;
3217 // ---------- initialize explosion content ----------------------------------
3218 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3220 if (IS_CUSTOM_ELEMENT(i))
3223 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3225 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3227 element_info[i].content.e[x][y] =
3228 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3229 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3230 i == EL_PLAYER_3 ? EL_EMERALD :
3231 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3232 i == EL_MOLE ? EL_EMERALD_RED :
3233 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3234 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3235 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3236 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3237 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3238 i == EL_WALL_EMERALD ? EL_EMERALD :
3239 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3240 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3241 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3242 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3243 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3244 i == EL_WALL_PEARL ? EL_PEARL :
3245 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3250 // ---------- initialize recursion detection --------------------------------
3251 recursion_loop_depth = 0;
3252 recursion_loop_detected = FALSE;
3253 recursion_loop_element = EL_UNDEFINED;
3255 // ---------- initialize graphics engine ------------------------------------
3256 game.scroll_delay_value =
3257 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3258 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3259 !setup.forced_scroll_delay ? 0 :
3260 setup.scroll_delay ? setup.scroll_delay_value : 0);
3261 game.scroll_delay_value =
3262 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3264 // ---------- initialize game engine snapshots ------------------------------
3265 for (i = 0; i < MAX_PLAYERS; i++)
3266 game.snapshot.last_action[i] = 0;
3267 game.snapshot.changed_action = FALSE;
3268 game.snapshot.collected_item = FALSE;
3269 game.snapshot.mode =
3270 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3271 SNAPSHOT_MODE_EVERY_STEP :
3272 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3273 SNAPSHOT_MODE_EVERY_MOVE :
3274 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3275 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3276 game.snapshot.save_snapshot = FALSE;
3278 // ---------- initialize level time for Supaplex engine ---------------------
3279 // Supaplex levels with time limit currently unsupported -- should be added
3280 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3283 // ----------initialize flag for handling mouse events ---------------------
3285 // set flag to default value: do not handle mouse events
3286 game.use_mouse_events = FALSE;
3288 // now check for custom elements which have mouse click events defined
3289 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3291 int element = EL_CUSTOM_START + i;
3293 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3294 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3295 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3296 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3297 game.use_mouse_events = TRUE;
3301 static int get_num_special_action(int element, int action_first,
3304 int num_special_action = 0;
3307 for (i = action_first; i <= action_last; i++)
3309 boolean found = FALSE;
3311 for (j = 0; j < NUM_DIRECTIONS; j++)
3312 if (el_act_dir2img(element, i, j) !=
3313 el_act_dir2img(element, ACTION_DEFAULT, j))
3317 num_special_action++;
3322 return num_special_action;
3326 // ============================================================================
3328 // ----------------------------------------------------------------------------
3329 // initialize and start new game
3330 // ============================================================================
3332 #if DEBUG_INIT_PLAYER
3333 static void DebugPrintPlayerStatus(char *message)
3340 printf("%s:\n", message);
3342 for (i = 0; i < MAX_PLAYERS; i++)
3344 struct PlayerInfo *player = &stored_player[i];
3346 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3350 player->connected_locally,
3351 player->connected_network,
3354 if (local_player == player)
3355 printf(" (local player)");
3364 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3365 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3366 int fade_mask = REDRAW_FIELD;
3368 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3369 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3370 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3371 int initial_move_dir = MV_DOWN;
3374 // required here to update video display before fading (FIX THIS)
3375 DrawMaskedBorder(REDRAW_DOOR_2);
3377 if (!game.restart_level)
3378 CloseDoor(DOOR_CLOSE_1);
3380 SetGameStatus(GAME_MODE_PLAYING);
3382 if (level_editor_test_game)
3383 FadeSkipNextFadeOut();
3385 FadeSetEnterScreen();
3388 fade_mask = REDRAW_ALL;
3390 FadeLevelSoundsAndMusic();
3392 ExpireSoundLoops(TRUE);
3396 if (level_editor_test_game)
3397 FadeSkipNextFadeIn();
3399 // needed if different viewport properties defined for playing
3400 ChangeViewportPropertiesIfNeeded();
3404 DrawCompleteVideoDisplay();
3406 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3409 InitGameControlValues();
3411 // don't play tapes over network
3412 network_playing = (network.enabled && !tape.playing);
3414 for (i = 0; i < MAX_PLAYERS; i++)
3416 struct PlayerInfo *player = &stored_player[i];
3418 player->index_nr = i;
3419 player->index_bit = (1 << i);
3420 player->element_nr = EL_PLAYER_1 + i;
3422 player->present = FALSE;
3423 player->active = FALSE;
3424 player->mapped = FALSE;
3426 player->killed = FALSE;
3427 player->reanimated = FALSE;
3428 player->buried = FALSE;
3431 player->effective_action = 0;
3432 player->programmed_action = 0;
3433 player->snap_action = 0;
3435 player->mouse_action.lx = 0;
3436 player->mouse_action.ly = 0;
3437 player->mouse_action.button = 0;
3438 player->mouse_action.button_hint = 0;
3440 player->effective_mouse_action.lx = 0;
3441 player->effective_mouse_action.ly = 0;
3442 player->effective_mouse_action.button = 0;
3443 player->effective_mouse_action.button_hint = 0;
3445 for (j = 0; j < MAX_NUM_KEYS; j++)
3446 player->key[j] = FALSE;
3448 player->num_white_keys = 0;
3450 player->dynabomb_count = 0;
3451 player->dynabomb_size = 1;
3452 player->dynabombs_left = 0;
3453 player->dynabomb_xl = FALSE;
3455 player->MovDir = initial_move_dir;
3458 player->GfxDir = initial_move_dir;
3459 player->GfxAction = ACTION_DEFAULT;
3461 player->StepFrame = 0;
3463 player->initial_element = player->element_nr;
3464 player->artwork_element =
3465 (level.use_artwork_element[i] ? level.artwork_element[i] :
3466 player->element_nr);
3467 player->use_murphy = FALSE;
3469 player->block_last_field = FALSE; // initialized in InitPlayerField()
3470 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3472 player->gravity = level.initial_player_gravity[i];
3474 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3476 player->actual_frame_counter = 0;
3478 player->step_counter = 0;
3480 player->last_move_dir = initial_move_dir;
3482 player->is_active = FALSE;
3484 player->is_waiting = FALSE;
3485 player->is_moving = FALSE;
3486 player->is_auto_moving = FALSE;
3487 player->is_digging = FALSE;
3488 player->is_snapping = FALSE;
3489 player->is_collecting = FALSE;
3490 player->is_pushing = FALSE;
3491 player->is_switching = FALSE;
3492 player->is_dropping = FALSE;
3493 player->is_dropping_pressed = FALSE;
3495 player->is_bored = FALSE;
3496 player->is_sleeping = FALSE;
3498 player->was_waiting = TRUE;
3499 player->was_moving = FALSE;
3500 player->was_snapping = FALSE;
3501 player->was_dropping = FALSE;
3503 player->force_dropping = FALSE;
3505 player->frame_counter_bored = -1;
3506 player->frame_counter_sleeping = -1;
3508 player->anim_delay_counter = 0;
3509 player->post_delay_counter = 0;
3511 player->dir_waiting = initial_move_dir;
3512 player->action_waiting = ACTION_DEFAULT;
3513 player->last_action_waiting = ACTION_DEFAULT;
3514 player->special_action_bored = ACTION_DEFAULT;
3515 player->special_action_sleeping = ACTION_DEFAULT;
3517 player->switch_x = -1;
3518 player->switch_y = -1;
3520 player->drop_x = -1;
3521 player->drop_y = -1;
3523 player->show_envelope = 0;
3525 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3527 player->push_delay = -1; // initialized when pushing starts
3528 player->push_delay_value = game.initial_push_delay_value;
3530 player->drop_delay = 0;
3531 player->drop_pressed_delay = 0;
3533 player->last_jx = -1;
3534 player->last_jy = -1;
3538 player->shield_normal_time_left = 0;
3539 player->shield_deadly_time_left = 0;
3541 player->inventory_infinite_element = EL_UNDEFINED;
3542 player->inventory_size = 0;
3544 if (level.use_initial_inventory[i])
3546 for (j = 0; j < level.initial_inventory_size[i]; j++)
3548 int element = level.initial_inventory_content[i][j];
3549 int collect_count = element_info[element].collect_count_initial;
3552 if (!IS_CUSTOM_ELEMENT(element))
3555 if (collect_count == 0)
3556 player->inventory_infinite_element = element;
3558 for (k = 0; k < collect_count; k++)
3559 if (player->inventory_size < MAX_INVENTORY_SIZE)
3560 player->inventory_element[player->inventory_size++] = element;
3564 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3565 SnapField(player, 0, 0);
3567 map_player_action[i] = i;
3570 network_player_action_received = FALSE;
3572 // initial null action
3573 if (network_playing)
3574 SendToServer_MovePlayer(MV_NONE);
3579 TimeLeft = level.time;
3582 ScreenMovDir = MV_NONE;
3586 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3588 game.robot_wheel_x = -1;
3589 game.robot_wheel_y = -1;
3594 game.all_players_gone = FALSE;
3596 game.LevelSolved = FALSE;
3597 game.GameOver = FALSE;
3599 game.GamePlayed = !tape.playing;
3601 game.LevelSolved_GameWon = FALSE;
3602 game.LevelSolved_GameEnd = FALSE;
3603 game.LevelSolved_SaveTape = FALSE;
3604 game.LevelSolved_SaveScore = FALSE;
3606 game.LevelSolved_CountingTime = 0;
3607 game.LevelSolved_CountingScore = 0;
3608 game.LevelSolved_CountingHealth = 0;
3610 game.panel.active = TRUE;
3612 game.no_time_limit = (level.time == 0);
3614 game.yamyam_content_nr = 0;
3615 game.robot_wheel_active = FALSE;
3616 game.magic_wall_active = FALSE;
3617 game.magic_wall_time_left = 0;
3618 game.light_time_left = 0;
3619 game.timegate_time_left = 0;
3620 game.switchgate_pos = 0;
3621 game.wind_direction = level.wind_direction_initial;
3624 game.score_final = 0;
3626 game.health = MAX_HEALTH;
3627 game.health_final = MAX_HEALTH;
3629 game.gems_still_needed = level.gems_needed;
3630 game.sokoban_fields_still_needed = 0;
3631 game.sokoban_objects_still_needed = 0;
3632 game.lights_still_needed = 0;
3633 game.players_still_needed = 0;
3634 game.friends_still_needed = 0;
3636 game.lenses_time_left = 0;
3637 game.magnify_time_left = 0;
3639 game.ball_active = level.ball_active_initial;
3640 game.ball_content_nr = 0;
3642 game.explosions_delayed = TRUE;
3644 game.envelope_active = FALSE;
3646 for (i = 0; i < NUM_BELTS; i++)
3648 game.belt_dir[i] = MV_NONE;
3649 game.belt_dir_nr[i] = 3; // not moving, next moving left
3652 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3653 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3655 #if DEBUG_INIT_PLAYER
3656 DebugPrintPlayerStatus("Player status at level initialization");
3659 SCAN_PLAYFIELD(x, y)
3661 Feld[x][y] = Last[x][y] = level.field[x][y];
3662 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3663 ChangeDelay[x][y] = 0;
3664 ChangePage[x][y] = -1;
3665 CustomValue[x][y] = 0; // initialized in InitField()
3666 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3668 WasJustMoving[x][y] = 0;
3669 WasJustFalling[x][y] = 0;
3670 CheckCollision[x][y] = 0;
3671 CheckImpact[x][y] = 0;
3673 Pushed[x][y] = FALSE;
3675 ChangeCount[x][y] = 0;
3676 ChangeEvent[x][y] = -1;
3678 ExplodePhase[x][y] = 0;
3679 ExplodeDelay[x][y] = 0;
3680 ExplodeField[x][y] = EX_TYPE_NONE;
3682 RunnerVisit[x][y] = 0;
3683 PlayerVisit[x][y] = 0;
3686 GfxRandom[x][y] = INIT_GFX_RANDOM();
3687 GfxElement[x][y] = EL_UNDEFINED;
3688 GfxAction[x][y] = ACTION_DEFAULT;
3689 GfxDir[x][y] = MV_NONE;
3690 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3693 SCAN_PLAYFIELD(x, y)
3695 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3697 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3699 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3702 InitField(x, y, TRUE);
3704 ResetGfxAnimation(x, y);
3709 for (i = 0; i < MAX_PLAYERS; i++)
3711 struct PlayerInfo *player = &stored_player[i];
3713 // set number of special actions for bored and sleeping animation
3714 player->num_special_action_bored =
3715 get_num_special_action(player->artwork_element,
3716 ACTION_BORING_1, ACTION_BORING_LAST);
3717 player->num_special_action_sleeping =
3718 get_num_special_action(player->artwork_element,
3719 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3722 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3723 emulate_sb ? EMU_SOKOBAN :
3724 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3726 // initialize type of slippery elements
3727 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3729 if (!IS_CUSTOM_ELEMENT(i))
3731 // default: elements slip down either to the left or right randomly
3732 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3734 // SP style elements prefer to slip down on the left side
3735 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3736 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3738 // BD style elements prefer to slip down on the left side
3739 if (game.emulation == EMU_BOULDERDASH)
3740 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3744 // initialize explosion and ignition delay
3745 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3747 if (!IS_CUSTOM_ELEMENT(i))
3750 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3751 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3752 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3753 int last_phase = (num_phase + 1) * delay;
3754 int half_phase = (num_phase / 2) * delay;
3756 element_info[i].explosion_delay = last_phase - 1;
3757 element_info[i].ignition_delay = half_phase;
3759 if (i == EL_BLACK_ORB)
3760 element_info[i].ignition_delay = 1;
3764 // correct non-moving belts to start moving left
3765 for (i = 0; i < NUM_BELTS; i++)
3766 if (game.belt_dir[i] == MV_NONE)
3767 game.belt_dir_nr[i] = 3; // not moving, next moving left
3769 #if USE_NEW_PLAYER_ASSIGNMENTS
3770 // use preferred player also in local single-player mode
3771 if (!network.enabled && !game.team_mode)
3773 int new_index_nr = setup.network_player_nr;
3775 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3777 for (i = 0; i < MAX_PLAYERS; i++)
3778 stored_player[i].connected_locally = FALSE;
3780 stored_player[new_index_nr].connected_locally = TRUE;
3784 for (i = 0; i < MAX_PLAYERS; i++)
3786 stored_player[i].connected = FALSE;
3788 // in network game mode, the local player might not be the first player
3789 if (stored_player[i].connected_locally)
3790 local_player = &stored_player[i];
3793 if (!network.enabled)
3794 local_player->connected = TRUE;
3798 for (i = 0; i < MAX_PLAYERS; i++)
3799 stored_player[i].connected = tape.player_participates[i];
3801 else if (network.enabled)
3803 // add team mode players connected over the network (needed for correct
3804 // assignment of player figures from level to locally playing players)
3806 for (i = 0; i < MAX_PLAYERS; i++)
3807 if (stored_player[i].connected_network)
3808 stored_player[i].connected = TRUE;
3810 else if (game.team_mode)
3812 // try to guess locally connected team mode players (needed for correct
3813 // assignment of player figures from level to locally playing players)
3815 for (i = 0; i < MAX_PLAYERS; i++)
3816 if (setup.input[i].use_joystick ||
3817 setup.input[i].key.left != KSYM_UNDEFINED)
3818 stored_player[i].connected = TRUE;
3821 #if DEBUG_INIT_PLAYER
3822 DebugPrintPlayerStatus("Player status after level initialization");
3825 #if DEBUG_INIT_PLAYER
3827 printf("Reassigning players ...\n");
3830 // check if any connected player was not found in playfield
3831 for (i = 0; i < MAX_PLAYERS; i++)
3833 struct PlayerInfo *player = &stored_player[i];
3835 if (player->connected && !player->present)
3837 struct PlayerInfo *field_player = NULL;
3839 #if DEBUG_INIT_PLAYER
3841 printf("- looking for field player for player %d ...\n", i + 1);
3844 // assign first free player found that is present in the playfield
3846 // first try: look for unmapped playfield player that is not connected
3847 for (j = 0; j < MAX_PLAYERS; j++)
3848 if (field_player == NULL &&
3849 stored_player[j].present &&
3850 !stored_player[j].mapped &&
3851 !stored_player[j].connected)
3852 field_player = &stored_player[j];
3854 // second try: look for *any* unmapped playfield player
3855 for (j = 0; j < MAX_PLAYERS; j++)
3856 if (field_player == NULL &&
3857 stored_player[j].present &&
3858 !stored_player[j].mapped)
3859 field_player = &stored_player[j];
3861 if (field_player != NULL)
3863 int jx = field_player->jx, jy = field_player->jy;
3865 #if DEBUG_INIT_PLAYER
3867 printf("- found player %d\n", field_player->index_nr + 1);
3870 player->present = FALSE;
3871 player->active = FALSE;
3873 field_player->present = TRUE;
3874 field_player->active = TRUE;
3877 player->initial_element = field_player->initial_element;
3878 player->artwork_element = field_player->artwork_element;
3880 player->block_last_field = field_player->block_last_field;
3881 player->block_delay_adjustment = field_player->block_delay_adjustment;
3884 StorePlayer[jx][jy] = field_player->element_nr;
3886 field_player->jx = field_player->last_jx = jx;
3887 field_player->jy = field_player->last_jy = jy;
3889 if (local_player == player)
3890 local_player = field_player;
3892 map_player_action[field_player->index_nr] = i;
3894 field_player->mapped = TRUE;
3896 #if DEBUG_INIT_PLAYER
3898 printf("- map_player_action[%d] == %d\n",
3899 field_player->index_nr + 1, i + 1);
3904 if (player->connected && player->present)
3905 player->mapped = TRUE;
3908 #if DEBUG_INIT_PLAYER
3909 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
3914 // check if any connected player was not found in playfield
3915 for (i = 0; i < MAX_PLAYERS; i++)
3917 struct PlayerInfo *player = &stored_player[i];
3919 if (player->connected && !player->present)
3921 for (j = 0; j < MAX_PLAYERS; j++)
3923 struct PlayerInfo *field_player = &stored_player[j];
3924 int jx = field_player->jx, jy = field_player->jy;
3926 // assign first free player found that is present in the playfield
3927 if (field_player->present && !field_player->connected)
3929 player->present = TRUE;
3930 player->active = TRUE;
3932 field_player->present = FALSE;
3933 field_player->active = FALSE;
3935 player->initial_element = field_player->initial_element;
3936 player->artwork_element = field_player->artwork_element;
3938 player->block_last_field = field_player->block_last_field;
3939 player->block_delay_adjustment = field_player->block_delay_adjustment;
3941 StorePlayer[jx][jy] = player->element_nr;
3943 player->jx = player->last_jx = jx;
3944 player->jy = player->last_jy = jy;
3954 printf("::: local_player->present == %d\n", local_player->present);
3957 // set focus to local player for network games, else to all players
3958 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
3959 game.centered_player_nr_next = game.centered_player_nr;
3960 game.set_centered_player = FALSE;
3961 game.set_centered_player_wrap = FALSE;
3963 if (network_playing && tape.recording)
3965 // store client dependent player focus when recording network games
3966 tape.centered_player_nr_next = game.centered_player_nr_next;
3967 tape.set_centered_player = TRUE;
3972 // when playing a tape, eliminate all players who do not participate
3974 #if USE_NEW_PLAYER_ASSIGNMENTS
3976 if (!game.team_mode)
3978 for (i = 0; i < MAX_PLAYERS; i++)
3980 if (stored_player[i].active &&
3981 !tape.player_participates[map_player_action[i]])
3983 struct PlayerInfo *player = &stored_player[i];
3984 int jx = player->jx, jy = player->jy;
3986 #if DEBUG_INIT_PLAYER
3988 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
3991 player->active = FALSE;
3992 StorePlayer[jx][jy] = 0;
3993 Feld[jx][jy] = EL_EMPTY;
4000 for (i = 0; i < MAX_PLAYERS; i++)
4002 if (stored_player[i].active &&
4003 !tape.player_participates[i])
4005 struct PlayerInfo *player = &stored_player[i];
4006 int jx = player->jx, jy = player->jy;
4008 player->active = FALSE;
4009 StorePlayer[jx][jy] = 0;
4010 Feld[jx][jy] = EL_EMPTY;
4015 else if (!network.enabled && !game.team_mode) // && !tape.playing
4017 // when in single player mode, eliminate all but the local player
4019 for (i = 0; i < MAX_PLAYERS; i++)
4021 struct PlayerInfo *player = &stored_player[i];
4023 if (player->active && player != local_player)
4025 int jx = player->jx, jy = player->jy;
4027 player->active = FALSE;
4028 player->present = FALSE;
4030 StorePlayer[jx][jy] = 0;
4031 Feld[jx][jy] = EL_EMPTY;
4036 for (i = 0; i < MAX_PLAYERS; i++)
4037 if (stored_player[i].active)
4038 game.players_still_needed++;
4040 if (level.solved_by_one_player)
4041 game.players_still_needed = 1;
4043 // when recording the game, store which players take part in the game
4046 #if USE_NEW_PLAYER_ASSIGNMENTS
4047 for (i = 0; i < MAX_PLAYERS; i++)
4048 if (stored_player[i].connected)
4049 tape.player_participates[i] = TRUE;
4051 for (i = 0; i < MAX_PLAYERS; i++)
4052 if (stored_player[i].active)
4053 tape.player_participates[i] = TRUE;
4057 #if DEBUG_INIT_PLAYER
4058 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4061 if (BorderElement == EL_EMPTY)
4064 SBX_Right = lev_fieldx - SCR_FIELDX;
4066 SBY_Lower = lev_fieldy - SCR_FIELDY;
4071 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4073 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4076 if (full_lev_fieldx <= SCR_FIELDX)
4077 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4078 if (full_lev_fieldy <= SCR_FIELDY)
4079 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4081 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4083 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4086 // if local player not found, look for custom element that might create
4087 // the player (make some assumptions about the right custom element)
4088 if (!local_player->present)
4090 int start_x = 0, start_y = 0;
4091 int found_rating = 0;
4092 int found_element = EL_UNDEFINED;
4093 int player_nr = local_player->index_nr;
4095 SCAN_PLAYFIELD(x, y)
4097 int element = Feld[x][y];
4102 if (level.use_start_element[player_nr] &&
4103 level.start_element[player_nr] == element &&
4110 found_element = element;
4113 if (!IS_CUSTOM_ELEMENT(element))
4116 if (CAN_CHANGE(element))
4118 for (i = 0; i < element_info[element].num_change_pages; i++)
4120 // check for player created from custom element as single target
4121 content = element_info[element].change_page[i].target_element;
4122 is_player = ELEM_IS_PLAYER(content);
4124 if (is_player && (found_rating < 3 ||
4125 (found_rating == 3 && element < found_element)))
4131 found_element = element;
4136 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4138 // check for player created from custom element as explosion content
4139 content = element_info[element].content.e[xx][yy];
4140 is_player = ELEM_IS_PLAYER(content);
4142 if (is_player && (found_rating < 2 ||
4143 (found_rating == 2 && element < found_element)))
4145 start_x = x + xx - 1;
4146 start_y = y + yy - 1;
4149 found_element = element;
4152 if (!CAN_CHANGE(element))
4155 for (i = 0; i < element_info[element].num_change_pages; i++)
4157 // check for player created from custom element as extended target
4159 element_info[element].change_page[i].target_content.e[xx][yy];
4161 is_player = ELEM_IS_PLAYER(content);
4163 if (is_player && (found_rating < 1 ||
4164 (found_rating == 1 && element < found_element)))
4166 start_x = x + xx - 1;
4167 start_y = y + yy - 1;
4170 found_element = element;
4176 scroll_x = SCROLL_POSITION_X(start_x);
4177 scroll_y = SCROLL_POSITION_Y(start_y);
4181 scroll_x = SCROLL_POSITION_X(local_player->jx);
4182 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4185 // !!! FIX THIS (START) !!!
4186 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4188 InitGameEngine_EM();
4190 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4192 InitGameEngine_SP();
4194 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4196 InitGameEngine_MM();
4200 DrawLevel(REDRAW_FIELD);
4203 // after drawing the level, correct some elements
4204 if (game.timegate_time_left == 0)
4205 CloseAllOpenTimegates();
4208 // blit playfield from scroll buffer to normal back buffer for fading in
4209 BlitScreenToBitmap(backbuffer);
4210 // !!! FIX THIS (END) !!!
4212 DrawMaskedBorder(fade_mask);
4217 // full screen redraw is required at this point in the following cases:
4218 // - special editor door undrawn when game was started from level editor
4219 // - drawing area (playfield) was changed and has to be removed completely
4220 redraw_mask = REDRAW_ALL;
4224 if (!game.restart_level)
4226 // copy default game door content to main double buffer
4228 // !!! CHECK AGAIN !!!
4229 SetPanelBackground();
4230 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4231 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4234 SetPanelBackground();
4235 SetDrawBackgroundMask(REDRAW_DOOR_1);
4237 UpdateAndDisplayGameControlValues();
4239 if (!game.restart_level)
4245 CreateGameButtons();
4250 // copy actual game door content to door double buffer for OpenDoor()
4251 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4253 OpenDoor(DOOR_OPEN_ALL);
4255 KeyboardAutoRepeatOffUnlessAutoplay();
4257 #if DEBUG_INIT_PLAYER
4258 DebugPrintPlayerStatus("Player status (final)");
4267 if (!game.restart_level && !tape.playing)
4269 LevelStats_incPlayed(level_nr);
4271 SaveLevelSetup_SeriesInfo();
4274 game.restart_level = FALSE;
4275 game.restart_game_message = NULL;
4276 game.request_active = FALSE;
4278 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4279 InitGameActions_MM();
4281 SaveEngineSnapshotToListInitial();
4283 if (!game.restart_level)
4285 PlaySound(SND_GAME_STARTING);
4287 if (setup.sound_music)
4292 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4293 int actual_player_x, int actual_player_y)
4295 // this is used for non-R'n'D game engines to update certain engine values
4297 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4299 actual_player_x = correctLevelPosX_EM(actual_player_x);
4300 actual_player_y = correctLevelPosY_EM(actual_player_y);
4303 // needed to determine if sounds are played within the visible screen area
4304 scroll_x = actual_scroll_x;
4305 scroll_y = actual_scroll_y;
4307 // needed to get player position for "follow finger" playing input method
4308 local_player->jx = actual_player_x;
4309 local_player->jy = actual_player_y;
4312 void InitMovDir(int x, int y)
4314 int i, element = Feld[x][y];
4315 static int xy[4][2] =
4322 static int direction[3][4] =
4324 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4325 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4326 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4335 Feld[x][y] = EL_BUG;
4336 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4339 case EL_SPACESHIP_RIGHT:
4340 case EL_SPACESHIP_UP:
4341 case EL_SPACESHIP_LEFT:
4342 case EL_SPACESHIP_DOWN:
4343 Feld[x][y] = EL_SPACESHIP;
4344 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4347 case EL_BD_BUTTERFLY_RIGHT:
4348 case EL_BD_BUTTERFLY_UP:
4349 case EL_BD_BUTTERFLY_LEFT:
4350 case EL_BD_BUTTERFLY_DOWN:
4351 Feld[x][y] = EL_BD_BUTTERFLY;
4352 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4355 case EL_BD_FIREFLY_RIGHT:
4356 case EL_BD_FIREFLY_UP:
4357 case EL_BD_FIREFLY_LEFT:
4358 case EL_BD_FIREFLY_DOWN:
4359 Feld[x][y] = EL_BD_FIREFLY;
4360 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4363 case EL_PACMAN_RIGHT:
4365 case EL_PACMAN_LEFT:
4366 case EL_PACMAN_DOWN:
4367 Feld[x][y] = EL_PACMAN;
4368 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4371 case EL_YAMYAM_LEFT:
4372 case EL_YAMYAM_RIGHT:
4374 case EL_YAMYAM_DOWN:
4375 Feld[x][y] = EL_YAMYAM;
4376 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4379 case EL_SP_SNIKSNAK:
4380 MovDir[x][y] = MV_UP;
4383 case EL_SP_ELECTRON:
4384 MovDir[x][y] = MV_LEFT;
4391 Feld[x][y] = EL_MOLE;
4392 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4396 if (IS_CUSTOM_ELEMENT(element))
4398 struct ElementInfo *ei = &element_info[element];
4399 int move_direction_initial = ei->move_direction_initial;
4400 int move_pattern = ei->move_pattern;
4402 if (move_direction_initial == MV_START_PREVIOUS)
4404 if (MovDir[x][y] != MV_NONE)
4407 move_direction_initial = MV_START_AUTOMATIC;
4410 if (move_direction_initial == MV_START_RANDOM)
4411 MovDir[x][y] = 1 << RND(4);
4412 else if (move_direction_initial & MV_ANY_DIRECTION)
4413 MovDir[x][y] = move_direction_initial;
4414 else if (move_pattern == MV_ALL_DIRECTIONS ||
4415 move_pattern == MV_TURNING_LEFT ||
4416 move_pattern == MV_TURNING_RIGHT ||
4417 move_pattern == MV_TURNING_LEFT_RIGHT ||
4418 move_pattern == MV_TURNING_RIGHT_LEFT ||
4419 move_pattern == MV_TURNING_RANDOM)
4420 MovDir[x][y] = 1 << RND(4);
4421 else if (move_pattern == MV_HORIZONTAL)
4422 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4423 else if (move_pattern == MV_VERTICAL)
4424 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4425 else if (move_pattern & MV_ANY_DIRECTION)
4426 MovDir[x][y] = element_info[element].move_pattern;
4427 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4428 move_pattern == MV_ALONG_RIGHT_SIDE)
4430 // use random direction as default start direction
4431 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4432 MovDir[x][y] = 1 << RND(4);
4434 for (i = 0; i < NUM_DIRECTIONS; i++)
4436 int x1 = x + xy[i][0];
4437 int y1 = y + xy[i][1];
4439 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4441 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4442 MovDir[x][y] = direction[0][i];
4444 MovDir[x][y] = direction[1][i];
4453 MovDir[x][y] = 1 << RND(4);
4455 if (element != EL_BUG &&
4456 element != EL_SPACESHIP &&
4457 element != EL_BD_BUTTERFLY &&
4458 element != EL_BD_FIREFLY)
4461 for (i = 0; i < NUM_DIRECTIONS; i++)
4463 int x1 = x + xy[i][0];
4464 int y1 = y + xy[i][1];
4466 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4468 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4470 MovDir[x][y] = direction[0][i];
4473 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4474 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4476 MovDir[x][y] = direction[1][i];
4485 GfxDir[x][y] = MovDir[x][y];
4488 void InitAmoebaNr(int x, int y)
4491 int group_nr = AmoebeNachbarNr(x, y);
4495 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4497 if (AmoebaCnt[i] == 0)
4505 AmoebaNr[x][y] = group_nr;
4506 AmoebaCnt[group_nr]++;
4507 AmoebaCnt2[group_nr]++;
4510 static void LevelSolved(void)
4512 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4513 game.players_still_needed > 0)
4516 game.LevelSolved = TRUE;
4517 game.GameOver = TRUE;
4519 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4520 game_em.lev->score :
4521 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4524 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4525 MM_HEALTH(game_mm.laser_overload_value) :
4528 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4529 game.LevelSolved_CountingScore = game.score_final;
4530 game.LevelSolved_CountingHealth = game.health_final;
4535 static int time_count_steps;
4536 static int time, time_final;
4537 static int score, score_final;
4538 static int health, health_final;
4539 static int game_over_delay_1 = 0;
4540 static int game_over_delay_2 = 0;
4541 static int game_over_delay_3 = 0;
4542 int game_over_delay_value_1 = 50;
4543 int game_over_delay_value_2 = 25;
4544 int game_over_delay_value_3 = 50;
4546 if (!game.LevelSolved_GameWon)
4550 // do not start end game actions before the player stops moving (to exit)
4551 if (local_player->active && local_player->MovPos)
4554 game.LevelSolved_GameWon = TRUE;
4555 game.LevelSolved_SaveTape = tape.recording;
4556 game.LevelSolved_SaveScore = !tape.playing;
4560 LevelStats_incSolved(level_nr);
4562 SaveLevelSetup_SeriesInfo();
4565 if (tape.auto_play) // tape might already be stopped here
4566 tape.auto_play_level_solved = TRUE;
4570 game_over_delay_1 = 0;
4571 game_over_delay_2 = 0;
4572 game_over_delay_3 = game_over_delay_value_3;
4574 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4575 score = score_final = game.score_final;
4576 health = health_final = game.health_final;
4578 if (level.score[SC_TIME_BONUS] > 0)
4583 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4585 else if (game.no_time_limit && TimePlayed < 999)
4588 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4591 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4593 game_over_delay_1 = game_over_delay_value_1;
4595 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4598 score_final += health * level.score[SC_TIME_BONUS];
4600 game_over_delay_2 = game_over_delay_value_2;
4603 game.score_final = score_final;
4604 game.health_final = health_final;
4607 if (level_editor_test_game)
4610 score = score_final;
4612 game.LevelSolved_CountingTime = time;
4613 game.LevelSolved_CountingScore = score;
4615 game_panel_controls[GAME_PANEL_TIME].value = time;
4616 game_panel_controls[GAME_PANEL_SCORE].value = score;
4618 DisplayGameControlValues();
4621 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4623 // check if last player has left the level
4624 if (game.exit_x >= 0 &&
4627 int x = game.exit_x;
4628 int y = game.exit_y;
4629 int element = Feld[x][y];
4631 // close exit door after last player
4632 if ((game.all_players_gone &&
4633 (element == EL_EXIT_OPEN ||
4634 element == EL_SP_EXIT_OPEN ||
4635 element == EL_STEEL_EXIT_OPEN)) ||
4636 element == EL_EM_EXIT_OPEN ||
4637 element == EL_EM_STEEL_EXIT_OPEN)
4641 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4642 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4643 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4644 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4645 EL_EM_STEEL_EXIT_CLOSING);
4647 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4650 // player disappears
4651 DrawLevelField(x, y);
4654 for (i = 0; i < MAX_PLAYERS; i++)
4656 struct PlayerInfo *player = &stored_player[i];
4658 if (player->present)
4660 RemovePlayer(player);
4662 // player disappears
4663 DrawLevelField(player->jx, player->jy);
4668 PlaySound(SND_GAME_WINNING);
4671 if (game_over_delay_1 > 0)
4673 game_over_delay_1--;
4678 if (time != time_final)
4680 int time_to_go = ABS(time_final - time);
4681 int time_count_dir = (time < time_final ? +1 : -1);
4683 if (time_to_go < time_count_steps)
4684 time_count_steps = 1;
4686 time += time_count_steps * time_count_dir;
4687 score += time_count_steps * level.score[SC_TIME_BONUS];
4689 game.LevelSolved_CountingTime = time;
4690 game.LevelSolved_CountingScore = score;
4692 game_panel_controls[GAME_PANEL_TIME].value = time;
4693 game_panel_controls[GAME_PANEL_SCORE].value = score;
4695 DisplayGameControlValues();
4697 if (time == time_final)
4698 StopSound(SND_GAME_LEVELTIME_BONUS);
4699 else if (setup.sound_loops)
4700 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4702 PlaySound(SND_GAME_LEVELTIME_BONUS);
4707 if (game_over_delay_2 > 0)
4709 game_over_delay_2--;
4714 if (health != health_final)
4716 int health_count_dir = (health < health_final ? +1 : -1);
4718 health += health_count_dir;
4719 score += level.score[SC_TIME_BONUS];
4721 game.LevelSolved_CountingHealth = health;
4722 game.LevelSolved_CountingScore = score;
4724 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4725 game_panel_controls[GAME_PANEL_SCORE].value = score;
4727 DisplayGameControlValues();
4729 if (health == health_final)
4730 StopSound(SND_GAME_LEVELTIME_BONUS);
4731 else if (setup.sound_loops)
4732 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4734 PlaySound(SND_GAME_LEVELTIME_BONUS);
4739 game.panel.active = FALSE;
4741 if (game_over_delay_3 > 0)
4743 game_over_delay_3--;
4753 // used instead of "level_nr" (needed for network games)
4754 int last_level_nr = levelset.level_nr;
4757 game.LevelSolved_GameEnd = TRUE;
4759 if (game.LevelSolved_SaveTape)
4761 // make sure that request dialog to save tape does not open door again
4762 if (!global.use_envelope_request)
4763 CloseDoor(DOOR_CLOSE_1);
4765 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4768 // if no tape is to be saved, close both doors simultaneously
4769 CloseDoor(DOOR_CLOSE_ALL);
4771 if (level_editor_test_game)
4773 SetGameStatus(GAME_MODE_MAIN);
4780 if (!game.LevelSolved_SaveScore)
4782 SetGameStatus(GAME_MODE_MAIN);
4789 if (level_nr == leveldir_current->handicap_level)
4791 leveldir_current->handicap_level++;
4793 SaveLevelSetup_SeriesInfo();
4796 if (setup.increment_levels &&
4797 level_nr < leveldir_current->last_level &&
4800 level_nr++; // advance to next level
4801 TapeErase(); // start with empty tape
4803 if (setup.auto_play_next_level)
4805 LoadLevel(level_nr);
4807 SaveLevelSetup_SeriesInfo();
4811 hi_pos = NewHiScore(last_level_nr);
4813 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4815 SetGameStatus(GAME_MODE_SCORES);
4817 DrawHallOfFame(last_level_nr, hi_pos);
4819 else if (setup.auto_play_next_level && setup.increment_levels &&
4820 last_level_nr < leveldir_current->last_level &&
4823 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4827 SetGameStatus(GAME_MODE_MAIN);
4833 int NewHiScore(int level_nr)
4837 boolean one_score_entry_per_name = !program.many_scores_per_name;
4839 LoadScore(level_nr);
4841 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4842 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4845 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4847 if (game.score_final > highscore[k].Score)
4849 // player has made it to the hall of fame
4851 if (k < MAX_SCORE_ENTRIES - 1)
4853 int m = MAX_SCORE_ENTRIES - 1;
4855 if (one_score_entry_per_name)
4857 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4858 if (strEqual(setup.player_name, highscore[l].Name))
4861 if (m == k) // player's new highscore overwrites his old one
4865 for (l = m; l > k; l--)
4867 strcpy(highscore[l].Name, highscore[l - 1].Name);
4868 highscore[l].Score = highscore[l - 1].Score;
4874 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4875 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4876 highscore[k].Score = game.score_final;
4881 else if (one_score_entry_per_name &&
4882 !strncmp(setup.player_name, highscore[k].Name,
4883 MAX_PLAYER_NAME_LEN))
4884 break; // player already there with a higher score
4888 SaveScore(level_nr);
4893 static int getElementMoveStepsizeExt(int x, int y, int direction)
4895 int element = Feld[x][y];
4896 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4897 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4898 int horiz_move = (dx != 0);
4899 int sign = (horiz_move ? dx : dy);
4900 int step = sign * element_info[element].move_stepsize;
4902 // special values for move stepsize for spring and things on conveyor belt
4905 if (CAN_FALL(element) &&
4906 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
4907 step = sign * MOVE_STEPSIZE_NORMAL / 2;
4908 else if (element == EL_SPRING)
4909 step = sign * MOVE_STEPSIZE_NORMAL * 2;
4915 static int getElementMoveStepsize(int x, int y)
4917 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
4920 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
4922 if (player->GfxAction != action || player->GfxDir != dir)
4924 player->GfxAction = action;
4925 player->GfxDir = dir;
4927 player->StepFrame = 0;
4931 static void ResetGfxFrame(int x, int y)
4933 // profiling showed that "autotest" spends 10~20% of its time in this function
4934 if (DrawingDeactivatedField())
4937 int element = Feld[x][y];
4938 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
4940 if (graphic_info[graphic].anim_global_sync)
4941 GfxFrame[x][y] = FrameCounter;
4942 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
4943 GfxFrame[x][y] = CustomValue[x][y];
4944 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
4945 GfxFrame[x][y] = element_info[element].collect_score;
4946 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
4947 GfxFrame[x][y] = ChangeDelay[x][y];
4950 static void ResetGfxAnimation(int x, int y)
4952 GfxAction[x][y] = ACTION_DEFAULT;
4953 GfxDir[x][y] = MovDir[x][y];
4956 ResetGfxFrame(x, y);
4959 static void ResetRandomAnimationValue(int x, int y)
4961 GfxRandom[x][y] = INIT_GFX_RANDOM();
4964 static void InitMovingField(int x, int y, int direction)
4966 int element = Feld[x][y];
4967 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4968 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4971 boolean is_moving_before, is_moving_after;
4973 // check if element was/is moving or being moved before/after mode change
4974 is_moving_before = (WasJustMoving[x][y] != 0);
4975 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
4977 // reset animation only for moving elements which change direction of moving
4978 // or which just started or stopped moving
4979 // (else CEs with property "can move" / "not moving" are reset each frame)
4980 if (is_moving_before != is_moving_after ||
4981 direction != MovDir[x][y])
4982 ResetGfxAnimation(x, y);
4984 MovDir[x][y] = direction;
4985 GfxDir[x][y] = direction;
4987 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
4988 direction == MV_DOWN && CAN_FALL(element) ?
4989 ACTION_FALLING : ACTION_MOVING);
4991 // this is needed for CEs with property "can move" / "not moving"
4993 if (is_moving_after)
4995 if (Feld[newx][newy] == EL_EMPTY)
4996 Feld[newx][newy] = EL_BLOCKED;
4998 MovDir[newx][newy] = MovDir[x][y];
5000 CustomValue[newx][newy] = CustomValue[x][y];
5002 GfxFrame[newx][newy] = GfxFrame[x][y];
5003 GfxRandom[newx][newy] = GfxRandom[x][y];
5004 GfxAction[newx][newy] = GfxAction[x][y];
5005 GfxDir[newx][newy] = GfxDir[x][y];
5009 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5011 int direction = MovDir[x][y];
5012 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5013 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5019 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5021 int oldx = x, oldy = y;
5022 int direction = MovDir[x][y];
5024 if (direction == MV_LEFT)
5026 else if (direction == MV_RIGHT)
5028 else if (direction == MV_UP)
5030 else if (direction == MV_DOWN)
5033 *comes_from_x = oldx;
5034 *comes_from_y = oldy;
5037 static int MovingOrBlocked2Element(int x, int y)
5039 int element = Feld[x][y];
5041 if (element == EL_BLOCKED)
5045 Blocked2Moving(x, y, &oldx, &oldy);
5046 return Feld[oldx][oldy];
5052 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5054 // like MovingOrBlocked2Element(), but if element is moving
5055 // and (x,y) is the field the moving element is just leaving,
5056 // return EL_BLOCKED instead of the element value
5057 int element = Feld[x][y];
5059 if (IS_MOVING(x, y))
5061 if (element == EL_BLOCKED)
5065 Blocked2Moving(x, y, &oldx, &oldy);
5066 return Feld[oldx][oldy];
5075 static void RemoveField(int x, int y)
5077 Feld[x][y] = EL_EMPTY;
5083 CustomValue[x][y] = 0;
5086 ChangeDelay[x][y] = 0;
5087 ChangePage[x][y] = -1;
5088 Pushed[x][y] = FALSE;
5090 GfxElement[x][y] = EL_UNDEFINED;
5091 GfxAction[x][y] = ACTION_DEFAULT;
5092 GfxDir[x][y] = MV_NONE;
5095 static void RemoveMovingField(int x, int y)
5097 int oldx = x, oldy = y, newx = x, newy = y;
5098 int element = Feld[x][y];
5099 int next_element = EL_UNDEFINED;
5101 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5104 if (IS_MOVING(x, y))
5106 Moving2Blocked(x, y, &newx, &newy);
5108 if (Feld[newx][newy] != EL_BLOCKED)
5110 // element is moving, but target field is not free (blocked), but
5111 // already occupied by something different (example: acid pool);
5112 // in this case, only remove the moving field, but not the target
5114 RemoveField(oldx, oldy);
5116 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5118 TEST_DrawLevelField(oldx, oldy);
5123 else if (element == EL_BLOCKED)
5125 Blocked2Moving(x, y, &oldx, &oldy);
5126 if (!IS_MOVING(oldx, oldy))
5130 if (element == EL_BLOCKED &&
5131 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5132 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5133 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5134 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5135 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5136 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5137 next_element = get_next_element(Feld[oldx][oldy]);
5139 RemoveField(oldx, oldy);
5140 RemoveField(newx, newy);
5142 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5144 if (next_element != EL_UNDEFINED)
5145 Feld[oldx][oldy] = next_element;
5147 TEST_DrawLevelField(oldx, oldy);
5148 TEST_DrawLevelField(newx, newy);
5151 void DrawDynamite(int x, int y)
5153 int sx = SCREENX(x), sy = SCREENY(y);
5154 int graphic = el2img(Feld[x][y]);
5157 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5160 if (IS_WALKABLE_INSIDE(Back[x][y]))
5164 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5165 else if (Store[x][y])
5166 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5168 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5170 if (Back[x][y] || Store[x][y])
5171 DrawGraphicThruMask(sx, sy, graphic, frame);
5173 DrawGraphic(sx, sy, graphic, frame);
5176 static void CheckDynamite(int x, int y)
5178 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5182 if (MovDelay[x][y] != 0)
5185 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5191 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5196 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5198 boolean num_checked_players = 0;
5201 for (i = 0; i < MAX_PLAYERS; i++)
5203 if (stored_player[i].active)
5205 int sx = stored_player[i].jx;
5206 int sy = stored_player[i].jy;
5208 if (num_checked_players == 0)
5215 *sx1 = MIN(*sx1, sx);
5216 *sy1 = MIN(*sy1, sy);
5217 *sx2 = MAX(*sx2, sx);
5218 *sy2 = MAX(*sy2, sy);
5221 num_checked_players++;
5226 static boolean checkIfAllPlayersFitToScreen_RND(void)
5228 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5230 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5232 return (sx2 - sx1 < SCR_FIELDX &&
5233 sy2 - sy1 < SCR_FIELDY);
5236 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5238 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5240 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5242 *sx = (sx1 + sx2) / 2;
5243 *sy = (sy1 + sy2) / 2;
5246 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5247 boolean center_screen, boolean quick_relocation)
5249 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5250 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5251 boolean no_delay = (tape.warp_forward);
5252 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5253 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5254 int new_scroll_x, new_scroll_y;
5256 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5258 // case 1: quick relocation inside visible screen (without scrolling)
5265 if (!level.shifted_relocation || center_screen)
5267 // relocation _with_ centering of screen
5269 new_scroll_x = SCROLL_POSITION_X(x);
5270 new_scroll_y = SCROLL_POSITION_Y(y);
5274 // relocation _without_ centering of screen
5276 int center_scroll_x = SCROLL_POSITION_X(old_x);
5277 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5278 int offset_x = x + (scroll_x - center_scroll_x);
5279 int offset_y = y + (scroll_y - center_scroll_y);
5281 // for new screen position, apply previous offset to center position
5282 new_scroll_x = SCROLL_POSITION_X(offset_x);
5283 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5286 if (quick_relocation)
5288 // case 2: quick relocation (redraw without visible scrolling)
5290 scroll_x = new_scroll_x;
5291 scroll_y = new_scroll_y;
5298 // case 3: visible relocation (with scrolling to new position)
5300 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5302 SetVideoFrameDelay(wait_delay_value);
5304 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5306 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5307 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5309 if (dx == 0 && dy == 0) // no scrolling needed at all
5315 // set values for horizontal/vertical screen scrolling (half tile size)
5316 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5317 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5318 int pos_x = dx * TILEX / 2;
5319 int pos_y = dy * TILEY / 2;
5320 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5321 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5323 ScrollLevel(dx, dy);
5326 // scroll in two steps of half tile size to make things smoother
5327 BlitScreenToBitmapExt_RND(window, fx, fy);
5329 // scroll second step to align at full tile size
5330 BlitScreenToBitmap(window);
5336 SetVideoFrameDelay(frame_delay_value_old);
5339 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5341 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5342 int player_nr = GET_PLAYER_NR(el_player);
5343 struct PlayerInfo *player = &stored_player[player_nr];
5344 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5345 boolean no_delay = (tape.warp_forward);
5346 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5347 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5348 int old_jx = player->jx;
5349 int old_jy = player->jy;
5350 int old_element = Feld[old_jx][old_jy];
5351 int element = Feld[jx][jy];
5352 boolean player_relocated = (old_jx != jx || old_jy != jy);
5354 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5355 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5356 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5357 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5358 int leave_side_horiz = move_dir_horiz;
5359 int leave_side_vert = move_dir_vert;
5360 int enter_side = enter_side_horiz | enter_side_vert;
5361 int leave_side = leave_side_horiz | leave_side_vert;
5363 if (player->buried) // do not reanimate dead player
5366 if (!player_relocated) // no need to relocate the player
5369 if (IS_PLAYER(jx, jy)) // player already placed at new position
5371 RemoveField(jx, jy); // temporarily remove newly placed player
5372 DrawLevelField(jx, jy);
5375 if (player->present)
5377 while (player->MovPos)
5379 ScrollPlayer(player, SCROLL_GO_ON);
5380 ScrollScreen(NULL, SCROLL_GO_ON);
5382 AdvanceFrameAndPlayerCounters(player->index_nr);
5386 BackToFront_WithFrameDelay(wait_delay_value);
5389 DrawPlayer(player); // needed here only to cleanup last field
5390 DrawLevelField(player->jx, player->jy); // remove player graphic
5392 player->is_moving = FALSE;
5395 if (IS_CUSTOM_ELEMENT(old_element))
5396 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5398 player->index_bit, leave_side);
5400 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5402 player->index_bit, leave_side);
5404 Feld[jx][jy] = el_player;
5405 InitPlayerField(jx, jy, el_player, TRUE);
5407 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5408 possible that the relocation target field did not contain a player element,
5409 but a walkable element, to which the new player was relocated -- in this
5410 case, restore that (already initialized!) element on the player field */
5411 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5413 Feld[jx][jy] = element; // restore previously existing element
5416 // only visually relocate centered player
5417 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5418 FALSE, level.instant_relocation);
5420 TestIfPlayerTouchesBadThing(jx, jy);
5421 TestIfPlayerTouchesCustomElement(jx, jy);
5423 if (IS_CUSTOM_ELEMENT(element))
5424 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5425 player->index_bit, enter_side);
5427 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5428 player->index_bit, enter_side);
5430 if (player->is_switching)
5432 /* ensure that relocation while still switching an element does not cause
5433 a new element to be treated as also switched directly after relocation
5434 (this is important for teleporter switches that teleport the player to
5435 a place where another teleporter switch is in the same direction, which
5436 would then incorrectly be treated as immediately switched before the
5437 direction key that caused the switch was released) */
5439 player->switch_x += jx - old_jx;
5440 player->switch_y += jy - old_jy;
5444 static void Explode(int ex, int ey, int phase, int mode)
5450 // !!! eliminate this variable !!!
5451 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5453 if (game.explosions_delayed)
5455 ExplodeField[ex][ey] = mode;
5459 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5461 int center_element = Feld[ex][ey];
5462 int artwork_element, explosion_element; // set these values later
5464 // remove things displayed in background while burning dynamite
5465 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5468 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5470 // put moving element to center field (and let it explode there)
5471 center_element = MovingOrBlocked2Element(ex, ey);
5472 RemoveMovingField(ex, ey);
5473 Feld[ex][ey] = center_element;
5476 // now "center_element" is finally determined -- set related values now
5477 artwork_element = center_element; // for custom player artwork
5478 explosion_element = center_element; // for custom player artwork
5480 if (IS_PLAYER(ex, ey))
5482 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5484 artwork_element = stored_player[player_nr].artwork_element;
5486 if (level.use_explosion_element[player_nr])
5488 explosion_element = level.explosion_element[player_nr];
5489 artwork_element = explosion_element;
5493 if (mode == EX_TYPE_NORMAL ||
5494 mode == EX_TYPE_CENTER ||
5495 mode == EX_TYPE_CROSS)
5496 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5498 last_phase = element_info[explosion_element].explosion_delay + 1;
5500 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5502 int xx = x - ex + 1;
5503 int yy = y - ey + 1;
5506 if (!IN_LEV_FIELD(x, y) ||
5507 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5508 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5511 element = Feld[x][y];
5513 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5515 element = MovingOrBlocked2Element(x, y);
5517 if (!IS_EXPLOSION_PROOF(element))
5518 RemoveMovingField(x, y);
5521 // indestructible elements can only explode in center (but not flames)
5522 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5523 mode == EX_TYPE_BORDER)) ||
5524 element == EL_FLAMES)
5527 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5528 behaviour, for example when touching a yamyam that explodes to rocks
5529 with active deadly shield, a rock is created under the player !!! */
5530 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5532 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5533 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5534 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5536 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5539 if (IS_ACTIVE_BOMB(element))
5541 // re-activate things under the bomb like gate or penguin
5542 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5549 // save walkable background elements while explosion on same tile
5550 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5551 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5552 Back[x][y] = element;
5554 // ignite explodable elements reached by other explosion
5555 if (element == EL_EXPLOSION)
5556 element = Store2[x][y];
5558 if (AmoebaNr[x][y] &&
5559 (element == EL_AMOEBA_FULL ||
5560 element == EL_BD_AMOEBA ||
5561 element == EL_AMOEBA_GROWING))
5563 AmoebaCnt[AmoebaNr[x][y]]--;
5564 AmoebaCnt2[AmoebaNr[x][y]]--;
5569 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5571 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5573 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5575 if (PLAYERINFO(ex, ey)->use_murphy)
5576 Store[x][y] = EL_EMPTY;
5579 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5580 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5581 else if (ELEM_IS_PLAYER(center_element))
5582 Store[x][y] = EL_EMPTY;
5583 else if (center_element == EL_YAMYAM)
5584 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5585 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5586 Store[x][y] = element_info[center_element].content.e[xx][yy];
5588 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5589 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5590 // otherwise) -- FIX THIS !!!
5591 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5592 Store[x][y] = element_info[element].content.e[1][1];
5594 else if (!CAN_EXPLODE(element))
5595 Store[x][y] = element_info[element].content.e[1][1];
5598 Store[x][y] = EL_EMPTY;
5600 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5601 center_element == EL_AMOEBA_TO_DIAMOND)
5602 Store2[x][y] = element;
5604 Feld[x][y] = EL_EXPLOSION;
5605 GfxElement[x][y] = artwork_element;
5607 ExplodePhase[x][y] = 1;
5608 ExplodeDelay[x][y] = last_phase;
5613 if (center_element == EL_YAMYAM)
5614 game.yamyam_content_nr =
5615 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5627 GfxFrame[x][y] = 0; // restart explosion animation
5629 last_phase = ExplodeDelay[x][y];
5631 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5633 // this can happen if the player leaves an explosion just in time
5634 if (GfxElement[x][y] == EL_UNDEFINED)
5635 GfxElement[x][y] = EL_EMPTY;
5637 border_element = Store2[x][y];
5638 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5639 border_element = StorePlayer[x][y];
5641 if (phase == element_info[border_element].ignition_delay ||
5642 phase == last_phase)
5644 boolean border_explosion = FALSE;
5646 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5647 !PLAYER_EXPLOSION_PROTECTED(x, y))
5649 KillPlayerUnlessExplosionProtected(x, y);
5650 border_explosion = TRUE;
5652 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5654 Feld[x][y] = Store2[x][y];
5657 border_explosion = TRUE;
5659 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5661 AmoebeUmwandeln(x, y);
5663 border_explosion = TRUE;
5666 // if an element just explodes due to another explosion (chain-reaction),
5667 // do not immediately end the new explosion when it was the last frame of
5668 // the explosion (as it would be done in the following "if"-statement!)
5669 if (border_explosion && phase == last_phase)
5673 if (phase == last_phase)
5677 element = Feld[x][y] = Store[x][y];
5678 Store[x][y] = Store2[x][y] = 0;
5679 GfxElement[x][y] = EL_UNDEFINED;
5681 // player can escape from explosions and might therefore be still alive
5682 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5683 element <= EL_PLAYER_IS_EXPLODING_4)
5685 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5686 int explosion_element = EL_PLAYER_1 + player_nr;
5687 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5688 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5690 if (level.use_explosion_element[player_nr])
5691 explosion_element = level.explosion_element[player_nr];
5693 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5694 element_info[explosion_element].content.e[xx][yy]);
5697 // restore probably existing indestructible background element
5698 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5699 element = Feld[x][y] = Back[x][y];
5702 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5703 GfxDir[x][y] = MV_NONE;
5704 ChangeDelay[x][y] = 0;
5705 ChangePage[x][y] = -1;
5707 CustomValue[x][y] = 0;
5709 InitField_WithBug2(x, y, FALSE);
5711 TEST_DrawLevelField(x, y);
5713 TestIfElementTouchesCustomElement(x, y);
5715 if (GFX_CRUMBLED(element))
5716 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5718 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5719 StorePlayer[x][y] = 0;
5721 if (ELEM_IS_PLAYER(element))
5722 RelocatePlayer(x, y, element);
5724 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5726 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5727 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5730 TEST_DrawLevelFieldCrumbled(x, y);
5732 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5734 DrawLevelElement(x, y, Back[x][y]);
5735 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5737 else if (IS_WALKABLE_UNDER(Back[x][y]))
5739 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5740 DrawLevelElementThruMask(x, y, Back[x][y]);
5742 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5743 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5747 static void DynaExplode(int ex, int ey)
5750 int dynabomb_element = Feld[ex][ey];
5751 int dynabomb_size = 1;
5752 boolean dynabomb_xl = FALSE;
5753 struct PlayerInfo *player;
5754 static int xy[4][2] =
5762 if (IS_ACTIVE_BOMB(dynabomb_element))
5764 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5765 dynabomb_size = player->dynabomb_size;
5766 dynabomb_xl = player->dynabomb_xl;
5767 player->dynabombs_left++;
5770 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5772 for (i = 0; i < NUM_DIRECTIONS; i++)
5774 for (j = 1; j <= dynabomb_size; j++)
5776 int x = ex + j * xy[i][0];
5777 int y = ey + j * xy[i][1];
5780 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5783 element = Feld[x][y];
5785 // do not restart explosions of fields with active bombs
5786 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5789 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5791 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5792 !IS_DIGGABLE(element) && !dynabomb_xl)
5798 void Bang(int x, int y)
5800 int element = MovingOrBlocked2Element(x, y);
5801 int explosion_type = EX_TYPE_NORMAL;
5803 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5805 struct PlayerInfo *player = PLAYERINFO(x, y);
5807 element = Feld[x][y] = player->initial_element;
5809 if (level.use_explosion_element[player->index_nr])
5811 int explosion_element = level.explosion_element[player->index_nr];
5813 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5814 explosion_type = EX_TYPE_CROSS;
5815 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5816 explosion_type = EX_TYPE_CENTER;
5824 case EL_BD_BUTTERFLY:
5827 case EL_DARK_YAMYAM:
5831 RaiseScoreElement(element);
5834 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5835 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5836 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5837 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5838 case EL_DYNABOMB_INCREASE_NUMBER:
5839 case EL_DYNABOMB_INCREASE_SIZE:
5840 case EL_DYNABOMB_INCREASE_POWER:
5841 explosion_type = EX_TYPE_DYNA;
5844 case EL_DC_LANDMINE:
5845 explosion_type = EX_TYPE_CENTER;
5850 case EL_LAMP_ACTIVE:
5851 case EL_AMOEBA_TO_DIAMOND:
5852 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5853 explosion_type = EX_TYPE_CENTER;
5857 if (element_info[element].explosion_type == EXPLODES_CROSS)
5858 explosion_type = EX_TYPE_CROSS;
5859 else if (element_info[element].explosion_type == EXPLODES_1X1)
5860 explosion_type = EX_TYPE_CENTER;
5864 if (explosion_type == EX_TYPE_DYNA)
5867 Explode(x, y, EX_PHASE_START, explosion_type);
5869 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5872 static void SplashAcid(int x, int y)
5874 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5875 (!IN_LEV_FIELD(x - 1, y - 2) ||
5876 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5877 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5879 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5880 (!IN_LEV_FIELD(x + 1, y - 2) ||
5881 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5882 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5884 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5887 static void InitBeltMovement(void)
5889 static int belt_base_element[4] =
5891 EL_CONVEYOR_BELT_1_LEFT,
5892 EL_CONVEYOR_BELT_2_LEFT,
5893 EL_CONVEYOR_BELT_3_LEFT,
5894 EL_CONVEYOR_BELT_4_LEFT
5896 static int belt_base_active_element[4] =
5898 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5899 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5900 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5901 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5906 // set frame order for belt animation graphic according to belt direction
5907 for (i = 0; i < NUM_BELTS; i++)
5911 for (j = 0; j < NUM_BELT_PARTS; j++)
5913 int element = belt_base_active_element[belt_nr] + j;
5914 int graphic_1 = el2img(element);
5915 int graphic_2 = el2panelimg(element);
5917 if (game.belt_dir[i] == MV_LEFT)
5919 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
5920 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
5924 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
5925 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
5930 SCAN_PLAYFIELD(x, y)
5932 int element = Feld[x][y];
5934 for (i = 0; i < NUM_BELTS; i++)
5936 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
5938 int e_belt_nr = getBeltNrFromBeltElement(element);
5941 if (e_belt_nr == belt_nr)
5943 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
5945 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
5952 static void ToggleBeltSwitch(int x, int y)
5954 static int belt_base_element[4] =
5956 EL_CONVEYOR_BELT_1_LEFT,
5957 EL_CONVEYOR_BELT_2_LEFT,
5958 EL_CONVEYOR_BELT_3_LEFT,
5959 EL_CONVEYOR_BELT_4_LEFT
5961 static int belt_base_active_element[4] =
5963 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
5964 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
5965 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
5966 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
5968 static int belt_base_switch_element[4] =
5970 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
5971 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
5972 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
5973 EL_CONVEYOR_BELT_4_SWITCH_LEFT
5975 static int belt_move_dir[4] =
5983 int element = Feld[x][y];
5984 int belt_nr = getBeltNrFromBeltSwitchElement(element);
5985 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
5986 int belt_dir = belt_move_dir[belt_dir_nr];
5989 if (!IS_BELT_SWITCH(element))
5992 game.belt_dir_nr[belt_nr] = belt_dir_nr;
5993 game.belt_dir[belt_nr] = belt_dir;
5995 if (belt_dir_nr == 3)
5998 // set frame order for belt animation graphic according to belt direction
5999 for (i = 0; i < NUM_BELT_PARTS; i++)
6001 int element = belt_base_active_element[belt_nr] + i;
6002 int graphic_1 = el2img(element);
6003 int graphic_2 = el2panelimg(element);
6005 if (belt_dir == MV_LEFT)
6007 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6008 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6012 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6013 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6017 SCAN_PLAYFIELD(xx, yy)
6019 int element = Feld[xx][yy];
6021 if (IS_BELT_SWITCH(element))
6023 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6025 if (e_belt_nr == belt_nr)
6027 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6028 TEST_DrawLevelField(xx, yy);
6031 else if (IS_BELT(element) && belt_dir != MV_NONE)
6033 int e_belt_nr = getBeltNrFromBeltElement(element);
6035 if (e_belt_nr == belt_nr)
6037 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
6039 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6040 TEST_DrawLevelField(xx, yy);
6043 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6045 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6047 if (e_belt_nr == belt_nr)
6049 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6051 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6052 TEST_DrawLevelField(xx, yy);
6058 static void ToggleSwitchgateSwitch(int x, int y)
6062 game.switchgate_pos = !game.switchgate_pos;
6064 SCAN_PLAYFIELD(xx, yy)
6066 int element = Feld[xx][yy];
6068 if (element == EL_SWITCHGATE_SWITCH_UP)
6070 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6071 TEST_DrawLevelField(xx, yy);
6073 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6075 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6076 TEST_DrawLevelField(xx, yy);
6078 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6080 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6081 TEST_DrawLevelField(xx, yy);
6083 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6085 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6086 TEST_DrawLevelField(xx, yy);
6088 else if (element == EL_SWITCHGATE_OPEN ||
6089 element == EL_SWITCHGATE_OPENING)
6091 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6093 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6095 else if (element == EL_SWITCHGATE_CLOSED ||
6096 element == EL_SWITCHGATE_CLOSING)
6098 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6100 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6105 static int getInvisibleActiveFromInvisibleElement(int element)
6107 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6108 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6109 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6113 static int getInvisibleFromInvisibleActiveElement(int element)
6115 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6116 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6117 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6121 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6125 SCAN_PLAYFIELD(x, y)
6127 int element = Feld[x][y];
6129 if (element == EL_LIGHT_SWITCH &&
6130 game.light_time_left > 0)
6132 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6133 TEST_DrawLevelField(x, y);
6135 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6136 game.light_time_left == 0)
6138 Feld[x][y] = EL_LIGHT_SWITCH;
6139 TEST_DrawLevelField(x, y);
6141 else if (element == EL_EMC_DRIPPER &&
6142 game.light_time_left > 0)
6144 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6145 TEST_DrawLevelField(x, y);
6147 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6148 game.light_time_left == 0)
6150 Feld[x][y] = EL_EMC_DRIPPER;
6151 TEST_DrawLevelField(x, y);
6153 else if (element == EL_INVISIBLE_STEELWALL ||
6154 element == EL_INVISIBLE_WALL ||
6155 element == EL_INVISIBLE_SAND)
6157 if (game.light_time_left > 0)
6158 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6160 TEST_DrawLevelField(x, y);
6162 // uncrumble neighbour fields, if needed
6163 if (element == EL_INVISIBLE_SAND)
6164 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6166 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6167 element == EL_INVISIBLE_WALL_ACTIVE ||
6168 element == EL_INVISIBLE_SAND_ACTIVE)
6170 if (game.light_time_left == 0)
6171 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6173 TEST_DrawLevelField(x, y);
6175 // re-crumble neighbour fields, if needed
6176 if (element == EL_INVISIBLE_SAND)
6177 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6182 static void RedrawAllInvisibleElementsForLenses(void)
6186 SCAN_PLAYFIELD(x, y)
6188 int element = Feld[x][y];
6190 if (element == EL_EMC_DRIPPER &&
6191 game.lenses_time_left > 0)
6193 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6194 TEST_DrawLevelField(x, y);
6196 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6197 game.lenses_time_left == 0)
6199 Feld[x][y] = EL_EMC_DRIPPER;
6200 TEST_DrawLevelField(x, y);
6202 else if (element == EL_INVISIBLE_STEELWALL ||
6203 element == EL_INVISIBLE_WALL ||
6204 element == EL_INVISIBLE_SAND)
6206 if (game.lenses_time_left > 0)
6207 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6209 TEST_DrawLevelField(x, y);
6211 // uncrumble neighbour fields, if needed
6212 if (element == EL_INVISIBLE_SAND)
6213 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6215 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6216 element == EL_INVISIBLE_WALL_ACTIVE ||
6217 element == EL_INVISIBLE_SAND_ACTIVE)
6219 if (game.lenses_time_left == 0)
6220 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6222 TEST_DrawLevelField(x, y);
6224 // re-crumble neighbour fields, if needed
6225 if (element == EL_INVISIBLE_SAND)
6226 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6231 static void RedrawAllInvisibleElementsForMagnifier(void)
6235 SCAN_PLAYFIELD(x, y)
6237 int element = Feld[x][y];
6239 if (element == EL_EMC_FAKE_GRASS &&
6240 game.magnify_time_left > 0)
6242 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6243 TEST_DrawLevelField(x, y);
6245 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6246 game.magnify_time_left == 0)
6248 Feld[x][y] = EL_EMC_FAKE_GRASS;
6249 TEST_DrawLevelField(x, y);
6251 else if (IS_GATE_GRAY(element) &&
6252 game.magnify_time_left > 0)
6254 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6255 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6256 IS_EM_GATE_GRAY(element) ?
6257 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6258 IS_EMC_GATE_GRAY(element) ?
6259 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6260 IS_DC_GATE_GRAY(element) ?
6261 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6263 TEST_DrawLevelField(x, y);
6265 else if (IS_GATE_GRAY_ACTIVE(element) &&
6266 game.magnify_time_left == 0)
6268 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6269 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6270 IS_EM_GATE_GRAY_ACTIVE(element) ?
6271 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6272 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6273 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6274 IS_DC_GATE_GRAY_ACTIVE(element) ?
6275 EL_DC_GATE_WHITE_GRAY :
6277 TEST_DrawLevelField(x, y);
6282 static void ToggleLightSwitch(int x, int y)
6284 int element = Feld[x][y];
6286 game.light_time_left =
6287 (element == EL_LIGHT_SWITCH ?
6288 level.time_light * FRAMES_PER_SECOND : 0);
6290 RedrawAllLightSwitchesAndInvisibleElements();
6293 static void ActivateTimegateSwitch(int x, int y)
6297 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6299 SCAN_PLAYFIELD(xx, yy)
6301 int element = Feld[xx][yy];
6303 if (element == EL_TIMEGATE_CLOSED ||
6304 element == EL_TIMEGATE_CLOSING)
6306 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6307 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6311 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6313 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6314 TEST_DrawLevelField(xx, yy);
6320 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6321 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6324 static void Impact(int x, int y)
6326 boolean last_line = (y == lev_fieldy - 1);
6327 boolean object_hit = FALSE;
6328 boolean impact = (last_line || object_hit);
6329 int element = Feld[x][y];
6330 int smashed = EL_STEELWALL;
6332 if (!last_line) // check if element below was hit
6334 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6337 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6338 MovDir[x][y + 1] != MV_DOWN ||
6339 MovPos[x][y + 1] <= TILEY / 2));
6341 // do not smash moving elements that left the smashed field in time
6342 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6343 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6346 #if USE_QUICKSAND_IMPACT_BUGFIX
6347 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6349 RemoveMovingField(x, y + 1);
6350 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6351 Feld[x][y + 2] = EL_ROCK;
6352 TEST_DrawLevelField(x, y + 2);
6357 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6359 RemoveMovingField(x, y + 1);
6360 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6361 Feld[x][y + 2] = EL_ROCK;
6362 TEST_DrawLevelField(x, y + 2);
6369 smashed = MovingOrBlocked2Element(x, y + 1);
6371 impact = (last_line || object_hit);
6374 if (!last_line && smashed == EL_ACID) // element falls into acid
6376 SplashAcid(x, y + 1);
6380 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6381 // only reset graphic animation if graphic really changes after impact
6383 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6385 ResetGfxAnimation(x, y);
6386 TEST_DrawLevelField(x, y);
6389 if (impact && CAN_EXPLODE_IMPACT(element))
6394 else if (impact && element == EL_PEARL &&
6395 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6397 ResetGfxAnimation(x, y);
6399 Feld[x][y] = EL_PEARL_BREAKING;
6400 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6403 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6405 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6410 if (impact && element == EL_AMOEBA_DROP)
6412 if (object_hit && IS_PLAYER(x, y + 1))
6413 KillPlayerUnlessEnemyProtected(x, y + 1);
6414 else if (object_hit && smashed == EL_PENGUIN)
6418 Feld[x][y] = EL_AMOEBA_GROWING;
6419 Store[x][y] = EL_AMOEBA_WET;
6421 ResetRandomAnimationValue(x, y);
6426 if (object_hit) // check which object was hit
6428 if ((CAN_PASS_MAGIC_WALL(element) &&
6429 (smashed == EL_MAGIC_WALL ||
6430 smashed == EL_BD_MAGIC_WALL)) ||
6431 (CAN_PASS_DC_MAGIC_WALL(element) &&
6432 smashed == EL_DC_MAGIC_WALL))
6435 int activated_magic_wall =
6436 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6437 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6438 EL_DC_MAGIC_WALL_ACTIVE);
6440 // activate magic wall / mill
6441 SCAN_PLAYFIELD(xx, yy)
6443 if (Feld[xx][yy] == smashed)
6444 Feld[xx][yy] = activated_magic_wall;
6447 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6448 game.magic_wall_active = TRUE;
6450 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6451 SND_MAGIC_WALL_ACTIVATING :
6452 smashed == EL_BD_MAGIC_WALL ?
6453 SND_BD_MAGIC_WALL_ACTIVATING :
6454 SND_DC_MAGIC_WALL_ACTIVATING));
6457 if (IS_PLAYER(x, y + 1))
6459 if (CAN_SMASH_PLAYER(element))
6461 KillPlayerUnlessEnemyProtected(x, y + 1);
6465 else if (smashed == EL_PENGUIN)
6467 if (CAN_SMASH_PLAYER(element))
6473 else if (element == EL_BD_DIAMOND)
6475 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6481 else if (((element == EL_SP_INFOTRON ||
6482 element == EL_SP_ZONK) &&
6483 (smashed == EL_SP_SNIKSNAK ||
6484 smashed == EL_SP_ELECTRON ||
6485 smashed == EL_SP_DISK_ORANGE)) ||
6486 (element == EL_SP_INFOTRON &&
6487 smashed == EL_SP_DISK_YELLOW))
6492 else if (CAN_SMASH_EVERYTHING(element))
6494 if (IS_CLASSIC_ENEMY(smashed) ||
6495 CAN_EXPLODE_SMASHED(smashed))
6500 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6502 if (smashed == EL_LAMP ||
6503 smashed == EL_LAMP_ACTIVE)
6508 else if (smashed == EL_NUT)
6510 Feld[x][y + 1] = EL_NUT_BREAKING;
6511 PlayLevelSound(x, y, SND_NUT_BREAKING);
6512 RaiseScoreElement(EL_NUT);
6515 else if (smashed == EL_PEARL)
6517 ResetGfxAnimation(x, y);
6519 Feld[x][y + 1] = EL_PEARL_BREAKING;
6520 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6523 else if (smashed == EL_DIAMOND)
6525 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6526 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6529 else if (IS_BELT_SWITCH(smashed))
6531 ToggleBeltSwitch(x, y + 1);
6533 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6534 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6535 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6536 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6538 ToggleSwitchgateSwitch(x, y + 1);
6540 else if (smashed == EL_LIGHT_SWITCH ||
6541 smashed == EL_LIGHT_SWITCH_ACTIVE)
6543 ToggleLightSwitch(x, y + 1);
6547 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6549 CheckElementChangeBySide(x, y + 1, smashed, element,
6550 CE_SWITCHED, CH_SIDE_TOP);
6551 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6557 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6562 // play sound of magic wall / mill
6564 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6565 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6566 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6568 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6569 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6570 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6571 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6572 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6573 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6578 // play sound of object that hits the ground
6579 if (last_line || object_hit)
6580 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6583 static void TurnRoundExt(int x, int y)
6595 { 0, 0 }, { 0, 0 }, { 0, 0 },
6600 int left, right, back;
6604 { MV_DOWN, MV_UP, MV_RIGHT },
6605 { MV_UP, MV_DOWN, MV_LEFT },
6607 { MV_LEFT, MV_RIGHT, MV_DOWN },
6611 { MV_RIGHT, MV_LEFT, MV_UP }
6614 int element = Feld[x][y];
6615 int move_pattern = element_info[element].move_pattern;
6617 int old_move_dir = MovDir[x][y];
6618 int left_dir = turn[old_move_dir].left;
6619 int right_dir = turn[old_move_dir].right;
6620 int back_dir = turn[old_move_dir].back;
6622 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6623 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6624 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6625 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6627 int left_x = x + left_dx, left_y = y + left_dy;
6628 int right_x = x + right_dx, right_y = y + right_dy;
6629 int move_x = x + move_dx, move_y = y + move_dy;
6633 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6635 TestIfBadThingTouchesOtherBadThing(x, y);
6637 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6638 MovDir[x][y] = right_dir;
6639 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6640 MovDir[x][y] = left_dir;
6642 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6644 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6647 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6649 TestIfBadThingTouchesOtherBadThing(x, y);
6651 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6652 MovDir[x][y] = left_dir;
6653 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6654 MovDir[x][y] = right_dir;
6656 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6658 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6661 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6663 TestIfBadThingTouchesOtherBadThing(x, y);
6665 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6666 MovDir[x][y] = left_dir;
6667 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6668 MovDir[x][y] = right_dir;
6670 if (MovDir[x][y] != old_move_dir)
6673 else if (element == EL_YAMYAM)
6675 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6676 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6678 if (can_turn_left && can_turn_right)
6679 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6680 else if (can_turn_left)
6681 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6682 else if (can_turn_right)
6683 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6685 MovDir[x][y] = back_dir;
6687 MovDelay[x][y] = 16 + 16 * RND(3);
6689 else if (element == EL_DARK_YAMYAM)
6691 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6693 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6696 if (can_turn_left && can_turn_right)
6697 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6698 else if (can_turn_left)
6699 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6700 else if (can_turn_right)
6701 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6703 MovDir[x][y] = back_dir;
6705 MovDelay[x][y] = 16 + 16 * RND(3);
6707 else if (element == EL_PACMAN)
6709 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6710 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6712 if (can_turn_left && can_turn_right)
6713 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6714 else if (can_turn_left)
6715 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6716 else if (can_turn_right)
6717 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6719 MovDir[x][y] = back_dir;
6721 MovDelay[x][y] = 6 + RND(40);
6723 else if (element == EL_PIG)
6725 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6726 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6727 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6728 boolean should_turn_left, should_turn_right, should_move_on;
6730 int rnd = RND(rnd_value);
6732 should_turn_left = (can_turn_left &&
6734 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6735 y + back_dy + left_dy)));
6736 should_turn_right = (can_turn_right &&
6738 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6739 y + back_dy + right_dy)));
6740 should_move_on = (can_move_on &&
6743 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6744 y + move_dy + left_dy) ||
6745 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6746 y + move_dy + right_dy)));
6748 if (should_turn_left || should_turn_right || should_move_on)
6750 if (should_turn_left && should_turn_right && should_move_on)
6751 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6752 rnd < 2 * rnd_value / 3 ? right_dir :
6754 else if (should_turn_left && should_turn_right)
6755 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6756 else if (should_turn_left && should_move_on)
6757 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6758 else if (should_turn_right && should_move_on)
6759 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6760 else if (should_turn_left)
6761 MovDir[x][y] = left_dir;
6762 else if (should_turn_right)
6763 MovDir[x][y] = right_dir;
6764 else if (should_move_on)
6765 MovDir[x][y] = old_move_dir;
6767 else if (can_move_on && rnd > rnd_value / 8)
6768 MovDir[x][y] = old_move_dir;
6769 else if (can_turn_left && can_turn_right)
6770 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6771 else if (can_turn_left && rnd > rnd_value / 8)
6772 MovDir[x][y] = left_dir;
6773 else if (can_turn_right && rnd > rnd_value/8)
6774 MovDir[x][y] = right_dir;
6776 MovDir[x][y] = back_dir;
6778 xx = x + move_xy[MovDir[x][y]].dx;
6779 yy = y + move_xy[MovDir[x][y]].dy;
6781 if (!IN_LEV_FIELD(xx, yy) ||
6782 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6783 MovDir[x][y] = old_move_dir;
6787 else if (element == EL_DRAGON)
6789 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6790 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6791 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6793 int rnd = RND(rnd_value);
6795 if (can_move_on && rnd > rnd_value / 8)
6796 MovDir[x][y] = old_move_dir;
6797 else if (can_turn_left && can_turn_right)
6798 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6799 else if (can_turn_left && rnd > rnd_value / 8)
6800 MovDir[x][y] = left_dir;
6801 else if (can_turn_right && rnd > rnd_value / 8)
6802 MovDir[x][y] = right_dir;
6804 MovDir[x][y] = back_dir;
6806 xx = x + move_xy[MovDir[x][y]].dx;
6807 yy = y + move_xy[MovDir[x][y]].dy;
6809 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6810 MovDir[x][y] = old_move_dir;
6814 else if (element == EL_MOLE)
6816 boolean can_move_on =
6817 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6818 IS_AMOEBOID(Feld[move_x][move_y]) ||
6819 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6822 boolean can_turn_left =
6823 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6824 IS_AMOEBOID(Feld[left_x][left_y])));
6826 boolean can_turn_right =
6827 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6828 IS_AMOEBOID(Feld[right_x][right_y])));
6830 if (can_turn_left && can_turn_right)
6831 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6832 else if (can_turn_left)
6833 MovDir[x][y] = left_dir;
6835 MovDir[x][y] = right_dir;
6838 if (MovDir[x][y] != old_move_dir)
6841 else if (element == EL_BALLOON)
6843 MovDir[x][y] = game.wind_direction;
6846 else if (element == EL_SPRING)
6848 if (MovDir[x][y] & MV_HORIZONTAL)
6850 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6851 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6853 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6854 ResetGfxAnimation(move_x, move_y);
6855 TEST_DrawLevelField(move_x, move_y);
6857 MovDir[x][y] = back_dir;
6859 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6860 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6861 MovDir[x][y] = MV_NONE;
6866 else if (element == EL_ROBOT ||
6867 element == EL_SATELLITE ||
6868 element == EL_PENGUIN ||
6869 element == EL_EMC_ANDROID)
6871 int attr_x = -1, attr_y = -1;
6873 if (game.all_players_gone)
6875 attr_x = game.exit_x;
6876 attr_y = game.exit_y;
6882 for (i = 0; i < MAX_PLAYERS; i++)
6884 struct PlayerInfo *player = &stored_player[i];
6885 int jx = player->jx, jy = player->jy;
6887 if (!player->active)
6891 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
6899 if (element == EL_ROBOT &&
6900 game.robot_wheel_x >= 0 &&
6901 game.robot_wheel_y >= 0 &&
6902 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
6903 game.engine_version < VERSION_IDENT(3,1,0,0)))
6905 attr_x = game.robot_wheel_x;
6906 attr_y = game.robot_wheel_y;
6909 if (element == EL_PENGUIN)
6912 static int xy[4][2] =
6920 for (i = 0; i < NUM_DIRECTIONS; i++)
6922 int ex = x + xy[i][0];
6923 int ey = y + xy[i][1];
6925 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
6926 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
6927 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
6928 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
6937 MovDir[x][y] = MV_NONE;
6939 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
6940 else if (attr_x > x)
6941 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
6943 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
6944 else if (attr_y > y)
6945 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
6947 if (element == EL_ROBOT)
6951 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6952 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
6953 Moving2Blocked(x, y, &newx, &newy);
6955 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
6956 MovDelay[x][y] = 8 + 8 * !RND(3);
6958 MovDelay[x][y] = 16;
6960 else if (element == EL_PENGUIN)
6966 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6968 boolean first_horiz = RND(2);
6969 int new_move_dir = MovDir[x][y];
6972 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6973 Moving2Blocked(x, y, &newx, &newy);
6975 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6979 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
6980 Moving2Blocked(x, y, &newx, &newy);
6982 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
6985 MovDir[x][y] = old_move_dir;
6989 else if (element == EL_SATELLITE)
6995 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
6997 boolean first_horiz = RND(2);
6998 int new_move_dir = MovDir[x][y];
7001 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7002 Moving2Blocked(x, y, &newx, &newy);
7004 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7008 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7009 Moving2Blocked(x, y, &newx, &newy);
7011 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7014 MovDir[x][y] = old_move_dir;
7018 else if (element == EL_EMC_ANDROID)
7020 static int check_pos[16] =
7022 -1, // 0 => (invalid)
7025 -1, // 3 => (invalid)
7027 0, // 5 => MV_LEFT | MV_UP
7028 2, // 6 => MV_RIGHT | MV_UP
7029 -1, // 7 => (invalid)
7031 6, // 9 => MV_LEFT | MV_DOWN
7032 4, // 10 => MV_RIGHT | MV_DOWN
7033 -1, // 11 => (invalid)
7034 -1, // 12 => (invalid)
7035 -1, // 13 => (invalid)
7036 -1, // 14 => (invalid)
7037 -1, // 15 => (invalid)
7045 { -1, -1, MV_LEFT | MV_UP },
7047 { +1, -1, MV_RIGHT | MV_UP },
7048 { +1, 0, MV_RIGHT },
7049 { +1, +1, MV_RIGHT | MV_DOWN },
7051 { -1, +1, MV_LEFT | MV_DOWN },
7054 int start_pos, check_order;
7055 boolean can_clone = FALSE;
7058 // check if there is any free field around current position
7059 for (i = 0; i < 8; i++)
7061 int newx = x + check_xy[i].dx;
7062 int newy = y + check_xy[i].dy;
7064 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7072 if (can_clone) // randomly find an element to clone
7076 start_pos = check_pos[RND(8)];
7077 check_order = (RND(2) ? -1 : +1);
7079 for (i = 0; i < 8; i++)
7081 int pos_raw = start_pos + i * check_order;
7082 int pos = (pos_raw + 8) % 8;
7083 int newx = x + check_xy[pos].dx;
7084 int newy = y + check_xy[pos].dy;
7086 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7088 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7089 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7091 Store[x][y] = Feld[newx][newy];
7100 if (can_clone) // randomly find a direction to move
7104 start_pos = check_pos[RND(8)];
7105 check_order = (RND(2) ? -1 : +1);
7107 for (i = 0; i < 8; i++)
7109 int pos_raw = start_pos + i * check_order;
7110 int pos = (pos_raw + 8) % 8;
7111 int newx = x + check_xy[pos].dx;
7112 int newy = y + check_xy[pos].dy;
7113 int new_move_dir = check_xy[pos].dir;
7115 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7117 MovDir[x][y] = new_move_dir;
7118 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7127 if (can_clone) // cloning and moving successful
7130 // cannot clone -- try to move towards player
7132 start_pos = check_pos[MovDir[x][y] & 0x0f];
7133 check_order = (RND(2) ? -1 : +1);
7135 for (i = 0; i < 3; i++)
7137 // first check start_pos, then previous/next or (next/previous) pos
7138 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7139 int pos = (pos_raw + 8) % 8;
7140 int newx = x + check_xy[pos].dx;
7141 int newy = y + check_xy[pos].dy;
7142 int new_move_dir = check_xy[pos].dir;
7144 if (IS_PLAYER(newx, newy))
7147 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7149 MovDir[x][y] = new_move_dir;
7150 MovDelay[x][y] = level.android_move_time * 8 + 1;
7157 else if (move_pattern == MV_TURNING_LEFT ||
7158 move_pattern == MV_TURNING_RIGHT ||
7159 move_pattern == MV_TURNING_LEFT_RIGHT ||
7160 move_pattern == MV_TURNING_RIGHT_LEFT ||
7161 move_pattern == MV_TURNING_RANDOM ||
7162 move_pattern == MV_ALL_DIRECTIONS)
7164 boolean can_turn_left =
7165 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7166 boolean can_turn_right =
7167 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7169 if (element_info[element].move_stepsize == 0) // "not moving"
7172 if (move_pattern == MV_TURNING_LEFT)
7173 MovDir[x][y] = left_dir;
7174 else if (move_pattern == MV_TURNING_RIGHT)
7175 MovDir[x][y] = right_dir;
7176 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7177 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7178 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7179 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7180 else if (move_pattern == MV_TURNING_RANDOM)
7181 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7182 can_turn_right && !can_turn_left ? right_dir :
7183 RND(2) ? left_dir : right_dir);
7184 else if (can_turn_left && can_turn_right)
7185 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7186 else if (can_turn_left)
7187 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7188 else if (can_turn_right)
7189 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7191 MovDir[x][y] = back_dir;
7193 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7195 else if (move_pattern == MV_HORIZONTAL ||
7196 move_pattern == MV_VERTICAL)
7198 if (move_pattern & old_move_dir)
7199 MovDir[x][y] = back_dir;
7200 else if (move_pattern == MV_HORIZONTAL)
7201 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7202 else if (move_pattern == MV_VERTICAL)
7203 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7205 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7207 else if (move_pattern & MV_ANY_DIRECTION)
7209 MovDir[x][y] = move_pattern;
7210 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7212 else if (move_pattern & MV_WIND_DIRECTION)
7214 MovDir[x][y] = game.wind_direction;
7215 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7217 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7219 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7220 MovDir[x][y] = left_dir;
7221 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7222 MovDir[x][y] = right_dir;
7224 if (MovDir[x][y] != old_move_dir)
7225 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7227 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7229 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7230 MovDir[x][y] = right_dir;
7231 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7232 MovDir[x][y] = left_dir;
7234 if (MovDir[x][y] != old_move_dir)
7235 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7237 else if (move_pattern == MV_TOWARDS_PLAYER ||
7238 move_pattern == MV_AWAY_FROM_PLAYER)
7240 int attr_x = -1, attr_y = -1;
7242 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7244 if (game.all_players_gone)
7246 attr_x = game.exit_x;
7247 attr_y = game.exit_y;
7253 for (i = 0; i < MAX_PLAYERS; i++)
7255 struct PlayerInfo *player = &stored_player[i];
7256 int jx = player->jx, jy = player->jy;
7258 if (!player->active)
7262 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7270 MovDir[x][y] = MV_NONE;
7272 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7273 else if (attr_x > x)
7274 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7276 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7277 else if (attr_y > y)
7278 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7280 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7282 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7284 boolean first_horiz = RND(2);
7285 int new_move_dir = MovDir[x][y];
7287 if (element_info[element].move_stepsize == 0) // "not moving"
7289 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7290 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7296 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7297 Moving2Blocked(x, y, &newx, &newy);
7299 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7303 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7304 Moving2Blocked(x, y, &newx, &newy);
7306 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7309 MovDir[x][y] = old_move_dir;
7312 else if (move_pattern == MV_WHEN_PUSHED ||
7313 move_pattern == MV_WHEN_DROPPED)
7315 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7316 MovDir[x][y] = MV_NONE;
7320 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7322 static int test_xy[7][2] =
7332 static int test_dir[7] =
7342 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7343 int move_preference = -1000000; // start with very low preference
7344 int new_move_dir = MV_NONE;
7345 int start_test = RND(4);
7348 for (i = 0; i < NUM_DIRECTIONS; i++)
7350 int move_dir = test_dir[start_test + i];
7351 int move_dir_preference;
7353 xx = x + test_xy[start_test + i][0];
7354 yy = y + test_xy[start_test + i][1];
7356 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7357 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7359 new_move_dir = move_dir;
7364 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7367 move_dir_preference = -1 * RunnerVisit[xx][yy];
7368 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7369 move_dir_preference = PlayerVisit[xx][yy];
7371 if (move_dir_preference > move_preference)
7373 // prefer field that has not been visited for the longest time
7374 move_preference = move_dir_preference;
7375 new_move_dir = move_dir;
7377 else if (move_dir_preference == move_preference &&
7378 move_dir == old_move_dir)
7380 // prefer last direction when all directions are preferred equally
7381 move_preference = move_dir_preference;
7382 new_move_dir = move_dir;
7386 MovDir[x][y] = new_move_dir;
7387 if (old_move_dir != new_move_dir)
7388 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7392 static void TurnRound(int x, int y)
7394 int direction = MovDir[x][y];
7398 GfxDir[x][y] = MovDir[x][y];
7400 if (direction != MovDir[x][y])
7404 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7406 ResetGfxFrame(x, y);
7409 static boolean JustBeingPushed(int x, int y)
7413 for (i = 0; i < MAX_PLAYERS; i++)
7415 struct PlayerInfo *player = &stored_player[i];
7417 if (player->active && player->is_pushing && player->MovPos)
7419 int next_jx = player->jx + (player->jx - player->last_jx);
7420 int next_jy = player->jy + (player->jy - player->last_jy);
7422 if (x == next_jx && y == next_jy)
7430 static void StartMoving(int x, int y)
7432 boolean started_moving = FALSE; // some elements can fall _and_ move
7433 int element = Feld[x][y];
7438 if (MovDelay[x][y] == 0)
7439 GfxAction[x][y] = ACTION_DEFAULT;
7441 if (CAN_FALL(element) && y < lev_fieldy - 1)
7443 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7444 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7445 if (JustBeingPushed(x, y))
7448 if (element == EL_QUICKSAND_FULL)
7450 if (IS_FREE(x, y + 1))
7452 InitMovingField(x, y, MV_DOWN);
7453 started_moving = TRUE;
7455 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7456 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7457 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7458 Store[x][y] = EL_ROCK;
7460 Store[x][y] = EL_ROCK;
7463 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7465 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7467 if (!MovDelay[x][y])
7469 MovDelay[x][y] = TILEY + 1;
7471 ResetGfxAnimation(x, y);
7472 ResetGfxAnimation(x, y + 1);
7477 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7478 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7485 Feld[x][y] = EL_QUICKSAND_EMPTY;
7486 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7487 Store[x][y + 1] = Store[x][y];
7490 PlayLevelSoundAction(x, y, ACTION_FILLING);
7492 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7494 if (!MovDelay[x][y])
7496 MovDelay[x][y] = TILEY + 1;
7498 ResetGfxAnimation(x, y);
7499 ResetGfxAnimation(x, y + 1);
7504 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7505 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7512 Feld[x][y] = EL_QUICKSAND_EMPTY;
7513 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7514 Store[x][y + 1] = Store[x][y];
7517 PlayLevelSoundAction(x, y, ACTION_FILLING);
7520 else if (element == EL_QUICKSAND_FAST_FULL)
7522 if (IS_FREE(x, y + 1))
7524 InitMovingField(x, y, MV_DOWN);
7525 started_moving = TRUE;
7527 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7528 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7529 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7530 Store[x][y] = EL_ROCK;
7532 Store[x][y] = EL_ROCK;
7535 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7537 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7539 if (!MovDelay[x][y])
7541 MovDelay[x][y] = TILEY + 1;
7543 ResetGfxAnimation(x, y);
7544 ResetGfxAnimation(x, y + 1);
7549 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7550 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7557 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7558 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7559 Store[x][y + 1] = Store[x][y];
7562 PlayLevelSoundAction(x, y, ACTION_FILLING);
7564 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7566 if (!MovDelay[x][y])
7568 MovDelay[x][y] = TILEY + 1;
7570 ResetGfxAnimation(x, y);
7571 ResetGfxAnimation(x, y + 1);
7576 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7577 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7584 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7585 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7586 Store[x][y + 1] = Store[x][y];
7589 PlayLevelSoundAction(x, y, ACTION_FILLING);
7592 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7593 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7595 InitMovingField(x, y, MV_DOWN);
7596 started_moving = TRUE;
7598 Feld[x][y] = EL_QUICKSAND_FILLING;
7599 Store[x][y] = element;
7601 PlayLevelSoundAction(x, y, ACTION_FILLING);
7603 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7604 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7606 InitMovingField(x, y, MV_DOWN);
7607 started_moving = TRUE;
7609 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7610 Store[x][y] = element;
7612 PlayLevelSoundAction(x, y, ACTION_FILLING);
7614 else if (element == EL_MAGIC_WALL_FULL)
7616 if (IS_FREE(x, y + 1))
7618 InitMovingField(x, y, MV_DOWN);
7619 started_moving = TRUE;
7621 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7622 Store[x][y] = EL_CHANGED(Store[x][y]);
7624 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7626 if (!MovDelay[x][y])
7627 MovDelay[x][y] = TILEY / 4 + 1;
7636 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7637 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7638 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7642 else if (element == EL_BD_MAGIC_WALL_FULL)
7644 if (IS_FREE(x, y + 1))
7646 InitMovingField(x, y, MV_DOWN);
7647 started_moving = TRUE;
7649 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7650 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7652 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7654 if (!MovDelay[x][y])
7655 MovDelay[x][y] = TILEY / 4 + 1;
7664 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7665 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7666 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7670 else if (element == EL_DC_MAGIC_WALL_FULL)
7672 if (IS_FREE(x, y + 1))
7674 InitMovingField(x, y, MV_DOWN);
7675 started_moving = TRUE;
7677 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7678 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7680 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7682 if (!MovDelay[x][y])
7683 MovDelay[x][y] = TILEY / 4 + 1;
7692 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7693 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7694 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7698 else if ((CAN_PASS_MAGIC_WALL(element) &&
7699 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7700 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7701 (CAN_PASS_DC_MAGIC_WALL(element) &&
7702 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7705 InitMovingField(x, y, MV_DOWN);
7706 started_moving = TRUE;
7709 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7710 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7711 EL_DC_MAGIC_WALL_FILLING);
7712 Store[x][y] = element;
7714 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7716 SplashAcid(x, y + 1);
7718 InitMovingField(x, y, MV_DOWN);
7719 started_moving = TRUE;
7721 Store[x][y] = EL_ACID;
7724 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7725 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7726 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7727 CAN_FALL(element) && WasJustFalling[x][y] &&
7728 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7730 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7731 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7732 (Feld[x][y + 1] == EL_BLOCKED)))
7734 /* this is needed for a special case not covered by calling "Impact()"
7735 from "ContinueMoving()": if an element moves to a tile directly below
7736 another element which was just falling on that tile (which was empty
7737 in the previous frame), the falling element above would just stop
7738 instead of smashing the element below (in previous version, the above
7739 element was just checked for "moving" instead of "falling", resulting
7740 in incorrect smashes caused by horizontal movement of the above
7741 element; also, the case of the player being the element to smash was
7742 simply not covered here... :-/ ) */
7744 CheckCollision[x][y] = 0;
7745 CheckImpact[x][y] = 0;
7749 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7751 if (MovDir[x][y] == MV_NONE)
7753 InitMovingField(x, y, MV_DOWN);
7754 started_moving = TRUE;
7757 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7759 if (WasJustFalling[x][y]) // prevent animation from being restarted
7760 MovDir[x][y] = MV_DOWN;
7762 InitMovingField(x, y, MV_DOWN);
7763 started_moving = TRUE;
7765 else if (element == EL_AMOEBA_DROP)
7767 Feld[x][y] = EL_AMOEBA_GROWING;
7768 Store[x][y] = EL_AMOEBA_WET;
7770 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7771 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7772 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7773 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7775 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7776 (IS_FREE(x - 1, y + 1) ||
7777 Feld[x - 1][y + 1] == EL_ACID));
7778 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7779 (IS_FREE(x + 1, y + 1) ||
7780 Feld[x + 1][y + 1] == EL_ACID));
7781 boolean can_fall_any = (can_fall_left || can_fall_right);
7782 boolean can_fall_both = (can_fall_left && can_fall_right);
7783 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7785 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7787 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7788 can_fall_right = FALSE;
7789 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7790 can_fall_left = FALSE;
7791 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7792 can_fall_right = FALSE;
7793 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7794 can_fall_left = FALSE;
7796 can_fall_any = (can_fall_left || can_fall_right);
7797 can_fall_both = FALSE;
7802 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7803 can_fall_right = FALSE; // slip down on left side
7805 can_fall_left = !(can_fall_right = RND(2));
7807 can_fall_both = FALSE;
7812 // if not determined otherwise, prefer left side for slipping down
7813 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7814 started_moving = TRUE;
7817 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7819 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7820 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7821 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7822 int belt_dir = game.belt_dir[belt_nr];
7824 if ((belt_dir == MV_LEFT && left_is_free) ||
7825 (belt_dir == MV_RIGHT && right_is_free))
7827 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7829 InitMovingField(x, y, belt_dir);
7830 started_moving = TRUE;
7832 Pushed[x][y] = TRUE;
7833 Pushed[nextx][y] = TRUE;
7835 GfxAction[x][y] = ACTION_DEFAULT;
7839 MovDir[x][y] = 0; // if element was moving, stop it
7844 // not "else if" because of elements that can fall and move (EL_SPRING)
7845 if (CAN_MOVE(element) && !started_moving)
7847 int move_pattern = element_info[element].move_pattern;
7850 Moving2Blocked(x, y, &newx, &newy);
7852 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7855 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7856 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7858 WasJustMoving[x][y] = 0;
7859 CheckCollision[x][y] = 0;
7861 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7863 if (Feld[x][y] != element) // element has changed
7867 if (!MovDelay[x][y]) // start new movement phase
7869 // all objects that can change their move direction after each step
7870 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7872 if (element != EL_YAMYAM &&
7873 element != EL_DARK_YAMYAM &&
7874 element != EL_PACMAN &&
7875 !(move_pattern & MV_ANY_DIRECTION) &&
7876 move_pattern != MV_TURNING_LEFT &&
7877 move_pattern != MV_TURNING_RIGHT &&
7878 move_pattern != MV_TURNING_LEFT_RIGHT &&
7879 move_pattern != MV_TURNING_RIGHT_LEFT &&
7880 move_pattern != MV_TURNING_RANDOM)
7884 if (MovDelay[x][y] && (element == EL_BUG ||
7885 element == EL_SPACESHIP ||
7886 element == EL_SP_SNIKSNAK ||
7887 element == EL_SP_ELECTRON ||
7888 element == EL_MOLE))
7889 TEST_DrawLevelField(x, y);
7893 if (MovDelay[x][y]) // wait some time before next movement
7897 if (element == EL_ROBOT ||
7898 element == EL_YAMYAM ||
7899 element == EL_DARK_YAMYAM)
7901 DrawLevelElementAnimationIfNeeded(x, y, element);
7902 PlayLevelSoundAction(x, y, ACTION_WAITING);
7904 else if (element == EL_SP_ELECTRON)
7905 DrawLevelElementAnimationIfNeeded(x, y, element);
7906 else if (element == EL_DRAGON)
7909 int dir = MovDir[x][y];
7910 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
7911 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
7912 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
7913 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
7914 dir == MV_UP ? IMG_FLAMES_1_UP :
7915 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
7916 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
7918 GfxAction[x][y] = ACTION_ATTACKING;
7920 if (IS_PLAYER(x, y))
7921 DrawPlayerField(x, y);
7923 TEST_DrawLevelField(x, y);
7925 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
7927 for (i = 1; i <= 3; i++)
7929 int xx = x + i * dx;
7930 int yy = y + i * dy;
7931 int sx = SCREENX(xx);
7932 int sy = SCREENY(yy);
7933 int flame_graphic = graphic + (i - 1);
7935 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
7940 int flamed = MovingOrBlocked2Element(xx, yy);
7942 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
7945 RemoveMovingField(xx, yy);
7947 ChangeDelay[xx][yy] = 0;
7949 Feld[xx][yy] = EL_FLAMES;
7951 if (IN_SCR_FIELD(sx, sy))
7953 TEST_DrawLevelFieldCrumbled(xx, yy);
7954 DrawGraphic(sx, sy, flame_graphic, frame);
7959 if (Feld[xx][yy] == EL_FLAMES)
7960 Feld[xx][yy] = EL_EMPTY;
7961 TEST_DrawLevelField(xx, yy);
7966 if (MovDelay[x][y]) // element still has to wait some time
7968 PlayLevelSoundAction(x, y, ACTION_WAITING);
7974 // now make next step
7976 Moving2Blocked(x, y, &newx, &newy); // get next screen position
7978 if (DONT_COLLIDE_WITH(element) &&
7979 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
7980 !PLAYER_ENEMY_PROTECTED(newx, newy))
7982 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
7987 else if (CAN_MOVE_INTO_ACID(element) &&
7988 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
7989 !IS_MV_DIAGONAL(MovDir[x][y]) &&
7990 (MovDir[x][y] == MV_DOWN ||
7991 game.engine_version >= VERSION_IDENT(3,1,0,0)))
7993 SplashAcid(newx, newy);
7994 Store[x][y] = EL_ACID;
7996 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
7998 if (Feld[newx][newy] == EL_EXIT_OPEN ||
7999 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
8000 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
8001 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8004 TEST_DrawLevelField(x, y);
8006 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8007 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8008 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8010 game.friends_still_needed--;
8011 if (!game.friends_still_needed &&
8013 game.all_players_gone)
8018 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
8020 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8021 TEST_DrawLevelField(newx, newy);
8023 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8025 else if (!IS_FREE(newx, newy))
8027 GfxAction[x][y] = ACTION_WAITING;
8029 if (IS_PLAYER(x, y))
8030 DrawPlayerField(x, y);
8032 TEST_DrawLevelField(x, y);
8037 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8039 if (IS_FOOD_PIG(Feld[newx][newy]))
8041 if (IS_MOVING(newx, newy))
8042 RemoveMovingField(newx, newy);
8045 Feld[newx][newy] = EL_EMPTY;
8046 TEST_DrawLevelField(newx, newy);
8049 PlayLevelSound(x, y, SND_PIG_DIGGING);
8051 else if (!IS_FREE(newx, newy))
8053 if (IS_PLAYER(x, y))
8054 DrawPlayerField(x, y);
8056 TEST_DrawLevelField(x, y);
8061 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8063 if (Store[x][y] != EL_EMPTY)
8065 boolean can_clone = FALSE;
8068 // check if element to clone is still there
8069 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8071 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8079 // cannot clone or target field not free anymore -- do not clone
8080 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8081 Store[x][y] = EL_EMPTY;
8084 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8086 if (IS_MV_DIAGONAL(MovDir[x][y]))
8088 int diagonal_move_dir = MovDir[x][y];
8089 int stored = Store[x][y];
8090 int change_delay = 8;
8093 // android is moving diagonally
8095 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8097 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8098 GfxElement[x][y] = EL_EMC_ANDROID;
8099 GfxAction[x][y] = ACTION_SHRINKING;
8100 GfxDir[x][y] = diagonal_move_dir;
8101 ChangeDelay[x][y] = change_delay;
8103 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8106 DrawLevelGraphicAnimation(x, y, graphic);
8107 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8109 if (Feld[newx][newy] == EL_ACID)
8111 SplashAcid(newx, newy);
8116 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8118 Store[newx][newy] = EL_EMC_ANDROID;
8119 GfxElement[newx][newy] = EL_EMC_ANDROID;
8120 GfxAction[newx][newy] = ACTION_GROWING;
8121 GfxDir[newx][newy] = diagonal_move_dir;
8122 ChangeDelay[newx][newy] = change_delay;
8124 graphic = el_act_dir2img(GfxElement[newx][newy],
8125 GfxAction[newx][newy], GfxDir[newx][newy]);
8127 DrawLevelGraphicAnimation(newx, newy, graphic);
8128 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8134 Feld[newx][newy] = EL_EMPTY;
8135 TEST_DrawLevelField(newx, newy);
8137 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8140 else if (!IS_FREE(newx, newy))
8145 else if (IS_CUSTOM_ELEMENT(element) &&
8146 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8148 if (!DigFieldByCE(newx, newy, element))
8151 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8153 RunnerVisit[x][y] = FrameCounter;
8154 PlayerVisit[x][y] /= 8; // expire player visit path
8157 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8159 if (!IS_FREE(newx, newy))
8161 if (IS_PLAYER(x, y))
8162 DrawPlayerField(x, y);
8164 TEST_DrawLevelField(x, y);
8170 boolean wanna_flame = !RND(10);
8171 int dx = newx - x, dy = newy - y;
8172 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8173 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8174 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8175 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8176 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8177 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8180 IS_CLASSIC_ENEMY(element1) ||
8181 IS_CLASSIC_ENEMY(element2)) &&
8182 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8183 element1 != EL_FLAMES && element2 != EL_FLAMES)
8185 ResetGfxAnimation(x, y);
8186 GfxAction[x][y] = ACTION_ATTACKING;
8188 if (IS_PLAYER(x, y))
8189 DrawPlayerField(x, y);
8191 TEST_DrawLevelField(x, y);
8193 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8195 MovDelay[x][y] = 50;
8197 Feld[newx][newy] = EL_FLAMES;
8198 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8199 Feld[newx1][newy1] = EL_FLAMES;
8200 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8201 Feld[newx2][newy2] = EL_FLAMES;
8207 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8208 Feld[newx][newy] == EL_DIAMOND)
8210 if (IS_MOVING(newx, newy))
8211 RemoveMovingField(newx, newy);
8214 Feld[newx][newy] = EL_EMPTY;
8215 TEST_DrawLevelField(newx, newy);
8218 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8220 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8221 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8223 if (AmoebaNr[newx][newy])
8225 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8226 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8227 Feld[newx][newy] == EL_BD_AMOEBA)
8228 AmoebaCnt[AmoebaNr[newx][newy]]--;
8231 if (IS_MOVING(newx, newy))
8233 RemoveMovingField(newx, newy);
8237 Feld[newx][newy] = EL_EMPTY;
8238 TEST_DrawLevelField(newx, newy);
8241 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8243 else if ((element == EL_PACMAN || element == EL_MOLE)
8244 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8246 if (AmoebaNr[newx][newy])
8248 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8249 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8250 Feld[newx][newy] == EL_BD_AMOEBA)
8251 AmoebaCnt[AmoebaNr[newx][newy]]--;
8254 if (element == EL_MOLE)
8256 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8257 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8259 ResetGfxAnimation(x, y);
8260 GfxAction[x][y] = ACTION_DIGGING;
8261 TEST_DrawLevelField(x, y);
8263 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8265 return; // wait for shrinking amoeba
8267 else // element == EL_PACMAN
8269 Feld[newx][newy] = EL_EMPTY;
8270 TEST_DrawLevelField(newx, newy);
8271 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8274 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8275 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8276 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8278 // wait for shrinking amoeba to completely disappear
8281 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8283 // object was running against a wall
8287 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8288 DrawLevelElementAnimation(x, y, element);
8290 if (DONT_TOUCH(element))
8291 TestIfBadThingTouchesPlayer(x, y);
8296 InitMovingField(x, y, MovDir[x][y]);
8298 PlayLevelSoundAction(x, y, ACTION_MOVING);
8302 ContinueMoving(x, y);
8305 void ContinueMoving(int x, int y)
8307 int element = Feld[x][y];
8308 struct ElementInfo *ei = &element_info[element];
8309 int direction = MovDir[x][y];
8310 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8311 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8312 int newx = x + dx, newy = y + dy;
8313 int stored = Store[x][y];
8314 int stored_new = Store[newx][newy];
8315 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8316 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8317 boolean last_line = (newy == lev_fieldy - 1);
8319 MovPos[x][y] += getElementMoveStepsize(x, y);
8321 if (pushed_by_player) // special case: moving object pushed by player
8322 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8324 if (ABS(MovPos[x][y]) < TILEX)
8326 TEST_DrawLevelField(x, y);
8328 return; // element is still moving
8331 // element reached destination field
8333 Feld[x][y] = EL_EMPTY;
8334 Feld[newx][newy] = element;
8335 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8337 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8339 element = Feld[newx][newy] = EL_ACID;
8341 else if (element == EL_MOLE)
8343 Feld[x][y] = EL_SAND;
8345 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8347 else if (element == EL_QUICKSAND_FILLING)
8349 element = Feld[newx][newy] = get_next_element(element);
8350 Store[newx][newy] = Store[x][y];
8352 else if (element == EL_QUICKSAND_EMPTYING)
8354 Feld[x][y] = get_next_element(element);
8355 element = Feld[newx][newy] = Store[x][y];
8357 else if (element == EL_QUICKSAND_FAST_FILLING)
8359 element = Feld[newx][newy] = get_next_element(element);
8360 Store[newx][newy] = Store[x][y];
8362 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8364 Feld[x][y] = get_next_element(element);
8365 element = Feld[newx][newy] = Store[x][y];
8367 else if (element == EL_MAGIC_WALL_FILLING)
8369 element = Feld[newx][newy] = get_next_element(element);
8370 if (!game.magic_wall_active)
8371 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8372 Store[newx][newy] = Store[x][y];
8374 else if (element == EL_MAGIC_WALL_EMPTYING)
8376 Feld[x][y] = get_next_element(element);
8377 if (!game.magic_wall_active)
8378 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8379 element = Feld[newx][newy] = Store[x][y];
8381 InitField(newx, newy, FALSE);
8383 else if (element == EL_BD_MAGIC_WALL_FILLING)
8385 element = Feld[newx][newy] = get_next_element(element);
8386 if (!game.magic_wall_active)
8387 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8388 Store[newx][newy] = Store[x][y];
8390 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8392 Feld[x][y] = get_next_element(element);
8393 if (!game.magic_wall_active)
8394 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8395 element = Feld[newx][newy] = Store[x][y];
8397 InitField(newx, newy, FALSE);
8399 else if (element == EL_DC_MAGIC_WALL_FILLING)
8401 element = Feld[newx][newy] = get_next_element(element);
8402 if (!game.magic_wall_active)
8403 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8404 Store[newx][newy] = Store[x][y];
8406 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8408 Feld[x][y] = get_next_element(element);
8409 if (!game.magic_wall_active)
8410 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8411 element = Feld[newx][newy] = Store[x][y];
8413 InitField(newx, newy, FALSE);
8415 else if (element == EL_AMOEBA_DROPPING)
8417 Feld[x][y] = get_next_element(element);
8418 element = Feld[newx][newy] = Store[x][y];
8420 else if (element == EL_SOKOBAN_OBJECT)
8423 Feld[x][y] = Back[x][y];
8425 if (Back[newx][newy])
8426 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8428 Back[x][y] = Back[newx][newy] = 0;
8431 Store[x][y] = EL_EMPTY;
8436 MovDelay[newx][newy] = 0;
8438 if (CAN_CHANGE_OR_HAS_ACTION(element))
8440 // copy element change control values to new field
8441 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8442 ChangePage[newx][newy] = ChangePage[x][y];
8443 ChangeCount[newx][newy] = ChangeCount[x][y];
8444 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8447 CustomValue[newx][newy] = CustomValue[x][y];
8449 ChangeDelay[x][y] = 0;
8450 ChangePage[x][y] = -1;
8451 ChangeCount[x][y] = 0;
8452 ChangeEvent[x][y] = -1;
8454 CustomValue[x][y] = 0;
8456 // copy animation control values to new field
8457 GfxFrame[newx][newy] = GfxFrame[x][y];
8458 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8459 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8460 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8462 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8464 // some elements can leave other elements behind after moving
8465 if (ei->move_leave_element != EL_EMPTY &&
8466 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8467 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8469 int move_leave_element = ei->move_leave_element;
8471 // this makes it possible to leave the removed element again
8472 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8473 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8475 Feld[x][y] = move_leave_element;
8477 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8478 MovDir[x][y] = direction;
8480 InitField(x, y, FALSE);
8482 if (GFX_CRUMBLED(Feld[x][y]))
8483 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8485 if (ELEM_IS_PLAYER(move_leave_element))
8486 RelocatePlayer(x, y, move_leave_element);
8489 // do this after checking for left-behind element
8490 ResetGfxAnimation(x, y); // reset animation values for old field
8492 if (!CAN_MOVE(element) ||
8493 (CAN_FALL(element) && direction == MV_DOWN &&
8494 (element == EL_SPRING ||
8495 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8496 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8497 GfxDir[x][y] = MovDir[newx][newy] = 0;
8499 TEST_DrawLevelField(x, y);
8500 TEST_DrawLevelField(newx, newy);
8502 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8504 // prevent pushed element from moving on in pushed direction
8505 if (pushed_by_player && CAN_MOVE(element) &&
8506 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8507 !(element_info[element].move_pattern & direction))
8508 TurnRound(newx, newy);
8510 // prevent elements on conveyor belt from moving on in last direction
8511 if (pushed_by_conveyor && CAN_FALL(element) &&
8512 direction & MV_HORIZONTAL)
8513 MovDir[newx][newy] = 0;
8515 if (!pushed_by_player)
8517 int nextx = newx + dx, nexty = newy + dy;
8518 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8520 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8522 if (CAN_FALL(element) && direction == MV_DOWN)
8523 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8525 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8526 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8528 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8529 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8532 if (DONT_TOUCH(element)) // object may be nasty to player or others
8534 TestIfBadThingTouchesPlayer(newx, newy);
8535 TestIfBadThingTouchesFriend(newx, newy);
8537 if (!IS_CUSTOM_ELEMENT(element))
8538 TestIfBadThingTouchesOtherBadThing(newx, newy);
8540 else if (element == EL_PENGUIN)
8541 TestIfFriendTouchesBadThing(newx, newy);
8543 if (DONT_GET_HIT_BY(element))
8545 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8548 // give the player one last chance (one more frame) to move away
8549 if (CAN_FALL(element) && direction == MV_DOWN &&
8550 (last_line || (!IS_FREE(x, newy + 1) &&
8551 (!IS_PLAYER(x, newy + 1) ||
8552 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8555 if (pushed_by_player && !game.use_change_when_pushing_bug)
8557 int push_side = MV_DIR_OPPOSITE(direction);
8558 struct PlayerInfo *player = PLAYERINFO(x, y);
8560 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8561 player->index_bit, push_side);
8562 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8563 player->index_bit, push_side);
8566 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8567 MovDelay[newx][newy] = 1;
8569 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8571 TestIfElementTouchesCustomElement(x, y); // empty or new element
8572 TestIfElementHitsCustomElement(newx, newy, direction);
8573 TestIfPlayerTouchesCustomElement(newx, newy);
8574 TestIfElementTouchesCustomElement(newx, newy);
8576 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8577 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8578 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8579 MV_DIR_OPPOSITE(direction));
8582 int AmoebeNachbarNr(int ax, int ay)
8585 int element = Feld[ax][ay];
8587 static int xy[4][2] =
8595 for (i = 0; i < NUM_DIRECTIONS; i++)
8597 int x = ax + xy[i][0];
8598 int y = ay + xy[i][1];
8600 if (!IN_LEV_FIELD(x, y))
8603 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8604 group_nr = AmoebaNr[x][y];
8610 static void AmoebenVereinigen(int ax, int ay)
8612 int i, x, y, xx, yy;
8613 int new_group_nr = AmoebaNr[ax][ay];
8614 static int xy[4][2] =
8622 if (new_group_nr == 0)
8625 for (i = 0; i < NUM_DIRECTIONS; i++)
8630 if (!IN_LEV_FIELD(x, y))
8633 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8634 Feld[x][y] == EL_BD_AMOEBA ||
8635 Feld[x][y] == EL_AMOEBA_DEAD) &&
8636 AmoebaNr[x][y] != new_group_nr)
8638 int old_group_nr = AmoebaNr[x][y];
8640 if (old_group_nr == 0)
8643 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8644 AmoebaCnt[old_group_nr] = 0;
8645 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8646 AmoebaCnt2[old_group_nr] = 0;
8648 SCAN_PLAYFIELD(xx, yy)
8650 if (AmoebaNr[xx][yy] == old_group_nr)
8651 AmoebaNr[xx][yy] = new_group_nr;
8657 void AmoebeUmwandeln(int ax, int ay)
8661 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8663 int group_nr = AmoebaNr[ax][ay];
8668 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8669 printf("AmoebeUmwandeln(): This should never happen!\n");
8674 SCAN_PLAYFIELD(x, y)
8676 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8679 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8683 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8684 SND_AMOEBA_TURNING_TO_GEM :
8685 SND_AMOEBA_TURNING_TO_ROCK));
8690 static int xy[4][2] =
8698 for (i = 0; i < NUM_DIRECTIONS; i++)
8703 if (!IN_LEV_FIELD(x, y))
8706 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8708 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8709 SND_AMOEBA_TURNING_TO_GEM :
8710 SND_AMOEBA_TURNING_TO_ROCK));
8717 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8720 int group_nr = AmoebaNr[ax][ay];
8721 boolean done = FALSE;
8726 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8727 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8732 SCAN_PLAYFIELD(x, y)
8734 if (AmoebaNr[x][y] == group_nr &&
8735 (Feld[x][y] == EL_AMOEBA_DEAD ||
8736 Feld[x][y] == EL_BD_AMOEBA ||
8737 Feld[x][y] == EL_AMOEBA_GROWING))
8740 Feld[x][y] = new_element;
8741 InitField(x, y, FALSE);
8742 TEST_DrawLevelField(x, y);
8748 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8749 SND_BD_AMOEBA_TURNING_TO_ROCK :
8750 SND_BD_AMOEBA_TURNING_TO_GEM));
8753 static void AmoebeWaechst(int x, int y)
8755 static unsigned int sound_delay = 0;
8756 static unsigned int sound_delay_value = 0;
8758 if (!MovDelay[x][y]) // start new growing cycle
8762 if (DelayReached(&sound_delay, sound_delay_value))
8764 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8765 sound_delay_value = 30;
8769 if (MovDelay[x][y]) // wait some time before growing bigger
8772 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8774 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8775 6 - MovDelay[x][y]);
8777 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8780 if (!MovDelay[x][y])
8782 Feld[x][y] = Store[x][y];
8784 TEST_DrawLevelField(x, y);
8789 static void AmoebaDisappearing(int x, int y)
8791 static unsigned int sound_delay = 0;
8792 static unsigned int sound_delay_value = 0;
8794 if (!MovDelay[x][y]) // start new shrinking cycle
8798 if (DelayReached(&sound_delay, sound_delay_value))
8799 sound_delay_value = 30;
8802 if (MovDelay[x][y]) // wait some time before shrinking
8805 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8807 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8808 6 - MovDelay[x][y]);
8810 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8813 if (!MovDelay[x][y])
8815 Feld[x][y] = EL_EMPTY;
8816 TEST_DrawLevelField(x, y);
8818 // don't let mole enter this field in this cycle;
8819 // (give priority to objects falling to this field from above)
8825 static void AmoebeAbleger(int ax, int ay)
8828 int element = Feld[ax][ay];
8829 int graphic = el2img(element);
8830 int newax = ax, neway = ay;
8831 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8832 static int xy[4][2] =
8840 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8842 Feld[ax][ay] = EL_AMOEBA_DEAD;
8843 TEST_DrawLevelField(ax, ay);
8847 if (IS_ANIMATED(graphic))
8848 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8850 if (!MovDelay[ax][ay]) // start making new amoeba field
8851 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8853 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8856 if (MovDelay[ax][ay])
8860 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8863 int x = ax + xy[start][0];
8864 int y = ay + xy[start][1];
8866 if (!IN_LEV_FIELD(x, y))
8869 if (IS_FREE(x, y) ||
8870 CAN_GROW_INTO(Feld[x][y]) ||
8871 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8872 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8878 if (newax == ax && neway == ay)
8881 else // normal or "filled" (BD style) amoeba
8884 boolean waiting_for_player = FALSE;
8886 for (i = 0; i < NUM_DIRECTIONS; i++)
8888 int j = (start + i) % 4;
8889 int x = ax + xy[j][0];
8890 int y = ay + xy[j][1];
8892 if (!IN_LEV_FIELD(x, y))
8895 if (IS_FREE(x, y) ||
8896 CAN_GROW_INTO(Feld[x][y]) ||
8897 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8898 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8904 else if (IS_PLAYER(x, y))
8905 waiting_for_player = TRUE;
8908 if (newax == ax && neway == ay) // amoeba cannot grow
8910 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
8912 Feld[ax][ay] = EL_AMOEBA_DEAD;
8913 TEST_DrawLevelField(ax, ay);
8914 AmoebaCnt[AmoebaNr[ax][ay]]--;
8916 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
8918 if (element == EL_AMOEBA_FULL)
8919 AmoebeUmwandeln(ax, ay);
8920 else if (element == EL_BD_AMOEBA)
8921 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
8926 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
8928 // amoeba gets larger by growing in some direction
8930 int new_group_nr = AmoebaNr[ax][ay];
8933 if (new_group_nr == 0)
8935 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
8936 printf("AmoebeAbleger(): This should never happen!\n");
8941 AmoebaNr[newax][neway] = new_group_nr;
8942 AmoebaCnt[new_group_nr]++;
8943 AmoebaCnt2[new_group_nr]++;
8945 // if amoeba touches other amoeba(s) after growing, unify them
8946 AmoebenVereinigen(newax, neway);
8948 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
8950 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
8956 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
8957 (neway == lev_fieldy - 1 && newax != ax))
8959 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
8960 Store[newax][neway] = element;
8962 else if (neway == ay || element == EL_EMC_DRIPPER)
8964 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
8966 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
8970 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
8971 Feld[ax][ay] = EL_AMOEBA_DROPPING;
8972 Store[ax][ay] = EL_AMOEBA_DROP;
8973 ContinueMoving(ax, ay);
8977 TEST_DrawLevelField(newax, neway);
8980 static void Life(int ax, int ay)
8984 int element = Feld[ax][ay];
8985 int graphic = el2img(element);
8986 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
8988 boolean changed = FALSE;
8990 if (IS_ANIMATED(graphic))
8991 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8996 if (!MovDelay[ax][ay]) // start new "game of life" cycle
8997 MovDelay[ax][ay] = life_time;
8999 if (MovDelay[ax][ay]) // wait some time before next cycle
9002 if (MovDelay[ax][ay])
9006 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9008 int xx = ax+x1, yy = ay+y1;
9009 int old_element = Feld[xx][yy];
9010 int num_neighbours = 0;
9012 if (!IN_LEV_FIELD(xx, yy))
9015 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9017 int x = xx+x2, y = yy+y2;
9019 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9022 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9023 boolean is_neighbour = FALSE;
9025 if (level.use_life_bugs)
9027 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9028 (IS_FREE(x, y) && Stop[x][y]));
9031 (Last[x][y] == element || is_player_cell);
9037 boolean is_free = FALSE;
9039 if (level.use_life_bugs)
9040 is_free = (IS_FREE(xx, yy));
9042 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9044 if (xx == ax && yy == ay) // field in the middle
9046 if (num_neighbours < life_parameter[0] ||
9047 num_neighbours > life_parameter[1])
9049 Feld[xx][yy] = EL_EMPTY;
9050 if (Feld[xx][yy] != old_element)
9051 TEST_DrawLevelField(xx, yy);
9052 Stop[xx][yy] = TRUE;
9056 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9057 { // free border field
9058 if (num_neighbours >= life_parameter[2] &&
9059 num_neighbours <= life_parameter[3])
9061 Feld[xx][yy] = element;
9062 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9063 if (Feld[xx][yy] != old_element)
9064 TEST_DrawLevelField(xx, yy);
9065 Stop[xx][yy] = TRUE;
9072 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9073 SND_GAME_OF_LIFE_GROWING);
9076 static void InitRobotWheel(int x, int y)
9078 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9081 static void RunRobotWheel(int x, int y)
9083 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9086 static void StopRobotWheel(int x, int y)
9088 if (game.robot_wheel_x == x &&
9089 game.robot_wheel_y == y)
9091 game.robot_wheel_x = -1;
9092 game.robot_wheel_y = -1;
9093 game.robot_wheel_active = FALSE;
9097 static void InitTimegateWheel(int x, int y)
9099 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9102 static void RunTimegateWheel(int x, int y)
9104 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9107 static void InitMagicBallDelay(int x, int y)
9109 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9112 static void ActivateMagicBall(int bx, int by)
9116 if (level.ball_random)
9118 int pos_border = RND(8); // select one of the eight border elements
9119 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9120 int xx = pos_content % 3;
9121 int yy = pos_content / 3;
9126 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9127 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9131 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9133 int xx = x - bx + 1;
9134 int yy = y - by + 1;
9136 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9137 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9141 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9144 static void CheckExit(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_EXIT_OPENING;
9166 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9169 static void CheckExitEM(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_EM_EXIT_OPENING;
9191 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9194 static void CheckExitSteel(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_STEEL_EXIT_OPENING;
9216 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9219 static void CheckExitSteelEM(int x, int y)
9221 if (game.gems_still_needed > 0 ||
9222 game.sokoban_fields_still_needed > 0 ||
9223 game.sokoban_objects_still_needed > 0 ||
9224 game.lights_still_needed > 0)
9226 int element = Feld[x][y];
9227 int graphic = el2img(element);
9229 if (IS_ANIMATED(graphic))
9230 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9235 // do not re-open exit door closed after last player
9236 if (game.all_players_gone)
9239 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9241 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9244 static void CheckExitSP(int x, int y)
9246 if (game.gems_still_needed > 0)
9248 int element = Feld[x][y];
9249 int graphic = el2img(element);
9251 if (IS_ANIMATED(graphic))
9252 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9257 // do not re-open exit door closed after last player
9258 if (game.all_players_gone)
9261 Feld[x][y] = EL_SP_EXIT_OPENING;
9263 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9266 static void CloseAllOpenTimegates(void)
9270 SCAN_PLAYFIELD(x, y)
9272 int element = Feld[x][y];
9274 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9276 Feld[x][y] = EL_TIMEGATE_CLOSING;
9278 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9283 static void DrawTwinkleOnField(int x, int y)
9285 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9288 if (Feld[x][y] == EL_BD_DIAMOND)
9291 if (MovDelay[x][y] == 0) // next animation frame
9292 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9294 if (MovDelay[x][y] != 0) // wait some time before next frame
9298 DrawLevelElementAnimation(x, y, Feld[x][y]);
9300 if (MovDelay[x][y] != 0)
9302 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9303 10 - MovDelay[x][y]);
9305 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9310 static void MauerWaechst(int x, int y)
9314 if (!MovDelay[x][y]) // next animation frame
9315 MovDelay[x][y] = 3 * delay;
9317 if (MovDelay[x][y]) // wait some time before next frame
9321 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9323 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9324 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9326 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9329 if (!MovDelay[x][y])
9331 if (MovDir[x][y] == MV_LEFT)
9333 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9334 TEST_DrawLevelField(x - 1, y);
9336 else if (MovDir[x][y] == MV_RIGHT)
9338 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9339 TEST_DrawLevelField(x + 1, y);
9341 else if (MovDir[x][y] == MV_UP)
9343 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9344 TEST_DrawLevelField(x, y - 1);
9348 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9349 TEST_DrawLevelField(x, y + 1);
9352 Feld[x][y] = Store[x][y];
9354 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9355 TEST_DrawLevelField(x, y);
9360 static void MauerAbleger(int ax, int ay)
9362 int element = Feld[ax][ay];
9363 int graphic = el2img(element);
9364 boolean oben_frei = FALSE, unten_frei = FALSE;
9365 boolean links_frei = FALSE, rechts_frei = FALSE;
9366 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9367 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9368 boolean new_wall = FALSE;
9370 if (IS_ANIMATED(graphic))
9371 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9373 if (!MovDelay[ax][ay]) // start building new wall
9374 MovDelay[ax][ay] = 6;
9376 if (MovDelay[ax][ay]) // wait some time before building new wall
9379 if (MovDelay[ax][ay])
9383 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9385 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9387 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9389 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9392 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9393 element == EL_EXPANDABLE_WALL_ANY)
9397 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9398 Store[ax][ay-1] = element;
9399 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9400 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9401 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9402 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9407 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9408 Store[ax][ay+1] = element;
9409 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9410 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9411 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9412 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9417 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9418 element == EL_EXPANDABLE_WALL_ANY ||
9419 element == EL_EXPANDABLE_WALL ||
9420 element == EL_BD_EXPANDABLE_WALL)
9424 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9425 Store[ax-1][ay] = element;
9426 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9427 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9428 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9429 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9435 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9436 Store[ax+1][ay] = element;
9437 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9438 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9439 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9440 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9445 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9446 TEST_DrawLevelField(ax, ay);
9448 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9450 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9451 unten_massiv = TRUE;
9452 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9453 links_massiv = TRUE;
9454 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9455 rechts_massiv = TRUE;
9457 if (((oben_massiv && unten_massiv) ||
9458 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9459 element == EL_EXPANDABLE_WALL) &&
9460 ((links_massiv && rechts_massiv) ||
9461 element == EL_EXPANDABLE_WALL_VERTICAL))
9462 Feld[ax][ay] = EL_WALL;
9465 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9468 static void MauerAblegerStahl(int ax, int ay)
9470 int element = Feld[ax][ay];
9471 int graphic = el2img(element);
9472 boolean oben_frei = FALSE, unten_frei = FALSE;
9473 boolean links_frei = FALSE, rechts_frei = FALSE;
9474 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9475 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9476 boolean new_wall = FALSE;
9478 if (IS_ANIMATED(graphic))
9479 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9481 if (!MovDelay[ax][ay]) // start building new wall
9482 MovDelay[ax][ay] = 6;
9484 if (MovDelay[ax][ay]) // wait some time before building new wall
9487 if (MovDelay[ax][ay])
9491 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9493 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9495 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9497 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9500 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9501 element == EL_EXPANDABLE_STEELWALL_ANY)
9505 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9506 Store[ax][ay-1] = element;
9507 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9508 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9509 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9510 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9515 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9516 Store[ax][ay+1] = element;
9517 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9518 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9519 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9520 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9525 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9526 element == EL_EXPANDABLE_STEELWALL_ANY)
9530 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9531 Store[ax-1][ay] = element;
9532 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9533 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9534 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9535 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9541 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9542 Store[ax+1][ay] = element;
9543 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9544 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9545 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9546 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9551 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9553 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9554 unten_massiv = TRUE;
9555 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9556 links_massiv = TRUE;
9557 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9558 rechts_massiv = TRUE;
9560 if (((oben_massiv && unten_massiv) ||
9561 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9562 ((links_massiv && rechts_massiv) ||
9563 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9564 Feld[ax][ay] = EL_STEELWALL;
9567 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9570 static void CheckForDragon(int x, int y)
9573 boolean dragon_found = FALSE;
9574 static int xy[4][2] =
9582 for (i = 0; i < NUM_DIRECTIONS; i++)
9584 for (j = 0; j < 4; j++)
9586 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9588 if (IN_LEV_FIELD(xx, yy) &&
9589 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9591 if (Feld[xx][yy] == EL_DRAGON)
9592 dragon_found = TRUE;
9601 for (i = 0; i < NUM_DIRECTIONS; i++)
9603 for (j = 0; j < 3; j++)
9605 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9607 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9609 Feld[xx][yy] = EL_EMPTY;
9610 TEST_DrawLevelField(xx, yy);
9619 static void InitBuggyBase(int x, int y)
9621 int element = Feld[x][y];
9622 int activating_delay = FRAMES_PER_SECOND / 4;
9625 (element == EL_SP_BUGGY_BASE ?
9626 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9627 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9629 element == EL_SP_BUGGY_BASE_ACTIVE ?
9630 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9633 static void WarnBuggyBase(int x, int y)
9636 static int xy[4][2] =
9644 for (i = 0; i < NUM_DIRECTIONS; i++)
9646 int xx = x + xy[i][0];
9647 int yy = y + xy[i][1];
9649 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9651 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9658 static void InitTrap(int x, int y)
9660 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9663 static void ActivateTrap(int x, int y)
9665 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9668 static void ChangeActiveTrap(int x, int y)
9670 int graphic = IMG_TRAP_ACTIVE;
9672 // if new animation frame was drawn, correct crumbled sand border
9673 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9674 TEST_DrawLevelFieldCrumbled(x, y);
9677 static int getSpecialActionElement(int element, int number, int base_element)
9679 return (element != EL_EMPTY ? element :
9680 number != -1 ? base_element + number - 1 :
9684 static int getModifiedActionNumber(int value_old, int operator, int operand,
9685 int value_min, int value_max)
9687 int value_new = (operator == CA_MODE_SET ? operand :
9688 operator == CA_MODE_ADD ? value_old + operand :
9689 operator == CA_MODE_SUBTRACT ? value_old - operand :
9690 operator == CA_MODE_MULTIPLY ? value_old * operand :
9691 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9692 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9695 return (value_new < value_min ? value_min :
9696 value_new > value_max ? value_max :
9700 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9702 struct ElementInfo *ei = &element_info[element];
9703 struct ElementChangeInfo *change = &ei->change_page[page];
9704 int target_element = change->target_element;
9705 int action_type = change->action_type;
9706 int action_mode = change->action_mode;
9707 int action_arg = change->action_arg;
9708 int action_element = change->action_element;
9711 if (!change->has_action)
9714 // ---------- determine action paramater values -----------------------------
9716 int level_time_value =
9717 (level.time > 0 ? TimeLeft :
9720 int action_arg_element_raw =
9721 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9722 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9723 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9724 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9725 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9726 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9727 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9729 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9731 int action_arg_direction =
9732 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9733 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9734 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9735 change->actual_trigger_side :
9736 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9737 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9740 int action_arg_number_min =
9741 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9744 int action_arg_number_max =
9745 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9746 action_type == CA_SET_LEVEL_GEMS ? 999 :
9747 action_type == CA_SET_LEVEL_TIME ? 9999 :
9748 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9749 action_type == CA_SET_CE_VALUE ? 9999 :
9750 action_type == CA_SET_CE_SCORE ? 9999 :
9753 int action_arg_number_reset =
9754 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9755 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9756 action_type == CA_SET_LEVEL_TIME ? level.time :
9757 action_type == CA_SET_LEVEL_SCORE ? 0 :
9758 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9759 action_type == CA_SET_CE_SCORE ? 0 :
9762 int action_arg_number =
9763 (action_arg <= CA_ARG_MAX ? action_arg :
9764 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9765 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9766 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9767 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9768 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9769 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9770 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9771 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9772 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9773 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9774 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9775 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9776 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9777 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9778 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9779 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9780 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9781 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9782 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9783 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9784 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9787 int action_arg_number_old =
9788 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9789 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9790 action_type == CA_SET_LEVEL_SCORE ? game.score :
9791 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9792 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9795 int action_arg_number_new =
9796 getModifiedActionNumber(action_arg_number_old,
9797 action_mode, action_arg_number,
9798 action_arg_number_min, action_arg_number_max);
9800 int trigger_player_bits =
9801 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9802 change->actual_trigger_player_bits : change->trigger_player);
9804 int action_arg_player_bits =
9805 (action_arg >= CA_ARG_PLAYER_1 &&
9806 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9807 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9808 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9811 // ---------- execute action -----------------------------------------------
9813 switch (action_type)
9820 // ---------- level actions ----------------------------------------------
9822 case CA_RESTART_LEVEL:
9824 game.restart_level = TRUE;
9829 case CA_SHOW_ENVELOPE:
9831 int element = getSpecialActionElement(action_arg_element,
9832 action_arg_number, EL_ENVELOPE_1);
9834 if (IS_ENVELOPE(element))
9835 local_player->show_envelope = element;
9840 case CA_SET_LEVEL_TIME:
9842 if (level.time > 0) // only modify limited time value
9844 TimeLeft = action_arg_number_new;
9846 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9848 DisplayGameControlValues();
9850 if (!TimeLeft && setup.time_limit)
9851 for (i = 0; i < MAX_PLAYERS; i++)
9852 KillPlayer(&stored_player[i]);
9858 case CA_SET_LEVEL_SCORE:
9860 game.score = action_arg_number_new;
9862 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9864 DisplayGameControlValues();
9869 case CA_SET_LEVEL_GEMS:
9871 game.gems_still_needed = action_arg_number_new;
9873 game.snapshot.collected_item = TRUE;
9875 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9877 DisplayGameControlValues();
9882 case CA_SET_LEVEL_WIND:
9884 game.wind_direction = action_arg_direction;
9889 case CA_SET_LEVEL_RANDOM_SEED:
9891 // ensure that setting a new random seed while playing is predictable
9892 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
9897 // ---------- player actions ---------------------------------------------
9899 case CA_MOVE_PLAYER:
9900 case CA_MOVE_PLAYER_NEW:
9902 // automatically move to the next field in specified direction
9903 for (i = 0; i < MAX_PLAYERS; i++)
9904 if (trigger_player_bits & (1 << i))
9905 if (action_type == CA_MOVE_PLAYER ||
9906 stored_player[i].MovPos == 0)
9907 stored_player[i].programmed_action = action_arg_direction;
9912 case CA_EXIT_PLAYER:
9914 for (i = 0; i < MAX_PLAYERS; i++)
9915 if (action_arg_player_bits & (1 << i))
9916 ExitPlayer(&stored_player[i]);
9918 if (game.players_still_needed == 0)
9924 case CA_KILL_PLAYER:
9926 for (i = 0; i < MAX_PLAYERS; i++)
9927 if (action_arg_player_bits & (1 << i))
9928 KillPlayer(&stored_player[i]);
9933 case CA_SET_PLAYER_KEYS:
9935 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
9936 int element = getSpecialActionElement(action_arg_element,
9937 action_arg_number, EL_KEY_1);
9939 if (IS_KEY(element))
9941 for (i = 0; i < MAX_PLAYERS; i++)
9943 if (trigger_player_bits & (1 << i))
9945 stored_player[i].key[KEY_NR(element)] = key_state;
9947 DrawGameDoorValues();
9955 case CA_SET_PLAYER_SPEED:
9957 for (i = 0; i < MAX_PLAYERS; i++)
9959 if (trigger_player_bits & (1 << i))
9961 int move_stepsize = TILEX / stored_player[i].move_delay_value;
9963 if (action_arg == CA_ARG_SPEED_FASTER &&
9964 stored_player[i].cannot_move)
9966 action_arg_number = STEPSIZE_VERY_SLOW;
9968 else if (action_arg == CA_ARG_SPEED_SLOWER ||
9969 action_arg == CA_ARG_SPEED_FASTER)
9971 action_arg_number = 2;
9972 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
9975 else if (action_arg == CA_ARG_NUMBER_RESET)
9977 action_arg_number = level.initial_player_stepsize[i];
9981 getModifiedActionNumber(move_stepsize,
9984 action_arg_number_min,
9985 action_arg_number_max);
9987 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
9994 case CA_SET_PLAYER_SHIELD:
9996 for (i = 0; i < MAX_PLAYERS; i++)
9998 if (trigger_player_bits & (1 << i))
10000 if (action_arg == CA_ARG_SHIELD_OFF)
10002 stored_player[i].shield_normal_time_left = 0;
10003 stored_player[i].shield_deadly_time_left = 0;
10005 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10007 stored_player[i].shield_normal_time_left = 999999;
10009 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10011 stored_player[i].shield_normal_time_left = 999999;
10012 stored_player[i].shield_deadly_time_left = 999999;
10020 case CA_SET_PLAYER_GRAVITY:
10022 for (i = 0; i < MAX_PLAYERS; i++)
10024 if (trigger_player_bits & (1 << i))
10026 stored_player[i].gravity =
10027 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10028 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10029 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10030 stored_player[i].gravity);
10037 case CA_SET_PLAYER_ARTWORK:
10039 for (i = 0; i < MAX_PLAYERS; i++)
10041 if (trigger_player_bits & (1 << i))
10043 int artwork_element = action_arg_element;
10045 if (action_arg == CA_ARG_ELEMENT_RESET)
10047 (level.use_artwork_element[i] ? level.artwork_element[i] :
10048 stored_player[i].element_nr);
10050 if (stored_player[i].artwork_element != artwork_element)
10051 stored_player[i].Frame = 0;
10053 stored_player[i].artwork_element = artwork_element;
10055 SetPlayerWaiting(&stored_player[i], FALSE);
10057 // set number of special actions for bored and sleeping animation
10058 stored_player[i].num_special_action_bored =
10059 get_num_special_action(artwork_element,
10060 ACTION_BORING_1, ACTION_BORING_LAST);
10061 stored_player[i].num_special_action_sleeping =
10062 get_num_special_action(artwork_element,
10063 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10070 case CA_SET_PLAYER_INVENTORY:
10072 for (i = 0; i < MAX_PLAYERS; i++)
10074 struct PlayerInfo *player = &stored_player[i];
10077 if (trigger_player_bits & (1 << i))
10079 int inventory_element = action_arg_element;
10081 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10082 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10083 action_arg == CA_ARG_ELEMENT_ACTION)
10085 int element = inventory_element;
10086 int collect_count = element_info[element].collect_count_initial;
10088 if (!IS_CUSTOM_ELEMENT(element))
10091 if (collect_count == 0)
10092 player->inventory_infinite_element = element;
10094 for (k = 0; k < collect_count; k++)
10095 if (player->inventory_size < MAX_INVENTORY_SIZE)
10096 player->inventory_element[player->inventory_size++] =
10099 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10100 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10101 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10103 if (player->inventory_infinite_element != EL_UNDEFINED &&
10104 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10105 action_arg_element_raw))
10106 player->inventory_infinite_element = EL_UNDEFINED;
10108 for (k = 0, j = 0; j < player->inventory_size; j++)
10110 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10111 action_arg_element_raw))
10112 player->inventory_element[k++] = player->inventory_element[j];
10115 player->inventory_size = k;
10117 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10119 if (player->inventory_size > 0)
10121 for (j = 0; j < player->inventory_size - 1; j++)
10122 player->inventory_element[j] = player->inventory_element[j + 1];
10124 player->inventory_size--;
10127 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10129 if (player->inventory_size > 0)
10130 player->inventory_size--;
10132 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10134 player->inventory_infinite_element = EL_UNDEFINED;
10135 player->inventory_size = 0;
10137 else if (action_arg == CA_ARG_INVENTORY_RESET)
10139 player->inventory_infinite_element = EL_UNDEFINED;
10140 player->inventory_size = 0;
10142 if (level.use_initial_inventory[i])
10144 for (j = 0; j < level.initial_inventory_size[i]; j++)
10146 int element = level.initial_inventory_content[i][j];
10147 int collect_count = element_info[element].collect_count_initial;
10149 if (!IS_CUSTOM_ELEMENT(element))
10152 if (collect_count == 0)
10153 player->inventory_infinite_element = element;
10155 for (k = 0; k < collect_count; k++)
10156 if (player->inventory_size < MAX_INVENTORY_SIZE)
10157 player->inventory_element[player->inventory_size++] =
10168 // ---------- CE actions -------------------------------------------------
10170 case CA_SET_CE_VALUE:
10172 int last_ce_value = CustomValue[x][y];
10174 CustomValue[x][y] = action_arg_number_new;
10176 if (CustomValue[x][y] != last_ce_value)
10178 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10179 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10181 if (CustomValue[x][y] == 0)
10183 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10184 ChangeCount[x][y] = 0; // allow at least one more change
10186 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10187 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10194 case CA_SET_CE_SCORE:
10196 int last_ce_score = ei->collect_score;
10198 ei->collect_score = action_arg_number_new;
10200 if (ei->collect_score != last_ce_score)
10202 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10203 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10205 if (ei->collect_score == 0)
10209 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10210 ChangeCount[x][y] = 0; // allow at least one more change
10212 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10213 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10216 This is a very special case that seems to be a mixture between
10217 CheckElementChange() and CheckTriggeredElementChange(): while
10218 the first one only affects single elements that are triggered
10219 directly, the second one affects multiple elements in the playfield
10220 that are triggered indirectly by another element. This is a third
10221 case: Changing the CE score always affects multiple identical CEs,
10222 so every affected CE must be checked, not only the single CE for
10223 which the CE score was changed in the first place (as every instance
10224 of that CE shares the same CE score, and therefore also can change)!
10226 SCAN_PLAYFIELD(xx, yy)
10228 if (Feld[xx][yy] == element)
10229 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10230 CE_SCORE_GETS_ZERO);
10238 case CA_SET_CE_ARTWORK:
10240 int artwork_element = action_arg_element;
10241 boolean reset_frame = FALSE;
10244 if (action_arg == CA_ARG_ELEMENT_RESET)
10245 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10248 if (ei->gfx_element != artwork_element)
10249 reset_frame = TRUE;
10251 ei->gfx_element = artwork_element;
10253 SCAN_PLAYFIELD(xx, yy)
10255 if (Feld[xx][yy] == element)
10259 ResetGfxAnimation(xx, yy);
10260 ResetRandomAnimationValue(xx, yy);
10263 TEST_DrawLevelField(xx, yy);
10270 // ---------- engine actions ---------------------------------------------
10272 case CA_SET_ENGINE_SCAN_MODE:
10274 InitPlayfieldScanMode(action_arg);
10284 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10286 int old_element = Feld[x][y];
10287 int new_element = GetElementFromGroupElement(element);
10288 int previous_move_direction = MovDir[x][y];
10289 int last_ce_value = CustomValue[x][y];
10290 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10291 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10292 boolean add_player_onto_element = (new_element_is_player &&
10293 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10294 IS_WALKABLE(old_element));
10296 if (!add_player_onto_element)
10298 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10299 RemoveMovingField(x, y);
10303 Feld[x][y] = new_element;
10305 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10306 MovDir[x][y] = previous_move_direction;
10308 if (element_info[new_element].use_last_ce_value)
10309 CustomValue[x][y] = last_ce_value;
10311 InitField_WithBug1(x, y, FALSE);
10313 new_element = Feld[x][y]; // element may have changed
10315 ResetGfxAnimation(x, y);
10316 ResetRandomAnimationValue(x, y);
10318 TEST_DrawLevelField(x, y);
10320 if (GFX_CRUMBLED(new_element))
10321 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10324 // check if element under the player changes from accessible to unaccessible
10325 // (needed for special case of dropping element which then changes)
10326 // (must be checked after creating new element for walkable group elements)
10327 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10328 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10335 // "ChangeCount" not set yet to allow "entered by player" change one time
10336 if (new_element_is_player)
10337 RelocatePlayer(x, y, new_element);
10340 ChangeCount[x][y]++; // count number of changes in the same frame
10342 TestIfBadThingTouchesPlayer(x, y);
10343 TestIfPlayerTouchesCustomElement(x, y);
10344 TestIfElementTouchesCustomElement(x, y);
10347 static void CreateField(int x, int y, int element)
10349 CreateFieldExt(x, y, element, FALSE);
10352 static void CreateElementFromChange(int x, int y, int element)
10354 element = GET_VALID_RUNTIME_ELEMENT(element);
10356 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10358 int old_element = Feld[x][y];
10360 // prevent changed element from moving in same engine frame
10361 // unless both old and new element can either fall or move
10362 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10363 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10367 CreateFieldExt(x, y, element, TRUE);
10370 static boolean ChangeElement(int x, int y, int element, int page)
10372 struct ElementInfo *ei = &element_info[element];
10373 struct ElementChangeInfo *change = &ei->change_page[page];
10374 int ce_value = CustomValue[x][y];
10375 int ce_score = ei->collect_score;
10376 int target_element;
10377 int old_element = Feld[x][y];
10379 // always use default change event to prevent running into a loop
10380 if (ChangeEvent[x][y] == -1)
10381 ChangeEvent[x][y] = CE_DELAY;
10383 if (ChangeEvent[x][y] == CE_DELAY)
10385 // reset actual trigger element, trigger player and action element
10386 change->actual_trigger_element = EL_EMPTY;
10387 change->actual_trigger_player = EL_EMPTY;
10388 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10389 change->actual_trigger_side = CH_SIDE_NONE;
10390 change->actual_trigger_ce_value = 0;
10391 change->actual_trigger_ce_score = 0;
10394 // do not change elements more than a specified maximum number of changes
10395 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10398 ChangeCount[x][y]++; // count number of changes in the same frame
10400 if (change->explode)
10407 if (change->use_target_content)
10409 boolean complete_replace = TRUE;
10410 boolean can_replace[3][3];
10413 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10416 boolean is_walkable;
10417 boolean is_diggable;
10418 boolean is_collectible;
10419 boolean is_removable;
10420 boolean is_destructible;
10421 int ex = x + xx - 1;
10422 int ey = y + yy - 1;
10423 int content_element = change->target_content.e[xx][yy];
10426 can_replace[xx][yy] = TRUE;
10428 if (ex == x && ey == y) // do not check changing element itself
10431 if (content_element == EL_EMPTY_SPACE)
10433 can_replace[xx][yy] = FALSE; // do not replace border with space
10438 if (!IN_LEV_FIELD(ex, ey))
10440 can_replace[xx][yy] = FALSE;
10441 complete_replace = FALSE;
10448 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10449 e = MovingOrBlocked2Element(ex, ey);
10451 is_empty = (IS_FREE(ex, ey) ||
10452 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10454 is_walkable = (is_empty || IS_WALKABLE(e));
10455 is_diggable = (is_empty || IS_DIGGABLE(e));
10456 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10457 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10458 is_removable = (is_diggable || is_collectible);
10460 can_replace[xx][yy] =
10461 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10462 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10463 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10464 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10465 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10466 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10467 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10469 if (!can_replace[xx][yy])
10470 complete_replace = FALSE;
10473 if (!change->only_if_complete || complete_replace)
10475 boolean something_has_changed = FALSE;
10477 if (change->only_if_complete && change->use_random_replace &&
10478 RND(100) < change->random_percentage)
10481 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10483 int ex = x + xx - 1;
10484 int ey = y + yy - 1;
10485 int content_element;
10487 if (can_replace[xx][yy] && (!change->use_random_replace ||
10488 RND(100) < change->random_percentage))
10490 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10491 RemoveMovingField(ex, ey);
10493 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10495 content_element = change->target_content.e[xx][yy];
10496 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10497 ce_value, ce_score);
10499 CreateElementFromChange(ex, ey, target_element);
10501 something_has_changed = TRUE;
10503 // for symmetry reasons, freeze newly created border elements
10504 if (ex != x || ey != y)
10505 Stop[ex][ey] = TRUE; // no more moving in this frame
10509 if (something_has_changed)
10511 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10512 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10518 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10519 ce_value, ce_score);
10521 if (element == EL_DIAGONAL_GROWING ||
10522 element == EL_DIAGONAL_SHRINKING)
10524 target_element = Store[x][y];
10526 Store[x][y] = EL_EMPTY;
10529 CreateElementFromChange(x, y, target_element);
10531 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10532 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10535 // this uses direct change before indirect change
10536 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10541 static void HandleElementChange(int x, int y, int page)
10543 int element = MovingOrBlocked2Element(x, y);
10544 struct ElementInfo *ei = &element_info[element];
10545 struct ElementChangeInfo *change = &ei->change_page[page];
10546 boolean handle_action_before_change = FALSE;
10549 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10550 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10553 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10554 x, y, element, element_info[element].token_name);
10555 printf("HandleElementChange(): This should never happen!\n");
10560 // this can happen with classic bombs on walkable, changing elements
10561 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10566 if (ChangeDelay[x][y] == 0) // initialize element change
10568 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10570 if (change->can_change)
10572 // !!! not clear why graphic animation should be reset at all here !!!
10573 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10574 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10577 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10579 When using an animation frame delay of 1 (this only happens with
10580 "sp_zonk.moving.left/right" in the classic graphics), the default
10581 (non-moving) animation shows wrong animation frames (while the
10582 moving animation, like "sp_zonk.moving.left/right", is correct,
10583 so this graphical bug never shows up with the classic graphics).
10584 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10585 be drawn instead of the correct frames 0,1,2,3. This is caused by
10586 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10587 an element change: First when the change delay ("ChangeDelay[][]")
10588 counter has reached zero after decrementing, then a second time in
10589 the next frame (after "GfxFrame[][]" was already incremented) when
10590 "ChangeDelay[][]" is reset to the initial delay value again.
10592 This causes frame 0 to be drawn twice, while the last frame won't
10593 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10595 As some animations may already be cleverly designed around this bug
10596 (at least the "Snake Bite" snake tail animation does this), it cannot
10597 simply be fixed here without breaking such existing animations.
10598 Unfortunately, it cannot easily be detected if a graphics set was
10599 designed "before" or "after" the bug was fixed. As a workaround,
10600 a new graphics set option "game.graphics_engine_version" was added
10601 to be able to specify the game's major release version for which the
10602 graphics set was designed, which can then be used to decide if the
10603 bugfix should be used (version 4 and above) or not (version 3 or
10604 below, or if no version was specified at all, as with old sets).
10606 (The wrong/fixed animation frames can be tested with the test level set
10607 "test_gfxframe" and level "000", which contains a specially prepared
10608 custom element at level position (x/y) == (11/9) which uses the zonk
10609 animation mentioned above. Using "game.graphics_engine_version: 4"
10610 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10611 This can also be seen from the debug output for this test element.)
10614 // when a custom element is about to change (for example by change delay),
10615 // do not reset graphic animation when the custom element is moving
10616 if (game.graphics_engine_version < 4 &&
10619 ResetGfxAnimation(x, y);
10620 ResetRandomAnimationValue(x, y);
10623 if (change->pre_change_function)
10624 change->pre_change_function(x, y);
10628 ChangeDelay[x][y]--;
10630 if (ChangeDelay[x][y] != 0) // continue element change
10632 if (change->can_change)
10634 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10636 if (IS_ANIMATED(graphic))
10637 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10639 if (change->change_function)
10640 change->change_function(x, y);
10643 else // finish element change
10645 if (ChangePage[x][y] != -1) // remember page from delayed change
10647 page = ChangePage[x][y];
10648 ChangePage[x][y] = -1;
10650 change = &ei->change_page[page];
10653 if (IS_MOVING(x, y)) // never change a running system ;-)
10655 ChangeDelay[x][y] = 1; // try change after next move step
10656 ChangePage[x][y] = page; // remember page to use for change
10661 // special case: set new level random seed before changing element
10662 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10663 handle_action_before_change = TRUE;
10665 if (change->has_action && handle_action_before_change)
10666 ExecuteCustomElementAction(x, y, element, page);
10668 if (change->can_change)
10670 if (ChangeElement(x, y, element, page))
10672 if (change->post_change_function)
10673 change->post_change_function(x, y);
10677 if (change->has_action && !handle_action_before_change)
10678 ExecuteCustomElementAction(x, y, element, page);
10682 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10683 int trigger_element,
10685 int trigger_player,
10689 boolean change_done_any = FALSE;
10690 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10693 if (!(trigger_events[trigger_element][trigger_event]))
10696 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10698 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10700 int element = EL_CUSTOM_START + i;
10701 boolean change_done = FALSE;
10704 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10705 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10708 for (p = 0; p < element_info[element].num_change_pages; p++)
10710 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10712 if (change->can_change_or_has_action &&
10713 change->has_event[trigger_event] &&
10714 change->trigger_side & trigger_side &&
10715 change->trigger_player & trigger_player &&
10716 change->trigger_page & trigger_page_bits &&
10717 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10719 change->actual_trigger_element = trigger_element;
10720 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10721 change->actual_trigger_player_bits = trigger_player;
10722 change->actual_trigger_side = trigger_side;
10723 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10724 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10726 if ((change->can_change && !change_done) || change->has_action)
10730 SCAN_PLAYFIELD(x, y)
10732 if (Feld[x][y] == element)
10734 if (change->can_change && !change_done)
10736 // if element already changed in this frame, not only prevent
10737 // another element change (checked in ChangeElement()), but
10738 // also prevent additional element actions for this element
10740 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10741 !level.use_action_after_change_bug)
10744 ChangeDelay[x][y] = 1;
10745 ChangeEvent[x][y] = trigger_event;
10747 HandleElementChange(x, y, p);
10749 else if (change->has_action)
10751 // if element already changed in this frame, not only prevent
10752 // another element change (checked in ChangeElement()), but
10753 // also prevent additional element actions for this element
10755 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10756 !level.use_action_after_change_bug)
10759 ExecuteCustomElementAction(x, y, element, p);
10760 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10765 if (change->can_change)
10767 change_done = TRUE;
10768 change_done_any = TRUE;
10775 RECURSION_LOOP_DETECTION_END();
10777 return change_done_any;
10780 static boolean CheckElementChangeExt(int x, int y,
10782 int trigger_element,
10784 int trigger_player,
10787 boolean change_done = FALSE;
10790 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10791 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10794 if (Feld[x][y] == EL_BLOCKED)
10796 Blocked2Moving(x, y, &x, &y);
10797 element = Feld[x][y];
10800 // check if element has already changed or is about to change after moving
10801 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10802 Feld[x][y] != element) ||
10804 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10805 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10806 ChangePage[x][y] != -1)))
10809 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10811 for (p = 0; p < element_info[element].num_change_pages; p++)
10813 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10815 /* check trigger element for all events where the element that is checked
10816 for changing interacts with a directly adjacent element -- this is
10817 different to element changes that affect other elements to change on the
10818 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10819 boolean check_trigger_element =
10820 (trigger_event == CE_TOUCHING_X ||
10821 trigger_event == CE_HITTING_X ||
10822 trigger_event == CE_HIT_BY_X ||
10823 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10825 if (change->can_change_or_has_action &&
10826 change->has_event[trigger_event] &&
10827 change->trigger_side & trigger_side &&
10828 change->trigger_player & trigger_player &&
10829 (!check_trigger_element ||
10830 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10832 change->actual_trigger_element = trigger_element;
10833 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10834 change->actual_trigger_player_bits = trigger_player;
10835 change->actual_trigger_side = trigger_side;
10836 change->actual_trigger_ce_value = CustomValue[x][y];
10837 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10839 // special case: trigger element not at (x,y) position for some events
10840 if (check_trigger_element)
10852 { 0, 0 }, { 0, 0 }, { 0, 0 },
10856 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10857 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10859 change->actual_trigger_ce_value = CustomValue[xx][yy];
10860 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10863 if (change->can_change && !change_done)
10865 ChangeDelay[x][y] = 1;
10866 ChangeEvent[x][y] = trigger_event;
10868 HandleElementChange(x, y, p);
10870 change_done = TRUE;
10872 else if (change->has_action)
10874 ExecuteCustomElementAction(x, y, element, p);
10875 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10880 RECURSION_LOOP_DETECTION_END();
10882 return change_done;
10885 static void PlayPlayerSound(struct PlayerInfo *player)
10887 int jx = player->jx, jy = player->jy;
10888 int sound_element = player->artwork_element;
10889 int last_action = player->last_action_waiting;
10890 int action = player->action_waiting;
10892 if (player->is_waiting)
10894 if (action != last_action)
10895 PlayLevelSoundElementAction(jx, jy, sound_element, action);
10897 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
10901 if (action != last_action)
10902 StopSound(element_info[sound_element].sound[last_action]);
10904 if (last_action == ACTION_SLEEPING)
10905 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
10909 static void PlayAllPlayersSound(void)
10913 for (i = 0; i < MAX_PLAYERS; i++)
10914 if (stored_player[i].active)
10915 PlayPlayerSound(&stored_player[i]);
10918 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
10920 boolean last_waiting = player->is_waiting;
10921 int move_dir = player->MovDir;
10923 player->dir_waiting = move_dir;
10924 player->last_action_waiting = player->action_waiting;
10928 if (!last_waiting) // not waiting -> waiting
10930 player->is_waiting = TRUE;
10932 player->frame_counter_bored =
10934 game.player_boring_delay_fixed +
10935 GetSimpleRandom(game.player_boring_delay_random);
10936 player->frame_counter_sleeping =
10938 game.player_sleeping_delay_fixed +
10939 GetSimpleRandom(game.player_sleeping_delay_random);
10941 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
10944 if (game.player_sleeping_delay_fixed +
10945 game.player_sleeping_delay_random > 0 &&
10946 player->anim_delay_counter == 0 &&
10947 player->post_delay_counter == 0 &&
10948 FrameCounter >= player->frame_counter_sleeping)
10949 player->is_sleeping = TRUE;
10950 else if (game.player_boring_delay_fixed +
10951 game.player_boring_delay_random > 0 &&
10952 FrameCounter >= player->frame_counter_bored)
10953 player->is_bored = TRUE;
10955 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
10956 player->is_bored ? ACTION_BORING :
10959 if (player->is_sleeping && player->use_murphy)
10961 // special case for sleeping Murphy when leaning against non-free tile
10963 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
10964 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
10965 !IS_MOVING(player->jx - 1, player->jy)))
10966 move_dir = MV_LEFT;
10967 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
10968 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
10969 !IS_MOVING(player->jx + 1, player->jy)))
10970 move_dir = MV_RIGHT;
10972 player->is_sleeping = FALSE;
10974 player->dir_waiting = move_dir;
10977 if (player->is_sleeping)
10979 if (player->num_special_action_sleeping > 0)
10981 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
10983 int last_special_action = player->special_action_sleeping;
10984 int num_special_action = player->num_special_action_sleeping;
10985 int special_action =
10986 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
10987 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
10988 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
10989 last_special_action + 1 : ACTION_SLEEPING);
10990 int special_graphic =
10991 el_act_dir2img(player->artwork_element, special_action, move_dir);
10993 player->anim_delay_counter =
10994 graphic_info[special_graphic].anim_delay_fixed +
10995 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
10996 player->post_delay_counter =
10997 graphic_info[special_graphic].post_delay_fixed +
10998 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11000 player->special_action_sleeping = special_action;
11003 if (player->anim_delay_counter > 0)
11005 player->action_waiting = player->special_action_sleeping;
11006 player->anim_delay_counter--;
11008 else if (player->post_delay_counter > 0)
11010 player->post_delay_counter--;
11014 else if (player->is_bored)
11016 if (player->num_special_action_bored > 0)
11018 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11020 int special_action =
11021 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11022 int special_graphic =
11023 el_act_dir2img(player->artwork_element, special_action, move_dir);
11025 player->anim_delay_counter =
11026 graphic_info[special_graphic].anim_delay_fixed +
11027 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11028 player->post_delay_counter =
11029 graphic_info[special_graphic].post_delay_fixed +
11030 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11032 player->special_action_bored = special_action;
11035 if (player->anim_delay_counter > 0)
11037 player->action_waiting = player->special_action_bored;
11038 player->anim_delay_counter--;
11040 else if (player->post_delay_counter > 0)
11042 player->post_delay_counter--;
11047 else if (last_waiting) // waiting -> not waiting
11049 player->is_waiting = FALSE;
11050 player->is_bored = FALSE;
11051 player->is_sleeping = FALSE;
11053 player->frame_counter_bored = -1;
11054 player->frame_counter_sleeping = -1;
11056 player->anim_delay_counter = 0;
11057 player->post_delay_counter = 0;
11059 player->dir_waiting = player->MovDir;
11060 player->action_waiting = ACTION_DEFAULT;
11062 player->special_action_bored = ACTION_DEFAULT;
11063 player->special_action_sleeping = ACTION_DEFAULT;
11067 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11069 if ((!player->is_moving && player->was_moving) ||
11070 (player->MovPos == 0 && player->was_moving) ||
11071 (player->is_snapping && !player->was_snapping) ||
11072 (player->is_dropping && !player->was_dropping))
11074 if (!CheckSaveEngineSnapshotToList())
11077 player->was_moving = FALSE;
11078 player->was_snapping = TRUE;
11079 player->was_dropping = TRUE;
11083 if (player->is_moving)
11084 player->was_moving = TRUE;
11086 if (!player->is_snapping)
11087 player->was_snapping = FALSE;
11089 if (!player->is_dropping)
11090 player->was_dropping = FALSE;
11094 static void CheckSingleStepMode(struct PlayerInfo *player)
11096 if (tape.single_step && tape.recording && !tape.pausing)
11098 /* as it is called "single step mode", just return to pause mode when the
11099 player stopped moving after one tile (or never starts moving at all) */
11100 if (!player->is_moving &&
11101 !player->is_pushing &&
11102 !player->is_dropping_pressed)
11103 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11106 CheckSaveEngineSnapshot(player);
11109 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11111 int left = player_action & JOY_LEFT;
11112 int right = player_action & JOY_RIGHT;
11113 int up = player_action & JOY_UP;
11114 int down = player_action & JOY_DOWN;
11115 int button1 = player_action & JOY_BUTTON_1;
11116 int button2 = player_action & JOY_BUTTON_2;
11117 int dx = (left ? -1 : right ? 1 : 0);
11118 int dy = (up ? -1 : down ? 1 : 0);
11120 if (!player->active || tape.pausing)
11126 SnapField(player, dx, dy);
11130 DropElement(player);
11132 MovePlayer(player, dx, dy);
11135 CheckSingleStepMode(player);
11137 SetPlayerWaiting(player, FALSE);
11139 return player_action;
11143 // no actions for this player (no input at player's configured device)
11145 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11146 SnapField(player, 0, 0);
11147 CheckGravityMovementWhenNotMoving(player);
11149 if (player->MovPos == 0)
11150 SetPlayerWaiting(player, TRUE);
11152 if (player->MovPos == 0) // needed for tape.playing
11153 player->is_moving = FALSE;
11155 player->is_dropping = FALSE;
11156 player->is_dropping_pressed = FALSE;
11157 player->drop_pressed_delay = 0;
11159 CheckSingleStepMode(player);
11165 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11168 if (!tape.use_mouse)
11171 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11172 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11173 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11176 static void SetTapeActionFromMouseAction(byte *tape_action,
11177 struct MouseActionInfo *mouse_action)
11179 if (!tape.use_mouse)
11182 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11183 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11184 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11187 static void CheckLevelSolved(void)
11189 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11191 if (game_em.level_solved &&
11192 !game_em.game_over) // game won
11196 game_em.game_over = TRUE;
11198 game.all_players_gone = TRUE;
11201 if (game_em.game_over) // game lost
11202 game.all_players_gone = TRUE;
11204 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11206 if (game_sp.level_solved &&
11207 !game_sp.game_over) // game won
11211 game_sp.game_over = TRUE;
11213 game.all_players_gone = TRUE;
11216 if (game_sp.game_over) // game lost
11217 game.all_players_gone = TRUE;
11219 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11221 if (game_mm.level_solved &&
11222 !game_mm.game_over) // game won
11226 game_mm.game_over = TRUE;
11228 game.all_players_gone = TRUE;
11231 if (game_mm.game_over) // game lost
11232 game.all_players_gone = TRUE;
11236 static void CheckLevelTime(void)
11240 if (TimeFrames >= FRAMES_PER_SECOND)
11245 for (i = 0; i < MAX_PLAYERS; i++)
11247 struct PlayerInfo *player = &stored_player[i];
11249 if (SHIELD_ON(player))
11251 player->shield_normal_time_left--;
11253 if (player->shield_deadly_time_left > 0)
11254 player->shield_deadly_time_left--;
11258 if (!game.LevelSolved && !level.use_step_counter)
11266 if (TimeLeft <= 10 && setup.time_limit)
11267 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11269 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11270 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11272 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11274 if (!TimeLeft && setup.time_limit)
11276 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11277 game_em.lev->killed_out_of_time = TRUE;
11279 for (i = 0; i < MAX_PLAYERS; i++)
11280 KillPlayer(&stored_player[i]);
11283 else if (game.no_time_limit && !game.all_players_gone)
11285 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11288 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11291 if (tape.recording || tape.playing)
11292 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11295 if (tape.recording || tape.playing)
11296 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11298 UpdateAndDisplayGameControlValues();
11301 void AdvanceFrameAndPlayerCounters(int player_nr)
11305 // advance frame counters (global frame counter and time frame counter)
11309 // advance player counters (counters for move delay, move animation etc.)
11310 for (i = 0; i < MAX_PLAYERS; i++)
11312 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11313 int move_delay_value = stored_player[i].move_delay_value;
11314 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11316 if (!advance_player_counters) // not all players may be affected
11319 if (move_frames == 0) // less than one move per game frame
11321 int stepsize = TILEX / move_delay_value;
11322 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11323 int count = (stored_player[i].is_moving ?
11324 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11326 if (count % delay == 0)
11330 stored_player[i].Frame += move_frames;
11332 if (stored_player[i].MovPos != 0)
11333 stored_player[i].StepFrame += move_frames;
11335 if (stored_player[i].move_delay > 0)
11336 stored_player[i].move_delay--;
11338 // due to bugs in previous versions, counter must count up, not down
11339 if (stored_player[i].push_delay != -1)
11340 stored_player[i].push_delay++;
11342 if (stored_player[i].drop_delay > 0)
11343 stored_player[i].drop_delay--;
11345 if (stored_player[i].is_dropping_pressed)
11346 stored_player[i].drop_pressed_delay++;
11350 void StartGameActions(boolean init_network_game, boolean record_tape,
11353 unsigned int new_random_seed = InitRND(random_seed);
11356 TapeStartRecording(new_random_seed);
11358 if (init_network_game)
11360 SendToServer_LevelFile();
11361 SendToServer_StartPlaying();
11369 static void GameActionsExt(void)
11372 static unsigned int game_frame_delay = 0;
11374 unsigned int game_frame_delay_value;
11375 byte *recorded_player_action;
11376 byte summarized_player_action = 0;
11377 byte tape_action[MAX_PLAYERS];
11380 // detect endless loops, caused by custom element programming
11381 if (recursion_loop_detected && recursion_loop_depth == 0)
11383 char *message = getStringCat3("Internal Error! Element ",
11384 EL_NAME(recursion_loop_element),
11385 " caused endless loop! Quit the game?");
11387 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11388 EL_NAME(recursion_loop_element));
11390 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11392 recursion_loop_detected = FALSE; // if game should be continued
11399 if (game.restart_level)
11400 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11402 CheckLevelSolved();
11404 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11407 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11410 if (game_status != GAME_MODE_PLAYING) // status might have changed
11413 game_frame_delay_value =
11414 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11416 if (tape.playing && tape.warp_forward && !tape.pausing)
11417 game_frame_delay_value = 0;
11419 SetVideoFrameDelay(game_frame_delay_value);
11421 // (de)activate virtual buttons depending on current game status
11422 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11424 if (game.all_players_gone) // if no players there to be controlled anymore
11425 SetOverlayActive(FALSE);
11426 else if (!tape.playing) // if game continues after tape stopped playing
11427 SetOverlayActive(TRUE);
11432 // ---------- main game synchronization point ----------
11434 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11436 printf("::: skip == %d\n", skip);
11439 // ---------- main game synchronization point ----------
11441 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11445 if (network_playing && !network_player_action_received)
11447 // try to get network player actions in time
11449 // last chance to get network player actions without main loop delay
11450 HandleNetworking();
11452 // game was quit by network peer
11453 if (game_status != GAME_MODE_PLAYING)
11456 // check if network player actions still missing and game still running
11457 if (!network_player_action_received && !checkGameEnded())
11458 return; // failed to get network player actions in time
11460 // do not yet reset "network_player_action_received" (for tape.pausing)
11466 // at this point we know that we really continue executing the game
11468 network_player_action_received = FALSE;
11470 // when playing tape, read previously recorded player input from tape data
11471 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11473 local_player->effective_mouse_action = local_player->mouse_action;
11475 if (recorded_player_action != NULL)
11476 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11477 recorded_player_action);
11479 // TapePlayAction() may return NULL when toggling to "pause before death"
11483 if (tape.set_centered_player)
11485 game.centered_player_nr_next = tape.centered_player_nr_next;
11486 game.set_centered_player = TRUE;
11489 for (i = 0; i < MAX_PLAYERS; i++)
11491 summarized_player_action |= stored_player[i].action;
11493 if (!network_playing && (game.team_mode || tape.playing))
11494 stored_player[i].effective_action = stored_player[i].action;
11497 if (network_playing && !checkGameEnded())
11498 SendToServer_MovePlayer(summarized_player_action);
11500 // summarize all actions at local players mapped input device position
11501 // (this allows using different input devices in single player mode)
11502 if (!network.enabled && !game.team_mode)
11503 stored_player[map_player_action[local_player->index_nr]].effective_action =
11504 summarized_player_action;
11506 // summarize all actions at centered player in local team mode
11507 if (tape.recording &&
11508 setup.team_mode && !network.enabled &&
11509 setup.input_on_focus &&
11510 game.centered_player_nr != -1)
11512 for (i = 0; i < MAX_PLAYERS; i++)
11513 stored_player[map_player_action[i]].effective_action =
11514 (i == game.centered_player_nr ? summarized_player_action : 0);
11517 if (recorded_player_action != NULL)
11518 for (i = 0; i < MAX_PLAYERS; i++)
11519 stored_player[i].effective_action = recorded_player_action[i];
11521 for (i = 0; i < MAX_PLAYERS; i++)
11523 tape_action[i] = stored_player[i].effective_action;
11525 /* (this may happen in the RND game engine if a player was not present on
11526 the playfield on level start, but appeared later from a custom element */
11527 if (setup.team_mode &&
11530 !tape.player_participates[i])
11531 tape.player_participates[i] = TRUE;
11534 SetTapeActionFromMouseAction(tape_action,
11535 &local_player->effective_mouse_action);
11537 // only record actions from input devices, but not programmed actions
11538 if (tape.recording)
11539 TapeRecordAction(tape_action);
11541 // remember if game was played (especially after tape stopped playing)
11542 if (!tape.playing && summarized_player_action)
11543 game.GamePlayed = TRUE;
11545 #if USE_NEW_PLAYER_ASSIGNMENTS
11546 // !!! also map player actions in single player mode !!!
11547 // if (game.team_mode)
11550 byte mapped_action[MAX_PLAYERS];
11552 #if DEBUG_PLAYER_ACTIONS
11554 for (i = 0; i < MAX_PLAYERS; i++)
11555 printf(" %d, ", stored_player[i].effective_action);
11558 for (i = 0; i < MAX_PLAYERS; i++)
11559 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11561 for (i = 0; i < MAX_PLAYERS; i++)
11562 stored_player[i].effective_action = mapped_action[i];
11564 #if DEBUG_PLAYER_ACTIONS
11566 for (i = 0; i < MAX_PLAYERS; i++)
11567 printf(" %d, ", stored_player[i].effective_action);
11571 #if DEBUG_PLAYER_ACTIONS
11575 for (i = 0; i < MAX_PLAYERS; i++)
11576 printf(" %d, ", stored_player[i].effective_action);
11582 for (i = 0; i < MAX_PLAYERS; i++)
11584 // allow engine snapshot in case of changed movement attempt
11585 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11586 (stored_player[i].effective_action & KEY_MOTION))
11587 game.snapshot.changed_action = TRUE;
11589 // allow engine snapshot in case of snapping/dropping attempt
11590 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11591 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11592 game.snapshot.changed_action = TRUE;
11594 game.snapshot.last_action[i] = stored_player[i].effective_action;
11597 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11599 GameActions_EM_Main();
11601 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11603 GameActions_SP_Main();
11605 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11607 GameActions_MM_Main();
11611 GameActions_RND_Main();
11614 BlitScreenToBitmap(backbuffer);
11616 CheckLevelSolved();
11619 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11621 if (global.show_frames_per_second)
11623 static unsigned int fps_counter = 0;
11624 static int fps_frames = 0;
11625 unsigned int fps_delay_ms = Counter() - fps_counter;
11629 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11631 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11634 fps_counter = Counter();
11636 // always draw FPS to screen after FPS value was updated
11637 redraw_mask |= REDRAW_FPS;
11640 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11641 if (GetDrawDeactivationMask() == REDRAW_NONE)
11642 redraw_mask |= REDRAW_FPS;
11646 static void GameActions_CheckSaveEngineSnapshot(void)
11648 if (!game.snapshot.save_snapshot)
11651 // clear flag for saving snapshot _before_ saving snapshot
11652 game.snapshot.save_snapshot = FALSE;
11654 SaveEngineSnapshotToList();
11657 void GameActions(void)
11661 GameActions_CheckSaveEngineSnapshot();
11664 void GameActions_EM_Main(void)
11666 byte effective_action[MAX_PLAYERS];
11667 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11670 for (i = 0; i < MAX_PLAYERS; i++)
11671 effective_action[i] = stored_player[i].effective_action;
11673 GameActions_EM(effective_action, warp_mode);
11676 void GameActions_SP_Main(void)
11678 byte effective_action[MAX_PLAYERS];
11679 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11682 for (i = 0; i < MAX_PLAYERS; i++)
11683 effective_action[i] = stored_player[i].effective_action;
11685 GameActions_SP(effective_action, warp_mode);
11687 for (i = 0; i < MAX_PLAYERS; i++)
11689 if (stored_player[i].force_dropping)
11690 stored_player[i].action |= KEY_BUTTON_DROP;
11692 stored_player[i].force_dropping = FALSE;
11696 void GameActions_MM_Main(void)
11698 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11700 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11703 void GameActions_RND_Main(void)
11708 void GameActions_RND(void)
11710 static struct MouseActionInfo mouse_action_last = { 0 };
11711 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11712 int magic_wall_x = 0, magic_wall_y = 0;
11713 int i, x, y, element, graphic, last_gfx_frame;
11715 InitPlayfieldScanModeVars();
11717 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11719 SCAN_PLAYFIELD(x, y)
11721 ChangeCount[x][y] = 0;
11722 ChangeEvent[x][y] = -1;
11726 if (game.set_centered_player)
11728 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11730 // switching to "all players" only possible if all players fit to screen
11731 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11733 game.centered_player_nr_next = game.centered_player_nr;
11734 game.set_centered_player = FALSE;
11737 // do not switch focus to non-existing (or non-active) player
11738 if (game.centered_player_nr_next >= 0 &&
11739 !stored_player[game.centered_player_nr_next].active)
11741 game.centered_player_nr_next = game.centered_player_nr;
11742 game.set_centered_player = FALSE;
11746 if (game.set_centered_player &&
11747 ScreenMovPos == 0) // screen currently aligned at tile position
11751 if (game.centered_player_nr_next == -1)
11753 setScreenCenteredToAllPlayers(&sx, &sy);
11757 sx = stored_player[game.centered_player_nr_next].jx;
11758 sy = stored_player[game.centered_player_nr_next].jy;
11761 game.centered_player_nr = game.centered_player_nr_next;
11762 game.set_centered_player = FALSE;
11764 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11765 DrawGameDoorValues();
11768 for (i = 0; i < MAX_PLAYERS; i++)
11770 int actual_player_action = stored_player[i].effective_action;
11773 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11774 - rnd_equinox_tetrachloride 048
11775 - rnd_equinox_tetrachloride_ii 096
11776 - rnd_emanuel_schmieg 002
11777 - doctor_sloan_ww 001, 020
11779 if (stored_player[i].MovPos == 0)
11780 CheckGravityMovement(&stored_player[i]);
11783 // overwrite programmed action with tape action
11784 if (stored_player[i].programmed_action)
11785 actual_player_action = stored_player[i].programmed_action;
11787 PlayerActions(&stored_player[i], actual_player_action);
11789 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11792 ScrollScreen(NULL, SCROLL_GO_ON);
11794 /* for backwards compatibility, the following code emulates a fixed bug that
11795 occured when pushing elements (causing elements that just made their last
11796 pushing step to already (if possible) make their first falling step in the
11797 same game frame, which is bad); this code is also needed to use the famous
11798 "spring push bug" which is used in older levels and might be wanted to be
11799 used also in newer levels, but in this case the buggy pushing code is only
11800 affecting the "spring" element and no other elements */
11802 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11804 for (i = 0; i < MAX_PLAYERS; i++)
11806 struct PlayerInfo *player = &stored_player[i];
11807 int x = player->jx;
11808 int y = player->jy;
11810 if (player->active && player->is_pushing && player->is_moving &&
11812 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11813 Feld[x][y] == EL_SPRING))
11815 ContinueMoving(x, y);
11817 // continue moving after pushing (this is actually a bug)
11818 if (!IS_MOVING(x, y))
11819 Stop[x][y] = FALSE;
11824 SCAN_PLAYFIELD(x, y)
11826 Last[x][y] = Feld[x][y];
11828 ChangeCount[x][y] = 0;
11829 ChangeEvent[x][y] = -1;
11831 // this must be handled before main playfield loop
11832 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11835 if (MovDelay[x][y] <= 0)
11839 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11842 if (MovDelay[x][y] <= 0)
11845 TEST_DrawLevelField(x, y);
11847 TestIfElementTouchesCustomElement(x, y); // for empty space
11852 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11854 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11855 printf("GameActions(): This should never happen!\n");
11857 ChangePage[x][y] = -1;
11861 Stop[x][y] = FALSE;
11862 if (WasJustMoving[x][y] > 0)
11863 WasJustMoving[x][y]--;
11864 if (WasJustFalling[x][y] > 0)
11865 WasJustFalling[x][y]--;
11866 if (CheckCollision[x][y] > 0)
11867 CheckCollision[x][y]--;
11868 if (CheckImpact[x][y] > 0)
11869 CheckImpact[x][y]--;
11873 /* reset finished pushing action (not done in ContinueMoving() to allow
11874 continuous pushing animation for elements with zero push delay) */
11875 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11877 ResetGfxAnimation(x, y);
11878 TEST_DrawLevelField(x, y);
11882 if (IS_BLOCKED(x, y))
11886 Blocked2Moving(x, y, &oldx, &oldy);
11887 if (!IS_MOVING(oldx, oldy))
11889 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11890 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11891 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11892 printf("GameActions(): This should never happen!\n");
11898 if (mouse_action.button)
11900 int new_button = (mouse_action.button && mouse_action_last.button == 0);
11902 x = local_player->mouse_action.lx;
11903 y = local_player->mouse_action.ly;
11904 element = Feld[x][y];
11908 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
11909 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
11912 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
11913 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
11916 SCAN_PLAYFIELD(x, y)
11918 element = Feld[x][y];
11919 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11920 last_gfx_frame = GfxFrame[x][y];
11922 ResetGfxFrame(x, y);
11924 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
11925 DrawLevelGraphicAnimation(x, y, graphic);
11927 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
11928 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
11929 ResetRandomAnimationValue(x, y);
11931 SetRandomAnimationValue(x, y);
11933 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
11935 if (IS_INACTIVE(element))
11937 if (IS_ANIMATED(graphic))
11938 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11943 // this may take place after moving, so 'element' may have changed
11944 if (IS_CHANGING(x, y) &&
11945 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
11947 int page = element_info[element].event_page_nr[CE_DELAY];
11949 HandleElementChange(x, y, page);
11951 element = Feld[x][y];
11952 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11955 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
11959 element = Feld[x][y];
11960 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11962 if (IS_ANIMATED(graphic) &&
11963 !IS_MOVING(x, y) &&
11965 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11967 if (IS_GEM(element) || element == EL_SP_INFOTRON)
11968 TEST_DrawTwinkleOnField(x, y);
11970 else if (element == EL_ACID)
11973 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11975 else if ((element == EL_EXIT_OPEN ||
11976 element == EL_EM_EXIT_OPEN ||
11977 element == EL_SP_EXIT_OPEN ||
11978 element == EL_STEEL_EXIT_OPEN ||
11979 element == EL_EM_STEEL_EXIT_OPEN ||
11980 element == EL_SP_TERMINAL ||
11981 element == EL_SP_TERMINAL_ACTIVE ||
11982 element == EL_EXTRA_TIME ||
11983 element == EL_SHIELD_NORMAL ||
11984 element == EL_SHIELD_DEADLY) &&
11985 IS_ANIMATED(graphic))
11986 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11987 else if (IS_MOVING(x, y))
11988 ContinueMoving(x, y);
11989 else if (IS_ACTIVE_BOMB(element))
11990 CheckDynamite(x, y);
11991 else if (element == EL_AMOEBA_GROWING)
11992 AmoebeWaechst(x, y);
11993 else if (element == EL_AMOEBA_SHRINKING)
11994 AmoebaDisappearing(x, y);
11996 #if !USE_NEW_AMOEBA_CODE
11997 else if (IS_AMOEBALIVE(element))
11998 AmoebeAbleger(x, y);
12001 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12003 else if (element == EL_EXIT_CLOSED)
12005 else if (element == EL_EM_EXIT_CLOSED)
12007 else if (element == EL_STEEL_EXIT_CLOSED)
12008 CheckExitSteel(x, y);
12009 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12010 CheckExitSteelEM(x, y);
12011 else if (element == EL_SP_EXIT_CLOSED)
12013 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12014 element == EL_EXPANDABLE_STEELWALL_GROWING)
12015 MauerWaechst(x, y);
12016 else if (element == EL_EXPANDABLE_WALL ||
12017 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12018 element == EL_EXPANDABLE_WALL_VERTICAL ||
12019 element == EL_EXPANDABLE_WALL_ANY ||
12020 element == EL_BD_EXPANDABLE_WALL)
12021 MauerAbleger(x, y);
12022 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12023 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12024 element == EL_EXPANDABLE_STEELWALL_ANY)
12025 MauerAblegerStahl(x, y);
12026 else if (element == EL_FLAMES)
12027 CheckForDragon(x, y);
12028 else if (element == EL_EXPLOSION)
12029 ; // drawing of correct explosion animation is handled separately
12030 else if (element == EL_ELEMENT_SNAPPING ||
12031 element == EL_DIAGONAL_SHRINKING ||
12032 element == EL_DIAGONAL_GROWING)
12034 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12036 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12038 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12039 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12041 if (IS_BELT_ACTIVE(element))
12042 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12044 if (game.magic_wall_active)
12046 int jx = local_player->jx, jy = local_player->jy;
12048 // play the element sound at the position nearest to the player
12049 if ((element == EL_MAGIC_WALL_FULL ||
12050 element == EL_MAGIC_WALL_ACTIVE ||
12051 element == EL_MAGIC_WALL_EMPTYING ||
12052 element == EL_BD_MAGIC_WALL_FULL ||
12053 element == EL_BD_MAGIC_WALL_ACTIVE ||
12054 element == EL_BD_MAGIC_WALL_EMPTYING ||
12055 element == EL_DC_MAGIC_WALL_FULL ||
12056 element == EL_DC_MAGIC_WALL_ACTIVE ||
12057 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12058 ABS(x - jx) + ABS(y - jy) <
12059 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12067 #if USE_NEW_AMOEBA_CODE
12068 // new experimental amoeba growth stuff
12069 if (!(FrameCounter % 8))
12071 static unsigned int random = 1684108901;
12073 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12075 x = RND(lev_fieldx);
12076 y = RND(lev_fieldy);
12077 element = Feld[x][y];
12079 if (!IS_PLAYER(x,y) &&
12080 (element == EL_EMPTY ||
12081 CAN_GROW_INTO(element) ||
12082 element == EL_QUICKSAND_EMPTY ||
12083 element == EL_QUICKSAND_FAST_EMPTY ||
12084 element == EL_ACID_SPLASH_LEFT ||
12085 element == EL_ACID_SPLASH_RIGHT))
12087 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12088 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12089 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12090 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12091 Feld[x][y] = EL_AMOEBA_DROP;
12094 random = random * 129 + 1;
12099 game.explosions_delayed = FALSE;
12101 SCAN_PLAYFIELD(x, y)
12103 element = Feld[x][y];
12105 if (ExplodeField[x][y])
12106 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12107 else if (element == EL_EXPLOSION)
12108 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12110 ExplodeField[x][y] = EX_TYPE_NONE;
12113 game.explosions_delayed = TRUE;
12115 if (game.magic_wall_active)
12117 if (!(game.magic_wall_time_left % 4))
12119 int element = Feld[magic_wall_x][magic_wall_y];
12121 if (element == EL_BD_MAGIC_WALL_FULL ||
12122 element == EL_BD_MAGIC_WALL_ACTIVE ||
12123 element == EL_BD_MAGIC_WALL_EMPTYING)
12124 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12125 else if (element == EL_DC_MAGIC_WALL_FULL ||
12126 element == EL_DC_MAGIC_WALL_ACTIVE ||
12127 element == EL_DC_MAGIC_WALL_EMPTYING)
12128 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12130 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12133 if (game.magic_wall_time_left > 0)
12135 game.magic_wall_time_left--;
12137 if (!game.magic_wall_time_left)
12139 SCAN_PLAYFIELD(x, y)
12141 element = Feld[x][y];
12143 if (element == EL_MAGIC_WALL_ACTIVE ||
12144 element == EL_MAGIC_WALL_FULL)
12146 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12147 TEST_DrawLevelField(x, y);
12149 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12150 element == EL_BD_MAGIC_WALL_FULL)
12152 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12153 TEST_DrawLevelField(x, y);
12155 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12156 element == EL_DC_MAGIC_WALL_FULL)
12158 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12159 TEST_DrawLevelField(x, y);
12163 game.magic_wall_active = FALSE;
12168 if (game.light_time_left > 0)
12170 game.light_time_left--;
12172 if (game.light_time_left == 0)
12173 RedrawAllLightSwitchesAndInvisibleElements();
12176 if (game.timegate_time_left > 0)
12178 game.timegate_time_left--;
12180 if (game.timegate_time_left == 0)
12181 CloseAllOpenTimegates();
12184 if (game.lenses_time_left > 0)
12186 game.lenses_time_left--;
12188 if (game.lenses_time_left == 0)
12189 RedrawAllInvisibleElementsForLenses();
12192 if (game.magnify_time_left > 0)
12194 game.magnify_time_left--;
12196 if (game.magnify_time_left == 0)
12197 RedrawAllInvisibleElementsForMagnifier();
12200 for (i = 0; i < MAX_PLAYERS; i++)
12202 struct PlayerInfo *player = &stored_player[i];
12204 if (SHIELD_ON(player))
12206 if (player->shield_deadly_time_left)
12207 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12208 else if (player->shield_normal_time_left)
12209 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12213 #if USE_DELAYED_GFX_REDRAW
12214 SCAN_PLAYFIELD(x, y)
12216 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12218 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12219 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12221 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12222 DrawLevelField(x, y);
12224 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12225 DrawLevelFieldCrumbled(x, y);
12227 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12228 DrawLevelFieldCrumbledNeighbours(x, y);
12230 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12231 DrawTwinkleOnField(x, y);
12234 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12239 PlayAllPlayersSound();
12241 for (i = 0; i < MAX_PLAYERS; i++)
12243 struct PlayerInfo *player = &stored_player[i];
12245 if (player->show_envelope != 0 && (!player->active ||
12246 player->MovPos == 0))
12248 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12250 player->show_envelope = 0;
12254 // use random number generator in every frame to make it less predictable
12255 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12258 mouse_action_last = mouse_action;
12261 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12263 int min_x = x, min_y = y, max_x = x, max_y = y;
12266 for (i = 0; i < MAX_PLAYERS; i++)
12268 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12270 if (!stored_player[i].active || &stored_player[i] == player)
12273 min_x = MIN(min_x, jx);
12274 min_y = MIN(min_y, jy);
12275 max_x = MAX(max_x, jx);
12276 max_y = MAX(max_y, jy);
12279 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12282 static boolean AllPlayersInVisibleScreen(void)
12286 for (i = 0; i < MAX_PLAYERS; i++)
12288 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12290 if (!stored_player[i].active)
12293 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12300 void ScrollLevel(int dx, int dy)
12302 int scroll_offset = 2 * TILEX_VAR;
12305 BlitBitmap(drawto_field, drawto_field,
12306 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12307 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12308 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12309 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12310 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12311 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12315 x = (dx == 1 ? BX1 : BX2);
12316 for (y = BY1; y <= BY2; y++)
12317 DrawScreenField(x, y);
12322 y = (dy == 1 ? BY1 : BY2);
12323 for (x = BX1; x <= BX2; x++)
12324 DrawScreenField(x, y);
12327 redraw_mask |= REDRAW_FIELD;
12330 static boolean canFallDown(struct PlayerInfo *player)
12332 int jx = player->jx, jy = player->jy;
12334 return (IN_LEV_FIELD(jx, jy + 1) &&
12335 (IS_FREE(jx, jy + 1) ||
12336 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12337 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12338 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12341 static boolean canPassField(int x, int y, int move_dir)
12343 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12344 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12345 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12346 int nextx = x + dx;
12347 int nexty = y + dy;
12348 int element = Feld[x][y];
12350 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12351 !CAN_MOVE(element) &&
12352 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12353 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12354 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12357 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12359 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12360 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12361 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12365 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12366 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12367 (IS_DIGGABLE(Feld[newx][newy]) ||
12368 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12369 canPassField(newx, newy, move_dir)));
12372 static void CheckGravityMovement(struct PlayerInfo *player)
12374 if (player->gravity && !player->programmed_action)
12376 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12377 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12378 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12379 int jx = player->jx, jy = player->jy;
12380 boolean player_is_moving_to_valid_field =
12381 (!player_is_snapping &&
12382 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12383 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12384 boolean player_can_fall_down = canFallDown(player);
12386 if (player_can_fall_down &&
12387 !player_is_moving_to_valid_field)
12388 player->programmed_action = MV_DOWN;
12392 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12394 return CheckGravityMovement(player);
12396 if (player->gravity && !player->programmed_action)
12398 int jx = player->jx, jy = player->jy;
12399 boolean field_under_player_is_free =
12400 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12401 boolean player_is_standing_on_valid_field =
12402 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12403 (IS_WALKABLE(Feld[jx][jy]) &&
12404 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12406 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12407 player->programmed_action = MV_DOWN;
12412 MovePlayerOneStep()
12413 -----------------------------------------------------------------------------
12414 dx, dy: direction (non-diagonal) to try to move the player to
12415 real_dx, real_dy: direction as read from input device (can be diagonal)
12418 boolean MovePlayerOneStep(struct PlayerInfo *player,
12419 int dx, int dy, int real_dx, int real_dy)
12421 int jx = player->jx, jy = player->jy;
12422 int new_jx = jx + dx, new_jy = jy + dy;
12424 boolean player_can_move = !player->cannot_move;
12426 if (!player->active || (!dx && !dy))
12427 return MP_NO_ACTION;
12429 player->MovDir = (dx < 0 ? MV_LEFT :
12430 dx > 0 ? MV_RIGHT :
12432 dy > 0 ? MV_DOWN : MV_NONE);
12434 if (!IN_LEV_FIELD(new_jx, new_jy))
12435 return MP_NO_ACTION;
12437 if (!player_can_move)
12439 if (player->MovPos == 0)
12441 player->is_moving = FALSE;
12442 player->is_digging = FALSE;
12443 player->is_collecting = FALSE;
12444 player->is_snapping = FALSE;
12445 player->is_pushing = FALSE;
12449 if (!network.enabled && game.centered_player_nr == -1 &&
12450 !AllPlayersInSight(player, new_jx, new_jy))
12451 return MP_NO_ACTION;
12453 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12454 if (can_move != MP_MOVING)
12457 // check if DigField() has caused relocation of the player
12458 if (player->jx != jx || player->jy != jy)
12459 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12461 StorePlayer[jx][jy] = 0;
12462 player->last_jx = jx;
12463 player->last_jy = jy;
12464 player->jx = new_jx;
12465 player->jy = new_jy;
12466 StorePlayer[new_jx][new_jy] = player->element_nr;
12468 if (player->move_delay_value_next != -1)
12470 player->move_delay_value = player->move_delay_value_next;
12471 player->move_delay_value_next = -1;
12475 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12477 player->step_counter++;
12479 PlayerVisit[jx][jy] = FrameCounter;
12481 player->is_moving = TRUE;
12484 // should better be called in MovePlayer(), but this breaks some tapes
12485 ScrollPlayer(player, SCROLL_INIT);
12491 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12493 int jx = player->jx, jy = player->jy;
12494 int old_jx = jx, old_jy = jy;
12495 int moved = MP_NO_ACTION;
12497 if (!player->active)
12502 if (player->MovPos == 0)
12504 player->is_moving = FALSE;
12505 player->is_digging = FALSE;
12506 player->is_collecting = FALSE;
12507 player->is_snapping = FALSE;
12508 player->is_pushing = FALSE;
12514 if (player->move_delay > 0)
12517 player->move_delay = -1; // set to "uninitialized" value
12519 // store if player is automatically moved to next field
12520 player->is_auto_moving = (player->programmed_action != MV_NONE);
12522 // remove the last programmed player action
12523 player->programmed_action = 0;
12525 if (player->MovPos)
12527 // should only happen if pre-1.2 tape recordings are played
12528 // this is only for backward compatibility
12530 int original_move_delay_value = player->move_delay_value;
12533 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12537 // scroll remaining steps with finest movement resolution
12538 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12540 while (player->MovPos)
12542 ScrollPlayer(player, SCROLL_GO_ON);
12543 ScrollScreen(NULL, SCROLL_GO_ON);
12545 AdvanceFrameAndPlayerCounters(player->index_nr);
12548 BackToFront_WithFrameDelay(0);
12551 player->move_delay_value = original_move_delay_value;
12554 player->is_active = FALSE;
12556 if (player->last_move_dir & MV_HORIZONTAL)
12558 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12559 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12563 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12564 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12567 if (!moved && !player->is_active)
12569 player->is_moving = FALSE;
12570 player->is_digging = FALSE;
12571 player->is_collecting = FALSE;
12572 player->is_snapping = FALSE;
12573 player->is_pushing = FALSE;
12579 if (moved & MP_MOVING && !ScreenMovPos &&
12580 (player->index_nr == game.centered_player_nr ||
12581 game.centered_player_nr == -1))
12583 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12585 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12587 // actual player has left the screen -- scroll in that direction
12588 if (jx != old_jx) // player has moved horizontally
12589 scroll_x += (jx - old_jx);
12590 else // player has moved vertically
12591 scroll_y += (jy - old_jy);
12595 int offset_raw = game.scroll_delay_value;
12597 if (jx != old_jx) // player has moved horizontally
12599 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12600 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12601 int new_scroll_x = jx - MIDPOSX + offset_x;
12603 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12604 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12605 scroll_x = new_scroll_x;
12607 // don't scroll over playfield boundaries
12608 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12610 // don't scroll more than one field at a time
12611 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12613 // don't scroll against the player's moving direction
12614 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12615 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12616 scroll_x = old_scroll_x;
12618 else // player has moved vertically
12620 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12621 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12622 int new_scroll_y = jy - MIDPOSY + offset_y;
12624 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12625 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12626 scroll_y = new_scroll_y;
12628 // don't scroll over playfield boundaries
12629 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12631 // don't scroll more than one field at a time
12632 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12634 // don't scroll against the player's moving direction
12635 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12636 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12637 scroll_y = old_scroll_y;
12641 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12643 if (!network.enabled && game.centered_player_nr == -1 &&
12644 !AllPlayersInVisibleScreen())
12646 scroll_x = old_scroll_x;
12647 scroll_y = old_scroll_y;
12651 ScrollScreen(player, SCROLL_INIT);
12652 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12657 player->StepFrame = 0;
12659 if (moved & MP_MOVING)
12661 if (old_jx != jx && old_jy == jy)
12662 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12663 else if (old_jx == jx && old_jy != jy)
12664 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12666 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12668 player->last_move_dir = player->MovDir;
12669 player->is_moving = TRUE;
12670 player->is_snapping = FALSE;
12671 player->is_switching = FALSE;
12672 player->is_dropping = FALSE;
12673 player->is_dropping_pressed = FALSE;
12674 player->drop_pressed_delay = 0;
12677 // should better be called here than above, but this breaks some tapes
12678 ScrollPlayer(player, SCROLL_INIT);
12683 CheckGravityMovementWhenNotMoving(player);
12685 player->is_moving = FALSE;
12687 /* at this point, the player is allowed to move, but cannot move right now
12688 (e.g. because of something blocking the way) -- ensure that the player
12689 is also allowed to move in the next frame (in old versions before 3.1.1,
12690 the player was forced to wait again for eight frames before next try) */
12692 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12693 player->move_delay = 0; // allow direct movement in the next frame
12696 if (player->move_delay == -1) // not yet initialized by DigField()
12697 player->move_delay = player->move_delay_value;
12699 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12701 TestIfPlayerTouchesBadThing(jx, jy);
12702 TestIfPlayerTouchesCustomElement(jx, jy);
12705 if (!player->active)
12706 RemovePlayer(player);
12711 void ScrollPlayer(struct PlayerInfo *player, int mode)
12713 int jx = player->jx, jy = player->jy;
12714 int last_jx = player->last_jx, last_jy = player->last_jy;
12715 int move_stepsize = TILEX / player->move_delay_value;
12717 if (!player->active)
12720 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12723 if (mode == SCROLL_INIT)
12725 player->actual_frame_counter = FrameCounter;
12726 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12728 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12729 Feld[last_jx][last_jy] == EL_EMPTY)
12731 int last_field_block_delay = 0; // start with no blocking at all
12732 int block_delay_adjustment = player->block_delay_adjustment;
12734 // if player blocks last field, add delay for exactly one move
12735 if (player->block_last_field)
12737 last_field_block_delay += player->move_delay_value;
12739 // when blocking enabled, prevent moving up despite gravity
12740 if (player->gravity && player->MovDir == MV_UP)
12741 block_delay_adjustment = -1;
12744 // add block delay adjustment (also possible when not blocking)
12745 last_field_block_delay += block_delay_adjustment;
12747 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12748 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12751 if (player->MovPos != 0) // player has not yet reached destination
12754 else if (!FrameReached(&player->actual_frame_counter, 1))
12757 if (player->MovPos != 0)
12759 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12760 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12762 // before DrawPlayer() to draw correct player graphic for this case
12763 if (player->MovPos == 0)
12764 CheckGravityMovement(player);
12767 if (player->MovPos == 0) // player reached destination field
12769 if (player->move_delay_reset_counter > 0)
12771 player->move_delay_reset_counter--;
12773 if (player->move_delay_reset_counter == 0)
12775 // continue with normal speed after quickly moving through gate
12776 HALVE_PLAYER_SPEED(player);
12778 // be able to make the next move without delay
12779 player->move_delay = 0;
12783 player->last_jx = jx;
12784 player->last_jy = jy;
12786 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12787 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12788 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12789 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12790 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12791 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12792 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12793 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12795 ExitPlayer(player);
12797 if (game.players_still_needed == 0 &&
12798 (game.friends_still_needed == 0 ||
12799 IS_SP_ELEMENT(Feld[jx][jy])))
12803 // this breaks one level: "machine", level 000
12805 int move_direction = player->MovDir;
12806 int enter_side = MV_DIR_OPPOSITE(move_direction);
12807 int leave_side = move_direction;
12808 int old_jx = last_jx;
12809 int old_jy = last_jy;
12810 int old_element = Feld[old_jx][old_jy];
12811 int new_element = Feld[jx][jy];
12813 if (IS_CUSTOM_ELEMENT(old_element))
12814 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12816 player->index_bit, leave_side);
12818 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12819 CE_PLAYER_LEAVES_X,
12820 player->index_bit, leave_side);
12822 if (IS_CUSTOM_ELEMENT(new_element))
12823 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12824 player->index_bit, enter_side);
12826 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12827 CE_PLAYER_ENTERS_X,
12828 player->index_bit, enter_side);
12830 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12831 CE_MOVE_OF_X, move_direction);
12834 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12836 TestIfPlayerTouchesBadThing(jx, jy);
12837 TestIfPlayerTouchesCustomElement(jx, jy);
12839 /* needed because pushed element has not yet reached its destination,
12840 so it would trigger a change event at its previous field location */
12841 if (!player->is_pushing)
12842 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12844 if (!player->active)
12845 RemovePlayer(player);
12848 if (!game.LevelSolved && level.use_step_counter)
12858 if (TimeLeft <= 10 && setup.time_limit)
12859 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12861 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12863 DisplayGameControlValues();
12865 if (!TimeLeft && setup.time_limit)
12866 for (i = 0; i < MAX_PLAYERS; i++)
12867 KillPlayer(&stored_player[i]);
12869 else if (game.no_time_limit && !game.all_players_gone)
12871 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12873 DisplayGameControlValues();
12877 if (tape.single_step && tape.recording && !tape.pausing &&
12878 !player->programmed_action)
12879 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12881 if (!player->programmed_action)
12882 CheckSaveEngineSnapshot(player);
12886 void ScrollScreen(struct PlayerInfo *player, int mode)
12888 static unsigned int screen_frame_counter = 0;
12890 if (mode == SCROLL_INIT)
12892 // set scrolling step size according to actual player's moving speed
12893 ScrollStepSize = TILEX / player->move_delay_value;
12895 screen_frame_counter = FrameCounter;
12896 ScreenMovDir = player->MovDir;
12897 ScreenMovPos = player->MovPos;
12898 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12901 else if (!FrameReached(&screen_frame_counter, 1))
12906 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
12907 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
12908 redraw_mask |= REDRAW_FIELD;
12911 ScreenMovDir = MV_NONE;
12914 void TestIfPlayerTouchesCustomElement(int x, int y)
12916 static int xy[4][2] =
12923 static int trigger_sides[4][2] =
12925 // center side border side
12926 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
12927 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
12928 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
12929 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
12931 static int touch_dir[4] =
12933 MV_LEFT | MV_RIGHT,
12938 int center_element = Feld[x][y]; // should always be non-moving!
12941 for (i = 0; i < NUM_DIRECTIONS; i++)
12943 int xx = x + xy[i][0];
12944 int yy = y + xy[i][1];
12945 int center_side = trigger_sides[i][0];
12946 int border_side = trigger_sides[i][1];
12947 int border_element;
12949 if (!IN_LEV_FIELD(xx, yy))
12952 if (IS_PLAYER(x, y)) // player found at center element
12954 struct PlayerInfo *player = PLAYERINFO(x, y);
12956 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12957 border_element = Feld[xx][yy]; // may be moving!
12958 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
12959 border_element = Feld[xx][yy];
12960 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
12961 border_element = MovingOrBlocked2Element(xx, yy);
12963 continue; // center and border element do not touch
12965 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
12966 player->index_bit, border_side);
12967 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
12968 CE_PLAYER_TOUCHES_X,
12969 player->index_bit, border_side);
12972 /* use player element that is initially defined in the level playfield,
12973 not the player element that corresponds to the runtime player number
12974 (example: a level that contains EL_PLAYER_3 as the only player would
12975 incorrectly give EL_PLAYER_1 for "player->element_nr") */
12976 int player_element = PLAYERINFO(x, y)->initial_element;
12978 CheckElementChangeBySide(xx, yy, border_element, player_element,
12979 CE_TOUCHING_X, border_side);
12982 else if (IS_PLAYER(xx, yy)) // player found at border element
12984 struct PlayerInfo *player = PLAYERINFO(xx, yy);
12986 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12988 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
12989 continue; // center and border element do not touch
12992 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
12993 player->index_bit, center_side);
12994 CheckTriggeredElementChangeByPlayer(x, y, center_element,
12995 CE_PLAYER_TOUCHES_X,
12996 player->index_bit, center_side);
12999 /* use player element that is initially defined in the level playfield,
13000 not the player element that corresponds to the runtime player number
13001 (example: a level that contains EL_PLAYER_3 as the only player would
13002 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13003 int player_element = PLAYERINFO(xx, yy)->initial_element;
13005 CheckElementChangeBySide(x, y, center_element, player_element,
13006 CE_TOUCHING_X, center_side);
13014 void TestIfElementTouchesCustomElement(int x, int y)
13016 static int xy[4][2] =
13023 static int trigger_sides[4][2] =
13025 // center side border side
13026 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13027 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13028 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13029 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13031 static int touch_dir[4] =
13033 MV_LEFT | MV_RIGHT,
13038 boolean change_center_element = FALSE;
13039 int center_element = Feld[x][y]; // should always be non-moving!
13040 int border_element_old[NUM_DIRECTIONS];
13043 for (i = 0; i < NUM_DIRECTIONS; i++)
13045 int xx = x + xy[i][0];
13046 int yy = y + xy[i][1];
13047 int border_element;
13049 border_element_old[i] = -1;
13051 if (!IN_LEV_FIELD(xx, yy))
13054 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13055 border_element = Feld[xx][yy]; // may be moving!
13056 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13057 border_element = Feld[xx][yy];
13058 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13059 border_element = MovingOrBlocked2Element(xx, yy);
13061 continue; // center and border element do not touch
13063 border_element_old[i] = border_element;
13066 for (i = 0; i < NUM_DIRECTIONS; i++)
13068 int xx = x + xy[i][0];
13069 int yy = y + xy[i][1];
13070 int center_side = trigger_sides[i][0];
13071 int border_element = border_element_old[i];
13073 if (border_element == -1)
13076 // check for change of border element
13077 CheckElementChangeBySide(xx, yy, border_element, center_element,
13078 CE_TOUCHING_X, center_side);
13080 // (center element cannot be player, so we dont have to check this here)
13083 for (i = 0; i < NUM_DIRECTIONS; i++)
13085 int xx = x + xy[i][0];
13086 int yy = y + xy[i][1];
13087 int border_side = trigger_sides[i][1];
13088 int border_element = border_element_old[i];
13090 if (border_element == -1)
13093 // check for change of center element (but change it only once)
13094 if (!change_center_element)
13095 change_center_element =
13096 CheckElementChangeBySide(x, y, center_element, border_element,
13097 CE_TOUCHING_X, border_side);
13099 if (IS_PLAYER(xx, yy))
13101 /* use player element that is initially defined in the level playfield,
13102 not the player element that corresponds to the runtime player number
13103 (example: a level that contains EL_PLAYER_3 as the only player would
13104 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13105 int player_element = PLAYERINFO(xx, yy)->initial_element;
13107 CheckElementChangeBySide(x, y, center_element, player_element,
13108 CE_TOUCHING_X, border_side);
13113 void TestIfElementHitsCustomElement(int x, int y, int direction)
13115 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13116 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13117 int hitx = x + dx, hity = y + dy;
13118 int hitting_element = Feld[x][y];
13119 int touched_element;
13121 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13124 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13125 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13127 if (IN_LEV_FIELD(hitx, hity))
13129 int opposite_direction = MV_DIR_OPPOSITE(direction);
13130 int hitting_side = direction;
13131 int touched_side = opposite_direction;
13132 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13133 MovDir[hitx][hity] != direction ||
13134 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13140 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13141 CE_HITTING_X, touched_side);
13143 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13144 CE_HIT_BY_X, hitting_side);
13146 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13147 CE_HIT_BY_SOMETHING, opposite_direction);
13149 if (IS_PLAYER(hitx, hity))
13151 /* use player element that is initially defined in the level playfield,
13152 not the player element that corresponds to the runtime player number
13153 (example: a level that contains EL_PLAYER_3 as the only player would
13154 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13155 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13157 CheckElementChangeBySide(x, y, hitting_element, player_element,
13158 CE_HITTING_X, touched_side);
13163 // "hitting something" is also true when hitting the playfield border
13164 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13165 CE_HITTING_SOMETHING, direction);
13168 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13170 int i, kill_x = -1, kill_y = -1;
13172 int bad_element = -1;
13173 static int test_xy[4][2] =
13180 static int test_dir[4] =
13188 for (i = 0; i < NUM_DIRECTIONS; i++)
13190 int test_x, test_y, test_move_dir, test_element;
13192 test_x = good_x + test_xy[i][0];
13193 test_y = good_y + test_xy[i][1];
13195 if (!IN_LEV_FIELD(test_x, test_y))
13199 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13201 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13203 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13204 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13206 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13207 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13211 bad_element = test_element;
13217 if (kill_x != -1 || kill_y != -1)
13219 if (IS_PLAYER(good_x, good_y))
13221 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13223 if (player->shield_deadly_time_left > 0 &&
13224 !IS_INDESTRUCTIBLE(bad_element))
13225 Bang(kill_x, kill_y);
13226 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13227 KillPlayer(player);
13230 Bang(good_x, good_y);
13234 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13236 int i, kill_x = -1, kill_y = -1;
13237 int bad_element = Feld[bad_x][bad_y];
13238 static int test_xy[4][2] =
13245 static int touch_dir[4] =
13247 MV_LEFT | MV_RIGHT,
13252 static int test_dir[4] =
13260 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13263 for (i = 0; i < NUM_DIRECTIONS; i++)
13265 int test_x, test_y, test_move_dir, test_element;
13267 test_x = bad_x + test_xy[i][0];
13268 test_y = bad_y + test_xy[i][1];
13270 if (!IN_LEV_FIELD(test_x, test_y))
13274 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13276 test_element = Feld[test_x][test_y];
13278 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13279 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13281 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13282 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13284 // good thing is player or penguin that does not move away
13285 if (IS_PLAYER(test_x, test_y))
13287 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13289 if (bad_element == EL_ROBOT && player->is_moving)
13290 continue; // robot does not kill player if he is moving
13292 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13294 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13295 continue; // center and border element do not touch
13303 else if (test_element == EL_PENGUIN)
13313 if (kill_x != -1 || kill_y != -1)
13315 if (IS_PLAYER(kill_x, kill_y))
13317 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13319 if (player->shield_deadly_time_left > 0 &&
13320 !IS_INDESTRUCTIBLE(bad_element))
13321 Bang(bad_x, bad_y);
13322 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13323 KillPlayer(player);
13326 Bang(kill_x, kill_y);
13330 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13332 int bad_element = Feld[bad_x][bad_y];
13333 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13334 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13335 int test_x = bad_x + dx, test_y = bad_y + dy;
13336 int test_move_dir, test_element;
13337 int kill_x = -1, kill_y = -1;
13339 if (!IN_LEV_FIELD(test_x, test_y))
13343 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13345 test_element = Feld[test_x][test_y];
13347 if (test_move_dir != bad_move_dir)
13349 // good thing can be player or penguin that does not move away
13350 if (IS_PLAYER(test_x, test_y))
13352 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13354 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13355 player as being hit when he is moving towards the bad thing, because
13356 the "get hit by" condition would be lost after the player stops) */
13357 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13358 return; // player moves away from bad thing
13363 else if (test_element == EL_PENGUIN)
13370 if (kill_x != -1 || kill_y != -1)
13372 if (IS_PLAYER(kill_x, kill_y))
13374 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13376 if (player->shield_deadly_time_left > 0 &&
13377 !IS_INDESTRUCTIBLE(bad_element))
13378 Bang(bad_x, bad_y);
13379 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13380 KillPlayer(player);
13383 Bang(kill_x, kill_y);
13387 void TestIfPlayerTouchesBadThing(int x, int y)
13389 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13392 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13394 TestIfGoodThingHitsBadThing(x, y, move_dir);
13397 void TestIfBadThingTouchesPlayer(int x, int y)
13399 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13402 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13404 TestIfBadThingHitsGoodThing(x, y, move_dir);
13407 void TestIfFriendTouchesBadThing(int x, int y)
13409 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13412 void TestIfBadThingTouchesFriend(int x, int y)
13414 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13417 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13419 int i, kill_x = bad_x, kill_y = bad_y;
13420 static int xy[4][2] =
13428 for (i = 0; i < NUM_DIRECTIONS; i++)
13432 x = bad_x + xy[i][0];
13433 y = bad_y + xy[i][1];
13434 if (!IN_LEV_FIELD(x, y))
13437 element = Feld[x][y];
13438 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13439 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13447 if (kill_x != bad_x || kill_y != bad_y)
13448 Bang(bad_x, bad_y);
13451 void KillPlayer(struct PlayerInfo *player)
13453 int jx = player->jx, jy = player->jy;
13455 if (!player->active)
13459 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13460 player->killed, player->active, player->reanimated);
13463 /* the following code was introduced to prevent an infinite loop when calling
13465 -> CheckTriggeredElementChangeExt()
13466 -> ExecuteCustomElementAction()
13468 -> (infinitely repeating the above sequence of function calls)
13469 which occurs when killing the player while having a CE with the setting
13470 "kill player X when explosion of <player X>"; the solution using a new
13471 field "player->killed" was chosen for backwards compatibility, although
13472 clever use of the fields "player->active" etc. would probably also work */
13474 if (player->killed)
13478 player->killed = TRUE;
13480 // remove accessible field at the player's position
13481 Feld[jx][jy] = EL_EMPTY;
13483 // deactivate shield (else Bang()/Explode() would not work right)
13484 player->shield_normal_time_left = 0;
13485 player->shield_deadly_time_left = 0;
13488 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13489 player->killed, player->active, player->reanimated);
13495 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13496 player->killed, player->active, player->reanimated);
13499 if (player->reanimated) // killed player may have been reanimated
13500 player->killed = player->reanimated = FALSE;
13502 BuryPlayer(player);
13505 static void KillPlayerUnlessEnemyProtected(int x, int y)
13507 if (!PLAYER_ENEMY_PROTECTED(x, y))
13508 KillPlayer(PLAYERINFO(x, y));
13511 static void KillPlayerUnlessExplosionProtected(int x, int y)
13513 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13514 KillPlayer(PLAYERINFO(x, y));
13517 void BuryPlayer(struct PlayerInfo *player)
13519 int jx = player->jx, jy = player->jy;
13521 if (!player->active)
13524 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13525 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13527 RemovePlayer(player);
13529 player->buried = TRUE;
13531 if (game.all_players_gone)
13532 game.GameOver = TRUE;
13535 void RemovePlayer(struct PlayerInfo *player)
13537 int jx = player->jx, jy = player->jy;
13538 int i, found = FALSE;
13540 player->present = FALSE;
13541 player->active = FALSE;
13543 // required for some CE actions (even if the player is not active anymore)
13544 player->MovPos = 0;
13546 if (!ExplodeField[jx][jy])
13547 StorePlayer[jx][jy] = 0;
13549 if (player->is_moving)
13550 TEST_DrawLevelField(player->last_jx, player->last_jy);
13552 for (i = 0; i < MAX_PLAYERS; i++)
13553 if (stored_player[i].active)
13558 game.all_players_gone = TRUE;
13559 game.GameOver = TRUE;
13562 game.exit_x = game.robot_wheel_x = jx;
13563 game.exit_y = game.robot_wheel_y = jy;
13566 void ExitPlayer(struct PlayerInfo *player)
13568 DrawPlayer(player); // needed here only to cleanup last field
13569 RemovePlayer(player);
13571 if (game.players_still_needed > 0)
13572 game.players_still_needed--;
13575 static void setFieldForSnapping(int x, int y, int element, int direction)
13577 struct ElementInfo *ei = &element_info[element];
13578 int direction_bit = MV_DIR_TO_BIT(direction);
13579 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13580 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13581 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13583 Feld[x][y] = EL_ELEMENT_SNAPPING;
13584 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13586 ResetGfxAnimation(x, y);
13588 GfxElement[x][y] = element;
13589 GfxAction[x][y] = action;
13590 GfxDir[x][y] = direction;
13591 GfxFrame[x][y] = -1;
13595 =============================================================================
13596 checkDiagonalPushing()
13597 -----------------------------------------------------------------------------
13598 check if diagonal input device direction results in pushing of object
13599 (by checking if the alternative direction is walkable, diggable, ...)
13600 =============================================================================
13603 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13604 int x, int y, int real_dx, int real_dy)
13606 int jx, jy, dx, dy, xx, yy;
13608 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13611 // diagonal direction: check alternative direction
13616 xx = jx + (dx == 0 ? real_dx : 0);
13617 yy = jy + (dy == 0 ? real_dy : 0);
13619 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13623 =============================================================================
13625 -----------------------------------------------------------------------------
13626 x, y: field next to player (non-diagonal) to try to dig to
13627 real_dx, real_dy: direction as read from input device (can be diagonal)
13628 =============================================================================
13631 static int DigField(struct PlayerInfo *player,
13632 int oldx, int oldy, int x, int y,
13633 int real_dx, int real_dy, int mode)
13635 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13636 boolean player_was_pushing = player->is_pushing;
13637 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13638 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13639 int jx = oldx, jy = oldy;
13640 int dx = x - jx, dy = y - jy;
13641 int nextx = x + dx, nexty = y + dy;
13642 int move_direction = (dx == -1 ? MV_LEFT :
13643 dx == +1 ? MV_RIGHT :
13645 dy == +1 ? MV_DOWN : MV_NONE);
13646 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13647 int dig_side = MV_DIR_OPPOSITE(move_direction);
13648 int old_element = Feld[jx][jy];
13649 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13652 if (is_player) // function can also be called by EL_PENGUIN
13654 if (player->MovPos == 0)
13656 player->is_digging = FALSE;
13657 player->is_collecting = FALSE;
13660 if (player->MovPos == 0) // last pushing move finished
13661 player->is_pushing = FALSE;
13663 if (mode == DF_NO_PUSH) // player just stopped pushing
13665 player->is_switching = FALSE;
13666 player->push_delay = -1;
13668 return MP_NO_ACTION;
13672 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13673 old_element = Back[jx][jy];
13675 // in case of element dropped at player position, check background
13676 else if (Back[jx][jy] != EL_EMPTY &&
13677 game.engine_version >= VERSION_IDENT(2,2,0,0))
13678 old_element = Back[jx][jy];
13680 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13681 return MP_NO_ACTION; // field has no opening in this direction
13683 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13684 return MP_NO_ACTION; // field has no opening in this direction
13686 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13690 Feld[jx][jy] = player->artwork_element;
13691 InitMovingField(jx, jy, MV_DOWN);
13692 Store[jx][jy] = EL_ACID;
13693 ContinueMoving(jx, jy);
13694 BuryPlayer(player);
13696 return MP_DONT_RUN_INTO;
13699 if (player_can_move && DONT_RUN_INTO(element))
13701 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13703 return MP_DONT_RUN_INTO;
13706 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13707 return MP_NO_ACTION;
13709 collect_count = element_info[element].collect_count_initial;
13711 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13712 return MP_NO_ACTION;
13714 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13715 player_can_move = player_can_move_or_snap;
13717 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13718 game.engine_version >= VERSION_IDENT(2,2,0,0))
13720 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13721 player->index_bit, dig_side);
13722 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13723 player->index_bit, dig_side);
13725 if (element == EL_DC_LANDMINE)
13728 if (Feld[x][y] != element) // field changed by snapping
13731 return MP_NO_ACTION;
13734 if (player->gravity && is_player && !player->is_auto_moving &&
13735 canFallDown(player) && move_direction != MV_DOWN &&
13736 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13737 return MP_NO_ACTION; // player cannot walk here due to gravity
13739 if (player_can_move &&
13740 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13742 int sound_element = SND_ELEMENT(element);
13743 int sound_action = ACTION_WALKING;
13745 if (IS_RND_GATE(element))
13747 if (!player->key[RND_GATE_NR(element)])
13748 return MP_NO_ACTION;
13750 else if (IS_RND_GATE_GRAY(element))
13752 if (!player->key[RND_GATE_GRAY_NR(element)])
13753 return MP_NO_ACTION;
13755 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13757 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13758 return MP_NO_ACTION;
13760 else if (element == EL_EXIT_OPEN ||
13761 element == EL_EM_EXIT_OPEN ||
13762 element == EL_EM_EXIT_OPENING ||
13763 element == EL_STEEL_EXIT_OPEN ||
13764 element == EL_EM_STEEL_EXIT_OPEN ||
13765 element == EL_EM_STEEL_EXIT_OPENING ||
13766 element == EL_SP_EXIT_OPEN ||
13767 element == EL_SP_EXIT_OPENING)
13769 sound_action = ACTION_PASSING; // player is passing exit
13771 else if (element == EL_EMPTY)
13773 sound_action = ACTION_MOVING; // nothing to walk on
13776 // play sound from background or player, whatever is available
13777 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13778 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13780 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13782 else if (player_can_move &&
13783 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13785 if (!ACCESS_FROM(element, opposite_direction))
13786 return MP_NO_ACTION; // field not accessible from this direction
13788 if (CAN_MOVE(element)) // only fixed elements can be passed!
13789 return MP_NO_ACTION;
13791 if (IS_EM_GATE(element))
13793 if (!player->key[EM_GATE_NR(element)])
13794 return MP_NO_ACTION;
13796 else if (IS_EM_GATE_GRAY(element))
13798 if (!player->key[EM_GATE_GRAY_NR(element)])
13799 return MP_NO_ACTION;
13801 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13803 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13804 return MP_NO_ACTION;
13806 else if (IS_EMC_GATE(element))
13808 if (!player->key[EMC_GATE_NR(element)])
13809 return MP_NO_ACTION;
13811 else if (IS_EMC_GATE_GRAY(element))
13813 if (!player->key[EMC_GATE_GRAY_NR(element)])
13814 return MP_NO_ACTION;
13816 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13818 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13819 return MP_NO_ACTION;
13821 else if (element == EL_DC_GATE_WHITE ||
13822 element == EL_DC_GATE_WHITE_GRAY ||
13823 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13825 if (player->num_white_keys == 0)
13826 return MP_NO_ACTION;
13828 player->num_white_keys--;
13830 else if (IS_SP_PORT(element))
13832 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13833 element == EL_SP_GRAVITY_PORT_RIGHT ||
13834 element == EL_SP_GRAVITY_PORT_UP ||
13835 element == EL_SP_GRAVITY_PORT_DOWN)
13836 player->gravity = !player->gravity;
13837 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13838 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13839 element == EL_SP_GRAVITY_ON_PORT_UP ||
13840 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13841 player->gravity = TRUE;
13842 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13843 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13844 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13845 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13846 player->gravity = FALSE;
13849 // automatically move to the next field with double speed
13850 player->programmed_action = move_direction;
13852 if (player->move_delay_reset_counter == 0)
13854 player->move_delay_reset_counter = 2; // two double speed steps
13856 DOUBLE_PLAYER_SPEED(player);
13859 PlayLevelSoundAction(x, y, ACTION_PASSING);
13861 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13865 if (mode != DF_SNAP)
13867 GfxElement[x][y] = GFX_ELEMENT(element);
13868 player->is_digging = TRUE;
13871 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13873 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13874 player->index_bit, dig_side);
13876 if (mode == DF_SNAP)
13878 if (level.block_snap_field)
13879 setFieldForSnapping(x, y, element, move_direction);
13881 TestIfElementTouchesCustomElement(x, y); // for empty space
13883 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13884 player->index_bit, dig_side);
13887 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13891 if (is_player && mode != DF_SNAP)
13893 GfxElement[x][y] = element;
13894 player->is_collecting = TRUE;
13897 if (element == EL_SPEED_PILL)
13899 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
13901 else if (element == EL_EXTRA_TIME && level.time > 0)
13903 TimeLeft += level.extra_time;
13905 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13907 DisplayGameControlValues();
13909 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
13911 player->shield_normal_time_left += level.shield_normal_time;
13912 if (element == EL_SHIELD_DEADLY)
13913 player->shield_deadly_time_left += level.shield_deadly_time;
13915 else if (element == EL_DYNAMITE ||
13916 element == EL_EM_DYNAMITE ||
13917 element == EL_SP_DISK_RED)
13919 if (player->inventory_size < MAX_INVENTORY_SIZE)
13920 player->inventory_element[player->inventory_size++] = element;
13922 DrawGameDoorValues();
13924 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
13926 player->dynabomb_count++;
13927 player->dynabombs_left++;
13929 else if (element == EL_DYNABOMB_INCREASE_SIZE)
13931 player->dynabomb_size++;
13933 else if (element == EL_DYNABOMB_INCREASE_POWER)
13935 player->dynabomb_xl = TRUE;
13937 else if (IS_KEY(element))
13939 player->key[KEY_NR(element)] = TRUE;
13941 DrawGameDoorValues();
13943 else if (element == EL_DC_KEY_WHITE)
13945 player->num_white_keys++;
13947 // display white keys?
13948 // DrawGameDoorValues();
13950 else if (IS_ENVELOPE(element))
13952 player->show_envelope = element;
13954 else if (element == EL_EMC_LENSES)
13956 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
13958 RedrawAllInvisibleElementsForLenses();
13960 else if (element == EL_EMC_MAGNIFIER)
13962 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
13964 RedrawAllInvisibleElementsForMagnifier();
13966 else if (IS_DROPPABLE(element) ||
13967 IS_THROWABLE(element)) // can be collected and dropped
13971 if (collect_count == 0)
13972 player->inventory_infinite_element = element;
13974 for (i = 0; i < collect_count; i++)
13975 if (player->inventory_size < MAX_INVENTORY_SIZE)
13976 player->inventory_element[player->inventory_size++] = element;
13978 DrawGameDoorValues();
13980 else if (collect_count > 0)
13982 game.gems_still_needed -= collect_count;
13983 if (game.gems_still_needed < 0)
13984 game.gems_still_needed = 0;
13986 game.snapshot.collected_item = TRUE;
13988 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
13990 DisplayGameControlValues();
13993 RaiseScoreElement(element);
13994 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
13997 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
13998 player->index_bit, dig_side);
14000 if (mode == DF_SNAP)
14002 if (level.block_snap_field)
14003 setFieldForSnapping(x, y, element, move_direction);
14005 TestIfElementTouchesCustomElement(x, y); // for empty space
14007 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14008 player->index_bit, dig_side);
14011 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14013 if (mode == DF_SNAP && element != EL_BD_ROCK)
14014 return MP_NO_ACTION;
14016 if (CAN_FALL(element) && dy)
14017 return MP_NO_ACTION;
14019 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14020 !(element == EL_SPRING && level.use_spring_bug))
14021 return MP_NO_ACTION;
14023 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14024 ((move_direction & MV_VERTICAL &&
14025 ((element_info[element].move_pattern & MV_LEFT &&
14026 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14027 (element_info[element].move_pattern & MV_RIGHT &&
14028 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14029 (move_direction & MV_HORIZONTAL &&
14030 ((element_info[element].move_pattern & MV_UP &&
14031 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14032 (element_info[element].move_pattern & MV_DOWN &&
14033 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14034 return MP_NO_ACTION;
14036 // do not push elements already moving away faster than player
14037 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14038 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14039 return MP_NO_ACTION;
14041 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14043 if (player->push_delay_value == -1 || !player_was_pushing)
14044 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14046 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14048 if (player->push_delay_value == -1)
14049 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14051 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14053 if (!player->is_pushing)
14054 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14057 player->is_pushing = TRUE;
14058 player->is_active = TRUE;
14060 if (!(IN_LEV_FIELD(nextx, nexty) &&
14061 (IS_FREE(nextx, nexty) ||
14062 (IS_SB_ELEMENT(element) &&
14063 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14064 (IS_CUSTOM_ELEMENT(element) &&
14065 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14066 return MP_NO_ACTION;
14068 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14069 return MP_NO_ACTION;
14071 if (player->push_delay == -1) // new pushing; restart delay
14072 player->push_delay = 0;
14074 if (player->push_delay < player->push_delay_value &&
14075 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14076 element != EL_SPRING && element != EL_BALLOON)
14078 // make sure that there is no move delay before next try to push
14079 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14080 player->move_delay = 0;
14082 return MP_NO_ACTION;
14085 if (IS_CUSTOM_ELEMENT(element) &&
14086 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14088 if (!DigFieldByCE(nextx, nexty, element))
14089 return MP_NO_ACTION;
14092 if (IS_SB_ELEMENT(element))
14094 boolean sokoban_task_solved = FALSE;
14096 if (element == EL_SOKOBAN_FIELD_FULL)
14098 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14100 IncrementSokobanFieldsNeeded();
14101 IncrementSokobanObjectsNeeded();
14104 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14106 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14108 DecrementSokobanFieldsNeeded();
14109 DecrementSokobanObjectsNeeded();
14111 // sokoban object was pushed from empty field to sokoban field
14112 if (Back[x][y] == EL_EMPTY)
14113 sokoban_task_solved = TRUE;
14116 Feld[x][y] = EL_SOKOBAN_OBJECT;
14118 if (Back[x][y] == Back[nextx][nexty])
14119 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14120 else if (Back[x][y] != 0)
14121 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14124 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14127 if (sokoban_task_solved &&
14128 game.sokoban_fields_still_needed == 0 &&
14129 game.sokoban_objects_still_needed == 0 &&
14130 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14132 game.players_still_needed = 0;
14136 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14140 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14142 InitMovingField(x, y, move_direction);
14143 GfxAction[x][y] = ACTION_PUSHING;
14145 if (mode == DF_SNAP)
14146 ContinueMoving(x, y);
14148 MovPos[x][y] = (dx != 0 ? dx : dy);
14150 Pushed[x][y] = TRUE;
14151 Pushed[nextx][nexty] = TRUE;
14153 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14154 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14156 player->push_delay_value = -1; // get new value later
14158 // check for element change _after_ element has been pushed
14159 if (game.use_change_when_pushing_bug)
14161 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14162 player->index_bit, dig_side);
14163 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14164 player->index_bit, dig_side);
14167 else if (IS_SWITCHABLE(element))
14169 if (PLAYER_SWITCHING(player, x, y))
14171 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14172 player->index_bit, dig_side);
14177 player->is_switching = TRUE;
14178 player->switch_x = x;
14179 player->switch_y = y;
14181 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14183 if (element == EL_ROBOT_WHEEL)
14185 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14187 game.robot_wheel_x = x;
14188 game.robot_wheel_y = y;
14189 game.robot_wheel_active = TRUE;
14191 TEST_DrawLevelField(x, y);
14193 else if (element == EL_SP_TERMINAL)
14197 SCAN_PLAYFIELD(xx, yy)
14199 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14203 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14205 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14207 ResetGfxAnimation(xx, yy);
14208 TEST_DrawLevelField(xx, yy);
14212 else if (IS_BELT_SWITCH(element))
14214 ToggleBeltSwitch(x, y);
14216 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14217 element == EL_SWITCHGATE_SWITCH_DOWN ||
14218 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14219 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14221 ToggleSwitchgateSwitch(x, y);
14223 else if (element == EL_LIGHT_SWITCH ||
14224 element == EL_LIGHT_SWITCH_ACTIVE)
14226 ToggleLightSwitch(x, y);
14228 else if (element == EL_TIMEGATE_SWITCH ||
14229 element == EL_DC_TIMEGATE_SWITCH)
14231 ActivateTimegateSwitch(x, y);
14233 else if (element == EL_BALLOON_SWITCH_LEFT ||
14234 element == EL_BALLOON_SWITCH_RIGHT ||
14235 element == EL_BALLOON_SWITCH_UP ||
14236 element == EL_BALLOON_SWITCH_DOWN ||
14237 element == EL_BALLOON_SWITCH_NONE ||
14238 element == EL_BALLOON_SWITCH_ANY)
14240 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14241 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14242 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14243 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14244 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14247 else if (element == EL_LAMP)
14249 Feld[x][y] = EL_LAMP_ACTIVE;
14250 game.lights_still_needed--;
14252 ResetGfxAnimation(x, y);
14253 TEST_DrawLevelField(x, y);
14255 else if (element == EL_TIME_ORB_FULL)
14257 Feld[x][y] = EL_TIME_ORB_EMPTY;
14259 if (level.time > 0 || level.use_time_orb_bug)
14261 TimeLeft += level.time_orb_time;
14262 game.no_time_limit = FALSE;
14264 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14266 DisplayGameControlValues();
14269 ResetGfxAnimation(x, y);
14270 TEST_DrawLevelField(x, y);
14272 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14273 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14277 game.ball_active = !game.ball_active;
14279 SCAN_PLAYFIELD(xx, yy)
14281 int e = Feld[xx][yy];
14283 if (game.ball_active)
14285 if (e == EL_EMC_MAGIC_BALL)
14286 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14287 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14288 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14292 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14293 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14294 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14295 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14300 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14301 player->index_bit, dig_side);
14303 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14304 player->index_bit, dig_side);
14306 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14307 player->index_bit, dig_side);
14313 if (!PLAYER_SWITCHING(player, x, y))
14315 player->is_switching = TRUE;
14316 player->switch_x = x;
14317 player->switch_y = y;
14319 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14320 player->index_bit, dig_side);
14321 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14322 player->index_bit, dig_side);
14324 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14325 player->index_bit, dig_side);
14326 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14327 player->index_bit, dig_side);
14330 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14331 player->index_bit, dig_side);
14332 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14333 player->index_bit, dig_side);
14335 return MP_NO_ACTION;
14338 player->push_delay = -1;
14340 if (is_player) // function can also be called by EL_PENGUIN
14342 if (Feld[x][y] != element) // really digged/collected something
14344 player->is_collecting = !player->is_digging;
14345 player->is_active = TRUE;
14352 static boolean DigFieldByCE(int x, int y, int digging_element)
14354 int element = Feld[x][y];
14356 if (!IS_FREE(x, y))
14358 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14359 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14362 // no element can dig solid indestructible elements
14363 if (IS_INDESTRUCTIBLE(element) &&
14364 !IS_DIGGABLE(element) &&
14365 !IS_COLLECTIBLE(element))
14368 if (AmoebaNr[x][y] &&
14369 (element == EL_AMOEBA_FULL ||
14370 element == EL_BD_AMOEBA ||
14371 element == EL_AMOEBA_GROWING))
14373 AmoebaCnt[AmoebaNr[x][y]]--;
14374 AmoebaCnt2[AmoebaNr[x][y]]--;
14377 if (IS_MOVING(x, y))
14378 RemoveMovingField(x, y);
14382 TEST_DrawLevelField(x, y);
14385 // if digged element was about to explode, prevent the explosion
14386 ExplodeField[x][y] = EX_TYPE_NONE;
14388 PlayLevelSoundAction(x, y, action);
14391 Store[x][y] = EL_EMPTY;
14393 // this makes it possible to leave the removed element again
14394 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14395 Store[x][y] = element;
14400 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14402 int jx = player->jx, jy = player->jy;
14403 int x = jx + dx, y = jy + dy;
14404 int snap_direction = (dx == -1 ? MV_LEFT :
14405 dx == +1 ? MV_RIGHT :
14407 dy == +1 ? MV_DOWN : MV_NONE);
14408 boolean can_continue_snapping = (level.continuous_snapping &&
14409 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14411 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14414 if (!player->active || !IN_LEV_FIELD(x, y))
14422 if (player->MovPos == 0)
14423 player->is_pushing = FALSE;
14425 player->is_snapping = FALSE;
14427 if (player->MovPos == 0)
14429 player->is_moving = FALSE;
14430 player->is_digging = FALSE;
14431 player->is_collecting = FALSE;
14437 // prevent snapping with already pressed snap key when not allowed
14438 if (player->is_snapping && !can_continue_snapping)
14441 player->MovDir = snap_direction;
14443 if (player->MovPos == 0)
14445 player->is_moving = FALSE;
14446 player->is_digging = FALSE;
14447 player->is_collecting = FALSE;
14450 player->is_dropping = FALSE;
14451 player->is_dropping_pressed = FALSE;
14452 player->drop_pressed_delay = 0;
14454 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14457 player->is_snapping = TRUE;
14458 player->is_active = TRUE;
14460 if (player->MovPos == 0)
14462 player->is_moving = FALSE;
14463 player->is_digging = FALSE;
14464 player->is_collecting = FALSE;
14467 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14468 TEST_DrawLevelField(player->last_jx, player->last_jy);
14470 TEST_DrawLevelField(x, y);
14475 static boolean DropElement(struct PlayerInfo *player)
14477 int old_element, new_element;
14478 int dropx = player->jx, dropy = player->jy;
14479 int drop_direction = player->MovDir;
14480 int drop_side = drop_direction;
14481 int drop_element = get_next_dropped_element(player);
14483 /* do not drop an element on top of another element; when holding drop key
14484 pressed without moving, dropped element must move away before the next
14485 element can be dropped (this is especially important if the next element
14486 is dynamite, which can be placed on background for historical reasons) */
14487 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14490 if (IS_THROWABLE(drop_element))
14492 dropx += GET_DX_FROM_DIR(drop_direction);
14493 dropy += GET_DY_FROM_DIR(drop_direction);
14495 if (!IN_LEV_FIELD(dropx, dropy))
14499 old_element = Feld[dropx][dropy]; // old element at dropping position
14500 new_element = drop_element; // default: no change when dropping
14502 // check if player is active, not moving and ready to drop
14503 if (!player->active || player->MovPos || player->drop_delay > 0)
14506 // check if player has anything that can be dropped
14507 if (new_element == EL_UNDEFINED)
14510 // only set if player has anything that can be dropped
14511 player->is_dropping_pressed = TRUE;
14513 // check if drop key was pressed long enough for EM style dynamite
14514 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14517 // check if anything can be dropped at the current position
14518 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14521 // collected custom elements can only be dropped on empty fields
14522 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14525 if (old_element != EL_EMPTY)
14526 Back[dropx][dropy] = old_element; // store old element on this field
14528 ResetGfxAnimation(dropx, dropy);
14529 ResetRandomAnimationValue(dropx, dropy);
14531 if (player->inventory_size > 0 ||
14532 player->inventory_infinite_element != EL_UNDEFINED)
14534 if (player->inventory_size > 0)
14536 player->inventory_size--;
14538 DrawGameDoorValues();
14540 if (new_element == EL_DYNAMITE)
14541 new_element = EL_DYNAMITE_ACTIVE;
14542 else if (new_element == EL_EM_DYNAMITE)
14543 new_element = EL_EM_DYNAMITE_ACTIVE;
14544 else if (new_element == EL_SP_DISK_RED)
14545 new_element = EL_SP_DISK_RED_ACTIVE;
14548 Feld[dropx][dropy] = new_element;
14550 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14551 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14552 el2img(Feld[dropx][dropy]), 0);
14554 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14556 // needed if previous element just changed to "empty" in the last frame
14557 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14559 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14560 player->index_bit, drop_side);
14561 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14563 player->index_bit, drop_side);
14565 TestIfElementTouchesCustomElement(dropx, dropy);
14567 else // player is dropping a dyna bomb
14569 player->dynabombs_left--;
14571 Feld[dropx][dropy] = new_element;
14573 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14574 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14575 el2img(Feld[dropx][dropy]), 0);
14577 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14580 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14581 InitField_WithBug1(dropx, dropy, FALSE);
14583 new_element = Feld[dropx][dropy]; // element might have changed
14585 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14586 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14588 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14589 MovDir[dropx][dropy] = drop_direction;
14591 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14593 // do not cause impact style collision by dropping elements that can fall
14594 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14597 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14598 player->is_dropping = TRUE;
14600 player->drop_pressed_delay = 0;
14601 player->is_dropping_pressed = FALSE;
14603 player->drop_x = dropx;
14604 player->drop_y = dropy;
14609 // ----------------------------------------------------------------------------
14610 // game sound playing functions
14611 // ----------------------------------------------------------------------------
14613 static int *loop_sound_frame = NULL;
14614 static int *loop_sound_volume = NULL;
14616 void InitPlayLevelSound(void)
14618 int num_sounds = getSoundListSize();
14620 checked_free(loop_sound_frame);
14621 checked_free(loop_sound_volume);
14623 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14624 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14627 static void PlayLevelSound(int x, int y, int nr)
14629 int sx = SCREENX(x), sy = SCREENY(y);
14630 int volume, stereo_position;
14631 int max_distance = 8;
14632 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14634 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14635 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14638 if (!IN_LEV_FIELD(x, y) ||
14639 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14640 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14643 volume = SOUND_MAX_VOLUME;
14645 if (!IN_SCR_FIELD(sx, sy))
14647 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14648 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14650 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14653 stereo_position = (SOUND_MAX_LEFT +
14654 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14655 (SCR_FIELDX + 2 * max_distance));
14657 if (IS_LOOP_SOUND(nr))
14659 /* This assures that quieter loop sounds do not overwrite louder ones,
14660 while restarting sound volume comparison with each new game frame. */
14662 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14665 loop_sound_volume[nr] = volume;
14666 loop_sound_frame[nr] = FrameCounter;
14669 PlaySoundExt(nr, volume, stereo_position, type);
14672 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14674 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14675 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14676 y < LEVELY(BY1) ? LEVELY(BY1) :
14677 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14681 static void PlayLevelSoundAction(int x, int y, int action)
14683 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14686 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14688 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14690 if (sound_effect != SND_UNDEFINED)
14691 PlayLevelSound(x, y, sound_effect);
14694 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14697 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14699 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14700 PlayLevelSound(x, y, sound_effect);
14703 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14705 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14707 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14708 PlayLevelSound(x, y, sound_effect);
14711 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14713 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14715 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14716 StopSound(sound_effect);
14719 static int getLevelMusicNr(void)
14721 if (levelset.music[level_nr] != MUS_UNDEFINED)
14722 return levelset.music[level_nr]; // from config file
14724 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14727 static void FadeLevelSounds(void)
14732 static void FadeLevelMusic(void)
14734 int music_nr = getLevelMusicNr();
14735 char *curr_music = getCurrentlyPlayingMusicFilename();
14736 char *next_music = getMusicInfoEntryFilename(music_nr);
14738 if (!strEqual(curr_music, next_music))
14742 void FadeLevelSoundsAndMusic(void)
14748 static void PlayLevelMusic(void)
14750 int music_nr = getLevelMusicNr();
14751 char *curr_music = getCurrentlyPlayingMusicFilename();
14752 char *next_music = getMusicInfoEntryFilename(music_nr);
14754 if (!strEqual(curr_music, next_music))
14755 PlayMusicLoop(music_nr);
14758 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14760 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14761 int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
14762 int x = xx - 1 - offset;
14763 int y = yy - 1 - offset;
14768 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14772 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14776 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14780 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14784 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14788 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14792 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14795 case SOUND_android_clone:
14796 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14799 case SOUND_android_move:
14800 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14804 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14808 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14812 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14815 case SOUND_eater_eat:
14816 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14820 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14823 case SOUND_collect:
14824 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14827 case SOUND_diamond:
14828 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14832 // !!! CHECK THIS !!!
14834 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14836 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14840 case SOUND_wonderfall:
14841 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14845 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14849 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14853 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14857 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14861 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14865 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14869 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14873 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14876 case SOUND_exit_open:
14877 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14880 case SOUND_exit_leave:
14881 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14884 case SOUND_dynamite:
14885 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14889 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14893 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14897 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14901 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
14905 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
14909 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
14913 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
14918 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
14920 int element = map_element_SP_to_RND(element_sp);
14921 int action = map_action_SP_to_RND(action_sp);
14922 int offset = (setup.sp_show_border_elements ? 0 : 1);
14923 int x = xx - offset;
14924 int y = yy - offset;
14926 PlayLevelSoundElementAction(x, y, element, action);
14929 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
14931 int element = map_element_MM_to_RND(element_mm);
14932 int action = map_action_MM_to_RND(action_mm);
14934 int x = xx - offset;
14935 int y = yy - offset;
14937 if (!IS_MM_ELEMENT(element))
14938 element = EL_MM_DEFAULT;
14940 PlayLevelSoundElementAction(x, y, element, action);
14943 void PlaySound_MM(int sound_mm)
14945 int sound = map_sound_MM_to_RND(sound_mm);
14947 if (sound == SND_UNDEFINED)
14953 void PlaySoundLoop_MM(int sound_mm)
14955 int sound = map_sound_MM_to_RND(sound_mm);
14957 if (sound == SND_UNDEFINED)
14960 PlaySoundLoop(sound);
14963 void StopSound_MM(int sound_mm)
14965 int sound = map_sound_MM_to_RND(sound_mm);
14967 if (sound == SND_UNDEFINED)
14973 void RaiseScore(int value)
14975 game.score += value;
14977 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
14979 DisplayGameControlValues();
14982 void RaiseScoreElement(int element)
14987 case EL_BD_DIAMOND:
14988 case EL_EMERALD_YELLOW:
14989 case EL_EMERALD_RED:
14990 case EL_EMERALD_PURPLE:
14991 case EL_SP_INFOTRON:
14992 RaiseScore(level.score[SC_EMERALD]);
14995 RaiseScore(level.score[SC_DIAMOND]);
14998 RaiseScore(level.score[SC_CRYSTAL]);
15001 RaiseScore(level.score[SC_PEARL]);
15004 case EL_BD_BUTTERFLY:
15005 case EL_SP_ELECTRON:
15006 RaiseScore(level.score[SC_BUG]);
15009 case EL_BD_FIREFLY:
15010 case EL_SP_SNIKSNAK:
15011 RaiseScore(level.score[SC_SPACESHIP]);
15014 case EL_DARK_YAMYAM:
15015 RaiseScore(level.score[SC_YAMYAM]);
15018 RaiseScore(level.score[SC_ROBOT]);
15021 RaiseScore(level.score[SC_PACMAN]);
15024 RaiseScore(level.score[SC_NUT]);
15027 case EL_EM_DYNAMITE:
15028 case EL_SP_DISK_RED:
15029 case EL_DYNABOMB_INCREASE_NUMBER:
15030 case EL_DYNABOMB_INCREASE_SIZE:
15031 case EL_DYNABOMB_INCREASE_POWER:
15032 RaiseScore(level.score[SC_DYNAMITE]);
15034 case EL_SHIELD_NORMAL:
15035 case EL_SHIELD_DEADLY:
15036 RaiseScore(level.score[SC_SHIELD]);
15038 case EL_EXTRA_TIME:
15039 RaiseScore(level.extra_time_score);
15053 case EL_DC_KEY_WHITE:
15054 RaiseScore(level.score[SC_KEY]);
15057 RaiseScore(element_info[element].collect_score);
15062 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15064 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15066 // closing door required in case of envelope style request dialogs
15069 // prevent short reactivation of overlay buttons while closing door
15070 SetOverlayActive(FALSE);
15072 CloseDoor(DOOR_CLOSE_1);
15075 if (network.enabled)
15076 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15080 FadeSkipNextFadeIn();
15082 SetGameStatus(GAME_MODE_MAIN);
15087 else // continue playing the game
15089 if (tape.playing && tape.deactivate_display)
15090 TapeDeactivateDisplayOff(TRUE);
15092 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15094 if (tape.playing && tape.deactivate_display)
15095 TapeDeactivateDisplayOn();
15099 void RequestQuitGame(boolean ask_if_really_quit)
15101 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15102 boolean skip_request = game.all_players_gone || quick_quit;
15104 RequestQuitGameExt(skip_request, quick_quit,
15105 "Do you really want to quit the game?");
15108 void RequestRestartGame(char *message)
15110 game.restart_game_message = NULL;
15112 boolean has_started_game = hasStartedNetworkGame();
15113 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15115 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15117 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15121 SetGameStatus(GAME_MODE_MAIN);
15127 void CheckGameOver(void)
15129 static boolean last_game_over = FALSE;
15130 static int game_over_delay = 0;
15131 int game_over_delay_value = 50;
15132 boolean game_over = checkGameFailed();
15134 // do not handle game over if request dialog is already active
15135 if (game.request_active)
15138 // do not ask to play again if game was never actually played
15139 if (!game.GamePlayed)
15144 last_game_over = FALSE;
15145 game_over_delay = game_over_delay_value;
15150 if (game_over_delay > 0)
15157 if (last_game_over != game_over)
15158 game.restart_game_message = (hasStartedNetworkGame() ?
15159 "Game over! Play it again?" :
15162 last_game_over = game_over;
15165 boolean checkGameSolved(void)
15167 // set for all game engines if level was solved
15168 return game.LevelSolved_GameEnd;
15171 boolean checkGameFailed(void)
15173 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15174 return (game_em.game_over && !game_em.level_solved);
15175 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15176 return (game_sp.game_over && !game_sp.level_solved);
15177 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15178 return (game_mm.game_over && !game_mm.level_solved);
15179 else // GAME_ENGINE_TYPE_RND
15180 return (game.GameOver && !game.LevelSolved);
15183 boolean checkGameEnded(void)
15185 return (checkGameSolved() || checkGameFailed());
15189 // ----------------------------------------------------------------------------
15190 // random generator functions
15191 // ----------------------------------------------------------------------------
15193 unsigned int InitEngineRandom_RND(int seed)
15195 game.num_random_calls = 0;
15197 return InitEngineRandom(seed);
15200 unsigned int RND(int max)
15204 game.num_random_calls++;
15206 return GetEngineRandom(max);
15213 // ----------------------------------------------------------------------------
15214 // game engine snapshot handling functions
15215 // ----------------------------------------------------------------------------
15217 struct EngineSnapshotInfo
15219 // runtime values for custom element collect score
15220 int collect_score[NUM_CUSTOM_ELEMENTS];
15222 // runtime values for group element choice position
15223 int choice_pos[NUM_GROUP_ELEMENTS];
15225 // runtime values for belt position animations
15226 int belt_graphic[4][NUM_BELT_PARTS];
15227 int belt_anim_mode[4][NUM_BELT_PARTS];
15230 static struct EngineSnapshotInfo engine_snapshot_rnd;
15231 static char *snapshot_level_identifier = NULL;
15232 static int snapshot_level_nr = -1;
15234 static void SaveEngineSnapshotValues_RND(void)
15236 static int belt_base_active_element[4] =
15238 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15239 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15240 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15241 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15245 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15247 int element = EL_CUSTOM_START + i;
15249 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15252 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15254 int element = EL_GROUP_START + i;
15256 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15259 for (i = 0; i < 4; i++)
15261 for (j = 0; j < NUM_BELT_PARTS; j++)
15263 int element = belt_base_active_element[i] + j;
15264 int graphic = el2img(element);
15265 int anim_mode = graphic_info[graphic].anim_mode;
15267 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15268 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15273 static void LoadEngineSnapshotValues_RND(void)
15275 unsigned int num_random_calls = game.num_random_calls;
15278 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15280 int element = EL_CUSTOM_START + i;
15282 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15285 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15287 int element = EL_GROUP_START + i;
15289 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15292 for (i = 0; i < 4; i++)
15294 for (j = 0; j < NUM_BELT_PARTS; j++)
15296 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15297 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15299 graphic_info[graphic].anim_mode = anim_mode;
15303 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15305 InitRND(tape.random_seed);
15306 for (i = 0; i < num_random_calls; i++)
15310 if (game.num_random_calls != num_random_calls)
15312 Error(ERR_INFO, "number of random calls out of sync");
15313 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15314 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15315 Error(ERR_EXIT, "this should not happen -- please debug");
15319 void FreeEngineSnapshotSingle(void)
15321 FreeSnapshotSingle();
15323 setString(&snapshot_level_identifier, NULL);
15324 snapshot_level_nr = -1;
15327 void FreeEngineSnapshotList(void)
15329 FreeSnapshotList();
15332 static ListNode *SaveEngineSnapshotBuffers(void)
15334 ListNode *buffers = NULL;
15336 // copy some special values to a structure better suited for the snapshot
15338 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15339 SaveEngineSnapshotValues_RND();
15340 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15341 SaveEngineSnapshotValues_EM();
15342 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15343 SaveEngineSnapshotValues_SP(&buffers);
15344 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15345 SaveEngineSnapshotValues_MM(&buffers);
15347 // save values stored in special snapshot structure
15349 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15350 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15351 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15352 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15353 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15354 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15355 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15356 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15358 // save further RND engine values
15360 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15361 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15362 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15364 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15365 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15366 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15367 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15368 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15370 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15371 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15372 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15374 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15376 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15377 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15379 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15380 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15381 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15382 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15383 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15384 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15385 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15386 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15387 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15388 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15389 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15390 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15391 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15392 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15393 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15394 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15395 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15396 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15398 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15399 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15401 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15402 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15403 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15405 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15406 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15408 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15409 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15410 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15411 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15412 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15414 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15415 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15418 ListNode *node = engine_snapshot_list_rnd;
15421 while (node != NULL)
15423 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15428 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15434 void SaveEngineSnapshotSingle(void)
15436 ListNode *buffers = SaveEngineSnapshotBuffers();
15438 // finally save all snapshot buffers to single snapshot
15439 SaveSnapshotSingle(buffers);
15441 // save level identification information
15442 setString(&snapshot_level_identifier, leveldir_current->identifier);
15443 snapshot_level_nr = level_nr;
15446 boolean CheckSaveEngineSnapshotToList(void)
15448 boolean save_snapshot =
15449 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15450 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15451 game.snapshot.changed_action) ||
15452 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15453 game.snapshot.collected_item));
15455 game.snapshot.changed_action = FALSE;
15456 game.snapshot.collected_item = FALSE;
15457 game.snapshot.save_snapshot = save_snapshot;
15459 return save_snapshot;
15462 void SaveEngineSnapshotToList(void)
15464 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15468 ListNode *buffers = SaveEngineSnapshotBuffers();
15470 // finally save all snapshot buffers to snapshot list
15471 SaveSnapshotToList(buffers);
15474 void SaveEngineSnapshotToListInitial(void)
15476 FreeEngineSnapshotList();
15478 SaveEngineSnapshotToList();
15481 static void LoadEngineSnapshotValues(void)
15483 // restore special values from snapshot structure
15485 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15486 LoadEngineSnapshotValues_RND();
15487 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15488 LoadEngineSnapshotValues_EM();
15489 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15490 LoadEngineSnapshotValues_SP();
15491 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15492 LoadEngineSnapshotValues_MM();
15495 void LoadEngineSnapshotSingle(void)
15497 LoadSnapshotSingle();
15499 LoadEngineSnapshotValues();
15502 static void LoadEngineSnapshot_Undo(int steps)
15504 LoadSnapshotFromList_Older(steps);
15506 LoadEngineSnapshotValues();
15509 static void LoadEngineSnapshot_Redo(int steps)
15511 LoadSnapshotFromList_Newer(steps);
15513 LoadEngineSnapshotValues();
15516 boolean CheckEngineSnapshotSingle(void)
15518 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15519 snapshot_level_nr == level_nr);
15522 boolean CheckEngineSnapshotList(void)
15524 return CheckSnapshotList();
15528 // ---------- new game button stuff -------------------------------------------
15535 boolean *setup_value;
15536 boolean allowed_on_tape;
15537 boolean is_touch_button;
15539 } gamebutton_info[NUM_GAME_BUTTONS] =
15542 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15543 GAME_CTRL_ID_STOP, NULL,
15544 TRUE, FALSE, "stop game"
15547 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15548 GAME_CTRL_ID_PAUSE, NULL,
15549 TRUE, FALSE, "pause game"
15552 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15553 GAME_CTRL_ID_PLAY, NULL,
15554 TRUE, FALSE, "play game"
15557 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15558 GAME_CTRL_ID_UNDO, NULL,
15559 TRUE, FALSE, "undo step"
15562 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15563 GAME_CTRL_ID_REDO, NULL,
15564 TRUE, FALSE, "redo step"
15567 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15568 GAME_CTRL_ID_SAVE, NULL,
15569 TRUE, FALSE, "save game"
15572 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15573 GAME_CTRL_ID_PAUSE2, NULL,
15574 TRUE, FALSE, "pause game"
15577 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15578 GAME_CTRL_ID_LOAD, NULL,
15579 TRUE, FALSE, "load game"
15582 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15583 GAME_CTRL_ID_PANEL_STOP, NULL,
15584 FALSE, FALSE, "stop game"
15587 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15588 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15589 FALSE, FALSE, "pause game"
15592 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15593 GAME_CTRL_ID_PANEL_PLAY, NULL,
15594 FALSE, FALSE, "play game"
15597 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15598 GAME_CTRL_ID_TOUCH_STOP, NULL,
15599 FALSE, TRUE, "stop game"
15602 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15603 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15604 FALSE, TRUE, "pause game"
15607 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15608 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15609 TRUE, FALSE, "background music on/off"
15612 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15613 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15614 TRUE, FALSE, "sound loops on/off"
15617 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15618 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15619 TRUE, FALSE, "normal sounds on/off"
15622 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15623 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15624 FALSE, FALSE, "background music on/off"
15627 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15628 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15629 FALSE, FALSE, "sound loops on/off"
15632 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15633 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15634 FALSE, FALSE, "normal sounds on/off"
15638 void CreateGameButtons(void)
15642 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15644 int graphic = gamebutton_info[i].graphic;
15645 struct GraphicInfo *gfx = &graphic_info[graphic];
15646 struct XY *pos = gamebutton_info[i].pos;
15647 struct GadgetInfo *gi;
15650 unsigned int event_mask;
15651 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15652 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15653 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15654 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15655 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15656 int gd_x = gfx->src_x;
15657 int gd_y = gfx->src_y;
15658 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15659 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15660 int gd_xa = gfx->src_x + gfx->active_xoffset;
15661 int gd_ya = gfx->src_y + gfx->active_yoffset;
15662 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15663 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15664 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15665 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15668 if (gfx->bitmap == NULL)
15670 game_gadget[id] = NULL;
15675 if (id == GAME_CTRL_ID_STOP ||
15676 id == GAME_CTRL_ID_PANEL_STOP ||
15677 id == GAME_CTRL_ID_TOUCH_STOP ||
15678 id == GAME_CTRL_ID_PLAY ||
15679 id == GAME_CTRL_ID_PANEL_PLAY ||
15680 id == GAME_CTRL_ID_SAVE ||
15681 id == GAME_CTRL_ID_LOAD)
15683 button_type = GD_TYPE_NORMAL_BUTTON;
15685 event_mask = GD_EVENT_RELEASED;
15687 else if (id == GAME_CTRL_ID_UNDO ||
15688 id == GAME_CTRL_ID_REDO)
15690 button_type = GD_TYPE_NORMAL_BUTTON;
15692 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15696 button_type = GD_TYPE_CHECK_BUTTON;
15697 checked = (gamebutton_info[i].setup_value != NULL ?
15698 *gamebutton_info[i].setup_value : FALSE);
15699 event_mask = GD_EVENT_PRESSED;
15702 gi = CreateGadget(GDI_CUSTOM_ID, id,
15703 GDI_IMAGE_ID, graphic,
15704 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15707 GDI_WIDTH, gfx->width,
15708 GDI_HEIGHT, gfx->height,
15709 GDI_TYPE, button_type,
15710 GDI_STATE, GD_BUTTON_UNPRESSED,
15711 GDI_CHECKED, checked,
15712 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15713 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15714 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15715 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15716 GDI_DIRECT_DRAW, FALSE,
15717 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15718 GDI_EVENT_MASK, event_mask,
15719 GDI_CALLBACK_ACTION, HandleGameButtons,
15723 Error(ERR_EXIT, "cannot create gadget");
15725 game_gadget[id] = gi;
15729 void FreeGameButtons(void)
15733 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15734 FreeGadget(game_gadget[i]);
15737 static void UnmapGameButtonsAtSamePosition(int id)
15741 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15743 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15744 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15745 UnmapGadget(game_gadget[i]);
15748 static void UnmapGameButtonsAtSamePosition_All(void)
15750 if (setup.show_snapshot_buttons)
15752 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15753 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15754 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15758 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15759 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15760 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15762 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15763 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15764 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15768 static void MapGameButtonsAtSamePosition(int id)
15772 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15774 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15775 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15776 MapGadget(game_gadget[i]);
15778 UnmapGameButtonsAtSamePosition_All();
15781 void MapUndoRedoButtons(void)
15783 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15784 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15786 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15787 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15790 void UnmapUndoRedoButtons(void)
15792 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15793 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15795 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15796 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15799 void ModifyPauseButtons(void)
15803 GAME_CTRL_ID_PAUSE,
15804 GAME_CTRL_ID_PAUSE2,
15805 GAME_CTRL_ID_PANEL_PAUSE,
15806 GAME_CTRL_ID_TOUCH_PAUSE,
15811 for (i = 0; ids[i] > -1; i++)
15812 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15815 static void MapGameButtonsExt(boolean on_tape)
15819 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15820 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15821 i != GAME_CTRL_ID_UNDO &&
15822 i != GAME_CTRL_ID_REDO)
15823 MapGadget(game_gadget[i]);
15825 UnmapGameButtonsAtSamePosition_All();
15827 RedrawGameButtons();
15830 static void UnmapGameButtonsExt(boolean on_tape)
15834 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15835 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15836 UnmapGadget(game_gadget[i]);
15839 static void RedrawGameButtonsExt(boolean on_tape)
15843 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15844 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15845 RedrawGadget(game_gadget[i]);
15848 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15853 gi->checked = state;
15856 static void RedrawSoundButtonGadget(int id)
15858 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15859 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15860 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15861 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15862 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15863 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15866 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15867 RedrawGadget(game_gadget[id2]);
15870 void MapGameButtons(void)
15872 MapGameButtonsExt(FALSE);
15875 void UnmapGameButtons(void)
15877 UnmapGameButtonsExt(FALSE);
15880 void RedrawGameButtons(void)
15882 RedrawGameButtonsExt(FALSE);
15885 void MapGameButtonsOnTape(void)
15887 MapGameButtonsExt(TRUE);
15890 void UnmapGameButtonsOnTape(void)
15892 UnmapGameButtonsExt(TRUE);
15895 void RedrawGameButtonsOnTape(void)
15897 RedrawGameButtonsExt(TRUE);
15900 static void GameUndoRedoExt(void)
15902 ClearPlayerAction();
15904 tape.pausing = TRUE;
15907 UpdateAndDisplayGameControlValues();
15909 DrawCompleteVideoDisplay();
15910 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
15911 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
15912 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
15917 static void GameUndo(int steps)
15919 if (!CheckEngineSnapshotList())
15922 LoadEngineSnapshot_Undo(steps);
15927 static void GameRedo(int steps)
15929 if (!CheckEngineSnapshotList())
15932 LoadEngineSnapshot_Redo(steps);
15937 static void HandleGameButtonsExt(int id, int button)
15939 static boolean game_undo_executed = FALSE;
15940 int steps = BUTTON_STEPSIZE(button);
15941 boolean handle_game_buttons =
15942 (game_status == GAME_MODE_PLAYING ||
15943 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
15945 if (!handle_game_buttons)
15950 case GAME_CTRL_ID_STOP:
15951 case GAME_CTRL_ID_PANEL_STOP:
15952 case GAME_CTRL_ID_TOUCH_STOP:
15953 if (game_status == GAME_MODE_MAIN)
15959 RequestQuitGame(TRUE);
15963 case GAME_CTRL_ID_PAUSE:
15964 case GAME_CTRL_ID_PAUSE2:
15965 case GAME_CTRL_ID_PANEL_PAUSE:
15966 case GAME_CTRL_ID_TOUCH_PAUSE:
15967 if (network.enabled && game_status == GAME_MODE_PLAYING)
15970 SendToServer_ContinuePlaying();
15972 SendToServer_PausePlaying();
15975 TapeTogglePause(TAPE_TOGGLE_MANUAL);
15977 game_undo_executed = FALSE;
15981 case GAME_CTRL_ID_PLAY:
15982 case GAME_CTRL_ID_PANEL_PLAY:
15983 if (game_status == GAME_MODE_MAIN)
15985 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15987 else if (tape.pausing)
15989 if (network.enabled)
15990 SendToServer_ContinuePlaying();
15992 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
15996 case GAME_CTRL_ID_UNDO:
15997 // Important: When using "save snapshot when collecting an item" mode,
15998 // load last (current) snapshot for first "undo" after pressing "pause"
15999 // (else the last-but-one snapshot would be loaded, because the snapshot
16000 // pointer already points to the last snapshot when pressing "pause",
16001 // which is fine for "every step/move" mode, but not for "every collect")
16002 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16003 !game_undo_executed)
16006 game_undo_executed = TRUE;
16011 case GAME_CTRL_ID_REDO:
16015 case GAME_CTRL_ID_SAVE:
16019 case GAME_CTRL_ID_LOAD:
16023 case SOUND_CTRL_ID_MUSIC:
16024 case SOUND_CTRL_ID_PANEL_MUSIC:
16025 if (setup.sound_music)
16027 setup.sound_music = FALSE;
16031 else if (audio.music_available)
16033 setup.sound = setup.sound_music = TRUE;
16035 SetAudioMode(setup.sound);
16037 if (game_status == GAME_MODE_PLAYING)
16041 RedrawSoundButtonGadget(id);
16045 case SOUND_CTRL_ID_LOOPS:
16046 case SOUND_CTRL_ID_PANEL_LOOPS:
16047 if (setup.sound_loops)
16048 setup.sound_loops = FALSE;
16049 else if (audio.loops_available)
16051 setup.sound = setup.sound_loops = TRUE;
16053 SetAudioMode(setup.sound);
16056 RedrawSoundButtonGadget(id);
16060 case SOUND_CTRL_ID_SIMPLE:
16061 case SOUND_CTRL_ID_PANEL_SIMPLE:
16062 if (setup.sound_simple)
16063 setup.sound_simple = FALSE;
16064 else if (audio.sound_available)
16066 setup.sound = setup.sound_simple = TRUE;
16068 SetAudioMode(setup.sound);
16071 RedrawSoundButtonGadget(id);
16080 static void HandleGameButtons(struct GadgetInfo *gi)
16082 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16085 void HandleSoundButtonKeys(Key key)
16087 if (key == setup.shortcut.sound_simple)
16088 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16089 else if (key == setup.shortcut.sound_loops)
16090 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16091 else if (key == setup.shortcut.sound_music)
16092 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);