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);
2844 printf("level %d: level.game_version == %06d\n", level_nr,
2845 level.game_version);
2846 printf(" tape.file_version == %06d\n",
2848 printf(" tape.game_version == %06d\n",
2850 printf(" tape.engine_version == %06d\n",
2851 tape.engine_version);
2852 printf(" => game.engine_version == %06d [tape mode: %s]\n",
2853 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2856 // --------------------------------------------------------------------------
2857 // set flags for bugs and changes according to active game engine version
2858 // --------------------------------------------------------------------------
2862 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2864 Bug was introduced in version:
2867 Bug was fixed in version:
2871 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2872 but the property "can fall" was missing, which caused some levels to be
2873 unsolvable. This was fixed in version 4.1.4.2.
2875 Affected levels/tapes:
2876 An example for a tape that was fixed by this bugfix is tape 029 from the
2877 level set "rnd_sam_bateman".
2878 The wrong behaviour will still be used for all levels or tapes that were
2879 created/recorded with it. An example for this is tape 023 from the level
2880 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2883 boolean use_amoeba_dropping_cannot_fall_bug =
2884 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2885 game.engine_version <= VERSION_IDENT(4,1,4,1)) ||
2887 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2888 tape.game_version <= VERSION_IDENT(4,1,4,1)));
2891 Summary of bugfix/change:
2892 Fixed move speed of elements entering or leaving magic wall.
2894 Fixed/changed in version:
2898 Before 2.0.1, move speed of elements entering or leaving magic wall was
2899 twice as fast as it is now.
2900 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2902 Affected levels/tapes:
2903 The first condition is generally needed for all levels/tapes before version
2904 2.0.1, which might use the old behaviour before it was changed; known tapes
2905 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2906 The second condition is an exception from the above case and is needed for
2907 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2908 above, but before it was known that this change would break tapes like the
2909 above and was fixed in 4.1.4.2, so that the changed behaviour was active
2910 although the engine version while recording maybe was before 2.0.1. There
2911 are a lot of tapes that are affected by this exception, like tape 006 from
2912 the level set "rnd_conor_mancone".
2915 boolean use_old_move_stepsize_for_magic_wall =
2916 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2918 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2919 tape.game_version < VERSION_IDENT(4,1,4,2)));
2922 Summary of bugfix/change:
2923 Fixed handling for custom elements that change when pushed by the player.
2925 Fixed/changed in version:
2929 Before 3.1.0, custom elements that "change when pushing" changed directly
2930 after the player started pushing them (until then handled in "DigField()").
2931 Since 3.1.0, these custom elements are not changed until the "pushing"
2932 move of the element is finished (now handled in "ContinueMoving()").
2934 Affected levels/tapes:
2935 The first condition is generally needed for all levels/tapes before version
2936 3.1.0, which might use the old behaviour before it was changed; known tapes
2937 that are affected are some tapes from the level set "Walpurgis Gardens" by
2939 The second condition is an exception from the above case and is needed for
2940 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2941 above (including some development versions of 3.1.0), but before it was
2942 known that this change would break tapes like the above and was fixed in
2943 3.1.1, so that the changed behaviour was active although the engine version
2944 while recording maybe was before 3.1.0. There is at least one tape that is
2945 affected by this exception, which is the tape for the one-level set "Bug
2946 Machine" by Juergen Bonhagen.
2949 game.use_change_when_pushing_bug =
2950 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2952 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2953 tape.game_version < VERSION_IDENT(3,1,1,0)));
2956 Summary of bugfix/change:
2957 Fixed handling for blocking the field the player leaves when moving.
2959 Fixed/changed in version:
2963 Before 3.1.1, when "block last field when moving" was enabled, the field
2964 the player is leaving when moving was blocked for the time of the move,
2965 and was directly unblocked afterwards. This resulted in the last field
2966 being blocked for exactly one less than the number of frames of one player
2967 move. Additionally, even when blocking was disabled, the last field was
2968 blocked for exactly one frame.
2969 Since 3.1.1, due to changes in player movement handling, the last field
2970 is not blocked at all when blocking is disabled. When blocking is enabled,
2971 the last field is blocked for exactly the number of frames of one player
2972 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2973 last field is blocked for exactly one more than the number of frames of
2976 Affected levels/tapes:
2977 (!!! yet to be determined -- probably many !!!)
2980 game.use_block_last_field_bug =
2981 (game.engine_version < VERSION_IDENT(3,1,1,0));
2983 /* various special flags and settings for native Emerald Mine game engine */
2985 game_em.use_single_button =
2986 (game.engine_version > VERSION_IDENT(4,0,0,2));
2988 game_em.use_snap_key_bug =
2989 (game.engine_version < VERSION_IDENT(4,0,1,0));
2991 game_em.use_old_explosions =
2992 (game.engine_version < VERSION_IDENT(4,1,4,2));
2994 // --------------------------------------------------------------------------
2996 // set maximal allowed number of custom element changes per game frame
2997 game.max_num_changes_per_frame = 1;
2999 // default scan direction: scan playfield from top/left to bottom/right
3000 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3002 // dynamically adjust element properties according to game engine version
3003 InitElementPropertiesEngine(game.engine_version);
3005 // ---------- initialize special element properties -------------------------
3007 // "EL_AMOEBA_DROPPING" missed property "can fall" between 2.0.1 and 4.1.4.1
3008 if (use_amoeba_dropping_cannot_fall_bug)
3009 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3011 // ---------- initialize player's initial move delay ------------------------
3013 // dynamically adjust player properties according to level information
3014 for (i = 0; i < MAX_PLAYERS; i++)
3015 game.initial_move_delay_value[i] =
3016 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3018 // dynamically adjust player properties according to game engine version
3019 for (i = 0; i < MAX_PLAYERS; i++)
3020 game.initial_move_delay[i] =
3021 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3022 game.initial_move_delay_value[i] : 0);
3024 // ---------- initialize player's initial push delay ------------------------
3026 // dynamically adjust player properties according to game engine version
3027 game.initial_push_delay_value =
3028 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3030 // ---------- initialize changing elements ----------------------------------
3032 // initialize changing elements information
3033 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3035 struct ElementInfo *ei = &element_info[i];
3037 // this pointer might have been changed in the level editor
3038 ei->change = &ei->change_page[0];
3040 if (!IS_CUSTOM_ELEMENT(i))
3042 ei->change->target_element = EL_EMPTY_SPACE;
3043 ei->change->delay_fixed = 0;
3044 ei->change->delay_random = 0;
3045 ei->change->delay_frames = 1;
3048 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3050 ei->has_change_event[j] = FALSE;
3052 ei->event_page_nr[j] = 0;
3053 ei->event_page[j] = &ei->change_page[0];
3057 // add changing elements from pre-defined list
3058 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3060 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3061 struct ElementInfo *ei = &element_info[ch_delay->element];
3063 ei->change->target_element = ch_delay->target_element;
3064 ei->change->delay_fixed = ch_delay->change_delay;
3066 ei->change->pre_change_function = ch_delay->pre_change_function;
3067 ei->change->change_function = ch_delay->change_function;
3068 ei->change->post_change_function = ch_delay->post_change_function;
3070 ei->change->can_change = TRUE;
3071 ei->change->can_change_or_has_action = TRUE;
3073 ei->has_change_event[CE_DELAY] = TRUE;
3075 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3076 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3079 // ---------- initialize internal run-time variables ------------------------
3081 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3083 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3085 for (j = 0; j < ei->num_change_pages; j++)
3087 ei->change_page[j].can_change_or_has_action =
3088 (ei->change_page[j].can_change |
3089 ei->change_page[j].has_action);
3093 // add change events from custom element configuration
3094 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3096 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3098 for (j = 0; j < ei->num_change_pages; j++)
3100 if (!ei->change_page[j].can_change_or_has_action)
3103 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3105 // only add event page for the first page found with this event
3106 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3108 ei->has_change_event[k] = TRUE;
3110 ei->event_page_nr[k] = j;
3111 ei->event_page[k] = &ei->change_page[j];
3117 // ---------- initialize reference elements in change conditions ------------
3119 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3121 int element = EL_CUSTOM_START + i;
3122 struct ElementInfo *ei = &element_info[element];
3124 for (j = 0; j < ei->num_change_pages; j++)
3126 int trigger_element = ei->change_page[j].initial_trigger_element;
3128 if (trigger_element >= EL_PREV_CE_8 &&
3129 trigger_element <= EL_NEXT_CE_8)
3130 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3132 ei->change_page[j].trigger_element = trigger_element;
3136 // ---------- initialize run-time trigger player and element ----------------
3138 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3140 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3142 for (j = 0; j < ei->num_change_pages; j++)
3144 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3145 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3146 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3147 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3148 ei->change_page[j].actual_trigger_ce_value = 0;
3149 ei->change_page[j].actual_trigger_ce_score = 0;
3153 // ---------- initialize trigger events -------------------------------------
3155 // initialize trigger events information
3156 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3157 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3158 trigger_events[i][j] = FALSE;
3160 // add trigger events from element change event properties
3161 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3163 struct ElementInfo *ei = &element_info[i];
3165 for (j = 0; j < ei->num_change_pages; j++)
3167 if (!ei->change_page[j].can_change_or_has_action)
3170 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3172 int trigger_element = ei->change_page[j].trigger_element;
3174 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3176 if (ei->change_page[j].has_event[k])
3178 if (IS_GROUP_ELEMENT(trigger_element))
3180 struct ElementGroupInfo *group =
3181 element_info[trigger_element].group;
3183 for (l = 0; l < group->num_elements_resolved; l++)
3184 trigger_events[group->element_resolved[l]][k] = TRUE;
3186 else if (trigger_element == EL_ANY_ELEMENT)
3187 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3188 trigger_events[l][k] = TRUE;
3190 trigger_events[trigger_element][k] = TRUE;
3197 // ---------- initialize push delay -----------------------------------------
3199 // initialize push delay values to default
3200 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3202 if (!IS_CUSTOM_ELEMENT(i))
3204 // set default push delay values (corrected since version 3.0.7-1)
3205 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3207 element_info[i].push_delay_fixed = 2;
3208 element_info[i].push_delay_random = 8;
3212 element_info[i].push_delay_fixed = 8;
3213 element_info[i].push_delay_random = 8;
3218 // set push delay value for certain elements from pre-defined list
3219 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3221 int e = push_delay_list[i].element;
3223 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3224 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3227 // set push delay value for Supaplex elements for newer engine versions
3228 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3230 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3232 if (IS_SP_ELEMENT(i))
3234 // set SP push delay to just enough to push under a falling zonk
3235 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3237 element_info[i].push_delay_fixed = delay;
3238 element_info[i].push_delay_random = 0;
3243 // ---------- initialize move stepsize --------------------------------------
3245 // initialize move stepsize values to default
3246 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3247 if (!IS_CUSTOM_ELEMENT(i))
3248 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3250 // set move stepsize value for certain elements from pre-defined list
3251 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3253 int e = move_stepsize_list[i].element;
3255 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3257 // set move stepsize value for certain elements for older engine versions
3258 if (use_old_move_stepsize_for_magic_wall)
3260 if (e == EL_MAGIC_WALL_FILLING ||
3261 e == EL_MAGIC_WALL_EMPTYING ||
3262 e == EL_BD_MAGIC_WALL_FILLING ||
3263 e == EL_BD_MAGIC_WALL_EMPTYING)
3264 element_info[e].move_stepsize *= 2;
3268 // ---------- initialize collect score --------------------------------------
3270 // initialize collect score values for custom elements from initial value
3271 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3272 if (IS_CUSTOM_ELEMENT(i))
3273 element_info[i].collect_score = element_info[i].collect_score_initial;
3275 // ---------- initialize collect count --------------------------------------
3277 // initialize collect count values for non-custom elements
3278 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3279 if (!IS_CUSTOM_ELEMENT(i))
3280 element_info[i].collect_count_initial = 0;
3282 // add collect count values for all elements from pre-defined list
3283 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3284 element_info[collect_count_list[i].element].collect_count_initial =
3285 collect_count_list[i].count;
3287 // ---------- initialize access direction -----------------------------------
3289 // initialize access direction values to default (access from every side)
3290 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3291 if (!IS_CUSTOM_ELEMENT(i))
3292 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3294 // set access direction value for certain elements from pre-defined list
3295 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3296 element_info[access_direction_list[i].element].access_direction =
3297 access_direction_list[i].direction;
3299 // ---------- initialize explosion content ----------------------------------
3300 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3302 if (IS_CUSTOM_ELEMENT(i))
3305 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3307 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3309 element_info[i].content.e[x][y] =
3310 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3311 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3312 i == EL_PLAYER_3 ? EL_EMERALD :
3313 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3314 i == EL_MOLE ? EL_EMERALD_RED :
3315 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3316 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3317 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3318 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3319 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3320 i == EL_WALL_EMERALD ? EL_EMERALD :
3321 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3322 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3323 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3324 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3325 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3326 i == EL_WALL_PEARL ? EL_PEARL :
3327 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3332 // ---------- initialize recursion detection --------------------------------
3333 recursion_loop_depth = 0;
3334 recursion_loop_detected = FALSE;
3335 recursion_loop_element = EL_UNDEFINED;
3337 // ---------- initialize graphics engine ------------------------------------
3338 game.scroll_delay_value =
3339 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3340 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3341 !setup.forced_scroll_delay ? 0 :
3342 setup.scroll_delay ? setup.scroll_delay_value : 0);
3343 game.scroll_delay_value =
3344 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3346 // ---------- initialize game engine snapshots ------------------------------
3347 for (i = 0; i < MAX_PLAYERS; i++)
3348 game.snapshot.last_action[i] = 0;
3349 game.snapshot.changed_action = FALSE;
3350 game.snapshot.collected_item = FALSE;
3351 game.snapshot.mode =
3352 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3353 SNAPSHOT_MODE_EVERY_STEP :
3354 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3355 SNAPSHOT_MODE_EVERY_MOVE :
3356 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3357 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3358 game.snapshot.save_snapshot = FALSE;
3360 // ---------- initialize level time for Supaplex engine ---------------------
3361 // Supaplex levels with time limit currently unsupported -- should be added
3362 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3365 // ---------- initialize flags for handling game actions --------------------
3367 // set flags for game actions to default values
3368 game.use_key_actions = TRUE;
3369 game.use_mouse_actions = FALSE;
3371 // when using Mirror Magic game engine, handle mouse events only
3372 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3374 game.use_key_actions = FALSE;
3375 game.use_mouse_actions = TRUE;
3378 // check for custom elements with mouse click events
3379 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3381 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3383 int element = EL_CUSTOM_START + i;
3385 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3386 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3387 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3388 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3389 game.use_mouse_actions = TRUE;
3394 static int get_num_special_action(int element, int action_first,
3397 int num_special_action = 0;
3400 for (i = action_first; i <= action_last; i++)
3402 boolean found = FALSE;
3404 for (j = 0; j < NUM_DIRECTIONS; j++)
3405 if (el_act_dir2img(element, i, j) !=
3406 el_act_dir2img(element, ACTION_DEFAULT, j))
3410 num_special_action++;
3415 return num_special_action;
3419 // ============================================================================
3421 // ----------------------------------------------------------------------------
3422 // initialize and start new game
3423 // ============================================================================
3425 #if DEBUG_INIT_PLAYER
3426 static void DebugPrintPlayerStatus(char *message)
3433 printf("%s:\n", message);
3435 for (i = 0; i < MAX_PLAYERS; i++)
3437 struct PlayerInfo *player = &stored_player[i];
3439 printf("- player %d: present == %d, connected == %d [%d/%d], active == %d",
3443 player->connected_locally,
3444 player->connected_network,
3447 if (local_player == player)
3448 printf(" (local player)");
3457 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3458 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3459 int fade_mask = REDRAW_FIELD;
3461 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3462 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3463 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3464 int initial_move_dir = MV_DOWN;
3467 // required here to update video display before fading (FIX THIS)
3468 DrawMaskedBorder(REDRAW_DOOR_2);
3470 if (!game.restart_level)
3471 CloseDoor(DOOR_CLOSE_1);
3473 SetGameStatus(GAME_MODE_PLAYING);
3475 if (level_editor_test_game)
3476 FadeSkipNextFadeOut();
3478 FadeSetEnterScreen();
3481 fade_mask = REDRAW_ALL;
3483 FadeLevelSoundsAndMusic();
3485 ExpireSoundLoops(TRUE);
3489 if (level_editor_test_game)
3490 FadeSkipNextFadeIn();
3492 // needed if different viewport properties defined for playing
3493 ChangeViewportPropertiesIfNeeded();
3497 DrawCompleteVideoDisplay();
3499 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3502 InitGameControlValues();
3504 // initialize tape actions from game when recording tape
3507 tape.use_key_actions = game.use_key_actions;
3508 tape.use_mouse_actions = game.use_mouse_actions;
3511 // don't play tapes over network
3512 network_playing = (network.enabled && !tape.playing);
3514 for (i = 0; i < MAX_PLAYERS; i++)
3516 struct PlayerInfo *player = &stored_player[i];
3518 player->index_nr = i;
3519 player->index_bit = (1 << i);
3520 player->element_nr = EL_PLAYER_1 + i;
3522 player->present = FALSE;
3523 player->active = FALSE;
3524 player->mapped = FALSE;
3526 player->killed = FALSE;
3527 player->reanimated = FALSE;
3528 player->buried = FALSE;
3531 player->effective_action = 0;
3532 player->programmed_action = 0;
3533 player->snap_action = 0;
3535 player->mouse_action.lx = 0;
3536 player->mouse_action.ly = 0;
3537 player->mouse_action.button = 0;
3538 player->mouse_action.button_hint = 0;
3540 player->effective_mouse_action.lx = 0;
3541 player->effective_mouse_action.ly = 0;
3542 player->effective_mouse_action.button = 0;
3543 player->effective_mouse_action.button_hint = 0;
3545 for (j = 0; j < MAX_NUM_KEYS; j++)
3546 player->key[j] = FALSE;
3548 player->num_white_keys = 0;
3550 player->dynabomb_count = 0;
3551 player->dynabomb_size = 1;
3552 player->dynabombs_left = 0;
3553 player->dynabomb_xl = FALSE;
3555 player->MovDir = initial_move_dir;
3558 player->GfxDir = initial_move_dir;
3559 player->GfxAction = ACTION_DEFAULT;
3561 player->StepFrame = 0;
3563 player->initial_element = player->element_nr;
3564 player->artwork_element =
3565 (level.use_artwork_element[i] ? level.artwork_element[i] :
3566 player->element_nr);
3567 player->use_murphy = FALSE;
3569 player->block_last_field = FALSE; // initialized in InitPlayerField()
3570 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3572 player->gravity = level.initial_player_gravity[i];
3574 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3576 player->actual_frame_counter = 0;
3578 player->step_counter = 0;
3580 player->last_move_dir = initial_move_dir;
3582 player->is_active = FALSE;
3584 player->is_waiting = FALSE;
3585 player->is_moving = FALSE;
3586 player->is_auto_moving = FALSE;
3587 player->is_digging = FALSE;
3588 player->is_snapping = FALSE;
3589 player->is_collecting = FALSE;
3590 player->is_pushing = FALSE;
3591 player->is_switching = FALSE;
3592 player->is_dropping = FALSE;
3593 player->is_dropping_pressed = FALSE;
3595 player->is_bored = FALSE;
3596 player->is_sleeping = FALSE;
3598 player->was_waiting = TRUE;
3599 player->was_moving = FALSE;
3600 player->was_snapping = FALSE;
3601 player->was_dropping = FALSE;
3603 player->force_dropping = FALSE;
3605 player->frame_counter_bored = -1;
3606 player->frame_counter_sleeping = -1;
3608 player->anim_delay_counter = 0;
3609 player->post_delay_counter = 0;
3611 player->dir_waiting = initial_move_dir;
3612 player->action_waiting = ACTION_DEFAULT;
3613 player->last_action_waiting = ACTION_DEFAULT;
3614 player->special_action_bored = ACTION_DEFAULT;
3615 player->special_action_sleeping = ACTION_DEFAULT;
3617 player->switch_x = -1;
3618 player->switch_y = -1;
3620 player->drop_x = -1;
3621 player->drop_y = -1;
3623 player->show_envelope = 0;
3625 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3627 player->push_delay = -1; // initialized when pushing starts
3628 player->push_delay_value = game.initial_push_delay_value;
3630 player->drop_delay = 0;
3631 player->drop_pressed_delay = 0;
3633 player->last_jx = -1;
3634 player->last_jy = -1;
3638 player->shield_normal_time_left = 0;
3639 player->shield_deadly_time_left = 0;
3641 player->inventory_infinite_element = EL_UNDEFINED;
3642 player->inventory_size = 0;
3644 if (level.use_initial_inventory[i])
3646 for (j = 0; j < level.initial_inventory_size[i]; j++)
3648 int element = level.initial_inventory_content[i][j];
3649 int collect_count = element_info[element].collect_count_initial;
3652 if (!IS_CUSTOM_ELEMENT(element))
3655 if (collect_count == 0)
3656 player->inventory_infinite_element = element;
3658 for (k = 0; k < collect_count; k++)
3659 if (player->inventory_size < MAX_INVENTORY_SIZE)
3660 player->inventory_element[player->inventory_size++] = element;
3664 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3665 SnapField(player, 0, 0);
3667 map_player_action[i] = i;
3670 network_player_action_received = FALSE;
3672 // initial null action
3673 if (network_playing)
3674 SendToServer_MovePlayer(MV_NONE);
3679 TimeLeft = level.time;
3682 ScreenMovDir = MV_NONE;
3686 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3688 game.robot_wheel_x = -1;
3689 game.robot_wheel_y = -1;
3694 game.all_players_gone = FALSE;
3696 game.LevelSolved = FALSE;
3697 game.GameOver = FALSE;
3699 game.GamePlayed = !tape.playing;
3701 game.LevelSolved_GameWon = FALSE;
3702 game.LevelSolved_GameEnd = FALSE;
3703 game.LevelSolved_SaveTape = FALSE;
3704 game.LevelSolved_SaveScore = FALSE;
3706 game.LevelSolved_CountingTime = 0;
3707 game.LevelSolved_CountingScore = 0;
3708 game.LevelSolved_CountingHealth = 0;
3710 game.panel.active = TRUE;
3712 game.no_time_limit = (level.time == 0);
3714 game.yamyam_content_nr = 0;
3715 game.robot_wheel_active = FALSE;
3716 game.magic_wall_active = FALSE;
3717 game.magic_wall_time_left = 0;
3718 game.light_time_left = 0;
3719 game.timegate_time_left = 0;
3720 game.switchgate_pos = 0;
3721 game.wind_direction = level.wind_direction_initial;
3724 game.score_final = 0;
3726 game.health = MAX_HEALTH;
3727 game.health_final = MAX_HEALTH;
3729 game.gems_still_needed = level.gems_needed;
3730 game.sokoban_fields_still_needed = 0;
3731 game.sokoban_objects_still_needed = 0;
3732 game.lights_still_needed = 0;
3733 game.players_still_needed = 0;
3734 game.friends_still_needed = 0;
3736 game.lenses_time_left = 0;
3737 game.magnify_time_left = 0;
3739 game.ball_active = level.ball_active_initial;
3740 game.ball_content_nr = 0;
3742 game.explosions_delayed = TRUE;
3744 game.envelope_active = FALSE;
3746 for (i = 0; i < NUM_BELTS; i++)
3748 game.belt_dir[i] = MV_NONE;
3749 game.belt_dir_nr[i] = 3; // not moving, next moving left
3752 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3753 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3755 #if DEBUG_INIT_PLAYER
3756 DebugPrintPlayerStatus("Player status at level initialization");
3759 SCAN_PLAYFIELD(x, y)
3761 Feld[x][y] = Last[x][y] = level.field[x][y];
3762 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3763 ChangeDelay[x][y] = 0;
3764 ChangePage[x][y] = -1;
3765 CustomValue[x][y] = 0; // initialized in InitField()
3766 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3768 WasJustMoving[x][y] = 0;
3769 WasJustFalling[x][y] = 0;
3770 CheckCollision[x][y] = 0;
3771 CheckImpact[x][y] = 0;
3773 Pushed[x][y] = FALSE;
3775 ChangeCount[x][y] = 0;
3776 ChangeEvent[x][y] = -1;
3778 ExplodePhase[x][y] = 0;
3779 ExplodeDelay[x][y] = 0;
3780 ExplodeField[x][y] = EX_TYPE_NONE;
3782 RunnerVisit[x][y] = 0;
3783 PlayerVisit[x][y] = 0;
3786 GfxRandom[x][y] = INIT_GFX_RANDOM();
3787 GfxElement[x][y] = EL_UNDEFINED;
3788 GfxAction[x][y] = ACTION_DEFAULT;
3789 GfxDir[x][y] = MV_NONE;
3790 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3793 SCAN_PLAYFIELD(x, y)
3795 if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y]))
3797 if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y]))
3799 if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y]))
3802 InitField(x, y, TRUE);
3804 ResetGfxAnimation(x, y);
3809 for (i = 0; i < MAX_PLAYERS; i++)
3811 struct PlayerInfo *player = &stored_player[i];
3813 // set number of special actions for bored and sleeping animation
3814 player->num_special_action_bored =
3815 get_num_special_action(player->artwork_element,
3816 ACTION_BORING_1, ACTION_BORING_LAST);
3817 player->num_special_action_sleeping =
3818 get_num_special_action(player->artwork_element,
3819 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3822 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3823 emulate_sb ? EMU_SOKOBAN :
3824 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3826 // initialize type of slippery elements
3827 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3829 if (!IS_CUSTOM_ELEMENT(i))
3831 // default: elements slip down either to the left or right randomly
3832 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3834 // SP style elements prefer to slip down on the left side
3835 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3836 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3838 // BD style elements prefer to slip down on the left side
3839 if (game.emulation == EMU_BOULDERDASH)
3840 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3844 // initialize explosion and ignition delay
3845 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3847 if (!IS_CUSTOM_ELEMENT(i))
3850 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3851 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3852 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3853 int last_phase = (num_phase + 1) * delay;
3854 int half_phase = (num_phase / 2) * delay;
3856 element_info[i].explosion_delay = last_phase - 1;
3857 element_info[i].ignition_delay = half_phase;
3859 if (i == EL_BLACK_ORB)
3860 element_info[i].ignition_delay = 1;
3864 // correct non-moving belts to start moving left
3865 for (i = 0; i < NUM_BELTS; i++)
3866 if (game.belt_dir[i] == MV_NONE)
3867 game.belt_dir_nr[i] = 3; // not moving, next moving left
3869 #if USE_NEW_PLAYER_ASSIGNMENTS
3870 // use preferred player also in local single-player mode
3871 if (!network.enabled && !game.team_mode)
3873 int new_index_nr = setup.network_player_nr;
3875 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3877 for (i = 0; i < MAX_PLAYERS; i++)
3878 stored_player[i].connected_locally = FALSE;
3880 stored_player[new_index_nr].connected_locally = TRUE;
3884 for (i = 0; i < MAX_PLAYERS; i++)
3886 stored_player[i].connected = FALSE;
3888 // in network game mode, the local player might not be the first player
3889 if (stored_player[i].connected_locally)
3890 local_player = &stored_player[i];
3893 if (!network.enabled)
3894 local_player->connected = TRUE;
3898 for (i = 0; i < MAX_PLAYERS; i++)
3899 stored_player[i].connected = tape.player_participates[i];
3901 else if (network.enabled)
3903 // add team mode players connected over the network (needed for correct
3904 // assignment of player figures from level to locally playing players)
3906 for (i = 0; i < MAX_PLAYERS; i++)
3907 if (stored_player[i].connected_network)
3908 stored_player[i].connected = TRUE;
3910 else if (game.team_mode)
3912 // try to guess locally connected team mode players (needed for correct
3913 // assignment of player figures from level to locally playing players)
3915 for (i = 0; i < MAX_PLAYERS; i++)
3916 if (setup.input[i].use_joystick ||
3917 setup.input[i].key.left != KSYM_UNDEFINED)
3918 stored_player[i].connected = TRUE;
3921 #if DEBUG_INIT_PLAYER
3922 DebugPrintPlayerStatus("Player status after level initialization");
3925 #if DEBUG_INIT_PLAYER
3927 printf("Reassigning players ...\n");
3930 // check if any connected player was not found in playfield
3931 for (i = 0; i < MAX_PLAYERS; i++)
3933 struct PlayerInfo *player = &stored_player[i];
3935 if (player->connected && !player->present)
3937 struct PlayerInfo *field_player = NULL;
3939 #if DEBUG_INIT_PLAYER
3941 printf("- looking for field player for player %d ...\n", i + 1);
3944 // assign first free player found that is present in the playfield
3946 // first try: look for unmapped playfield player that is not connected
3947 for (j = 0; j < MAX_PLAYERS; j++)
3948 if (field_player == NULL &&
3949 stored_player[j].present &&
3950 !stored_player[j].mapped &&
3951 !stored_player[j].connected)
3952 field_player = &stored_player[j];
3954 // second try: look for *any* unmapped playfield player
3955 for (j = 0; j < MAX_PLAYERS; j++)
3956 if (field_player == NULL &&
3957 stored_player[j].present &&
3958 !stored_player[j].mapped)
3959 field_player = &stored_player[j];
3961 if (field_player != NULL)
3963 int jx = field_player->jx, jy = field_player->jy;
3965 #if DEBUG_INIT_PLAYER
3967 printf("- found player %d\n", field_player->index_nr + 1);
3970 player->present = FALSE;
3971 player->active = FALSE;
3973 field_player->present = TRUE;
3974 field_player->active = TRUE;
3977 player->initial_element = field_player->initial_element;
3978 player->artwork_element = field_player->artwork_element;
3980 player->block_last_field = field_player->block_last_field;
3981 player->block_delay_adjustment = field_player->block_delay_adjustment;
3984 StorePlayer[jx][jy] = field_player->element_nr;
3986 field_player->jx = field_player->last_jx = jx;
3987 field_player->jy = field_player->last_jy = jy;
3989 if (local_player == player)
3990 local_player = field_player;
3992 map_player_action[field_player->index_nr] = i;
3994 field_player->mapped = TRUE;
3996 #if DEBUG_INIT_PLAYER
3998 printf("- map_player_action[%d] == %d\n",
3999 field_player->index_nr + 1, i + 1);
4004 if (player->connected && player->present)
4005 player->mapped = TRUE;
4008 #if DEBUG_INIT_PLAYER
4009 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4014 // check if any connected player was not found in playfield
4015 for (i = 0; i < MAX_PLAYERS; i++)
4017 struct PlayerInfo *player = &stored_player[i];
4019 if (player->connected && !player->present)
4021 for (j = 0; j < MAX_PLAYERS; j++)
4023 struct PlayerInfo *field_player = &stored_player[j];
4024 int jx = field_player->jx, jy = field_player->jy;
4026 // assign first free player found that is present in the playfield
4027 if (field_player->present && !field_player->connected)
4029 player->present = TRUE;
4030 player->active = TRUE;
4032 field_player->present = FALSE;
4033 field_player->active = FALSE;
4035 player->initial_element = field_player->initial_element;
4036 player->artwork_element = field_player->artwork_element;
4038 player->block_last_field = field_player->block_last_field;
4039 player->block_delay_adjustment = field_player->block_delay_adjustment;
4041 StorePlayer[jx][jy] = player->element_nr;
4043 player->jx = player->last_jx = jx;
4044 player->jy = player->last_jy = jy;
4054 printf("::: local_player->present == %d\n", local_player->present);
4057 // set focus to local player for network games, else to all players
4058 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4059 game.centered_player_nr_next = game.centered_player_nr;
4060 game.set_centered_player = FALSE;
4061 game.set_centered_player_wrap = FALSE;
4063 if (network_playing && tape.recording)
4065 // store client dependent player focus when recording network games
4066 tape.centered_player_nr_next = game.centered_player_nr_next;
4067 tape.set_centered_player = TRUE;
4072 // when playing a tape, eliminate all players who do not participate
4074 #if USE_NEW_PLAYER_ASSIGNMENTS
4076 if (!game.team_mode)
4078 for (i = 0; i < MAX_PLAYERS; i++)
4080 if (stored_player[i].active &&
4081 !tape.player_participates[map_player_action[i]])
4083 struct PlayerInfo *player = &stored_player[i];
4084 int jx = player->jx, jy = player->jy;
4086 #if DEBUG_INIT_PLAYER
4088 printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
4091 player->active = FALSE;
4092 StorePlayer[jx][jy] = 0;
4093 Feld[jx][jy] = EL_EMPTY;
4100 for (i = 0; i < MAX_PLAYERS; i++)
4102 if (stored_player[i].active &&
4103 !tape.player_participates[i])
4105 struct PlayerInfo *player = &stored_player[i];
4106 int jx = player->jx, jy = player->jy;
4108 player->active = FALSE;
4109 StorePlayer[jx][jy] = 0;
4110 Feld[jx][jy] = EL_EMPTY;
4115 else if (!network.enabled && !game.team_mode) // && !tape.playing
4117 // when in single player mode, eliminate all but the local player
4119 for (i = 0; i < MAX_PLAYERS; i++)
4121 struct PlayerInfo *player = &stored_player[i];
4123 if (player->active && player != local_player)
4125 int jx = player->jx, jy = player->jy;
4127 player->active = FALSE;
4128 player->present = FALSE;
4130 StorePlayer[jx][jy] = 0;
4131 Feld[jx][jy] = EL_EMPTY;
4136 for (i = 0; i < MAX_PLAYERS; i++)
4137 if (stored_player[i].active)
4138 game.players_still_needed++;
4140 if (level.solved_by_one_player)
4141 game.players_still_needed = 1;
4143 // when recording the game, store which players take part in the game
4146 #if USE_NEW_PLAYER_ASSIGNMENTS
4147 for (i = 0; i < MAX_PLAYERS; i++)
4148 if (stored_player[i].connected)
4149 tape.player_participates[i] = TRUE;
4151 for (i = 0; i < MAX_PLAYERS; i++)
4152 if (stored_player[i].active)
4153 tape.player_participates[i] = TRUE;
4157 #if DEBUG_INIT_PLAYER
4158 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4161 if (BorderElement == EL_EMPTY)
4164 SBX_Right = lev_fieldx - SCR_FIELDX;
4166 SBY_Lower = lev_fieldy - SCR_FIELDY;
4171 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4173 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4176 if (full_lev_fieldx <= SCR_FIELDX)
4177 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4178 if (full_lev_fieldy <= SCR_FIELDY)
4179 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4181 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4183 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4186 // if local player not found, look for custom element that might create
4187 // the player (make some assumptions about the right custom element)
4188 if (!local_player->present)
4190 int start_x = 0, start_y = 0;
4191 int found_rating = 0;
4192 int found_element = EL_UNDEFINED;
4193 int player_nr = local_player->index_nr;
4195 SCAN_PLAYFIELD(x, y)
4197 int element = Feld[x][y];
4202 if (level.use_start_element[player_nr] &&
4203 level.start_element[player_nr] == element &&
4210 found_element = element;
4213 if (!IS_CUSTOM_ELEMENT(element))
4216 if (CAN_CHANGE(element))
4218 for (i = 0; i < element_info[element].num_change_pages; i++)
4220 // check for player created from custom element as single target
4221 content = element_info[element].change_page[i].target_element;
4222 is_player = ELEM_IS_PLAYER(content);
4224 if (is_player && (found_rating < 3 ||
4225 (found_rating == 3 && element < found_element)))
4231 found_element = element;
4236 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4238 // check for player created from custom element as explosion content
4239 content = element_info[element].content.e[xx][yy];
4240 is_player = ELEM_IS_PLAYER(content);
4242 if (is_player && (found_rating < 2 ||
4243 (found_rating == 2 && element < found_element)))
4245 start_x = x + xx - 1;
4246 start_y = y + yy - 1;
4249 found_element = element;
4252 if (!CAN_CHANGE(element))
4255 for (i = 0; i < element_info[element].num_change_pages; i++)
4257 // check for player created from custom element as extended target
4259 element_info[element].change_page[i].target_content.e[xx][yy];
4261 is_player = ELEM_IS_PLAYER(content);
4263 if (is_player && (found_rating < 1 ||
4264 (found_rating == 1 && element < found_element)))
4266 start_x = x + xx - 1;
4267 start_y = y + yy - 1;
4270 found_element = element;
4276 scroll_x = SCROLL_POSITION_X(start_x);
4277 scroll_y = SCROLL_POSITION_Y(start_y);
4281 scroll_x = SCROLL_POSITION_X(local_player->jx);
4282 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4285 // !!! FIX THIS (START) !!!
4286 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4288 InitGameEngine_EM();
4290 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4292 InitGameEngine_SP();
4294 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4296 InitGameEngine_MM();
4300 DrawLevel(REDRAW_FIELD);
4303 // after drawing the level, correct some elements
4304 if (game.timegate_time_left == 0)
4305 CloseAllOpenTimegates();
4308 // blit playfield from scroll buffer to normal back buffer for fading in
4309 BlitScreenToBitmap(backbuffer);
4310 // !!! FIX THIS (END) !!!
4312 DrawMaskedBorder(fade_mask);
4317 // full screen redraw is required at this point in the following cases:
4318 // - special editor door undrawn when game was started from level editor
4319 // - drawing area (playfield) was changed and has to be removed completely
4320 redraw_mask = REDRAW_ALL;
4324 if (!game.restart_level)
4326 // copy default game door content to main double buffer
4328 // !!! CHECK AGAIN !!!
4329 SetPanelBackground();
4330 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4331 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4334 SetPanelBackground();
4335 SetDrawBackgroundMask(REDRAW_DOOR_1);
4337 UpdateAndDisplayGameControlValues();
4339 if (!game.restart_level)
4345 CreateGameButtons();
4350 // copy actual game door content to door double buffer for OpenDoor()
4351 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4353 OpenDoor(DOOR_OPEN_ALL);
4355 KeyboardAutoRepeatOffUnlessAutoplay();
4357 #if DEBUG_INIT_PLAYER
4358 DebugPrintPlayerStatus("Player status (final)");
4367 if (!game.restart_level && !tape.playing)
4369 LevelStats_incPlayed(level_nr);
4371 SaveLevelSetup_SeriesInfo();
4374 game.restart_level = FALSE;
4375 game.restart_game_message = NULL;
4376 game.request_active = FALSE;
4378 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4379 InitGameActions_MM();
4381 SaveEngineSnapshotToListInitial();
4383 if (!game.restart_level)
4385 PlaySound(SND_GAME_STARTING);
4387 if (setup.sound_music)
4392 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4393 int actual_player_x, int actual_player_y)
4395 // this is used for non-R'n'D game engines to update certain engine values
4397 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4399 actual_scroll_x = correctLevelPosX_EM(actual_scroll_x);
4400 actual_scroll_y = correctLevelPosY_EM(actual_scroll_y);
4402 actual_player_x = correctLevelPosX_EM(actual_player_x);
4403 actual_player_y = correctLevelPosY_EM(actual_player_y);
4406 // needed to determine if sounds are played within the visible screen area
4407 scroll_x = actual_scroll_x;
4408 scroll_y = actual_scroll_y;
4410 // needed to get player position for "follow finger" playing input method
4411 local_player->jx = actual_player_x;
4412 local_player->jy = actual_player_y;
4415 void InitMovDir(int x, int y)
4417 int i, element = Feld[x][y];
4418 static int xy[4][2] =
4425 static int direction[3][4] =
4427 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4428 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4429 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4438 Feld[x][y] = EL_BUG;
4439 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4442 case EL_SPACESHIP_RIGHT:
4443 case EL_SPACESHIP_UP:
4444 case EL_SPACESHIP_LEFT:
4445 case EL_SPACESHIP_DOWN:
4446 Feld[x][y] = EL_SPACESHIP;
4447 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4450 case EL_BD_BUTTERFLY_RIGHT:
4451 case EL_BD_BUTTERFLY_UP:
4452 case EL_BD_BUTTERFLY_LEFT:
4453 case EL_BD_BUTTERFLY_DOWN:
4454 Feld[x][y] = EL_BD_BUTTERFLY;
4455 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4458 case EL_BD_FIREFLY_RIGHT:
4459 case EL_BD_FIREFLY_UP:
4460 case EL_BD_FIREFLY_LEFT:
4461 case EL_BD_FIREFLY_DOWN:
4462 Feld[x][y] = EL_BD_FIREFLY;
4463 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4466 case EL_PACMAN_RIGHT:
4468 case EL_PACMAN_LEFT:
4469 case EL_PACMAN_DOWN:
4470 Feld[x][y] = EL_PACMAN;
4471 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4474 case EL_YAMYAM_LEFT:
4475 case EL_YAMYAM_RIGHT:
4477 case EL_YAMYAM_DOWN:
4478 Feld[x][y] = EL_YAMYAM;
4479 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4482 case EL_SP_SNIKSNAK:
4483 MovDir[x][y] = MV_UP;
4486 case EL_SP_ELECTRON:
4487 MovDir[x][y] = MV_LEFT;
4494 Feld[x][y] = EL_MOLE;
4495 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4499 if (IS_CUSTOM_ELEMENT(element))
4501 struct ElementInfo *ei = &element_info[element];
4502 int move_direction_initial = ei->move_direction_initial;
4503 int move_pattern = ei->move_pattern;
4505 if (move_direction_initial == MV_START_PREVIOUS)
4507 if (MovDir[x][y] != MV_NONE)
4510 move_direction_initial = MV_START_AUTOMATIC;
4513 if (move_direction_initial == MV_START_RANDOM)
4514 MovDir[x][y] = 1 << RND(4);
4515 else if (move_direction_initial & MV_ANY_DIRECTION)
4516 MovDir[x][y] = move_direction_initial;
4517 else if (move_pattern == MV_ALL_DIRECTIONS ||
4518 move_pattern == MV_TURNING_LEFT ||
4519 move_pattern == MV_TURNING_RIGHT ||
4520 move_pattern == MV_TURNING_LEFT_RIGHT ||
4521 move_pattern == MV_TURNING_RIGHT_LEFT ||
4522 move_pattern == MV_TURNING_RANDOM)
4523 MovDir[x][y] = 1 << RND(4);
4524 else if (move_pattern == MV_HORIZONTAL)
4525 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4526 else if (move_pattern == MV_VERTICAL)
4527 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4528 else if (move_pattern & MV_ANY_DIRECTION)
4529 MovDir[x][y] = element_info[element].move_pattern;
4530 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4531 move_pattern == MV_ALONG_RIGHT_SIDE)
4533 // use random direction as default start direction
4534 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4535 MovDir[x][y] = 1 << RND(4);
4537 for (i = 0; i < NUM_DIRECTIONS; i++)
4539 int x1 = x + xy[i][0];
4540 int y1 = y + xy[i][1];
4542 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4544 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4545 MovDir[x][y] = direction[0][i];
4547 MovDir[x][y] = direction[1][i];
4556 MovDir[x][y] = 1 << RND(4);
4558 if (element != EL_BUG &&
4559 element != EL_SPACESHIP &&
4560 element != EL_BD_BUTTERFLY &&
4561 element != EL_BD_FIREFLY)
4564 for (i = 0; i < NUM_DIRECTIONS; i++)
4566 int x1 = x + xy[i][0];
4567 int y1 = y + xy[i][1];
4569 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4571 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4573 MovDir[x][y] = direction[0][i];
4576 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4577 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4579 MovDir[x][y] = direction[1][i];
4588 GfxDir[x][y] = MovDir[x][y];
4591 void InitAmoebaNr(int x, int y)
4594 int group_nr = AmoebeNachbarNr(x, y);
4598 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4600 if (AmoebaCnt[i] == 0)
4608 AmoebaNr[x][y] = group_nr;
4609 AmoebaCnt[group_nr]++;
4610 AmoebaCnt2[group_nr]++;
4613 static void LevelSolved(void)
4615 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4616 game.players_still_needed > 0)
4619 game.LevelSolved = TRUE;
4620 game.GameOver = TRUE;
4622 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4623 game_em.lev->score :
4624 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4627 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4628 MM_HEALTH(game_mm.laser_overload_value) :
4631 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4632 game.LevelSolved_CountingScore = game.score_final;
4633 game.LevelSolved_CountingHealth = game.health_final;
4638 static int time_count_steps;
4639 static int time, time_final;
4640 static int score, score_final;
4641 static int health, health_final;
4642 static int game_over_delay_1 = 0;
4643 static int game_over_delay_2 = 0;
4644 static int game_over_delay_3 = 0;
4645 int game_over_delay_value_1 = 50;
4646 int game_over_delay_value_2 = 25;
4647 int game_over_delay_value_3 = 50;
4649 if (!game.LevelSolved_GameWon)
4653 // do not start end game actions before the player stops moving (to exit)
4654 if (local_player->active && local_player->MovPos)
4657 game.LevelSolved_GameWon = TRUE;
4658 game.LevelSolved_SaveTape = tape.recording;
4659 game.LevelSolved_SaveScore = !tape.playing;
4663 LevelStats_incSolved(level_nr);
4665 SaveLevelSetup_SeriesInfo();
4668 if (tape.auto_play) // tape might already be stopped here
4669 tape.auto_play_level_solved = TRUE;
4673 game_over_delay_1 = 0;
4674 game_over_delay_2 = 0;
4675 game_over_delay_3 = game_over_delay_value_3;
4677 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4678 score = score_final = game.score_final;
4679 health = health_final = game.health_final;
4681 if (level.score[SC_TIME_BONUS] > 0)
4686 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4688 else if (game.no_time_limit && TimePlayed < 999)
4691 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4694 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4696 game_over_delay_1 = game_over_delay_value_1;
4698 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4701 score_final += health * level.score[SC_TIME_BONUS];
4703 game_over_delay_2 = game_over_delay_value_2;
4706 game.score_final = score_final;
4707 game.health_final = health_final;
4710 if (level_editor_test_game)
4713 score = score_final;
4715 game.LevelSolved_CountingTime = time;
4716 game.LevelSolved_CountingScore = score;
4718 game_panel_controls[GAME_PANEL_TIME].value = time;
4719 game_panel_controls[GAME_PANEL_SCORE].value = score;
4721 DisplayGameControlValues();
4724 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4726 // check if last player has left the level
4727 if (game.exit_x >= 0 &&
4730 int x = game.exit_x;
4731 int y = game.exit_y;
4732 int element = Feld[x][y];
4734 // close exit door after last player
4735 if ((game.all_players_gone &&
4736 (element == EL_EXIT_OPEN ||
4737 element == EL_SP_EXIT_OPEN ||
4738 element == EL_STEEL_EXIT_OPEN)) ||
4739 element == EL_EM_EXIT_OPEN ||
4740 element == EL_EM_STEEL_EXIT_OPEN)
4744 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4745 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4746 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4747 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4748 EL_EM_STEEL_EXIT_CLOSING);
4750 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4753 // player disappears
4754 DrawLevelField(x, y);
4757 for (i = 0; i < MAX_PLAYERS; i++)
4759 struct PlayerInfo *player = &stored_player[i];
4761 if (player->present)
4763 RemovePlayer(player);
4765 // player disappears
4766 DrawLevelField(player->jx, player->jy);
4771 PlaySound(SND_GAME_WINNING);
4774 if (game_over_delay_1 > 0)
4776 game_over_delay_1--;
4781 if (time != time_final)
4783 int time_to_go = ABS(time_final - time);
4784 int time_count_dir = (time < time_final ? +1 : -1);
4786 if (time_to_go < time_count_steps)
4787 time_count_steps = 1;
4789 time += time_count_steps * time_count_dir;
4790 score += time_count_steps * level.score[SC_TIME_BONUS];
4792 game.LevelSolved_CountingTime = time;
4793 game.LevelSolved_CountingScore = score;
4795 game_panel_controls[GAME_PANEL_TIME].value = time;
4796 game_panel_controls[GAME_PANEL_SCORE].value = score;
4798 DisplayGameControlValues();
4800 if (time == time_final)
4801 StopSound(SND_GAME_LEVELTIME_BONUS);
4802 else if (setup.sound_loops)
4803 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4805 PlaySound(SND_GAME_LEVELTIME_BONUS);
4810 if (game_over_delay_2 > 0)
4812 game_over_delay_2--;
4817 if (health != health_final)
4819 int health_count_dir = (health < health_final ? +1 : -1);
4821 health += health_count_dir;
4822 score += level.score[SC_TIME_BONUS];
4824 game.LevelSolved_CountingHealth = health;
4825 game.LevelSolved_CountingScore = score;
4827 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4828 game_panel_controls[GAME_PANEL_SCORE].value = score;
4830 DisplayGameControlValues();
4832 if (health == health_final)
4833 StopSound(SND_GAME_LEVELTIME_BONUS);
4834 else if (setup.sound_loops)
4835 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4837 PlaySound(SND_GAME_LEVELTIME_BONUS);
4842 game.panel.active = FALSE;
4844 if (game_over_delay_3 > 0)
4846 game_over_delay_3--;
4856 // used instead of "level_nr" (needed for network games)
4857 int last_level_nr = levelset.level_nr;
4860 game.LevelSolved_GameEnd = TRUE;
4862 if (game.LevelSolved_SaveTape)
4864 // make sure that request dialog to save tape does not open door again
4865 if (!global.use_envelope_request)
4866 CloseDoor(DOOR_CLOSE_1);
4868 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4871 // if no tape is to be saved, close both doors simultaneously
4872 CloseDoor(DOOR_CLOSE_ALL);
4874 if (level_editor_test_game)
4876 SetGameStatus(GAME_MODE_MAIN);
4883 if (!game.LevelSolved_SaveScore)
4885 SetGameStatus(GAME_MODE_MAIN);
4892 if (level_nr == leveldir_current->handicap_level)
4894 leveldir_current->handicap_level++;
4896 SaveLevelSetup_SeriesInfo();
4899 if (setup.increment_levels &&
4900 level_nr < leveldir_current->last_level &&
4903 level_nr++; // advance to next level
4904 TapeErase(); // start with empty tape
4906 if (setup.auto_play_next_level)
4908 LoadLevel(level_nr);
4910 SaveLevelSetup_SeriesInfo();
4914 hi_pos = NewHiScore(last_level_nr);
4916 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4918 SetGameStatus(GAME_MODE_SCORES);
4920 DrawHallOfFame(last_level_nr, hi_pos);
4922 else if (setup.auto_play_next_level && setup.increment_levels &&
4923 last_level_nr < leveldir_current->last_level &&
4926 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4930 SetGameStatus(GAME_MODE_MAIN);
4936 int NewHiScore(int level_nr)
4940 boolean one_score_entry_per_name = !program.many_scores_per_name;
4942 LoadScore(level_nr);
4944 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4945 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4948 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4950 if (game.score_final > highscore[k].Score)
4952 // player has made it to the hall of fame
4954 if (k < MAX_SCORE_ENTRIES - 1)
4956 int m = MAX_SCORE_ENTRIES - 1;
4958 if (one_score_entry_per_name)
4960 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4961 if (strEqual(setup.player_name, highscore[l].Name))
4964 if (m == k) // player's new highscore overwrites his old one
4968 for (l = m; l > k; l--)
4970 strcpy(highscore[l].Name, highscore[l - 1].Name);
4971 highscore[l].Score = highscore[l - 1].Score;
4977 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4978 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4979 highscore[k].Score = game.score_final;
4984 else if (one_score_entry_per_name &&
4985 !strncmp(setup.player_name, highscore[k].Name,
4986 MAX_PLAYER_NAME_LEN))
4987 break; // player already there with a higher score
4991 SaveScore(level_nr);
4996 static int getElementMoveStepsizeExt(int x, int y, int direction)
4998 int element = Feld[x][y];
4999 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5000 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5001 int horiz_move = (dx != 0);
5002 int sign = (horiz_move ? dx : dy);
5003 int step = sign * element_info[element].move_stepsize;
5005 // special values for move stepsize for spring and things on conveyor belt
5008 if (CAN_FALL(element) &&
5009 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1]))
5010 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5011 else if (element == EL_SPRING)
5012 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5018 static int getElementMoveStepsize(int x, int y)
5020 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5023 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5025 if (player->GfxAction != action || player->GfxDir != dir)
5027 player->GfxAction = action;
5028 player->GfxDir = dir;
5030 player->StepFrame = 0;
5034 static void ResetGfxFrame(int x, int y)
5036 // profiling showed that "autotest" spends 10~20% of its time in this function
5037 if (DrawingDeactivatedField())
5040 int element = Feld[x][y];
5041 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5043 if (graphic_info[graphic].anim_global_sync)
5044 GfxFrame[x][y] = FrameCounter;
5045 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5046 GfxFrame[x][y] = CustomValue[x][y];
5047 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5048 GfxFrame[x][y] = element_info[element].collect_score;
5049 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5050 GfxFrame[x][y] = ChangeDelay[x][y];
5053 static void ResetGfxAnimation(int x, int y)
5055 GfxAction[x][y] = ACTION_DEFAULT;
5056 GfxDir[x][y] = MovDir[x][y];
5059 ResetGfxFrame(x, y);
5062 static void ResetRandomAnimationValue(int x, int y)
5064 GfxRandom[x][y] = INIT_GFX_RANDOM();
5067 static void InitMovingField(int x, int y, int direction)
5069 int element = Feld[x][y];
5070 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5071 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5074 boolean is_moving_before, is_moving_after;
5076 // check if element was/is moving or being moved before/after mode change
5077 is_moving_before = (WasJustMoving[x][y] != 0);
5078 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5080 // reset animation only for moving elements which change direction of moving
5081 // or which just started or stopped moving
5082 // (else CEs with property "can move" / "not moving" are reset each frame)
5083 if (is_moving_before != is_moving_after ||
5084 direction != MovDir[x][y])
5085 ResetGfxAnimation(x, y);
5087 MovDir[x][y] = direction;
5088 GfxDir[x][y] = direction;
5090 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5091 direction == MV_DOWN && CAN_FALL(element) ?
5092 ACTION_FALLING : ACTION_MOVING);
5094 // this is needed for CEs with property "can move" / "not moving"
5096 if (is_moving_after)
5098 if (Feld[newx][newy] == EL_EMPTY)
5099 Feld[newx][newy] = EL_BLOCKED;
5101 MovDir[newx][newy] = MovDir[x][y];
5103 CustomValue[newx][newy] = CustomValue[x][y];
5105 GfxFrame[newx][newy] = GfxFrame[x][y];
5106 GfxRandom[newx][newy] = GfxRandom[x][y];
5107 GfxAction[newx][newy] = GfxAction[x][y];
5108 GfxDir[newx][newy] = GfxDir[x][y];
5112 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5114 int direction = MovDir[x][y];
5115 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5116 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5122 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5124 int oldx = x, oldy = y;
5125 int direction = MovDir[x][y];
5127 if (direction == MV_LEFT)
5129 else if (direction == MV_RIGHT)
5131 else if (direction == MV_UP)
5133 else if (direction == MV_DOWN)
5136 *comes_from_x = oldx;
5137 *comes_from_y = oldy;
5140 static int MovingOrBlocked2Element(int x, int y)
5142 int element = Feld[x][y];
5144 if (element == EL_BLOCKED)
5148 Blocked2Moving(x, y, &oldx, &oldy);
5149 return Feld[oldx][oldy];
5155 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5157 // like MovingOrBlocked2Element(), but if element is moving
5158 // and (x,y) is the field the moving element is just leaving,
5159 // return EL_BLOCKED instead of the element value
5160 int element = Feld[x][y];
5162 if (IS_MOVING(x, y))
5164 if (element == EL_BLOCKED)
5168 Blocked2Moving(x, y, &oldx, &oldy);
5169 return Feld[oldx][oldy];
5178 static void RemoveField(int x, int y)
5180 Feld[x][y] = EL_EMPTY;
5186 CustomValue[x][y] = 0;
5189 ChangeDelay[x][y] = 0;
5190 ChangePage[x][y] = -1;
5191 Pushed[x][y] = FALSE;
5193 GfxElement[x][y] = EL_UNDEFINED;
5194 GfxAction[x][y] = ACTION_DEFAULT;
5195 GfxDir[x][y] = MV_NONE;
5198 static void RemoveMovingField(int x, int y)
5200 int oldx = x, oldy = y, newx = x, newy = y;
5201 int element = Feld[x][y];
5202 int next_element = EL_UNDEFINED;
5204 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5207 if (IS_MOVING(x, y))
5209 Moving2Blocked(x, y, &newx, &newy);
5211 if (Feld[newx][newy] != EL_BLOCKED)
5213 // element is moving, but target field is not free (blocked), but
5214 // already occupied by something different (example: acid pool);
5215 // in this case, only remove the moving field, but not the target
5217 RemoveField(oldx, oldy);
5219 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5221 TEST_DrawLevelField(oldx, oldy);
5226 else if (element == EL_BLOCKED)
5228 Blocked2Moving(x, y, &oldx, &oldy);
5229 if (!IS_MOVING(oldx, oldy))
5233 if (element == EL_BLOCKED &&
5234 (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5235 Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5236 Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5237 Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5238 Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5239 Feld[oldx][oldy] == EL_AMOEBA_DROPPING))
5240 next_element = get_next_element(Feld[oldx][oldy]);
5242 RemoveField(oldx, oldy);
5243 RemoveField(newx, newy);
5245 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5247 if (next_element != EL_UNDEFINED)
5248 Feld[oldx][oldy] = next_element;
5250 TEST_DrawLevelField(oldx, oldy);
5251 TEST_DrawLevelField(newx, newy);
5254 void DrawDynamite(int x, int y)
5256 int sx = SCREENX(x), sy = SCREENY(y);
5257 int graphic = el2img(Feld[x][y]);
5260 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5263 if (IS_WALKABLE_INSIDE(Back[x][y]))
5267 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5268 else if (Store[x][y])
5269 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5271 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5273 if (Back[x][y] || Store[x][y])
5274 DrawGraphicThruMask(sx, sy, graphic, frame);
5276 DrawGraphic(sx, sy, graphic, frame);
5279 static void CheckDynamite(int x, int y)
5281 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5285 if (MovDelay[x][y] != 0)
5288 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5294 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5299 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5301 boolean num_checked_players = 0;
5304 for (i = 0; i < MAX_PLAYERS; i++)
5306 if (stored_player[i].active)
5308 int sx = stored_player[i].jx;
5309 int sy = stored_player[i].jy;
5311 if (num_checked_players == 0)
5318 *sx1 = MIN(*sx1, sx);
5319 *sy1 = MIN(*sy1, sy);
5320 *sx2 = MAX(*sx2, sx);
5321 *sy2 = MAX(*sy2, sy);
5324 num_checked_players++;
5329 static boolean checkIfAllPlayersFitToScreen_RND(void)
5331 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5333 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5335 return (sx2 - sx1 < SCR_FIELDX &&
5336 sy2 - sy1 < SCR_FIELDY);
5339 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5341 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5343 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5345 *sx = (sx1 + sx2) / 2;
5346 *sy = (sy1 + sy2) / 2;
5349 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5350 boolean center_screen, boolean quick_relocation)
5352 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5353 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5354 boolean no_delay = (tape.warp_forward);
5355 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5356 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5357 int new_scroll_x, new_scroll_y;
5359 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5361 // case 1: quick relocation inside visible screen (without scrolling)
5368 if (!level.shifted_relocation || center_screen)
5370 // relocation _with_ centering of screen
5372 new_scroll_x = SCROLL_POSITION_X(x);
5373 new_scroll_y = SCROLL_POSITION_Y(y);
5377 // relocation _without_ centering of screen
5379 int center_scroll_x = SCROLL_POSITION_X(old_x);
5380 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5381 int offset_x = x + (scroll_x - center_scroll_x);
5382 int offset_y = y + (scroll_y - center_scroll_y);
5384 // for new screen position, apply previous offset to center position
5385 new_scroll_x = SCROLL_POSITION_X(offset_x);
5386 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5389 if (quick_relocation)
5391 // case 2: quick relocation (redraw without visible scrolling)
5393 scroll_x = new_scroll_x;
5394 scroll_y = new_scroll_y;
5401 // case 3: visible relocation (with scrolling to new position)
5403 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5405 SetVideoFrameDelay(wait_delay_value);
5407 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5409 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5410 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5412 if (dx == 0 && dy == 0) // no scrolling needed at all
5418 // set values for horizontal/vertical screen scrolling (half tile size)
5419 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5420 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5421 int pos_x = dx * TILEX / 2;
5422 int pos_y = dy * TILEY / 2;
5423 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5424 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5426 ScrollLevel(dx, dy);
5429 // scroll in two steps of half tile size to make things smoother
5430 BlitScreenToBitmapExt_RND(window, fx, fy);
5432 // scroll second step to align at full tile size
5433 BlitScreenToBitmap(window);
5439 SetVideoFrameDelay(frame_delay_value_old);
5442 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5444 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5445 int player_nr = GET_PLAYER_NR(el_player);
5446 struct PlayerInfo *player = &stored_player[player_nr];
5447 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5448 boolean no_delay = (tape.warp_forward);
5449 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5450 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5451 int old_jx = player->jx;
5452 int old_jy = player->jy;
5453 int old_element = Feld[old_jx][old_jy];
5454 int element = Feld[jx][jy];
5455 boolean player_relocated = (old_jx != jx || old_jy != jy);
5457 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5458 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5459 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5460 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5461 int leave_side_horiz = move_dir_horiz;
5462 int leave_side_vert = move_dir_vert;
5463 int enter_side = enter_side_horiz | enter_side_vert;
5464 int leave_side = leave_side_horiz | leave_side_vert;
5466 if (player->buried) // do not reanimate dead player
5469 if (!player_relocated) // no need to relocate the player
5472 if (IS_PLAYER(jx, jy)) // player already placed at new position
5474 RemoveField(jx, jy); // temporarily remove newly placed player
5475 DrawLevelField(jx, jy);
5478 if (player->present)
5480 while (player->MovPos)
5482 ScrollPlayer(player, SCROLL_GO_ON);
5483 ScrollScreen(NULL, SCROLL_GO_ON);
5485 AdvanceFrameAndPlayerCounters(player->index_nr);
5489 BackToFront_WithFrameDelay(wait_delay_value);
5492 DrawPlayer(player); // needed here only to cleanup last field
5493 DrawLevelField(player->jx, player->jy); // remove player graphic
5495 player->is_moving = FALSE;
5498 if (IS_CUSTOM_ELEMENT(old_element))
5499 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5501 player->index_bit, leave_side);
5503 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5505 player->index_bit, leave_side);
5507 Feld[jx][jy] = el_player;
5508 InitPlayerField(jx, jy, el_player, TRUE);
5510 /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be
5511 possible that the relocation target field did not contain a player element,
5512 but a walkable element, to which the new player was relocated -- in this
5513 case, restore that (already initialized!) element on the player field */
5514 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5516 Feld[jx][jy] = element; // restore previously existing element
5519 // only visually relocate centered player
5520 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5521 FALSE, level.instant_relocation);
5523 TestIfPlayerTouchesBadThing(jx, jy);
5524 TestIfPlayerTouchesCustomElement(jx, jy);
5526 if (IS_CUSTOM_ELEMENT(element))
5527 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5528 player->index_bit, enter_side);
5530 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5531 player->index_bit, enter_side);
5533 if (player->is_switching)
5535 /* ensure that relocation while still switching an element does not cause
5536 a new element to be treated as also switched directly after relocation
5537 (this is important for teleporter switches that teleport the player to
5538 a place where another teleporter switch is in the same direction, which
5539 would then incorrectly be treated as immediately switched before the
5540 direction key that caused the switch was released) */
5542 player->switch_x += jx - old_jx;
5543 player->switch_y += jy - old_jy;
5547 static void Explode(int ex, int ey, int phase, int mode)
5553 // !!! eliminate this variable !!!
5554 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5556 if (game.explosions_delayed)
5558 ExplodeField[ex][ey] = mode;
5562 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5564 int center_element = Feld[ex][ey];
5565 int artwork_element, explosion_element; // set these values later
5567 // remove things displayed in background while burning dynamite
5568 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5571 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5573 // put moving element to center field (and let it explode there)
5574 center_element = MovingOrBlocked2Element(ex, ey);
5575 RemoveMovingField(ex, ey);
5576 Feld[ex][ey] = center_element;
5579 // now "center_element" is finally determined -- set related values now
5580 artwork_element = center_element; // for custom player artwork
5581 explosion_element = center_element; // for custom player artwork
5583 if (IS_PLAYER(ex, ey))
5585 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5587 artwork_element = stored_player[player_nr].artwork_element;
5589 if (level.use_explosion_element[player_nr])
5591 explosion_element = level.explosion_element[player_nr];
5592 artwork_element = explosion_element;
5596 if (mode == EX_TYPE_NORMAL ||
5597 mode == EX_TYPE_CENTER ||
5598 mode == EX_TYPE_CROSS)
5599 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5601 last_phase = element_info[explosion_element].explosion_delay + 1;
5603 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5605 int xx = x - ex + 1;
5606 int yy = y - ey + 1;
5609 if (!IN_LEV_FIELD(x, y) ||
5610 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5611 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5614 element = Feld[x][y];
5616 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5618 element = MovingOrBlocked2Element(x, y);
5620 if (!IS_EXPLOSION_PROOF(element))
5621 RemoveMovingField(x, y);
5624 // indestructible elements can only explode in center (but not flames)
5625 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5626 mode == EX_TYPE_BORDER)) ||
5627 element == EL_FLAMES)
5630 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5631 behaviour, for example when touching a yamyam that explodes to rocks
5632 with active deadly shield, a rock is created under the player !!! */
5633 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5635 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5636 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5637 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5639 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5642 if (IS_ACTIVE_BOMB(element))
5644 // re-activate things under the bomb like gate or penguin
5645 Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5652 // save walkable background elements while explosion on same tile
5653 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5654 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5655 Back[x][y] = element;
5657 // ignite explodable elements reached by other explosion
5658 if (element == EL_EXPLOSION)
5659 element = Store2[x][y];
5661 if (AmoebaNr[x][y] &&
5662 (element == EL_AMOEBA_FULL ||
5663 element == EL_BD_AMOEBA ||
5664 element == EL_AMOEBA_GROWING))
5666 AmoebaCnt[AmoebaNr[x][y]]--;
5667 AmoebaCnt2[AmoebaNr[x][y]]--;
5672 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5674 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5676 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5678 if (PLAYERINFO(ex, ey)->use_murphy)
5679 Store[x][y] = EL_EMPTY;
5682 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5683 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5684 else if (ELEM_IS_PLAYER(center_element))
5685 Store[x][y] = EL_EMPTY;
5686 else if (center_element == EL_YAMYAM)
5687 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5688 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5689 Store[x][y] = element_info[center_element].content.e[xx][yy];
5691 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5692 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5693 // otherwise) -- FIX THIS !!!
5694 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5695 Store[x][y] = element_info[element].content.e[1][1];
5697 else if (!CAN_EXPLODE(element))
5698 Store[x][y] = element_info[element].content.e[1][1];
5701 Store[x][y] = EL_EMPTY;
5703 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5704 center_element == EL_AMOEBA_TO_DIAMOND)
5705 Store2[x][y] = element;
5707 Feld[x][y] = EL_EXPLOSION;
5708 GfxElement[x][y] = artwork_element;
5710 ExplodePhase[x][y] = 1;
5711 ExplodeDelay[x][y] = last_phase;
5716 if (center_element == EL_YAMYAM)
5717 game.yamyam_content_nr =
5718 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5730 GfxFrame[x][y] = 0; // restart explosion animation
5732 last_phase = ExplodeDelay[x][y];
5734 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5736 // this can happen if the player leaves an explosion just in time
5737 if (GfxElement[x][y] == EL_UNDEFINED)
5738 GfxElement[x][y] = EL_EMPTY;
5740 border_element = Store2[x][y];
5741 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5742 border_element = StorePlayer[x][y];
5744 if (phase == element_info[border_element].ignition_delay ||
5745 phase == last_phase)
5747 boolean border_explosion = FALSE;
5749 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5750 !PLAYER_EXPLOSION_PROTECTED(x, y))
5752 KillPlayerUnlessExplosionProtected(x, y);
5753 border_explosion = TRUE;
5755 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5757 Feld[x][y] = Store2[x][y];
5760 border_explosion = TRUE;
5762 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5764 AmoebeUmwandeln(x, y);
5766 border_explosion = TRUE;
5769 // if an element just explodes due to another explosion (chain-reaction),
5770 // do not immediately end the new explosion when it was the last frame of
5771 // the explosion (as it would be done in the following "if"-statement!)
5772 if (border_explosion && phase == last_phase)
5776 if (phase == last_phase)
5780 element = Feld[x][y] = Store[x][y];
5781 Store[x][y] = Store2[x][y] = 0;
5782 GfxElement[x][y] = EL_UNDEFINED;
5784 // player can escape from explosions and might therefore be still alive
5785 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5786 element <= EL_PLAYER_IS_EXPLODING_4)
5788 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5789 int explosion_element = EL_PLAYER_1 + player_nr;
5790 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5791 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5793 if (level.use_explosion_element[player_nr])
5794 explosion_element = level.explosion_element[player_nr];
5796 Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5797 element_info[explosion_element].content.e[xx][yy]);
5800 // restore probably existing indestructible background element
5801 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5802 element = Feld[x][y] = Back[x][y];
5805 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5806 GfxDir[x][y] = MV_NONE;
5807 ChangeDelay[x][y] = 0;
5808 ChangePage[x][y] = -1;
5810 CustomValue[x][y] = 0;
5812 InitField_WithBug2(x, y, FALSE);
5814 TEST_DrawLevelField(x, y);
5816 TestIfElementTouchesCustomElement(x, y);
5818 if (GFX_CRUMBLED(element))
5819 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5821 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5822 StorePlayer[x][y] = 0;
5824 if (ELEM_IS_PLAYER(element))
5825 RelocatePlayer(x, y, element);
5827 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5829 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5830 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5833 TEST_DrawLevelFieldCrumbled(x, y);
5835 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5837 DrawLevelElement(x, y, Back[x][y]);
5838 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5840 else if (IS_WALKABLE_UNDER(Back[x][y]))
5842 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5843 DrawLevelElementThruMask(x, y, Back[x][y]);
5845 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5846 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5850 static void DynaExplode(int ex, int ey)
5853 int dynabomb_element = Feld[ex][ey];
5854 int dynabomb_size = 1;
5855 boolean dynabomb_xl = FALSE;
5856 struct PlayerInfo *player;
5857 static int xy[4][2] =
5865 if (IS_ACTIVE_BOMB(dynabomb_element))
5867 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5868 dynabomb_size = player->dynabomb_size;
5869 dynabomb_xl = player->dynabomb_xl;
5870 player->dynabombs_left++;
5873 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5875 for (i = 0; i < NUM_DIRECTIONS; i++)
5877 for (j = 1; j <= dynabomb_size; j++)
5879 int x = ex + j * xy[i][0];
5880 int y = ey + j * xy[i][1];
5883 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y]))
5886 element = Feld[x][y];
5888 // do not restart explosions of fields with active bombs
5889 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5892 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5894 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5895 !IS_DIGGABLE(element) && !dynabomb_xl)
5901 void Bang(int x, int y)
5903 int element = MovingOrBlocked2Element(x, y);
5904 int explosion_type = EX_TYPE_NORMAL;
5906 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5908 struct PlayerInfo *player = PLAYERINFO(x, y);
5910 element = Feld[x][y] = player->initial_element;
5912 if (level.use_explosion_element[player->index_nr])
5914 int explosion_element = level.explosion_element[player->index_nr];
5916 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5917 explosion_type = EX_TYPE_CROSS;
5918 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5919 explosion_type = EX_TYPE_CENTER;
5927 case EL_BD_BUTTERFLY:
5930 case EL_DARK_YAMYAM:
5934 RaiseScoreElement(element);
5937 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5938 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5939 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5940 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5941 case EL_DYNABOMB_INCREASE_NUMBER:
5942 case EL_DYNABOMB_INCREASE_SIZE:
5943 case EL_DYNABOMB_INCREASE_POWER:
5944 explosion_type = EX_TYPE_DYNA;
5947 case EL_DC_LANDMINE:
5948 explosion_type = EX_TYPE_CENTER;
5953 case EL_LAMP_ACTIVE:
5954 case EL_AMOEBA_TO_DIAMOND:
5955 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5956 explosion_type = EX_TYPE_CENTER;
5960 if (element_info[element].explosion_type == EXPLODES_CROSS)
5961 explosion_type = EX_TYPE_CROSS;
5962 else if (element_info[element].explosion_type == EXPLODES_1X1)
5963 explosion_type = EX_TYPE_CENTER;
5967 if (explosion_type == EX_TYPE_DYNA)
5970 Explode(x, y, EX_PHASE_START, explosion_type);
5972 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5975 static void SplashAcid(int x, int y)
5977 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5978 (!IN_LEV_FIELD(x - 1, y - 2) ||
5979 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5980 Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5982 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5983 (!IN_LEV_FIELD(x + 1, y - 2) ||
5984 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5985 Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5987 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5990 static void InitBeltMovement(void)
5992 static int belt_base_element[4] =
5994 EL_CONVEYOR_BELT_1_LEFT,
5995 EL_CONVEYOR_BELT_2_LEFT,
5996 EL_CONVEYOR_BELT_3_LEFT,
5997 EL_CONVEYOR_BELT_4_LEFT
5999 static int belt_base_active_element[4] =
6001 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6002 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6003 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6004 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6009 // set frame order for belt animation graphic according to belt direction
6010 for (i = 0; i < NUM_BELTS; i++)
6014 for (j = 0; j < NUM_BELT_PARTS; j++)
6016 int element = belt_base_active_element[belt_nr] + j;
6017 int graphic_1 = el2img(element);
6018 int graphic_2 = el2panelimg(element);
6020 if (game.belt_dir[i] == MV_LEFT)
6022 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6023 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6027 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6028 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6033 SCAN_PLAYFIELD(x, y)
6035 int element = Feld[x][y];
6037 for (i = 0; i < NUM_BELTS; i++)
6039 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6041 int e_belt_nr = getBeltNrFromBeltElement(element);
6044 if (e_belt_nr == belt_nr)
6046 int belt_part = Feld[x][y] - belt_base_element[belt_nr];
6048 Feld[x][y] = belt_base_active_element[belt_nr] + belt_part;
6055 static void ToggleBeltSwitch(int x, int y)
6057 static int belt_base_element[4] =
6059 EL_CONVEYOR_BELT_1_LEFT,
6060 EL_CONVEYOR_BELT_2_LEFT,
6061 EL_CONVEYOR_BELT_3_LEFT,
6062 EL_CONVEYOR_BELT_4_LEFT
6064 static int belt_base_active_element[4] =
6066 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6067 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6068 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6069 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6071 static int belt_base_switch_element[4] =
6073 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6074 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6075 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6076 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6078 static int belt_move_dir[4] =
6086 int element = Feld[x][y];
6087 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6088 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6089 int belt_dir = belt_move_dir[belt_dir_nr];
6092 if (!IS_BELT_SWITCH(element))
6095 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6096 game.belt_dir[belt_nr] = belt_dir;
6098 if (belt_dir_nr == 3)
6101 // set frame order for belt animation graphic according to belt direction
6102 for (i = 0; i < NUM_BELT_PARTS; i++)
6104 int element = belt_base_active_element[belt_nr] + i;
6105 int graphic_1 = el2img(element);
6106 int graphic_2 = el2panelimg(element);
6108 if (belt_dir == MV_LEFT)
6110 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6111 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6115 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6116 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6120 SCAN_PLAYFIELD(xx, yy)
6122 int element = Feld[xx][yy];
6124 if (IS_BELT_SWITCH(element))
6126 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6128 if (e_belt_nr == belt_nr)
6130 Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6131 TEST_DrawLevelField(xx, yy);
6134 else if (IS_BELT(element) && belt_dir != MV_NONE)
6136 int e_belt_nr = getBeltNrFromBeltElement(element);
6138 if (e_belt_nr == belt_nr)
6140 int belt_part = Feld[xx][yy] - belt_base_element[belt_nr];
6142 Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6143 TEST_DrawLevelField(xx, yy);
6146 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6148 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6150 if (e_belt_nr == belt_nr)
6152 int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr];
6154 Feld[xx][yy] = belt_base_element[belt_nr] + belt_part;
6155 TEST_DrawLevelField(xx, yy);
6161 static void ToggleSwitchgateSwitch(int x, int y)
6165 game.switchgate_pos = !game.switchgate_pos;
6167 SCAN_PLAYFIELD(xx, yy)
6169 int element = Feld[xx][yy];
6171 if (element == EL_SWITCHGATE_SWITCH_UP)
6173 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6174 TEST_DrawLevelField(xx, yy);
6176 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6178 Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6179 TEST_DrawLevelField(xx, yy);
6181 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6183 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6184 TEST_DrawLevelField(xx, yy);
6186 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6188 Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6189 TEST_DrawLevelField(xx, yy);
6191 else if (element == EL_SWITCHGATE_OPEN ||
6192 element == EL_SWITCHGATE_OPENING)
6194 Feld[xx][yy] = EL_SWITCHGATE_CLOSING;
6196 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6198 else if (element == EL_SWITCHGATE_CLOSED ||
6199 element == EL_SWITCHGATE_CLOSING)
6201 Feld[xx][yy] = EL_SWITCHGATE_OPENING;
6203 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6208 static int getInvisibleActiveFromInvisibleElement(int element)
6210 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6211 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6212 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6216 static int getInvisibleFromInvisibleActiveElement(int element)
6218 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6219 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6220 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6224 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6228 SCAN_PLAYFIELD(x, y)
6230 int element = Feld[x][y];
6232 if (element == EL_LIGHT_SWITCH &&
6233 game.light_time_left > 0)
6235 Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6236 TEST_DrawLevelField(x, y);
6238 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6239 game.light_time_left == 0)
6241 Feld[x][y] = EL_LIGHT_SWITCH;
6242 TEST_DrawLevelField(x, y);
6244 else if (element == EL_EMC_DRIPPER &&
6245 game.light_time_left > 0)
6247 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6248 TEST_DrawLevelField(x, y);
6250 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6251 game.light_time_left == 0)
6253 Feld[x][y] = EL_EMC_DRIPPER;
6254 TEST_DrawLevelField(x, y);
6256 else if (element == EL_INVISIBLE_STEELWALL ||
6257 element == EL_INVISIBLE_WALL ||
6258 element == EL_INVISIBLE_SAND)
6260 if (game.light_time_left > 0)
6261 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6263 TEST_DrawLevelField(x, y);
6265 // uncrumble neighbour fields, if needed
6266 if (element == EL_INVISIBLE_SAND)
6267 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6269 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6270 element == EL_INVISIBLE_WALL_ACTIVE ||
6271 element == EL_INVISIBLE_SAND_ACTIVE)
6273 if (game.light_time_left == 0)
6274 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6276 TEST_DrawLevelField(x, y);
6278 // re-crumble neighbour fields, if needed
6279 if (element == EL_INVISIBLE_SAND)
6280 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6285 static void RedrawAllInvisibleElementsForLenses(void)
6289 SCAN_PLAYFIELD(x, y)
6291 int element = Feld[x][y];
6293 if (element == EL_EMC_DRIPPER &&
6294 game.lenses_time_left > 0)
6296 Feld[x][y] = EL_EMC_DRIPPER_ACTIVE;
6297 TEST_DrawLevelField(x, y);
6299 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6300 game.lenses_time_left == 0)
6302 Feld[x][y] = EL_EMC_DRIPPER;
6303 TEST_DrawLevelField(x, y);
6305 else if (element == EL_INVISIBLE_STEELWALL ||
6306 element == EL_INVISIBLE_WALL ||
6307 element == EL_INVISIBLE_SAND)
6309 if (game.lenses_time_left > 0)
6310 Feld[x][y] = getInvisibleActiveFromInvisibleElement(element);
6312 TEST_DrawLevelField(x, y);
6314 // uncrumble neighbour fields, if needed
6315 if (element == EL_INVISIBLE_SAND)
6316 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6318 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6319 element == EL_INVISIBLE_WALL_ACTIVE ||
6320 element == EL_INVISIBLE_SAND_ACTIVE)
6322 if (game.lenses_time_left == 0)
6323 Feld[x][y] = getInvisibleFromInvisibleActiveElement(element);
6325 TEST_DrawLevelField(x, y);
6327 // re-crumble neighbour fields, if needed
6328 if (element == EL_INVISIBLE_SAND)
6329 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6334 static void RedrawAllInvisibleElementsForMagnifier(void)
6338 SCAN_PLAYFIELD(x, y)
6340 int element = Feld[x][y];
6342 if (element == EL_EMC_FAKE_GRASS &&
6343 game.magnify_time_left > 0)
6345 Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6346 TEST_DrawLevelField(x, y);
6348 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6349 game.magnify_time_left == 0)
6351 Feld[x][y] = EL_EMC_FAKE_GRASS;
6352 TEST_DrawLevelField(x, y);
6354 else if (IS_GATE_GRAY(element) &&
6355 game.magnify_time_left > 0)
6357 Feld[x][y] = (IS_RND_GATE_GRAY(element) ?
6358 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6359 IS_EM_GATE_GRAY(element) ?
6360 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6361 IS_EMC_GATE_GRAY(element) ?
6362 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6363 IS_DC_GATE_GRAY(element) ?
6364 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6366 TEST_DrawLevelField(x, y);
6368 else if (IS_GATE_GRAY_ACTIVE(element) &&
6369 game.magnify_time_left == 0)
6371 Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6372 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6373 IS_EM_GATE_GRAY_ACTIVE(element) ?
6374 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6375 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6376 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6377 IS_DC_GATE_GRAY_ACTIVE(element) ?
6378 EL_DC_GATE_WHITE_GRAY :
6380 TEST_DrawLevelField(x, y);
6385 static void ToggleLightSwitch(int x, int y)
6387 int element = Feld[x][y];
6389 game.light_time_left =
6390 (element == EL_LIGHT_SWITCH ?
6391 level.time_light * FRAMES_PER_SECOND : 0);
6393 RedrawAllLightSwitchesAndInvisibleElements();
6396 static void ActivateTimegateSwitch(int x, int y)
6400 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6402 SCAN_PLAYFIELD(xx, yy)
6404 int element = Feld[xx][yy];
6406 if (element == EL_TIMEGATE_CLOSED ||
6407 element == EL_TIMEGATE_CLOSING)
6409 Feld[xx][yy] = EL_TIMEGATE_OPENING;
6410 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6414 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6416 Feld[xx][yy] = EL_TIMEGATE_SWITCH;
6417 TEST_DrawLevelField(xx, yy);
6423 Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6424 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6427 static void Impact(int x, int y)
6429 boolean last_line = (y == lev_fieldy - 1);
6430 boolean object_hit = FALSE;
6431 boolean impact = (last_line || object_hit);
6432 int element = Feld[x][y];
6433 int smashed = EL_STEELWALL;
6435 if (!last_line) // check if element below was hit
6437 if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING)
6440 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6441 MovDir[x][y + 1] != MV_DOWN ||
6442 MovPos[x][y + 1] <= TILEY / 2));
6444 // do not smash moving elements that left the smashed field in time
6445 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6446 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6449 #if USE_QUICKSAND_IMPACT_BUGFIX
6450 if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6452 RemoveMovingField(x, y + 1);
6453 Feld[x][y + 1] = EL_QUICKSAND_EMPTY;
6454 Feld[x][y + 2] = EL_ROCK;
6455 TEST_DrawLevelField(x, y + 2);
6460 if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6462 RemoveMovingField(x, y + 1);
6463 Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6464 Feld[x][y + 2] = EL_ROCK;
6465 TEST_DrawLevelField(x, y + 2);
6472 smashed = MovingOrBlocked2Element(x, y + 1);
6474 impact = (last_line || object_hit);
6477 if (!last_line && smashed == EL_ACID) // element falls into acid
6479 SplashAcid(x, y + 1);
6483 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6484 // only reset graphic animation if graphic really changes after impact
6486 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6488 ResetGfxAnimation(x, y);
6489 TEST_DrawLevelField(x, y);
6492 if (impact && CAN_EXPLODE_IMPACT(element))
6497 else if (impact && element == EL_PEARL &&
6498 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6500 ResetGfxAnimation(x, y);
6502 Feld[x][y] = EL_PEARL_BREAKING;
6503 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6506 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6508 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6513 if (impact && element == EL_AMOEBA_DROP)
6515 if (object_hit && IS_PLAYER(x, y + 1))
6516 KillPlayerUnlessEnemyProtected(x, y + 1);
6517 else if (object_hit && smashed == EL_PENGUIN)
6521 Feld[x][y] = EL_AMOEBA_GROWING;
6522 Store[x][y] = EL_AMOEBA_WET;
6524 ResetRandomAnimationValue(x, y);
6529 if (object_hit) // check which object was hit
6531 if ((CAN_PASS_MAGIC_WALL(element) &&
6532 (smashed == EL_MAGIC_WALL ||
6533 smashed == EL_BD_MAGIC_WALL)) ||
6534 (CAN_PASS_DC_MAGIC_WALL(element) &&
6535 smashed == EL_DC_MAGIC_WALL))
6538 int activated_magic_wall =
6539 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6540 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6541 EL_DC_MAGIC_WALL_ACTIVE);
6543 // activate magic wall / mill
6544 SCAN_PLAYFIELD(xx, yy)
6546 if (Feld[xx][yy] == smashed)
6547 Feld[xx][yy] = activated_magic_wall;
6550 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6551 game.magic_wall_active = TRUE;
6553 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6554 SND_MAGIC_WALL_ACTIVATING :
6555 smashed == EL_BD_MAGIC_WALL ?
6556 SND_BD_MAGIC_WALL_ACTIVATING :
6557 SND_DC_MAGIC_WALL_ACTIVATING));
6560 if (IS_PLAYER(x, y + 1))
6562 if (CAN_SMASH_PLAYER(element))
6564 KillPlayerUnlessEnemyProtected(x, y + 1);
6568 else if (smashed == EL_PENGUIN)
6570 if (CAN_SMASH_PLAYER(element))
6576 else if (element == EL_BD_DIAMOND)
6578 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6584 else if (((element == EL_SP_INFOTRON ||
6585 element == EL_SP_ZONK) &&
6586 (smashed == EL_SP_SNIKSNAK ||
6587 smashed == EL_SP_ELECTRON ||
6588 smashed == EL_SP_DISK_ORANGE)) ||
6589 (element == EL_SP_INFOTRON &&
6590 smashed == EL_SP_DISK_YELLOW))
6595 else if (CAN_SMASH_EVERYTHING(element))
6597 if (IS_CLASSIC_ENEMY(smashed) ||
6598 CAN_EXPLODE_SMASHED(smashed))
6603 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6605 if (smashed == EL_LAMP ||
6606 smashed == EL_LAMP_ACTIVE)
6611 else if (smashed == EL_NUT)
6613 Feld[x][y + 1] = EL_NUT_BREAKING;
6614 PlayLevelSound(x, y, SND_NUT_BREAKING);
6615 RaiseScoreElement(EL_NUT);
6618 else if (smashed == EL_PEARL)
6620 ResetGfxAnimation(x, y);
6622 Feld[x][y + 1] = EL_PEARL_BREAKING;
6623 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6626 else if (smashed == EL_DIAMOND)
6628 Feld[x][y + 1] = EL_DIAMOND_BREAKING;
6629 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6632 else if (IS_BELT_SWITCH(smashed))
6634 ToggleBeltSwitch(x, y + 1);
6636 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6637 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6638 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6639 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6641 ToggleSwitchgateSwitch(x, y + 1);
6643 else if (smashed == EL_LIGHT_SWITCH ||
6644 smashed == EL_LIGHT_SWITCH_ACTIVE)
6646 ToggleLightSwitch(x, y + 1);
6650 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6652 CheckElementChangeBySide(x, y + 1, smashed, element,
6653 CE_SWITCHED, CH_SIDE_TOP);
6654 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6660 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6665 // play sound of magic wall / mill
6667 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6668 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6669 Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6671 if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6672 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6673 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6674 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6675 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6676 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6681 // play sound of object that hits the ground
6682 if (last_line || object_hit)
6683 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6686 static void TurnRoundExt(int x, int y)
6698 { 0, 0 }, { 0, 0 }, { 0, 0 },
6703 int left, right, back;
6707 { MV_DOWN, MV_UP, MV_RIGHT },
6708 { MV_UP, MV_DOWN, MV_LEFT },
6710 { MV_LEFT, MV_RIGHT, MV_DOWN },
6714 { MV_RIGHT, MV_LEFT, MV_UP }
6717 int element = Feld[x][y];
6718 int move_pattern = element_info[element].move_pattern;
6720 int old_move_dir = MovDir[x][y];
6721 int left_dir = turn[old_move_dir].left;
6722 int right_dir = turn[old_move_dir].right;
6723 int back_dir = turn[old_move_dir].back;
6725 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6726 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6727 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6728 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6730 int left_x = x + left_dx, left_y = y + left_dy;
6731 int right_x = x + right_dx, right_y = y + right_dy;
6732 int move_x = x + move_dx, move_y = y + move_dy;
6736 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6738 TestIfBadThingTouchesOtherBadThing(x, y);
6740 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6741 MovDir[x][y] = right_dir;
6742 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6743 MovDir[x][y] = left_dir;
6745 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6747 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6750 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6752 TestIfBadThingTouchesOtherBadThing(x, y);
6754 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6755 MovDir[x][y] = left_dir;
6756 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6757 MovDir[x][y] = right_dir;
6759 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6761 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6764 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6766 TestIfBadThingTouchesOtherBadThing(x, y);
6768 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6769 MovDir[x][y] = left_dir;
6770 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6771 MovDir[x][y] = right_dir;
6773 if (MovDir[x][y] != old_move_dir)
6776 else if (element == EL_YAMYAM)
6778 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6779 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6781 if (can_turn_left && can_turn_right)
6782 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6783 else if (can_turn_left)
6784 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6785 else if (can_turn_right)
6786 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6788 MovDir[x][y] = back_dir;
6790 MovDelay[x][y] = 16 + 16 * RND(3);
6792 else if (element == EL_DARK_YAMYAM)
6794 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6796 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6799 if (can_turn_left && can_turn_right)
6800 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6801 else if (can_turn_left)
6802 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6803 else if (can_turn_right)
6804 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6806 MovDir[x][y] = back_dir;
6808 MovDelay[x][y] = 16 + 16 * RND(3);
6810 else if (element == EL_PACMAN)
6812 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6813 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6815 if (can_turn_left && can_turn_right)
6816 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6817 else if (can_turn_left)
6818 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6819 else if (can_turn_right)
6820 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6822 MovDir[x][y] = back_dir;
6824 MovDelay[x][y] = 6 + RND(40);
6826 else if (element == EL_PIG)
6828 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6829 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6830 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6831 boolean should_turn_left, should_turn_right, should_move_on;
6833 int rnd = RND(rnd_value);
6835 should_turn_left = (can_turn_left &&
6837 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6838 y + back_dy + left_dy)));
6839 should_turn_right = (can_turn_right &&
6841 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6842 y + back_dy + right_dy)));
6843 should_move_on = (can_move_on &&
6846 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6847 y + move_dy + left_dy) ||
6848 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6849 y + move_dy + right_dy)));
6851 if (should_turn_left || should_turn_right || should_move_on)
6853 if (should_turn_left && should_turn_right && should_move_on)
6854 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6855 rnd < 2 * rnd_value / 3 ? right_dir :
6857 else if (should_turn_left && should_turn_right)
6858 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6859 else if (should_turn_left && should_move_on)
6860 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6861 else if (should_turn_right && should_move_on)
6862 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6863 else if (should_turn_left)
6864 MovDir[x][y] = left_dir;
6865 else if (should_turn_right)
6866 MovDir[x][y] = right_dir;
6867 else if (should_move_on)
6868 MovDir[x][y] = old_move_dir;
6870 else if (can_move_on && rnd > rnd_value / 8)
6871 MovDir[x][y] = old_move_dir;
6872 else if (can_turn_left && can_turn_right)
6873 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6874 else if (can_turn_left && rnd > rnd_value / 8)
6875 MovDir[x][y] = left_dir;
6876 else if (can_turn_right && rnd > rnd_value/8)
6877 MovDir[x][y] = right_dir;
6879 MovDir[x][y] = back_dir;
6881 xx = x + move_xy[MovDir[x][y]].dx;
6882 yy = y + move_xy[MovDir[x][y]].dy;
6884 if (!IN_LEV_FIELD(xx, yy) ||
6885 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy])))
6886 MovDir[x][y] = old_move_dir;
6890 else if (element == EL_DRAGON)
6892 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6893 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6894 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6896 int rnd = RND(rnd_value);
6898 if (can_move_on && rnd > rnd_value / 8)
6899 MovDir[x][y] = old_move_dir;
6900 else if (can_turn_left && can_turn_right)
6901 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6902 else if (can_turn_left && rnd > rnd_value / 8)
6903 MovDir[x][y] = left_dir;
6904 else if (can_turn_right && rnd > rnd_value / 8)
6905 MovDir[x][y] = right_dir;
6907 MovDir[x][y] = back_dir;
6909 xx = x + move_xy[MovDir[x][y]].dx;
6910 yy = y + move_xy[MovDir[x][y]].dy;
6912 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6913 MovDir[x][y] = old_move_dir;
6917 else if (element == EL_MOLE)
6919 boolean can_move_on =
6920 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6921 IS_AMOEBOID(Feld[move_x][move_y]) ||
6922 Feld[move_x][move_y] == EL_AMOEBA_SHRINKING));
6925 boolean can_turn_left =
6926 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6927 IS_AMOEBOID(Feld[left_x][left_y])));
6929 boolean can_turn_right =
6930 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6931 IS_AMOEBOID(Feld[right_x][right_y])));
6933 if (can_turn_left && can_turn_right)
6934 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6935 else if (can_turn_left)
6936 MovDir[x][y] = left_dir;
6938 MovDir[x][y] = right_dir;
6941 if (MovDir[x][y] != old_move_dir)
6944 else if (element == EL_BALLOON)
6946 MovDir[x][y] = game.wind_direction;
6949 else if (element == EL_SPRING)
6951 if (MovDir[x][y] & MV_HORIZONTAL)
6953 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6954 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6956 Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6957 ResetGfxAnimation(move_x, move_y);
6958 TEST_DrawLevelField(move_x, move_y);
6960 MovDir[x][y] = back_dir;
6962 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6963 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6964 MovDir[x][y] = MV_NONE;
6969 else if (element == EL_ROBOT ||
6970 element == EL_SATELLITE ||
6971 element == EL_PENGUIN ||
6972 element == EL_EMC_ANDROID)
6974 int attr_x = -1, attr_y = -1;
6976 if (game.all_players_gone)
6978 attr_x = game.exit_x;
6979 attr_y = game.exit_y;
6985 for (i = 0; i < MAX_PLAYERS; i++)
6987 struct PlayerInfo *player = &stored_player[i];
6988 int jx = player->jx, jy = player->jy;
6990 if (!player->active)
6994 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7002 if (element == EL_ROBOT &&
7003 game.robot_wheel_x >= 0 &&
7004 game.robot_wheel_y >= 0 &&
7005 (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7006 game.engine_version < VERSION_IDENT(3,1,0,0)))
7008 attr_x = game.robot_wheel_x;
7009 attr_y = game.robot_wheel_y;
7012 if (element == EL_PENGUIN)
7015 static int xy[4][2] =
7023 for (i = 0; i < NUM_DIRECTIONS; i++)
7025 int ex = x + xy[i][0];
7026 int ey = y + xy[i][1];
7028 if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN ||
7029 Feld[ex][ey] == EL_EM_EXIT_OPEN ||
7030 Feld[ex][ey] == EL_STEEL_EXIT_OPEN ||
7031 Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7040 MovDir[x][y] = MV_NONE;
7042 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7043 else if (attr_x > x)
7044 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7046 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7047 else if (attr_y > y)
7048 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7050 if (element == EL_ROBOT)
7054 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7055 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7056 Moving2Blocked(x, y, &newx, &newy);
7058 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7059 MovDelay[x][y] = 8 + 8 * !RND(3);
7061 MovDelay[x][y] = 16;
7063 else if (element == EL_PENGUIN)
7069 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7071 boolean first_horiz = RND(2);
7072 int new_move_dir = MovDir[x][y];
7075 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7076 Moving2Blocked(x, y, &newx, &newy);
7078 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7082 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7083 Moving2Blocked(x, y, &newx, &newy);
7085 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7088 MovDir[x][y] = old_move_dir;
7092 else if (element == EL_SATELLITE)
7098 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7100 boolean first_horiz = RND(2);
7101 int new_move_dir = MovDir[x][y];
7104 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7105 Moving2Blocked(x, y, &newx, &newy);
7107 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7111 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7112 Moving2Blocked(x, y, &newx, &newy);
7114 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7117 MovDir[x][y] = old_move_dir;
7121 else if (element == EL_EMC_ANDROID)
7123 static int check_pos[16] =
7125 -1, // 0 => (invalid)
7128 -1, // 3 => (invalid)
7130 0, // 5 => MV_LEFT | MV_UP
7131 2, // 6 => MV_RIGHT | MV_UP
7132 -1, // 7 => (invalid)
7134 6, // 9 => MV_LEFT | MV_DOWN
7135 4, // 10 => MV_RIGHT | MV_DOWN
7136 -1, // 11 => (invalid)
7137 -1, // 12 => (invalid)
7138 -1, // 13 => (invalid)
7139 -1, // 14 => (invalid)
7140 -1, // 15 => (invalid)
7148 { -1, -1, MV_LEFT | MV_UP },
7150 { +1, -1, MV_RIGHT | MV_UP },
7151 { +1, 0, MV_RIGHT },
7152 { +1, +1, MV_RIGHT | MV_DOWN },
7154 { -1, +1, MV_LEFT | MV_DOWN },
7157 int start_pos, check_order;
7158 boolean can_clone = FALSE;
7161 // check if there is any free field around current position
7162 for (i = 0; i < 8; i++)
7164 int newx = x + check_xy[i].dx;
7165 int newy = y + check_xy[i].dy;
7167 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7175 if (can_clone) // randomly find an element to clone
7179 start_pos = check_pos[RND(8)];
7180 check_order = (RND(2) ? -1 : +1);
7182 for (i = 0; i < 8; i++)
7184 int pos_raw = start_pos + i * check_order;
7185 int pos = (pos_raw + 8) % 8;
7186 int newx = x + check_xy[pos].dx;
7187 int newy = y + check_xy[pos].dy;
7189 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7191 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7192 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7194 Store[x][y] = Feld[newx][newy];
7203 if (can_clone) // randomly find a direction to move
7207 start_pos = check_pos[RND(8)];
7208 check_order = (RND(2) ? -1 : +1);
7210 for (i = 0; i < 8; i++)
7212 int pos_raw = start_pos + i * check_order;
7213 int pos = (pos_raw + 8) % 8;
7214 int newx = x + check_xy[pos].dx;
7215 int newy = y + check_xy[pos].dy;
7216 int new_move_dir = check_xy[pos].dir;
7218 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7220 MovDir[x][y] = new_move_dir;
7221 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7230 if (can_clone) // cloning and moving successful
7233 // cannot clone -- try to move towards player
7235 start_pos = check_pos[MovDir[x][y] & 0x0f];
7236 check_order = (RND(2) ? -1 : +1);
7238 for (i = 0; i < 3; i++)
7240 // first check start_pos, then previous/next or (next/previous) pos
7241 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7242 int pos = (pos_raw + 8) % 8;
7243 int newx = x + check_xy[pos].dx;
7244 int newy = y + check_xy[pos].dy;
7245 int new_move_dir = check_xy[pos].dir;
7247 if (IS_PLAYER(newx, newy))
7250 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7252 MovDir[x][y] = new_move_dir;
7253 MovDelay[x][y] = level.android_move_time * 8 + 1;
7260 else if (move_pattern == MV_TURNING_LEFT ||
7261 move_pattern == MV_TURNING_RIGHT ||
7262 move_pattern == MV_TURNING_LEFT_RIGHT ||
7263 move_pattern == MV_TURNING_RIGHT_LEFT ||
7264 move_pattern == MV_TURNING_RANDOM ||
7265 move_pattern == MV_ALL_DIRECTIONS)
7267 boolean can_turn_left =
7268 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7269 boolean can_turn_right =
7270 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7272 if (element_info[element].move_stepsize == 0) // "not moving"
7275 if (move_pattern == MV_TURNING_LEFT)
7276 MovDir[x][y] = left_dir;
7277 else if (move_pattern == MV_TURNING_RIGHT)
7278 MovDir[x][y] = right_dir;
7279 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7280 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7281 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7282 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7283 else if (move_pattern == MV_TURNING_RANDOM)
7284 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7285 can_turn_right && !can_turn_left ? right_dir :
7286 RND(2) ? left_dir : right_dir);
7287 else if (can_turn_left && can_turn_right)
7288 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7289 else if (can_turn_left)
7290 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7291 else if (can_turn_right)
7292 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7294 MovDir[x][y] = back_dir;
7296 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7298 else if (move_pattern == MV_HORIZONTAL ||
7299 move_pattern == MV_VERTICAL)
7301 if (move_pattern & old_move_dir)
7302 MovDir[x][y] = back_dir;
7303 else if (move_pattern == MV_HORIZONTAL)
7304 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7305 else if (move_pattern == MV_VERTICAL)
7306 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7308 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7310 else if (move_pattern & MV_ANY_DIRECTION)
7312 MovDir[x][y] = move_pattern;
7313 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7315 else if (move_pattern & MV_WIND_DIRECTION)
7317 MovDir[x][y] = game.wind_direction;
7318 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7320 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7322 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7323 MovDir[x][y] = left_dir;
7324 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7325 MovDir[x][y] = right_dir;
7327 if (MovDir[x][y] != old_move_dir)
7328 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7330 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7332 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7333 MovDir[x][y] = right_dir;
7334 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7335 MovDir[x][y] = left_dir;
7337 if (MovDir[x][y] != old_move_dir)
7338 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7340 else if (move_pattern == MV_TOWARDS_PLAYER ||
7341 move_pattern == MV_AWAY_FROM_PLAYER)
7343 int attr_x = -1, attr_y = -1;
7345 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7347 if (game.all_players_gone)
7349 attr_x = game.exit_x;
7350 attr_y = game.exit_y;
7356 for (i = 0; i < MAX_PLAYERS; i++)
7358 struct PlayerInfo *player = &stored_player[i];
7359 int jx = player->jx, jy = player->jy;
7361 if (!player->active)
7365 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7373 MovDir[x][y] = MV_NONE;
7375 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7376 else if (attr_x > x)
7377 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7379 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7380 else if (attr_y > y)
7381 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7383 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7385 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7387 boolean first_horiz = RND(2);
7388 int new_move_dir = MovDir[x][y];
7390 if (element_info[element].move_stepsize == 0) // "not moving"
7392 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7393 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7399 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7400 Moving2Blocked(x, y, &newx, &newy);
7402 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7406 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7407 Moving2Blocked(x, y, &newx, &newy);
7409 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7412 MovDir[x][y] = old_move_dir;
7415 else if (move_pattern == MV_WHEN_PUSHED ||
7416 move_pattern == MV_WHEN_DROPPED)
7418 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7419 MovDir[x][y] = MV_NONE;
7423 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7425 static int test_xy[7][2] =
7435 static int test_dir[7] =
7445 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7446 int move_preference = -1000000; // start with very low preference
7447 int new_move_dir = MV_NONE;
7448 int start_test = RND(4);
7451 for (i = 0; i < NUM_DIRECTIONS; i++)
7453 int move_dir = test_dir[start_test + i];
7454 int move_dir_preference;
7456 xx = x + test_xy[start_test + i][0];
7457 yy = y + test_xy[start_test + i][1];
7459 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7460 (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING))
7462 new_move_dir = move_dir;
7467 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7470 move_dir_preference = -1 * RunnerVisit[xx][yy];
7471 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7472 move_dir_preference = PlayerVisit[xx][yy];
7474 if (move_dir_preference > move_preference)
7476 // prefer field that has not been visited for the longest time
7477 move_preference = move_dir_preference;
7478 new_move_dir = move_dir;
7480 else if (move_dir_preference == move_preference &&
7481 move_dir == old_move_dir)
7483 // prefer last direction when all directions are preferred equally
7484 move_preference = move_dir_preference;
7485 new_move_dir = move_dir;
7489 MovDir[x][y] = new_move_dir;
7490 if (old_move_dir != new_move_dir)
7491 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7495 static void TurnRound(int x, int y)
7497 int direction = MovDir[x][y];
7501 GfxDir[x][y] = MovDir[x][y];
7503 if (direction != MovDir[x][y])
7507 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7509 ResetGfxFrame(x, y);
7512 static boolean JustBeingPushed(int x, int y)
7516 for (i = 0; i < MAX_PLAYERS; i++)
7518 struct PlayerInfo *player = &stored_player[i];
7520 if (player->active && player->is_pushing && player->MovPos)
7522 int next_jx = player->jx + (player->jx - player->last_jx);
7523 int next_jy = player->jy + (player->jy - player->last_jy);
7525 if (x == next_jx && y == next_jy)
7533 static void StartMoving(int x, int y)
7535 boolean started_moving = FALSE; // some elements can fall _and_ move
7536 int element = Feld[x][y];
7541 if (MovDelay[x][y] == 0)
7542 GfxAction[x][y] = ACTION_DEFAULT;
7544 if (CAN_FALL(element) && y < lev_fieldy - 1)
7546 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7547 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7548 if (JustBeingPushed(x, y))
7551 if (element == EL_QUICKSAND_FULL)
7553 if (IS_FREE(x, y + 1))
7555 InitMovingField(x, y, MV_DOWN);
7556 started_moving = TRUE;
7558 Feld[x][y] = EL_QUICKSAND_EMPTYING;
7559 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7560 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7561 Store[x][y] = EL_ROCK;
7563 Store[x][y] = EL_ROCK;
7566 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7568 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7570 if (!MovDelay[x][y])
7572 MovDelay[x][y] = TILEY + 1;
7574 ResetGfxAnimation(x, y);
7575 ResetGfxAnimation(x, y + 1);
7580 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7581 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7588 Feld[x][y] = EL_QUICKSAND_EMPTY;
7589 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7590 Store[x][y + 1] = Store[x][y];
7593 PlayLevelSoundAction(x, y, ACTION_FILLING);
7595 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7597 if (!MovDelay[x][y])
7599 MovDelay[x][y] = TILEY + 1;
7601 ResetGfxAnimation(x, y);
7602 ResetGfxAnimation(x, y + 1);
7607 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7608 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7615 Feld[x][y] = EL_QUICKSAND_EMPTY;
7616 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7617 Store[x][y + 1] = Store[x][y];
7620 PlayLevelSoundAction(x, y, ACTION_FILLING);
7623 else if (element == EL_QUICKSAND_FAST_FULL)
7625 if (IS_FREE(x, y + 1))
7627 InitMovingField(x, y, MV_DOWN);
7628 started_moving = TRUE;
7630 Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7631 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7632 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7633 Store[x][y] = EL_ROCK;
7635 Store[x][y] = EL_ROCK;
7638 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7640 else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7642 if (!MovDelay[x][y])
7644 MovDelay[x][y] = TILEY + 1;
7646 ResetGfxAnimation(x, y);
7647 ResetGfxAnimation(x, y + 1);
7652 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7653 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7660 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7661 Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7662 Store[x][y + 1] = Store[x][y];
7665 PlayLevelSoundAction(x, y, ACTION_FILLING);
7667 else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7669 if (!MovDelay[x][y])
7671 MovDelay[x][y] = TILEY + 1;
7673 ResetGfxAnimation(x, y);
7674 ResetGfxAnimation(x, y + 1);
7679 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7680 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7687 Feld[x][y] = EL_QUICKSAND_FAST_EMPTY;
7688 Feld[x][y + 1] = EL_QUICKSAND_FULL;
7689 Store[x][y + 1] = Store[x][y];
7692 PlayLevelSoundAction(x, y, ACTION_FILLING);
7695 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7696 Feld[x][y + 1] == EL_QUICKSAND_EMPTY)
7698 InitMovingField(x, y, MV_DOWN);
7699 started_moving = TRUE;
7701 Feld[x][y] = EL_QUICKSAND_FILLING;
7702 Store[x][y] = element;
7704 PlayLevelSoundAction(x, y, ACTION_FILLING);
7706 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7707 Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7709 InitMovingField(x, y, MV_DOWN);
7710 started_moving = TRUE;
7712 Feld[x][y] = EL_QUICKSAND_FAST_FILLING;
7713 Store[x][y] = element;
7715 PlayLevelSoundAction(x, y, ACTION_FILLING);
7717 else if (element == EL_MAGIC_WALL_FULL)
7719 if (IS_FREE(x, y + 1))
7721 InitMovingField(x, y, MV_DOWN);
7722 started_moving = TRUE;
7724 Feld[x][y] = EL_MAGIC_WALL_EMPTYING;
7725 Store[x][y] = EL_CHANGED(Store[x][y]);
7727 else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7729 if (!MovDelay[x][y])
7730 MovDelay[x][y] = TILEY / 4 + 1;
7739 Feld[x][y] = EL_MAGIC_WALL_ACTIVE;
7740 Feld[x][y + 1] = EL_MAGIC_WALL_FULL;
7741 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7745 else if (element == EL_BD_MAGIC_WALL_FULL)
7747 if (IS_FREE(x, y + 1))
7749 InitMovingField(x, y, MV_DOWN);
7750 started_moving = TRUE;
7752 Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7753 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7755 else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7757 if (!MovDelay[x][y])
7758 MovDelay[x][y] = TILEY / 4 + 1;
7767 Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7768 Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7769 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7773 else if (element == EL_DC_MAGIC_WALL_FULL)
7775 if (IS_FREE(x, y + 1))
7777 InitMovingField(x, y, MV_DOWN);
7778 started_moving = TRUE;
7780 Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7781 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7783 else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7785 if (!MovDelay[x][y])
7786 MovDelay[x][y] = TILEY / 4 + 1;
7795 Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7796 Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7797 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7801 else if ((CAN_PASS_MAGIC_WALL(element) &&
7802 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7803 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7804 (CAN_PASS_DC_MAGIC_WALL(element) &&
7805 (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7808 InitMovingField(x, y, MV_DOWN);
7809 started_moving = TRUE;
7812 (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7813 Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7814 EL_DC_MAGIC_WALL_FILLING);
7815 Store[x][y] = element;
7817 else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID)
7819 SplashAcid(x, y + 1);
7821 InitMovingField(x, y, MV_DOWN);
7822 started_moving = TRUE;
7824 Store[x][y] = EL_ACID;
7827 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7828 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7829 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7830 CAN_FALL(element) && WasJustFalling[x][y] &&
7831 (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7833 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7834 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7835 (Feld[x][y + 1] == EL_BLOCKED)))
7837 /* this is needed for a special case not covered by calling "Impact()"
7838 from "ContinueMoving()": if an element moves to a tile directly below
7839 another element which was just falling on that tile (which was empty
7840 in the previous frame), the falling element above would just stop
7841 instead of smashing the element below (in previous version, the above
7842 element was just checked for "moving" instead of "falling", resulting
7843 in incorrect smashes caused by horizontal movement of the above
7844 element; also, the case of the player being the element to smash was
7845 simply not covered here... :-/ ) */
7847 CheckCollision[x][y] = 0;
7848 CheckImpact[x][y] = 0;
7852 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7854 if (MovDir[x][y] == MV_NONE)
7856 InitMovingField(x, y, MV_DOWN);
7857 started_moving = TRUE;
7860 else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING)
7862 if (WasJustFalling[x][y]) // prevent animation from being restarted
7863 MovDir[x][y] = MV_DOWN;
7865 InitMovingField(x, y, MV_DOWN);
7866 started_moving = TRUE;
7868 else if (element == EL_AMOEBA_DROP)
7870 Feld[x][y] = EL_AMOEBA_GROWING;
7871 Store[x][y] = EL_AMOEBA_WET;
7873 else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7874 (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) &&
7875 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7876 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7878 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7879 (IS_FREE(x - 1, y + 1) ||
7880 Feld[x - 1][y + 1] == EL_ACID));
7881 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7882 (IS_FREE(x + 1, y + 1) ||
7883 Feld[x + 1][y + 1] == EL_ACID));
7884 boolean can_fall_any = (can_fall_left || can_fall_right);
7885 boolean can_fall_both = (can_fall_left && can_fall_right);
7886 int slippery_type = element_info[Feld[x][y + 1]].slippery_type;
7888 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7890 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7891 can_fall_right = FALSE;
7892 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7893 can_fall_left = FALSE;
7894 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7895 can_fall_right = FALSE;
7896 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7897 can_fall_left = FALSE;
7899 can_fall_any = (can_fall_left || can_fall_right);
7900 can_fall_both = FALSE;
7905 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7906 can_fall_right = FALSE; // slip down on left side
7908 can_fall_left = !(can_fall_right = RND(2));
7910 can_fall_both = FALSE;
7915 // if not determined otherwise, prefer left side for slipping down
7916 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7917 started_moving = TRUE;
7920 else if (IS_BELT_ACTIVE(Feld[x][y + 1]))
7922 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7923 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7924 int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]);
7925 int belt_dir = game.belt_dir[belt_nr];
7927 if ((belt_dir == MV_LEFT && left_is_free) ||
7928 (belt_dir == MV_RIGHT && right_is_free))
7930 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7932 InitMovingField(x, y, belt_dir);
7933 started_moving = TRUE;
7935 Pushed[x][y] = TRUE;
7936 Pushed[nextx][y] = TRUE;
7938 GfxAction[x][y] = ACTION_DEFAULT;
7942 MovDir[x][y] = 0; // if element was moving, stop it
7947 // not "else if" because of elements that can fall and move (EL_SPRING)
7948 if (CAN_MOVE(element) && !started_moving)
7950 int move_pattern = element_info[element].move_pattern;
7953 Moving2Blocked(x, y, &newx, &newy);
7955 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7958 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7959 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7961 WasJustMoving[x][y] = 0;
7962 CheckCollision[x][y] = 0;
7964 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7966 if (Feld[x][y] != element) // element has changed
7970 if (!MovDelay[x][y]) // start new movement phase
7972 // all objects that can change their move direction after each step
7973 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7975 if (element != EL_YAMYAM &&
7976 element != EL_DARK_YAMYAM &&
7977 element != EL_PACMAN &&
7978 !(move_pattern & MV_ANY_DIRECTION) &&
7979 move_pattern != MV_TURNING_LEFT &&
7980 move_pattern != MV_TURNING_RIGHT &&
7981 move_pattern != MV_TURNING_LEFT_RIGHT &&
7982 move_pattern != MV_TURNING_RIGHT_LEFT &&
7983 move_pattern != MV_TURNING_RANDOM)
7987 if (MovDelay[x][y] && (element == EL_BUG ||
7988 element == EL_SPACESHIP ||
7989 element == EL_SP_SNIKSNAK ||
7990 element == EL_SP_ELECTRON ||
7991 element == EL_MOLE))
7992 TEST_DrawLevelField(x, y);
7996 if (MovDelay[x][y]) // wait some time before next movement
8000 if (element == EL_ROBOT ||
8001 element == EL_YAMYAM ||
8002 element == EL_DARK_YAMYAM)
8004 DrawLevelElementAnimationIfNeeded(x, y, element);
8005 PlayLevelSoundAction(x, y, ACTION_WAITING);
8007 else if (element == EL_SP_ELECTRON)
8008 DrawLevelElementAnimationIfNeeded(x, y, element);
8009 else if (element == EL_DRAGON)
8012 int dir = MovDir[x][y];
8013 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8014 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8015 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8016 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8017 dir == MV_UP ? IMG_FLAMES_1_UP :
8018 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8019 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8021 GfxAction[x][y] = ACTION_ATTACKING;
8023 if (IS_PLAYER(x, y))
8024 DrawPlayerField(x, y);
8026 TEST_DrawLevelField(x, y);
8028 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8030 for (i = 1; i <= 3; i++)
8032 int xx = x + i * dx;
8033 int yy = y + i * dy;
8034 int sx = SCREENX(xx);
8035 int sy = SCREENY(yy);
8036 int flame_graphic = graphic + (i - 1);
8038 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy]))
8043 int flamed = MovingOrBlocked2Element(xx, yy);
8045 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8048 RemoveMovingField(xx, yy);
8050 ChangeDelay[xx][yy] = 0;
8052 Feld[xx][yy] = EL_FLAMES;
8054 if (IN_SCR_FIELD(sx, sy))
8056 TEST_DrawLevelFieldCrumbled(xx, yy);
8057 DrawGraphic(sx, sy, flame_graphic, frame);
8062 if (Feld[xx][yy] == EL_FLAMES)
8063 Feld[xx][yy] = EL_EMPTY;
8064 TEST_DrawLevelField(xx, yy);
8069 if (MovDelay[x][y]) // element still has to wait some time
8071 PlayLevelSoundAction(x, y, ACTION_WAITING);
8077 // now make next step
8079 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8081 if (DONT_COLLIDE_WITH(element) &&
8082 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8083 !PLAYER_ENEMY_PROTECTED(newx, newy))
8085 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8090 else if (CAN_MOVE_INTO_ACID(element) &&
8091 IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID &&
8092 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8093 (MovDir[x][y] == MV_DOWN ||
8094 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8096 SplashAcid(newx, newy);
8097 Store[x][y] = EL_ACID;
8099 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8101 if (Feld[newx][newy] == EL_EXIT_OPEN ||
8102 Feld[newx][newy] == EL_EM_EXIT_OPEN ||
8103 Feld[newx][newy] == EL_STEEL_EXIT_OPEN ||
8104 Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8107 TEST_DrawLevelField(x, y);
8109 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8110 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8111 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8113 game.friends_still_needed--;
8114 if (!game.friends_still_needed &&
8116 game.all_players_gone)
8121 else if (IS_FOOD_PENGUIN(Feld[newx][newy]))
8123 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8124 TEST_DrawLevelField(newx, newy);
8126 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8128 else if (!IS_FREE(newx, newy))
8130 GfxAction[x][y] = ACTION_WAITING;
8132 if (IS_PLAYER(x, y))
8133 DrawPlayerField(x, y);
8135 TEST_DrawLevelField(x, y);
8140 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8142 if (IS_FOOD_PIG(Feld[newx][newy]))
8144 if (IS_MOVING(newx, newy))
8145 RemoveMovingField(newx, newy);
8148 Feld[newx][newy] = EL_EMPTY;
8149 TEST_DrawLevelField(newx, newy);
8152 PlayLevelSound(x, y, SND_PIG_DIGGING);
8154 else if (!IS_FREE(newx, newy))
8156 if (IS_PLAYER(x, y))
8157 DrawPlayerField(x, y);
8159 TEST_DrawLevelField(x, y);
8164 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8166 if (Store[x][y] != EL_EMPTY)
8168 boolean can_clone = FALSE;
8171 // check if element to clone is still there
8172 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8174 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y])
8182 // cannot clone or target field not free anymore -- do not clone
8183 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8184 Store[x][y] = EL_EMPTY;
8187 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8189 if (IS_MV_DIAGONAL(MovDir[x][y]))
8191 int diagonal_move_dir = MovDir[x][y];
8192 int stored = Store[x][y];
8193 int change_delay = 8;
8196 // android is moving diagonally
8198 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8200 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8201 GfxElement[x][y] = EL_EMC_ANDROID;
8202 GfxAction[x][y] = ACTION_SHRINKING;
8203 GfxDir[x][y] = diagonal_move_dir;
8204 ChangeDelay[x][y] = change_delay;
8206 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8209 DrawLevelGraphicAnimation(x, y, graphic);
8210 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8212 if (Feld[newx][newy] == EL_ACID)
8214 SplashAcid(newx, newy);
8219 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8221 Store[newx][newy] = EL_EMC_ANDROID;
8222 GfxElement[newx][newy] = EL_EMC_ANDROID;
8223 GfxAction[newx][newy] = ACTION_GROWING;
8224 GfxDir[newx][newy] = diagonal_move_dir;
8225 ChangeDelay[newx][newy] = change_delay;
8227 graphic = el_act_dir2img(GfxElement[newx][newy],
8228 GfxAction[newx][newy], GfxDir[newx][newy]);
8230 DrawLevelGraphicAnimation(newx, newy, graphic);
8231 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8237 Feld[newx][newy] = EL_EMPTY;
8238 TEST_DrawLevelField(newx, newy);
8240 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8243 else if (!IS_FREE(newx, newy))
8248 else if (IS_CUSTOM_ELEMENT(element) &&
8249 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8251 if (!DigFieldByCE(newx, newy, element))
8254 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8256 RunnerVisit[x][y] = FrameCounter;
8257 PlayerVisit[x][y] /= 8; // expire player visit path
8260 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8262 if (!IS_FREE(newx, newy))
8264 if (IS_PLAYER(x, y))
8265 DrawPlayerField(x, y);
8267 TEST_DrawLevelField(x, y);
8273 boolean wanna_flame = !RND(10);
8274 int dx = newx - x, dy = newy - y;
8275 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8276 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8277 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8278 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8279 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8280 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8283 IS_CLASSIC_ENEMY(element1) ||
8284 IS_CLASSIC_ENEMY(element2)) &&
8285 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8286 element1 != EL_FLAMES && element2 != EL_FLAMES)
8288 ResetGfxAnimation(x, y);
8289 GfxAction[x][y] = ACTION_ATTACKING;
8291 if (IS_PLAYER(x, y))
8292 DrawPlayerField(x, y);
8294 TEST_DrawLevelField(x, y);
8296 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8298 MovDelay[x][y] = 50;
8300 Feld[newx][newy] = EL_FLAMES;
8301 if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY)
8302 Feld[newx1][newy1] = EL_FLAMES;
8303 if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY)
8304 Feld[newx2][newy2] = EL_FLAMES;
8310 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8311 Feld[newx][newy] == EL_DIAMOND)
8313 if (IS_MOVING(newx, newy))
8314 RemoveMovingField(newx, newy);
8317 Feld[newx][newy] = EL_EMPTY;
8318 TEST_DrawLevelField(newx, newy);
8321 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8323 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8324 IS_FOOD_DARK_YAMYAM(Feld[newx][newy]))
8326 if (AmoebaNr[newx][newy])
8328 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8329 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8330 Feld[newx][newy] == EL_BD_AMOEBA)
8331 AmoebaCnt[AmoebaNr[newx][newy]]--;
8334 if (IS_MOVING(newx, newy))
8336 RemoveMovingField(newx, newy);
8340 Feld[newx][newy] = EL_EMPTY;
8341 TEST_DrawLevelField(newx, newy);
8344 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8346 else if ((element == EL_PACMAN || element == EL_MOLE)
8347 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy]))
8349 if (AmoebaNr[newx][newy])
8351 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8352 if (Feld[newx][newy] == EL_AMOEBA_FULL ||
8353 Feld[newx][newy] == EL_BD_AMOEBA)
8354 AmoebaCnt[AmoebaNr[newx][newy]]--;
8357 if (element == EL_MOLE)
8359 Feld[newx][newy] = EL_AMOEBA_SHRINKING;
8360 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8362 ResetGfxAnimation(x, y);
8363 GfxAction[x][y] = ACTION_DIGGING;
8364 TEST_DrawLevelField(x, y);
8366 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8368 return; // wait for shrinking amoeba
8370 else // element == EL_PACMAN
8372 Feld[newx][newy] = EL_EMPTY;
8373 TEST_DrawLevelField(newx, newy);
8374 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8377 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8378 (Feld[newx][newy] == EL_AMOEBA_SHRINKING ||
8379 (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8381 // wait for shrinking amoeba to completely disappear
8384 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8386 // object was running against a wall
8390 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8391 DrawLevelElementAnimation(x, y, element);
8393 if (DONT_TOUCH(element))
8394 TestIfBadThingTouchesPlayer(x, y);
8399 InitMovingField(x, y, MovDir[x][y]);
8401 PlayLevelSoundAction(x, y, ACTION_MOVING);
8405 ContinueMoving(x, y);
8408 void ContinueMoving(int x, int y)
8410 int element = Feld[x][y];
8411 struct ElementInfo *ei = &element_info[element];
8412 int direction = MovDir[x][y];
8413 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8414 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8415 int newx = x + dx, newy = y + dy;
8416 int stored = Store[x][y];
8417 int stored_new = Store[newx][newy];
8418 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8419 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8420 boolean last_line = (newy == lev_fieldy - 1);
8422 MovPos[x][y] += getElementMoveStepsize(x, y);
8424 if (pushed_by_player) // special case: moving object pushed by player
8425 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8427 if (ABS(MovPos[x][y]) < TILEX)
8429 TEST_DrawLevelField(x, y);
8431 return; // element is still moving
8434 // element reached destination field
8436 Feld[x][y] = EL_EMPTY;
8437 Feld[newx][newy] = element;
8438 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8440 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8442 element = Feld[newx][newy] = EL_ACID;
8444 else if (element == EL_MOLE)
8446 Feld[x][y] = EL_SAND;
8448 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8450 else if (element == EL_QUICKSAND_FILLING)
8452 element = Feld[newx][newy] = get_next_element(element);
8453 Store[newx][newy] = Store[x][y];
8455 else if (element == EL_QUICKSAND_EMPTYING)
8457 Feld[x][y] = get_next_element(element);
8458 element = Feld[newx][newy] = Store[x][y];
8460 else if (element == EL_QUICKSAND_FAST_FILLING)
8462 element = Feld[newx][newy] = get_next_element(element);
8463 Store[newx][newy] = Store[x][y];
8465 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8467 Feld[x][y] = get_next_element(element);
8468 element = Feld[newx][newy] = Store[x][y];
8470 else if (element == EL_MAGIC_WALL_FILLING)
8472 element = Feld[newx][newy] = get_next_element(element);
8473 if (!game.magic_wall_active)
8474 element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD;
8475 Store[newx][newy] = Store[x][y];
8477 else if (element == EL_MAGIC_WALL_EMPTYING)
8479 Feld[x][y] = get_next_element(element);
8480 if (!game.magic_wall_active)
8481 Feld[x][y] = EL_MAGIC_WALL_DEAD;
8482 element = Feld[newx][newy] = Store[x][y];
8484 InitField(newx, newy, FALSE);
8486 else if (element == EL_BD_MAGIC_WALL_FILLING)
8488 element = Feld[newx][newy] = get_next_element(element);
8489 if (!game.magic_wall_active)
8490 element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8491 Store[newx][newy] = Store[x][y];
8493 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8495 Feld[x][y] = get_next_element(element);
8496 if (!game.magic_wall_active)
8497 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
8498 element = Feld[newx][newy] = Store[x][y];
8500 InitField(newx, newy, FALSE);
8502 else if (element == EL_DC_MAGIC_WALL_FILLING)
8504 element = Feld[newx][newy] = get_next_element(element);
8505 if (!game.magic_wall_active)
8506 element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8507 Store[newx][newy] = Store[x][y];
8509 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8511 Feld[x][y] = get_next_element(element);
8512 if (!game.magic_wall_active)
8513 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
8514 element = Feld[newx][newy] = Store[x][y];
8516 InitField(newx, newy, FALSE);
8518 else if (element == EL_AMOEBA_DROPPING)
8520 Feld[x][y] = get_next_element(element);
8521 element = Feld[newx][newy] = Store[x][y];
8523 else if (element == EL_SOKOBAN_OBJECT)
8526 Feld[x][y] = Back[x][y];
8528 if (Back[newx][newy])
8529 Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8531 Back[x][y] = Back[newx][newy] = 0;
8534 Store[x][y] = EL_EMPTY;
8539 MovDelay[newx][newy] = 0;
8541 if (CAN_CHANGE_OR_HAS_ACTION(element))
8543 // copy element change control values to new field
8544 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8545 ChangePage[newx][newy] = ChangePage[x][y];
8546 ChangeCount[newx][newy] = ChangeCount[x][y];
8547 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8550 CustomValue[newx][newy] = CustomValue[x][y];
8552 ChangeDelay[x][y] = 0;
8553 ChangePage[x][y] = -1;
8554 ChangeCount[x][y] = 0;
8555 ChangeEvent[x][y] = -1;
8557 CustomValue[x][y] = 0;
8559 // copy animation control values to new field
8560 GfxFrame[newx][newy] = GfxFrame[x][y];
8561 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8562 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8563 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8565 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8567 // some elements can leave other elements behind after moving
8568 if (ei->move_leave_element != EL_EMPTY &&
8569 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8570 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8572 int move_leave_element = ei->move_leave_element;
8574 // this makes it possible to leave the removed element again
8575 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8576 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8578 Feld[x][y] = move_leave_element;
8580 if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
8581 MovDir[x][y] = direction;
8583 InitField(x, y, FALSE);
8585 if (GFX_CRUMBLED(Feld[x][y]))
8586 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8588 if (ELEM_IS_PLAYER(move_leave_element))
8589 RelocatePlayer(x, y, move_leave_element);
8592 // do this after checking for left-behind element
8593 ResetGfxAnimation(x, y); // reset animation values for old field
8595 if (!CAN_MOVE(element) ||
8596 (CAN_FALL(element) && direction == MV_DOWN &&
8597 (element == EL_SPRING ||
8598 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8599 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8600 GfxDir[x][y] = MovDir[newx][newy] = 0;
8602 TEST_DrawLevelField(x, y);
8603 TEST_DrawLevelField(newx, newy);
8605 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8607 // prevent pushed element from moving on in pushed direction
8608 if (pushed_by_player && CAN_MOVE(element) &&
8609 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8610 !(element_info[element].move_pattern & direction))
8611 TurnRound(newx, newy);
8613 // prevent elements on conveyor belt from moving on in last direction
8614 if (pushed_by_conveyor && CAN_FALL(element) &&
8615 direction & MV_HORIZONTAL)
8616 MovDir[newx][newy] = 0;
8618 if (!pushed_by_player)
8620 int nextx = newx + dx, nexty = newy + dy;
8621 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8623 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8625 if (CAN_FALL(element) && direction == MV_DOWN)
8626 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8628 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8629 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8631 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8632 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8635 if (DONT_TOUCH(element)) // object may be nasty to player or others
8637 TestIfBadThingTouchesPlayer(newx, newy);
8638 TestIfBadThingTouchesFriend(newx, newy);
8640 if (!IS_CUSTOM_ELEMENT(element))
8641 TestIfBadThingTouchesOtherBadThing(newx, newy);
8643 else if (element == EL_PENGUIN)
8644 TestIfFriendTouchesBadThing(newx, newy);
8646 if (DONT_GET_HIT_BY(element))
8648 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8651 // give the player one last chance (one more frame) to move away
8652 if (CAN_FALL(element) && direction == MV_DOWN &&
8653 (last_line || (!IS_FREE(x, newy + 1) &&
8654 (!IS_PLAYER(x, newy + 1) ||
8655 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8658 if (pushed_by_player && !game.use_change_when_pushing_bug)
8660 int push_side = MV_DIR_OPPOSITE(direction);
8661 struct PlayerInfo *player = PLAYERINFO(x, y);
8663 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8664 player->index_bit, push_side);
8665 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8666 player->index_bit, push_side);
8669 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8670 MovDelay[newx][newy] = 1;
8672 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8674 TestIfElementTouchesCustomElement(x, y); // empty or new element
8675 TestIfElementHitsCustomElement(newx, newy, direction);
8676 TestIfPlayerTouchesCustomElement(newx, newy);
8677 TestIfElementTouchesCustomElement(newx, newy);
8679 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8680 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8681 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8682 MV_DIR_OPPOSITE(direction));
8685 int AmoebeNachbarNr(int ax, int ay)
8688 int element = Feld[ax][ay];
8690 static int xy[4][2] =
8698 for (i = 0; i < NUM_DIRECTIONS; i++)
8700 int x = ax + xy[i][0];
8701 int y = ay + xy[i][1];
8703 if (!IN_LEV_FIELD(x, y))
8706 if (Feld[x][y] == element && AmoebaNr[x][y] > 0)
8707 group_nr = AmoebaNr[x][y];
8713 static void AmoebenVereinigen(int ax, int ay)
8715 int i, x, y, xx, yy;
8716 int new_group_nr = AmoebaNr[ax][ay];
8717 static int xy[4][2] =
8725 if (new_group_nr == 0)
8728 for (i = 0; i < NUM_DIRECTIONS; i++)
8733 if (!IN_LEV_FIELD(x, y))
8736 if ((Feld[x][y] == EL_AMOEBA_FULL ||
8737 Feld[x][y] == EL_BD_AMOEBA ||
8738 Feld[x][y] == EL_AMOEBA_DEAD) &&
8739 AmoebaNr[x][y] != new_group_nr)
8741 int old_group_nr = AmoebaNr[x][y];
8743 if (old_group_nr == 0)
8746 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8747 AmoebaCnt[old_group_nr] = 0;
8748 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8749 AmoebaCnt2[old_group_nr] = 0;
8751 SCAN_PLAYFIELD(xx, yy)
8753 if (AmoebaNr[xx][yy] == old_group_nr)
8754 AmoebaNr[xx][yy] = new_group_nr;
8760 void AmoebeUmwandeln(int ax, int ay)
8764 if (Feld[ax][ay] == EL_AMOEBA_DEAD)
8766 int group_nr = AmoebaNr[ax][ay];
8771 printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay);
8772 printf("AmoebeUmwandeln(): This should never happen!\n");
8777 SCAN_PLAYFIELD(x, y)
8779 if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8782 Feld[x][y] = EL_AMOEBA_TO_DIAMOND;
8786 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8787 SND_AMOEBA_TURNING_TO_GEM :
8788 SND_AMOEBA_TURNING_TO_ROCK));
8793 static int xy[4][2] =
8801 for (i = 0; i < NUM_DIRECTIONS; i++)
8806 if (!IN_LEV_FIELD(x, y))
8809 if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND)
8811 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8812 SND_AMOEBA_TURNING_TO_GEM :
8813 SND_AMOEBA_TURNING_TO_ROCK));
8820 static void AmoebeUmwandelnBD(int ax, int ay, int new_element)
8823 int group_nr = AmoebaNr[ax][ay];
8824 boolean done = FALSE;
8829 printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay);
8830 printf("AmoebeUmwandelnBD(): This should never happen!\n");
8835 SCAN_PLAYFIELD(x, y)
8837 if (AmoebaNr[x][y] == group_nr &&
8838 (Feld[x][y] == EL_AMOEBA_DEAD ||
8839 Feld[x][y] == EL_BD_AMOEBA ||
8840 Feld[x][y] == EL_AMOEBA_GROWING))
8843 Feld[x][y] = new_element;
8844 InitField(x, y, FALSE);
8845 TEST_DrawLevelField(x, y);
8851 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8852 SND_BD_AMOEBA_TURNING_TO_ROCK :
8853 SND_BD_AMOEBA_TURNING_TO_GEM));
8856 static void AmoebeWaechst(int x, int y)
8858 static unsigned int sound_delay = 0;
8859 static unsigned int sound_delay_value = 0;
8861 if (!MovDelay[x][y]) // start new growing cycle
8865 if (DelayReached(&sound_delay, sound_delay_value))
8867 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8868 sound_delay_value = 30;
8872 if (MovDelay[x][y]) // wait some time before growing bigger
8875 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8877 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8878 6 - MovDelay[x][y]);
8880 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8883 if (!MovDelay[x][y])
8885 Feld[x][y] = Store[x][y];
8887 TEST_DrawLevelField(x, y);
8892 static void AmoebaDisappearing(int x, int y)
8894 static unsigned int sound_delay = 0;
8895 static unsigned int sound_delay_value = 0;
8897 if (!MovDelay[x][y]) // start new shrinking cycle
8901 if (DelayReached(&sound_delay, sound_delay_value))
8902 sound_delay_value = 30;
8905 if (MovDelay[x][y]) // wait some time before shrinking
8908 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8910 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8911 6 - MovDelay[x][y]);
8913 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8916 if (!MovDelay[x][y])
8918 Feld[x][y] = EL_EMPTY;
8919 TEST_DrawLevelField(x, y);
8921 // don't let mole enter this field in this cycle;
8922 // (give priority to objects falling to this field from above)
8928 static void AmoebeAbleger(int ax, int ay)
8931 int element = Feld[ax][ay];
8932 int graphic = el2img(element);
8933 int newax = ax, neway = ay;
8934 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8935 static int xy[4][2] =
8943 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8945 Feld[ax][ay] = EL_AMOEBA_DEAD;
8946 TEST_DrawLevelField(ax, ay);
8950 if (IS_ANIMATED(graphic))
8951 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8953 if (!MovDelay[ax][ay]) // start making new amoeba field
8954 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8956 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8959 if (MovDelay[ax][ay])
8963 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8966 int x = ax + xy[start][0];
8967 int y = ay + xy[start][1];
8969 if (!IN_LEV_FIELD(x, y))
8972 if (IS_FREE(x, y) ||
8973 CAN_GROW_INTO(Feld[x][y]) ||
8974 Feld[x][y] == EL_QUICKSAND_EMPTY ||
8975 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
8981 if (newax == ax && neway == ay)
8984 else // normal or "filled" (BD style) amoeba
8987 boolean waiting_for_player = FALSE;
8989 for (i = 0; i < NUM_DIRECTIONS; i++)
8991 int j = (start + i) % 4;
8992 int x = ax + xy[j][0];
8993 int y = ay + xy[j][1];
8995 if (!IN_LEV_FIELD(x, y))
8998 if (IS_FREE(x, y) ||
8999 CAN_GROW_INTO(Feld[x][y]) ||
9000 Feld[x][y] == EL_QUICKSAND_EMPTY ||
9001 Feld[x][y] == EL_QUICKSAND_FAST_EMPTY)
9007 else if (IS_PLAYER(x, y))
9008 waiting_for_player = TRUE;
9011 if (newax == ax && neway == ay) // amoeba cannot grow
9013 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9015 Feld[ax][ay] = EL_AMOEBA_DEAD;
9016 TEST_DrawLevelField(ax, ay);
9017 AmoebaCnt[AmoebaNr[ax][ay]]--;
9019 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9021 if (element == EL_AMOEBA_FULL)
9022 AmoebeUmwandeln(ax, ay);
9023 else if (element == EL_BD_AMOEBA)
9024 AmoebeUmwandelnBD(ax, ay, level.amoeba_content);
9029 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9031 // amoeba gets larger by growing in some direction
9033 int new_group_nr = AmoebaNr[ax][ay];
9036 if (new_group_nr == 0)
9038 printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway);
9039 printf("AmoebeAbleger(): This should never happen!\n");
9044 AmoebaNr[newax][neway] = new_group_nr;
9045 AmoebaCnt[new_group_nr]++;
9046 AmoebaCnt2[new_group_nr]++;
9048 // if amoeba touches other amoeba(s) after growing, unify them
9049 AmoebenVereinigen(newax, neway);
9051 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9053 AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK);
9059 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9060 (neway == lev_fieldy - 1 && newax != ax))
9062 Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9063 Store[newax][neway] = element;
9065 else if (neway == ay || element == EL_EMC_DRIPPER)
9067 Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9069 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9073 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9074 Feld[ax][ay] = EL_AMOEBA_DROPPING;
9075 Store[ax][ay] = EL_AMOEBA_DROP;
9076 ContinueMoving(ax, ay);
9080 TEST_DrawLevelField(newax, neway);
9083 static void Life(int ax, int ay)
9087 int element = Feld[ax][ay];
9088 int graphic = el2img(element);
9089 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9091 boolean changed = FALSE;
9093 if (IS_ANIMATED(graphic))
9094 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9099 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9100 MovDelay[ax][ay] = life_time;
9102 if (MovDelay[ax][ay]) // wait some time before next cycle
9105 if (MovDelay[ax][ay])
9109 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9111 int xx = ax+x1, yy = ay+y1;
9112 int old_element = Feld[xx][yy];
9113 int num_neighbours = 0;
9115 if (!IN_LEV_FIELD(xx, yy))
9118 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9120 int x = xx+x2, y = yy+y2;
9122 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9125 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9126 boolean is_neighbour = FALSE;
9128 if (level.use_life_bugs)
9130 (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9131 (IS_FREE(x, y) && Stop[x][y]));
9134 (Last[x][y] == element || is_player_cell);
9140 boolean is_free = FALSE;
9142 if (level.use_life_bugs)
9143 is_free = (IS_FREE(xx, yy));
9145 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9147 if (xx == ax && yy == ay) // field in the middle
9149 if (num_neighbours < life_parameter[0] ||
9150 num_neighbours > life_parameter[1])
9152 Feld[xx][yy] = EL_EMPTY;
9153 if (Feld[xx][yy] != old_element)
9154 TEST_DrawLevelField(xx, yy);
9155 Stop[xx][yy] = TRUE;
9159 else if (is_free || CAN_GROW_INTO(Feld[xx][yy]))
9160 { // free border field
9161 if (num_neighbours >= life_parameter[2] &&
9162 num_neighbours <= life_parameter[3])
9164 Feld[xx][yy] = element;
9165 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9166 if (Feld[xx][yy] != old_element)
9167 TEST_DrawLevelField(xx, yy);
9168 Stop[xx][yy] = TRUE;
9175 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9176 SND_GAME_OF_LIFE_GROWING);
9179 static void InitRobotWheel(int x, int y)
9181 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9184 static void RunRobotWheel(int x, int y)
9186 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9189 static void StopRobotWheel(int x, int y)
9191 if (game.robot_wheel_x == x &&
9192 game.robot_wheel_y == y)
9194 game.robot_wheel_x = -1;
9195 game.robot_wheel_y = -1;
9196 game.robot_wheel_active = FALSE;
9200 static void InitTimegateWheel(int x, int y)
9202 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9205 static void RunTimegateWheel(int x, int y)
9207 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9210 static void InitMagicBallDelay(int x, int y)
9212 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9215 static void ActivateMagicBall(int bx, int by)
9219 if (level.ball_random)
9221 int pos_border = RND(8); // select one of the eight border elements
9222 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9223 int xx = pos_content % 3;
9224 int yy = pos_content / 3;
9229 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9230 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9234 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9236 int xx = x - bx + 1;
9237 int yy = y - by + 1;
9239 if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY)
9240 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9244 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9247 static void CheckExit(int x, int y)
9249 if (game.gems_still_needed > 0 ||
9250 game.sokoban_fields_still_needed > 0 ||
9251 game.sokoban_objects_still_needed > 0 ||
9252 game.lights_still_needed > 0)
9254 int element = Feld[x][y];
9255 int graphic = el2img(element);
9257 if (IS_ANIMATED(graphic))
9258 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9263 // do not re-open exit door closed after last player
9264 if (game.all_players_gone)
9267 Feld[x][y] = EL_EXIT_OPENING;
9269 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9272 static void CheckExitEM(int x, int y)
9274 if (game.gems_still_needed > 0 ||
9275 game.sokoban_fields_still_needed > 0 ||
9276 game.sokoban_objects_still_needed > 0 ||
9277 game.lights_still_needed > 0)
9279 int element = Feld[x][y];
9280 int graphic = el2img(element);
9282 if (IS_ANIMATED(graphic))
9283 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9288 // do not re-open exit door closed after last player
9289 if (game.all_players_gone)
9292 Feld[x][y] = EL_EM_EXIT_OPENING;
9294 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9297 static void CheckExitSteel(int x, int y)
9299 if (game.gems_still_needed > 0 ||
9300 game.sokoban_fields_still_needed > 0 ||
9301 game.sokoban_objects_still_needed > 0 ||
9302 game.lights_still_needed > 0)
9304 int element = Feld[x][y];
9305 int graphic = el2img(element);
9307 if (IS_ANIMATED(graphic))
9308 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9313 // do not re-open exit door closed after last player
9314 if (game.all_players_gone)
9317 Feld[x][y] = EL_STEEL_EXIT_OPENING;
9319 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9322 static void CheckExitSteelEM(int x, int y)
9324 if (game.gems_still_needed > 0 ||
9325 game.sokoban_fields_still_needed > 0 ||
9326 game.sokoban_objects_still_needed > 0 ||
9327 game.lights_still_needed > 0)
9329 int element = Feld[x][y];
9330 int graphic = el2img(element);
9332 if (IS_ANIMATED(graphic))
9333 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9338 // do not re-open exit door closed after last player
9339 if (game.all_players_gone)
9342 Feld[x][y] = EL_EM_STEEL_EXIT_OPENING;
9344 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9347 static void CheckExitSP(int x, int y)
9349 if (game.gems_still_needed > 0)
9351 int element = Feld[x][y];
9352 int graphic = el2img(element);
9354 if (IS_ANIMATED(graphic))
9355 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9360 // do not re-open exit door closed after last player
9361 if (game.all_players_gone)
9364 Feld[x][y] = EL_SP_EXIT_OPENING;
9366 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9369 static void CloseAllOpenTimegates(void)
9373 SCAN_PLAYFIELD(x, y)
9375 int element = Feld[x][y];
9377 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9379 Feld[x][y] = EL_TIMEGATE_CLOSING;
9381 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9386 static void DrawTwinkleOnField(int x, int y)
9388 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9391 if (Feld[x][y] == EL_BD_DIAMOND)
9394 if (MovDelay[x][y] == 0) // next animation frame
9395 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9397 if (MovDelay[x][y] != 0) // wait some time before next frame
9401 DrawLevelElementAnimation(x, y, Feld[x][y]);
9403 if (MovDelay[x][y] != 0)
9405 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9406 10 - MovDelay[x][y]);
9408 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9413 static void MauerWaechst(int x, int y)
9417 if (!MovDelay[x][y]) // next animation frame
9418 MovDelay[x][y] = 3 * delay;
9420 if (MovDelay[x][y]) // wait some time before next frame
9424 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9426 int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]);
9427 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9429 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9432 if (!MovDelay[x][y])
9434 if (MovDir[x][y] == MV_LEFT)
9436 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y]))
9437 TEST_DrawLevelField(x - 1, y);
9439 else if (MovDir[x][y] == MV_RIGHT)
9441 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y]))
9442 TEST_DrawLevelField(x + 1, y);
9444 else if (MovDir[x][y] == MV_UP)
9446 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1]))
9447 TEST_DrawLevelField(x, y - 1);
9451 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1]))
9452 TEST_DrawLevelField(x, y + 1);
9455 Feld[x][y] = Store[x][y];
9457 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9458 TEST_DrawLevelField(x, y);
9463 static void MauerAbleger(int ax, int ay)
9465 int element = Feld[ax][ay];
9466 int graphic = el2img(element);
9467 boolean oben_frei = FALSE, unten_frei = FALSE;
9468 boolean links_frei = FALSE, rechts_frei = FALSE;
9469 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9470 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9471 boolean new_wall = FALSE;
9473 if (IS_ANIMATED(graphic))
9474 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9476 if (!MovDelay[ax][ay]) // start building new wall
9477 MovDelay[ax][ay] = 6;
9479 if (MovDelay[ax][ay]) // wait some time before building new wall
9482 if (MovDelay[ax][ay])
9486 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9488 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9490 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9492 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9495 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9496 element == EL_EXPANDABLE_WALL_ANY)
9500 Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9501 Store[ax][ay-1] = element;
9502 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9503 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9504 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9505 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9510 Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9511 Store[ax][ay+1] = element;
9512 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9513 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9514 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9515 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9520 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9521 element == EL_EXPANDABLE_WALL_ANY ||
9522 element == EL_EXPANDABLE_WALL ||
9523 element == EL_BD_EXPANDABLE_WALL)
9527 Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9528 Store[ax-1][ay] = element;
9529 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9530 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9531 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9532 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9538 Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9539 Store[ax+1][ay] = element;
9540 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9541 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9542 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9543 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9548 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9549 TEST_DrawLevelField(ax, ay);
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_WALL_HORIZONTAL ||
9562 element == EL_EXPANDABLE_WALL) &&
9563 ((links_massiv && rechts_massiv) ||
9564 element == EL_EXPANDABLE_WALL_VERTICAL))
9565 Feld[ax][ay] = EL_WALL;
9568 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9571 static void MauerAblegerStahl(int ax, int ay)
9573 int element = Feld[ax][ay];
9574 int graphic = el2img(element);
9575 boolean oben_frei = FALSE, unten_frei = FALSE;
9576 boolean links_frei = FALSE, rechts_frei = FALSE;
9577 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9578 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9579 boolean new_wall = FALSE;
9581 if (IS_ANIMATED(graphic))
9582 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9584 if (!MovDelay[ax][ay]) // start building new wall
9585 MovDelay[ax][ay] = 6;
9587 if (MovDelay[ax][ay]) // wait some time before building new wall
9590 if (MovDelay[ax][ay])
9594 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9596 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9598 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9600 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9603 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9604 element == EL_EXPANDABLE_STEELWALL_ANY)
9608 Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9609 Store[ax][ay-1] = element;
9610 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9611 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9612 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9613 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9618 Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9619 Store[ax][ay+1] = element;
9620 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9621 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9622 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9623 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9628 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9629 element == EL_EXPANDABLE_STEELWALL_ANY)
9633 Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9634 Store[ax-1][ay] = element;
9635 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9636 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9637 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9638 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9644 Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9645 Store[ax+1][ay] = element;
9646 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9647 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9648 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9649 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9654 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1]))
9656 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1]))
9657 unten_massiv = TRUE;
9658 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay]))
9659 links_massiv = TRUE;
9660 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay]))
9661 rechts_massiv = TRUE;
9663 if (((oben_massiv && unten_massiv) ||
9664 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9665 ((links_massiv && rechts_massiv) ||
9666 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9667 Feld[ax][ay] = EL_STEELWALL;
9670 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9673 static void CheckForDragon(int x, int y)
9676 boolean dragon_found = FALSE;
9677 static int xy[4][2] =
9685 for (i = 0; i < NUM_DIRECTIONS; i++)
9687 for (j = 0; j < 4; j++)
9689 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9691 if (IN_LEV_FIELD(xx, yy) &&
9692 (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON))
9694 if (Feld[xx][yy] == EL_DRAGON)
9695 dragon_found = TRUE;
9704 for (i = 0; i < NUM_DIRECTIONS; i++)
9706 for (j = 0; j < 3; j++)
9708 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9710 if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES)
9712 Feld[xx][yy] = EL_EMPTY;
9713 TEST_DrawLevelField(xx, yy);
9722 static void InitBuggyBase(int x, int y)
9724 int element = Feld[x][y];
9725 int activating_delay = FRAMES_PER_SECOND / 4;
9728 (element == EL_SP_BUGGY_BASE ?
9729 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9730 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9732 element == EL_SP_BUGGY_BASE_ACTIVE ?
9733 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9736 static void WarnBuggyBase(int x, int y)
9739 static int xy[4][2] =
9747 for (i = 0; i < NUM_DIRECTIONS; i++)
9749 int xx = x + xy[i][0];
9750 int yy = y + xy[i][1];
9752 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9754 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9761 static void InitTrap(int x, int y)
9763 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9766 static void ActivateTrap(int x, int y)
9768 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9771 static void ChangeActiveTrap(int x, int y)
9773 int graphic = IMG_TRAP_ACTIVE;
9775 // if new animation frame was drawn, correct crumbled sand border
9776 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9777 TEST_DrawLevelFieldCrumbled(x, y);
9780 static int getSpecialActionElement(int element, int number, int base_element)
9782 return (element != EL_EMPTY ? element :
9783 number != -1 ? base_element + number - 1 :
9787 static int getModifiedActionNumber(int value_old, int operator, int operand,
9788 int value_min, int value_max)
9790 int value_new = (operator == CA_MODE_SET ? operand :
9791 operator == CA_MODE_ADD ? value_old + operand :
9792 operator == CA_MODE_SUBTRACT ? value_old - operand :
9793 operator == CA_MODE_MULTIPLY ? value_old * operand :
9794 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9795 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9798 return (value_new < value_min ? value_min :
9799 value_new > value_max ? value_max :
9803 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9805 struct ElementInfo *ei = &element_info[element];
9806 struct ElementChangeInfo *change = &ei->change_page[page];
9807 int target_element = change->target_element;
9808 int action_type = change->action_type;
9809 int action_mode = change->action_mode;
9810 int action_arg = change->action_arg;
9811 int action_element = change->action_element;
9814 if (!change->has_action)
9817 // ---------- determine action paramater values -----------------------------
9819 int level_time_value =
9820 (level.time > 0 ? TimeLeft :
9823 int action_arg_element_raw =
9824 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9825 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9826 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9827 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9828 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9829 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9830 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9832 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9834 int action_arg_direction =
9835 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9836 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9837 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9838 change->actual_trigger_side :
9839 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9840 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9843 int action_arg_number_min =
9844 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9847 int action_arg_number_max =
9848 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9849 action_type == CA_SET_LEVEL_GEMS ? 999 :
9850 action_type == CA_SET_LEVEL_TIME ? 9999 :
9851 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9852 action_type == CA_SET_CE_VALUE ? 9999 :
9853 action_type == CA_SET_CE_SCORE ? 9999 :
9856 int action_arg_number_reset =
9857 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9858 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9859 action_type == CA_SET_LEVEL_TIME ? level.time :
9860 action_type == CA_SET_LEVEL_SCORE ? 0 :
9861 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9862 action_type == CA_SET_CE_SCORE ? 0 :
9865 int action_arg_number =
9866 (action_arg <= CA_ARG_MAX ? action_arg :
9867 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9868 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9869 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9870 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9871 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9872 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9873 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9874 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9875 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9876 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9877 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9878 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9879 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9880 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9881 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9882 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9883 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9884 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9885 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9886 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9887 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9890 int action_arg_number_old =
9891 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9892 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9893 action_type == CA_SET_LEVEL_SCORE ? game.score :
9894 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9895 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9898 int action_arg_number_new =
9899 getModifiedActionNumber(action_arg_number_old,
9900 action_mode, action_arg_number,
9901 action_arg_number_min, action_arg_number_max);
9903 int trigger_player_bits =
9904 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9905 change->actual_trigger_player_bits : change->trigger_player);
9907 int action_arg_player_bits =
9908 (action_arg >= CA_ARG_PLAYER_1 &&
9909 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9910 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9911 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9914 // ---------- execute action -----------------------------------------------
9916 switch (action_type)
9923 // ---------- level actions ----------------------------------------------
9925 case CA_RESTART_LEVEL:
9927 game.restart_level = TRUE;
9932 case CA_SHOW_ENVELOPE:
9934 int element = getSpecialActionElement(action_arg_element,
9935 action_arg_number, EL_ENVELOPE_1);
9937 if (IS_ENVELOPE(element))
9938 local_player->show_envelope = element;
9943 case CA_SET_LEVEL_TIME:
9945 if (level.time > 0) // only modify limited time value
9947 TimeLeft = action_arg_number_new;
9949 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9951 DisplayGameControlValues();
9953 if (!TimeLeft && setup.time_limit)
9954 for (i = 0; i < MAX_PLAYERS; i++)
9955 KillPlayer(&stored_player[i]);
9961 case CA_SET_LEVEL_SCORE:
9963 game.score = action_arg_number_new;
9965 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9967 DisplayGameControlValues();
9972 case CA_SET_LEVEL_GEMS:
9974 game.gems_still_needed = action_arg_number_new;
9976 game.snapshot.collected_item = TRUE;
9978 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9980 DisplayGameControlValues();
9985 case CA_SET_LEVEL_WIND:
9987 game.wind_direction = action_arg_direction;
9992 case CA_SET_LEVEL_RANDOM_SEED:
9994 // ensure that setting a new random seed while playing is predictable
9995 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10000 // ---------- player actions ---------------------------------------------
10002 case CA_MOVE_PLAYER:
10003 case CA_MOVE_PLAYER_NEW:
10005 // automatically move to the next field in specified direction
10006 for (i = 0; i < MAX_PLAYERS; i++)
10007 if (trigger_player_bits & (1 << i))
10008 if (action_type == CA_MOVE_PLAYER ||
10009 stored_player[i].MovPos == 0)
10010 stored_player[i].programmed_action = action_arg_direction;
10015 case CA_EXIT_PLAYER:
10017 for (i = 0; i < MAX_PLAYERS; i++)
10018 if (action_arg_player_bits & (1 << i))
10019 ExitPlayer(&stored_player[i]);
10021 if (game.players_still_needed == 0)
10027 case CA_KILL_PLAYER:
10029 for (i = 0; i < MAX_PLAYERS; i++)
10030 if (action_arg_player_bits & (1 << i))
10031 KillPlayer(&stored_player[i]);
10036 case CA_SET_PLAYER_KEYS:
10038 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10039 int element = getSpecialActionElement(action_arg_element,
10040 action_arg_number, EL_KEY_1);
10042 if (IS_KEY(element))
10044 for (i = 0; i < MAX_PLAYERS; i++)
10046 if (trigger_player_bits & (1 << i))
10048 stored_player[i].key[KEY_NR(element)] = key_state;
10050 DrawGameDoorValues();
10058 case CA_SET_PLAYER_SPEED:
10060 for (i = 0; i < MAX_PLAYERS; i++)
10062 if (trigger_player_bits & (1 << i))
10064 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10066 if (action_arg == CA_ARG_SPEED_FASTER &&
10067 stored_player[i].cannot_move)
10069 action_arg_number = STEPSIZE_VERY_SLOW;
10071 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10072 action_arg == CA_ARG_SPEED_FASTER)
10074 action_arg_number = 2;
10075 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10078 else if (action_arg == CA_ARG_NUMBER_RESET)
10080 action_arg_number = level.initial_player_stepsize[i];
10084 getModifiedActionNumber(move_stepsize,
10087 action_arg_number_min,
10088 action_arg_number_max);
10090 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10097 case CA_SET_PLAYER_SHIELD:
10099 for (i = 0; i < MAX_PLAYERS; i++)
10101 if (trigger_player_bits & (1 << i))
10103 if (action_arg == CA_ARG_SHIELD_OFF)
10105 stored_player[i].shield_normal_time_left = 0;
10106 stored_player[i].shield_deadly_time_left = 0;
10108 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10110 stored_player[i].shield_normal_time_left = 999999;
10112 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10114 stored_player[i].shield_normal_time_left = 999999;
10115 stored_player[i].shield_deadly_time_left = 999999;
10123 case CA_SET_PLAYER_GRAVITY:
10125 for (i = 0; i < MAX_PLAYERS; i++)
10127 if (trigger_player_bits & (1 << i))
10129 stored_player[i].gravity =
10130 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10131 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10132 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10133 stored_player[i].gravity);
10140 case CA_SET_PLAYER_ARTWORK:
10142 for (i = 0; i < MAX_PLAYERS; i++)
10144 if (trigger_player_bits & (1 << i))
10146 int artwork_element = action_arg_element;
10148 if (action_arg == CA_ARG_ELEMENT_RESET)
10150 (level.use_artwork_element[i] ? level.artwork_element[i] :
10151 stored_player[i].element_nr);
10153 if (stored_player[i].artwork_element != artwork_element)
10154 stored_player[i].Frame = 0;
10156 stored_player[i].artwork_element = artwork_element;
10158 SetPlayerWaiting(&stored_player[i], FALSE);
10160 // set number of special actions for bored and sleeping animation
10161 stored_player[i].num_special_action_bored =
10162 get_num_special_action(artwork_element,
10163 ACTION_BORING_1, ACTION_BORING_LAST);
10164 stored_player[i].num_special_action_sleeping =
10165 get_num_special_action(artwork_element,
10166 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10173 case CA_SET_PLAYER_INVENTORY:
10175 for (i = 0; i < MAX_PLAYERS; i++)
10177 struct PlayerInfo *player = &stored_player[i];
10180 if (trigger_player_bits & (1 << i))
10182 int inventory_element = action_arg_element;
10184 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10185 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10186 action_arg == CA_ARG_ELEMENT_ACTION)
10188 int element = inventory_element;
10189 int collect_count = element_info[element].collect_count_initial;
10191 if (!IS_CUSTOM_ELEMENT(element))
10194 if (collect_count == 0)
10195 player->inventory_infinite_element = element;
10197 for (k = 0; k < collect_count; k++)
10198 if (player->inventory_size < MAX_INVENTORY_SIZE)
10199 player->inventory_element[player->inventory_size++] =
10202 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10203 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10204 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10206 if (player->inventory_infinite_element != EL_UNDEFINED &&
10207 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10208 action_arg_element_raw))
10209 player->inventory_infinite_element = EL_UNDEFINED;
10211 for (k = 0, j = 0; j < player->inventory_size; j++)
10213 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10214 action_arg_element_raw))
10215 player->inventory_element[k++] = player->inventory_element[j];
10218 player->inventory_size = k;
10220 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10222 if (player->inventory_size > 0)
10224 for (j = 0; j < player->inventory_size - 1; j++)
10225 player->inventory_element[j] = player->inventory_element[j + 1];
10227 player->inventory_size--;
10230 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10232 if (player->inventory_size > 0)
10233 player->inventory_size--;
10235 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10237 player->inventory_infinite_element = EL_UNDEFINED;
10238 player->inventory_size = 0;
10240 else if (action_arg == CA_ARG_INVENTORY_RESET)
10242 player->inventory_infinite_element = EL_UNDEFINED;
10243 player->inventory_size = 0;
10245 if (level.use_initial_inventory[i])
10247 for (j = 0; j < level.initial_inventory_size[i]; j++)
10249 int element = level.initial_inventory_content[i][j];
10250 int collect_count = element_info[element].collect_count_initial;
10252 if (!IS_CUSTOM_ELEMENT(element))
10255 if (collect_count == 0)
10256 player->inventory_infinite_element = element;
10258 for (k = 0; k < collect_count; k++)
10259 if (player->inventory_size < MAX_INVENTORY_SIZE)
10260 player->inventory_element[player->inventory_size++] =
10271 // ---------- CE actions -------------------------------------------------
10273 case CA_SET_CE_VALUE:
10275 int last_ce_value = CustomValue[x][y];
10277 CustomValue[x][y] = action_arg_number_new;
10279 if (CustomValue[x][y] != last_ce_value)
10281 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10282 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10284 if (CustomValue[x][y] == 0)
10286 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10287 ChangeCount[x][y] = 0; // allow at least one more change
10289 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10290 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10297 case CA_SET_CE_SCORE:
10299 int last_ce_score = ei->collect_score;
10301 ei->collect_score = action_arg_number_new;
10303 if (ei->collect_score != last_ce_score)
10305 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10306 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10308 if (ei->collect_score == 0)
10312 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10313 ChangeCount[x][y] = 0; // allow at least one more change
10315 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10316 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10319 This is a very special case that seems to be a mixture between
10320 CheckElementChange() and CheckTriggeredElementChange(): while
10321 the first one only affects single elements that are triggered
10322 directly, the second one affects multiple elements in the playfield
10323 that are triggered indirectly by another element. This is a third
10324 case: Changing the CE score always affects multiple identical CEs,
10325 so every affected CE must be checked, not only the single CE for
10326 which the CE score was changed in the first place (as every instance
10327 of that CE shares the same CE score, and therefore also can change)!
10329 SCAN_PLAYFIELD(xx, yy)
10331 if (Feld[xx][yy] == element)
10332 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10333 CE_SCORE_GETS_ZERO);
10341 case CA_SET_CE_ARTWORK:
10343 int artwork_element = action_arg_element;
10344 boolean reset_frame = FALSE;
10347 if (action_arg == CA_ARG_ELEMENT_RESET)
10348 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10351 if (ei->gfx_element != artwork_element)
10352 reset_frame = TRUE;
10354 ei->gfx_element = artwork_element;
10356 SCAN_PLAYFIELD(xx, yy)
10358 if (Feld[xx][yy] == element)
10362 ResetGfxAnimation(xx, yy);
10363 ResetRandomAnimationValue(xx, yy);
10366 TEST_DrawLevelField(xx, yy);
10373 // ---------- engine actions ---------------------------------------------
10375 case CA_SET_ENGINE_SCAN_MODE:
10377 InitPlayfieldScanMode(action_arg);
10387 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10389 int old_element = Feld[x][y];
10390 int new_element = GetElementFromGroupElement(element);
10391 int previous_move_direction = MovDir[x][y];
10392 int last_ce_value = CustomValue[x][y];
10393 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10394 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10395 boolean add_player_onto_element = (new_element_is_player &&
10396 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10397 IS_WALKABLE(old_element));
10399 if (!add_player_onto_element)
10401 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10402 RemoveMovingField(x, y);
10406 Feld[x][y] = new_element;
10408 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10409 MovDir[x][y] = previous_move_direction;
10411 if (element_info[new_element].use_last_ce_value)
10412 CustomValue[x][y] = last_ce_value;
10414 InitField_WithBug1(x, y, FALSE);
10416 new_element = Feld[x][y]; // element may have changed
10418 ResetGfxAnimation(x, y);
10419 ResetRandomAnimationValue(x, y);
10421 TEST_DrawLevelField(x, y);
10423 if (GFX_CRUMBLED(new_element))
10424 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10427 // check if element under the player changes from accessible to unaccessible
10428 // (needed for special case of dropping element which then changes)
10429 // (must be checked after creating new element for walkable group elements)
10430 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10431 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10438 // "ChangeCount" not set yet to allow "entered by player" change one time
10439 if (new_element_is_player)
10440 RelocatePlayer(x, y, new_element);
10443 ChangeCount[x][y]++; // count number of changes in the same frame
10445 TestIfBadThingTouchesPlayer(x, y);
10446 TestIfPlayerTouchesCustomElement(x, y);
10447 TestIfElementTouchesCustomElement(x, y);
10450 static void CreateField(int x, int y, int element)
10452 CreateFieldExt(x, y, element, FALSE);
10455 static void CreateElementFromChange(int x, int y, int element)
10457 element = GET_VALID_RUNTIME_ELEMENT(element);
10459 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10461 int old_element = Feld[x][y];
10463 // prevent changed element from moving in same engine frame
10464 // unless both old and new element can either fall or move
10465 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10466 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10470 CreateFieldExt(x, y, element, TRUE);
10473 static boolean ChangeElement(int x, int y, int element, int page)
10475 struct ElementInfo *ei = &element_info[element];
10476 struct ElementChangeInfo *change = &ei->change_page[page];
10477 int ce_value = CustomValue[x][y];
10478 int ce_score = ei->collect_score;
10479 int target_element;
10480 int old_element = Feld[x][y];
10482 // always use default change event to prevent running into a loop
10483 if (ChangeEvent[x][y] == -1)
10484 ChangeEvent[x][y] = CE_DELAY;
10486 if (ChangeEvent[x][y] == CE_DELAY)
10488 // reset actual trigger element, trigger player and action element
10489 change->actual_trigger_element = EL_EMPTY;
10490 change->actual_trigger_player = EL_EMPTY;
10491 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10492 change->actual_trigger_side = CH_SIDE_NONE;
10493 change->actual_trigger_ce_value = 0;
10494 change->actual_trigger_ce_score = 0;
10497 // do not change elements more than a specified maximum number of changes
10498 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10501 ChangeCount[x][y]++; // count number of changes in the same frame
10503 if (change->explode)
10510 if (change->use_target_content)
10512 boolean complete_replace = TRUE;
10513 boolean can_replace[3][3];
10516 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10519 boolean is_walkable;
10520 boolean is_diggable;
10521 boolean is_collectible;
10522 boolean is_removable;
10523 boolean is_destructible;
10524 int ex = x + xx - 1;
10525 int ey = y + yy - 1;
10526 int content_element = change->target_content.e[xx][yy];
10529 can_replace[xx][yy] = TRUE;
10531 if (ex == x && ey == y) // do not check changing element itself
10534 if (content_element == EL_EMPTY_SPACE)
10536 can_replace[xx][yy] = FALSE; // do not replace border with space
10541 if (!IN_LEV_FIELD(ex, ey))
10543 can_replace[xx][yy] = FALSE;
10544 complete_replace = FALSE;
10551 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10552 e = MovingOrBlocked2Element(ex, ey);
10554 is_empty = (IS_FREE(ex, ey) ||
10555 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10557 is_walkable = (is_empty || IS_WALKABLE(e));
10558 is_diggable = (is_empty || IS_DIGGABLE(e));
10559 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10560 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10561 is_removable = (is_diggable || is_collectible);
10563 can_replace[xx][yy] =
10564 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10565 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10566 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10567 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10568 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10569 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10570 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10572 if (!can_replace[xx][yy])
10573 complete_replace = FALSE;
10576 if (!change->only_if_complete || complete_replace)
10578 boolean something_has_changed = FALSE;
10580 if (change->only_if_complete && change->use_random_replace &&
10581 RND(100) < change->random_percentage)
10584 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10586 int ex = x + xx - 1;
10587 int ey = y + yy - 1;
10588 int content_element;
10590 if (can_replace[xx][yy] && (!change->use_random_replace ||
10591 RND(100) < change->random_percentage))
10593 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10594 RemoveMovingField(ex, ey);
10596 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10598 content_element = change->target_content.e[xx][yy];
10599 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10600 ce_value, ce_score);
10602 CreateElementFromChange(ex, ey, target_element);
10604 something_has_changed = TRUE;
10606 // for symmetry reasons, freeze newly created border elements
10607 if (ex != x || ey != y)
10608 Stop[ex][ey] = TRUE; // no more moving in this frame
10612 if (something_has_changed)
10614 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10615 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10621 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10622 ce_value, ce_score);
10624 if (element == EL_DIAGONAL_GROWING ||
10625 element == EL_DIAGONAL_SHRINKING)
10627 target_element = Store[x][y];
10629 Store[x][y] = EL_EMPTY;
10632 CreateElementFromChange(x, y, target_element);
10634 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10635 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10638 // this uses direct change before indirect change
10639 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10644 static void HandleElementChange(int x, int y, int page)
10646 int element = MovingOrBlocked2Element(x, y);
10647 struct ElementInfo *ei = &element_info[element];
10648 struct ElementChangeInfo *change = &ei->change_page[page];
10649 boolean handle_action_before_change = FALSE;
10652 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10653 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10656 printf("HandleElementChange(): %d,%d: element = %d ('%s')\n",
10657 x, y, element, element_info[element].token_name);
10658 printf("HandleElementChange(): This should never happen!\n");
10663 // this can happen with classic bombs on walkable, changing elements
10664 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10669 if (ChangeDelay[x][y] == 0) // initialize element change
10671 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10673 if (change->can_change)
10675 // !!! not clear why graphic animation should be reset at all here !!!
10676 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10677 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10680 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10682 When using an animation frame delay of 1 (this only happens with
10683 "sp_zonk.moving.left/right" in the classic graphics), the default
10684 (non-moving) animation shows wrong animation frames (while the
10685 moving animation, like "sp_zonk.moving.left/right", is correct,
10686 so this graphical bug never shows up with the classic graphics).
10687 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10688 be drawn instead of the correct frames 0,1,2,3. This is caused by
10689 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10690 an element change: First when the change delay ("ChangeDelay[][]")
10691 counter has reached zero after decrementing, then a second time in
10692 the next frame (after "GfxFrame[][]" was already incremented) when
10693 "ChangeDelay[][]" is reset to the initial delay value again.
10695 This causes frame 0 to be drawn twice, while the last frame won't
10696 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10698 As some animations may already be cleverly designed around this bug
10699 (at least the "Snake Bite" snake tail animation does this), it cannot
10700 simply be fixed here without breaking such existing animations.
10701 Unfortunately, it cannot easily be detected if a graphics set was
10702 designed "before" or "after" the bug was fixed. As a workaround,
10703 a new graphics set option "game.graphics_engine_version" was added
10704 to be able to specify the game's major release version for which the
10705 graphics set was designed, which can then be used to decide if the
10706 bugfix should be used (version 4 and above) or not (version 3 or
10707 below, or if no version was specified at all, as with old sets).
10709 (The wrong/fixed animation frames can be tested with the test level set
10710 "test_gfxframe" and level "000", which contains a specially prepared
10711 custom element at level position (x/y) == (11/9) which uses the zonk
10712 animation mentioned above. Using "game.graphics_engine_version: 4"
10713 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10714 This can also be seen from the debug output for this test element.)
10717 // when a custom element is about to change (for example by change delay),
10718 // do not reset graphic animation when the custom element is moving
10719 if (game.graphics_engine_version < 4 &&
10722 ResetGfxAnimation(x, y);
10723 ResetRandomAnimationValue(x, y);
10726 if (change->pre_change_function)
10727 change->pre_change_function(x, y);
10731 ChangeDelay[x][y]--;
10733 if (ChangeDelay[x][y] != 0) // continue element change
10735 if (change->can_change)
10737 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10739 if (IS_ANIMATED(graphic))
10740 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10742 if (change->change_function)
10743 change->change_function(x, y);
10746 else // finish element change
10748 if (ChangePage[x][y] != -1) // remember page from delayed change
10750 page = ChangePage[x][y];
10751 ChangePage[x][y] = -1;
10753 change = &ei->change_page[page];
10756 if (IS_MOVING(x, y)) // never change a running system ;-)
10758 ChangeDelay[x][y] = 1; // try change after next move step
10759 ChangePage[x][y] = page; // remember page to use for change
10764 // special case: set new level random seed before changing element
10765 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10766 handle_action_before_change = TRUE;
10768 if (change->has_action && handle_action_before_change)
10769 ExecuteCustomElementAction(x, y, element, page);
10771 if (change->can_change)
10773 if (ChangeElement(x, y, element, page))
10775 if (change->post_change_function)
10776 change->post_change_function(x, y);
10780 if (change->has_action && !handle_action_before_change)
10781 ExecuteCustomElementAction(x, y, element, page);
10785 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10786 int trigger_element,
10788 int trigger_player,
10792 boolean change_done_any = FALSE;
10793 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10796 if (!(trigger_events[trigger_element][trigger_event]))
10799 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10801 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10803 int element = EL_CUSTOM_START + i;
10804 boolean change_done = FALSE;
10807 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10808 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10811 for (p = 0; p < element_info[element].num_change_pages; p++)
10813 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10815 if (change->can_change_or_has_action &&
10816 change->has_event[trigger_event] &&
10817 change->trigger_side & trigger_side &&
10818 change->trigger_player & trigger_player &&
10819 change->trigger_page & trigger_page_bits &&
10820 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10822 change->actual_trigger_element = trigger_element;
10823 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10824 change->actual_trigger_player_bits = trigger_player;
10825 change->actual_trigger_side = trigger_side;
10826 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10827 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10829 if ((change->can_change && !change_done) || change->has_action)
10833 SCAN_PLAYFIELD(x, y)
10835 if (Feld[x][y] == element)
10837 if (change->can_change && !change_done)
10839 // if element already changed in this frame, not only prevent
10840 // another element change (checked in ChangeElement()), but
10841 // also prevent additional element actions for this element
10843 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10844 !level.use_action_after_change_bug)
10847 ChangeDelay[x][y] = 1;
10848 ChangeEvent[x][y] = trigger_event;
10850 HandleElementChange(x, y, p);
10852 else if (change->has_action)
10854 // if element already changed in this frame, not only prevent
10855 // another element change (checked in ChangeElement()), but
10856 // also prevent additional element actions for this element
10858 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10859 !level.use_action_after_change_bug)
10862 ExecuteCustomElementAction(x, y, element, p);
10863 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10868 if (change->can_change)
10870 change_done = TRUE;
10871 change_done_any = TRUE;
10878 RECURSION_LOOP_DETECTION_END();
10880 return change_done_any;
10883 static boolean CheckElementChangeExt(int x, int y,
10885 int trigger_element,
10887 int trigger_player,
10890 boolean change_done = FALSE;
10893 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10894 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10897 if (Feld[x][y] == EL_BLOCKED)
10899 Blocked2Moving(x, y, &x, &y);
10900 element = Feld[x][y];
10903 // check if element has already changed or is about to change after moving
10904 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10905 Feld[x][y] != element) ||
10907 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10908 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10909 ChangePage[x][y] != -1)))
10912 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10914 for (p = 0; p < element_info[element].num_change_pages; p++)
10916 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10918 /* check trigger element for all events where the element that is checked
10919 for changing interacts with a directly adjacent element -- this is
10920 different to element changes that affect other elements to change on the
10921 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10922 boolean check_trigger_element =
10923 (trigger_event == CE_TOUCHING_X ||
10924 trigger_event == CE_HITTING_X ||
10925 trigger_event == CE_HIT_BY_X ||
10926 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10928 if (change->can_change_or_has_action &&
10929 change->has_event[trigger_event] &&
10930 change->trigger_side & trigger_side &&
10931 change->trigger_player & trigger_player &&
10932 (!check_trigger_element ||
10933 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10935 change->actual_trigger_element = trigger_element;
10936 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10937 change->actual_trigger_player_bits = trigger_player;
10938 change->actual_trigger_side = trigger_side;
10939 change->actual_trigger_ce_value = CustomValue[x][y];
10940 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10942 // special case: trigger element not at (x,y) position for some events
10943 if (check_trigger_element)
10955 { 0, 0 }, { 0, 0 }, { 0, 0 },
10959 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10960 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10962 change->actual_trigger_ce_value = CustomValue[xx][yy];
10963 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10966 if (change->can_change && !change_done)
10968 ChangeDelay[x][y] = 1;
10969 ChangeEvent[x][y] = trigger_event;
10971 HandleElementChange(x, y, p);
10973 change_done = TRUE;
10975 else if (change->has_action)
10977 ExecuteCustomElementAction(x, y, element, p);
10978 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10983 RECURSION_LOOP_DETECTION_END();
10985 return change_done;
10988 static void PlayPlayerSound(struct PlayerInfo *player)
10990 int jx = player->jx, jy = player->jy;
10991 int sound_element = player->artwork_element;
10992 int last_action = player->last_action_waiting;
10993 int action = player->action_waiting;
10995 if (player->is_waiting)
10997 if (action != last_action)
10998 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11000 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11004 if (action != last_action)
11005 StopSound(element_info[sound_element].sound[last_action]);
11007 if (last_action == ACTION_SLEEPING)
11008 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11012 static void PlayAllPlayersSound(void)
11016 for (i = 0; i < MAX_PLAYERS; i++)
11017 if (stored_player[i].active)
11018 PlayPlayerSound(&stored_player[i]);
11021 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11023 boolean last_waiting = player->is_waiting;
11024 int move_dir = player->MovDir;
11026 player->dir_waiting = move_dir;
11027 player->last_action_waiting = player->action_waiting;
11031 if (!last_waiting) // not waiting -> waiting
11033 player->is_waiting = TRUE;
11035 player->frame_counter_bored =
11037 game.player_boring_delay_fixed +
11038 GetSimpleRandom(game.player_boring_delay_random);
11039 player->frame_counter_sleeping =
11041 game.player_sleeping_delay_fixed +
11042 GetSimpleRandom(game.player_sleeping_delay_random);
11044 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11047 if (game.player_sleeping_delay_fixed +
11048 game.player_sleeping_delay_random > 0 &&
11049 player->anim_delay_counter == 0 &&
11050 player->post_delay_counter == 0 &&
11051 FrameCounter >= player->frame_counter_sleeping)
11052 player->is_sleeping = TRUE;
11053 else if (game.player_boring_delay_fixed +
11054 game.player_boring_delay_random > 0 &&
11055 FrameCounter >= player->frame_counter_bored)
11056 player->is_bored = TRUE;
11058 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11059 player->is_bored ? ACTION_BORING :
11062 if (player->is_sleeping && player->use_murphy)
11064 // special case for sleeping Murphy when leaning against non-free tile
11066 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11067 (Feld[player->jx - 1][player->jy] != EL_EMPTY &&
11068 !IS_MOVING(player->jx - 1, player->jy)))
11069 move_dir = MV_LEFT;
11070 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11071 (Feld[player->jx + 1][player->jy] != EL_EMPTY &&
11072 !IS_MOVING(player->jx + 1, player->jy)))
11073 move_dir = MV_RIGHT;
11075 player->is_sleeping = FALSE;
11077 player->dir_waiting = move_dir;
11080 if (player->is_sleeping)
11082 if (player->num_special_action_sleeping > 0)
11084 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11086 int last_special_action = player->special_action_sleeping;
11087 int num_special_action = player->num_special_action_sleeping;
11088 int special_action =
11089 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11090 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11091 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11092 last_special_action + 1 : ACTION_SLEEPING);
11093 int special_graphic =
11094 el_act_dir2img(player->artwork_element, special_action, move_dir);
11096 player->anim_delay_counter =
11097 graphic_info[special_graphic].anim_delay_fixed +
11098 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11099 player->post_delay_counter =
11100 graphic_info[special_graphic].post_delay_fixed +
11101 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11103 player->special_action_sleeping = special_action;
11106 if (player->anim_delay_counter > 0)
11108 player->action_waiting = player->special_action_sleeping;
11109 player->anim_delay_counter--;
11111 else if (player->post_delay_counter > 0)
11113 player->post_delay_counter--;
11117 else if (player->is_bored)
11119 if (player->num_special_action_bored > 0)
11121 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11123 int special_action =
11124 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11125 int special_graphic =
11126 el_act_dir2img(player->artwork_element, special_action, move_dir);
11128 player->anim_delay_counter =
11129 graphic_info[special_graphic].anim_delay_fixed +
11130 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11131 player->post_delay_counter =
11132 graphic_info[special_graphic].post_delay_fixed +
11133 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11135 player->special_action_bored = special_action;
11138 if (player->anim_delay_counter > 0)
11140 player->action_waiting = player->special_action_bored;
11141 player->anim_delay_counter--;
11143 else if (player->post_delay_counter > 0)
11145 player->post_delay_counter--;
11150 else if (last_waiting) // waiting -> not waiting
11152 player->is_waiting = FALSE;
11153 player->is_bored = FALSE;
11154 player->is_sleeping = FALSE;
11156 player->frame_counter_bored = -1;
11157 player->frame_counter_sleeping = -1;
11159 player->anim_delay_counter = 0;
11160 player->post_delay_counter = 0;
11162 player->dir_waiting = player->MovDir;
11163 player->action_waiting = ACTION_DEFAULT;
11165 player->special_action_bored = ACTION_DEFAULT;
11166 player->special_action_sleeping = ACTION_DEFAULT;
11170 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11172 if ((!player->is_moving && player->was_moving) ||
11173 (player->MovPos == 0 && player->was_moving) ||
11174 (player->is_snapping && !player->was_snapping) ||
11175 (player->is_dropping && !player->was_dropping))
11177 if (!CheckSaveEngineSnapshotToList())
11180 player->was_moving = FALSE;
11181 player->was_snapping = TRUE;
11182 player->was_dropping = TRUE;
11186 if (player->is_moving)
11187 player->was_moving = TRUE;
11189 if (!player->is_snapping)
11190 player->was_snapping = FALSE;
11192 if (!player->is_dropping)
11193 player->was_dropping = FALSE;
11197 static void CheckSingleStepMode(struct PlayerInfo *player)
11199 if (tape.single_step && tape.recording && !tape.pausing)
11201 /* as it is called "single step mode", just return to pause mode when the
11202 player stopped moving after one tile (or never starts moving at all) */
11203 if (!player->is_moving &&
11204 !player->is_pushing &&
11205 !player->is_dropping_pressed)
11206 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11209 CheckSaveEngineSnapshot(player);
11212 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11214 int left = player_action & JOY_LEFT;
11215 int right = player_action & JOY_RIGHT;
11216 int up = player_action & JOY_UP;
11217 int down = player_action & JOY_DOWN;
11218 int button1 = player_action & JOY_BUTTON_1;
11219 int button2 = player_action & JOY_BUTTON_2;
11220 int dx = (left ? -1 : right ? 1 : 0);
11221 int dy = (up ? -1 : down ? 1 : 0);
11223 if (!player->active || tape.pausing)
11229 SnapField(player, dx, dy);
11233 DropElement(player);
11235 MovePlayer(player, dx, dy);
11238 CheckSingleStepMode(player);
11240 SetPlayerWaiting(player, FALSE);
11242 return player_action;
11246 // no actions for this player (no input at player's configured device)
11248 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11249 SnapField(player, 0, 0);
11250 CheckGravityMovementWhenNotMoving(player);
11252 if (player->MovPos == 0)
11253 SetPlayerWaiting(player, TRUE);
11255 if (player->MovPos == 0) // needed for tape.playing
11256 player->is_moving = FALSE;
11258 player->is_dropping = FALSE;
11259 player->is_dropping_pressed = FALSE;
11260 player->drop_pressed_delay = 0;
11262 CheckSingleStepMode(player);
11268 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11271 if (!tape.use_mouse_actions)
11274 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11275 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11276 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11279 static void SetTapeActionFromMouseAction(byte *tape_action,
11280 struct MouseActionInfo *mouse_action)
11282 if (!tape.use_mouse_actions)
11285 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11286 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11287 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11290 static void CheckLevelSolved(void)
11292 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11294 if (game_em.level_solved &&
11295 !game_em.game_over) // game won
11299 game_em.game_over = TRUE;
11301 game.all_players_gone = TRUE;
11304 if (game_em.game_over) // game lost
11305 game.all_players_gone = TRUE;
11307 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11309 if (game_sp.level_solved &&
11310 !game_sp.game_over) // game won
11314 game_sp.game_over = TRUE;
11316 game.all_players_gone = TRUE;
11319 if (game_sp.game_over) // game lost
11320 game.all_players_gone = TRUE;
11322 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11324 if (game_mm.level_solved &&
11325 !game_mm.game_over) // game won
11329 game_mm.game_over = TRUE;
11331 game.all_players_gone = TRUE;
11334 if (game_mm.game_over) // game lost
11335 game.all_players_gone = TRUE;
11339 static void CheckLevelTime(void)
11343 if (TimeFrames >= FRAMES_PER_SECOND)
11348 for (i = 0; i < MAX_PLAYERS; i++)
11350 struct PlayerInfo *player = &stored_player[i];
11352 if (SHIELD_ON(player))
11354 player->shield_normal_time_left--;
11356 if (player->shield_deadly_time_left > 0)
11357 player->shield_deadly_time_left--;
11361 if (!game.LevelSolved && !level.use_step_counter)
11369 if (TimeLeft <= 10 && setup.time_limit)
11370 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11372 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11373 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11375 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11377 if (!TimeLeft && setup.time_limit)
11379 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11380 game_em.lev->killed_out_of_time = TRUE;
11382 for (i = 0; i < MAX_PLAYERS; i++)
11383 KillPlayer(&stored_player[i]);
11386 else if (game.no_time_limit && !game.all_players_gone)
11388 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11391 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11394 if (tape.recording || tape.playing)
11395 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11398 if (tape.recording || tape.playing)
11399 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11401 UpdateAndDisplayGameControlValues();
11404 void AdvanceFrameAndPlayerCounters(int player_nr)
11408 // advance frame counters (global frame counter and time frame counter)
11412 // advance player counters (counters for move delay, move animation etc.)
11413 for (i = 0; i < MAX_PLAYERS; i++)
11415 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11416 int move_delay_value = stored_player[i].move_delay_value;
11417 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11419 if (!advance_player_counters) // not all players may be affected
11422 if (move_frames == 0) // less than one move per game frame
11424 int stepsize = TILEX / move_delay_value;
11425 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11426 int count = (stored_player[i].is_moving ?
11427 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11429 if (count % delay == 0)
11433 stored_player[i].Frame += move_frames;
11435 if (stored_player[i].MovPos != 0)
11436 stored_player[i].StepFrame += move_frames;
11438 if (stored_player[i].move_delay > 0)
11439 stored_player[i].move_delay--;
11441 // due to bugs in previous versions, counter must count up, not down
11442 if (stored_player[i].push_delay != -1)
11443 stored_player[i].push_delay++;
11445 if (stored_player[i].drop_delay > 0)
11446 stored_player[i].drop_delay--;
11448 if (stored_player[i].is_dropping_pressed)
11449 stored_player[i].drop_pressed_delay++;
11453 void StartGameActions(boolean init_network_game, boolean record_tape,
11456 unsigned int new_random_seed = InitRND(random_seed);
11459 TapeStartRecording(new_random_seed);
11461 if (init_network_game)
11463 SendToServer_LevelFile();
11464 SendToServer_StartPlaying();
11472 static void GameActionsExt(void)
11475 static unsigned int game_frame_delay = 0;
11477 unsigned int game_frame_delay_value;
11478 byte *recorded_player_action;
11479 byte summarized_player_action = 0;
11480 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11483 // detect endless loops, caused by custom element programming
11484 if (recursion_loop_detected && recursion_loop_depth == 0)
11486 char *message = getStringCat3("Internal Error! Element ",
11487 EL_NAME(recursion_loop_element),
11488 " caused endless loop! Quit the game?");
11490 Error(ERR_WARN, "element '%s' caused endless loop in game engine",
11491 EL_NAME(recursion_loop_element));
11493 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11495 recursion_loop_detected = FALSE; // if game should be continued
11502 if (game.restart_level)
11503 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11505 CheckLevelSolved();
11507 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11510 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11513 if (game_status != GAME_MODE_PLAYING) // status might have changed
11516 game_frame_delay_value =
11517 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11519 if (tape.playing && tape.warp_forward && !tape.pausing)
11520 game_frame_delay_value = 0;
11522 SetVideoFrameDelay(game_frame_delay_value);
11524 // (de)activate virtual buttons depending on current game status
11525 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11527 if (game.all_players_gone) // if no players there to be controlled anymore
11528 SetOverlayActive(FALSE);
11529 else if (!tape.playing) // if game continues after tape stopped playing
11530 SetOverlayActive(TRUE);
11535 // ---------- main game synchronization point ----------
11537 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11539 printf("::: skip == %d\n", skip);
11542 // ---------- main game synchronization point ----------
11544 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11548 if (network_playing && !network_player_action_received)
11550 // try to get network player actions in time
11552 // last chance to get network player actions without main loop delay
11553 HandleNetworking();
11555 // game was quit by network peer
11556 if (game_status != GAME_MODE_PLAYING)
11559 // check if network player actions still missing and game still running
11560 if (!network_player_action_received && !checkGameEnded())
11561 return; // failed to get network player actions in time
11563 // do not yet reset "network_player_action_received" (for tape.pausing)
11569 // at this point we know that we really continue executing the game
11571 network_player_action_received = FALSE;
11573 // when playing tape, read previously recorded player input from tape data
11574 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11576 local_player->effective_mouse_action = local_player->mouse_action;
11578 if (recorded_player_action != NULL)
11579 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11580 recorded_player_action);
11582 // TapePlayAction() may return NULL when toggling to "pause before death"
11586 if (tape.set_centered_player)
11588 game.centered_player_nr_next = tape.centered_player_nr_next;
11589 game.set_centered_player = TRUE;
11592 for (i = 0; i < MAX_PLAYERS; i++)
11594 summarized_player_action |= stored_player[i].action;
11596 if (!network_playing && (game.team_mode || tape.playing))
11597 stored_player[i].effective_action = stored_player[i].action;
11600 if (network_playing && !checkGameEnded())
11601 SendToServer_MovePlayer(summarized_player_action);
11603 // summarize all actions at local players mapped input device position
11604 // (this allows using different input devices in single player mode)
11605 if (!network.enabled && !game.team_mode)
11606 stored_player[map_player_action[local_player->index_nr]].effective_action =
11607 summarized_player_action;
11609 // summarize all actions at centered player in local team mode
11610 if (tape.recording &&
11611 setup.team_mode && !network.enabled &&
11612 setup.input_on_focus &&
11613 game.centered_player_nr != -1)
11615 for (i = 0; i < MAX_PLAYERS; i++)
11616 stored_player[map_player_action[i]].effective_action =
11617 (i == game.centered_player_nr ? summarized_player_action : 0);
11620 if (recorded_player_action != NULL)
11621 for (i = 0; i < MAX_PLAYERS; i++)
11622 stored_player[i].effective_action = recorded_player_action[i];
11624 for (i = 0; i < MAX_PLAYERS; i++)
11626 tape_action[i] = stored_player[i].effective_action;
11628 /* (this may happen in the RND game engine if a player was not present on
11629 the playfield on level start, but appeared later from a custom element */
11630 if (setup.team_mode &&
11633 !tape.player_participates[i])
11634 tape.player_participates[i] = TRUE;
11637 SetTapeActionFromMouseAction(tape_action,
11638 &local_player->effective_mouse_action);
11640 // only record actions from input devices, but not programmed actions
11641 if (tape.recording)
11642 TapeRecordAction(tape_action);
11644 // remember if game was played (especially after tape stopped playing)
11645 if (!tape.playing && summarized_player_action)
11646 game.GamePlayed = TRUE;
11648 #if USE_NEW_PLAYER_ASSIGNMENTS
11649 // !!! also map player actions in single player mode !!!
11650 // if (game.team_mode)
11653 byte mapped_action[MAX_PLAYERS];
11655 #if DEBUG_PLAYER_ACTIONS
11657 for (i = 0; i < MAX_PLAYERS; i++)
11658 printf(" %d, ", stored_player[i].effective_action);
11661 for (i = 0; i < MAX_PLAYERS; i++)
11662 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11664 for (i = 0; i < MAX_PLAYERS; i++)
11665 stored_player[i].effective_action = mapped_action[i];
11667 #if DEBUG_PLAYER_ACTIONS
11669 for (i = 0; i < MAX_PLAYERS; i++)
11670 printf(" %d, ", stored_player[i].effective_action);
11674 #if DEBUG_PLAYER_ACTIONS
11678 for (i = 0; i < MAX_PLAYERS; i++)
11679 printf(" %d, ", stored_player[i].effective_action);
11685 for (i = 0; i < MAX_PLAYERS; i++)
11687 // allow engine snapshot in case of changed movement attempt
11688 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11689 (stored_player[i].effective_action & KEY_MOTION))
11690 game.snapshot.changed_action = TRUE;
11692 // allow engine snapshot in case of snapping/dropping attempt
11693 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11694 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11695 game.snapshot.changed_action = TRUE;
11697 game.snapshot.last_action[i] = stored_player[i].effective_action;
11700 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11702 GameActions_EM_Main();
11704 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11706 GameActions_SP_Main();
11708 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11710 GameActions_MM_Main();
11714 GameActions_RND_Main();
11717 BlitScreenToBitmap(backbuffer);
11719 CheckLevelSolved();
11722 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11724 if (global.show_frames_per_second)
11726 static unsigned int fps_counter = 0;
11727 static int fps_frames = 0;
11728 unsigned int fps_delay_ms = Counter() - fps_counter;
11732 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11734 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11737 fps_counter = Counter();
11739 // always draw FPS to screen after FPS value was updated
11740 redraw_mask |= REDRAW_FPS;
11743 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11744 if (GetDrawDeactivationMask() == REDRAW_NONE)
11745 redraw_mask |= REDRAW_FPS;
11749 static void GameActions_CheckSaveEngineSnapshot(void)
11751 if (!game.snapshot.save_snapshot)
11754 // clear flag for saving snapshot _before_ saving snapshot
11755 game.snapshot.save_snapshot = FALSE;
11757 SaveEngineSnapshotToList();
11760 void GameActions(void)
11764 GameActions_CheckSaveEngineSnapshot();
11767 void GameActions_EM_Main(void)
11769 byte effective_action[MAX_PLAYERS];
11770 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11773 for (i = 0; i < MAX_PLAYERS; i++)
11774 effective_action[i] = stored_player[i].effective_action;
11776 GameActions_EM(effective_action, warp_mode);
11779 void GameActions_SP_Main(void)
11781 byte effective_action[MAX_PLAYERS];
11782 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11785 for (i = 0; i < MAX_PLAYERS; i++)
11786 effective_action[i] = stored_player[i].effective_action;
11788 GameActions_SP(effective_action, warp_mode);
11790 for (i = 0; i < MAX_PLAYERS; i++)
11792 if (stored_player[i].force_dropping)
11793 stored_player[i].action |= KEY_BUTTON_DROP;
11795 stored_player[i].force_dropping = FALSE;
11799 void GameActions_MM_Main(void)
11801 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11803 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11806 void GameActions_RND_Main(void)
11811 void GameActions_RND(void)
11813 static struct MouseActionInfo mouse_action_last = { 0 };
11814 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11815 int magic_wall_x = 0, magic_wall_y = 0;
11816 int i, x, y, element, graphic, last_gfx_frame;
11818 InitPlayfieldScanModeVars();
11820 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11822 SCAN_PLAYFIELD(x, y)
11824 ChangeCount[x][y] = 0;
11825 ChangeEvent[x][y] = -1;
11829 if (game.set_centered_player)
11831 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11833 // switching to "all players" only possible if all players fit to screen
11834 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11836 game.centered_player_nr_next = game.centered_player_nr;
11837 game.set_centered_player = FALSE;
11840 // do not switch focus to non-existing (or non-active) player
11841 if (game.centered_player_nr_next >= 0 &&
11842 !stored_player[game.centered_player_nr_next].active)
11844 game.centered_player_nr_next = game.centered_player_nr;
11845 game.set_centered_player = FALSE;
11849 if (game.set_centered_player &&
11850 ScreenMovPos == 0) // screen currently aligned at tile position
11854 if (game.centered_player_nr_next == -1)
11856 setScreenCenteredToAllPlayers(&sx, &sy);
11860 sx = stored_player[game.centered_player_nr_next].jx;
11861 sy = stored_player[game.centered_player_nr_next].jy;
11864 game.centered_player_nr = game.centered_player_nr_next;
11865 game.set_centered_player = FALSE;
11867 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11868 DrawGameDoorValues();
11871 for (i = 0; i < MAX_PLAYERS; i++)
11873 int actual_player_action = stored_player[i].effective_action;
11876 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11877 - rnd_equinox_tetrachloride 048
11878 - rnd_equinox_tetrachloride_ii 096
11879 - rnd_emanuel_schmieg 002
11880 - doctor_sloan_ww 001, 020
11882 if (stored_player[i].MovPos == 0)
11883 CheckGravityMovement(&stored_player[i]);
11886 // overwrite programmed action with tape action
11887 if (stored_player[i].programmed_action)
11888 actual_player_action = stored_player[i].programmed_action;
11890 PlayerActions(&stored_player[i], actual_player_action);
11892 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11895 ScrollScreen(NULL, SCROLL_GO_ON);
11897 /* for backwards compatibility, the following code emulates a fixed bug that
11898 occured when pushing elements (causing elements that just made their last
11899 pushing step to already (if possible) make their first falling step in the
11900 same game frame, which is bad); this code is also needed to use the famous
11901 "spring push bug" which is used in older levels and might be wanted to be
11902 used also in newer levels, but in this case the buggy pushing code is only
11903 affecting the "spring" element and no other elements */
11905 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11907 for (i = 0; i < MAX_PLAYERS; i++)
11909 struct PlayerInfo *player = &stored_player[i];
11910 int x = player->jx;
11911 int y = player->jy;
11913 if (player->active && player->is_pushing && player->is_moving &&
11915 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11916 Feld[x][y] == EL_SPRING))
11918 ContinueMoving(x, y);
11920 // continue moving after pushing (this is actually a bug)
11921 if (!IS_MOVING(x, y))
11922 Stop[x][y] = FALSE;
11927 SCAN_PLAYFIELD(x, y)
11929 Last[x][y] = Feld[x][y];
11931 ChangeCount[x][y] = 0;
11932 ChangeEvent[x][y] = -1;
11934 // this must be handled before main playfield loop
11935 if (Feld[x][y] == EL_PLAYER_IS_LEAVING)
11938 if (MovDelay[x][y] <= 0)
11942 if (Feld[x][y] == EL_ELEMENT_SNAPPING)
11945 if (MovDelay[x][y] <= 0)
11948 TEST_DrawLevelField(x, y);
11950 TestIfElementTouchesCustomElement(x, y); // for empty space
11955 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11957 printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y);
11958 printf("GameActions(): This should never happen!\n");
11960 ChangePage[x][y] = -1;
11964 Stop[x][y] = FALSE;
11965 if (WasJustMoving[x][y] > 0)
11966 WasJustMoving[x][y]--;
11967 if (WasJustFalling[x][y] > 0)
11968 WasJustFalling[x][y]--;
11969 if (CheckCollision[x][y] > 0)
11970 CheckCollision[x][y]--;
11971 if (CheckImpact[x][y] > 0)
11972 CheckImpact[x][y]--;
11976 /* reset finished pushing action (not done in ContinueMoving() to allow
11977 continuous pushing animation for elements with zero push delay) */
11978 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11980 ResetGfxAnimation(x, y);
11981 TEST_DrawLevelField(x, y);
11985 if (IS_BLOCKED(x, y))
11989 Blocked2Moving(x, y, &oldx, &oldy);
11990 if (!IS_MOVING(oldx, oldy))
11992 printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n");
11993 printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y);
11994 printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy);
11995 printf("GameActions(): This should never happen!\n");
12001 if (mouse_action.button)
12003 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12005 x = mouse_action.lx;
12006 y = mouse_action.ly;
12007 element = Feld[x][y];
12011 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12012 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12015 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12016 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12019 SCAN_PLAYFIELD(x, y)
12021 element = Feld[x][y];
12022 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12023 last_gfx_frame = GfxFrame[x][y];
12025 ResetGfxFrame(x, y);
12027 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12028 DrawLevelGraphicAnimation(x, y, graphic);
12030 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12031 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12032 ResetRandomAnimationValue(x, y);
12034 SetRandomAnimationValue(x, y);
12036 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12038 if (IS_INACTIVE(element))
12040 if (IS_ANIMATED(graphic))
12041 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12046 // this may take place after moving, so 'element' may have changed
12047 if (IS_CHANGING(x, y) &&
12048 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12050 int page = element_info[element].event_page_nr[CE_DELAY];
12052 HandleElementChange(x, y, page);
12054 element = Feld[x][y];
12055 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12058 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12062 element = Feld[x][y];
12063 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12065 if (IS_ANIMATED(graphic) &&
12066 !IS_MOVING(x, y) &&
12068 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12070 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12071 TEST_DrawTwinkleOnField(x, y);
12073 else if (element == EL_ACID)
12076 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12078 else if ((element == EL_EXIT_OPEN ||
12079 element == EL_EM_EXIT_OPEN ||
12080 element == EL_SP_EXIT_OPEN ||
12081 element == EL_STEEL_EXIT_OPEN ||
12082 element == EL_EM_STEEL_EXIT_OPEN ||
12083 element == EL_SP_TERMINAL ||
12084 element == EL_SP_TERMINAL_ACTIVE ||
12085 element == EL_EXTRA_TIME ||
12086 element == EL_SHIELD_NORMAL ||
12087 element == EL_SHIELD_DEADLY) &&
12088 IS_ANIMATED(graphic))
12089 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12090 else if (IS_MOVING(x, y))
12091 ContinueMoving(x, y);
12092 else if (IS_ACTIVE_BOMB(element))
12093 CheckDynamite(x, y);
12094 else if (element == EL_AMOEBA_GROWING)
12095 AmoebeWaechst(x, y);
12096 else if (element == EL_AMOEBA_SHRINKING)
12097 AmoebaDisappearing(x, y);
12099 #if !USE_NEW_AMOEBA_CODE
12100 else if (IS_AMOEBALIVE(element))
12101 AmoebeAbleger(x, y);
12104 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12106 else if (element == EL_EXIT_CLOSED)
12108 else if (element == EL_EM_EXIT_CLOSED)
12110 else if (element == EL_STEEL_EXIT_CLOSED)
12111 CheckExitSteel(x, y);
12112 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12113 CheckExitSteelEM(x, y);
12114 else if (element == EL_SP_EXIT_CLOSED)
12116 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12117 element == EL_EXPANDABLE_STEELWALL_GROWING)
12118 MauerWaechst(x, y);
12119 else if (element == EL_EXPANDABLE_WALL ||
12120 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12121 element == EL_EXPANDABLE_WALL_VERTICAL ||
12122 element == EL_EXPANDABLE_WALL_ANY ||
12123 element == EL_BD_EXPANDABLE_WALL)
12124 MauerAbleger(x, y);
12125 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12126 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12127 element == EL_EXPANDABLE_STEELWALL_ANY)
12128 MauerAblegerStahl(x, y);
12129 else if (element == EL_FLAMES)
12130 CheckForDragon(x, y);
12131 else if (element == EL_EXPLOSION)
12132 ; // drawing of correct explosion animation is handled separately
12133 else if (element == EL_ELEMENT_SNAPPING ||
12134 element == EL_DIAGONAL_SHRINKING ||
12135 element == EL_DIAGONAL_GROWING)
12137 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12139 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12141 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12142 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12144 if (IS_BELT_ACTIVE(element))
12145 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12147 if (game.magic_wall_active)
12149 int jx = local_player->jx, jy = local_player->jy;
12151 // play the element sound at the position nearest to the player
12152 if ((element == EL_MAGIC_WALL_FULL ||
12153 element == EL_MAGIC_WALL_ACTIVE ||
12154 element == EL_MAGIC_WALL_EMPTYING ||
12155 element == EL_BD_MAGIC_WALL_FULL ||
12156 element == EL_BD_MAGIC_WALL_ACTIVE ||
12157 element == EL_BD_MAGIC_WALL_EMPTYING ||
12158 element == EL_DC_MAGIC_WALL_FULL ||
12159 element == EL_DC_MAGIC_WALL_ACTIVE ||
12160 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12161 ABS(x - jx) + ABS(y - jy) <
12162 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12170 #if USE_NEW_AMOEBA_CODE
12171 // new experimental amoeba growth stuff
12172 if (!(FrameCounter % 8))
12174 static unsigned int random = 1684108901;
12176 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12178 x = RND(lev_fieldx);
12179 y = RND(lev_fieldy);
12180 element = Feld[x][y];
12182 if (!IS_PLAYER(x,y) &&
12183 (element == EL_EMPTY ||
12184 CAN_GROW_INTO(element) ||
12185 element == EL_QUICKSAND_EMPTY ||
12186 element == EL_QUICKSAND_FAST_EMPTY ||
12187 element == EL_ACID_SPLASH_LEFT ||
12188 element == EL_ACID_SPLASH_RIGHT))
12190 if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) ||
12191 (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) ||
12192 (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) ||
12193 (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET))
12194 Feld[x][y] = EL_AMOEBA_DROP;
12197 random = random * 129 + 1;
12202 game.explosions_delayed = FALSE;
12204 SCAN_PLAYFIELD(x, y)
12206 element = Feld[x][y];
12208 if (ExplodeField[x][y])
12209 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12210 else if (element == EL_EXPLOSION)
12211 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12213 ExplodeField[x][y] = EX_TYPE_NONE;
12216 game.explosions_delayed = TRUE;
12218 if (game.magic_wall_active)
12220 if (!(game.magic_wall_time_left % 4))
12222 int element = Feld[magic_wall_x][magic_wall_y];
12224 if (element == EL_BD_MAGIC_WALL_FULL ||
12225 element == EL_BD_MAGIC_WALL_ACTIVE ||
12226 element == EL_BD_MAGIC_WALL_EMPTYING)
12227 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12228 else if (element == EL_DC_MAGIC_WALL_FULL ||
12229 element == EL_DC_MAGIC_WALL_ACTIVE ||
12230 element == EL_DC_MAGIC_WALL_EMPTYING)
12231 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12233 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12236 if (game.magic_wall_time_left > 0)
12238 game.magic_wall_time_left--;
12240 if (!game.magic_wall_time_left)
12242 SCAN_PLAYFIELD(x, y)
12244 element = Feld[x][y];
12246 if (element == EL_MAGIC_WALL_ACTIVE ||
12247 element == EL_MAGIC_WALL_FULL)
12249 Feld[x][y] = EL_MAGIC_WALL_DEAD;
12250 TEST_DrawLevelField(x, y);
12252 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12253 element == EL_BD_MAGIC_WALL_FULL)
12255 Feld[x][y] = EL_BD_MAGIC_WALL_DEAD;
12256 TEST_DrawLevelField(x, y);
12258 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12259 element == EL_DC_MAGIC_WALL_FULL)
12261 Feld[x][y] = EL_DC_MAGIC_WALL_DEAD;
12262 TEST_DrawLevelField(x, y);
12266 game.magic_wall_active = FALSE;
12271 if (game.light_time_left > 0)
12273 game.light_time_left--;
12275 if (game.light_time_left == 0)
12276 RedrawAllLightSwitchesAndInvisibleElements();
12279 if (game.timegate_time_left > 0)
12281 game.timegate_time_left--;
12283 if (game.timegate_time_left == 0)
12284 CloseAllOpenTimegates();
12287 if (game.lenses_time_left > 0)
12289 game.lenses_time_left--;
12291 if (game.lenses_time_left == 0)
12292 RedrawAllInvisibleElementsForLenses();
12295 if (game.magnify_time_left > 0)
12297 game.magnify_time_left--;
12299 if (game.magnify_time_left == 0)
12300 RedrawAllInvisibleElementsForMagnifier();
12303 for (i = 0; i < MAX_PLAYERS; i++)
12305 struct PlayerInfo *player = &stored_player[i];
12307 if (SHIELD_ON(player))
12309 if (player->shield_deadly_time_left)
12310 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12311 else if (player->shield_normal_time_left)
12312 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12316 #if USE_DELAYED_GFX_REDRAW
12317 SCAN_PLAYFIELD(x, y)
12319 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12321 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12322 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12324 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12325 DrawLevelField(x, y);
12327 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12328 DrawLevelFieldCrumbled(x, y);
12330 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12331 DrawLevelFieldCrumbledNeighbours(x, y);
12333 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12334 DrawTwinkleOnField(x, y);
12337 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12342 PlayAllPlayersSound();
12344 for (i = 0; i < MAX_PLAYERS; i++)
12346 struct PlayerInfo *player = &stored_player[i];
12348 if (player->show_envelope != 0 && (!player->active ||
12349 player->MovPos == 0))
12351 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12353 player->show_envelope = 0;
12357 // use random number generator in every frame to make it less predictable
12358 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12361 mouse_action_last = mouse_action;
12364 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12366 int min_x = x, min_y = y, max_x = x, max_y = y;
12369 for (i = 0; i < MAX_PLAYERS; i++)
12371 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12373 if (!stored_player[i].active || &stored_player[i] == player)
12376 min_x = MIN(min_x, jx);
12377 min_y = MIN(min_y, jy);
12378 max_x = MAX(max_x, jx);
12379 max_y = MAX(max_y, jy);
12382 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12385 static boolean AllPlayersInVisibleScreen(void)
12389 for (i = 0; i < MAX_PLAYERS; i++)
12391 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12393 if (!stored_player[i].active)
12396 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12403 void ScrollLevel(int dx, int dy)
12405 int scroll_offset = 2 * TILEX_VAR;
12408 BlitBitmap(drawto_field, drawto_field,
12409 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12410 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12411 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12412 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12413 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12414 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12418 x = (dx == 1 ? BX1 : BX2);
12419 for (y = BY1; y <= BY2; y++)
12420 DrawScreenField(x, y);
12425 y = (dy == 1 ? BY1 : BY2);
12426 for (x = BX1; x <= BX2; x++)
12427 DrawScreenField(x, y);
12430 redraw_mask |= REDRAW_FIELD;
12433 static boolean canFallDown(struct PlayerInfo *player)
12435 int jx = player->jx, jy = player->jy;
12437 return (IN_LEV_FIELD(jx, jy + 1) &&
12438 (IS_FREE(jx, jy + 1) ||
12439 (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12440 IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) &&
12441 !IS_WALKABLE_INSIDE(Feld[jx][jy]));
12444 static boolean canPassField(int x, int y, int move_dir)
12446 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12447 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12448 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12449 int nextx = x + dx;
12450 int nexty = y + dy;
12451 int element = Feld[x][y];
12453 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12454 !CAN_MOVE(element) &&
12455 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12456 IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) &&
12457 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12460 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12462 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12463 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12464 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12468 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12469 IS_GRAVITY_REACHABLE(Feld[newx][newy]) &&
12470 (IS_DIGGABLE(Feld[newx][newy]) ||
12471 IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) ||
12472 canPassField(newx, newy, move_dir)));
12475 static void CheckGravityMovement(struct PlayerInfo *player)
12477 if (player->gravity && !player->programmed_action)
12479 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12480 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12481 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12482 int jx = player->jx, jy = player->jy;
12483 boolean player_is_moving_to_valid_field =
12484 (!player_is_snapping &&
12485 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12486 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12487 boolean player_can_fall_down = canFallDown(player);
12489 if (player_can_fall_down &&
12490 !player_is_moving_to_valid_field)
12491 player->programmed_action = MV_DOWN;
12495 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12497 return CheckGravityMovement(player);
12499 if (player->gravity && !player->programmed_action)
12501 int jx = player->jx, jy = player->jy;
12502 boolean field_under_player_is_free =
12503 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12504 boolean player_is_standing_on_valid_field =
12505 (IS_WALKABLE_INSIDE(Feld[jx][jy]) ||
12506 (IS_WALKABLE(Feld[jx][jy]) &&
12507 !(element_info[Feld[jx][jy]].access_direction & MV_DOWN)));
12509 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12510 player->programmed_action = MV_DOWN;
12515 MovePlayerOneStep()
12516 -----------------------------------------------------------------------------
12517 dx, dy: direction (non-diagonal) to try to move the player to
12518 real_dx, real_dy: direction as read from input device (can be diagonal)
12521 boolean MovePlayerOneStep(struct PlayerInfo *player,
12522 int dx, int dy, int real_dx, int real_dy)
12524 int jx = player->jx, jy = player->jy;
12525 int new_jx = jx + dx, new_jy = jy + dy;
12527 boolean player_can_move = !player->cannot_move;
12529 if (!player->active || (!dx && !dy))
12530 return MP_NO_ACTION;
12532 player->MovDir = (dx < 0 ? MV_LEFT :
12533 dx > 0 ? MV_RIGHT :
12535 dy > 0 ? MV_DOWN : MV_NONE);
12537 if (!IN_LEV_FIELD(new_jx, new_jy))
12538 return MP_NO_ACTION;
12540 if (!player_can_move)
12542 if (player->MovPos == 0)
12544 player->is_moving = FALSE;
12545 player->is_digging = FALSE;
12546 player->is_collecting = FALSE;
12547 player->is_snapping = FALSE;
12548 player->is_pushing = FALSE;
12552 if (!network.enabled && game.centered_player_nr == -1 &&
12553 !AllPlayersInSight(player, new_jx, new_jy))
12554 return MP_NO_ACTION;
12556 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12557 if (can_move != MP_MOVING)
12560 // check if DigField() has caused relocation of the player
12561 if (player->jx != jx || player->jy != jy)
12562 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12564 StorePlayer[jx][jy] = 0;
12565 player->last_jx = jx;
12566 player->last_jy = jy;
12567 player->jx = new_jx;
12568 player->jy = new_jy;
12569 StorePlayer[new_jx][new_jy] = player->element_nr;
12571 if (player->move_delay_value_next != -1)
12573 player->move_delay_value = player->move_delay_value_next;
12574 player->move_delay_value_next = -1;
12578 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12580 player->step_counter++;
12582 PlayerVisit[jx][jy] = FrameCounter;
12584 player->is_moving = TRUE;
12587 // should better be called in MovePlayer(), but this breaks some tapes
12588 ScrollPlayer(player, SCROLL_INIT);
12594 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12596 int jx = player->jx, jy = player->jy;
12597 int old_jx = jx, old_jy = jy;
12598 int moved = MP_NO_ACTION;
12600 if (!player->active)
12605 if (player->MovPos == 0)
12607 player->is_moving = FALSE;
12608 player->is_digging = FALSE;
12609 player->is_collecting = FALSE;
12610 player->is_snapping = FALSE;
12611 player->is_pushing = FALSE;
12617 if (player->move_delay > 0)
12620 player->move_delay = -1; // set to "uninitialized" value
12622 // store if player is automatically moved to next field
12623 player->is_auto_moving = (player->programmed_action != MV_NONE);
12625 // remove the last programmed player action
12626 player->programmed_action = 0;
12628 if (player->MovPos)
12630 // should only happen if pre-1.2 tape recordings are played
12631 // this is only for backward compatibility
12633 int original_move_delay_value = player->move_delay_value;
12636 printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n",
12640 // scroll remaining steps with finest movement resolution
12641 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12643 while (player->MovPos)
12645 ScrollPlayer(player, SCROLL_GO_ON);
12646 ScrollScreen(NULL, SCROLL_GO_ON);
12648 AdvanceFrameAndPlayerCounters(player->index_nr);
12651 BackToFront_WithFrameDelay(0);
12654 player->move_delay_value = original_move_delay_value;
12657 player->is_active = FALSE;
12659 if (player->last_move_dir & MV_HORIZONTAL)
12661 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12662 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12666 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12667 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12670 if (!moved && !player->is_active)
12672 player->is_moving = FALSE;
12673 player->is_digging = FALSE;
12674 player->is_collecting = FALSE;
12675 player->is_snapping = FALSE;
12676 player->is_pushing = FALSE;
12682 if (moved & MP_MOVING && !ScreenMovPos &&
12683 (player->index_nr == game.centered_player_nr ||
12684 game.centered_player_nr == -1))
12686 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12688 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12690 // actual player has left the screen -- scroll in that direction
12691 if (jx != old_jx) // player has moved horizontally
12692 scroll_x += (jx - old_jx);
12693 else // player has moved vertically
12694 scroll_y += (jy - old_jy);
12698 int offset_raw = game.scroll_delay_value;
12700 if (jx != old_jx) // player has moved horizontally
12702 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12703 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12704 int new_scroll_x = jx - MIDPOSX + offset_x;
12706 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12707 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12708 scroll_x = new_scroll_x;
12710 // don't scroll over playfield boundaries
12711 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12713 // don't scroll more than one field at a time
12714 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12716 // don't scroll against the player's moving direction
12717 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12718 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12719 scroll_x = old_scroll_x;
12721 else // player has moved vertically
12723 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12724 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12725 int new_scroll_y = jy - MIDPOSY + offset_y;
12727 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12728 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12729 scroll_y = new_scroll_y;
12731 // don't scroll over playfield boundaries
12732 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12734 // don't scroll more than one field at a time
12735 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12737 // don't scroll against the player's moving direction
12738 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12739 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12740 scroll_y = old_scroll_y;
12744 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12746 if (!network.enabled && game.centered_player_nr == -1 &&
12747 !AllPlayersInVisibleScreen())
12749 scroll_x = old_scroll_x;
12750 scroll_y = old_scroll_y;
12754 ScrollScreen(player, SCROLL_INIT);
12755 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12760 player->StepFrame = 0;
12762 if (moved & MP_MOVING)
12764 if (old_jx != jx && old_jy == jy)
12765 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12766 else if (old_jx == jx && old_jy != jy)
12767 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12769 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12771 player->last_move_dir = player->MovDir;
12772 player->is_moving = TRUE;
12773 player->is_snapping = FALSE;
12774 player->is_switching = FALSE;
12775 player->is_dropping = FALSE;
12776 player->is_dropping_pressed = FALSE;
12777 player->drop_pressed_delay = 0;
12780 // should better be called here than above, but this breaks some tapes
12781 ScrollPlayer(player, SCROLL_INIT);
12786 CheckGravityMovementWhenNotMoving(player);
12788 player->is_moving = FALSE;
12790 /* at this point, the player is allowed to move, but cannot move right now
12791 (e.g. because of something blocking the way) -- ensure that the player
12792 is also allowed to move in the next frame (in old versions before 3.1.1,
12793 the player was forced to wait again for eight frames before next try) */
12795 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12796 player->move_delay = 0; // allow direct movement in the next frame
12799 if (player->move_delay == -1) // not yet initialized by DigField()
12800 player->move_delay = player->move_delay_value;
12802 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12804 TestIfPlayerTouchesBadThing(jx, jy);
12805 TestIfPlayerTouchesCustomElement(jx, jy);
12808 if (!player->active)
12809 RemovePlayer(player);
12814 void ScrollPlayer(struct PlayerInfo *player, int mode)
12816 int jx = player->jx, jy = player->jy;
12817 int last_jx = player->last_jx, last_jy = player->last_jy;
12818 int move_stepsize = TILEX / player->move_delay_value;
12820 if (!player->active)
12823 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12826 if (mode == SCROLL_INIT)
12828 player->actual_frame_counter = FrameCounter;
12829 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12831 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12832 Feld[last_jx][last_jy] == EL_EMPTY)
12834 int last_field_block_delay = 0; // start with no blocking at all
12835 int block_delay_adjustment = player->block_delay_adjustment;
12837 // if player blocks last field, add delay for exactly one move
12838 if (player->block_last_field)
12840 last_field_block_delay += player->move_delay_value;
12842 // when blocking enabled, prevent moving up despite gravity
12843 if (player->gravity && player->MovDir == MV_UP)
12844 block_delay_adjustment = -1;
12847 // add block delay adjustment (also possible when not blocking)
12848 last_field_block_delay += block_delay_adjustment;
12850 Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12851 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12854 if (player->MovPos != 0) // player has not yet reached destination
12857 else if (!FrameReached(&player->actual_frame_counter, 1))
12860 if (player->MovPos != 0)
12862 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12863 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12865 // before DrawPlayer() to draw correct player graphic for this case
12866 if (player->MovPos == 0)
12867 CheckGravityMovement(player);
12870 if (player->MovPos == 0) // player reached destination field
12872 if (player->move_delay_reset_counter > 0)
12874 player->move_delay_reset_counter--;
12876 if (player->move_delay_reset_counter == 0)
12878 // continue with normal speed after quickly moving through gate
12879 HALVE_PLAYER_SPEED(player);
12881 // be able to make the next move without delay
12882 player->move_delay = 0;
12886 player->last_jx = jx;
12887 player->last_jy = jy;
12889 if (Feld[jx][jy] == EL_EXIT_OPEN ||
12890 Feld[jx][jy] == EL_EM_EXIT_OPEN ||
12891 Feld[jx][jy] == EL_EM_EXIT_OPENING ||
12892 Feld[jx][jy] == EL_STEEL_EXIT_OPEN ||
12893 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12894 Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12895 Feld[jx][jy] == EL_SP_EXIT_OPEN ||
12896 Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12898 ExitPlayer(player);
12900 if (game.players_still_needed == 0 &&
12901 (game.friends_still_needed == 0 ||
12902 IS_SP_ELEMENT(Feld[jx][jy])))
12906 // this breaks one level: "machine", level 000
12908 int move_direction = player->MovDir;
12909 int enter_side = MV_DIR_OPPOSITE(move_direction);
12910 int leave_side = move_direction;
12911 int old_jx = last_jx;
12912 int old_jy = last_jy;
12913 int old_element = Feld[old_jx][old_jy];
12914 int new_element = Feld[jx][jy];
12916 if (IS_CUSTOM_ELEMENT(old_element))
12917 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12919 player->index_bit, leave_side);
12921 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12922 CE_PLAYER_LEAVES_X,
12923 player->index_bit, leave_side);
12925 if (IS_CUSTOM_ELEMENT(new_element))
12926 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12927 player->index_bit, enter_side);
12929 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12930 CE_PLAYER_ENTERS_X,
12931 player->index_bit, enter_side);
12933 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12934 CE_MOVE_OF_X, move_direction);
12937 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12939 TestIfPlayerTouchesBadThing(jx, jy);
12940 TestIfPlayerTouchesCustomElement(jx, jy);
12942 /* needed because pushed element has not yet reached its destination,
12943 so it would trigger a change event at its previous field location */
12944 if (!player->is_pushing)
12945 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12947 if (!player->active)
12948 RemovePlayer(player);
12951 if (!game.LevelSolved && level.use_step_counter)
12961 if (TimeLeft <= 10 && setup.time_limit)
12962 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12964 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12966 DisplayGameControlValues();
12968 if (!TimeLeft && setup.time_limit)
12969 for (i = 0; i < MAX_PLAYERS; i++)
12970 KillPlayer(&stored_player[i]);
12972 else if (game.no_time_limit && !game.all_players_gone)
12974 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12976 DisplayGameControlValues();
12980 if (tape.single_step && tape.recording && !tape.pausing &&
12981 !player->programmed_action)
12982 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12984 if (!player->programmed_action)
12985 CheckSaveEngineSnapshot(player);
12989 void ScrollScreen(struct PlayerInfo *player, int mode)
12991 static unsigned int screen_frame_counter = 0;
12993 if (mode == SCROLL_INIT)
12995 // set scrolling step size according to actual player's moving speed
12996 ScrollStepSize = TILEX / player->move_delay_value;
12998 screen_frame_counter = FrameCounter;
12999 ScreenMovDir = player->MovDir;
13000 ScreenMovPos = player->MovPos;
13001 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13004 else if (!FrameReached(&screen_frame_counter, 1))
13009 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13010 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13011 redraw_mask |= REDRAW_FIELD;
13014 ScreenMovDir = MV_NONE;
13017 void TestIfPlayerTouchesCustomElement(int x, int y)
13019 static int xy[4][2] =
13026 static int trigger_sides[4][2] =
13028 // center side border side
13029 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13030 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13031 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13032 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13034 static int touch_dir[4] =
13036 MV_LEFT | MV_RIGHT,
13041 int center_element = Feld[x][y]; // should always be non-moving!
13044 for (i = 0; i < NUM_DIRECTIONS; i++)
13046 int xx = x + xy[i][0];
13047 int yy = y + xy[i][1];
13048 int center_side = trigger_sides[i][0];
13049 int border_side = trigger_sides[i][1];
13050 int border_element;
13052 if (!IN_LEV_FIELD(xx, yy))
13055 if (IS_PLAYER(x, y)) // player found at center element
13057 struct PlayerInfo *player = PLAYERINFO(x, y);
13059 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13060 border_element = Feld[xx][yy]; // may be moving!
13061 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13062 border_element = Feld[xx][yy];
13063 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13064 border_element = MovingOrBlocked2Element(xx, yy);
13066 continue; // center and border element do not touch
13068 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13069 player->index_bit, border_side);
13070 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13071 CE_PLAYER_TOUCHES_X,
13072 player->index_bit, border_side);
13075 /* use player element that is initially defined in the level playfield,
13076 not the player element that corresponds to the runtime player number
13077 (example: a level that contains EL_PLAYER_3 as the only player would
13078 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13079 int player_element = PLAYERINFO(x, y)->initial_element;
13081 CheckElementChangeBySide(xx, yy, border_element, player_element,
13082 CE_TOUCHING_X, border_side);
13085 else if (IS_PLAYER(xx, yy)) // player found at border element
13087 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13089 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13091 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13092 continue; // center and border element do not touch
13095 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13096 player->index_bit, center_side);
13097 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13098 CE_PLAYER_TOUCHES_X,
13099 player->index_bit, center_side);
13102 /* use player element that is initially defined in the level playfield,
13103 not the player element that corresponds to the runtime player number
13104 (example: a level that contains EL_PLAYER_3 as the only player would
13105 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13106 int player_element = PLAYERINFO(xx, yy)->initial_element;
13108 CheckElementChangeBySide(x, y, center_element, player_element,
13109 CE_TOUCHING_X, center_side);
13117 void TestIfElementTouchesCustomElement(int x, int y)
13119 static int xy[4][2] =
13126 static int trigger_sides[4][2] =
13128 // center side border side
13129 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13130 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13131 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13132 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13134 static int touch_dir[4] =
13136 MV_LEFT | MV_RIGHT,
13141 boolean change_center_element = FALSE;
13142 int center_element = Feld[x][y]; // should always be non-moving!
13143 int border_element_old[NUM_DIRECTIONS];
13146 for (i = 0; i < NUM_DIRECTIONS; i++)
13148 int xx = x + xy[i][0];
13149 int yy = y + xy[i][1];
13150 int border_element;
13152 border_element_old[i] = -1;
13154 if (!IN_LEV_FIELD(xx, yy))
13157 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13158 border_element = Feld[xx][yy]; // may be moving!
13159 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13160 border_element = Feld[xx][yy];
13161 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13162 border_element = MovingOrBlocked2Element(xx, yy);
13164 continue; // center and border element do not touch
13166 border_element_old[i] = border_element;
13169 for (i = 0; i < NUM_DIRECTIONS; i++)
13171 int xx = x + xy[i][0];
13172 int yy = y + xy[i][1];
13173 int center_side = trigger_sides[i][0];
13174 int border_element = border_element_old[i];
13176 if (border_element == -1)
13179 // check for change of border element
13180 CheckElementChangeBySide(xx, yy, border_element, center_element,
13181 CE_TOUCHING_X, center_side);
13183 // (center element cannot be player, so we dont have to check this here)
13186 for (i = 0; i < NUM_DIRECTIONS; i++)
13188 int xx = x + xy[i][0];
13189 int yy = y + xy[i][1];
13190 int border_side = trigger_sides[i][1];
13191 int border_element = border_element_old[i];
13193 if (border_element == -1)
13196 // check for change of center element (but change it only once)
13197 if (!change_center_element)
13198 change_center_element =
13199 CheckElementChangeBySide(x, y, center_element, border_element,
13200 CE_TOUCHING_X, border_side);
13202 if (IS_PLAYER(xx, yy))
13204 /* use player element that is initially defined in the level playfield,
13205 not the player element that corresponds to the runtime player number
13206 (example: a level that contains EL_PLAYER_3 as the only player would
13207 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13208 int player_element = PLAYERINFO(xx, yy)->initial_element;
13210 CheckElementChangeBySide(x, y, center_element, player_element,
13211 CE_TOUCHING_X, border_side);
13216 void TestIfElementHitsCustomElement(int x, int y, int direction)
13218 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13219 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13220 int hitx = x + dx, hity = y + dy;
13221 int hitting_element = Feld[x][y];
13222 int touched_element;
13224 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13227 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13228 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13230 if (IN_LEV_FIELD(hitx, hity))
13232 int opposite_direction = MV_DIR_OPPOSITE(direction);
13233 int hitting_side = direction;
13234 int touched_side = opposite_direction;
13235 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13236 MovDir[hitx][hity] != direction ||
13237 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13243 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13244 CE_HITTING_X, touched_side);
13246 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13247 CE_HIT_BY_X, hitting_side);
13249 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13250 CE_HIT_BY_SOMETHING, opposite_direction);
13252 if (IS_PLAYER(hitx, hity))
13254 /* use player element that is initially defined in the level playfield,
13255 not the player element that corresponds to the runtime player number
13256 (example: a level that contains EL_PLAYER_3 as the only player would
13257 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13258 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13260 CheckElementChangeBySide(x, y, hitting_element, player_element,
13261 CE_HITTING_X, touched_side);
13266 // "hitting something" is also true when hitting the playfield border
13267 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13268 CE_HITTING_SOMETHING, direction);
13271 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13273 int i, kill_x = -1, kill_y = -1;
13275 int bad_element = -1;
13276 static int test_xy[4][2] =
13283 static int test_dir[4] =
13291 for (i = 0; i < NUM_DIRECTIONS; i++)
13293 int test_x, test_y, test_move_dir, test_element;
13295 test_x = good_x + test_xy[i][0];
13296 test_y = good_y + test_xy[i][1];
13298 if (!IN_LEV_FIELD(test_x, test_y))
13302 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13304 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13306 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13307 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13309 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13310 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13314 bad_element = test_element;
13320 if (kill_x != -1 || kill_y != -1)
13322 if (IS_PLAYER(good_x, good_y))
13324 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13326 if (player->shield_deadly_time_left > 0 &&
13327 !IS_INDESTRUCTIBLE(bad_element))
13328 Bang(kill_x, kill_y);
13329 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13330 KillPlayer(player);
13333 Bang(good_x, good_y);
13337 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13339 int i, kill_x = -1, kill_y = -1;
13340 int bad_element = Feld[bad_x][bad_y];
13341 static int test_xy[4][2] =
13348 static int touch_dir[4] =
13350 MV_LEFT | MV_RIGHT,
13355 static int test_dir[4] =
13363 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13366 for (i = 0; i < NUM_DIRECTIONS; i++)
13368 int test_x, test_y, test_move_dir, test_element;
13370 test_x = bad_x + test_xy[i][0];
13371 test_y = bad_y + test_xy[i][1];
13373 if (!IN_LEV_FIELD(test_x, test_y))
13377 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13379 test_element = Feld[test_x][test_y];
13381 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13382 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13384 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13385 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13387 // good thing is player or penguin that does not move away
13388 if (IS_PLAYER(test_x, test_y))
13390 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13392 if (bad_element == EL_ROBOT && player->is_moving)
13393 continue; // robot does not kill player if he is moving
13395 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13397 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13398 continue; // center and border element do not touch
13406 else if (test_element == EL_PENGUIN)
13416 if (kill_x != -1 || kill_y != -1)
13418 if (IS_PLAYER(kill_x, kill_y))
13420 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13422 if (player->shield_deadly_time_left > 0 &&
13423 !IS_INDESTRUCTIBLE(bad_element))
13424 Bang(bad_x, bad_y);
13425 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13426 KillPlayer(player);
13429 Bang(kill_x, kill_y);
13433 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13435 int bad_element = Feld[bad_x][bad_y];
13436 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13437 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13438 int test_x = bad_x + dx, test_y = bad_y + dy;
13439 int test_move_dir, test_element;
13440 int kill_x = -1, kill_y = -1;
13442 if (!IN_LEV_FIELD(test_x, test_y))
13446 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13448 test_element = Feld[test_x][test_y];
13450 if (test_move_dir != bad_move_dir)
13452 // good thing can be player or penguin that does not move away
13453 if (IS_PLAYER(test_x, test_y))
13455 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13457 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13458 player as being hit when he is moving towards the bad thing, because
13459 the "get hit by" condition would be lost after the player stops) */
13460 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13461 return; // player moves away from bad thing
13466 else if (test_element == EL_PENGUIN)
13473 if (kill_x != -1 || kill_y != -1)
13475 if (IS_PLAYER(kill_x, kill_y))
13477 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13479 if (player->shield_deadly_time_left > 0 &&
13480 !IS_INDESTRUCTIBLE(bad_element))
13481 Bang(bad_x, bad_y);
13482 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13483 KillPlayer(player);
13486 Bang(kill_x, kill_y);
13490 void TestIfPlayerTouchesBadThing(int x, int y)
13492 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13495 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13497 TestIfGoodThingHitsBadThing(x, y, move_dir);
13500 void TestIfBadThingTouchesPlayer(int x, int y)
13502 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13505 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13507 TestIfBadThingHitsGoodThing(x, y, move_dir);
13510 void TestIfFriendTouchesBadThing(int x, int y)
13512 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13515 void TestIfBadThingTouchesFriend(int x, int y)
13517 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13520 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13522 int i, kill_x = bad_x, kill_y = bad_y;
13523 static int xy[4][2] =
13531 for (i = 0; i < NUM_DIRECTIONS; i++)
13535 x = bad_x + xy[i][0];
13536 y = bad_y + xy[i][1];
13537 if (!IN_LEV_FIELD(x, y))
13540 element = Feld[x][y];
13541 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13542 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13550 if (kill_x != bad_x || kill_y != bad_y)
13551 Bang(bad_x, bad_y);
13554 void KillPlayer(struct PlayerInfo *player)
13556 int jx = player->jx, jy = player->jy;
13558 if (!player->active)
13562 printf("::: 0: killed == %d, active == %d, reanimated == %d\n",
13563 player->killed, player->active, player->reanimated);
13566 /* the following code was introduced to prevent an infinite loop when calling
13568 -> CheckTriggeredElementChangeExt()
13569 -> ExecuteCustomElementAction()
13571 -> (infinitely repeating the above sequence of function calls)
13572 which occurs when killing the player while having a CE with the setting
13573 "kill player X when explosion of <player X>"; the solution using a new
13574 field "player->killed" was chosen for backwards compatibility, although
13575 clever use of the fields "player->active" etc. would probably also work */
13577 if (player->killed)
13581 player->killed = TRUE;
13583 // remove accessible field at the player's position
13584 Feld[jx][jy] = EL_EMPTY;
13586 // deactivate shield (else Bang()/Explode() would not work right)
13587 player->shield_normal_time_left = 0;
13588 player->shield_deadly_time_left = 0;
13591 printf("::: 1: killed == %d, active == %d, reanimated == %d\n",
13592 player->killed, player->active, player->reanimated);
13598 printf("::: 2: killed == %d, active == %d, reanimated == %d\n",
13599 player->killed, player->active, player->reanimated);
13602 if (player->reanimated) // killed player may have been reanimated
13603 player->killed = player->reanimated = FALSE;
13605 BuryPlayer(player);
13608 static void KillPlayerUnlessEnemyProtected(int x, int y)
13610 if (!PLAYER_ENEMY_PROTECTED(x, y))
13611 KillPlayer(PLAYERINFO(x, y));
13614 static void KillPlayerUnlessExplosionProtected(int x, int y)
13616 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13617 KillPlayer(PLAYERINFO(x, y));
13620 void BuryPlayer(struct PlayerInfo *player)
13622 int jx = player->jx, jy = player->jy;
13624 if (!player->active)
13627 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13628 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13630 RemovePlayer(player);
13632 player->buried = TRUE;
13634 if (game.all_players_gone)
13635 game.GameOver = TRUE;
13638 void RemovePlayer(struct PlayerInfo *player)
13640 int jx = player->jx, jy = player->jy;
13641 int i, found = FALSE;
13643 player->present = FALSE;
13644 player->active = FALSE;
13646 // required for some CE actions (even if the player is not active anymore)
13647 player->MovPos = 0;
13649 if (!ExplodeField[jx][jy])
13650 StorePlayer[jx][jy] = 0;
13652 if (player->is_moving)
13653 TEST_DrawLevelField(player->last_jx, player->last_jy);
13655 for (i = 0; i < MAX_PLAYERS; i++)
13656 if (stored_player[i].active)
13661 game.all_players_gone = TRUE;
13662 game.GameOver = TRUE;
13665 game.exit_x = game.robot_wheel_x = jx;
13666 game.exit_y = game.robot_wheel_y = jy;
13669 void ExitPlayer(struct PlayerInfo *player)
13671 DrawPlayer(player); // needed here only to cleanup last field
13672 RemovePlayer(player);
13674 if (game.players_still_needed > 0)
13675 game.players_still_needed--;
13678 static void setFieldForSnapping(int x, int y, int element, int direction)
13680 struct ElementInfo *ei = &element_info[element];
13681 int direction_bit = MV_DIR_TO_BIT(direction);
13682 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13683 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13684 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13686 Feld[x][y] = EL_ELEMENT_SNAPPING;
13687 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13689 ResetGfxAnimation(x, y);
13691 GfxElement[x][y] = element;
13692 GfxAction[x][y] = action;
13693 GfxDir[x][y] = direction;
13694 GfxFrame[x][y] = -1;
13698 =============================================================================
13699 checkDiagonalPushing()
13700 -----------------------------------------------------------------------------
13701 check if diagonal input device direction results in pushing of object
13702 (by checking if the alternative direction is walkable, diggable, ...)
13703 =============================================================================
13706 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13707 int x, int y, int real_dx, int real_dy)
13709 int jx, jy, dx, dy, xx, yy;
13711 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13714 // diagonal direction: check alternative direction
13719 xx = jx + (dx == 0 ? real_dx : 0);
13720 yy = jy + (dy == 0 ? real_dy : 0);
13722 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy]));
13726 =============================================================================
13728 -----------------------------------------------------------------------------
13729 x, y: field next to player (non-diagonal) to try to dig to
13730 real_dx, real_dy: direction as read from input device (can be diagonal)
13731 =============================================================================
13734 static int DigField(struct PlayerInfo *player,
13735 int oldx, int oldy, int x, int y,
13736 int real_dx, int real_dy, int mode)
13738 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13739 boolean player_was_pushing = player->is_pushing;
13740 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13741 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13742 int jx = oldx, jy = oldy;
13743 int dx = x - jx, dy = y - jy;
13744 int nextx = x + dx, nexty = y + dy;
13745 int move_direction = (dx == -1 ? MV_LEFT :
13746 dx == +1 ? MV_RIGHT :
13748 dy == +1 ? MV_DOWN : MV_NONE);
13749 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13750 int dig_side = MV_DIR_OPPOSITE(move_direction);
13751 int old_element = Feld[jx][jy];
13752 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13755 if (is_player) // function can also be called by EL_PENGUIN
13757 if (player->MovPos == 0)
13759 player->is_digging = FALSE;
13760 player->is_collecting = FALSE;
13763 if (player->MovPos == 0) // last pushing move finished
13764 player->is_pushing = FALSE;
13766 if (mode == DF_NO_PUSH) // player just stopped pushing
13768 player->is_switching = FALSE;
13769 player->push_delay = -1;
13771 return MP_NO_ACTION;
13775 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13776 old_element = Back[jx][jy];
13778 // in case of element dropped at player position, check background
13779 else if (Back[jx][jy] != EL_EMPTY &&
13780 game.engine_version >= VERSION_IDENT(2,2,0,0))
13781 old_element = Back[jx][jy];
13783 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13784 return MP_NO_ACTION; // field has no opening in this direction
13786 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13787 return MP_NO_ACTION; // field has no opening in this direction
13789 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13793 Feld[jx][jy] = player->artwork_element;
13794 InitMovingField(jx, jy, MV_DOWN);
13795 Store[jx][jy] = EL_ACID;
13796 ContinueMoving(jx, jy);
13797 BuryPlayer(player);
13799 return MP_DONT_RUN_INTO;
13802 if (player_can_move && DONT_RUN_INTO(element))
13804 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13806 return MP_DONT_RUN_INTO;
13809 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13810 return MP_NO_ACTION;
13812 collect_count = element_info[element].collect_count_initial;
13814 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13815 return MP_NO_ACTION;
13817 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13818 player_can_move = player_can_move_or_snap;
13820 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13821 game.engine_version >= VERSION_IDENT(2,2,0,0))
13823 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13824 player->index_bit, dig_side);
13825 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13826 player->index_bit, dig_side);
13828 if (element == EL_DC_LANDMINE)
13831 if (Feld[x][y] != element) // field changed by snapping
13834 return MP_NO_ACTION;
13837 if (player->gravity && is_player && !player->is_auto_moving &&
13838 canFallDown(player) && move_direction != MV_DOWN &&
13839 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13840 return MP_NO_ACTION; // player cannot walk here due to gravity
13842 if (player_can_move &&
13843 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13845 int sound_element = SND_ELEMENT(element);
13846 int sound_action = ACTION_WALKING;
13848 if (IS_RND_GATE(element))
13850 if (!player->key[RND_GATE_NR(element)])
13851 return MP_NO_ACTION;
13853 else if (IS_RND_GATE_GRAY(element))
13855 if (!player->key[RND_GATE_GRAY_NR(element)])
13856 return MP_NO_ACTION;
13858 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13860 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13861 return MP_NO_ACTION;
13863 else if (element == EL_EXIT_OPEN ||
13864 element == EL_EM_EXIT_OPEN ||
13865 element == EL_EM_EXIT_OPENING ||
13866 element == EL_STEEL_EXIT_OPEN ||
13867 element == EL_EM_STEEL_EXIT_OPEN ||
13868 element == EL_EM_STEEL_EXIT_OPENING ||
13869 element == EL_SP_EXIT_OPEN ||
13870 element == EL_SP_EXIT_OPENING)
13872 sound_action = ACTION_PASSING; // player is passing exit
13874 else if (element == EL_EMPTY)
13876 sound_action = ACTION_MOVING; // nothing to walk on
13879 // play sound from background or player, whatever is available
13880 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13881 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13883 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13885 else if (player_can_move &&
13886 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13888 if (!ACCESS_FROM(element, opposite_direction))
13889 return MP_NO_ACTION; // field not accessible from this direction
13891 if (CAN_MOVE(element)) // only fixed elements can be passed!
13892 return MP_NO_ACTION;
13894 if (IS_EM_GATE(element))
13896 if (!player->key[EM_GATE_NR(element)])
13897 return MP_NO_ACTION;
13899 else if (IS_EM_GATE_GRAY(element))
13901 if (!player->key[EM_GATE_GRAY_NR(element)])
13902 return MP_NO_ACTION;
13904 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13906 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13907 return MP_NO_ACTION;
13909 else if (IS_EMC_GATE(element))
13911 if (!player->key[EMC_GATE_NR(element)])
13912 return MP_NO_ACTION;
13914 else if (IS_EMC_GATE_GRAY(element))
13916 if (!player->key[EMC_GATE_GRAY_NR(element)])
13917 return MP_NO_ACTION;
13919 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13921 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13922 return MP_NO_ACTION;
13924 else if (element == EL_DC_GATE_WHITE ||
13925 element == EL_DC_GATE_WHITE_GRAY ||
13926 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13928 if (player->num_white_keys == 0)
13929 return MP_NO_ACTION;
13931 player->num_white_keys--;
13933 else if (IS_SP_PORT(element))
13935 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13936 element == EL_SP_GRAVITY_PORT_RIGHT ||
13937 element == EL_SP_GRAVITY_PORT_UP ||
13938 element == EL_SP_GRAVITY_PORT_DOWN)
13939 player->gravity = !player->gravity;
13940 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13941 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13942 element == EL_SP_GRAVITY_ON_PORT_UP ||
13943 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13944 player->gravity = TRUE;
13945 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13946 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13947 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13948 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13949 player->gravity = FALSE;
13952 // automatically move to the next field with double speed
13953 player->programmed_action = move_direction;
13955 if (player->move_delay_reset_counter == 0)
13957 player->move_delay_reset_counter = 2; // two double speed steps
13959 DOUBLE_PLAYER_SPEED(player);
13962 PlayLevelSoundAction(x, y, ACTION_PASSING);
13964 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13968 if (mode != DF_SNAP)
13970 GfxElement[x][y] = GFX_ELEMENT(element);
13971 player->is_digging = TRUE;
13974 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13976 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13977 player->index_bit, dig_side);
13979 if (mode == DF_SNAP)
13981 if (level.block_snap_field)
13982 setFieldForSnapping(x, y, element, move_direction);
13984 TestIfElementTouchesCustomElement(x, y); // for empty space
13986 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13987 player->index_bit, dig_side);
13990 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
13994 if (is_player && mode != DF_SNAP)
13996 GfxElement[x][y] = element;
13997 player->is_collecting = TRUE;
14000 if (element == EL_SPEED_PILL)
14002 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14004 else if (element == EL_EXTRA_TIME && level.time > 0)
14006 TimeLeft += level.extra_time;
14008 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14010 DisplayGameControlValues();
14012 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14014 player->shield_normal_time_left += level.shield_normal_time;
14015 if (element == EL_SHIELD_DEADLY)
14016 player->shield_deadly_time_left += level.shield_deadly_time;
14018 else if (element == EL_DYNAMITE ||
14019 element == EL_EM_DYNAMITE ||
14020 element == EL_SP_DISK_RED)
14022 if (player->inventory_size < MAX_INVENTORY_SIZE)
14023 player->inventory_element[player->inventory_size++] = element;
14025 DrawGameDoorValues();
14027 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14029 player->dynabomb_count++;
14030 player->dynabombs_left++;
14032 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14034 player->dynabomb_size++;
14036 else if (element == EL_DYNABOMB_INCREASE_POWER)
14038 player->dynabomb_xl = TRUE;
14040 else if (IS_KEY(element))
14042 player->key[KEY_NR(element)] = TRUE;
14044 DrawGameDoorValues();
14046 else if (element == EL_DC_KEY_WHITE)
14048 player->num_white_keys++;
14050 // display white keys?
14051 // DrawGameDoorValues();
14053 else if (IS_ENVELOPE(element))
14055 player->show_envelope = element;
14057 else if (element == EL_EMC_LENSES)
14059 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14061 RedrawAllInvisibleElementsForLenses();
14063 else if (element == EL_EMC_MAGNIFIER)
14065 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14067 RedrawAllInvisibleElementsForMagnifier();
14069 else if (IS_DROPPABLE(element) ||
14070 IS_THROWABLE(element)) // can be collected and dropped
14074 if (collect_count == 0)
14075 player->inventory_infinite_element = element;
14077 for (i = 0; i < collect_count; i++)
14078 if (player->inventory_size < MAX_INVENTORY_SIZE)
14079 player->inventory_element[player->inventory_size++] = element;
14081 DrawGameDoorValues();
14083 else if (collect_count > 0)
14085 game.gems_still_needed -= collect_count;
14086 if (game.gems_still_needed < 0)
14087 game.gems_still_needed = 0;
14089 game.snapshot.collected_item = TRUE;
14091 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14093 DisplayGameControlValues();
14096 RaiseScoreElement(element);
14097 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14100 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14101 player->index_bit, dig_side);
14103 if (mode == DF_SNAP)
14105 if (level.block_snap_field)
14106 setFieldForSnapping(x, y, element, move_direction);
14108 TestIfElementTouchesCustomElement(x, y); // for empty space
14110 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14111 player->index_bit, dig_side);
14114 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14116 if (mode == DF_SNAP && element != EL_BD_ROCK)
14117 return MP_NO_ACTION;
14119 if (CAN_FALL(element) && dy)
14120 return MP_NO_ACTION;
14122 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14123 !(element == EL_SPRING && level.use_spring_bug))
14124 return MP_NO_ACTION;
14126 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14127 ((move_direction & MV_VERTICAL &&
14128 ((element_info[element].move_pattern & MV_LEFT &&
14129 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14130 (element_info[element].move_pattern & MV_RIGHT &&
14131 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14132 (move_direction & MV_HORIZONTAL &&
14133 ((element_info[element].move_pattern & MV_UP &&
14134 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14135 (element_info[element].move_pattern & MV_DOWN &&
14136 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14137 return MP_NO_ACTION;
14139 // do not push elements already moving away faster than player
14140 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14141 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14142 return MP_NO_ACTION;
14144 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14146 if (player->push_delay_value == -1 || !player_was_pushing)
14147 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14149 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14151 if (player->push_delay_value == -1)
14152 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14154 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14156 if (!player->is_pushing)
14157 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14160 player->is_pushing = TRUE;
14161 player->is_active = TRUE;
14163 if (!(IN_LEV_FIELD(nextx, nexty) &&
14164 (IS_FREE(nextx, nexty) ||
14165 (IS_SB_ELEMENT(element) &&
14166 Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14167 (IS_CUSTOM_ELEMENT(element) &&
14168 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14169 return MP_NO_ACTION;
14171 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14172 return MP_NO_ACTION;
14174 if (player->push_delay == -1) // new pushing; restart delay
14175 player->push_delay = 0;
14177 if (player->push_delay < player->push_delay_value &&
14178 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14179 element != EL_SPRING && element != EL_BALLOON)
14181 // make sure that there is no move delay before next try to push
14182 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14183 player->move_delay = 0;
14185 return MP_NO_ACTION;
14188 if (IS_CUSTOM_ELEMENT(element) &&
14189 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14191 if (!DigFieldByCE(nextx, nexty, element))
14192 return MP_NO_ACTION;
14195 if (IS_SB_ELEMENT(element))
14197 boolean sokoban_task_solved = FALSE;
14199 if (element == EL_SOKOBAN_FIELD_FULL)
14201 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14203 IncrementSokobanFieldsNeeded();
14204 IncrementSokobanObjectsNeeded();
14207 if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14209 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14211 DecrementSokobanFieldsNeeded();
14212 DecrementSokobanObjectsNeeded();
14214 // sokoban object was pushed from empty field to sokoban field
14215 if (Back[x][y] == EL_EMPTY)
14216 sokoban_task_solved = TRUE;
14219 Feld[x][y] = EL_SOKOBAN_OBJECT;
14221 if (Back[x][y] == Back[nextx][nexty])
14222 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14223 else if (Back[x][y] != 0)
14224 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14227 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14230 if (sokoban_task_solved &&
14231 game.sokoban_fields_still_needed == 0 &&
14232 game.sokoban_objects_still_needed == 0 &&
14233 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14235 game.players_still_needed = 0;
14239 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14243 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14245 InitMovingField(x, y, move_direction);
14246 GfxAction[x][y] = ACTION_PUSHING;
14248 if (mode == DF_SNAP)
14249 ContinueMoving(x, y);
14251 MovPos[x][y] = (dx != 0 ? dx : dy);
14253 Pushed[x][y] = TRUE;
14254 Pushed[nextx][nexty] = TRUE;
14256 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14257 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14259 player->push_delay_value = -1; // get new value later
14261 // check for element change _after_ element has been pushed
14262 if (game.use_change_when_pushing_bug)
14264 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14265 player->index_bit, dig_side);
14266 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14267 player->index_bit, dig_side);
14270 else if (IS_SWITCHABLE(element))
14272 if (PLAYER_SWITCHING(player, x, y))
14274 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14275 player->index_bit, dig_side);
14280 player->is_switching = TRUE;
14281 player->switch_x = x;
14282 player->switch_y = y;
14284 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14286 if (element == EL_ROBOT_WHEEL)
14288 Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14290 game.robot_wheel_x = x;
14291 game.robot_wheel_y = y;
14292 game.robot_wheel_active = TRUE;
14294 TEST_DrawLevelField(x, y);
14296 else if (element == EL_SP_TERMINAL)
14300 SCAN_PLAYFIELD(xx, yy)
14302 if (Feld[xx][yy] == EL_SP_DISK_YELLOW)
14306 else if (Feld[xx][yy] == EL_SP_TERMINAL)
14308 Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14310 ResetGfxAnimation(xx, yy);
14311 TEST_DrawLevelField(xx, yy);
14315 else if (IS_BELT_SWITCH(element))
14317 ToggleBeltSwitch(x, y);
14319 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14320 element == EL_SWITCHGATE_SWITCH_DOWN ||
14321 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14322 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14324 ToggleSwitchgateSwitch(x, y);
14326 else if (element == EL_LIGHT_SWITCH ||
14327 element == EL_LIGHT_SWITCH_ACTIVE)
14329 ToggleLightSwitch(x, y);
14331 else if (element == EL_TIMEGATE_SWITCH ||
14332 element == EL_DC_TIMEGATE_SWITCH)
14334 ActivateTimegateSwitch(x, y);
14336 else if (element == EL_BALLOON_SWITCH_LEFT ||
14337 element == EL_BALLOON_SWITCH_RIGHT ||
14338 element == EL_BALLOON_SWITCH_UP ||
14339 element == EL_BALLOON_SWITCH_DOWN ||
14340 element == EL_BALLOON_SWITCH_NONE ||
14341 element == EL_BALLOON_SWITCH_ANY)
14343 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14344 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14345 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14346 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14347 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14350 else if (element == EL_LAMP)
14352 Feld[x][y] = EL_LAMP_ACTIVE;
14353 game.lights_still_needed--;
14355 ResetGfxAnimation(x, y);
14356 TEST_DrawLevelField(x, y);
14358 else if (element == EL_TIME_ORB_FULL)
14360 Feld[x][y] = EL_TIME_ORB_EMPTY;
14362 if (level.time > 0 || level.use_time_orb_bug)
14364 TimeLeft += level.time_orb_time;
14365 game.no_time_limit = FALSE;
14367 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14369 DisplayGameControlValues();
14372 ResetGfxAnimation(x, y);
14373 TEST_DrawLevelField(x, y);
14375 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14376 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14380 game.ball_active = !game.ball_active;
14382 SCAN_PLAYFIELD(xx, yy)
14384 int e = Feld[xx][yy];
14386 if (game.ball_active)
14388 if (e == EL_EMC_MAGIC_BALL)
14389 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14390 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14391 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14395 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14396 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14397 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14398 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14403 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14404 player->index_bit, dig_side);
14406 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14407 player->index_bit, dig_side);
14409 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14410 player->index_bit, dig_side);
14416 if (!PLAYER_SWITCHING(player, x, y))
14418 player->is_switching = TRUE;
14419 player->switch_x = x;
14420 player->switch_y = y;
14422 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14423 player->index_bit, dig_side);
14424 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14425 player->index_bit, dig_side);
14427 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14428 player->index_bit, dig_side);
14429 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14430 player->index_bit, dig_side);
14433 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14434 player->index_bit, dig_side);
14435 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14436 player->index_bit, dig_side);
14438 return MP_NO_ACTION;
14441 player->push_delay = -1;
14443 if (is_player) // function can also be called by EL_PENGUIN
14445 if (Feld[x][y] != element) // really digged/collected something
14447 player->is_collecting = !player->is_digging;
14448 player->is_active = TRUE;
14455 static boolean DigFieldByCE(int x, int y, int digging_element)
14457 int element = Feld[x][y];
14459 if (!IS_FREE(x, y))
14461 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14462 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14465 // no element can dig solid indestructible elements
14466 if (IS_INDESTRUCTIBLE(element) &&
14467 !IS_DIGGABLE(element) &&
14468 !IS_COLLECTIBLE(element))
14471 if (AmoebaNr[x][y] &&
14472 (element == EL_AMOEBA_FULL ||
14473 element == EL_BD_AMOEBA ||
14474 element == EL_AMOEBA_GROWING))
14476 AmoebaCnt[AmoebaNr[x][y]]--;
14477 AmoebaCnt2[AmoebaNr[x][y]]--;
14480 if (IS_MOVING(x, y))
14481 RemoveMovingField(x, y);
14485 TEST_DrawLevelField(x, y);
14488 // if digged element was about to explode, prevent the explosion
14489 ExplodeField[x][y] = EX_TYPE_NONE;
14491 PlayLevelSoundAction(x, y, action);
14494 Store[x][y] = EL_EMPTY;
14496 // this makes it possible to leave the removed element again
14497 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14498 Store[x][y] = element;
14503 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14505 int jx = player->jx, jy = player->jy;
14506 int x = jx + dx, y = jy + dy;
14507 int snap_direction = (dx == -1 ? MV_LEFT :
14508 dx == +1 ? MV_RIGHT :
14510 dy == +1 ? MV_DOWN : MV_NONE);
14511 boolean can_continue_snapping = (level.continuous_snapping &&
14512 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14514 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14517 if (!player->active || !IN_LEV_FIELD(x, y))
14525 if (player->MovPos == 0)
14526 player->is_pushing = FALSE;
14528 player->is_snapping = FALSE;
14530 if (player->MovPos == 0)
14532 player->is_moving = FALSE;
14533 player->is_digging = FALSE;
14534 player->is_collecting = FALSE;
14540 // prevent snapping with already pressed snap key when not allowed
14541 if (player->is_snapping && !can_continue_snapping)
14544 player->MovDir = snap_direction;
14546 if (player->MovPos == 0)
14548 player->is_moving = FALSE;
14549 player->is_digging = FALSE;
14550 player->is_collecting = FALSE;
14553 player->is_dropping = FALSE;
14554 player->is_dropping_pressed = FALSE;
14555 player->drop_pressed_delay = 0;
14557 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14560 player->is_snapping = TRUE;
14561 player->is_active = TRUE;
14563 if (player->MovPos == 0)
14565 player->is_moving = FALSE;
14566 player->is_digging = FALSE;
14567 player->is_collecting = FALSE;
14570 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14571 TEST_DrawLevelField(player->last_jx, player->last_jy);
14573 TEST_DrawLevelField(x, y);
14578 static boolean DropElement(struct PlayerInfo *player)
14580 int old_element, new_element;
14581 int dropx = player->jx, dropy = player->jy;
14582 int drop_direction = player->MovDir;
14583 int drop_side = drop_direction;
14584 int drop_element = get_next_dropped_element(player);
14586 /* do not drop an element on top of another element; when holding drop key
14587 pressed without moving, dropped element must move away before the next
14588 element can be dropped (this is especially important if the next element
14589 is dynamite, which can be placed on background for historical reasons) */
14590 if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY)
14593 if (IS_THROWABLE(drop_element))
14595 dropx += GET_DX_FROM_DIR(drop_direction);
14596 dropy += GET_DY_FROM_DIR(drop_direction);
14598 if (!IN_LEV_FIELD(dropx, dropy))
14602 old_element = Feld[dropx][dropy]; // old element at dropping position
14603 new_element = drop_element; // default: no change when dropping
14605 // check if player is active, not moving and ready to drop
14606 if (!player->active || player->MovPos || player->drop_delay > 0)
14609 // check if player has anything that can be dropped
14610 if (new_element == EL_UNDEFINED)
14613 // only set if player has anything that can be dropped
14614 player->is_dropping_pressed = TRUE;
14616 // check if drop key was pressed long enough for EM style dynamite
14617 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14620 // check if anything can be dropped at the current position
14621 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14624 // collected custom elements can only be dropped on empty fields
14625 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14628 if (old_element != EL_EMPTY)
14629 Back[dropx][dropy] = old_element; // store old element on this field
14631 ResetGfxAnimation(dropx, dropy);
14632 ResetRandomAnimationValue(dropx, dropy);
14634 if (player->inventory_size > 0 ||
14635 player->inventory_infinite_element != EL_UNDEFINED)
14637 if (player->inventory_size > 0)
14639 player->inventory_size--;
14641 DrawGameDoorValues();
14643 if (new_element == EL_DYNAMITE)
14644 new_element = EL_DYNAMITE_ACTIVE;
14645 else if (new_element == EL_EM_DYNAMITE)
14646 new_element = EL_EM_DYNAMITE_ACTIVE;
14647 else if (new_element == EL_SP_DISK_RED)
14648 new_element = EL_SP_DISK_RED_ACTIVE;
14651 Feld[dropx][dropy] = new_element;
14653 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14654 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14655 el2img(Feld[dropx][dropy]), 0);
14657 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14659 // needed if previous element just changed to "empty" in the last frame
14660 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14662 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14663 player->index_bit, drop_side);
14664 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14666 player->index_bit, drop_side);
14668 TestIfElementTouchesCustomElement(dropx, dropy);
14670 else // player is dropping a dyna bomb
14672 player->dynabombs_left--;
14674 Feld[dropx][dropy] = new_element;
14676 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14677 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14678 el2img(Feld[dropx][dropy]), 0);
14680 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14683 if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change
14684 InitField_WithBug1(dropx, dropy, FALSE);
14686 new_element = Feld[dropx][dropy]; // element might have changed
14688 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14689 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14691 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14692 MovDir[dropx][dropy] = drop_direction;
14694 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14696 // do not cause impact style collision by dropping elements that can fall
14697 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14700 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14701 player->is_dropping = TRUE;
14703 player->drop_pressed_delay = 0;
14704 player->is_dropping_pressed = FALSE;
14706 player->drop_x = dropx;
14707 player->drop_y = dropy;
14712 // ----------------------------------------------------------------------------
14713 // game sound playing functions
14714 // ----------------------------------------------------------------------------
14716 static int *loop_sound_frame = NULL;
14717 static int *loop_sound_volume = NULL;
14719 void InitPlayLevelSound(void)
14721 int num_sounds = getSoundListSize();
14723 checked_free(loop_sound_frame);
14724 checked_free(loop_sound_volume);
14726 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14727 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14730 static void PlayLevelSound(int x, int y, int nr)
14732 int sx = SCREENX(x), sy = SCREENY(y);
14733 int volume, stereo_position;
14734 int max_distance = 8;
14735 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14737 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14738 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14741 if (!IN_LEV_FIELD(x, y) ||
14742 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14743 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14746 volume = SOUND_MAX_VOLUME;
14748 if (!IN_SCR_FIELD(sx, sy))
14750 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14751 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14753 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14756 stereo_position = (SOUND_MAX_LEFT +
14757 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14758 (SCR_FIELDX + 2 * max_distance));
14760 if (IS_LOOP_SOUND(nr))
14762 /* This assures that quieter loop sounds do not overwrite louder ones,
14763 while restarting sound volume comparison with each new game frame. */
14765 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14768 loop_sound_volume[nr] = volume;
14769 loop_sound_frame[nr] = FrameCounter;
14772 PlaySoundExt(nr, volume, stereo_position, type);
14775 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14777 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14778 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14779 y < LEVELY(BY1) ? LEVELY(BY1) :
14780 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14784 static void PlayLevelSoundAction(int x, int y, int action)
14786 PlayLevelSoundElementAction(x, y, Feld[x][y], action);
14789 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14791 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14793 if (sound_effect != SND_UNDEFINED)
14794 PlayLevelSound(x, y, sound_effect);
14797 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14800 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14802 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14803 PlayLevelSound(x, y, sound_effect);
14806 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14808 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14810 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14811 PlayLevelSound(x, y, sound_effect);
14814 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14816 int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action];
14818 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14819 StopSound(sound_effect);
14822 static int getLevelMusicNr(void)
14824 if (levelset.music[level_nr] != MUS_UNDEFINED)
14825 return levelset.music[level_nr]; // from config file
14827 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14830 static void FadeLevelSounds(void)
14835 static void FadeLevelMusic(void)
14837 int music_nr = getLevelMusicNr();
14838 char *curr_music = getCurrentlyPlayingMusicFilename();
14839 char *next_music = getMusicInfoEntryFilename(music_nr);
14841 if (!strEqual(curr_music, next_music))
14845 void FadeLevelSoundsAndMusic(void)
14851 static void PlayLevelMusic(void)
14853 int music_nr = getLevelMusicNr();
14854 char *curr_music = getCurrentlyPlayingMusicFilename();
14855 char *next_music = getMusicInfoEntryFilename(music_nr);
14857 if (!strEqual(curr_music, next_music))
14858 PlayMusicLoop(music_nr);
14861 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14863 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14865 int x = xx - offset;
14866 int y = yy - offset;
14868 x = correctLevelPosX_EM(x);
14869 y = correctLevelPosY_EM(y);
14874 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14878 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14882 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14886 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14890 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14894 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14898 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14901 case SOUND_android_clone:
14902 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14905 case SOUND_android_move:
14906 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14910 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14914 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14918 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14921 case SOUND_eater_eat:
14922 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14926 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14929 case SOUND_collect:
14930 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14933 case SOUND_diamond:
14934 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14938 // !!! CHECK THIS !!!
14940 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14942 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14946 case SOUND_wonderfall:
14947 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14951 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14955 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14959 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14963 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14967 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14971 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14975 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14979 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14982 case SOUND_exit_open:
14983 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14986 case SOUND_exit_leave:
14987 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14990 case SOUND_dynamite:
14991 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14995 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14999 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15003 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15007 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15011 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15015 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15019 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15024 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15026 int element = map_element_SP_to_RND(element_sp);
15027 int action = map_action_SP_to_RND(action_sp);
15028 int offset = (setup.sp_show_border_elements ? 0 : 1);
15029 int x = xx - offset;
15030 int y = yy - offset;
15032 PlayLevelSoundElementAction(x, y, element, action);
15035 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15037 int element = map_element_MM_to_RND(element_mm);
15038 int action = map_action_MM_to_RND(action_mm);
15040 int x = xx - offset;
15041 int y = yy - offset;
15043 if (!IS_MM_ELEMENT(element))
15044 element = EL_MM_DEFAULT;
15046 PlayLevelSoundElementAction(x, y, element, action);
15049 void PlaySound_MM(int sound_mm)
15051 int sound = map_sound_MM_to_RND(sound_mm);
15053 if (sound == SND_UNDEFINED)
15059 void PlaySoundLoop_MM(int sound_mm)
15061 int sound = map_sound_MM_to_RND(sound_mm);
15063 if (sound == SND_UNDEFINED)
15066 PlaySoundLoop(sound);
15069 void StopSound_MM(int sound_mm)
15071 int sound = map_sound_MM_to_RND(sound_mm);
15073 if (sound == SND_UNDEFINED)
15079 void RaiseScore(int value)
15081 game.score += value;
15083 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15085 DisplayGameControlValues();
15088 void RaiseScoreElement(int element)
15093 case EL_BD_DIAMOND:
15094 case EL_EMERALD_YELLOW:
15095 case EL_EMERALD_RED:
15096 case EL_EMERALD_PURPLE:
15097 case EL_SP_INFOTRON:
15098 RaiseScore(level.score[SC_EMERALD]);
15101 RaiseScore(level.score[SC_DIAMOND]);
15104 RaiseScore(level.score[SC_CRYSTAL]);
15107 RaiseScore(level.score[SC_PEARL]);
15110 case EL_BD_BUTTERFLY:
15111 case EL_SP_ELECTRON:
15112 RaiseScore(level.score[SC_BUG]);
15115 case EL_BD_FIREFLY:
15116 case EL_SP_SNIKSNAK:
15117 RaiseScore(level.score[SC_SPACESHIP]);
15120 case EL_DARK_YAMYAM:
15121 RaiseScore(level.score[SC_YAMYAM]);
15124 RaiseScore(level.score[SC_ROBOT]);
15127 RaiseScore(level.score[SC_PACMAN]);
15130 RaiseScore(level.score[SC_NUT]);
15133 case EL_EM_DYNAMITE:
15134 case EL_SP_DISK_RED:
15135 case EL_DYNABOMB_INCREASE_NUMBER:
15136 case EL_DYNABOMB_INCREASE_SIZE:
15137 case EL_DYNABOMB_INCREASE_POWER:
15138 RaiseScore(level.score[SC_DYNAMITE]);
15140 case EL_SHIELD_NORMAL:
15141 case EL_SHIELD_DEADLY:
15142 RaiseScore(level.score[SC_SHIELD]);
15144 case EL_EXTRA_TIME:
15145 RaiseScore(level.extra_time_score);
15159 case EL_DC_KEY_WHITE:
15160 RaiseScore(level.score[SC_KEY]);
15163 RaiseScore(element_info[element].collect_score);
15168 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15170 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15172 // closing door required in case of envelope style request dialogs
15175 // prevent short reactivation of overlay buttons while closing door
15176 SetOverlayActive(FALSE);
15178 CloseDoor(DOOR_CLOSE_1);
15181 if (network.enabled)
15182 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15186 FadeSkipNextFadeIn();
15188 SetGameStatus(GAME_MODE_MAIN);
15193 else // continue playing the game
15195 if (tape.playing && tape.deactivate_display)
15196 TapeDeactivateDisplayOff(TRUE);
15198 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15200 if (tape.playing && tape.deactivate_display)
15201 TapeDeactivateDisplayOn();
15205 void RequestQuitGame(boolean ask_if_really_quit)
15207 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15208 boolean skip_request = game.all_players_gone || quick_quit;
15210 RequestQuitGameExt(skip_request, quick_quit,
15211 "Do you really want to quit the game?");
15214 void RequestRestartGame(char *message)
15216 game.restart_game_message = NULL;
15218 boolean has_started_game = hasStartedNetworkGame();
15219 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15221 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15223 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15227 SetGameStatus(GAME_MODE_MAIN);
15233 void CheckGameOver(void)
15235 static boolean last_game_over = FALSE;
15236 static int game_over_delay = 0;
15237 int game_over_delay_value = 50;
15238 boolean game_over = checkGameFailed();
15240 // do not handle game over if request dialog is already active
15241 if (game.request_active)
15244 // do not ask to play again if game was never actually played
15245 if (!game.GamePlayed)
15250 last_game_over = FALSE;
15251 game_over_delay = game_over_delay_value;
15256 if (game_over_delay > 0)
15263 if (last_game_over != game_over)
15264 game.restart_game_message = (hasStartedNetworkGame() ?
15265 "Game over! Play it again?" :
15268 last_game_over = game_over;
15271 boolean checkGameSolved(void)
15273 // set for all game engines if level was solved
15274 return game.LevelSolved_GameEnd;
15277 boolean checkGameFailed(void)
15279 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15280 return (game_em.game_over && !game_em.level_solved);
15281 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15282 return (game_sp.game_over && !game_sp.level_solved);
15283 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15284 return (game_mm.game_over && !game_mm.level_solved);
15285 else // GAME_ENGINE_TYPE_RND
15286 return (game.GameOver && !game.LevelSolved);
15289 boolean checkGameEnded(void)
15291 return (checkGameSolved() || checkGameFailed());
15295 // ----------------------------------------------------------------------------
15296 // random generator functions
15297 // ----------------------------------------------------------------------------
15299 unsigned int InitEngineRandom_RND(int seed)
15301 game.num_random_calls = 0;
15303 return InitEngineRandom(seed);
15306 unsigned int RND(int max)
15310 game.num_random_calls++;
15312 return GetEngineRandom(max);
15319 // ----------------------------------------------------------------------------
15320 // game engine snapshot handling functions
15321 // ----------------------------------------------------------------------------
15323 struct EngineSnapshotInfo
15325 // runtime values for custom element collect score
15326 int collect_score[NUM_CUSTOM_ELEMENTS];
15328 // runtime values for group element choice position
15329 int choice_pos[NUM_GROUP_ELEMENTS];
15331 // runtime values for belt position animations
15332 int belt_graphic[4][NUM_BELT_PARTS];
15333 int belt_anim_mode[4][NUM_BELT_PARTS];
15336 static struct EngineSnapshotInfo engine_snapshot_rnd;
15337 static char *snapshot_level_identifier = NULL;
15338 static int snapshot_level_nr = -1;
15340 static void SaveEngineSnapshotValues_RND(void)
15342 static int belt_base_active_element[4] =
15344 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15345 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15346 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15347 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15351 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15353 int element = EL_CUSTOM_START + i;
15355 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15358 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15360 int element = EL_GROUP_START + i;
15362 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15365 for (i = 0; i < 4; i++)
15367 for (j = 0; j < NUM_BELT_PARTS; j++)
15369 int element = belt_base_active_element[i] + j;
15370 int graphic = el2img(element);
15371 int anim_mode = graphic_info[graphic].anim_mode;
15373 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15374 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15379 static void LoadEngineSnapshotValues_RND(void)
15381 unsigned int num_random_calls = game.num_random_calls;
15384 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15386 int element = EL_CUSTOM_START + i;
15388 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15391 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15393 int element = EL_GROUP_START + i;
15395 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15398 for (i = 0; i < 4; i++)
15400 for (j = 0; j < NUM_BELT_PARTS; j++)
15402 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15403 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15405 graphic_info[graphic].anim_mode = anim_mode;
15409 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15411 InitRND(tape.random_seed);
15412 for (i = 0; i < num_random_calls; i++)
15416 if (game.num_random_calls != num_random_calls)
15418 Error(ERR_INFO, "number of random calls out of sync");
15419 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15420 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15421 Error(ERR_EXIT, "this should not happen -- please debug");
15425 void FreeEngineSnapshotSingle(void)
15427 FreeSnapshotSingle();
15429 setString(&snapshot_level_identifier, NULL);
15430 snapshot_level_nr = -1;
15433 void FreeEngineSnapshotList(void)
15435 FreeSnapshotList();
15438 static ListNode *SaveEngineSnapshotBuffers(void)
15440 ListNode *buffers = NULL;
15442 // copy some special values to a structure better suited for the snapshot
15444 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15445 SaveEngineSnapshotValues_RND();
15446 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15447 SaveEngineSnapshotValues_EM();
15448 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15449 SaveEngineSnapshotValues_SP(&buffers);
15450 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15451 SaveEngineSnapshotValues_MM(&buffers);
15453 // save values stored in special snapshot structure
15455 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15456 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15457 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15458 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15459 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15460 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15461 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15462 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15464 // save further RND engine values
15466 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15467 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15468 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15470 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15471 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15472 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15473 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15474 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15476 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15477 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15478 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15480 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15482 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15483 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15485 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld));
15486 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15487 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15488 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15489 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15490 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15491 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15492 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15493 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15494 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15495 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15496 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15497 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15498 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15499 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15500 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15501 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15502 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15504 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15505 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15507 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15508 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15509 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15511 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15512 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15514 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15515 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15516 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15517 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15518 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15520 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15521 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15524 ListNode *node = engine_snapshot_list_rnd;
15527 while (node != NULL)
15529 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15534 printf("::: size of engine snapshot: %d bytes\n", num_bytes);
15540 void SaveEngineSnapshotSingle(void)
15542 ListNode *buffers = SaveEngineSnapshotBuffers();
15544 // finally save all snapshot buffers to single snapshot
15545 SaveSnapshotSingle(buffers);
15547 // save level identification information
15548 setString(&snapshot_level_identifier, leveldir_current->identifier);
15549 snapshot_level_nr = level_nr;
15552 boolean CheckSaveEngineSnapshotToList(void)
15554 boolean save_snapshot =
15555 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15556 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15557 game.snapshot.changed_action) ||
15558 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15559 game.snapshot.collected_item));
15561 game.snapshot.changed_action = FALSE;
15562 game.snapshot.collected_item = FALSE;
15563 game.snapshot.save_snapshot = save_snapshot;
15565 return save_snapshot;
15568 void SaveEngineSnapshotToList(void)
15570 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15574 ListNode *buffers = SaveEngineSnapshotBuffers();
15576 // finally save all snapshot buffers to snapshot list
15577 SaveSnapshotToList(buffers);
15580 void SaveEngineSnapshotToListInitial(void)
15582 FreeEngineSnapshotList();
15584 SaveEngineSnapshotToList();
15587 static void LoadEngineSnapshotValues(void)
15589 // restore special values from snapshot structure
15591 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15592 LoadEngineSnapshotValues_RND();
15593 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15594 LoadEngineSnapshotValues_EM();
15595 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15596 LoadEngineSnapshotValues_SP();
15597 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15598 LoadEngineSnapshotValues_MM();
15601 void LoadEngineSnapshotSingle(void)
15603 LoadSnapshotSingle();
15605 LoadEngineSnapshotValues();
15608 static void LoadEngineSnapshot_Undo(int steps)
15610 LoadSnapshotFromList_Older(steps);
15612 LoadEngineSnapshotValues();
15615 static void LoadEngineSnapshot_Redo(int steps)
15617 LoadSnapshotFromList_Newer(steps);
15619 LoadEngineSnapshotValues();
15622 boolean CheckEngineSnapshotSingle(void)
15624 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15625 snapshot_level_nr == level_nr);
15628 boolean CheckEngineSnapshotList(void)
15630 return CheckSnapshotList();
15634 // ---------- new game button stuff -------------------------------------------
15641 boolean *setup_value;
15642 boolean allowed_on_tape;
15643 boolean is_touch_button;
15645 } gamebutton_info[NUM_GAME_BUTTONS] =
15648 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15649 GAME_CTRL_ID_STOP, NULL,
15650 TRUE, FALSE, "stop game"
15653 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15654 GAME_CTRL_ID_PAUSE, NULL,
15655 TRUE, FALSE, "pause game"
15658 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15659 GAME_CTRL_ID_PLAY, NULL,
15660 TRUE, FALSE, "play game"
15663 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15664 GAME_CTRL_ID_UNDO, NULL,
15665 TRUE, FALSE, "undo step"
15668 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15669 GAME_CTRL_ID_REDO, NULL,
15670 TRUE, FALSE, "redo step"
15673 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15674 GAME_CTRL_ID_SAVE, NULL,
15675 TRUE, FALSE, "save game"
15678 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15679 GAME_CTRL_ID_PAUSE2, NULL,
15680 TRUE, FALSE, "pause game"
15683 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15684 GAME_CTRL_ID_LOAD, NULL,
15685 TRUE, FALSE, "load game"
15688 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15689 GAME_CTRL_ID_PANEL_STOP, NULL,
15690 FALSE, FALSE, "stop game"
15693 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15694 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15695 FALSE, FALSE, "pause game"
15698 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15699 GAME_CTRL_ID_PANEL_PLAY, NULL,
15700 FALSE, FALSE, "play game"
15703 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15704 GAME_CTRL_ID_TOUCH_STOP, NULL,
15705 FALSE, TRUE, "stop game"
15708 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15709 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15710 FALSE, TRUE, "pause game"
15713 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15714 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15715 TRUE, FALSE, "background music on/off"
15718 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15719 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15720 TRUE, FALSE, "sound loops on/off"
15723 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15724 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15725 TRUE, FALSE, "normal sounds on/off"
15728 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15729 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15730 FALSE, FALSE, "background music on/off"
15733 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15734 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15735 FALSE, FALSE, "sound loops on/off"
15738 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15739 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15740 FALSE, FALSE, "normal sounds on/off"
15744 void CreateGameButtons(void)
15748 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15750 int graphic = gamebutton_info[i].graphic;
15751 struct GraphicInfo *gfx = &graphic_info[graphic];
15752 struct XY *pos = gamebutton_info[i].pos;
15753 struct GadgetInfo *gi;
15756 unsigned int event_mask;
15757 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15758 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15759 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15760 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15761 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15762 int gd_x = gfx->src_x;
15763 int gd_y = gfx->src_y;
15764 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15765 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15766 int gd_xa = gfx->src_x + gfx->active_xoffset;
15767 int gd_ya = gfx->src_y + gfx->active_yoffset;
15768 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15769 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15770 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15771 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15774 if (gfx->bitmap == NULL)
15776 game_gadget[id] = NULL;
15781 if (id == GAME_CTRL_ID_STOP ||
15782 id == GAME_CTRL_ID_PANEL_STOP ||
15783 id == GAME_CTRL_ID_TOUCH_STOP ||
15784 id == GAME_CTRL_ID_PLAY ||
15785 id == GAME_CTRL_ID_PANEL_PLAY ||
15786 id == GAME_CTRL_ID_SAVE ||
15787 id == GAME_CTRL_ID_LOAD)
15789 button_type = GD_TYPE_NORMAL_BUTTON;
15791 event_mask = GD_EVENT_RELEASED;
15793 else if (id == GAME_CTRL_ID_UNDO ||
15794 id == GAME_CTRL_ID_REDO)
15796 button_type = GD_TYPE_NORMAL_BUTTON;
15798 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15802 button_type = GD_TYPE_CHECK_BUTTON;
15803 checked = (gamebutton_info[i].setup_value != NULL ?
15804 *gamebutton_info[i].setup_value : FALSE);
15805 event_mask = GD_EVENT_PRESSED;
15808 gi = CreateGadget(GDI_CUSTOM_ID, id,
15809 GDI_IMAGE_ID, graphic,
15810 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15813 GDI_WIDTH, gfx->width,
15814 GDI_HEIGHT, gfx->height,
15815 GDI_TYPE, button_type,
15816 GDI_STATE, GD_BUTTON_UNPRESSED,
15817 GDI_CHECKED, checked,
15818 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15819 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15820 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15821 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15822 GDI_DIRECT_DRAW, FALSE,
15823 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15824 GDI_EVENT_MASK, event_mask,
15825 GDI_CALLBACK_ACTION, HandleGameButtons,
15829 Error(ERR_EXIT, "cannot create gadget");
15831 game_gadget[id] = gi;
15835 void FreeGameButtons(void)
15839 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15840 FreeGadget(game_gadget[i]);
15843 static void UnmapGameButtonsAtSamePosition(int id)
15847 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15849 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15850 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15851 UnmapGadget(game_gadget[i]);
15854 static void UnmapGameButtonsAtSamePosition_All(void)
15856 if (setup.show_snapshot_buttons)
15858 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15859 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15860 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15864 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15865 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15866 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15868 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15869 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15870 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15874 static void MapGameButtonsAtSamePosition(int id)
15878 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15880 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15881 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15882 MapGadget(game_gadget[i]);
15884 UnmapGameButtonsAtSamePosition_All();
15887 void MapUndoRedoButtons(void)
15889 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15890 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15892 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15893 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15896 void UnmapUndoRedoButtons(void)
15898 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15899 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15901 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15902 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15905 void ModifyPauseButtons(void)
15909 GAME_CTRL_ID_PAUSE,
15910 GAME_CTRL_ID_PAUSE2,
15911 GAME_CTRL_ID_PANEL_PAUSE,
15912 GAME_CTRL_ID_TOUCH_PAUSE,
15917 for (i = 0; ids[i] > -1; i++)
15918 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15921 static void MapGameButtonsExt(boolean on_tape)
15925 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15926 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15927 i != GAME_CTRL_ID_UNDO &&
15928 i != GAME_CTRL_ID_REDO)
15929 MapGadget(game_gadget[i]);
15931 UnmapGameButtonsAtSamePosition_All();
15933 RedrawGameButtons();
15936 static void UnmapGameButtonsExt(boolean on_tape)
15940 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15941 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15942 UnmapGadget(game_gadget[i]);
15945 static void RedrawGameButtonsExt(boolean on_tape)
15949 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15950 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15951 RedrawGadget(game_gadget[i]);
15954 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15959 gi->checked = state;
15962 static void RedrawSoundButtonGadget(int id)
15964 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15965 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15966 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15967 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15968 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15969 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15972 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15973 RedrawGadget(game_gadget[id2]);
15976 void MapGameButtons(void)
15978 MapGameButtonsExt(FALSE);
15981 void UnmapGameButtons(void)
15983 UnmapGameButtonsExt(FALSE);
15986 void RedrawGameButtons(void)
15988 RedrawGameButtonsExt(FALSE);
15991 void MapGameButtonsOnTape(void)
15993 MapGameButtonsExt(TRUE);
15996 void UnmapGameButtonsOnTape(void)
15998 UnmapGameButtonsExt(TRUE);
16001 void RedrawGameButtonsOnTape(void)
16003 RedrawGameButtonsExt(TRUE);
16006 static void GameUndoRedoExt(void)
16008 ClearPlayerAction();
16010 tape.pausing = TRUE;
16013 UpdateAndDisplayGameControlValues();
16015 DrawCompleteVideoDisplay();
16016 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16017 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16018 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16023 static void GameUndo(int steps)
16025 if (!CheckEngineSnapshotList())
16028 LoadEngineSnapshot_Undo(steps);
16033 static void GameRedo(int steps)
16035 if (!CheckEngineSnapshotList())
16038 LoadEngineSnapshot_Redo(steps);
16043 static void HandleGameButtonsExt(int id, int button)
16045 static boolean game_undo_executed = FALSE;
16046 int steps = BUTTON_STEPSIZE(button);
16047 boolean handle_game_buttons =
16048 (game_status == GAME_MODE_PLAYING ||
16049 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16051 if (!handle_game_buttons)
16056 case GAME_CTRL_ID_STOP:
16057 case GAME_CTRL_ID_PANEL_STOP:
16058 case GAME_CTRL_ID_TOUCH_STOP:
16059 if (game_status == GAME_MODE_MAIN)
16065 RequestQuitGame(TRUE);
16069 case GAME_CTRL_ID_PAUSE:
16070 case GAME_CTRL_ID_PAUSE2:
16071 case GAME_CTRL_ID_PANEL_PAUSE:
16072 case GAME_CTRL_ID_TOUCH_PAUSE:
16073 if (network.enabled && game_status == GAME_MODE_PLAYING)
16076 SendToServer_ContinuePlaying();
16078 SendToServer_PausePlaying();
16081 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16083 game_undo_executed = FALSE;
16087 case GAME_CTRL_ID_PLAY:
16088 case GAME_CTRL_ID_PANEL_PLAY:
16089 if (game_status == GAME_MODE_MAIN)
16091 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16093 else if (tape.pausing)
16095 if (network.enabled)
16096 SendToServer_ContinuePlaying();
16098 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16102 case GAME_CTRL_ID_UNDO:
16103 // Important: When using "save snapshot when collecting an item" mode,
16104 // load last (current) snapshot for first "undo" after pressing "pause"
16105 // (else the last-but-one snapshot would be loaded, because the snapshot
16106 // pointer already points to the last snapshot when pressing "pause",
16107 // which is fine for "every step/move" mode, but not for "every collect")
16108 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16109 !game_undo_executed)
16112 game_undo_executed = TRUE;
16117 case GAME_CTRL_ID_REDO:
16121 case GAME_CTRL_ID_SAVE:
16125 case GAME_CTRL_ID_LOAD:
16129 case SOUND_CTRL_ID_MUSIC:
16130 case SOUND_CTRL_ID_PANEL_MUSIC:
16131 if (setup.sound_music)
16133 setup.sound_music = FALSE;
16137 else if (audio.music_available)
16139 setup.sound = setup.sound_music = TRUE;
16141 SetAudioMode(setup.sound);
16143 if (game_status == GAME_MODE_PLAYING)
16147 RedrawSoundButtonGadget(id);
16151 case SOUND_CTRL_ID_LOOPS:
16152 case SOUND_CTRL_ID_PANEL_LOOPS:
16153 if (setup.sound_loops)
16154 setup.sound_loops = FALSE;
16155 else if (audio.loops_available)
16157 setup.sound = setup.sound_loops = TRUE;
16159 SetAudioMode(setup.sound);
16162 RedrawSoundButtonGadget(id);
16166 case SOUND_CTRL_ID_SIMPLE:
16167 case SOUND_CTRL_ID_PANEL_SIMPLE:
16168 if (setup.sound_simple)
16169 setup.sound_simple = FALSE;
16170 else if (audio.sound_available)
16172 setup.sound = setup.sound_simple = TRUE;
16174 SetAudioMode(setup.sound);
16177 RedrawSoundButtonGadget(id);
16186 static void HandleGameButtons(struct GadgetInfo *gi)
16188 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16191 void HandleSoundButtonKeys(Key key)
16193 if (key == setup.shortcut.sound_simple)
16194 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16195 else if (key == setup.shortcut.sound_loops)
16196 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16197 else if (key == setup.shortcut.sound_music)
16198 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);