1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://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 Tile[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 Tile[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 Tile[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, Tile[x][y] == EL_EMC_PLANT)
950 #define ANDROID_CAN_CLONE_FIELD(x, y) \
951 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[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, Tile[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(Tile[x][y]))
963 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
966 #define PIG_CAN_ENTER_FIELD(e, x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
969 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
971 Tile[x][y] == EL_EM_EXIT_OPEN || \
972 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
973 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
974 IS_FOOD_PENGUIN(Tile[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) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
986 Tile[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(Tile[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 AmoebaNeighbourNr(int, int);
1096 void AmoebaToDiamond(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 void TestFieldAfterSnapping(int, int, int, int, int);
1124 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1126 // for detection of endless loops, caused by custom element programming
1127 // (using maximal playfield width x 10 is just a rough approximation)
1128 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1130 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1132 if (recursion_loop_detected) \
1135 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1137 recursion_loop_detected = TRUE; \
1138 recursion_loop_element = (e); \
1141 recursion_loop_depth++; \
1144 #define RECURSION_LOOP_DETECTION_END() \
1146 recursion_loop_depth--; \
1149 static int recursion_loop_depth;
1150 static boolean recursion_loop_detected;
1151 static boolean recursion_loop_element;
1153 static int map_player_action[MAX_PLAYERS];
1156 // ----------------------------------------------------------------------------
1157 // definition of elements that automatically change to other elements after
1158 // a specified time, eventually calling a function when changing
1159 // ----------------------------------------------------------------------------
1161 // forward declaration for changer functions
1162 static void InitBuggyBase(int, int);
1163 static void WarnBuggyBase(int, int);
1165 static void InitTrap(int, int);
1166 static void ActivateTrap(int, int);
1167 static void ChangeActiveTrap(int, int);
1169 static void InitRobotWheel(int, int);
1170 static void RunRobotWheel(int, int);
1171 static void StopRobotWheel(int, int);
1173 static void InitTimegateWheel(int, int);
1174 static void RunTimegateWheel(int, int);
1176 static void InitMagicBallDelay(int, int);
1177 static void ActivateMagicBall(int, int);
1179 struct ChangingElementInfo
1184 void (*pre_change_function)(int x, int y);
1185 void (*change_function)(int x, int y);
1186 void (*post_change_function)(int x, int y);
1189 static struct ChangingElementInfo change_delay_list[] =
1224 EL_STEEL_EXIT_OPENING,
1232 EL_STEEL_EXIT_CLOSING,
1233 EL_STEEL_EXIT_CLOSED,
1256 EL_EM_STEEL_EXIT_OPENING,
1257 EL_EM_STEEL_EXIT_OPEN,
1264 EL_EM_STEEL_EXIT_CLOSING,
1288 EL_SWITCHGATE_OPENING,
1296 EL_SWITCHGATE_CLOSING,
1297 EL_SWITCHGATE_CLOSED,
1304 EL_TIMEGATE_OPENING,
1312 EL_TIMEGATE_CLOSING,
1321 EL_ACID_SPLASH_LEFT,
1329 EL_ACID_SPLASH_RIGHT,
1338 EL_SP_BUGGY_BASE_ACTIVATING,
1345 EL_SP_BUGGY_BASE_ACTIVATING,
1346 EL_SP_BUGGY_BASE_ACTIVE,
1353 EL_SP_BUGGY_BASE_ACTIVE,
1377 EL_ROBOT_WHEEL_ACTIVE,
1385 EL_TIMEGATE_SWITCH_ACTIVE,
1393 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1394 EL_DC_TIMEGATE_SWITCH,
1401 EL_EMC_MAGIC_BALL_ACTIVE,
1402 EL_EMC_MAGIC_BALL_ACTIVE,
1409 EL_EMC_SPRING_BUMPER_ACTIVE,
1410 EL_EMC_SPRING_BUMPER,
1417 EL_DIAGONAL_SHRINKING,
1425 EL_DIAGONAL_GROWING,
1446 int push_delay_fixed, push_delay_random;
1450 { EL_SPRING, 0, 0 },
1451 { EL_BALLOON, 0, 0 },
1453 { EL_SOKOBAN_OBJECT, 2, 0 },
1454 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1455 { EL_SATELLITE, 2, 0 },
1456 { EL_SP_DISK_YELLOW, 2, 0 },
1458 { EL_UNDEFINED, 0, 0 },
1466 move_stepsize_list[] =
1468 { EL_AMOEBA_DROP, 2 },
1469 { EL_AMOEBA_DROPPING, 2 },
1470 { EL_QUICKSAND_FILLING, 1 },
1471 { EL_QUICKSAND_EMPTYING, 1 },
1472 { EL_QUICKSAND_FAST_FILLING, 2 },
1473 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1474 { EL_MAGIC_WALL_FILLING, 2 },
1475 { EL_MAGIC_WALL_EMPTYING, 2 },
1476 { EL_BD_MAGIC_WALL_FILLING, 2 },
1477 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1478 { EL_DC_MAGIC_WALL_FILLING, 2 },
1479 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1481 { EL_UNDEFINED, 0 },
1489 collect_count_list[] =
1492 { EL_BD_DIAMOND, 1 },
1493 { EL_EMERALD_YELLOW, 1 },
1494 { EL_EMERALD_RED, 1 },
1495 { EL_EMERALD_PURPLE, 1 },
1497 { EL_SP_INFOTRON, 1 },
1501 { EL_UNDEFINED, 0 },
1509 access_direction_list[] =
1511 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1512 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1513 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1514 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1515 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1516 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1517 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1518 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1519 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1520 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1521 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1523 { EL_SP_PORT_LEFT, MV_RIGHT },
1524 { EL_SP_PORT_RIGHT, MV_LEFT },
1525 { EL_SP_PORT_UP, MV_DOWN },
1526 { EL_SP_PORT_DOWN, MV_UP },
1527 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1528 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1529 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1530 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1531 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1532 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1533 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1534 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1535 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1536 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1537 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1538 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1539 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1540 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1541 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1543 { EL_UNDEFINED, MV_NONE }
1546 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1548 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1549 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1550 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1551 IS_JUST_CHANGING(x, y))
1553 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1555 // static variables for playfield scan mode (scanning forward or backward)
1556 static int playfield_scan_start_x = 0;
1557 static int playfield_scan_start_y = 0;
1558 static int playfield_scan_delta_x = 1;
1559 static int playfield_scan_delta_y = 1;
1561 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1562 (y) >= 0 && (y) <= lev_fieldy - 1; \
1563 (y) += playfield_scan_delta_y) \
1564 for ((x) = playfield_scan_start_x; \
1565 (x) >= 0 && (x) <= lev_fieldx - 1; \
1566 (x) += playfield_scan_delta_x)
1569 void DEBUG_SetMaximumDynamite(void)
1573 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1574 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1575 local_player->inventory_element[local_player->inventory_size++] =
1580 static void InitPlayfieldScanModeVars(void)
1582 if (game.use_reverse_scan_direction)
1584 playfield_scan_start_x = lev_fieldx - 1;
1585 playfield_scan_start_y = lev_fieldy - 1;
1587 playfield_scan_delta_x = -1;
1588 playfield_scan_delta_y = -1;
1592 playfield_scan_start_x = 0;
1593 playfield_scan_start_y = 0;
1595 playfield_scan_delta_x = 1;
1596 playfield_scan_delta_y = 1;
1600 static void InitPlayfieldScanMode(int mode)
1602 game.use_reverse_scan_direction =
1603 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1605 InitPlayfieldScanModeVars();
1608 static int get_move_delay_from_stepsize(int move_stepsize)
1611 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1613 // make sure that stepsize value is always a power of 2
1614 move_stepsize = (1 << log_2(move_stepsize));
1616 return TILEX / move_stepsize;
1619 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1622 int player_nr = player->index_nr;
1623 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1624 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1626 // do no immediately change move delay -- the player might just be moving
1627 player->move_delay_value_next = move_delay;
1629 // information if player can move must be set separately
1630 player->cannot_move = cannot_move;
1634 player->move_delay = game.initial_move_delay[player_nr];
1635 player->move_delay_value = game.initial_move_delay_value[player_nr];
1637 player->move_delay_value_next = -1;
1639 player->move_delay_reset_counter = 0;
1643 void GetPlayerConfig(void)
1645 GameFrameDelay = setup.game_frame_delay;
1647 if (!audio.sound_available)
1648 setup.sound_simple = FALSE;
1650 if (!audio.loops_available)
1651 setup.sound_loops = FALSE;
1653 if (!audio.music_available)
1654 setup.sound_music = FALSE;
1656 if (!video.fullscreen_available)
1657 setup.fullscreen = FALSE;
1659 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1661 SetAudioMode(setup.sound);
1664 int GetElementFromGroupElement(int element)
1666 if (IS_GROUP_ELEMENT(element))
1668 struct ElementGroupInfo *group = element_info[element].group;
1669 int last_anim_random_frame = gfx.anim_random_frame;
1672 if (group->choice_mode == ANIM_RANDOM)
1673 gfx.anim_random_frame = RND(group->num_elements_resolved);
1675 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1676 group->choice_mode, 0,
1679 if (group->choice_mode == ANIM_RANDOM)
1680 gfx.anim_random_frame = last_anim_random_frame;
1682 group->choice_pos++;
1684 element = group->element_resolved[element_pos];
1690 static void IncrementSokobanFieldsNeeded(void)
1692 if (level.sb_fields_needed)
1693 game.sokoban_fields_still_needed++;
1696 static void IncrementSokobanObjectsNeeded(void)
1698 if (level.sb_objects_needed)
1699 game.sokoban_objects_still_needed++;
1702 static void DecrementSokobanFieldsNeeded(void)
1704 if (game.sokoban_fields_still_needed > 0)
1705 game.sokoban_fields_still_needed--;
1708 static void DecrementSokobanObjectsNeeded(void)
1710 if (game.sokoban_objects_still_needed > 0)
1711 game.sokoban_objects_still_needed--;
1714 static void InitPlayerField(int x, int y, int element, boolean init_game)
1716 if (element == EL_SP_MURPHY)
1720 if (stored_player[0].present)
1722 Tile[x][y] = EL_SP_MURPHY_CLONE;
1728 stored_player[0].initial_element = element;
1729 stored_player[0].use_murphy = TRUE;
1731 if (!level.use_artwork_element[0])
1732 stored_player[0].artwork_element = EL_SP_MURPHY;
1735 Tile[x][y] = EL_PLAYER_1;
1741 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1742 int jx = player->jx, jy = player->jy;
1744 player->present = TRUE;
1746 player->block_last_field = (element == EL_SP_MURPHY ?
1747 level.sp_block_last_field :
1748 level.block_last_field);
1750 // ---------- initialize player's last field block delay ------------------
1752 // always start with reliable default value (no adjustment needed)
1753 player->block_delay_adjustment = 0;
1755 // special case 1: in Supaplex, Murphy blocks last field one more frame
1756 if (player->block_last_field && element == EL_SP_MURPHY)
1757 player->block_delay_adjustment = 1;
1759 // special case 2: in game engines before 3.1.1, blocking was different
1760 if (game.use_block_last_field_bug)
1761 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1763 if (!network.enabled || player->connected_network)
1765 player->active = TRUE;
1767 // remove potentially duplicate players
1768 if (StorePlayer[jx][jy] == Tile[x][y])
1769 StorePlayer[jx][jy] = 0;
1771 StorePlayer[x][y] = Tile[x][y];
1773 #if DEBUG_INIT_PLAYER
1774 Debug("game:init:player", "- player element %d activated",
1775 player->element_nr);
1776 Debug("game:init:player", " (local player is %d and currently %s)",
1777 local_player->element_nr,
1778 local_player->active ? "active" : "not active");
1782 Tile[x][y] = EL_EMPTY;
1784 player->jx = player->last_jx = x;
1785 player->jy = player->last_jy = y;
1788 // always check if player was just killed and should be reanimated
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 = Tile[x][y];
1809 InitPlayerField(x, y, element, init_game);
1812 case EL_SOKOBAN_FIELD_PLAYER:
1813 element = Tile[x][y] = EL_PLAYER_1;
1814 InitField(x, y, init_game);
1816 element = Tile[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 && Tile[x+1][y] == EL_ACID)
1830 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1831 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1832 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1833 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1834 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1835 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1836 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1837 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1838 Tile[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:
1880 case EL_SPRING_LEFT:
1881 case EL_SPRING_RIGHT:
1885 case EL_AMOEBA_FULL:
1890 case EL_AMOEBA_DROP:
1891 if (y == lev_fieldy - 1)
1893 Tile[x][y] = EL_AMOEBA_GROWING;
1894 Store[x][y] = EL_AMOEBA_WET;
1898 case EL_DYNAMITE_ACTIVE:
1899 case EL_SP_DISK_RED_ACTIVE:
1900 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1901 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1902 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1903 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1904 MovDelay[x][y] = 96;
1907 case EL_EM_DYNAMITE_ACTIVE:
1908 MovDelay[x][y] = 32;
1912 game.lights_still_needed++;
1916 game.friends_still_needed++;
1921 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1924 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1925 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1926 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1927 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1928 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1929 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1930 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1931 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1932 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1933 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1934 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1935 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1938 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1939 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1940 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1942 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1944 game.belt_dir[belt_nr] = belt_dir;
1945 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1947 else // more than one switch -- set it like the first switch
1949 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1954 case EL_LIGHT_SWITCH_ACTIVE:
1956 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1959 case EL_INVISIBLE_STEELWALL:
1960 case EL_INVISIBLE_WALL:
1961 case EL_INVISIBLE_SAND:
1962 if (game.light_time_left > 0 ||
1963 game.lenses_time_left > 0)
1964 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1967 case EL_EMC_MAGIC_BALL:
1968 if (game.ball_active)
1969 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1972 case EL_EMC_MAGIC_BALL_SWITCH:
1973 if (game.ball_active)
1974 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1977 case EL_TRIGGER_PLAYER:
1978 case EL_TRIGGER_ELEMENT:
1979 case EL_TRIGGER_CE_VALUE:
1980 case EL_TRIGGER_CE_SCORE:
1982 case EL_ANY_ELEMENT:
1983 case EL_CURRENT_CE_VALUE:
1984 case EL_CURRENT_CE_SCORE:
2001 // reference elements should not be used on the playfield
2002 Tile[x][y] = EL_EMPTY;
2006 if (IS_CUSTOM_ELEMENT(element))
2008 if (CAN_MOVE(element))
2011 if (!element_info[element].use_last_ce_value || init_game)
2012 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2014 else if (IS_GROUP_ELEMENT(element))
2016 Tile[x][y] = GetElementFromGroupElement(element);
2018 InitField(x, y, init_game);
2025 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2028 static void InitField_WithBug1(int x, int y, boolean init_game)
2030 InitField(x, y, init_game);
2032 // not needed to call InitMovDir() -- already done by InitField()!
2033 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2034 CAN_MOVE(Tile[x][y]))
2038 static void InitField_WithBug2(int x, int y, boolean init_game)
2040 int old_element = Tile[x][y];
2042 InitField(x, y, init_game);
2044 // not needed to call InitMovDir() -- already done by InitField()!
2045 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2046 CAN_MOVE(old_element) &&
2047 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2050 /* this case is in fact a combination of not less than three bugs:
2051 first, it calls InitMovDir() for elements that can move, although this is
2052 already done by InitField(); then, it checks the element that was at this
2053 field _before_ the call to InitField() (which can change it); lastly, it
2054 was not called for "mole with direction" elements, which were treated as
2055 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2059 static int get_key_element_from_nr(int key_nr)
2061 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2062 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2063 EL_EM_KEY_1 : EL_KEY_1);
2065 return key_base_element + key_nr;
2068 static int get_next_dropped_element(struct PlayerInfo *player)
2070 return (player->inventory_size > 0 ?
2071 player->inventory_element[player->inventory_size - 1] :
2072 player->inventory_infinite_element != EL_UNDEFINED ?
2073 player->inventory_infinite_element :
2074 player->dynabombs_left > 0 ?
2075 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2079 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2081 // pos >= 0: get element from bottom of the stack;
2082 // pos < 0: get element from top of the stack
2086 int min_inventory_size = -pos;
2087 int inventory_pos = player->inventory_size - min_inventory_size;
2088 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2090 return (player->inventory_size >= min_inventory_size ?
2091 player->inventory_element[inventory_pos] :
2092 player->inventory_infinite_element != EL_UNDEFINED ?
2093 player->inventory_infinite_element :
2094 player->dynabombs_left >= min_dynabombs_left ?
2095 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2100 int min_dynabombs_left = pos + 1;
2101 int min_inventory_size = pos + 1 - player->dynabombs_left;
2102 int inventory_pos = pos - player->dynabombs_left;
2104 return (player->inventory_infinite_element != EL_UNDEFINED ?
2105 player->inventory_infinite_element :
2106 player->dynabombs_left >= min_dynabombs_left ?
2107 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2108 player->inventory_size >= min_inventory_size ?
2109 player->inventory_element[inventory_pos] :
2114 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2116 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2117 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2120 if (gpo1->sort_priority != gpo2->sort_priority)
2121 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2123 compare_result = gpo1->nr - gpo2->nr;
2125 return compare_result;
2128 int getPlayerInventorySize(int player_nr)
2130 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2131 return game_em.ply[player_nr]->dynamite;
2132 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2133 return game_sp.red_disk_count;
2135 return stored_player[player_nr].inventory_size;
2138 static void InitGameControlValues(void)
2142 for (i = 0; game_panel_controls[i].nr != -1; i++)
2144 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2145 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2146 struct TextPosInfo *pos = gpc->pos;
2148 int type = gpc->type;
2152 Error("'game_panel_controls' structure corrupted at %d", i);
2154 Fail("this should not happen -- please debug");
2157 // force update of game controls after initialization
2158 gpc->value = gpc->last_value = -1;
2159 gpc->frame = gpc->last_frame = -1;
2160 gpc->gfx_frame = -1;
2162 // determine panel value width for later calculation of alignment
2163 if (type == TYPE_INTEGER || type == TYPE_STRING)
2165 pos->width = pos->size * getFontWidth(pos->font);
2166 pos->height = getFontHeight(pos->font);
2168 else if (type == TYPE_ELEMENT)
2170 pos->width = pos->size;
2171 pos->height = pos->size;
2174 // fill structure for game panel draw order
2176 gpo->sort_priority = pos->sort_priority;
2179 // sort game panel controls according to sort_priority and control number
2180 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2181 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2184 static void UpdatePlayfieldElementCount(void)
2186 boolean use_element_count = FALSE;
2189 // first check if it is needed at all to calculate playfield element count
2190 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2191 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2192 use_element_count = TRUE;
2194 if (!use_element_count)
2197 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2198 element_info[i].element_count = 0;
2200 SCAN_PLAYFIELD(x, y)
2202 element_info[Tile[x][y]].element_count++;
2205 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2206 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2207 if (IS_IN_GROUP(j, i))
2208 element_info[EL_GROUP_START + i].element_count +=
2209 element_info[j].element_count;
2212 static void UpdateGameControlValues(void)
2215 int time = (game.LevelSolved ?
2216 game.LevelSolved_CountingTime :
2217 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2219 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2220 game_sp.time_played :
2221 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2222 game_mm.energy_left :
2223 game.no_time_limit ? TimePlayed : TimeLeft);
2224 int score = (game.LevelSolved ?
2225 game.LevelSolved_CountingScore :
2226 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2227 game_em.lev->score :
2228 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2230 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2233 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2234 game_em.lev->gems_needed :
2235 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2236 game_sp.infotrons_still_needed :
2237 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2238 game_mm.kettles_still_needed :
2239 game.gems_still_needed);
2240 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2241 game_em.lev->gems_needed > 0 :
2242 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2243 game_sp.infotrons_still_needed > 0 :
2244 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2245 game_mm.kettles_still_needed > 0 ||
2246 game_mm.lights_still_needed > 0 :
2247 game.gems_still_needed > 0 ||
2248 game.sokoban_fields_still_needed > 0 ||
2249 game.sokoban_objects_still_needed > 0 ||
2250 game.lights_still_needed > 0);
2251 int health = (game.LevelSolved ?
2252 game.LevelSolved_CountingHealth :
2253 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2254 MM_HEALTH(game_mm.laser_overload_value) :
2256 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2258 UpdatePlayfieldElementCount();
2260 // update game panel control values
2262 // used instead of "level_nr" (for network games)
2263 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2264 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2266 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2267 for (i = 0; i < MAX_NUM_KEYS; i++)
2268 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2269 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2270 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2272 if (game.centered_player_nr == -1)
2274 for (i = 0; i < MAX_PLAYERS; i++)
2276 // only one player in Supaplex game engine
2277 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2280 for (k = 0; k < MAX_NUM_KEYS; k++)
2282 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2284 if (game_em.ply[i]->keys & (1 << k))
2285 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2286 get_key_element_from_nr(k);
2288 else if (stored_player[i].key[k])
2289 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2290 get_key_element_from_nr(k);
2293 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2294 getPlayerInventorySize(i);
2296 if (stored_player[i].num_white_keys > 0)
2297 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2300 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2301 stored_player[i].num_white_keys;
2306 int player_nr = game.centered_player_nr;
2308 for (k = 0; k < MAX_NUM_KEYS; k++)
2310 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2312 if (game_em.ply[player_nr]->keys & (1 << k))
2313 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2314 get_key_element_from_nr(k);
2316 else if (stored_player[player_nr].key[k])
2317 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2318 get_key_element_from_nr(k);
2321 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2322 getPlayerInventorySize(player_nr);
2324 if (stored_player[player_nr].num_white_keys > 0)
2325 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2327 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2328 stored_player[player_nr].num_white_keys;
2331 // re-arrange keys on game panel, if needed or if defined by style settings
2332 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2334 int nr = GAME_PANEL_KEY_1 + i;
2335 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2336 struct TextPosInfo *pos = gpc->pos;
2338 // skip check if key is not in the player's inventory
2339 if (gpc->value == EL_EMPTY)
2342 // check if keys should be arranged on panel from left to right
2343 if (pos->style == STYLE_LEFTMOST_POSITION)
2345 // check previous key positions (left from current key)
2346 for (k = 0; k < i; k++)
2348 int nr_new = GAME_PANEL_KEY_1 + k;
2350 if (game_panel_controls[nr_new].value == EL_EMPTY)
2352 game_panel_controls[nr_new].value = gpc->value;
2353 gpc->value = EL_EMPTY;
2360 // check if "undefined" keys can be placed at some other position
2361 if (pos->x == -1 && pos->y == -1)
2363 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2365 // 1st try: display key at the same position as normal or EM keys
2366 if (game_panel_controls[nr_new].value == EL_EMPTY)
2368 game_panel_controls[nr_new].value = gpc->value;
2372 // 2nd try: display key at the next free position in the key panel
2373 for (k = 0; k < STD_NUM_KEYS; k++)
2375 nr_new = GAME_PANEL_KEY_1 + k;
2377 if (game_panel_controls[nr_new].value == EL_EMPTY)
2379 game_panel_controls[nr_new].value = gpc->value;
2388 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2390 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2391 get_inventory_element_from_pos(local_player, i);
2392 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2393 get_inventory_element_from_pos(local_player, -i - 1);
2396 game_panel_controls[GAME_PANEL_SCORE].value = score;
2397 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2399 game_panel_controls[GAME_PANEL_TIME].value = time;
2401 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2402 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2403 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2405 if (level.time == 0)
2406 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2408 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2410 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2411 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2413 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2415 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2416 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2418 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2419 local_player->shield_normal_time_left;
2420 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2421 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2423 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2424 local_player->shield_deadly_time_left;
2426 game_panel_controls[GAME_PANEL_EXIT].value =
2427 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2429 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2430 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2431 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2432 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2433 EL_EMC_MAGIC_BALL_SWITCH);
2435 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2436 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2437 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2438 game.light_time_left;
2440 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2441 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2442 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2443 game.timegate_time_left;
2445 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2446 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2448 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2449 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2450 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2451 game.lenses_time_left;
2453 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2454 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2455 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2456 game.magnify_time_left;
2458 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2459 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2460 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2461 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2462 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2463 EL_BALLOON_SWITCH_NONE);
2465 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2466 local_player->dynabomb_count;
2467 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2468 local_player->dynabomb_size;
2469 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2470 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2472 game_panel_controls[GAME_PANEL_PENGUINS].value =
2473 game.friends_still_needed;
2475 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2476 game.sokoban_objects_still_needed;
2477 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2478 game.sokoban_fields_still_needed;
2480 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2481 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2483 for (i = 0; i < NUM_BELTS; i++)
2485 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2486 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2487 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2488 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2489 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2492 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2493 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2494 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2495 game.magic_wall_time_left;
2497 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2498 local_player->gravity;
2500 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2501 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2503 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2504 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2505 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2506 game.panel.element[i].id : EL_UNDEFINED);
2508 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2509 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2510 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2511 element_info[game.panel.element_count[i].id].element_count : 0);
2513 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2514 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2515 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2516 element_info[game.panel.ce_score[i].id].collect_score : 0);
2518 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2519 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2520 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2521 element_info[game.panel.ce_score_element[i].id].collect_score :
2524 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2525 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2526 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2528 // update game panel control frames
2530 for (i = 0; game_panel_controls[i].nr != -1; i++)
2532 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2534 if (gpc->type == TYPE_ELEMENT)
2536 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2538 int last_anim_random_frame = gfx.anim_random_frame;
2539 int element = gpc->value;
2540 int graphic = el2panelimg(element);
2541 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2542 sync_random_frame : INIT_GFX_RANDOM());
2544 if (gpc->value != gpc->last_value)
2547 gpc->gfx_random = init_gfx_random;
2553 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2554 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2555 gpc->gfx_random = init_gfx_random;
2558 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2559 gfx.anim_random_frame = gpc->gfx_random;
2561 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2562 gpc->gfx_frame = element_info[element].collect_score;
2564 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2566 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2567 gfx.anim_random_frame = last_anim_random_frame;
2570 else if (gpc->type == TYPE_GRAPHIC)
2572 if (gpc->graphic != IMG_UNDEFINED)
2574 int last_anim_random_frame = gfx.anim_random_frame;
2575 int graphic = gpc->graphic;
2576 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2577 sync_random_frame : INIT_GFX_RANDOM());
2579 if (gpc->value != gpc->last_value)
2582 gpc->gfx_random = init_gfx_random;
2588 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2589 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2590 gpc->gfx_random = init_gfx_random;
2593 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2594 gfx.anim_random_frame = gpc->gfx_random;
2596 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2598 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2599 gfx.anim_random_frame = last_anim_random_frame;
2605 static void DisplayGameControlValues(void)
2607 boolean redraw_panel = FALSE;
2610 for (i = 0; game_panel_controls[i].nr != -1; i++)
2612 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2614 if (PANEL_DEACTIVATED(gpc->pos))
2617 if (gpc->value == gpc->last_value &&
2618 gpc->frame == gpc->last_frame)
2621 redraw_panel = TRUE;
2627 // copy default game door content to main double buffer
2629 // !!! CHECK AGAIN !!!
2630 SetPanelBackground();
2631 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2632 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2634 // redraw game control buttons
2635 RedrawGameButtons();
2637 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2639 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2641 int nr = game_panel_order[i].nr;
2642 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2643 struct TextPosInfo *pos = gpc->pos;
2644 int type = gpc->type;
2645 int value = gpc->value;
2646 int frame = gpc->frame;
2647 int size = pos->size;
2648 int font = pos->font;
2649 boolean draw_masked = pos->draw_masked;
2650 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2652 if (PANEL_DEACTIVATED(pos))
2655 if (pos->class == get_hash_from_key("extra_panel_items") &&
2656 !setup.prefer_extra_panel_items)
2659 gpc->last_value = value;
2660 gpc->last_frame = frame;
2662 if (type == TYPE_INTEGER)
2664 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2665 nr == GAME_PANEL_TIME)
2667 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2669 if (use_dynamic_size) // use dynamic number of digits
2671 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2672 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2673 int size2 = size1 + 1;
2674 int font1 = pos->font;
2675 int font2 = pos->font_alt;
2677 size = (value < value_change ? size1 : size2);
2678 font = (value < value_change ? font1 : font2);
2682 // correct text size if "digits" is zero or less
2684 size = strlen(int2str(value, size));
2686 // dynamically correct text alignment
2687 pos->width = size * getFontWidth(font);
2689 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2690 int2str(value, size), font, mask_mode);
2692 else if (type == TYPE_ELEMENT)
2694 int element, graphic;
2698 int dst_x = PANEL_XPOS(pos);
2699 int dst_y = PANEL_YPOS(pos);
2701 if (value != EL_UNDEFINED && value != EL_EMPTY)
2704 graphic = el2panelimg(value);
2707 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2708 element, EL_NAME(element), size);
2711 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2714 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2717 width = graphic_info[graphic].width * size / TILESIZE;
2718 height = graphic_info[graphic].height * size / TILESIZE;
2721 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2724 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2728 else if (type == TYPE_GRAPHIC)
2730 int graphic = gpc->graphic;
2731 int graphic_active = gpc->graphic_active;
2735 int dst_x = PANEL_XPOS(pos);
2736 int dst_y = PANEL_YPOS(pos);
2737 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2738 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2740 if (graphic != IMG_UNDEFINED && !skip)
2742 if (pos->style == STYLE_REVERSE)
2743 value = 100 - value;
2745 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2747 if (pos->direction & MV_HORIZONTAL)
2749 width = graphic_info[graphic_active].width * value / 100;
2750 height = graphic_info[graphic_active].height;
2752 if (pos->direction == MV_LEFT)
2754 src_x += graphic_info[graphic_active].width - width;
2755 dst_x += graphic_info[graphic_active].width - width;
2760 width = graphic_info[graphic_active].width;
2761 height = graphic_info[graphic_active].height * value / 100;
2763 if (pos->direction == MV_UP)
2765 src_y += graphic_info[graphic_active].height - height;
2766 dst_y += graphic_info[graphic_active].height - height;
2771 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2774 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2777 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2779 if (pos->direction & MV_HORIZONTAL)
2781 if (pos->direction == MV_RIGHT)
2788 dst_x = PANEL_XPOS(pos);
2791 width = graphic_info[graphic].width - width;
2795 if (pos->direction == MV_DOWN)
2802 dst_y = PANEL_YPOS(pos);
2805 height = graphic_info[graphic].height - height;
2809 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2812 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2816 else if (type == TYPE_STRING)
2818 boolean active = (value != 0);
2819 char *state_normal = "off";
2820 char *state_active = "on";
2821 char *state = (active ? state_active : state_normal);
2822 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2823 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2824 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2825 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2827 if (nr == GAME_PANEL_GRAVITY_STATE)
2829 int font1 = pos->font; // (used for normal state)
2830 int font2 = pos->font_alt; // (used for active state)
2832 font = (active ? font2 : font1);
2841 // don't truncate output if "chars" is zero or less
2844 // dynamically correct text alignment
2845 pos->width = size * getFontWidth(font);
2848 s_cut = getStringCopyN(s, size);
2850 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2851 s_cut, font, mask_mode);
2857 redraw_mask |= REDRAW_DOOR_1;
2860 SetGameStatus(GAME_MODE_PLAYING);
2863 void UpdateAndDisplayGameControlValues(void)
2865 if (tape.deactivate_display)
2868 UpdateGameControlValues();
2869 DisplayGameControlValues();
2873 static void UpdateGameDoorValues(void)
2875 UpdateGameControlValues();
2879 void DrawGameDoorValues(void)
2881 DisplayGameControlValues();
2885 // ============================================================================
2887 // ----------------------------------------------------------------------------
2888 // initialize game engine due to level / tape version number
2889 // ============================================================================
2891 static void InitGameEngine(void)
2893 int i, j, k, l, x, y;
2895 // set game engine from tape file when re-playing, else from level file
2896 game.engine_version = (tape.playing ? tape.engine_version :
2897 level.game_version);
2899 // set single or multi-player game mode (needed for re-playing tapes)
2900 game.team_mode = setup.team_mode;
2904 int num_players = 0;
2906 for (i = 0; i < MAX_PLAYERS; i++)
2907 if (tape.player_participates[i])
2910 // multi-player tapes contain input data for more than one player
2911 game.team_mode = (num_players > 1);
2915 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2916 level.game_version);
2917 Debug("game:init:level", " tape.file_version == %06d",
2919 Debug("game:init:level", " tape.game_version == %06d",
2921 Debug("game:init:level", " tape.engine_version == %06d",
2922 tape.engine_version);
2923 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2924 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2927 // --------------------------------------------------------------------------
2928 // set flags for bugs and changes according to active game engine version
2929 // --------------------------------------------------------------------------
2933 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2935 Bug was introduced in version:
2938 Bug was fixed in version:
2942 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2943 but the property "can fall" was missing, which caused some levels to be
2944 unsolvable. This was fixed in version 4.2.0.0.
2946 Affected levels/tapes:
2947 An example for a tape that was fixed by this bugfix is tape 029 from the
2948 level set "rnd_sam_bateman".
2949 The wrong behaviour will still be used for all levels or tapes that were
2950 created/recorded with it. An example for this is tape 023 from the level
2951 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2954 boolean use_amoeba_dropping_cannot_fall_bug =
2955 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2956 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2958 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2959 tape.game_version < VERSION_IDENT(4,2,0,0)));
2962 Summary of bugfix/change:
2963 Fixed move speed of elements entering or leaving magic wall.
2965 Fixed/changed in version:
2969 Before 2.0.1, move speed of elements entering or leaving magic wall was
2970 twice as fast as it is now.
2971 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2973 Affected levels/tapes:
2974 The first condition is generally needed for all levels/tapes before version
2975 2.0.1, which might use the old behaviour before it was changed; known tapes
2976 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2977 The second condition is an exception from the above case and is needed for
2978 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2979 above, but before it was known that this change would break tapes like the
2980 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2981 although the engine version while recording maybe was before 2.0.1. There
2982 are a lot of tapes that are affected by this exception, like tape 006 from
2983 the level set "rnd_conor_mancone".
2986 boolean use_old_move_stepsize_for_magic_wall =
2987 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2989 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2990 tape.game_version < VERSION_IDENT(4,2,0,0)));
2993 Summary of bugfix/change:
2994 Fixed handling for custom elements that change when pushed by the player.
2996 Fixed/changed in version:
3000 Before 3.1.0, custom elements that "change when pushing" changed directly
3001 after the player started pushing them (until then handled in "DigField()").
3002 Since 3.1.0, these custom elements are not changed until the "pushing"
3003 move of the element is finished (now handled in "ContinueMoving()").
3005 Affected levels/tapes:
3006 The first condition is generally needed for all levels/tapes before version
3007 3.1.0, which might use the old behaviour before it was changed; known tapes
3008 that are affected are some tapes from the level set "Walpurgis Gardens" by
3010 The second condition is an exception from the above case and is needed for
3011 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3012 above (including some development versions of 3.1.0), but before it was
3013 known that this change would break tapes like the above and was fixed in
3014 3.1.1, so that the changed behaviour was active although the engine version
3015 while recording maybe was before 3.1.0. There is at least one tape that is
3016 affected by this exception, which is the tape for the one-level set "Bug
3017 Machine" by Juergen Bonhagen.
3020 game.use_change_when_pushing_bug =
3021 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3023 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3024 tape.game_version < VERSION_IDENT(3,1,1,0)));
3027 Summary of bugfix/change:
3028 Fixed handling for blocking the field the player leaves when moving.
3030 Fixed/changed in version:
3034 Before 3.1.1, when "block last field when moving" was enabled, the field
3035 the player is leaving when moving was blocked for the time of the move,
3036 and was directly unblocked afterwards. This resulted in the last field
3037 being blocked for exactly one less than the number of frames of one player
3038 move. Additionally, even when blocking was disabled, the last field was
3039 blocked for exactly one frame.
3040 Since 3.1.1, due to changes in player movement handling, the last field
3041 is not blocked at all when blocking is disabled. When blocking is enabled,
3042 the last field is blocked for exactly the number of frames of one player
3043 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3044 last field is blocked for exactly one more than the number of frames of
3047 Affected levels/tapes:
3048 (!!! yet to be determined -- probably many !!!)
3051 game.use_block_last_field_bug =
3052 (game.engine_version < VERSION_IDENT(3,1,1,0));
3054 /* various special flags and settings for native Emerald Mine game engine */
3056 game_em.use_single_button =
3057 (game.engine_version > VERSION_IDENT(4,0,0,2));
3059 game_em.use_snap_key_bug =
3060 (game.engine_version < VERSION_IDENT(4,0,1,0));
3062 game_em.use_random_bug =
3063 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3065 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3067 game_em.use_old_explosions = use_old_em_engine;
3068 game_em.use_old_android = use_old_em_engine;
3069 game_em.use_old_push_elements = use_old_em_engine;
3070 game_em.use_old_push_into_acid = use_old_em_engine;
3072 game_em.use_wrap_around = !use_old_em_engine;
3074 // --------------------------------------------------------------------------
3076 // set maximal allowed number of custom element changes per game frame
3077 game.max_num_changes_per_frame = 1;
3079 // default scan direction: scan playfield from top/left to bottom/right
3080 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3082 // dynamically adjust element properties according to game engine version
3083 InitElementPropertiesEngine(game.engine_version);
3085 // ---------- initialize special element properties -------------------------
3087 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3088 if (use_amoeba_dropping_cannot_fall_bug)
3089 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3091 // ---------- initialize player's initial move delay ------------------------
3093 // dynamically adjust player properties according to level information
3094 for (i = 0; i < MAX_PLAYERS; i++)
3095 game.initial_move_delay_value[i] =
3096 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3098 // dynamically adjust player properties according to game engine version
3099 for (i = 0; i < MAX_PLAYERS; i++)
3100 game.initial_move_delay[i] =
3101 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3102 game.initial_move_delay_value[i] : 0);
3104 // ---------- initialize player's initial push delay ------------------------
3106 // dynamically adjust player properties according to game engine version
3107 game.initial_push_delay_value =
3108 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3110 // ---------- initialize changing elements ----------------------------------
3112 // initialize changing elements information
3113 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3115 struct ElementInfo *ei = &element_info[i];
3117 // this pointer might have been changed in the level editor
3118 ei->change = &ei->change_page[0];
3120 if (!IS_CUSTOM_ELEMENT(i))
3122 ei->change->target_element = EL_EMPTY_SPACE;
3123 ei->change->delay_fixed = 0;
3124 ei->change->delay_random = 0;
3125 ei->change->delay_frames = 1;
3128 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3130 ei->has_change_event[j] = FALSE;
3132 ei->event_page_nr[j] = 0;
3133 ei->event_page[j] = &ei->change_page[0];
3137 // add changing elements from pre-defined list
3138 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3140 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3141 struct ElementInfo *ei = &element_info[ch_delay->element];
3143 ei->change->target_element = ch_delay->target_element;
3144 ei->change->delay_fixed = ch_delay->change_delay;
3146 ei->change->pre_change_function = ch_delay->pre_change_function;
3147 ei->change->change_function = ch_delay->change_function;
3148 ei->change->post_change_function = ch_delay->post_change_function;
3150 ei->change->can_change = TRUE;
3151 ei->change->can_change_or_has_action = TRUE;
3153 ei->has_change_event[CE_DELAY] = TRUE;
3155 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3156 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3159 // ---------- initialize internal run-time variables ------------------------
3161 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3163 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3165 for (j = 0; j < ei->num_change_pages; j++)
3167 ei->change_page[j].can_change_or_has_action =
3168 (ei->change_page[j].can_change |
3169 ei->change_page[j].has_action);
3173 // add change events from custom element configuration
3174 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3176 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3178 for (j = 0; j < ei->num_change_pages; j++)
3180 if (!ei->change_page[j].can_change_or_has_action)
3183 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3185 // only add event page for the first page found with this event
3186 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3188 ei->has_change_event[k] = TRUE;
3190 ei->event_page_nr[k] = j;
3191 ei->event_page[k] = &ei->change_page[j];
3197 // ---------- initialize reference elements in change conditions ------------
3199 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3201 int element = EL_CUSTOM_START + i;
3202 struct ElementInfo *ei = &element_info[element];
3204 for (j = 0; j < ei->num_change_pages; j++)
3206 int trigger_element = ei->change_page[j].initial_trigger_element;
3208 if (trigger_element >= EL_PREV_CE_8 &&
3209 trigger_element <= EL_NEXT_CE_8)
3210 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3212 ei->change_page[j].trigger_element = trigger_element;
3216 // ---------- initialize run-time trigger player and element ----------------
3218 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3220 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3222 for (j = 0; j < ei->num_change_pages; j++)
3224 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3225 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3226 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3227 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3228 ei->change_page[j].actual_trigger_ce_value = 0;
3229 ei->change_page[j].actual_trigger_ce_score = 0;
3233 // ---------- initialize trigger events -------------------------------------
3235 // initialize trigger events information
3236 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3237 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3238 trigger_events[i][j] = FALSE;
3240 // add trigger events from element change event properties
3241 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3243 struct ElementInfo *ei = &element_info[i];
3245 for (j = 0; j < ei->num_change_pages; j++)
3247 if (!ei->change_page[j].can_change_or_has_action)
3250 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3252 int trigger_element = ei->change_page[j].trigger_element;
3254 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3256 if (ei->change_page[j].has_event[k])
3258 if (IS_GROUP_ELEMENT(trigger_element))
3260 struct ElementGroupInfo *group =
3261 element_info[trigger_element].group;
3263 for (l = 0; l < group->num_elements_resolved; l++)
3264 trigger_events[group->element_resolved[l]][k] = TRUE;
3266 else if (trigger_element == EL_ANY_ELEMENT)
3267 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3268 trigger_events[l][k] = TRUE;
3270 trigger_events[trigger_element][k] = TRUE;
3277 // ---------- initialize push delay -----------------------------------------
3279 // initialize push delay values to default
3280 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3282 if (!IS_CUSTOM_ELEMENT(i))
3284 // set default push delay values (corrected since version 3.0.7-1)
3285 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3287 element_info[i].push_delay_fixed = 2;
3288 element_info[i].push_delay_random = 8;
3292 element_info[i].push_delay_fixed = 8;
3293 element_info[i].push_delay_random = 8;
3298 // set push delay value for certain elements from pre-defined list
3299 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3301 int e = push_delay_list[i].element;
3303 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3304 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3307 // set push delay value for Supaplex elements for newer engine versions
3308 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3310 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3312 if (IS_SP_ELEMENT(i))
3314 // set SP push delay to just enough to push under a falling zonk
3315 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3317 element_info[i].push_delay_fixed = delay;
3318 element_info[i].push_delay_random = 0;
3323 // ---------- initialize move stepsize --------------------------------------
3325 // initialize move stepsize values to default
3326 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3327 if (!IS_CUSTOM_ELEMENT(i))
3328 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3330 // set move stepsize value for certain elements from pre-defined list
3331 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3333 int e = move_stepsize_list[i].element;
3335 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3337 // set move stepsize value for certain elements for older engine versions
3338 if (use_old_move_stepsize_for_magic_wall)
3340 if (e == EL_MAGIC_WALL_FILLING ||
3341 e == EL_MAGIC_WALL_EMPTYING ||
3342 e == EL_BD_MAGIC_WALL_FILLING ||
3343 e == EL_BD_MAGIC_WALL_EMPTYING)
3344 element_info[e].move_stepsize *= 2;
3348 // ---------- initialize collect score --------------------------------------
3350 // initialize collect score values for custom elements from initial value
3351 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3352 if (IS_CUSTOM_ELEMENT(i))
3353 element_info[i].collect_score = element_info[i].collect_score_initial;
3355 // ---------- initialize collect count --------------------------------------
3357 // initialize collect count values for non-custom elements
3358 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3359 if (!IS_CUSTOM_ELEMENT(i))
3360 element_info[i].collect_count_initial = 0;
3362 // add collect count values for all elements from pre-defined list
3363 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3364 element_info[collect_count_list[i].element].collect_count_initial =
3365 collect_count_list[i].count;
3367 // ---------- initialize access direction -----------------------------------
3369 // initialize access direction values to default (access from every side)
3370 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3371 if (!IS_CUSTOM_ELEMENT(i))
3372 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3374 // set access direction value for certain elements from pre-defined list
3375 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3376 element_info[access_direction_list[i].element].access_direction =
3377 access_direction_list[i].direction;
3379 // ---------- initialize explosion content ----------------------------------
3380 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3382 if (IS_CUSTOM_ELEMENT(i))
3385 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3387 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3389 element_info[i].content.e[x][y] =
3390 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3391 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3392 i == EL_PLAYER_3 ? EL_EMERALD :
3393 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3394 i == EL_MOLE ? EL_EMERALD_RED :
3395 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3396 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3397 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3398 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3399 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3400 i == EL_WALL_EMERALD ? EL_EMERALD :
3401 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3402 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3403 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3404 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3405 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3406 i == EL_WALL_PEARL ? EL_PEARL :
3407 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3412 // ---------- initialize recursion detection --------------------------------
3413 recursion_loop_depth = 0;
3414 recursion_loop_detected = FALSE;
3415 recursion_loop_element = EL_UNDEFINED;
3417 // ---------- initialize graphics engine ------------------------------------
3418 game.scroll_delay_value =
3419 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3420 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3421 !setup.forced_scroll_delay ? 0 :
3422 setup.scroll_delay ? setup.scroll_delay_value : 0);
3423 game.scroll_delay_value =
3424 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3426 // ---------- initialize game engine snapshots ------------------------------
3427 for (i = 0; i < MAX_PLAYERS; i++)
3428 game.snapshot.last_action[i] = 0;
3429 game.snapshot.changed_action = FALSE;
3430 game.snapshot.collected_item = FALSE;
3431 game.snapshot.mode =
3432 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3433 SNAPSHOT_MODE_EVERY_STEP :
3434 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3435 SNAPSHOT_MODE_EVERY_MOVE :
3436 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3437 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3438 game.snapshot.save_snapshot = FALSE;
3440 // ---------- initialize level time for Supaplex engine ---------------------
3441 // Supaplex levels with time limit currently unsupported -- should be added
3442 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3445 // ---------- initialize flags for handling game actions --------------------
3447 // set flags for game actions to default values
3448 game.use_key_actions = TRUE;
3449 game.use_mouse_actions = FALSE;
3451 // when using Mirror Magic game engine, handle mouse events only
3452 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3454 game.use_key_actions = FALSE;
3455 game.use_mouse_actions = TRUE;
3458 // check for custom elements with mouse click events
3459 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3461 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3463 int element = EL_CUSTOM_START + i;
3465 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3466 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3467 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3468 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3469 game.use_mouse_actions = TRUE;
3474 static int get_num_special_action(int element, int action_first,
3477 int num_special_action = 0;
3480 for (i = action_first; i <= action_last; i++)
3482 boolean found = FALSE;
3484 for (j = 0; j < NUM_DIRECTIONS; j++)
3485 if (el_act_dir2img(element, i, j) !=
3486 el_act_dir2img(element, ACTION_DEFAULT, j))
3490 num_special_action++;
3495 return num_special_action;
3499 // ============================================================================
3501 // ----------------------------------------------------------------------------
3502 // initialize and start new game
3503 // ============================================================================
3505 #if DEBUG_INIT_PLAYER
3506 static void DebugPrintPlayerStatus(char *message)
3513 Debug("game:init:player", "%s:", message);
3515 for (i = 0; i < MAX_PLAYERS; i++)
3517 struct PlayerInfo *player = &stored_player[i];
3519 Debug("game:init:player",
3520 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3524 player->connected_locally,
3525 player->connected_network,
3527 (local_player == player ? " (local player)" : ""));
3534 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3535 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3536 int fade_mask = REDRAW_FIELD;
3538 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3539 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3540 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3541 int initial_move_dir = MV_DOWN;
3544 // required here to update video display before fading (FIX THIS)
3545 DrawMaskedBorder(REDRAW_DOOR_2);
3547 if (!game.restart_level)
3548 CloseDoor(DOOR_CLOSE_1);
3550 SetGameStatus(GAME_MODE_PLAYING);
3552 if (level_editor_test_game)
3553 FadeSkipNextFadeOut();
3555 FadeSetEnterScreen();
3558 fade_mask = REDRAW_ALL;
3560 FadeLevelSoundsAndMusic();
3562 ExpireSoundLoops(TRUE);
3566 if (level_editor_test_game)
3567 FadeSkipNextFadeIn();
3569 // needed if different viewport properties defined for playing
3570 ChangeViewportPropertiesIfNeeded();
3574 DrawCompleteVideoDisplay();
3576 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3579 InitGameControlValues();
3583 // initialize tape actions from game when recording tape
3584 tape.use_key_actions = game.use_key_actions;
3585 tape.use_mouse_actions = game.use_mouse_actions;
3587 // initialize visible playfield size when recording tape (for team mode)
3588 tape.scr_fieldx = SCR_FIELDX;
3589 tape.scr_fieldy = SCR_FIELDY;
3592 // don't play tapes over network
3593 network_playing = (network.enabled && !tape.playing);
3595 for (i = 0; i < MAX_PLAYERS; i++)
3597 struct PlayerInfo *player = &stored_player[i];
3599 player->index_nr = i;
3600 player->index_bit = (1 << i);
3601 player->element_nr = EL_PLAYER_1 + i;
3603 player->present = FALSE;
3604 player->active = FALSE;
3605 player->mapped = FALSE;
3607 player->killed = FALSE;
3608 player->reanimated = FALSE;
3609 player->buried = FALSE;
3612 player->effective_action = 0;
3613 player->programmed_action = 0;
3614 player->snap_action = 0;
3616 player->mouse_action.lx = 0;
3617 player->mouse_action.ly = 0;
3618 player->mouse_action.button = 0;
3619 player->mouse_action.button_hint = 0;
3621 player->effective_mouse_action.lx = 0;
3622 player->effective_mouse_action.ly = 0;
3623 player->effective_mouse_action.button = 0;
3624 player->effective_mouse_action.button_hint = 0;
3626 for (j = 0; j < MAX_NUM_KEYS; j++)
3627 player->key[j] = FALSE;
3629 player->num_white_keys = 0;
3631 player->dynabomb_count = 0;
3632 player->dynabomb_size = 1;
3633 player->dynabombs_left = 0;
3634 player->dynabomb_xl = FALSE;
3636 player->MovDir = initial_move_dir;
3639 player->GfxDir = initial_move_dir;
3640 player->GfxAction = ACTION_DEFAULT;
3642 player->StepFrame = 0;
3644 player->initial_element = player->element_nr;
3645 player->artwork_element =
3646 (level.use_artwork_element[i] ? level.artwork_element[i] :
3647 player->element_nr);
3648 player->use_murphy = FALSE;
3650 player->block_last_field = FALSE; // initialized in InitPlayerField()
3651 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3653 player->gravity = level.initial_player_gravity[i];
3655 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3657 player->actual_frame_counter = 0;
3659 player->step_counter = 0;
3661 player->last_move_dir = initial_move_dir;
3663 player->is_active = FALSE;
3665 player->is_waiting = FALSE;
3666 player->is_moving = FALSE;
3667 player->is_auto_moving = FALSE;
3668 player->is_digging = FALSE;
3669 player->is_snapping = FALSE;
3670 player->is_collecting = FALSE;
3671 player->is_pushing = FALSE;
3672 player->is_switching = FALSE;
3673 player->is_dropping = FALSE;
3674 player->is_dropping_pressed = FALSE;
3676 player->is_bored = FALSE;
3677 player->is_sleeping = FALSE;
3679 player->was_waiting = TRUE;
3680 player->was_moving = FALSE;
3681 player->was_snapping = FALSE;
3682 player->was_dropping = FALSE;
3684 player->force_dropping = FALSE;
3686 player->frame_counter_bored = -1;
3687 player->frame_counter_sleeping = -1;
3689 player->anim_delay_counter = 0;
3690 player->post_delay_counter = 0;
3692 player->dir_waiting = initial_move_dir;
3693 player->action_waiting = ACTION_DEFAULT;
3694 player->last_action_waiting = ACTION_DEFAULT;
3695 player->special_action_bored = ACTION_DEFAULT;
3696 player->special_action_sleeping = ACTION_DEFAULT;
3698 player->switch_x = -1;
3699 player->switch_y = -1;
3701 player->drop_x = -1;
3702 player->drop_y = -1;
3704 player->show_envelope = 0;
3706 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3708 player->push_delay = -1; // initialized when pushing starts
3709 player->push_delay_value = game.initial_push_delay_value;
3711 player->drop_delay = 0;
3712 player->drop_pressed_delay = 0;
3714 player->last_jx = -1;
3715 player->last_jy = -1;
3719 player->shield_normal_time_left = 0;
3720 player->shield_deadly_time_left = 0;
3722 player->last_removed_element = EL_UNDEFINED;
3724 player->inventory_infinite_element = EL_UNDEFINED;
3725 player->inventory_size = 0;
3727 if (level.use_initial_inventory[i])
3729 for (j = 0; j < level.initial_inventory_size[i]; j++)
3731 int element = level.initial_inventory_content[i][j];
3732 int collect_count = element_info[element].collect_count_initial;
3735 if (!IS_CUSTOM_ELEMENT(element))
3738 if (collect_count == 0)
3739 player->inventory_infinite_element = element;
3741 for (k = 0; k < collect_count; k++)
3742 if (player->inventory_size < MAX_INVENTORY_SIZE)
3743 player->inventory_element[player->inventory_size++] = element;
3747 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3748 SnapField(player, 0, 0);
3750 map_player_action[i] = i;
3753 network_player_action_received = FALSE;
3755 // initial null action
3756 if (network_playing)
3757 SendToServer_MovePlayer(MV_NONE);
3762 TimeLeft = level.time;
3765 ScreenMovDir = MV_NONE;
3769 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3771 game.robot_wheel_x = -1;
3772 game.robot_wheel_y = -1;
3777 game.all_players_gone = FALSE;
3779 game.LevelSolved = FALSE;
3780 game.GameOver = FALSE;
3782 game.GamePlayed = !tape.playing;
3784 game.LevelSolved_GameWon = FALSE;
3785 game.LevelSolved_GameEnd = FALSE;
3786 game.LevelSolved_SaveTape = FALSE;
3787 game.LevelSolved_SaveScore = FALSE;
3789 game.LevelSolved_CountingTime = 0;
3790 game.LevelSolved_CountingScore = 0;
3791 game.LevelSolved_CountingHealth = 0;
3793 game.panel.active = TRUE;
3795 game.no_time_limit = (level.time == 0);
3797 game.yamyam_content_nr = 0;
3798 game.robot_wheel_active = FALSE;
3799 game.magic_wall_active = FALSE;
3800 game.magic_wall_time_left = 0;
3801 game.light_time_left = 0;
3802 game.timegate_time_left = 0;
3803 game.switchgate_pos = 0;
3804 game.wind_direction = level.wind_direction_initial;
3807 game.score_final = 0;
3809 game.health = MAX_HEALTH;
3810 game.health_final = MAX_HEALTH;
3812 game.gems_still_needed = level.gems_needed;
3813 game.sokoban_fields_still_needed = 0;
3814 game.sokoban_objects_still_needed = 0;
3815 game.lights_still_needed = 0;
3816 game.players_still_needed = 0;
3817 game.friends_still_needed = 0;
3819 game.lenses_time_left = 0;
3820 game.magnify_time_left = 0;
3822 game.ball_active = level.ball_active_initial;
3823 game.ball_content_nr = 0;
3825 game.explosions_delayed = TRUE;
3827 game.envelope_active = FALSE;
3829 for (i = 0; i < NUM_BELTS; i++)
3831 game.belt_dir[i] = MV_NONE;
3832 game.belt_dir_nr[i] = 3; // not moving, next moving left
3835 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3836 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3838 #if DEBUG_INIT_PLAYER
3839 DebugPrintPlayerStatus("Player status at level initialization");
3842 SCAN_PLAYFIELD(x, y)
3844 Tile[x][y] = Last[x][y] = level.field[x][y];
3845 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3846 ChangeDelay[x][y] = 0;
3847 ChangePage[x][y] = -1;
3848 CustomValue[x][y] = 0; // initialized in InitField()
3849 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3851 WasJustMoving[x][y] = 0;
3852 WasJustFalling[x][y] = 0;
3853 CheckCollision[x][y] = 0;
3854 CheckImpact[x][y] = 0;
3856 Pushed[x][y] = FALSE;
3858 ChangeCount[x][y] = 0;
3859 ChangeEvent[x][y] = -1;
3861 ExplodePhase[x][y] = 0;
3862 ExplodeDelay[x][y] = 0;
3863 ExplodeField[x][y] = EX_TYPE_NONE;
3865 RunnerVisit[x][y] = 0;
3866 PlayerVisit[x][y] = 0;
3869 GfxRandom[x][y] = INIT_GFX_RANDOM();
3870 GfxElement[x][y] = EL_UNDEFINED;
3871 GfxAction[x][y] = ACTION_DEFAULT;
3872 GfxDir[x][y] = MV_NONE;
3873 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3876 SCAN_PLAYFIELD(x, y)
3878 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3880 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3882 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3885 InitField(x, y, TRUE);
3887 ResetGfxAnimation(x, y);
3892 for (i = 0; i < MAX_PLAYERS; i++)
3894 struct PlayerInfo *player = &stored_player[i];
3896 // set number of special actions for bored and sleeping animation
3897 player->num_special_action_bored =
3898 get_num_special_action(player->artwork_element,
3899 ACTION_BORING_1, ACTION_BORING_LAST);
3900 player->num_special_action_sleeping =
3901 get_num_special_action(player->artwork_element,
3902 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3905 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3906 emulate_sb ? EMU_SOKOBAN :
3907 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3909 // initialize type of slippery elements
3910 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3912 if (!IS_CUSTOM_ELEMENT(i))
3914 // default: elements slip down either to the left or right randomly
3915 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3917 // SP style elements prefer to slip down on the left side
3918 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3919 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3921 // BD style elements prefer to slip down on the left side
3922 if (game.emulation == EMU_BOULDERDASH)
3923 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3927 // initialize explosion and ignition delay
3928 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3930 if (!IS_CUSTOM_ELEMENT(i))
3933 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3934 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3935 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3936 int last_phase = (num_phase + 1) * delay;
3937 int half_phase = (num_phase / 2) * delay;
3939 element_info[i].explosion_delay = last_phase - 1;
3940 element_info[i].ignition_delay = half_phase;
3942 if (i == EL_BLACK_ORB)
3943 element_info[i].ignition_delay = 1;
3947 // correct non-moving belts to start moving left
3948 for (i = 0; i < NUM_BELTS; i++)
3949 if (game.belt_dir[i] == MV_NONE)
3950 game.belt_dir_nr[i] = 3; // not moving, next moving left
3952 #if USE_NEW_PLAYER_ASSIGNMENTS
3953 // use preferred player also in local single-player mode
3954 if (!network.enabled && !game.team_mode)
3956 int new_index_nr = setup.network_player_nr;
3958 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3960 for (i = 0; i < MAX_PLAYERS; i++)
3961 stored_player[i].connected_locally = FALSE;
3963 stored_player[new_index_nr].connected_locally = TRUE;
3967 for (i = 0; i < MAX_PLAYERS; i++)
3969 stored_player[i].connected = FALSE;
3971 // in network game mode, the local player might not be the first player
3972 if (stored_player[i].connected_locally)
3973 local_player = &stored_player[i];
3976 if (!network.enabled)
3977 local_player->connected = TRUE;
3981 for (i = 0; i < MAX_PLAYERS; i++)
3982 stored_player[i].connected = tape.player_participates[i];
3984 else if (network.enabled)
3986 // add team mode players connected over the network (needed for correct
3987 // assignment of player figures from level to locally playing players)
3989 for (i = 0; i < MAX_PLAYERS; i++)
3990 if (stored_player[i].connected_network)
3991 stored_player[i].connected = TRUE;
3993 else if (game.team_mode)
3995 // try to guess locally connected team mode players (needed for correct
3996 // assignment of player figures from level to locally playing players)
3998 for (i = 0; i < MAX_PLAYERS; i++)
3999 if (setup.input[i].use_joystick ||
4000 setup.input[i].key.left != KSYM_UNDEFINED)
4001 stored_player[i].connected = TRUE;
4004 #if DEBUG_INIT_PLAYER
4005 DebugPrintPlayerStatus("Player status after level initialization");
4008 #if DEBUG_INIT_PLAYER
4009 Debug("game:init:player", "Reassigning players ...");
4012 // check if any connected player was not found in playfield
4013 for (i = 0; i < MAX_PLAYERS; i++)
4015 struct PlayerInfo *player = &stored_player[i];
4017 if (player->connected && !player->present)
4019 struct PlayerInfo *field_player = NULL;
4021 #if DEBUG_INIT_PLAYER
4022 Debug("game:init:player",
4023 "- looking for field player for player %d ...", i + 1);
4026 // assign first free player found that is present in the playfield
4028 // first try: look for unmapped playfield player that is not connected
4029 for (j = 0; j < MAX_PLAYERS; j++)
4030 if (field_player == NULL &&
4031 stored_player[j].present &&
4032 !stored_player[j].mapped &&
4033 !stored_player[j].connected)
4034 field_player = &stored_player[j];
4036 // second try: look for *any* unmapped playfield player
4037 for (j = 0; j < MAX_PLAYERS; j++)
4038 if (field_player == NULL &&
4039 stored_player[j].present &&
4040 !stored_player[j].mapped)
4041 field_player = &stored_player[j];
4043 if (field_player != NULL)
4045 int jx = field_player->jx, jy = field_player->jy;
4047 #if DEBUG_INIT_PLAYER
4048 Debug("game:init:player", "- found player %d",
4049 field_player->index_nr + 1);
4052 player->present = FALSE;
4053 player->active = FALSE;
4055 field_player->present = TRUE;
4056 field_player->active = TRUE;
4059 player->initial_element = field_player->initial_element;
4060 player->artwork_element = field_player->artwork_element;
4062 player->block_last_field = field_player->block_last_field;
4063 player->block_delay_adjustment = field_player->block_delay_adjustment;
4066 StorePlayer[jx][jy] = field_player->element_nr;
4068 field_player->jx = field_player->last_jx = jx;
4069 field_player->jy = field_player->last_jy = jy;
4071 if (local_player == player)
4072 local_player = field_player;
4074 map_player_action[field_player->index_nr] = i;
4076 field_player->mapped = TRUE;
4078 #if DEBUG_INIT_PLAYER
4079 Debug("game:init:player", "- map_player_action[%d] == %d",
4080 field_player->index_nr + 1, i + 1);
4085 if (player->connected && player->present)
4086 player->mapped = TRUE;
4089 #if DEBUG_INIT_PLAYER
4090 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4095 // check if any connected player was not found in playfield
4096 for (i = 0; i < MAX_PLAYERS; i++)
4098 struct PlayerInfo *player = &stored_player[i];
4100 if (player->connected && !player->present)
4102 for (j = 0; j < MAX_PLAYERS; j++)
4104 struct PlayerInfo *field_player = &stored_player[j];
4105 int jx = field_player->jx, jy = field_player->jy;
4107 // assign first free player found that is present in the playfield
4108 if (field_player->present && !field_player->connected)
4110 player->present = TRUE;
4111 player->active = TRUE;
4113 field_player->present = FALSE;
4114 field_player->active = FALSE;
4116 player->initial_element = field_player->initial_element;
4117 player->artwork_element = field_player->artwork_element;
4119 player->block_last_field = field_player->block_last_field;
4120 player->block_delay_adjustment = field_player->block_delay_adjustment;
4122 StorePlayer[jx][jy] = player->element_nr;
4124 player->jx = player->last_jx = jx;
4125 player->jy = player->last_jy = jy;
4135 Debug("game:init:player", "local_player->present == %d",
4136 local_player->present);
4139 // set focus to local player for network games, else to all players
4140 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4141 game.centered_player_nr_next = game.centered_player_nr;
4142 game.set_centered_player = FALSE;
4143 game.set_centered_player_wrap = FALSE;
4145 if (network_playing && tape.recording)
4147 // store client dependent player focus when recording network games
4148 tape.centered_player_nr_next = game.centered_player_nr_next;
4149 tape.set_centered_player = TRUE;
4154 // when playing a tape, eliminate all players who do not participate
4156 #if USE_NEW_PLAYER_ASSIGNMENTS
4158 if (!game.team_mode)
4160 for (i = 0; i < MAX_PLAYERS; i++)
4162 if (stored_player[i].active &&
4163 !tape.player_participates[map_player_action[i]])
4165 struct PlayerInfo *player = &stored_player[i];
4166 int jx = player->jx, jy = player->jy;
4168 #if DEBUG_INIT_PLAYER
4169 Debug("game:init:player", "Removing player %d at (%d, %d)",
4173 player->active = FALSE;
4174 StorePlayer[jx][jy] = 0;
4175 Tile[jx][jy] = EL_EMPTY;
4182 for (i = 0; i < MAX_PLAYERS; i++)
4184 if (stored_player[i].active &&
4185 !tape.player_participates[i])
4187 struct PlayerInfo *player = &stored_player[i];
4188 int jx = player->jx, jy = player->jy;
4190 player->active = FALSE;
4191 StorePlayer[jx][jy] = 0;
4192 Tile[jx][jy] = EL_EMPTY;
4197 else if (!network.enabled && !game.team_mode) // && !tape.playing
4199 // when in single player mode, eliminate all but the local player
4201 for (i = 0; i < MAX_PLAYERS; i++)
4203 struct PlayerInfo *player = &stored_player[i];
4205 if (player->active && player != local_player)
4207 int jx = player->jx, jy = player->jy;
4209 player->active = FALSE;
4210 player->present = FALSE;
4212 StorePlayer[jx][jy] = 0;
4213 Tile[jx][jy] = EL_EMPTY;
4218 for (i = 0; i < MAX_PLAYERS; i++)
4219 if (stored_player[i].active)
4220 game.players_still_needed++;
4222 if (level.solved_by_one_player)
4223 game.players_still_needed = 1;
4225 // when recording the game, store which players take part in the game
4228 #if USE_NEW_PLAYER_ASSIGNMENTS
4229 for (i = 0; i < MAX_PLAYERS; i++)
4230 if (stored_player[i].connected)
4231 tape.player_participates[i] = TRUE;
4233 for (i = 0; i < MAX_PLAYERS; i++)
4234 if (stored_player[i].active)
4235 tape.player_participates[i] = TRUE;
4239 #if DEBUG_INIT_PLAYER
4240 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4243 if (BorderElement == EL_EMPTY)
4246 SBX_Right = lev_fieldx - SCR_FIELDX;
4248 SBY_Lower = lev_fieldy - SCR_FIELDY;
4253 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4255 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4258 if (full_lev_fieldx <= SCR_FIELDX)
4259 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4260 if (full_lev_fieldy <= SCR_FIELDY)
4261 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4263 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4265 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4268 // if local player not found, look for custom element that might create
4269 // the player (make some assumptions about the right custom element)
4270 if (!local_player->present)
4272 int start_x = 0, start_y = 0;
4273 int found_rating = 0;
4274 int found_element = EL_UNDEFINED;
4275 int player_nr = local_player->index_nr;
4277 SCAN_PLAYFIELD(x, y)
4279 int element = Tile[x][y];
4284 if (level.use_start_element[player_nr] &&
4285 level.start_element[player_nr] == element &&
4292 found_element = element;
4295 if (!IS_CUSTOM_ELEMENT(element))
4298 if (CAN_CHANGE(element))
4300 for (i = 0; i < element_info[element].num_change_pages; i++)
4302 // check for player created from custom element as single target
4303 content = element_info[element].change_page[i].target_element;
4304 is_player = ELEM_IS_PLAYER(content);
4306 if (is_player && (found_rating < 3 ||
4307 (found_rating == 3 && element < found_element)))
4313 found_element = element;
4318 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4320 // check for player created from custom element as explosion content
4321 content = element_info[element].content.e[xx][yy];
4322 is_player = ELEM_IS_PLAYER(content);
4324 if (is_player && (found_rating < 2 ||
4325 (found_rating == 2 && element < found_element)))
4327 start_x = x + xx - 1;
4328 start_y = y + yy - 1;
4331 found_element = element;
4334 if (!CAN_CHANGE(element))
4337 for (i = 0; i < element_info[element].num_change_pages; i++)
4339 // check for player created from custom element as extended target
4341 element_info[element].change_page[i].target_content.e[xx][yy];
4343 is_player = ELEM_IS_PLAYER(content);
4345 if (is_player && (found_rating < 1 ||
4346 (found_rating == 1 && element < found_element)))
4348 start_x = x + xx - 1;
4349 start_y = y + yy - 1;
4352 found_element = element;
4358 scroll_x = SCROLL_POSITION_X(start_x);
4359 scroll_y = SCROLL_POSITION_Y(start_y);
4363 scroll_x = SCROLL_POSITION_X(local_player->jx);
4364 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4367 // !!! FIX THIS (START) !!!
4368 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4370 InitGameEngine_EM();
4372 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4374 InitGameEngine_SP();
4376 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4378 InitGameEngine_MM();
4382 DrawLevel(REDRAW_FIELD);
4385 // after drawing the level, correct some elements
4386 if (game.timegate_time_left == 0)
4387 CloseAllOpenTimegates();
4390 // blit playfield from scroll buffer to normal back buffer for fading in
4391 BlitScreenToBitmap(backbuffer);
4392 // !!! FIX THIS (END) !!!
4394 DrawMaskedBorder(fade_mask);
4399 // full screen redraw is required at this point in the following cases:
4400 // - special editor door undrawn when game was started from level editor
4401 // - drawing area (playfield) was changed and has to be removed completely
4402 redraw_mask = REDRAW_ALL;
4406 if (!game.restart_level)
4408 // copy default game door content to main double buffer
4410 // !!! CHECK AGAIN !!!
4411 SetPanelBackground();
4412 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4413 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4416 SetPanelBackground();
4417 SetDrawBackgroundMask(REDRAW_DOOR_1);
4419 UpdateAndDisplayGameControlValues();
4421 if (!game.restart_level)
4427 CreateGameButtons();
4432 // copy actual game door content to door double buffer for OpenDoor()
4433 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4435 OpenDoor(DOOR_OPEN_ALL);
4437 KeyboardAutoRepeatOffUnlessAutoplay();
4439 #if DEBUG_INIT_PLAYER
4440 DebugPrintPlayerStatus("Player status (final)");
4449 if (!game.restart_level && !tape.playing)
4451 LevelStats_incPlayed(level_nr);
4453 SaveLevelSetup_SeriesInfo();
4456 game.restart_level = FALSE;
4457 game.restart_game_message = NULL;
4459 game.request_active = FALSE;
4460 game.request_active_or_moving = FALSE;
4462 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4463 InitGameActions_MM();
4465 SaveEngineSnapshotToListInitial();
4467 if (!game.restart_level)
4469 PlaySound(SND_GAME_STARTING);
4471 if (setup.sound_music)
4476 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4477 int actual_player_x, int actual_player_y)
4479 // this is used for non-R'n'D game engines to update certain engine values
4481 // needed to determine if sounds are played within the visible screen area
4482 scroll_x = actual_scroll_x;
4483 scroll_y = actual_scroll_y;
4485 // needed to get player position for "follow finger" playing input method
4486 local_player->jx = actual_player_x;
4487 local_player->jy = actual_player_y;
4490 void InitMovDir(int x, int y)
4492 int i, element = Tile[x][y];
4493 static int xy[4][2] =
4500 static int direction[3][4] =
4502 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4503 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4504 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4513 Tile[x][y] = EL_BUG;
4514 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4517 case EL_SPACESHIP_RIGHT:
4518 case EL_SPACESHIP_UP:
4519 case EL_SPACESHIP_LEFT:
4520 case EL_SPACESHIP_DOWN:
4521 Tile[x][y] = EL_SPACESHIP;
4522 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4525 case EL_BD_BUTTERFLY_RIGHT:
4526 case EL_BD_BUTTERFLY_UP:
4527 case EL_BD_BUTTERFLY_LEFT:
4528 case EL_BD_BUTTERFLY_DOWN:
4529 Tile[x][y] = EL_BD_BUTTERFLY;
4530 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4533 case EL_BD_FIREFLY_RIGHT:
4534 case EL_BD_FIREFLY_UP:
4535 case EL_BD_FIREFLY_LEFT:
4536 case EL_BD_FIREFLY_DOWN:
4537 Tile[x][y] = EL_BD_FIREFLY;
4538 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4541 case EL_PACMAN_RIGHT:
4543 case EL_PACMAN_LEFT:
4544 case EL_PACMAN_DOWN:
4545 Tile[x][y] = EL_PACMAN;
4546 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4549 case EL_YAMYAM_LEFT:
4550 case EL_YAMYAM_RIGHT:
4552 case EL_YAMYAM_DOWN:
4553 Tile[x][y] = EL_YAMYAM;
4554 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4557 case EL_SP_SNIKSNAK:
4558 MovDir[x][y] = MV_UP;
4561 case EL_SP_ELECTRON:
4562 MovDir[x][y] = MV_LEFT;
4569 Tile[x][y] = EL_MOLE;
4570 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4573 case EL_SPRING_LEFT:
4574 case EL_SPRING_RIGHT:
4575 Tile[x][y] = EL_SPRING;
4576 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4580 if (IS_CUSTOM_ELEMENT(element))
4582 struct ElementInfo *ei = &element_info[element];
4583 int move_direction_initial = ei->move_direction_initial;
4584 int move_pattern = ei->move_pattern;
4586 if (move_direction_initial == MV_START_PREVIOUS)
4588 if (MovDir[x][y] != MV_NONE)
4591 move_direction_initial = MV_START_AUTOMATIC;
4594 if (move_direction_initial == MV_START_RANDOM)
4595 MovDir[x][y] = 1 << RND(4);
4596 else if (move_direction_initial & MV_ANY_DIRECTION)
4597 MovDir[x][y] = move_direction_initial;
4598 else if (move_pattern == MV_ALL_DIRECTIONS ||
4599 move_pattern == MV_TURNING_LEFT ||
4600 move_pattern == MV_TURNING_RIGHT ||
4601 move_pattern == MV_TURNING_LEFT_RIGHT ||
4602 move_pattern == MV_TURNING_RIGHT_LEFT ||
4603 move_pattern == MV_TURNING_RANDOM)
4604 MovDir[x][y] = 1 << RND(4);
4605 else if (move_pattern == MV_HORIZONTAL)
4606 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4607 else if (move_pattern == MV_VERTICAL)
4608 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4609 else if (move_pattern & MV_ANY_DIRECTION)
4610 MovDir[x][y] = element_info[element].move_pattern;
4611 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4612 move_pattern == MV_ALONG_RIGHT_SIDE)
4614 // use random direction as default start direction
4615 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4616 MovDir[x][y] = 1 << RND(4);
4618 for (i = 0; i < NUM_DIRECTIONS; i++)
4620 int x1 = x + xy[i][0];
4621 int y1 = y + xy[i][1];
4623 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4625 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4626 MovDir[x][y] = direction[0][i];
4628 MovDir[x][y] = direction[1][i];
4637 MovDir[x][y] = 1 << RND(4);
4639 if (element != EL_BUG &&
4640 element != EL_SPACESHIP &&
4641 element != EL_BD_BUTTERFLY &&
4642 element != EL_BD_FIREFLY)
4645 for (i = 0; i < NUM_DIRECTIONS; i++)
4647 int x1 = x + xy[i][0];
4648 int y1 = y + xy[i][1];
4650 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4652 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4654 MovDir[x][y] = direction[0][i];
4657 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4658 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4660 MovDir[x][y] = direction[1][i];
4669 GfxDir[x][y] = MovDir[x][y];
4672 void InitAmoebaNr(int x, int y)
4675 int group_nr = AmoebaNeighbourNr(x, y);
4679 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4681 if (AmoebaCnt[i] == 0)
4689 AmoebaNr[x][y] = group_nr;
4690 AmoebaCnt[group_nr]++;
4691 AmoebaCnt2[group_nr]++;
4694 static void LevelSolved(void)
4696 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4697 game.players_still_needed > 0)
4700 game.LevelSolved = TRUE;
4701 game.GameOver = TRUE;
4703 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4704 game_em.lev->score :
4705 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4708 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4709 MM_HEALTH(game_mm.laser_overload_value) :
4712 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4713 game.LevelSolved_CountingScore = game.score_final;
4714 game.LevelSolved_CountingHealth = game.health_final;
4719 static int time_count_steps;
4720 static int time, time_final;
4721 static int score, score_final;
4722 static int health, health_final;
4723 static int game_over_delay_1 = 0;
4724 static int game_over_delay_2 = 0;
4725 static int game_over_delay_3 = 0;
4726 int game_over_delay_value_1 = 50;
4727 int game_over_delay_value_2 = 25;
4728 int game_over_delay_value_3 = 50;
4730 if (!game.LevelSolved_GameWon)
4734 // do not start end game actions before the player stops moving (to exit)
4735 if (local_player->active && local_player->MovPos)
4738 game.LevelSolved_GameWon = TRUE;
4739 game.LevelSolved_SaveTape = tape.recording;
4740 game.LevelSolved_SaveScore = !tape.playing;
4744 LevelStats_incSolved(level_nr);
4746 SaveLevelSetup_SeriesInfo();
4749 if (tape.auto_play) // tape might already be stopped here
4750 tape.auto_play_level_solved = TRUE;
4754 game_over_delay_1 = 0;
4755 game_over_delay_2 = 0;
4756 game_over_delay_3 = game_over_delay_value_3;
4758 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4759 score = score_final = game.score_final;
4760 health = health_final = game.health_final;
4762 if (level.score[SC_TIME_BONUS] > 0)
4767 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4769 else if (game.no_time_limit && TimePlayed < 999)
4772 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4775 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4777 game_over_delay_1 = game_over_delay_value_1;
4779 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4782 score_final += health * level.score[SC_TIME_BONUS];
4784 game_over_delay_2 = game_over_delay_value_2;
4787 game.score_final = score_final;
4788 game.health_final = health_final;
4791 if (level_editor_test_game)
4794 score = score_final;
4796 game.LevelSolved_CountingTime = time;
4797 game.LevelSolved_CountingScore = score;
4799 game_panel_controls[GAME_PANEL_TIME].value = time;
4800 game_panel_controls[GAME_PANEL_SCORE].value = score;
4802 DisplayGameControlValues();
4805 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4807 // check if last player has left the level
4808 if (game.exit_x >= 0 &&
4811 int x = game.exit_x;
4812 int y = game.exit_y;
4813 int element = Tile[x][y];
4815 // close exit door after last player
4816 if ((game.all_players_gone &&
4817 (element == EL_EXIT_OPEN ||
4818 element == EL_SP_EXIT_OPEN ||
4819 element == EL_STEEL_EXIT_OPEN)) ||
4820 element == EL_EM_EXIT_OPEN ||
4821 element == EL_EM_STEEL_EXIT_OPEN)
4825 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4826 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4827 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4828 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4829 EL_EM_STEEL_EXIT_CLOSING);
4831 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4834 // player disappears
4835 DrawLevelField(x, y);
4838 for (i = 0; i < MAX_PLAYERS; i++)
4840 struct PlayerInfo *player = &stored_player[i];
4842 if (player->present)
4844 RemovePlayer(player);
4846 // player disappears
4847 DrawLevelField(player->jx, player->jy);
4852 PlaySound(SND_GAME_WINNING);
4855 if (game_over_delay_1 > 0)
4857 game_over_delay_1--;
4862 if (time != time_final)
4864 int time_to_go = ABS(time_final - time);
4865 int time_count_dir = (time < time_final ? +1 : -1);
4867 if (time_to_go < time_count_steps)
4868 time_count_steps = 1;
4870 time += time_count_steps * time_count_dir;
4871 score += time_count_steps * level.score[SC_TIME_BONUS];
4873 game.LevelSolved_CountingTime = time;
4874 game.LevelSolved_CountingScore = score;
4876 game_panel_controls[GAME_PANEL_TIME].value = time;
4877 game_panel_controls[GAME_PANEL_SCORE].value = score;
4879 DisplayGameControlValues();
4881 if (time == time_final)
4882 StopSound(SND_GAME_LEVELTIME_BONUS);
4883 else if (setup.sound_loops)
4884 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4886 PlaySound(SND_GAME_LEVELTIME_BONUS);
4891 if (game_over_delay_2 > 0)
4893 game_over_delay_2--;
4898 if (health != health_final)
4900 int health_count_dir = (health < health_final ? +1 : -1);
4902 health += health_count_dir;
4903 score += level.score[SC_TIME_BONUS];
4905 game.LevelSolved_CountingHealth = health;
4906 game.LevelSolved_CountingScore = score;
4908 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4909 game_panel_controls[GAME_PANEL_SCORE].value = score;
4911 DisplayGameControlValues();
4913 if (health == health_final)
4914 StopSound(SND_GAME_LEVELTIME_BONUS);
4915 else if (setup.sound_loops)
4916 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4918 PlaySound(SND_GAME_LEVELTIME_BONUS);
4923 game.panel.active = FALSE;
4925 if (game_over_delay_3 > 0)
4927 game_over_delay_3--;
4937 // used instead of "level_nr" (needed for network games)
4938 int last_level_nr = levelset.level_nr;
4941 game.LevelSolved_GameEnd = TRUE;
4943 if (game.LevelSolved_SaveTape)
4945 // make sure that request dialog to save tape does not open door again
4946 if (!global.use_envelope_request)
4947 CloseDoor(DOOR_CLOSE_1);
4949 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4952 // if no tape is to be saved, close both doors simultaneously
4953 CloseDoor(DOOR_CLOSE_ALL);
4955 if (level_editor_test_game)
4957 SetGameStatus(GAME_MODE_MAIN);
4964 if (!game.LevelSolved_SaveScore)
4966 SetGameStatus(GAME_MODE_MAIN);
4973 if (level_nr == leveldir_current->handicap_level)
4975 leveldir_current->handicap_level++;
4977 SaveLevelSetup_SeriesInfo();
4980 if (setup.increment_levels &&
4981 level_nr < leveldir_current->last_level &&
4984 level_nr++; // advance to next level
4985 TapeErase(); // start with empty tape
4987 if (setup.auto_play_next_level)
4989 LoadLevel(level_nr);
4991 SaveLevelSetup_SeriesInfo();
4995 hi_pos = NewHiScore(last_level_nr);
4997 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4999 SetGameStatus(GAME_MODE_SCORES);
5001 DrawHallOfFame(last_level_nr, hi_pos);
5003 else if (setup.auto_play_next_level && setup.increment_levels &&
5004 last_level_nr < leveldir_current->last_level &&
5007 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5011 SetGameStatus(GAME_MODE_MAIN);
5017 int NewHiScore(int level_nr)
5021 boolean one_score_entry_per_name = !program.many_scores_per_name;
5023 LoadScore(level_nr);
5025 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
5026 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
5029 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
5031 if (game.score_final > highscore[k].Score)
5033 // player has made it to the hall of fame
5035 if (k < MAX_SCORE_ENTRIES - 1)
5037 int m = MAX_SCORE_ENTRIES - 1;
5039 if (one_score_entry_per_name)
5041 for (l = k; l < MAX_SCORE_ENTRIES; l++)
5042 if (strEqual(setup.player_name, highscore[l].Name))
5045 if (m == k) // player's new highscore overwrites his old one
5049 for (l = m; l > k; l--)
5051 strcpy(highscore[l].Name, highscore[l - 1].Name);
5052 highscore[l].Score = highscore[l - 1].Score;
5058 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
5059 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
5060 highscore[k].Score = game.score_final;
5065 else if (one_score_entry_per_name &&
5066 !strncmp(setup.player_name, highscore[k].Name,
5067 MAX_PLAYER_NAME_LEN))
5068 break; // player already there with a higher score
5072 SaveScore(level_nr);
5077 static int getElementMoveStepsizeExt(int x, int y, int direction)
5079 int element = Tile[x][y];
5080 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5081 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5082 int horiz_move = (dx != 0);
5083 int sign = (horiz_move ? dx : dy);
5084 int step = sign * element_info[element].move_stepsize;
5086 // special values for move stepsize for spring and things on conveyor belt
5089 if (CAN_FALL(element) &&
5090 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5091 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5092 else if (element == EL_SPRING)
5093 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5099 static int getElementMoveStepsize(int x, int y)
5101 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5104 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5106 if (player->GfxAction != action || player->GfxDir != dir)
5108 player->GfxAction = action;
5109 player->GfxDir = dir;
5111 player->StepFrame = 0;
5115 static void ResetGfxFrame(int x, int y)
5117 // profiling showed that "autotest" spends 10~20% of its time in this function
5118 if (DrawingDeactivatedField())
5121 int element = Tile[x][y];
5122 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5124 if (graphic_info[graphic].anim_global_sync)
5125 GfxFrame[x][y] = FrameCounter;
5126 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5127 GfxFrame[x][y] = CustomValue[x][y];
5128 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5129 GfxFrame[x][y] = element_info[element].collect_score;
5130 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5131 GfxFrame[x][y] = ChangeDelay[x][y];
5134 static void ResetGfxAnimation(int x, int y)
5136 GfxAction[x][y] = ACTION_DEFAULT;
5137 GfxDir[x][y] = MovDir[x][y];
5140 ResetGfxFrame(x, y);
5143 static void ResetRandomAnimationValue(int x, int y)
5145 GfxRandom[x][y] = INIT_GFX_RANDOM();
5148 static void InitMovingField(int x, int y, int direction)
5150 int element = Tile[x][y];
5151 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5152 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5155 boolean is_moving_before, is_moving_after;
5157 // check if element was/is moving or being moved before/after mode change
5158 is_moving_before = (WasJustMoving[x][y] != 0);
5159 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5161 // reset animation only for moving elements which change direction of moving
5162 // or which just started or stopped moving
5163 // (else CEs with property "can move" / "not moving" are reset each frame)
5164 if (is_moving_before != is_moving_after ||
5165 direction != MovDir[x][y])
5166 ResetGfxAnimation(x, y);
5168 MovDir[x][y] = direction;
5169 GfxDir[x][y] = direction;
5171 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5172 direction == MV_DOWN && CAN_FALL(element) ?
5173 ACTION_FALLING : ACTION_MOVING);
5175 // this is needed for CEs with property "can move" / "not moving"
5177 if (is_moving_after)
5179 if (Tile[newx][newy] == EL_EMPTY)
5180 Tile[newx][newy] = EL_BLOCKED;
5182 MovDir[newx][newy] = MovDir[x][y];
5184 CustomValue[newx][newy] = CustomValue[x][y];
5186 GfxFrame[newx][newy] = GfxFrame[x][y];
5187 GfxRandom[newx][newy] = GfxRandom[x][y];
5188 GfxAction[newx][newy] = GfxAction[x][y];
5189 GfxDir[newx][newy] = GfxDir[x][y];
5193 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5195 int direction = MovDir[x][y];
5196 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5197 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5203 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5205 int oldx = x, oldy = y;
5206 int direction = MovDir[x][y];
5208 if (direction == MV_LEFT)
5210 else if (direction == MV_RIGHT)
5212 else if (direction == MV_UP)
5214 else if (direction == MV_DOWN)
5217 *comes_from_x = oldx;
5218 *comes_from_y = oldy;
5221 static int MovingOrBlocked2Element(int x, int y)
5223 int element = Tile[x][y];
5225 if (element == EL_BLOCKED)
5229 Blocked2Moving(x, y, &oldx, &oldy);
5230 return Tile[oldx][oldy];
5236 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5238 // like MovingOrBlocked2Element(), but if element is moving
5239 // and (x,y) is the field the moving element is just leaving,
5240 // return EL_BLOCKED instead of the element value
5241 int element = Tile[x][y];
5243 if (IS_MOVING(x, y))
5245 if (element == EL_BLOCKED)
5249 Blocked2Moving(x, y, &oldx, &oldy);
5250 return Tile[oldx][oldy];
5259 static void RemoveField(int x, int y)
5261 Tile[x][y] = EL_EMPTY;
5267 CustomValue[x][y] = 0;
5270 ChangeDelay[x][y] = 0;
5271 ChangePage[x][y] = -1;
5272 Pushed[x][y] = FALSE;
5274 GfxElement[x][y] = EL_UNDEFINED;
5275 GfxAction[x][y] = ACTION_DEFAULT;
5276 GfxDir[x][y] = MV_NONE;
5279 static void RemoveMovingField(int x, int y)
5281 int oldx = x, oldy = y, newx = x, newy = y;
5282 int element = Tile[x][y];
5283 int next_element = EL_UNDEFINED;
5285 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5288 if (IS_MOVING(x, y))
5290 Moving2Blocked(x, y, &newx, &newy);
5292 if (Tile[newx][newy] != EL_BLOCKED)
5294 // element is moving, but target field is not free (blocked), but
5295 // already occupied by something different (example: acid pool);
5296 // in this case, only remove the moving field, but not the target
5298 RemoveField(oldx, oldy);
5300 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5302 TEST_DrawLevelField(oldx, oldy);
5307 else if (element == EL_BLOCKED)
5309 Blocked2Moving(x, y, &oldx, &oldy);
5310 if (!IS_MOVING(oldx, oldy))
5314 if (element == EL_BLOCKED &&
5315 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5316 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5317 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5318 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5319 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5320 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5321 next_element = get_next_element(Tile[oldx][oldy]);
5323 RemoveField(oldx, oldy);
5324 RemoveField(newx, newy);
5326 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5328 if (next_element != EL_UNDEFINED)
5329 Tile[oldx][oldy] = next_element;
5331 TEST_DrawLevelField(oldx, oldy);
5332 TEST_DrawLevelField(newx, newy);
5335 void DrawDynamite(int x, int y)
5337 int sx = SCREENX(x), sy = SCREENY(y);
5338 int graphic = el2img(Tile[x][y]);
5341 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5344 if (IS_WALKABLE_INSIDE(Back[x][y]))
5348 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5349 else if (Store[x][y])
5350 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5352 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5354 if (Back[x][y] || Store[x][y])
5355 DrawGraphicThruMask(sx, sy, graphic, frame);
5357 DrawGraphic(sx, sy, graphic, frame);
5360 static void CheckDynamite(int x, int y)
5362 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5366 if (MovDelay[x][y] != 0)
5369 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5375 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5380 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5382 boolean num_checked_players = 0;
5385 for (i = 0; i < MAX_PLAYERS; i++)
5387 if (stored_player[i].active)
5389 int sx = stored_player[i].jx;
5390 int sy = stored_player[i].jy;
5392 if (num_checked_players == 0)
5399 *sx1 = MIN(*sx1, sx);
5400 *sy1 = MIN(*sy1, sy);
5401 *sx2 = MAX(*sx2, sx);
5402 *sy2 = MAX(*sy2, sy);
5405 num_checked_players++;
5410 static boolean checkIfAllPlayersFitToScreen_RND(void)
5412 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5414 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5416 return (sx2 - sx1 < SCR_FIELDX &&
5417 sy2 - sy1 < SCR_FIELDY);
5420 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5422 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5424 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5426 *sx = (sx1 + sx2) / 2;
5427 *sy = (sy1 + sy2) / 2;
5430 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5431 boolean center_screen, boolean quick_relocation)
5433 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5434 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5435 boolean no_delay = (tape.warp_forward);
5436 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5437 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5438 int new_scroll_x, new_scroll_y;
5440 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5442 // case 1: quick relocation inside visible screen (without scrolling)
5449 if (!level.shifted_relocation || center_screen)
5451 // relocation _with_ centering of screen
5453 new_scroll_x = SCROLL_POSITION_X(x);
5454 new_scroll_y = SCROLL_POSITION_Y(y);
5458 // relocation _without_ centering of screen
5460 int center_scroll_x = SCROLL_POSITION_X(old_x);
5461 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5462 int offset_x = x + (scroll_x - center_scroll_x);
5463 int offset_y = y + (scroll_y - center_scroll_y);
5465 // for new screen position, apply previous offset to center position
5466 new_scroll_x = SCROLL_POSITION_X(offset_x);
5467 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5470 if (quick_relocation)
5472 // case 2: quick relocation (redraw without visible scrolling)
5474 scroll_x = new_scroll_x;
5475 scroll_y = new_scroll_y;
5482 // case 3: visible relocation (with scrolling to new position)
5484 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5486 SetVideoFrameDelay(wait_delay_value);
5488 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5490 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5491 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5493 if (dx == 0 && dy == 0) // no scrolling needed at all
5499 // set values for horizontal/vertical screen scrolling (half tile size)
5500 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5501 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5502 int pos_x = dx * TILEX / 2;
5503 int pos_y = dy * TILEY / 2;
5504 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5505 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5507 ScrollLevel(dx, dy);
5510 // scroll in two steps of half tile size to make things smoother
5511 BlitScreenToBitmapExt_RND(window, fx, fy);
5513 // scroll second step to align at full tile size
5514 BlitScreenToBitmap(window);
5520 SetVideoFrameDelay(frame_delay_value_old);
5523 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5525 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5526 int player_nr = GET_PLAYER_NR(el_player);
5527 struct PlayerInfo *player = &stored_player[player_nr];
5528 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5529 boolean no_delay = (tape.warp_forward);
5530 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5531 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5532 int old_jx = player->jx;
5533 int old_jy = player->jy;
5534 int old_element = Tile[old_jx][old_jy];
5535 int element = Tile[jx][jy];
5536 boolean player_relocated = (old_jx != jx || old_jy != jy);
5538 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5539 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5540 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5541 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5542 int leave_side_horiz = move_dir_horiz;
5543 int leave_side_vert = move_dir_vert;
5544 int enter_side = enter_side_horiz | enter_side_vert;
5545 int leave_side = leave_side_horiz | leave_side_vert;
5547 if (player->buried) // do not reanimate dead player
5550 if (!player_relocated) // no need to relocate the player
5553 if (IS_PLAYER(jx, jy)) // player already placed at new position
5555 RemoveField(jx, jy); // temporarily remove newly placed player
5556 DrawLevelField(jx, jy);
5559 if (player->present)
5561 while (player->MovPos)
5563 ScrollPlayer(player, SCROLL_GO_ON);
5564 ScrollScreen(NULL, SCROLL_GO_ON);
5566 AdvanceFrameAndPlayerCounters(player->index_nr);
5570 BackToFront_WithFrameDelay(wait_delay_value);
5573 DrawPlayer(player); // needed here only to cleanup last field
5574 DrawLevelField(player->jx, player->jy); // remove player graphic
5576 player->is_moving = FALSE;
5579 if (IS_CUSTOM_ELEMENT(old_element))
5580 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5582 player->index_bit, leave_side);
5584 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5586 player->index_bit, leave_side);
5588 Tile[jx][jy] = el_player;
5589 InitPlayerField(jx, jy, el_player, TRUE);
5591 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5592 possible that the relocation target field did not contain a player element,
5593 but a walkable element, to which the new player was relocated -- in this
5594 case, restore that (already initialized!) element on the player field */
5595 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5597 Tile[jx][jy] = element; // restore previously existing element
5600 // only visually relocate centered player
5601 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5602 FALSE, level.instant_relocation);
5604 TestIfPlayerTouchesBadThing(jx, jy);
5605 TestIfPlayerTouchesCustomElement(jx, jy);
5607 if (IS_CUSTOM_ELEMENT(element))
5608 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5609 player->index_bit, enter_side);
5611 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5612 player->index_bit, enter_side);
5614 if (player->is_switching)
5616 /* ensure that relocation while still switching an element does not cause
5617 a new element to be treated as also switched directly after relocation
5618 (this is important for teleporter switches that teleport the player to
5619 a place where another teleporter switch is in the same direction, which
5620 would then incorrectly be treated as immediately switched before the
5621 direction key that caused the switch was released) */
5623 player->switch_x += jx - old_jx;
5624 player->switch_y += jy - old_jy;
5628 static void Explode(int ex, int ey, int phase, int mode)
5634 // !!! eliminate this variable !!!
5635 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5637 if (game.explosions_delayed)
5639 ExplodeField[ex][ey] = mode;
5643 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5645 int center_element = Tile[ex][ey];
5646 int artwork_element, explosion_element; // set these values later
5648 // remove things displayed in background while burning dynamite
5649 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5652 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5654 // put moving element to center field (and let it explode there)
5655 center_element = MovingOrBlocked2Element(ex, ey);
5656 RemoveMovingField(ex, ey);
5657 Tile[ex][ey] = center_element;
5660 // now "center_element" is finally determined -- set related values now
5661 artwork_element = center_element; // for custom player artwork
5662 explosion_element = center_element; // for custom player artwork
5664 if (IS_PLAYER(ex, ey))
5666 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5668 artwork_element = stored_player[player_nr].artwork_element;
5670 if (level.use_explosion_element[player_nr])
5672 explosion_element = level.explosion_element[player_nr];
5673 artwork_element = explosion_element;
5677 if (mode == EX_TYPE_NORMAL ||
5678 mode == EX_TYPE_CENTER ||
5679 mode == EX_TYPE_CROSS)
5680 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5682 last_phase = element_info[explosion_element].explosion_delay + 1;
5684 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5686 int xx = x - ex + 1;
5687 int yy = y - ey + 1;
5690 if (!IN_LEV_FIELD(x, y) ||
5691 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5692 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5695 element = Tile[x][y];
5697 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5699 element = MovingOrBlocked2Element(x, y);
5701 if (!IS_EXPLOSION_PROOF(element))
5702 RemoveMovingField(x, y);
5705 // indestructible elements can only explode in center (but not flames)
5706 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5707 mode == EX_TYPE_BORDER)) ||
5708 element == EL_FLAMES)
5711 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5712 behaviour, for example when touching a yamyam that explodes to rocks
5713 with active deadly shield, a rock is created under the player !!! */
5714 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5716 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5717 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5718 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5720 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5723 if (IS_ACTIVE_BOMB(element))
5725 // re-activate things under the bomb like gate or penguin
5726 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5733 // save walkable background elements while explosion on same tile
5734 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5735 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5736 Back[x][y] = element;
5738 // ignite explodable elements reached by other explosion
5739 if (element == EL_EXPLOSION)
5740 element = Store2[x][y];
5742 if (AmoebaNr[x][y] &&
5743 (element == EL_AMOEBA_FULL ||
5744 element == EL_BD_AMOEBA ||
5745 element == EL_AMOEBA_GROWING))
5747 AmoebaCnt[AmoebaNr[x][y]]--;
5748 AmoebaCnt2[AmoebaNr[x][y]]--;
5753 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5755 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5757 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5759 if (PLAYERINFO(ex, ey)->use_murphy)
5760 Store[x][y] = EL_EMPTY;
5763 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5764 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5765 else if (ELEM_IS_PLAYER(center_element))
5766 Store[x][y] = EL_EMPTY;
5767 else if (center_element == EL_YAMYAM)
5768 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5769 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5770 Store[x][y] = element_info[center_element].content.e[xx][yy];
5772 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5773 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5774 // otherwise) -- FIX THIS !!!
5775 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5776 Store[x][y] = element_info[element].content.e[1][1];
5778 else if (!CAN_EXPLODE(element))
5779 Store[x][y] = element_info[element].content.e[1][1];
5782 Store[x][y] = EL_EMPTY;
5784 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5785 center_element == EL_AMOEBA_TO_DIAMOND)
5786 Store2[x][y] = element;
5788 Tile[x][y] = EL_EXPLOSION;
5789 GfxElement[x][y] = artwork_element;
5791 ExplodePhase[x][y] = 1;
5792 ExplodeDelay[x][y] = last_phase;
5797 if (center_element == EL_YAMYAM)
5798 game.yamyam_content_nr =
5799 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5811 GfxFrame[x][y] = 0; // restart explosion animation
5813 last_phase = ExplodeDelay[x][y];
5815 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5817 // this can happen if the player leaves an explosion just in time
5818 if (GfxElement[x][y] == EL_UNDEFINED)
5819 GfxElement[x][y] = EL_EMPTY;
5821 border_element = Store2[x][y];
5822 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5823 border_element = StorePlayer[x][y];
5825 if (phase == element_info[border_element].ignition_delay ||
5826 phase == last_phase)
5828 boolean border_explosion = FALSE;
5830 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5831 !PLAYER_EXPLOSION_PROTECTED(x, y))
5833 KillPlayerUnlessExplosionProtected(x, y);
5834 border_explosion = TRUE;
5836 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5838 Tile[x][y] = Store2[x][y];
5841 border_explosion = TRUE;
5843 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5845 AmoebaToDiamond(x, y);
5847 border_explosion = TRUE;
5850 // if an element just explodes due to another explosion (chain-reaction),
5851 // do not immediately end the new explosion when it was the last frame of
5852 // the explosion (as it would be done in the following "if"-statement!)
5853 if (border_explosion && phase == last_phase)
5857 if (phase == last_phase)
5861 element = Tile[x][y] = Store[x][y];
5862 Store[x][y] = Store2[x][y] = 0;
5863 GfxElement[x][y] = EL_UNDEFINED;
5865 // player can escape from explosions and might therefore be still alive
5866 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5867 element <= EL_PLAYER_IS_EXPLODING_4)
5869 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5870 int explosion_element = EL_PLAYER_1 + player_nr;
5871 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5872 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5874 if (level.use_explosion_element[player_nr])
5875 explosion_element = level.explosion_element[player_nr];
5877 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5878 element_info[explosion_element].content.e[xx][yy]);
5881 // restore probably existing indestructible background element
5882 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5883 element = Tile[x][y] = Back[x][y];
5886 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5887 GfxDir[x][y] = MV_NONE;
5888 ChangeDelay[x][y] = 0;
5889 ChangePage[x][y] = -1;
5891 CustomValue[x][y] = 0;
5893 InitField_WithBug2(x, y, FALSE);
5895 TEST_DrawLevelField(x, y);
5897 TestIfElementTouchesCustomElement(x, y);
5899 if (GFX_CRUMBLED(element))
5900 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5902 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5903 StorePlayer[x][y] = 0;
5905 if (ELEM_IS_PLAYER(element))
5906 RelocatePlayer(x, y, element);
5908 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5910 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5911 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5914 TEST_DrawLevelFieldCrumbled(x, y);
5916 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5918 DrawLevelElement(x, y, Back[x][y]);
5919 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5921 else if (IS_WALKABLE_UNDER(Back[x][y]))
5923 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5924 DrawLevelElementThruMask(x, y, Back[x][y]);
5926 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5927 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5931 static void DynaExplode(int ex, int ey)
5934 int dynabomb_element = Tile[ex][ey];
5935 int dynabomb_size = 1;
5936 boolean dynabomb_xl = FALSE;
5937 struct PlayerInfo *player;
5938 static int xy[4][2] =
5946 if (IS_ACTIVE_BOMB(dynabomb_element))
5948 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5949 dynabomb_size = player->dynabomb_size;
5950 dynabomb_xl = player->dynabomb_xl;
5951 player->dynabombs_left++;
5954 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5956 for (i = 0; i < NUM_DIRECTIONS; i++)
5958 for (j = 1; j <= dynabomb_size; j++)
5960 int x = ex + j * xy[i][0];
5961 int y = ey + j * xy[i][1];
5964 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5967 element = Tile[x][y];
5969 // do not restart explosions of fields with active bombs
5970 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5973 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5975 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5976 !IS_DIGGABLE(element) && !dynabomb_xl)
5982 void Bang(int x, int y)
5984 int element = MovingOrBlocked2Element(x, y);
5985 int explosion_type = EX_TYPE_NORMAL;
5987 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5989 struct PlayerInfo *player = PLAYERINFO(x, y);
5991 element = Tile[x][y] = player->initial_element;
5993 if (level.use_explosion_element[player->index_nr])
5995 int explosion_element = level.explosion_element[player->index_nr];
5997 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5998 explosion_type = EX_TYPE_CROSS;
5999 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6000 explosion_type = EX_TYPE_CENTER;
6008 case EL_BD_BUTTERFLY:
6011 case EL_DARK_YAMYAM:
6015 RaiseScoreElement(element);
6018 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6019 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6020 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6021 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6022 case EL_DYNABOMB_INCREASE_NUMBER:
6023 case EL_DYNABOMB_INCREASE_SIZE:
6024 case EL_DYNABOMB_INCREASE_POWER:
6025 explosion_type = EX_TYPE_DYNA;
6028 case EL_DC_LANDMINE:
6029 explosion_type = EX_TYPE_CENTER;
6034 case EL_LAMP_ACTIVE:
6035 case EL_AMOEBA_TO_DIAMOND:
6036 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6037 explosion_type = EX_TYPE_CENTER;
6041 if (element_info[element].explosion_type == EXPLODES_CROSS)
6042 explosion_type = EX_TYPE_CROSS;
6043 else if (element_info[element].explosion_type == EXPLODES_1X1)
6044 explosion_type = EX_TYPE_CENTER;
6048 if (explosion_type == EX_TYPE_DYNA)
6051 Explode(x, y, EX_PHASE_START, explosion_type);
6053 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6056 static void SplashAcid(int x, int y)
6058 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6059 (!IN_LEV_FIELD(x - 1, y - 2) ||
6060 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6061 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6063 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6064 (!IN_LEV_FIELD(x + 1, y - 2) ||
6065 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6066 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6068 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6071 static void InitBeltMovement(void)
6073 static int belt_base_element[4] =
6075 EL_CONVEYOR_BELT_1_LEFT,
6076 EL_CONVEYOR_BELT_2_LEFT,
6077 EL_CONVEYOR_BELT_3_LEFT,
6078 EL_CONVEYOR_BELT_4_LEFT
6080 static int belt_base_active_element[4] =
6082 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6083 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6084 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6085 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6090 // set frame order for belt animation graphic according to belt direction
6091 for (i = 0; i < NUM_BELTS; i++)
6095 for (j = 0; j < NUM_BELT_PARTS; j++)
6097 int element = belt_base_active_element[belt_nr] + j;
6098 int graphic_1 = el2img(element);
6099 int graphic_2 = el2panelimg(element);
6101 if (game.belt_dir[i] == MV_LEFT)
6103 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6104 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6108 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6109 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6114 SCAN_PLAYFIELD(x, y)
6116 int element = Tile[x][y];
6118 for (i = 0; i < NUM_BELTS; i++)
6120 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6122 int e_belt_nr = getBeltNrFromBeltElement(element);
6125 if (e_belt_nr == belt_nr)
6127 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6129 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6136 static void ToggleBeltSwitch(int x, int y)
6138 static int belt_base_element[4] =
6140 EL_CONVEYOR_BELT_1_LEFT,
6141 EL_CONVEYOR_BELT_2_LEFT,
6142 EL_CONVEYOR_BELT_3_LEFT,
6143 EL_CONVEYOR_BELT_4_LEFT
6145 static int belt_base_active_element[4] =
6147 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6148 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6149 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6150 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6152 static int belt_base_switch_element[4] =
6154 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6155 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6156 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6157 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6159 static int belt_move_dir[4] =
6167 int element = Tile[x][y];
6168 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6169 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6170 int belt_dir = belt_move_dir[belt_dir_nr];
6173 if (!IS_BELT_SWITCH(element))
6176 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6177 game.belt_dir[belt_nr] = belt_dir;
6179 if (belt_dir_nr == 3)
6182 // set frame order for belt animation graphic according to belt direction
6183 for (i = 0; i < NUM_BELT_PARTS; i++)
6185 int element = belt_base_active_element[belt_nr] + i;
6186 int graphic_1 = el2img(element);
6187 int graphic_2 = el2panelimg(element);
6189 if (belt_dir == MV_LEFT)
6191 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6192 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6196 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6197 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6201 SCAN_PLAYFIELD(xx, yy)
6203 int element = Tile[xx][yy];
6205 if (IS_BELT_SWITCH(element))
6207 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6209 if (e_belt_nr == belt_nr)
6211 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6212 TEST_DrawLevelField(xx, yy);
6215 else if (IS_BELT(element) && belt_dir != MV_NONE)
6217 int e_belt_nr = getBeltNrFromBeltElement(element);
6219 if (e_belt_nr == belt_nr)
6221 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6223 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6224 TEST_DrawLevelField(xx, yy);
6227 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6229 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6231 if (e_belt_nr == belt_nr)
6233 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6235 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6236 TEST_DrawLevelField(xx, yy);
6242 static void ToggleSwitchgateSwitch(int x, int y)
6246 game.switchgate_pos = !game.switchgate_pos;
6248 SCAN_PLAYFIELD(xx, yy)
6250 int element = Tile[xx][yy];
6252 if (element == EL_SWITCHGATE_SWITCH_UP)
6254 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6255 TEST_DrawLevelField(xx, yy);
6257 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6259 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6260 TEST_DrawLevelField(xx, yy);
6262 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6264 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6265 TEST_DrawLevelField(xx, yy);
6267 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6269 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6270 TEST_DrawLevelField(xx, yy);
6272 else if (element == EL_SWITCHGATE_OPEN ||
6273 element == EL_SWITCHGATE_OPENING)
6275 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6277 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6279 else if (element == EL_SWITCHGATE_CLOSED ||
6280 element == EL_SWITCHGATE_CLOSING)
6282 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6284 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6289 static int getInvisibleActiveFromInvisibleElement(int element)
6291 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6292 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6293 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6297 static int getInvisibleFromInvisibleActiveElement(int element)
6299 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6300 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6301 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6305 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6309 SCAN_PLAYFIELD(x, y)
6311 int element = Tile[x][y];
6313 if (element == EL_LIGHT_SWITCH &&
6314 game.light_time_left > 0)
6316 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6317 TEST_DrawLevelField(x, y);
6319 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6320 game.light_time_left == 0)
6322 Tile[x][y] = EL_LIGHT_SWITCH;
6323 TEST_DrawLevelField(x, y);
6325 else if (element == EL_EMC_DRIPPER &&
6326 game.light_time_left > 0)
6328 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6329 TEST_DrawLevelField(x, y);
6331 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6332 game.light_time_left == 0)
6334 Tile[x][y] = EL_EMC_DRIPPER;
6335 TEST_DrawLevelField(x, y);
6337 else if (element == EL_INVISIBLE_STEELWALL ||
6338 element == EL_INVISIBLE_WALL ||
6339 element == EL_INVISIBLE_SAND)
6341 if (game.light_time_left > 0)
6342 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6344 TEST_DrawLevelField(x, y);
6346 // uncrumble neighbour fields, if needed
6347 if (element == EL_INVISIBLE_SAND)
6348 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6350 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6351 element == EL_INVISIBLE_WALL_ACTIVE ||
6352 element == EL_INVISIBLE_SAND_ACTIVE)
6354 if (game.light_time_left == 0)
6355 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6357 TEST_DrawLevelField(x, y);
6359 // re-crumble neighbour fields, if needed
6360 if (element == EL_INVISIBLE_SAND)
6361 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6366 static void RedrawAllInvisibleElementsForLenses(void)
6370 SCAN_PLAYFIELD(x, y)
6372 int element = Tile[x][y];
6374 if (element == EL_EMC_DRIPPER &&
6375 game.lenses_time_left > 0)
6377 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6378 TEST_DrawLevelField(x, y);
6380 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6381 game.lenses_time_left == 0)
6383 Tile[x][y] = EL_EMC_DRIPPER;
6384 TEST_DrawLevelField(x, y);
6386 else if (element == EL_INVISIBLE_STEELWALL ||
6387 element == EL_INVISIBLE_WALL ||
6388 element == EL_INVISIBLE_SAND)
6390 if (game.lenses_time_left > 0)
6391 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6393 TEST_DrawLevelField(x, y);
6395 // uncrumble neighbour fields, if needed
6396 if (element == EL_INVISIBLE_SAND)
6397 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6399 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6400 element == EL_INVISIBLE_WALL_ACTIVE ||
6401 element == EL_INVISIBLE_SAND_ACTIVE)
6403 if (game.lenses_time_left == 0)
6404 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6406 TEST_DrawLevelField(x, y);
6408 // re-crumble neighbour fields, if needed
6409 if (element == EL_INVISIBLE_SAND)
6410 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6415 static void RedrawAllInvisibleElementsForMagnifier(void)
6419 SCAN_PLAYFIELD(x, y)
6421 int element = Tile[x][y];
6423 if (element == EL_EMC_FAKE_GRASS &&
6424 game.magnify_time_left > 0)
6426 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6427 TEST_DrawLevelField(x, y);
6429 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6430 game.magnify_time_left == 0)
6432 Tile[x][y] = EL_EMC_FAKE_GRASS;
6433 TEST_DrawLevelField(x, y);
6435 else if (IS_GATE_GRAY(element) &&
6436 game.magnify_time_left > 0)
6438 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6439 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6440 IS_EM_GATE_GRAY(element) ?
6441 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6442 IS_EMC_GATE_GRAY(element) ?
6443 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6444 IS_DC_GATE_GRAY(element) ?
6445 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6447 TEST_DrawLevelField(x, y);
6449 else if (IS_GATE_GRAY_ACTIVE(element) &&
6450 game.magnify_time_left == 0)
6452 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6453 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6454 IS_EM_GATE_GRAY_ACTIVE(element) ?
6455 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6456 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6457 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6458 IS_DC_GATE_GRAY_ACTIVE(element) ?
6459 EL_DC_GATE_WHITE_GRAY :
6461 TEST_DrawLevelField(x, y);
6466 static void ToggleLightSwitch(int x, int y)
6468 int element = Tile[x][y];
6470 game.light_time_left =
6471 (element == EL_LIGHT_SWITCH ?
6472 level.time_light * FRAMES_PER_SECOND : 0);
6474 RedrawAllLightSwitchesAndInvisibleElements();
6477 static void ActivateTimegateSwitch(int x, int y)
6481 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6483 SCAN_PLAYFIELD(xx, yy)
6485 int element = Tile[xx][yy];
6487 if (element == EL_TIMEGATE_CLOSED ||
6488 element == EL_TIMEGATE_CLOSING)
6490 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6491 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6495 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6497 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6498 TEST_DrawLevelField(xx, yy);
6504 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6505 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6508 static void Impact(int x, int y)
6510 boolean last_line = (y == lev_fieldy - 1);
6511 boolean object_hit = FALSE;
6512 boolean impact = (last_line || object_hit);
6513 int element = Tile[x][y];
6514 int smashed = EL_STEELWALL;
6516 if (!last_line) // check if element below was hit
6518 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6521 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6522 MovDir[x][y + 1] != MV_DOWN ||
6523 MovPos[x][y + 1] <= TILEY / 2));
6525 // do not smash moving elements that left the smashed field in time
6526 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6527 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6530 #if USE_QUICKSAND_IMPACT_BUGFIX
6531 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6533 RemoveMovingField(x, y + 1);
6534 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6535 Tile[x][y + 2] = EL_ROCK;
6536 TEST_DrawLevelField(x, y + 2);
6541 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6543 RemoveMovingField(x, y + 1);
6544 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6545 Tile[x][y + 2] = EL_ROCK;
6546 TEST_DrawLevelField(x, y + 2);
6553 smashed = MovingOrBlocked2Element(x, y + 1);
6555 impact = (last_line || object_hit);
6558 if (!last_line && smashed == EL_ACID) // element falls into acid
6560 SplashAcid(x, y + 1);
6564 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6565 // only reset graphic animation if graphic really changes after impact
6567 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6569 ResetGfxAnimation(x, y);
6570 TEST_DrawLevelField(x, y);
6573 if (impact && CAN_EXPLODE_IMPACT(element))
6578 else if (impact && element == EL_PEARL &&
6579 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6581 ResetGfxAnimation(x, y);
6583 Tile[x][y] = EL_PEARL_BREAKING;
6584 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6587 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6589 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6594 if (impact && element == EL_AMOEBA_DROP)
6596 if (object_hit && IS_PLAYER(x, y + 1))
6597 KillPlayerUnlessEnemyProtected(x, y + 1);
6598 else if (object_hit && smashed == EL_PENGUIN)
6602 Tile[x][y] = EL_AMOEBA_GROWING;
6603 Store[x][y] = EL_AMOEBA_WET;
6605 ResetRandomAnimationValue(x, y);
6610 if (object_hit) // check which object was hit
6612 if ((CAN_PASS_MAGIC_WALL(element) &&
6613 (smashed == EL_MAGIC_WALL ||
6614 smashed == EL_BD_MAGIC_WALL)) ||
6615 (CAN_PASS_DC_MAGIC_WALL(element) &&
6616 smashed == EL_DC_MAGIC_WALL))
6619 int activated_magic_wall =
6620 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6621 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6622 EL_DC_MAGIC_WALL_ACTIVE);
6624 // activate magic wall / mill
6625 SCAN_PLAYFIELD(xx, yy)
6627 if (Tile[xx][yy] == smashed)
6628 Tile[xx][yy] = activated_magic_wall;
6631 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6632 game.magic_wall_active = TRUE;
6634 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6635 SND_MAGIC_WALL_ACTIVATING :
6636 smashed == EL_BD_MAGIC_WALL ?
6637 SND_BD_MAGIC_WALL_ACTIVATING :
6638 SND_DC_MAGIC_WALL_ACTIVATING));
6641 if (IS_PLAYER(x, y + 1))
6643 if (CAN_SMASH_PLAYER(element))
6645 KillPlayerUnlessEnemyProtected(x, y + 1);
6649 else if (smashed == EL_PENGUIN)
6651 if (CAN_SMASH_PLAYER(element))
6657 else if (element == EL_BD_DIAMOND)
6659 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6665 else if (((element == EL_SP_INFOTRON ||
6666 element == EL_SP_ZONK) &&
6667 (smashed == EL_SP_SNIKSNAK ||
6668 smashed == EL_SP_ELECTRON ||
6669 smashed == EL_SP_DISK_ORANGE)) ||
6670 (element == EL_SP_INFOTRON &&
6671 smashed == EL_SP_DISK_YELLOW))
6676 else if (CAN_SMASH_EVERYTHING(element))
6678 if (IS_CLASSIC_ENEMY(smashed) ||
6679 CAN_EXPLODE_SMASHED(smashed))
6684 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6686 if (smashed == EL_LAMP ||
6687 smashed == EL_LAMP_ACTIVE)
6692 else if (smashed == EL_NUT)
6694 Tile[x][y + 1] = EL_NUT_BREAKING;
6695 PlayLevelSound(x, y, SND_NUT_BREAKING);
6696 RaiseScoreElement(EL_NUT);
6699 else if (smashed == EL_PEARL)
6701 ResetGfxAnimation(x, y);
6703 Tile[x][y + 1] = EL_PEARL_BREAKING;
6704 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6707 else if (smashed == EL_DIAMOND)
6709 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6710 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6713 else if (IS_BELT_SWITCH(smashed))
6715 ToggleBeltSwitch(x, y + 1);
6717 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6718 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6719 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6720 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6722 ToggleSwitchgateSwitch(x, y + 1);
6724 else if (smashed == EL_LIGHT_SWITCH ||
6725 smashed == EL_LIGHT_SWITCH_ACTIVE)
6727 ToggleLightSwitch(x, y + 1);
6731 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6733 CheckElementChangeBySide(x, y + 1, smashed, element,
6734 CE_SWITCHED, CH_SIDE_TOP);
6735 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6741 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6746 // play sound of magic wall / mill
6748 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6749 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6750 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6752 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6753 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6754 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6755 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6756 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6757 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6762 // play sound of object that hits the ground
6763 if (last_line || object_hit)
6764 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6767 static void TurnRoundExt(int x, int y)
6779 { 0, 0 }, { 0, 0 }, { 0, 0 },
6784 int left, right, back;
6788 { MV_DOWN, MV_UP, MV_RIGHT },
6789 { MV_UP, MV_DOWN, MV_LEFT },
6791 { MV_LEFT, MV_RIGHT, MV_DOWN },
6795 { MV_RIGHT, MV_LEFT, MV_UP }
6798 int element = Tile[x][y];
6799 int move_pattern = element_info[element].move_pattern;
6801 int old_move_dir = MovDir[x][y];
6802 int left_dir = turn[old_move_dir].left;
6803 int right_dir = turn[old_move_dir].right;
6804 int back_dir = turn[old_move_dir].back;
6806 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6807 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6808 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6809 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6811 int left_x = x + left_dx, left_y = y + left_dy;
6812 int right_x = x + right_dx, right_y = y + right_dy;
6813 int move_x = x + move_dx, move_y = y + move_dy;
6817 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6819 TestIfBadThingTouchesOtherBadThing(x, y);
6821 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6822 MovDir[x][y] = right_dir;
6823 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6824 MovDir[x][y] = left_dir;
6826 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6828 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6831 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6833 TestIfBadThingTouchesOtherBadThing(x, y);
6835 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6836 MovDir[x][y] = left_dir;
6837 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6838 MovDir[x][y] = right_dir;
6840 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6842 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6845 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6847 TestIfBadThingTouchesOtherBadThing(x, y);
6849 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6850 MovDir[x][y] = left_dir;
6851 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6852 MovDir[x][y] = right_dir;
6854 if (MovDir[x][y] != old_move_dir)
6857 else if (element == EL_YAMYAM)
6859 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6860 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6862 if (can_turn_left && can_turn_right)
6863 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6864 else if (can_turn_left)
6865 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6866 else if (can_turn_right)
6867 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6869 MovDir[x][y] = back_dir;
6871 MovDelay[x][y] = 16 + 16 * RND(3);
6873 else if (element == EL_DARK_YAMYAM)
6875 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6877 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6880 if (can_turn_left && can_turn_right)
6881 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6882 else if (can_turn_left)
6883 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6884 else if (can_turn_right)
6885 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6887 MovDir[x][y] = back_dir;
6889 MovDelay[x][y] = 16 + 16 * RND(3);
6891 else if (element == EL_PACMAN)
6893 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6894 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6896 if (can_turn_left && can_turn_right)
6897 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6898 else if (can_turn_left)
6899 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6900 else if (can_turn_right)
6901 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6903 MovDir[x][y] = back_dir;
6905 MovDelay[x][y] = 6 + RND(40);
6907 else if (element == EL_PIG)
6909 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6910 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6911 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6912 boolean should_turn_left, should_turn_right, should_move_on;
6914 int rnd = RND(rnd_value);
6916 should_turn_left = (can_turn_left &&
6918 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6919 y + back_dy + left_dy)));
6920 should_turn_right = (can_turn_right &&
6922 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6923 y + back_dy + right_dy)));
6924 should_move_on = (can_move_on &&
6927 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6928 y + move_dy + left_dy) ||
6929 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6930 y + move_dy + right_dy)));
6932 if (should_turn_left || should_turn_right || should_move_on)
6934 if (should_turn_left && should_turn_right && should_move_on)
6935 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6936 rnd < 2 * rnd_value / 3 ? right_dir :
6938 else if (should_turn_left && should_turn_right)
6939 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6940 else if (should_turn_left && should_move_on)
6941 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6942 else if (should_turn_right && should_move_on)
6943 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6944 else if (should_turn_left)
6945 MovDir[x][y] = left_dir;
6946 else if (should_turn_right)
6947 MovDir[x][y] = right_dir;
6948 else if (should_move_on)
6949 MovDir[x][y] = old_move_dir;
6951 else if (can_move_on && rnd > rnd_value / 8)
6952 MovDir[x][y] = old_move_dir;
6953 else if (can_turn_left && can_turn_right)
6954 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6955 else if (can_turn_left && rnd > rnd_value / 8)
6956 MovDir[x][y] = left_dir;
6957 else if (can_turn_right && rnd > rnd_value/8)
6958 MovDir[x][y] = right_dir;
6960 MovDir[x][y] = back_dir;
6962 xx = x + move_xy[MovDir[x][y]].dx;
6963 yy = y + move_xy[MovDir[x][y]].dy;
6965 if (!IN_LEV_FIELD(xx, yy) ||
6966 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6967 MovDir[x][y] = old_move_dir;
6971 else if (element == EL_DRAGON)
6973 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6974 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6975 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6977 int rnd = RND(rnd_value);
6979 if (can_move_on && rnd > rnd_value / 8)
6980 MovDir[x][y] = old_move_dir;
6981 else if (can_turn_left && can_turn_right)
6982 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6983 else if (can_turn_left && rnd > rnd_value / 8)
6984 MovDir[x][y] = left_dir;
6985 else if (can_turn_right && rnd > rnd_value / 8)
6986 MovDir[x][y] = right_dir;
6988 MovDir[x][y] = back_dir;
6990 xx = x + move_xy[MovDir[x][y]].dx;
6991 yy = y + move_xy[MovDir[x][y]].dy;
6993 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6994 MovDir[x][y] = old_move_dir;
6998 else if (element == EL_MOLE)
7000 boolean can_move_on =
7001 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7002 IS_AMOEBOID(Tile[move_x][move_y]) ||
7003 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7006 boolean can_turn_left =
7007 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7008 IS_AMOEBOID(Tile[left_x][left_y])));
7010 boolean can_turn_right =
7011 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7012 IS_AMOEBOID(Tile[right_x][right_y])));
7014 if (can_turn_left && can_turn_right)
7015 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7016 else if (can_turn_left)
7017 MovDir[x][y] = left_dir;
7019 MovDir[x][y] = right_dir;
7022 if (MovDir[x][y] != old_move_dir)
7025 else if (element == EL_BALLOON)
7027 MovDir[x][y] = game.wind_direction;
7030 else if (element == EL_SPRING)
7032 if (MovDir[x][y] & MV_HORIZONTAL)
7034 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7035 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7037 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7038 ResetGfxAnimation(move_x, move_y);
7039 TEST_DrawLevelField(move_x, move_y);
7041 MovDir[x][y] = back_dir;
7043 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7044 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7045 MovDir[x][y] = MV_NONE;
7050 else if (element == EL_ROBOT ||
7051 element == EL_SATELLITE ||
7052 element == EL_PENGUIN ||
7053 element == EL_EMC_ANDROID)
7055 int attr_x = -1, attr_y = -1;
7057 if (game.all_players_gone)
7059 attr_x = game.exit_x;
7060 attr_y = game.exit_y;
7066 for (i = 0; i < MAX_PLAYERS; i++)
7068 struct PlayerInfo *player = &stored_player[i];
7069 int jx = player->jx, jy = player->jy;
7071 if (!player->active)
7075 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7083 if (element == EL_ROBOT &&
7084 game.robot_wheel_x >= 0 &&
7085 game.robot_wheel_y >= 0 &&
7086 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7087 game.engine_version < VERSION_IDENT(3,1,0,0)))
7089 attr_x = game.robot_wheel_x;
7090 attr_y = game.robot_wheel_y;
7093 if (element == EL_PENGUIN)
7096 static int xy[4][2] =
7104 for (i = 0; i < NUM_DIRECTIONS; i++)
7106 int ex = x + xy[i][0];
7107 int ey = y + xy[i][1];
7109 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7110 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7111 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7112 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7121 MovDir[x][y] = MV_NONE;
7123 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7124 else if (attr_x > x)
7125 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7127 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7128 else if (attr_y > y)
7129 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7131 if (element == EL_ROBOT)
7135 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7136 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7137 Moving2Blocked(x, y, &newx, &newy);
7139 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7140 MovDelay[x][y] = 8 + 8 * !RND(3);
7142 MovDelay[x][y] = 16;
7144 else if (element == EL_PENGUIN)
7150 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7152 boolean first_horiz = RND(2);
7153 int new_move_dir = MovDir[x][y];
7156 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7157 Moving2Blocked(x, y, &newx, &newy);
7159 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7163 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7164 Moving2Blocked(x, y, &newx, &newy);
7166 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7169 MovDir[x][y] = old_move_dir;
7173 else if (element == EL_SATELLITE)
7179 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7181 boolean first_horiz = RND(2);
7182 int new_move_dir = MovDir[x][y];
7185 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7186 Moving2Blocked(x, y, &newx, &newy);
7188 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7192 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7193 Moving2Blocked(x, y, &newx, &newy);
7195 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7198 MovDir[x][y] = old_move_dir;
7202 else if (element == EL_EMC_ANDROID)
7204 static int check_pos[16] =
7206 -1, // 0 => (invalid)
7209 -1, // 3 => (invalid)
7211 0, // 5 => MV_LEFT | MV_UP
7212 2, // 6 => MV_RIGHT | MV_UP
7213 -1, // 7 => (invalid)
7215 6, // 9 => MV_LEFT | MV_DOWN
7216 4, // 10 => MV_RIGHT | MV_DOWN
7217 -1, // 11 => (invalid)
7218 -1, // 12 => (invalid)
7219 -1, // 13 => (invalid)
7220 -1, // 14 => (invalid)
7221 -1, // 15 => (invalid)
7229 { -1, -1, MV_LEFT | MV_UP },
7231 { +1, -1, MV_RIGHT | MV_UP },
7232 { +1, 0, MV_RIGHT },
7233 { +1, +1, MV_RIGHT | MV_DOWN },
7235 { -1, +1, MV_LEFT | MV_DOWN },
7238 int start_pos, check_order;
7239 boolean can_clone = FALSE;
7242 // check if there is any free field around current position
7243 for (i = 0; i < 8; i++)
7245 int newx = x + check_xy[i].dx;
7246 int newy = y + check_xy[i].dy;
7248 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7256 if (can_clone) // randomly find an element to clone
7260 start_pos = check_pos[RND(8)];
7261 check_order = (RND(2) ? -1 : +1);
7263 for (i = 0; i < 8; i++)
7265 int pos_raw = start_pos + i * check_order;
7266 int pos = (pos_raw + 8) % 8;
7267 int newx = x + check_xy[pos].dx;
7268 int newy = y + check_xy[pos].dy;
7270 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7272 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7273 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7275 Store[x][y] = Tile[newx][newy];
7284 if (can_clone) // randomly find a direction to move
7288 start_pos = check_pos[RND(8)];
7289 check_order = (RND(2) ? -1 : +1);
7291 for (i = 0; i < 8; i++)
7293 int pos_raw = start_pos + i * check_order;
7294 int pos = (pos_raw + 8) % 8;
7295 int newx = x + check_xy[pos].dx;
7296 int newy = y + check_xy[pos].dy;
7297 int new_move_dir = check_xy[pos].dir;
7299 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7301 MovDir[x][y] = new_move_dir;
7302 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7311 if (can_clone) // cloning and moving successful
7314 // cannot clone -- try to move towards player
7316 start_pos = check_pos[MovDir[x][y] & 0x0f];
7317 check_order = (RND(2) ? -1 : +1);
7319 for (i = 0; i < 3; i++)
7321 // first check start_pos, then previous/next or (next/previous) pos
7322 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7323 int pos = (pos_raw + 8) % 8;
7324 int newx = x + check_xy[pos].dx;
7325 int newy = y + check_xy[pos].dy;
7326 int new_move_dir = check_xy[pos].dir;
7328 if (IS_PLAYER(newx, newy))
7331 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7333 MovDir[x][y] = new_move_dir;
7334 MovDelay[x][y] = level.android_move_time * 8 + 1;
7341 else if (move_pattern == MV_TURNING_LEFT ||
7342 move_pattern == MV_TURNING_RIGHT ||
7343 move_pattern == MV_TURNING_LEFT_RIGHT ||
7344 move_pattern == MV_TURNING_RIGHT_LEFT ||
7345 move_pattern == MV_TURNING_RANDOM ||
7346 move_pattern == MV_ALL_DIRECTIONS)
7348 boolean can_turn_left =
7349 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7350 boolean can_turn_right =
7351 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7353 if (element_info[element].move_stepsize == 0) // "not moving"
7356 if (move_pattern == MV_TURNING_LEFT)
7357 MovDir[x][y] = left_dir;
7358 else if (move_pattern == MV_TURNING_RIGHT)
7359 MovDir[x][y] = right_dir;
7360 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7361 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7362 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7363 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7364 else if (move_pattern == MV_TURNING_RANDOM)
7365 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7366 can_turn_right && !can_turn_left ? right_dir :
7367 RND(2) ? left_dir : right_dir);
7368 else if (can_turn_left && can_turn_right)
7369 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7370 else if (can_turn_left)
7371 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7372 else if (can_turn_right)
7373 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7375 MovDir[x][y] = back_dir;
7377 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7379 else if (move_pattern == MV_HORIZONTAL ||
7380 move_pattern == MV_VERTICAL)
7382 if (move_pattern & old_move_dir)
7383 MovDir[x][y] = back_dir;
7384 else if (move_pattern == MV_HORIZONTAL)
7385 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7386 else if (move_pattern == MV_VERTICAL)
7387 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7389 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7391 else if (move_pattern & MV_ANY_DIRECTION)
7393 MovDir[x][y] = move_pattern;
7394 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7396 else if (move_pattern & MV_WIND_DIRECTION)
7398 MovDir[x][y] = game.wind_direction;
7399 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7401 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7403 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7404 MovDir[x][y] = left_dir;
7405 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7406 MovDir[x][y] = right_dir;
7408 if (MovDir[x][y] != old_move_dir)
7409 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7411 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7413 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7414 MovDir[x][y] = right_dir;
7415 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7416 MovDir[x][y] = left_dir;
7418 if (MovDir[x][y] != old_move_dir)
7419 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7421 else if (move_pattern == MV_TOWARDS_PLAYER ||
7422 move_pattern == MV_AWAY_FROM_PLAYER)
7424 int attr_x = -1, attr_y = -1;
7426 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7428 if (game.all_players_gone)
7430 attr_x = game.exit_x;
7431 attr_y = game.exit_y;
7437 for (i = 0; i < MAX_PLAYERS; i++)
7439 struct PlayerInfo *player = &stored_player[i];
7440 int jx = player->jx, jy = player->jy;
7442 if (!player->active)
7446 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7454 MovDir[x][y] = MV_NONE;
7456 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7457 else if (attr_x > x)
7458 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7460 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7461 else if (attr_y > y)
7462 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7464 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7466 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7468 boolean first_horiz = RND(2);
7469 int new_move_dir = MovDir[x][y];
7471 if (element_info[element].move_stepsize == 0) // "not moving"
7473 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7474 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7480 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7481 Moving2Blocked(x, y, &newx, &newy);
7483 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7487 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7488 Moving2Blocked(x, y, &newx, &newy);
7490 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7493 MovDir[x][y] = old_move_dir;
7496 else if (move_pattern == MV_WHEN_PUSHED ||
7497 move_pattern == MV_WHEN_DROPPED)
7499 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7500 MovDir[x][y] = MV_NONE;
7504 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7506 static int test_xy[7][2] =
7516 static int test_dir[7] =
7526 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7527 int move_preference = -1000000; // start with very low preference
7528 int new_move_dir = MV_NONE;
7529 int start_test = RND(4);
7532 for (i = 0; i < NUM_DIRECTIONS; i++)
7534 int move_dir = test_dir[start_test + i];
7535 int move_dir_preference;
7537 xx = x + test_xy[start_test + i][0];
7538 yy = y + test_xy[start_test + i][1];
7540 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7541 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7543 new_move_dir = move_dir;
7548 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7551 move_dir_preference = -1 * RunnerVisit[xx][yy];
7552 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7553 move_dir_preference = PlayerVisit[xx][yy];
7555 if (move_dir_preference > move_preference)
7557 // prefer field that has not been visited for the longest time
7558 move_preference = move_dir_preference;
7559 new_move_dir = move_dir;
7561 else if (move_dir_preference == move_preference &&
7562 move_dir == old_move_dir)
7564 // prefer last direction when all directions are preferred equally
7565 move_preference = move_dir_preference;
7566 new_move_dir = move_dir;
7570 MovDir[x][y] = new_move_dir;
7571 if (old_move_dir != new_move_dir)
7572 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7576 static void TurnRound(int x, int y)
7578 int direction = MovDir[x][y];
7582 GfxDir[x][y] = MovDir[x][y];
7584 if (direction != MovDir[x][y])
7588 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7590 ResetGfxFrame(x, y);
7593 static boolean JustBeingPushed(int x, int y)
7597 for (i = 0; i < MAX_PLAYERS; i++)
7599 struct PlayerInfo *player = &stored_player[i];
7601 if (player->active && player->is_pushing && player->MovPos)
7603 int next_jx = player->jx + (player->jx - player->last_jx);
7604 int next_jy = player->jy + (player->jy - player->last_jy);
7606 if (x == next_jx && y == next_jy)
7614 static void StartMoving(int x, int y)
7616 boolean started_moving = FALSE; // some elements can fall _and_ move
7617 int element = Tile[x][y];
7622 if (MovDelay[x][y] == 0)
7623 GfxAction[x][y] = ACTION_DEFAULT;
7625 if (CAN_FALL(element) && y < lev_fieldy - 1)
7627 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7628 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7629 if (JustBeingPushed(x, y))
7632 if (element == EL_QUICKSAND_FULL)
7634 if (IS_FREE(x, y + 1))
7636 InitMovingField(x, y, MV_DOWN);
7637 started_moving = TRUE;
7639 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7640 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7641 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7642 Store[x][y] = EL_ROCK;
7644 Store[x][y] = EL_ROCK;
7647 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7649 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7651 if (!MovDelay[x][y])
7653 MovDelay[x][y] = TILEY + 1;
7655 ResetGfxAnimation(x, y);
7656 ResetGfxAnimation(x, y + 1);
7661 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7662 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7669 Tile[x][y] = EL_QUICKSAND_EMPTY;
7670 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7671 Store[x][y + 1] = Store[x][y];
7674 PlayLevelSoundAction(x, y, ACTION_FILLING);
7676 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7678 if (!MovDelay[x][y])
7680 MovDelay[x][y] = TILEY + 1;
7682 ResetGfxAnimation(x, y);
7683 ResetGfxAnimation(x, y + 1);
7688 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7689 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7696 Tile[x][y] = EL_QUICKSAND_EMPTY;
7697 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7698 Store[x][y + 1] = Store[x][y];
7701 PlayLevelSoundAction(x, y, ACTION_FILLING);
7704 else if (element == EL_QUICKSAND_FAST_FULL)
7706 if (IS_FREE(x, y + 1))
7708 InitMovingField(x, y, MV_DOWN);
7709 started_moving = TRUE;
7711 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7712 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7713 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7714 Store[x][y] = EL_ROCK;
7716 Store[x][y] = EL_ROCK;
7719 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7721 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7723 if (!MovDelay[x][y])
7725 MovDelay[x][y] = TILEY + 1;
7727 ResetGfxAnimation(x, y);
7728 ResetGfxAnimation(x, y + 1);
7733 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7734 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7741 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7742 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7743 Store[x][y + 1] = Store[x][y];
7746 PlayLevelSoundAction(x, y, ACTION_FILLING);
7748 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7750 if (!MovDelay[x][y])
7752 MovDelay[x][y] = TILEY + 1;
7754 ResetGfxAnimation(x, y);
7755 ResetGfxAnimation(x, y + 1);
7760 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7761 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7768 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7769 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7770 Store[x][y + 1] = Store[x][y];
7773 PlayLevelSoundAction(x, y, ACTION_FILLING);
7776 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7777 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7779 InitMovingField(x, y, MV_DOWN);
7780 started_moving = TRUE;
7782 Tile[x][y] = EL_QUICKSAND_FILLING;
7783 Store[x][y] = element;
7785 PlayLevelSoundAction(x, y, ACTION_FILLING);
7787 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7788 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7790 InitMovingField(x, y, MV_DOWN);
7791 started_moving = TRUE;
7793 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7794 Store[x][y] = element;
7796 PlayLevelSoundAction(x, y, ACTION_FILLING);
7798 else if (element == EL_MAGIC_WALL_FULL)
7800 if (IS_FREE(x, y + 1))
7802 InitMovingField(x, y, MV_DOWN);
7803 started_moving = TRUE;
7805 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7806 Store[x][y] = EL_CHANGED(Store[x][y]);
7808 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7810 if (!MovDelay[x][y])
7811 MovDelay[x][y] = TILEY / 4 + 1;
7820 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7821 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7822 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7826 else if (element == EL_BD_MAGIC_WALL_FULL)
7828 if (IS_FREE(x, y + 1))
7830 InitMovingField(x, y, MV_DOWN);
7831 started_moving = TRUE;
7833 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7834 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7836 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7838 if (!MovDelay[x][y])
7839 MovDelay[x][y] = TILEY / 4 + 1;
7848 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7849 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7850 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7854 else if (element == EL_DC_MAGIC_WALL_FULL)
7856 if (IS_FREE(x, y + 1))
7858 InitMovingField(x, y, MV_DOWN);
7859 started_moving = TRUE;
7861 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7862 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7864 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7866 if (!MovDelay[x][y])
7867 MovDelay[x][y] = TILEY / 4 + 1;
7876 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7877 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7878 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7882 else if ((CAN_PASS_MAGIC_WALL(element) &&
7883 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7884 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7885 (CAN_PASS_DC_MAGIC_WALL(element) &&
7886 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7889 InitMovingField(x, y, MV_DOWN);
7890 started_moving = TRUE;
7893 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7894 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7895 EL_DC_MAGIC_WALL_FILLING);
7896 Store[x][y] = element;
7898 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7900 SplashAcid(x, y + 1);
7902 InitMovingField(x, y, MV_DOWN);
7903 started_moving = TRUE;
7905 Store[x][y] = EL_ACID;
7908 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7909 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7910 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7911 CAN_FALL(element) && WasJustFalling[x][y] &&
7912 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7914 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7915 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7916 (Tile[x][y + 1] == EL_BLOCKED)))
7918 /* this is needed for a special case not covered by calling "Impact()"
7919 from "ContinueMoving()": if an element moves to a tile directly below
7920 another element which was just falling on that tile (which was empty
7921 in the previous frame), the falling element above would just stop
7922 instead of smashing the element below (in previous version, the above
7923 element was just checked for "moving" instead of "falling", resulting
7924 in incorrect smashes caused by horizontal movement of the above
7925 element; also, the case of the player being the element to smash was
7926 simply not covered here... :-/ ) */
7928 CheckCollision[x][y] = 0;
7929 CheckImpact[x][y] = 0;
7933 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7935 if (MovDir[x][y] == MV_NONE)
7937 InitMovingField(x, y, MV_DOWN);
7938 started_moving = TRUE;
7941 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7943 if (WasJustFalling[x][y]) // prevent animation from being restarted
7944 MovDir[x][y] = MV_DOWN;
7946 InitMovingField(x, y, MV_DOWN);
7947 started_moving = TRUE;
7949 else if (element == EL_AMOEBA_DROP)
7951 Tile[x][y] = EL_AMOEBA_GROWING;
7952 Store[x][y] = EL_AMOEBA_WET;
7954 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7955 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7956 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7957 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7959 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7960 (IS_FREE(x - 1, y + 1) ||
7961 Tile[x - 1][y + 1] == EL_ACID));
7962 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7963 (IS_FREE(x + 1, y + 1) ||
7964 Tile[x + 1][y + 1] == EL_ACID));
7965 boolean can_fall_any = (can_fall_left || can_fall_right);
7966 boolean can_fall_both = (can_fall_left && can_fall_right);
7967 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7969 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7971 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7972 can_fall_right = FALSE;
7973 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7974 can_fall_left = FALSE;
7975 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7976 can_fall_right = FALSE;
7977 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7978 can_fall_left = FALSE;
7980 can_fall_any = (can_fall_left || can_fall_right);
7981 can_fall_both = FALSE;
7986 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7987 can_fall_right = FALSE; // slip down on left side
7989 can_fall_left = !(can_fall_right = RND(2));
7991 can_fall_both = FALSE;
7996 // if not determined otherwise, prefer left side for slipping down
7997 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7998 started_moving = TRUE;
8001 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8003 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8004 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8005 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8006 int belt_dir = game.belt_dir[belt_nr];
8008 if ((belt_dir == MV_LEFT && left_is_free) ||
8009 (belt_dir == MV_RIGHT && right_is_free))
8011 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8013 InitMovingField(x, y, belt_dir);
8014 started_moving = TRUE;
8016 Pushed[x][y] = TRUE;
8017 Pushed[nextx][y] = TRUE;
8019 GfxAction[x][y] = ACTION_DEFAULT;
8023 MovDir[x][y] = 0; // if element was moving, stop it
8028 // not "else if" because of elements that can fall and move (EL_SPRING)
8029 if (CAN_MOVE(element) && !started_moving)
8031 int move_pattern = element_info[element].move_pattern;
8034 Moving2Blocked(x, y, &newx, &newy);
8036 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8039 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8040 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8042 WasJustMoving[x][y] = 0;
8043 CheckCollision[x][y] = 0;
8045 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8047 if (Tile[x][y] != element) // element has changed
8051 if (!MovDelay[x][y]) // start new movement phase
8053 // all objects that can change their move direction after each step
8054 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8056 if (element != EL_YAMYAM &&
8057 element != EL_DARK_YAMYAM &&
8058 element != EL_PACMAN &&
8059 !(move_pattern & MV_ANY_DIRECTION) &&
8060 move_pattern != MV_TURNING_LEFT &&
8061 move_pattern != MV_TURNING_RIGHT &&
8062 move_pattern != MV_TURNING_LEFT_RIGHT &&
8063 move_pattern != MV_TURNING_RIGHT_LEFT &&
8064 move_pattern != MV_TURNING_RANDOM)
8068 if (MovDelay[x][y] && (element == EL_BUG ||
8069 element == EL_SPACESHIP ||
8070 element == EL_SP_SNIKSNAK ||
8071 element == EL_SP_ELECTRON ||
8072 element == EL_MOLE))
8073 TEST_DrawLevelField(x, y);
8077 if (MovDelay[x][y]) // wait some time before next movement
8081 if (element == EL_ROBOT ||
8082 element == EL_YAMYAM ||
8083 element == EL_DARK_YAMYAM)
8085 DrawLevelElementAnimationIfNeeded(x, y, element);
8086 PlayLevelSoundAction(x, y, ACTION_WAITING);
8088 else if (element == EL_SP_ELECTRON)
8089 DrawLevelElementAnimationIfNeeded(x, y, element);
8090 else if (element == EL_DRAGON)
8093 int dir = MovDir[x][y];
8094 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8095 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8096 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8097 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8098 dir == MV_UP ? IMG_FLAMES_1_UP :
8099 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8100 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8102 GfxAction[x][y] = ACTION_ATTACKING;
8104 if (IS_PLAYER(x, y))
8105 DrawPlayerField(x, y);
8107 TEST_DrawLevelField(x, y);
8109 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8111 for (i = 1; i <= 3; i++)
8113 int xx = x + i * dx;
8114 int yy = y + i * dy;
8115 int sx = SCREENX(xx);
8116 int sy = SCREENY(yy);
8117 int flame_graphic = graphic + (i - 1);
8119 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8124 int flamed = MovingOrBlocked2Element(xx, yy);
8126 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8129 RemoveMovingField(xx, yy);
8131 ChangeDelay[xx][yy] = 0;
8133 Tile[xx][yy] = EL_FLAMES;
8135 if (IN_SCR_FIELD(sx, sy))
8137 TEST_DrawLevelFieldCrumbled(xx, yy);
8138 DrawGraphic(sx, sy, flame_graphic, frame);
8143 if (Tile[xx][yy] == EL_FLAMES)
8144 Tile[xx][yy] = EL_EMPTY;
8145 TEST_DrawLevelField(xx, yy);
8150 if (MovDelay[x][y]) // element still has to wait some time
8152 PlayLevelSoundAction(x, y, ACTION_WAITING);
8158 // now make next step
8160 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8162 if (DONT_COLLIDE_WITH(element) &&
8163 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8164 !PLAYER_ENEMY_PROTECTED(newx, newy))
8166 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8171 else if (CAN_MOVE_INTO_ACID(element) &&
8172 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8173 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8174 (MovDir[x][y] == MV_DOWN ||
8175 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8177 SplashAcid(newx, newy);
8178 Store[x][y] = EL_ACID;
8180 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8182 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8183 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8184 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8185 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8188 TEST_DrawLevelField(x, y);
8190 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8191 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8192 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8194 game.friends_still_needed--;
8195 if (!game.friends_still_needed &&
8197 game.all_players_gone)
8202 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8204 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8205 TEST_DrawLevelField(newx, newy);
8207 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8209 else if (!IS_FREE(newx, newy))
8211 GfxAction[x][y] = ACTION_WAITING;
8213 if (IS_PLAYER(x, y))
8214 DrawPlayerField(x, y);
8216 TEST_DrawLevelField(x, y);
8221 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8223 if (IS_FOOD_PIG(Tile[newx][newy]))
8225 if (IS_MOVING(newx, newy))
8226 RemoveMovingField(newx, newy);
8229 Tile[newx][newy] = EL_EMPTY;
8230 TEST_DrawLevelField(newx, newy);
8233 PlayLevelSound(x, y, SND_PIG_DIGGING);
8235 else if (!IS_FREE(newx, newy))
8237 if (IS_PLAYER(x, y))
8238 DrawPlayerField(x, y);
8240 TEST_DrawLevelField(x, y);
8245 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8247 if (Store[x][y] != EL_EMPTY)
8249 boolean can_clone = FALSE;
8252 // check if element to clone is still there
8253 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8255 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8263 // cannot clone or target field not free anymore -- do not clone
8264 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8265 Store[x][y] = EL_EMPTY;
8268 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8270 if (IS_MV_DIAGONAL(MovDir[x][y]))
8272 int diagonal_move_dir = MovDir[x][y];
8273 int stored = Store[x][y];
8274 int change_delay = 8;
8277 // android is moving diagonally
8279 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8281 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8282 GfxElement[x][y] = EL_EMC_ANDROID;
8283 GfxAction[x][y] = ACTION_SHRINKING;
8284 GfxDir[x][y] = diagonal_move_dir;
8285 ChangeDelay[x][y] = change_delay;
8287 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8290 DrawLevelGraphicAnimation(x, y, graphic);
8291 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8293 if (Tile[newx][newy] == EL_ACID)
8295 SplashAcid(newx, newy);
8300 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8302 Store[newx][newy] = EL_EMC_ANDROID;
8303 GfxElement[newx][newy] = EL_EMC_ANDROID;
8304 GfxAction[newx][newy] = ACTION_GROWING;
8305 GfxDir[newx][newy] = diagonal_move_dir;
8306 ChangeDelay[newx][newy] = change_delay;
8308 graphic = el_act_dir2img(GfxElement[newx][newy],
8309 GfxAction[newx][newy], GfxDir[newx][newy]);
8311 DrawLevelGraphicAnimation(newx, newy, graphic);
8312 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8318 Tile[newx][newy] = EL_EMPTY;
8319 TEST_DrawLevelField(newx, newy);
8321 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8324 else if (!IS_FREE(newx, newy))
8329 else if (IS_CUSTOM_ELEMENT(element) &&
8330 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8332 if (!DigFieldByCE(newx, newy, element))
8335 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8337 RunnerVisit[x][y] = FrameCounter;
8338 PlayerVisit[x][y] /= 8; // expire player visit path
8341 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8343 if (!IS_FREE(newx, newy))
8345 if (IS_PLAYER(x, y))
8346 DrawPlayerField(x, y);
8348 TEST_DrawLevelField(x, y);
8354 boolean wanna_flame = !RND(10);
8355 int dx = newx - x, dy = newy - y;
8356 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8357 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8358 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8359 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8360 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8361 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8364 IS_CLASSIC_ENEMY(element1) ||
8365 IS_CLASSIC_ENEMY(element2)) &&
8366 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8367 element1 != EL_FLAMES && element2 != EL_FLAMES)
8369 ResetGfxAnimation(x, y);
8370 GfxAction[x][y] = ACTION_ATTACKING;
8372 if (IS_PLAYER(x, y))
8373 DrawPlayerField(x, y);
8375 TEST_DrawLevelField(x, y);
8377 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8379 MovDelay[x][y] = 50;
8381 Tile[newx][newy] = EL_FLAMES;
8382 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8383 Tile[newx1][newy1] = EL_FLAMES;
8384 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8385 Tile[newx2][newy2] = EL_FLAMES;
8391 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8392 Tile[newx][newy] == EL_DIAMOND)
8394 if (IS_MOVING(newx, newy))
8395 RemoveMovingField(newx, newy);
8398 Tile[newx][newy] = EL_EMPTY;
8399 TEST_DrawLevelField(newx, newy);
8402 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8404 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8405 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8407 if (AmoebaNr[newx][newy])
8409 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8410 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8411 Tile[newx][newy] == EL_BD_AMOEBA)
8412 AmoebaCnt[AmoebaNr[newx][newy]]--;
8415 if (IS_MOVING(newx, newy))
8417 RemoveMovingField(newx, newy);
8421 Tile[newx][newy] = EL_EMPTY;
8422 TEST_DrawLevelField(newx, newy);
8425 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8427 else if ((element == EL_PACMAN || element == EL_MOLE)
8428 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8430 if (AmoebaNr[newx][newy])
8432 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8433 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8434 Tile[newx][newy] == EL_BD_AMOEBA)
8435 AmoebaCnt[AmoebaNr[newx][newy]]--;
8438 if (element == EL_MOLE)
8440 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8441 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8443 ResetGfxAnimation(x, y);
8444 GfxAction[x][y] = ACTION_DIGGING;
8445 TEST_DrawLevelField(x, y);
8447 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8449 return; // wait for shrinking amoeba
8451 else // element == EL_PACMAN
8453 Tile[newx][newy] = EL_EMPTY;
8454 TEST_DrawLevelField(newx, newy);
8455 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8458 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8459 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8460 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8462 // wait for shrinking amoeba to completely disappear
8465 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8467 // object was running against a wall
8471 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8472 DrawLevelElementAnimation(x, y, element);
8474 if (DONT_TOUCH(element))
8475 TestIfBadThingTouchesPlayer(x, y);
8480 InitMovingField(x, y, MovDir[x][y]);
8482 PlayLevelSoundAction(x, y, ACTION_MOVING);
8486 ContinueMoving(x, y);
8489 void ContinueMoving(int x, int y)
8491 int element = Tile[x][y];
8492 struct ElementInfo *ei = &element_info[element];
8493 int direction = MovDir[x][y];
8494 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8495 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8496 int newx = x + dx, newy = y + dy;
8497 int stored = Store[x][y];
8498 int stored_new = Store[newx][newy];
8499 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8500 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8501 boolean last_line = (newy == lev_fieldy - 1);
8503 MovPos[x][y] += getElementMoveStepsize(x, y);
8505 if (pushed_by_player) // special case: moving object pushed by player
8506 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8508 if (ABS(MovPos[x][y]) < TILEX)
8510 TEST_DrawLevelField(x, y);
8512 return; // element is still moving
8515 // element reached destination field
8517 Tile[x][y] = EL_EMPTY;
8518 Tile[newx][newy] = element;
8519 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8521 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8523 element = Tile[newx][newy] = EL_ACID;
8525 else if (element == EL_MOLE)
8527 Tile[x][y] = EL_SAND;
8529 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8531 else if (element == EL_QUICKSAND_FILLING)
8533 element = Tile[newx][newy] = get_next_element(element);
8534 Store[newx][newy] = Store[x][y];
8536 else if (element == EL_QUICKSAND_EMPTYING)
8538 Tile[x][y] = get_next_element(element);
8539 element = Tile[newx][newy] = Store[x][y];
8541 else if (element == EL_QUICKSAND_FAST_FILLING)
8543 element = Tile[newx][newy] = get_next_element(element);
8544 Store[newx][newy] = Store[x][y];
8546 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8548 Tile[x][y] = get_next_element(element);
8549 element = Tile[newx][newy] = Store[x][y];
8551 else if (element == EL_MAGIC_WALL_FILLING)
8553 element = Tile[newx][newy] = get_next_element(element);
8554 if (!game.magic_wall_active)
8555 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8556 Store[newx][newy] = Store[x][y];
8558 else if (element == EL_MAGIC_WALL_EMPTYING)
8560 Tile[x][y] = get_next_element(element);
8561 if (!game.magic_wall_active)
8562 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8563 element = Tile[newx][newy] = Store[x][y];
8565 InitField(newx, newy, FALSE);
8567 else if (element == EL_BD_MAGIC_WALL_FILLING)
8569 element = Tile[newx][newy] = get_next_element(element);
8570 if (!game.magic_wall_active)
8571 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8572 Store[newx][newy] = Store[x][y];
8574 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8576 Tile[x][y] = get_next_element(element);
8577 if (!game.magic_wall_active)
8578 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8579 element = Tile[newx][newy] = Store[x][y];
8581 InitField(newx, newy, FALSE);
8583 else if (element == EL_DC_MAGIC_WALL_FILLING)
8585 element = Tile[newx][newy] = get_next_element(element);
8586 if (!game.magic_wall_active)
8587 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8588 Store[newx][newy] = Store[x][y];
8590 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8592 Tile[x][y] = get_next_element(element);
8593 if (!game.magic_wall_active)
8594 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8595 element = Tile[newx][newy] = Store[x][y];
8597 InitField(newx, newy, FALSE);
8599 else if (element == EL_AMOEBA_DROPPING)
8601 Tile[x][y] = get_next_element(element);
8602 element = Tile[newx][newy] = Store[x][y];
8604 else if (element == EL_SOKOBAN_OBJECT)
8607 Tile[x][y] = Back[x][y];
8609 if (Back[newx][newy])
8610 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8612 Back[x][y] = Back[newx][newy] = 0;
8615 Store[x][y] = EL_EMPTY;
8620 MovDelay[newx][newy] = 0;
8622 if (CAN_CHANGE_OR_HAS_ACTION(element))
8624 // copy element change control values to new field
8625 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8626 ChangePage[newx][newy] = ChangePage[x][y];
8627 ChangeCount[newx][newy] = ChangeCount[x][y];
8628 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8631 CustomValue[newx][newy] = CustomValue[x][y];
8633 ChangeDelay[x][y] = 0;
8634 ChangePage[x][y] = -1;
8635 ChangeCount[x][y] = 0;
8636 ChangeEvent[x][y] = -1;
8638 CustomValue[x][y] = 0;
8640 // copy animation control values to new field
8641 GfxFrame[newx][newy] = GfxFrame[x][y];
8642 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8643 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8644 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8646 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8648 // some elements can leave other elements behind after moving
8649 if (ei->move_leave_element != EL_EMPTY &&
8650 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8651 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8653 int move_leave_element = ei->move_leave_element;
8655 // this makes it possible to leave the removed element again
8656 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8657 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8659 Tile[x][y] = move_leave_element;
8661 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8662 MovDir[x][y] = direction;
8664 InitField(x, y, FALSE);
8666 if (GFX_CRUMBLED(Tile[x][y]))
8667 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8669 if (ELEM_IS_PLAYER(move_leave_element))
8670 RelocatePlayer(x, y, move_leave_element);
8673 // do this after checking for left-behind element
8674 ResetGfxAnimation(x, y); // reset animation values for old field
8676 if (!CAN_MOVE(element) ||
8677 (CAN_FALL(element) && direction == MV_DOWN &&
8678 (element == EL_SPRING ||
8679 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8680 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8681 GfxDir[x][y] = MovDir[newx][newy] = 0;
8683 TEST_DrawLevelField(x, y);
8684 TEST_DrawLevelField(newx, newy);
8686 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8688 // prevent pushed element from moving on in pushed direction
8689 if (pushed_by_player && CAN_MOVE(element) &&
8690 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8691 !(element_info[element].move_pattern & direction))
8692 TurnRound(newx, newy);
8694 // prevent elements on conveyor belt from moving on in last direction
8695 if (pushed_by_conveyor && CAN_FALL(element) &&
8696 direction & MV_HORIZONTAL)
8697 MovDir[newx][newy] = 0;
8699 if (!pushed_by_player)
8701 int nextx = newx + dx, nexty = newy + dy;
8702 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8704 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8706 if (CAN_FALL(element) && direction == MV_DOWN)
8707 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8709 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8710 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8712 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8713 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8716 if (DONT_TOUCH(element)) // object may be nasty to player or others
8718 TestIfBadThingTouchesPlayer(newx, newy);
8719 TestIfBadThingTouchesFriend(newx, newy);
8721 if (!IS_CUSTOM_ELEMENT(element))
8722 TestIfBadThingTouchesOtherBadThing(newx, newy);
8724 else if (element == EL_PENGUIN)
8725 TestIfFriendTouchesBadThing(newx, newy);
8727 if (DONT_GET_HIT_BY(element))
8729 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8732 // give the player one last chance (one more frame) to move away
8733 if (CAN_FALL(element) && direction == MV_DOWN &&
8734 (last_line || (!IS_FREE(x, newy + 1) &&
8735 (!IS_PLAYER(x, newy + 1) ||
8736 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8739 if (pushed_by_player && !game.use_change_when_pushing_bug)
8741 int push_side = MV_DIR_OPPOSITE(direction);
8742 struct PlayerInfo *player = PLAYERINFO(x, y);
8744 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8745 player->index_bit, push_side);
8746 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8747 player->index_bit, push_side);
8750 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8751 MovDelay[newx][newy] = 1;
8753 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8755 TestIfElementTouchesCustomElement(x, y); // empty or new element
8756 TestIfElementHitsCustomElement(newx, newy, direction);
8757 TestIfPlayerTouchesCustomElement(newx, newy);
8758 TestIfElementTouchesCustomElement(newx, newy);
8760 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8761 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8762 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8763 MV_DIR_OPPOSITE(direction));
8766 int AmoebaNeighbourNr(int ax, int ay)
8769 int element = Tile[ax][ay];
8771 static int xy[4][2] =
8779 for (i = 0; i < NUM_DIRECTIONS; i++)
8781 int x = ax + xy[i][0];
8782 int y = ay + xy[i][1];
8784 if (!IN_LEV_FIELD(x, y))
8787 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8788 group_nr = AmoebaNr[x][y];
8794 static void AmoebaMerge(int ax, int ay)
8796 int i, x, y, xx, yy;
8797 int new_group_nr = AmoebaNr[ax][ay];
8798 static int xy[4][2] =
8806 if (new_group_nr == 0)
8809 for (i = 0; i < NUM_DIRECTIONS; i++)
8814 if (!IN_LEV_FIELD(x, y))
8817 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8818 Tile[x][y] == EL_BD_AMOEBA ||
8819 Tile[x][y] == EL_AMOEBA_DEAD) &&
8820 AmoebaNr[x][y] != new_group_nr)
8822 int old_group_nr = AmoebaNr[x][y];
8824 if (old_group_nr == 0)
8827 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8828 AmoebaCnt[old_group_nr] = 0;
8829 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8830 AmoebaCnt2[old_group_nr] = 0;
8832 SCAN_PLAYFIELD(xx, yy)
8834 if (AmoebaNr[xx][yy] == old_group_nr)
8835 AmoebaNr[xx][yy] = new_group_nr;
8841 void AmoebaToDiamond(int ax, int ay)
8845 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8847 int group_nr = AmoebaNr[ax][ay];
8852 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8853 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8859 SCAN_PLAYFIELD(x, y)
8861 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8864 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8868 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8869 SND_AMOEBA_TURNING_TO_GEM :
8870 SND_AMOEBA_TURNING_TO_ROCK));
8875 static int xy[4][2] =
8883 for (i = 0; i < NUM_DIRECTIONS; i++)
8888 if (!IN_LEV_FIELD(x, y))
8891 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8893 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8894 SND_AMOEBA_TURNING_TO_GEM :
8895 SND_AMOEBA_TURNING_TO_ROCK));
8902 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8905 int group_nr = AmoebaNr[ax][ay];
8906 boolean done = FALSE;
8911 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8912 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8918 SCAN_PLAYFIELD(x, y)
8920 if (AmoebaNr[x][y] == group_nr &&
8921 (Tile[x][y] == EL_AMOEBA_DEAD ||
8922 Tile[x][y] == EL_BD_AMOEBA ||
8923 Tile[x][y] == EL_AMOEBA_GROWING))
8926 Tile[x][y] = new_element;
8927 InitField(x, y, FALSE);
8928 TEST_DrawLevelField(x, y);
8934 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8935 SND_BD_AMOEBA_TURNING_TO_ROCK :
8936 SND_BD_AMOEBA_TURNING_TO_GEM));
8939 static void AmoebaGrowing(int x, int y)
8941 static unsigned int sound_delay = 0;
8942 static unsigned int sound_delay_value = 0;
8944 if (!MovDelay[x][y]) // start new growing cycle
8948 if (DelayReached(&sound_delay, sound_delay_value))
8950 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8951 sound_delay_value = 30;
8955 if (MovDelay[x][y]) // wait some time before growing bigger
8958 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8960 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8961 6 - MovDelay[x][y]);
8963 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8966 if (!MovDelay[x][y])
8968 Tile[x][y] = Store[x][y];
8970 TEST_DrawLevelField(x, y);
8975 static void AmoebaShrinking(int x, int y)
8977 static unsigned int sound_delay = 0;
8978 static unsigned int sound_delay_value = 0;
8980 if (!MovDelay[x][y]) // start new shrinking cycle
8984 if (DelayReached(&sound_delay, sound_delay_value))
8985 sound_delay_value = 30;
8988 if (MovDelay[x][y]) // wait some time before shrinking
8991 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8993 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8994 6 - MovDelay[x][y]);
8996 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8999 if (!MovDelay[x][y])
9001 Tile[x][y] = EL_EMPTY;
9002 TEST_DrawLevelField(x, y);
9004 // don't let mole enter this field in this cycle;
9005 // (give priority to objects falling to this field from above)
9011 static void AmoebaReproduce(int ax, int ay)
9014 int element = Tile[ax][ay];
9015 int graphic = el2img(element);
9016 int newax = ax, neway = ay;
9017 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9018 static int xy[4][2] =
9026 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9028 Tile[ax][ay] = EL_AMOEBA_DEAD;
9029 TEST_DrawLevelField(ax, ay);
9033 if (IS_ANIMATED(graphic))
9034 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9036 if (!MovDelay[ax][ay]) // start making new amoeba field
9037 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9039 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9042 if (MovDelay[ax][ay])
9046 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9049 int x = ax + xy[start][0];
9050 int y = ay + xy[start][1];
9052 if (!IN_LEV_FIELD(x, y))
9055 if (IS_FREE(x, y) ||
9056 CAN_GROW_INTO(Tile[x][y]) ||
9057 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9058 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9064 if (newax == ax && neway == ay)
9067 else // normal or "filled" (BD style) amoeba
9070 boolean waiting_for_player = FALSE;
9072 for (i = 0; i < NUM_DIRECTIONS; i++)
9074 int j = (start + i) % 4;
9075 int x = ax + xy[j][0];
9076 int y = ay + xy[j][1];
9078 if (!IN_LEV_FIELD(x, y))
9081 if (IS_FREE(x, y) ||
9082 CAN_GROW_INTO(Tile[x][y]) ||
9083 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9084 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9090 else if (IS_PLAYER(x, y))
9091 waiting_for_player = TRUE;
9094 if (newax == ax && neway == ay) // amoeba cannot grow
9096 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9098 Tile[ax][ay] = EL_AMOEBA_DEAD;
9099 TEST_DrawLevelField(ax, ay);
9100 AmoebaCnt[AmoebaNr[ax][ay]]--;
9102 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9104 if (element == EL_AMOEBA_FULL)
9105 AmoebaToDiamond(ax, ay);
9106 else if (element == EL_BD_AMOEBA)
9107 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9112 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9114 // amoeba gets larger by growing in some direction
9116 int new_group_nr = AmoebaNr[ax][ay];
9119 if (new_group_nr == 0)
9121 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9123 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9129 AmoebaNr[newax][neway] = new_group_nr;
9130 AmoebaCnt[new_group_nr]++;
9131 AmoebaCnt2[new_group_nr]++;
9133 // if amoeba touches other amoeba(s) after growing, unify them
9134 AmoebaMerge(newax, neway);
9136 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9138 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9144 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9145 (neway == lev_fieldy - 1 && newax != ax))
9147 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9148 Store[newax][neway] = element;
9150 else if (neway == ay || element == EL_EMC_DRIPPER)
9152 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9154 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9158 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9159 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9160 Store[ax][ay] = EL_AMOEBA_DROP;
9161 ContinueMoving(ax, ay);
9165 TEST_DrawLevelField(newax, neway);
9168 static void Life(int ax, int ay)
9172 int element = Tile[ax][ay];
9173 int graphic = el2img(element);
9174 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9176 boolean changed = FALSE;
9178 if (IS_ANIMATED(graphic))
9179 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9184 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9185 MovDelay[ax][ay] = life_time;
9187 if (MovDelay[ax][ay]) // wait some time before next cycle
9190 if (MovDelay[ax][ay])
9194 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9196 int xx = ax+x1, yy = ay+y1;
9197 int old_element = Tile[xx][yy];
9198 int num_neighbours = 0;
9200 if (!IN_LEV_FIELD(xx, yy))
9203 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9205 int x = xx+x2, y = yy+y2;
9207 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9210 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9211 boolean is_neighbour = FALSE;
9213 if (level.use_life_bugs)
9215 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9216 (IS_FREE(x, y) && Stop[x][y]));
9219 (Last[x][y] == element || is_player_cell);
9225 boolean is_free = FALSE;
9227 if (level.use_life_bugs)
9228 is_free = (IS_FREE(xx, yy));
9230 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9232 if (xx == ax && yy == ay) // field in the middle
9234 if (num_neighbours < life_parameter[0] ||
9235 num_neighbours > life_parameter[1])
9237 Tile[xx][yy] = EL_EMPTY;
9238 if (Tile[xx][yy] != old_element)
9239 TEST_DrawLevelField(xx, yy);
9240 Stop[xx][yy] = TRUE;
9244 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9245 { // free border field
9246 if (num_neighbours >= life_parameter[2] &&
9247 num_neighbours <= life_parameter[3])
9249 Tile[xx][yy] = element;
9250 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9251 if (Tile[xx][yy] != old_element)
9252 TEST_DrawLevelField(xx, yy);
9253 Stop[xx][yy] = TRUE;
9260 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9261 SND_GAME_OF_LIFE_GROWING);
9264 static void InitRobotWheel(int x, int y)
9266 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9269 static void RunRobotWheel(int x, int y)
9271 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9274 static void StopRobotWheel(int x, int y)
9276 if (game.robot_wheel_x == x &&
9277 game.robot_wheel_y == y)
9279 game.robot_wheel_x = -1;
9280 game.robot_wheel_y = -1;
9281 game.robot_wheel_active = FALSE;
9285 static void InitTimegateWheel(int x, int y)
9287 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9290 static void RunTimegateWheel(int x, int y)
9292 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9295 static void InitMagicBallDelay(int x, int y)
9297 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9300 static void ActivateMagicBall(int bx, int by)
9304 if (level.ball_random)
9306 int pos_border = RND(8); // select one of the eight border elements
9307 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9308 int xx = pos_content % 3;
9309 int yy = pos_content / 3;
9314 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9315 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9319 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9321 int xx = x - bx + 1;
9322 int yy = y - by + 1;
9324 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9325 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9329 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9332 static void CheckExit(int x, int y)
9334 if (game.gems_still_needed > 0 ||
9335 game.sokoban_fields_still_needed > 0 ||
9336 game.sokoban_objects_still_needed > 0 ||
9337 game.lights_still_needed > 0)
9339 int element = Tile[x][y];
9340 int graphic = el2img(element);
9342 if (IS_ANIMATED(graphic))
9343 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9348 // do not re-open exit door closed after last player
9349 if (game.all_players_gone)
9352 Tile[x][y] = EL_EXIT_OPENING;
9354 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9357 static void CheckExitEM(int x, int y)
9359 if (game.gems_still_needed > 0 ||
9360 game.sokoban_fields_still_needed > 0 ||
9361 game.sokoban_objects_still_needed > 0 ||
9362 game.lights_still_needed > 0)
9364 int element = Tile[x][y];
9365 int graphic = el2img(element);
9367 if (IS_ANIMATED(graphic))
9368 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9373 // do not re-open exit door closed after last player
9374 if (game.all_players_gone)
9377 Tile[x][y] = EL_EM_EXIT_OPENING;
9379 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9382 static void CheckExitSteel(int x, int y)
9384 if (game.gems_still_needed > 0 ||
9385 game.sokoban_fields_still_needed > 0 ||
9386 game.sokoban_objects_still_needed > 0 ||
9387 game.lights_still_needed > 0)
9389 int element = Tile[x][y];
9390 int graphic = el2img(element);
9392 if (IS_ANIMATED(graphic))
9393 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9398 // do not re-open exit door closed after last player
9399 if (game.all_players_gone)
9402 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9404 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9407 static void CheckExitSteelEM(int x, int y)
9409 if (game.gems_still_needed > 0 ||
9410 game.sokoban_fields_still_needed > 0 ||
9411 game.sokoban_objects_still_needed > 0 ||
9412 game.lights_still_needed > 0)
9414 int element = Tile[x][y];
9415 int graphic = el2img(element);
9417 if (IS_ANIMATED(graphic))
9418 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9423 // do not re-open exit door closed after last player
9424 if (game.all_players_gone)
9427 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9429 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9432 static void CheckExitSP(int x, int y)
9434 if (game.gems_still_needed > 0)
9436 int element = Tile[x][y];
9437 int graphic = el2img(element);
9439 if (IS_ANIMATED(graphic))
9440 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9445 // do not re-open exit door closed after last player
9446 if (game.all_players_gone)
9449 Tile[x][y] = EL_SP_EXIT_OPENING;
9451 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9454 static void CloseAllOpenTimegates(void)
9458 SCAN_PLAYFIELD(x, y)
9460 int element = Tile[x][y];
9462 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9464 Tile[x][y] = EL_TIMEGATE_CLOSING;
9466 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9471 static void DrawTwinkleOnField(int x, int y)
9473 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9476 if (Tile[x][y] == EL_BD_DIAMOND)
9479 if (MovDelay[x][y] == 0) // next animation frame
9480 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9482 if (MovDelay[x][y] != 0) // wait some time before next frame
9486 DrawLevelElementAnimation(x, y, Tile[x][y]);
9488 if (MovDelay[x][y] != 0)
9490 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9491 10 - MovDelay[x][y]);
9493 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9498 static void MauerWaechst(int x, int y)
9502 if (!MovDelay[x][y]) // next animation frame
9503 MovDelay[x][y] = 3 * delay;
9505 if (MovDelay[x][y]) // wait some time before next frame
9509 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9511 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9512 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9514 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9517 if (!MovDelay[x][y])
9519 if (MovDir[x][y] == MV_LEFT)
9521 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9522 TEST_DrawLevelField(x - 1, y);
9524 else if (MovDir[x][y] == MV_RIGHT)
9526 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9527 TEST_DrawLevelField(x + 1, y);
9529 else if (MovDir[x][y] == MV_UP)
9531 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9532 TEST_DrawLevelField(x, y - 1);
9536 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9537 TEST_DrawLevelField(x, y + 1);
9540 Tile[x][y] = Store[x][y];
9542 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9543 TEST_DrawLevelField(x, y);
9548 static void MauerAbleger(int ax, int ay)
9550 int element = Tile[ax][ay];
9551 int graphic = el2img(element);
9552 boolean oben_frei = FALSE, unten_frei = FALSE;
9553 boolean links_frei = FALSE, rechts_frei = FALSE;
9554 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9555 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9556 boolean new_wall = FALSE;
9558 if (IS_ANIMATED(graphic))
9559 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9561 if (!MovDelay[ax][ay]) // start building new wall
9562 MovDelay[ax][ay] = 6;
9564 if (MovDelay[ax][ay]) // wait some time before building new wall
9567 if (MovDelay[ax][ay])
9571 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9573 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9575 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9577 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9580 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9581 element == EL_EXPANDABLE_WALL_ANY)
9585 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9586 Store[ax][ay-1] = element;
9587 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9588 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9589 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9590 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9595 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9596 Store[ax][ay+1] = element;
9597 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9598 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9599 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9600 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9605 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9606 element == EL_EXPANDABLE_WALL_ANY ||
9607 element == EL_EXPANDABLE_WALL ||
9608 element == EL_BD_EXPANDABLE_WALL)
9612 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9613 Store[ax-1][ay] = element;
9614 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9615 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9616 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9617 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9623 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9624 Store[ax+1][ay] = element;
9625 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9626 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9627 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9628 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9633 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9634 TEST_DrawLevelField(ax, ay);
9636 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9638 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9639 unten_massiv = TRUE;
9640 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9641 links_massiv = TRUE;
9642 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9643 rechts_massiv = TRUE;
9645 if (((oben_massiv && unten_massiv) ||
9646 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9647 element == EL_EXPANDABLE_WALL) &&
9648 ((links_massiv && rechts_massiv) ||
9649 element == EL_EXPANDABLE_WALL_VERTICAL))
9650 Tile[ax][ay] = EL_WALL;
9653 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9656 static void MauerAblegerStahl(int ax, int ay)
9658 int element = Tile[ax][ay];
9659 int graphic = el2img(element);
9660 boolean oben_frei = FALSE, unten_frei = FALSE;
9661 boolean links_frei = FALSE, rechts_frei = FALSE;
9662 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9663 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9664 boolean new_wall = FALSE;
9666 if (IS_ANIMATED(graphic))
9667 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9669 if (!MovDelay[ax][ay]) // start building new wall
9670 MovDelay[ax][ay] = 6;
9672 if (MovDelay[ax][ay]) // wait some time before building new wall
9675 if (MovDelay[ax][ay])
9679 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9681 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9683 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9685 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9688 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9689 element == EL_EXPANDABLE_STEELWALL_ANY)
9693 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9694 Store[ax][ay-1] = element;
9695 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9696 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9697 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9698 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9703 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9704 Store[ax][ay+1] = element;
9705 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9706 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9707 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9708 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9713 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9714 element == EL_EXPANDABLE_STEELWALL_ANY)
9718 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9719 Store[ax-1][ay] = element;
9720 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9721 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9722 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9723 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9729 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9730 Store[ax+1][ay] = element;
9731 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9732 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9733 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9734 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9739 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9741 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9742 unten_massiv = TRUE;
9743 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9744 links_massiv = TRUE;
9745 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9746 rechts_massiv = TRUE;
9748 if (((oben_massiv && unten_massiv) ||
9749 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9750 ((links_massiv && rechts_massiv) ||
9751 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9752 Tile[ax][ay] = EL_STEELWALL;
9755 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9758 static void CheckForDragon(int x, int y)
9761 boolean dragon_found = FALSE;
9762 static int xy[4][2] =
9770 for (i = 0; i < NUM_DIRECTIONS; i++)
9772 for (j = 0; j < 4; j++)
9774 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9776 if (IN_LEV_FIELD(xx, yy) &&
9777 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9779 if (Tile[xx][yy] == EL_DRAGON)
9780 dragon_found = TRUE;
9789 for (i = 0; i < NUM_DIRECTIONS; i++)
9791 for (j = 0; j < 3; j++)
9793 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9795 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9797 Tile[xx][yy] = EL_EMPTY;
9798 TEST_DrawLevelField(xx, yy);
9807 static void InitBuggyBase(int x, int y)
9809 int element = Tile[x][y];
9810 int activating_delay = FRAMES_PER_SECOND / 4;
9813 (element == EL_SP_BUGGY_BASE ?
9814 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9815 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9817 element == EL_SP_BUGGY_BASE_ACTIVE ?
9818 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9821 static void WarnBuggyBase(int x, int y)
9824 static int xy[4][2] =
9832 for (i = 0; i < NUM_DIRECTIONS; i++)
9834 int xx = x + xy[i][0];
9835 int yy = y + xy[i][1];
9837 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9839 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9846 static void InitTrap(int x, int y)
9848 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9851 static void ActivateTrap(int x, int y)
9853 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9856 static void ChangeActiveTrap(int x, int y)
9858 int graphic = IMG_TRAP_ACTIVE;
9860 // if new animation frame was drawn, correct crumbled sand border
9861 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9862 TEST_DrawLevelFieldCrumbled(x, y);
9865 static int getSpecialActionElement(int element, int number, int base_element)
9867 return (element != EL_EMPTY ? element :
9868 number != -1 ? base_element + number - 1 :
9872 static int getModifiedActionNumber(int value_old, int operator, int operand,
9873 int value_min, int value_max)
9875 int value_new = (operator == CA_MODE_SET ? operand :
9876 operator == CA_MODE_ADD ? value_old + operand :
9877 operator == CA_MODE_SUBTRACT ? value_old - operand :
9878 operator == CA_MODE_MULTIPLY ? value_old * operand :
9879 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9880 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9883 return (value_new < value_min ? value_min :
9884 value_new > value_max ? value_max :
9888 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9890 struct ElementInfo *ei = &element_info[element];
9891 struct ElementChangeInfo *change = &ei->change_page[page];
9892 int target_element = change->target_element;
9893 int action_type = change->action_type;
9894 int action_mode = change->action_mode;
9895 int action_arg = change->action_arg;
9896 int action_element = change->action_element;
9899 if (!change->has_action)
9902 // ---------- determine action paramater values -----------------------------
9904 int level_time_value =
9905 (level.time > 0 ? TimeLeft :
9908 int action_arg_element_raw =
9909 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9910 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9911 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9912 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9913 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9914 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9915 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9917 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9919 int action_arg_direction =
9920 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9921 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9922 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9923 change->actual_trigger_side :
9924 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9925 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9928 int action_arg_number_min =
9929 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9932 int action_arg_number_max =
9933 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9934 action_type == CA_SET_LEVEL_GEMS ? 999 :
9935 action_type == CA_SET_LEVEL_TIME ? 9999 :
9936 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9937 action_type == CA_SET_CE_VALUE ? 9999 :
9938 action_type == CA_SET_CE_SCORE ? 9999 :
9941 int action_arg_number_reset =
9942 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9943 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9944 action_type == CA_SET_LEVEL_TIME ? level.time :
9945 action_type == CA_SET_LEVEL_SCORE ? 0 :
9946 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9947 action_type == CA_SET_CE_SCORE ? 0 :
9950 int action_arg_number =
9951 (action_arg <= CA_ARG_MAX ? action_arg :
9952 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9953 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9954 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9955 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9956 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9957 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9958 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9959 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9960 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9961 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9962 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9963 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9964 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9965 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9966 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9967 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9968 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9969 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9970 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9971 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9972 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9975 int action_arg_number_old =
9976 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9977 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9978 action_type == CA_SET_LEVEL_SCORE ? game.score :
9979 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9980 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9983 int action_arg_number_new =
9984 getModifiedActionNumber(action_arg_number_old,
9985 action_mode, action_arg_number,
9986 action_arg_number_min, action_arg_number_max);
9988 int trigger_player_bits =
9989 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9990 change->actual_trigger_player_bits : change->trigger_player);
9992 int action_arg_player_bits =
9993 (action_arg >= CA_ARG_PLAYER_1 &&
9994 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9995 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9996 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9999 // ---------- execute action -----------------------------------------------
10001 switch (action_type)
10008 // ---------- level actions ----------------------------------------------
10010 case CA_RESTART_LEVEL:
10012 game.restart_level = TRUE;
10017 case CA_SHOW_ENVELOPE:
10019 int element = getSpecialActionElement(action_arg_element,
10020 action_arg_number, EL_ENVELOPE_1);
10022 if (IS_ENVELOPE(element))
10023 local_player->show_envelope = element;
10028 case CA_SET_LEVEL_TIME:
10030 if (level.time > 0) // only modify limited time value
10032 TimeLeft = action_arg_number_new;
10034 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10036 DisplayGameControlValues();
10038 if (!TimeLeft && setup.time_limit)
10039 for (i = 0; i < MAX_PLAYERS; i++)
10040 KillPlayer(&stored_player[i]);
10046 case CA_SET_LEVEL_SCORE:
10048 game.score = action_arg_number_new;
10050 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10052 DisplayGameControlValues();
10057 case CA_SET_LEVEL_GEMS:
10059 game.gems_still_needed = action_arg_number_new;
10061 game.snapshot.collected_item = TRUE;
10063 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10065 DisplayGameControlValues();
10070 case CA_SET_LEVEL_WIND:
10072 game.wind_direction = action_arg_direction;
10077 case CA_SET_LEVEL_RANDOM_SEED:
10079 // ensure that setting a new random seed while playing is predictable
10080 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10085 // ---------- player actions ---------------------------------------------
10087 case CA_MOVE_PLAYER:
10088 case CA_MOVE_PLAYER_NEW:
10090 // automatically move to the next field in specified direction
10091 for (i = 0; i < MAX_PLAYERS; i++)
10092 if (trigger_player_bits & (1 << i))
10093 if (action_type == CA_MOVE_PLAYER ||
10094 stored_player[i].MovPos == 0)
10095 stored_player[i].programmed_action = action_arg_direction;
10100 case CA_EXIT_PLAYER:
10102 for (i = 0; i < MAX_PLAYERS; i++)
10103 if (action_arg_player_bits & (1 << i))
10104 ExitPlayer(&stored_player[i]);
10106 if (game.players_still_needed == 0)
10112 case CA_KILL_PLAYER:
10114 for (i = 0; i < MAX_PLAYERS; i++)
10115 if (action_arg_player_bits & (1 << i))
10116 KillPlayer(&stored_player[i]);
10121 case CA_SET_PLAYER_KEYS:
10123 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10124 int element = getSpecialActionElement(action_arg_element,
10125 action_arg_number, EL_KEY_1);
10127 if (IS_KEY(element))
10129 for (i = 0; i < MAX_PLAYERS; i++)
10131 if (trigger_player_bits & (1 << i))
10133 stored_player[i].key[KEY_NR(element)] = key_state;
10135 DrawGameDoorValues();
10143 case CA_SET_PLAYER_SPEED:
10145 for (i = 0; i < MAX_PLAYERS; i++)
10147 if (trigger_player_bits & (1 << i))
10149 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10151 if (action_arg == CA_ARG_SPEED_FASTER &&
10152 stored_player[i].cannot_move)
10154 action_arg_number = STEPSIZE_VERY_SLOW;
10156 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10157 action_arg == CA_ARG_SPEED_FASTER)
10159 action_arg_number = 2;
10160 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10163 else if (action_arg == CA_ARG_NUMBER_RESET)
10165 action_arg_number = level.initial_player_stepsize[i];
10169 getModifiedActionNumber(move_stepsize,
10172 action_arg_number_min,
10173 action_arg_number_max);
10175 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10182 case CA_SET_PLAYER_SHIELD:
10184 for (i = 0; i < MAX_PLAYERS; i++)
10186 if (trigger_player_bits & (1 << i))
10188 if (action_arg == CA_ARG_SHIELD_OFF)
10190 stored_player[i].shield_normal_time_left = 0;
10191 stored_player[i].shield_deadly_time_left = 0;
10193 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10195 stored_player[i].shield_normal_time_left = 999999;
10197 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10199 stored_player[i].shield_normal_time_left = 999999;
10200 stored_player[i].shield_deadly_time_left = 999999;
10208 case CA_SET_PLAYER_GRAVITY:
10210 for (i = 0; i < MAX_PLAYERS; i++)
10212 if (trigger_player_bits & (1 << i))
10214 stored_player[i].gravity =
10215 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10216 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10217 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10218 stored_player[i].gravity);
10225 case CA_SET_PLAYER_ARTWORK:
10227 for (i = 0; i < MAX_PLAYERS; i++)
10229 if (trigger_player_bits & (1 << i))
10231 int artwork_element = action_arg_element;
10233 if (action_arg == CA_ARG_ELEMENT_RESET)
10235 (level.use_artwork_element[i] ? level.artwork_element[i] :
10236 stored_player[i].element_nr);
10238 if (stored_player[i].artwork_element != artwork_element)
10239 stored_player[i].Frame = 0;
10241 stored_player[i].artwork_element = artwork_element;
10243 SetPlayerWaiting(&stored_player[i], FALSE);
10245 // set number of special actions for bored and sleeping animation
10246 stored_player[i].num_special_action_bored =
10247 get_num_special_action(artwork_element,
10248 ACTION_BORING_1, ACTION_BORING_LAST);
10249 stored_player[i].num_special_action_sleeping =
10250 get_num_special_action(artwork_element,
10251 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10258 case CA_SET_PLAYER_INVENTORY:
10260 for (i = 0; i < MAX_PLAYERS; i++)
10262 struct PlayerInfo *player = &stored_player[i];
10265 if (trigger_player_bits & (1 << i))
10267 int inventory_element = action_arg_element;
10269 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10270 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10271 action_arg == CA_ARG_ELEMENT_ACTION)
10273 int element = inventory_element;
10274 int collect_count = element_info[element].collect_count_initial;
10276 if (!IS_CUSTOM_ELEMENT(element))
10279 if (collect_count == 0)
10280 player->inventory_infinite_element = element;
10282 for (k = 0; k < collect_count; k++)
10283 if (player->inventory_size < MAX_INVENTORY_SIZE)
10284 player->inventory_element[player->inventory_size++] =
10287 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10288 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10289 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10291 if (player->inventory_infinite_element != EL_UNDEFINED &&
10292 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10293 action_arg_element_raw))
10294 player->inventory_infinite_element = EL_UNDEFINED;
10296 for (k = 0, j = 0; j < player->inventory_size; j++)
10298 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10299 action_arg_element_raw))
10300 player->inventory_element[k++] = player->inventory_element[j];
10303 player->inventory_size = k;
10305 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10307 if (player->inventory_size > 0)
10309 for (j = 0; j < player->inventory_size - 1; j++)
10310 player->inventory_element[j] = player->inventory_element[j + 1];
10312 player->inventory_size--;
10315 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10317 if (player->inventory_size > 0)
10318 player->inventory_size--;
10320 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10322 player->inventory_infinite_element = EL_UNDEFINED;
10323 player->inventory_size = 0;
10325 else if (action_arg == CA_ARG_INVENTORY_RESET)
10327 player->inventory_infinite_element = EL_UNDEFINED;
10328 player->inventory_size = 0;
10330 if (level.use_initial_inventory[i])
10332 for (j = 0; j < level.initial_inventory_size[i]; j++)
10334 int element = level.initial_inventory_content[i][j];
10335 int collect_count = element_info[element].collect_count_initial;
10337 if (!IS_CUSTOM_ELEMENT(element))
10340 if (collect_count == 0)
10341 player->inventory_infinite_element = element;
10343 for (k = 0; k < collect_count; k++)
10344 if (player->inventory_size < MAX_INVENTORY_SIZE)
10345 player->inventory_element[player->inventory_size++] =
10356 // ---------- CE actions -------------------------------------------------
10358 case CA_SET_CE_VALUE:
10360 int last_ce_value = CustomValue[x][y];
10362 CustomValue[x][y] = action_arg_number_new;
10364 if (CustomValue[x][y] != last_ce_value)
10366 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10367 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10369 if (CustomValue[x][y] == 0)
10371 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10372 ChangeCount[x][y] = 0; // allow at least one more change
10374 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10375 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10382 case CA_SET_CE_SCORE:
10384 int last_ce_score = ei->collect_score;
10386 ei->collect_score = action_arg_number_new;
10388 if (ei->collect_score != last_ce_score)
10390 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10391 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10393 if (ei->collect_score == 0)
10397 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10398 ChangeCount[x][y] = 0; // allow at least one more change
10400 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10401 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10404 This is a very special case that seems to be a mixture between
10405 CheckElementChange() and CheckTriggeredElementChange(): while
10406 the first one only affects single elements that are triggered
10407 directly, the second one affects multiple elements in the playfield
10408 that are triggered indirectly by another element. This is a third
10409 case: Changing the CE score always affects multiple identical CEs,
10410 so every affected CE must be checked, not only the single CE for
10411 which the CE score was changed in the first place (as every instance
10412 of that CE shares the same CE score, and therefore also can change)!
10414 SCAN_PLAYFIELD(xx, yy)
10416 if (Tile[xx][yy] == element)
10417 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10418 CE_SCORE_GETS_ZERO);
10426 case CA_SET_CE_ARTWORK:
10428 int artwork_element = action_arg_element;
10429 boolean reset_frame = FALSE;
10432 if (action_arg == CA_ARG_ELEMENT_RESET)
10433 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10436 if (ei->gfx_element != artwork_element)
10437 reset_frame = TRUE;
10439 ei->gfx_element = artwork_element;
10441 SCAN_PLAYFIELD(xx, yy)
10443 if (Tile[xx][yy] == element)
10447 ResetGfxAnimation(xx, yy);
10448 ResetRandomAnimationValue(xx, yy);
10451 TEST_DrawLevelField(xx, yy);
10458 // ---------- engine actions ---------------------------------------------
10460 case CA_SET_ENGINE_SCAN_MODE:
10462 InitPlayfieldScanMode(action_arg);
10472 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10474 int old_element = Tile[x][y];
10475 int new_element = GetElementFromGroupElement(element);
10476 int previous_move_direction = MovDir[x][y];
10477 int last_ce_value = CustomValue[x][y];
10478 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10479 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10480 boolean add_player_onto_element = (new_element_is_player &&
10481 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10482 IS_WALKABLE(old_element));
10484 if (!add_player_onto_element)
10486 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10487 RemoveMovingField(x, y);
10491 Tile[x][y] = new_element;
10493 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10494 MovDir[x][y] = previous_move_direction;
10496 if (element_info[new_element].use_last_ce_value)
10497 CustomValue[x][y] = last_ce_value;
10499 InitField_WithBug1(x, y, FALSE);
10501 new_element = Tile[x][y]; // element may have changed
10503 ResetGfxAnimation(x, y);
10504 ResetRandomAnimationValue(x, y);
10506 TEST_DrawLevelField(x, y);
10508 if (GFX_CRUMBLED(new_element))
10509 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10512 // check if element under the player changes from accessible to unaccessible
10513 // (needed for special case of dropping element which then changes)
10514 // (must be checked after creating new element for walkable group elements)
10515 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10516 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10523 // "ChangeCount" not set yet to allow "entered by player" change one time
10524 if (new_element_is_player)
10525 RelocatePlayer(x, y, new_element);
10528 ChangeCount[x][y]++; // count number of changes in the same frame
10530 TestIfBadThingTouchesPlayer(x, y);
10531 TestIfPlayerTouchesCustomElement(x, y);
10532 TestIfElementTouchesCustomElement(x, y);
10535 static void CreateField(int x, int y, int element)
10537 CreateFieldExt(x, y, element, FALSE);
10540 static void CreateElementFromChange(int x, int y, int element)
10542 element = GET_VALID_RUNTIME_ELEMENT(element);
10544 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10546 int old_element = Tile[x][y];
10548 // prevent changed element from moving in same engine frame
10549 // unless both old and new element can either fall or move
10550 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10551 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10555 CreateFieldExt(x, y, element, TRUE);
10558 static boolean ChangeElement(int x, int y, int element, int page)
10560 struct ElementInfo *ei = &element_info[element];
10561 struct ElementChangeInfo *change = &ei->change_page[page];
10562 int ce_value = CustomValue[x][y];
10563 int ce_score = ei->collect_score;
10564 int target_element;
10565 int old_element = Tile[x][y];
10567 // always use default change event to prevent running into a loop
10568 if (ChangeEvent[x][y] == -1)
10569 ChangeEvent[x][y] = CE_DELAY;
10571 if (ChangeEvent[x][y] == CE_DELAY)
10573 // reset actual trigger element, trigger player and action element
10574 change->actual_trigger_element = EL_EMPTY;
10575 change->actual_trigger_player = EL_EMPTY;
10576 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10577 change->actual_trigger_side = CH_SIDE_NONE;
10578 change->actual_trigger_ce_value = 0;
10579 change->actual_trigger_ce_score = 0;
10582 // do not change elements more than a specified maximum number of changes
10583 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10586 ChangeCount[x][y]++; // count number of changes in the same frame
10588 if (change->explode)
10595 if (change->use_target_content)
10597 boolean complete_replace = TRUE;
10598 boolean can_replace[3][3];
10601 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10604 boolean is_walkable;
10605 boolean is_diggable;
10606 boolean is_collectible;
10607 boolean is_removable;
10608 boolean is_destructible;
10609 int ex = x + xx - 1;
10610 int ey = y + yy - 1;
10611 int content_element = change->target_content.e[xx][yy];
10614 can_replace[xx][yy] = TRUE;
10616 if (ex == x && ey == y) // do not check changing element itself
10619 if (content_element == EL_EMPTY_SPACE)
10621 can_replace[xx][yy] = FALSE; // do not replace border with space
10626 if (!IN_LEV_FIELD(ex, ey))
10628 can_replace[xx][yy] = FALSE;
10629 complete_replace = FALSE;
10636 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10637 e = MovingOrBlocked2Element(ex, ey);
10639 is_empty = (IS_FREE(ex, ey) ||
10640 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10642 is_walkable = (is_empty || IS_WALKABLE(e));
10643 is_diggable = (is_empty || IS_DIGGABLE(e));
10644 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10645 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10646 is_removable = (is_diggable || is_collectible);
10648 can_replace[xx][yy] =
10649 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10650 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10651 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10652 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10653 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10654 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10655 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10657 if (!can_replace[xx][yy])
10658 complete_replace = FALSE;
10661 if (!change->only_if_complete || complete_replace)
10663 boolean something_has_changed = FALSE;
10665 if (change->only_if_complete && change->use_random_replace &&
10666 RND(100) < change->random_percentage)
10669 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10671 int ex = x + xx - 1;
10672 int ey = y + yy - 1;
10673 int content_element;
10675 if (can_replace[xx][yy] && (!change->use_random_replace ||
10676 RND(100) < change->random_percentage))
10678 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10679 RemoveMovingField(ex, ey);
10681 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10683 content_element = change->target_content.e[xx][yy];
10684 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10685 ce_value, ce_score);
10687 CreateElementFromChange(ex, ey, target_element);
10689 something_has_changed = TRUE;
10691 // for symmetry reasons, freeze newly created border elements
10692 if (ex != x || ey != y)
10693 Stop[ex][ey] = TRUE; // no more moving in this frame
10697 if (something_has_changed)
10699 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10700 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10706 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10707 ce_value, ce_score);
10709 if (element == EL_DIAGONAL_GROWING ||
10710 element == EL_DIAGONAL_SHRINKING)
10712 target_element = Store[x][y];
10714 Store[x][y] = EL_EMPTY;
10717 CreateElementFromChange(x, y, target_element);
10719 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10720 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10723 // this uses direct change before indirect change
10724 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10729 static void HandleElementChange(int x, int y, int page)
10731 int element = MovingOrBlocked2Element(x, y);
10732 struct ElementInfo *ei = &element_info[element];
10733 struct ElementChangeInfo *change = &ei->change_page[page];
10734 boolean handle_action_before_change = FALSE;
10737 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10738 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10740 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10741 x, y, element, element_info[element].token_name);
10742 Debug("game:playing:HandleElementChange", "This should never happen!");
10746 // this can happen with classic bombs on walkable, changing elements
10747 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10752 if (ChangeDelay[x][y] == 0) // initialize element change
10754 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10756 if (change->can_change)
10758 // !!! not clear why graphic animation should be reset at all here !!!
10759 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10760 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10763 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10765 When using an animation frame delay of 1 (this only happens with
10766 "sp_zonk.moving.left/right" in the classic graphics), the default
10767 (non-moving) animation shows wrong animation frames (while the
10768 moving animation, like "sp_zonk.moving.left/right", is correct,
10769 so this graphical bug never shows up with the classic graphics).
10770 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10771 be drawn instead of the correct frames 0,1,2,3. This is caused by
10772 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10773 an element change: First when the change delay ("ChangeDelay[][]")
10774 counter has reached zero after decrementing, then a second time in
10775 the next frame (after "GfxFrame[][]" was already incremented) when
10776 "ChangeDelay[][]" is reset to the initial delay value again.
10778 This causes frame 0 to be drawn twice, while the last frame won't
10779 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10781 As some animations may already be cleverly designed around this bug
10782 (at least the "Snake Bite" snake tail animation does this), it cannot
10783 simply be fixed here without breaking such existing animations.
10784 Unfortunately, it cannot easily be detected if a graphics set was
10785 designed "before" or "after" the bug was fixed. As a workaround,
10786 a new graphics set option "game.graphics_engine_version" was added
10787 to be able to specify the game's major release version for which the
10788 graphics set was designed, which can then be used to decide if the
10789 bugfix should be used (version 4 and above) or not (version 3 or
10790 below, or if no version was specified at all, as with old sets).
10792 (The wrong/fixed animation frames can be tested with the test level set
10793 "test_gfxframe" and level "000", which contains a specially prepared
10794 custom element at level position (x/y) == (11/9) which uses the zonk
10795 animation mentioned above. Using "game.graphics_engine_version: 4"
10796 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10797 This can also be seen from the debug output for this test element.)
10800 // when a custom element is about to change (for example by change delay),
10801 // do not reset graphic animation when the custom element is moving
10802 if (game.graphics_engine_version < 4 &&
10805 ResetGfxAnimation(x, y);
10806 ResetRandomAnimationValue(x, y);
10809 if (change->pre_change_function)
10810 change->pre_change_function(x, y);
10814 ChangeDelay[x][y]--;
10816 if (ChangeDelay[x][y] != 0) // continue element change
10818 if (change->can_change)
10820 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10822 if (IS_ANIMATED(graphic))
10823 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10825 if (change->change_function)
10826 change->change_function(x, y);
10829 else // finish element change
10831 if (ChangePage[x][y] != -1) // remember page from delayed change
10833 page = ChangePage[x][y];
10834 ChangePage[x][y] = -1;
10836 change = &ei->change_page[page];
10839 if (IS_MOVING(x, y)) // never change a running system ;-)
10841 ChangeDelay[x][y] = 1; // try change after next move step
10842 ChangePage[x][y] = page; // remember page to use for change
10847 // special case: set new level random seed before changing element
10848 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10849 handle_action_before_change = TRUE;
10851 if (change->has_action && handle_action_before_change)
10852 ExecuteCustomElementAction(x, y, element, page);
10854 if (change->can_change)
10856 if (ChangeElement(x, y, element, page))
10858 if (change->post_change_function)
10859 change->post_change_function(x, y);
10863 if (change->has_action && !handle_action_before_change)
10864 ExecuteCustomElementAction(x, y, element, page);
10868 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10869 int trigger_element,
10871 int trigger_player,
10875 boolean change_done_any = FALSE;
10876 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10879 if (!(trigger_events[trigger_element][trigger_event]))
10882 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10884 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10886 int element = EL_CUSTOM_START + i;
10887 boolean change_done = FALSE;
10890 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10891 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10894 for (p = 0; p < element_info[element].num_change_pages; p++)
10896 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10898 if (change->can_change_or_has_action &&
10899 change->has_event[trigger_event] &&
10900 change->trigger_side & trigger_side &&
10901 change->trigger_player & trigger_player &&
10902 change->trigger_page & trigger_page_bits &&
10903 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10905 change->actual_trigger_element = trigger_element;
10906 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10907 change->actual_trigger_player_bits = trigger_player;
10908 change->actual_trigger_side = trigger_side;
10909 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10910 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10912 if ((change->can_change && !change_done) || change->has_action)
10916 SCAN_PLAYFIELD(x, y)
10918 if (Tile[x][y] == element)
10920 if (change->can_change && !change_done)
10922 // if element already changed in this frame, not only prevent
10923 // another element change (checked in ChangeElement()), but
10924 // also prevent additional element actions for this element
10926 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10927 !level.use_action_after_change_bug)
10930 ChangeDelay[x][y] = 1;
10931 ChangeEvent[x][y] = trigger_event;
10933 HandleElementChange(x, y, p);
10935 else if (change->has_action)
10937 // if element already changed in this frame, not only prevent
10938 // another element change (checked in ChangeElement()), but
10939 // also prevent additional element actions for this element
10941 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10942 !level.use_action_after_change_bug)
10945 ExecuteCustomElementAction(x, y, element, p);
10946 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10951 if (change->can_change)
10953 change_done = TRUE;
10954 change_done_any = TRUE;
10961 RECURSION_LOOP_DETECTION_END();
10963 return change_done_any;
10966 static boolean CheckElementChangeExt(int x, int y,
10968 int trigger_element,
10970 int trigger_player,
10973 boolean change_done = FALSE;
10976 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10977 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10980 if (Tile[x][y] == EL_BLOCKED)
10982 Blocked2Moving(x, y, &x, &y);
10983 element = Tile[x][y];
10986 // check if element has already changed or is about to change after moving
10987 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10988 Tile[x][y] != element) ||
10990 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10991 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10992 ChangePage[x][y] != -1)))
10995 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10997 for (p = 0; p < element_info[element].num_change_pages; p++)
10999 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11001 /* check trigger element for all events where the element that is checked
11002 for changing interacts with a directly adjacent element -- this is
11003 different to element changes that affect other elements to change on the
11004 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11005 boolean check_trigger_element =
11006 (trigger_event == CE_TOUCHING_X ||
11007 trigger_event == CE_HITTING_X ||
11008 trigger_event == CE_HIT_BY_X ||
11009 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11011 if (change->can_change_or_has_action &&
11012 change->has_event[trigger_event] &&
11013 change->trigger_side & trigger_side &&
11014 change->trigger_player & trigger_player &&
11015 (!check_trigger_element ||
11016 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11018 change->actual_trigger_element = trigger_element;
11019 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11020 change->actual_trigger_player_bits = trigger_player;
11021 change->actual_trigger_side = trigger_side;
11022 change->actual_trigger_ce_value = CustomValue[x][y];
11023 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11025 // special case: trigger element not at (x,y) position for some events
11026 if (check_trigger_element)
11038 { 0, 0 }, { 0, 0 }, { 0, 0 },
11042 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11043 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11045 change->actual_trigger_ce_value = CustomValue[xx][yy];
11046 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11049 if (change->can_change && !change_done)
11051 ChangeDelay[x][y] = 1;
11052 ChangeEvent[x][y] = trigger_event;
11054 HandleElementChange(x, y, p);
11056 change_done = TRUE;
11058 else if (change->has_action)
11060 ExecuteCustomElementAction(x, y, element, p);
11061 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11066 RECURSION_LOOP_DETECTION_END();
11068 return change_done;
11071 static void PlayPlayerSound(struct PlayerInfo *player)
11073 int jx = player->jx, jy = player->jy;
11074 int sound_element = player->artwork_element;
11075 int last_action = player->last_action_waiting;
11076 int action = player->action_waiting;
11078 if (player->is_waiting)
11080 if (action != last_action)
11081 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11083 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11087 if (action != last_action)
11088 StopSound(element_info[sound_element].sound[last_action]);
11090 if (last_action == ACTION_SLEEPING)
11091 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11095 static void PlayAllPlayersSound(void)
11099 for (i = 0; i < MAX_PLAYERS; i++)
11100 if (stored_player[i].active)
11101 PlayPlayerSound(&stored_player[i]);
11104 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11106 boolean last_waiting = player->is_waiting;
11107 int move_dir = player->MovDir;
11109 player->dir_waiting = move_dir;
11110 player->last_action_waiting = player->action_waiting;
11114 if (!last_waiting) // not waiting -> waiting
11116 player->is_waiting = TRUE;
11118 player->frame_counter_bored =
11120 game.player_boring_delay_fixed +
11121 GetSimpleRandom(game.player_boring_delay_random);
11122 player->frame_counter_sleeping =
11124 game.player_sleeping_delay_fixed +
11125 GetSimpleRandom(game.player_sleeping_delay_random);
11127 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11130 if (game.player_sleeping_delay_fixed +
11131 game.player_sleeping_delay_random > 0 &&
11132 player->anim_delay_counter == 0 &&
11133 player->post_delay_counter == 0 &&
11134 FrameCounter >= player->frame_counter_sleeping)
11135 player->is_sleeping = TRUE;
11136 else if (game.player_boring_delay_fixed +
11137 game.player_boring_delay_random > 0 &&
11138 FrameCounter >= player->frame_counter_bored)
11139 player->is_bored = TRUE;
11141 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11142 player->is_bored ? ACTION_BORING :
11145 if (player->is_sleeping && player->use_murphy)
11147 // special case for sleeping Murphy when leaning against non-free tile
11149 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11150 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11151 !IS_MOVING(player->jx - 1, player->jy)))
11152 move_dir = MV_LEFT;
11153 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11154 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11155 !IS_MOVING(player->jx + 1, player->jy)))
11156 move_dir = MV_RIGHT;
11158 player->is_sleeping = FALSE;
11160 player->dir_waiting = move_dir;
11163 if (player->is_sleeping)
11165 if (player->num_special_action_sleeping > 0)
11167 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11169 int last_special_action = player->special_action_sleeping;
11170 int num_special_action = player->num_special_action_sleeping;
11171 int special_action =
11172 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11173 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11174 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11175 last_special_action + 1 : ACTION_SLEEPING);
11176 int special_graphic =
11177 el_act_dir2img(player->artwork_element, special_action, move_dir);
11179 player->anim_delay_counter =
11180 graphic_info[special_graphic].anim_delay_fixed +
11181 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11182 player->post_delay_counter =
11183 graphic_info[special_graphic].post_delay_fixed +
11184 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11186 player->special_action_sleeping = special_action;
11189 if (player->anim_delay_counter > 0)
11191 player->action_waiting = player->special_action_sleeping;
11192 player->anim_delay_counter--;
11194 else if (player->post_delay_counter > 0)
11196 player->post_delay_counter--;
11200 else if (player->is_bored)
11202 if (player->num_special_action_bored > 0)
11204 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11206 int special_action =
11207 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11208 int special_graphic =
11209 el_act_dir2img(player->artwork_element, special_action, move_dir);
11211 player->anim_delay_counter =
11212 graphic_info[special_graphic].anim_delay_fixed +
11213 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11214 player->post_delay_counter =
11215 graphic_info[special_graphic].post_delay_fixed +
11216 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11218 player->special_action_bored = special_action;
11221 if (player->anim_delay_counter > 0)
11223 player->action_waiting = player->special_action_bored;
11224 player->anim_delay_counter--;
11226 else if (player->post_delay_counter > 0)
11228 player->post_delay_counter--;
11233 else if (last_waiting) // waiting -> not waiting
11235 player->is_waiting = FALSE;
11236 player->is_bored = FALSE;
11237 player->is_sleeping = FALSE;
11239 player->frame_counter_bored = -1;
11240 player->frame_counter_sleeping = -1;
11242 player->anim_delay_counter = 0;
11243 player->post_delay_counter = 0;
11245 player->dir_waiting = player->MovDir;
11246 player->action_waiting = ACTION_DEFAULT;
11248 player->special_action_bored = ACTION_DEFAULT;
11249 player->special_action_sleeping = ACTION_DEFAULT;
11253 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11255 if ((!player->is_moving && player->was_moving) ||
11256 (player->MovPos == 0 && player->was_moving) ||
11257 (player->is_snapping && !player->was_snapping) ||
11258 (player->is_dropping && !player->was_dropping))
11260 if (!CheckSaveEngineSnapshotToList())
11263 player->was_moving = FALSE;
11264 player->was_snapping = TRUE;
11265 player->was_dropping = TRUE;
11269 if (player->is_moving)
11270 player->was_moving = TRUE;
11272 if (!player->is_snapping)
11273 player->was_snapping = FALSE;
11275 if (!player->is_dropping)
11276 player->was_dropping = FALSE;
11279 static struct MouseActionInfo mouse_action_last = { 0 };
11280 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11281 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11284 CheckSaveEngineSnapshotToList();
11286 mouse_action_last = mouse_action;
11289 static void CheckSingleStepMode(struct PlayerInfo *player)
11291 if (tape.single_step && tape.recording && !tape.pausing)
11293 // as it is called "single step mode", just return to pause mode when the
11294 // player stopped moving after one tile (or never starts moving at all)
11295 // (reverse logic needed here in case single step mode used in team mode)
11296 if (player->is_moving ||
11297 player->is_pushing ||
11298 player->is_dropping_pressed ||
11299 player->effective_mouse_action.button)
11300 game.enter_single_step_mode = FALSE;
11303 CheckSaveEngineSnapshot(player);
11306 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11308 int left = player_action & JOY_LEFT;
11309 int right = player_action & JOY_RIGHT;
11310 int up = player_action & JOY_UP;
11311 int down = player_action & JOY_DOWN;
11312 int button1 = player_action & JOY_BUTTON_1;
11313 int button2 = player_action & JOY_BUTTON_2;
11314 int dx = (left ? -1 : right ? 1 : 0);
11315 int dy = (up ? -1 : down ? 1 : 0);
11317 if (!player->active || tape.pausing)
11323 SnapField(player, dx, dy);
11327 DropElement(player);
11329 MovePlayer(player, dx, dy);
11332 CheckSingleStepMode(player);
11334 SetPlayerWaiting(player, FALSE);
11336 return player_action;
11340 // no actions for this player (no input at player's configured device)
11342 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11343 SnapField(player, 0, 0);
11344 CheckGravityMovementWhenNotMoving(player);
11346 if (player->MovPos == 0)
11347 SetPlayerWaiting(player, TRUE);
11349 if (player->MovPos == 0) // needed for tape.playing
11350 player->is_moving = FALSE;
11352 player->is_dropping = FALSE;
11353 player->is_dropping_pressed = FALSE;
11354 player->drop_pressed_delay = 0;
11356 CheckSingleStepMode(player);
11362 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11365 if (!tape.use_mouse_actions)
11368 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11369 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11370 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11373 static void SetTapeActionFromMouseAction(byte *tape_action,
11374 struct MouseActionInfo *mouse_action)
11376 if (!tape.use_mouse_actions)
11379 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11380 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11381 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11384 static void CheckLevelSolved(void)
11386 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11388 if (game_em.level_solved &&
11389 !game_em.game_over) // game won
11393 game_em.game_over = TRUE;
11395 game.all_players_gone = TRUE;
11398 if (game_em.game_over) // game lost
11399 game.all_players_gone = TRUE;
11401 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11403 if (game_sp.level_solved &&
11404 !game_sp.game_over) // game won
11408 game_sp.game_over = TRUE;
11410 game.all_players_gone = TRUE;
11413 if (game_sp.game_over) // game lost
11414 game.all_players_gone = TRUE;
11416 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11418 if (game_mm.level_solved &&
11419 !game_mm.game_over) // game won
11423 game_mm.game_over = TRUE;
11425 game.all_players_gone = TRUE;
11428 if (game_mm.game_over) // game lost
11429 game.all_players_gone = TRUE;
11433 static void CheckLevelTime(void)
11437 if (TimeFrames >= FRAMES_PER_SECOND)
11442 for (i = 0; i < MAX_PLAYERS; i++)
11444 struct PlayerInfo *player = &stored_player[i];
11446 if (SHIELD_ON(player))
11448 player->shield_normal_time_left--;
11450 if (player->shield_deadly_time_left > 0)
11451 player->shield_deadly_time_left--;
11455 if (!game.LevelSolved && !level.use_step_counter)
11463 if (TimeLeft <= 10 && setup.time_limit)
11464 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11466 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11467 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11469 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11471 if (!TimeLeft && setup.time_limit)
11473 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11474 game_em.lev->killed_out_of_time = TRUE;
11476 for (i = 0; i < MAX_PLAYERS; i++)
11477 KillPlayer(&stored_player[i]);
11480 else if (game.no_time_limit && !game.all_players_gone)
11482 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11485 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11488 if (tape.recording || tape.playing)
11489 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11492 if (tape.recording || tape.playing)
11493 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11495 UpdateAndDisplayGameControlValues();
11498 void AdvanceFrameAndPlayerCounters(int player_nr)
11502 // advance frame counters (global frame counter and time frame counter)
11506 // advance player counters (counters for move delay, move animation etc.)
11507 for (i = 0; i < MAX_PLAYERS; i++)
11509 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11510 int move_delay_value = stored_player[i].move_delay_value;
11511 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11513 if (!advance_player_counters) // not all players may be affected
11516 if (move_frames == 0) // less than one move per game frame
11518 int stepsize = TILEX / move_delay_value;
11519 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11520 int count = (stored_player[i].is_moving ?
11521 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11523 if (count % delay == 0)
11527 stored_player[i].Frame += move_frames;
11529 if (stored_player[i].MovPos != 0)
11530 stored_player[i].StepFrame += move_frames;
11532 if (stored_player[i].move_delay > 0)
11533 stored_player[i].move_delay--;
11535 // due to bugs in previous versions, counter must count up, not down
11536 if (stored_player[i].push_delay != -1)
11537 stored_player[i].push_delay++;
11539 if (stored_player[i].drop_delay > 0)
11540 stored_player[i].drop_delay--;
11542 if (stored_player[i].is_dropping_pressed)
11543 stored_player[i].drop_pressed_delay++;
11547 void StartGameActions(boolean init_network_game, boolean record_tape,
11550 unsigned int new_random_seed = InitRND(random_seed);
11553 TapeStartRecording(new_random_seed);
11555 if (init_network_game)
11557 SendToServer_LevelFile();
11558 SendToServer_StartPlaying();
11566 static void GameActionsExt(void)
11569 static unsigned int game_frame_delay = 0;
11571 unsigned int game_frame_delay_value;
11572 byte *recorded_player_action;
11573 byte summarized_player_action = 0;
11574 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11577 // detect endless loops, caused by custom element programming
11578 if (recursion_loop_detected && recursion_loop_depth == 0)
11580 char *message = getStringCat3("Internal Error! Element ",
11581 EL_NAME(recursion_loop_element),
11582 " caused endless loop! Quit the game?");
11584 Warn("element '%s' caused endless loop in game engine",
11585 EL_NAME(recursion_loop_element));
11587 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11589 recursion_loop_detected = FALSE; // if game should be continued
11596 if (game.restart_level)
11597 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11599 CheckLevelSolved();
11601 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11604 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11607 if (game_status != GAME_MODE_PLAYING) // status might have changed
11610 game_frame_delay_value =
11611 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11613 if (tape.playing && tape.warp_forward && !tape.pausing)
11614 game_frame_delay_value = 0;
11616 SetVideoFrameDelay(game_frame_delay_value);
11618 // (de)activate virtual buttons depending on current game status
11619 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11621 if (game.all_players_gone) // if no players there to be controlled anymore
11622 SetOverlayActive(FALSE);
11623 else if (!tape.playing) // if game continues after tape stopped playing
11624 SetOverlayActive(TRUE);
11629 // ---------- main game synchronization point ----------
11631 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11633 Debug("game:playing:skip", "skip == %d", skip);
11636 // ---------- main game synchronization point ----------
11638 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11642 if (network_playing && !network_player_action_received)
11644 // try to get network player actions in time
11646 // last chance to get network player actions without main loop delay
11647 HandleNetworking();
11649 // game was quit by network peer
11650 if (game_status != GAME_MODE_PLAYING)
11653 // check if network player actions still missing and game still running
11654 if (!network_player_action_received && !checkGameEnded())
11655 return; // failed to get network player actions in time
11657 // do not yet reset "network_player_action_received" (for tape.pausing)
11663 // at this point we know that we really continue executing the game
11665 network_player_action_received = FALSE;
11667 // when playing tape, read previously recorded player input from tape data
11668 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11670 local_player->effective_mouse_action = local_player->mouse_action;
11672 if (recorded_player_action != NULL)
11673 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11674 recorded_player_action);
11676 // TapePlayAction() may return NULL when toggling to "pause before death"
11680 if (tape.set_centered_player)
11682 game.centered_player_nr_next = tape.centered_player_nr_next;
11683 game.set_centered_player = TRUE;
11686 for (i = 0; i < MAX_PLAYERS; i++)
11688 summarized_player_action |= stored_player[i].action;
11690 if (!network_playing && (game.team_mode || tape.playing))
11691 stored_player[i].effective_action = stored_player[i].action;
11694 if (network_playing && !checkGameEnded())
11695 SendToServer_MovePlayer(summarized_player_action);
11697 // summarize all actions at local players mapped input device position
11698 // (this allows using different input devices in single player mode)
11699 if (!network.enabled && !game.team_mode)
11700 stored_player[map_player_action[local_player->index_nr]].effective_action =
11701 summarized_player_action;
11703 // summarize all actions at centered player in local team mode
11704 if (tape.recording &&
11705 setup.team_mode && !network.enabled &&
11706 setup.input_on_focus &&
11707 game.centered_player_nr != -1)
11709 for (i = 0; i < MAX_PLAYERS; i++)
11710 stored_player[map_player_action[i]].effective_action =
11711 (i == game.centered_player_nr ? summarized_player_action : 0);
11714 if (recorded_player_action != NULL)
11715 for (i = 0; i < MAX_PLAYERS; i++)
11716 stored_player[i].effective_action = recorded_player_action[i];
11718 for (i = 0; i < MAX_PLAYERS; i++)
11720 tape_action[i] = stored_player[i].effective_action;
11722 /* (this may happen in the RND game engine if a player was not present on
11723 the playfield on level start, but appeared later from a custom element */
11724 if (setup.team_mode &&
11727 !tape.player_participates[i])
11728 tape.player_participates[i] = TRUE;
11731 SetTapeActionFromMouseAction(tape_action,
11732 &local_player->effective_mouse_action);
11734 // only record actions from input devices, but not programmed actions
11735 if (tape.recording)
11736 TapeRecordAction(tape_action);
11738 // remember if game was played (especially after tape stopped playing)
11739 if (!tape.playing && summarized_player_action)
11740 game.GamePlayed = TRUE;
11742 #if USE_NEW_PLAYER_ASSIGNMENTS
11743 // !!! also map player actions in single player mode !!!
11744 // if (game.team_mode)
11747 byte mapped_action[MAX_PLAYERS];
11749 #if DEBUG_PLAYER_ACTIONS
11750 for (i = 0; i < MAX_PLAYERS; i++)
11751 DebugContinued("", "%d, ", stored_player[i].effective_action);
11754 for (i = 0; i < MAX_PLAYERS; i++)
11755 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11757 for (i = 0; i < MAX_PLAYERS; i++)
11758 stored_player[i].effective_action = mapped_action[i];
11760 #if DEBUG_PLAYER_ACTIONS
11761 DebugContinued("", "=> ");
11762 for (i = 0; i < MAX_PLAYERS; i++)
11763 DebugContinued("", "%d, ", stored_player[i].effective_action);
11764 DebugContinued("game:playing:player", "\n");
11767 #if DEBUG_PLAYER_ACTIONS
11770 for (i = 0; i < MAX_PLAYERS; i++)
11771 DebugContinued("", "%d, ", stored_player[i].effective_action);
11772 DebugContinued("game:playing:player", "\n");
11777 for (i = 0; i < MAX_PLAYERS; i++)
11779 // allow engine snapshot in case of changed movement attempt
11780 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11781 (stored_player[i].effective_action & KEY_MOTION))
11782 game.snapshot.changed_action = TRUE;
11784 // allow engine snapshot in case of snapping/dropping attempt
11785 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11786 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11787 game.snapshot.changed_action = TRUE;
11789 game.snapshot.last_action[i] = stored_player[i].effective_action;
11792 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11794 GameActions_EM_Main();
11796 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11798 GameActions_SP_Main();
11800 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11802 GameActions_MM_Main();
11806 GameActions_RND_Main();
11809 BlitScreenToBitmap(backbuffer);
11811 CheckLevelSolved();
11814 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11816 if (global.show_frames_per_second)
11818 static unsigned int fps_counter = 0;
11819 static int fps_frames = 0;
11820 unsigned int fps_delay_ms = Counter() - fps_counter;
11824 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11826 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11829 fps_counter = Counter();
11831 // always draw FPS to screen after FPS value was updated
11832 redraw_mask |= REDRAW_FPS;
11835 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11836 if (GetDrawDeactivationMask() == REDRAW_NONE)
11837 redraw_mask |= REDRAW_FPS;
11841 static void GameActions_CheckSaveEngineSnapshot(void)
11843 if (!game.snapshot.save_snapshot)
11846 // clear flag for saving snapshot _before_ saving snapshot
11847 game.snapshot.save_snapshot = FALSE;
11849 SaveEngineSnapshotToList();
11852 void GameActions(void)
11856 GameActions_CheckSaveEngineSnapshot();
11859 void GameActions_EM_Main(void)
11861 byte effective_action[MAX_PLAYERS];
11862 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11865 for (i = 0; i < MAX_PLAYERS; i++)
11866 effective_action[i] = stored_player[i].effective_action;
11868 GameActions_EM(effective_action, warp_mode);
11871 void GameActions_SP_Main(void)
11873 byte effective_action[MAX_PLAYERS];
11874 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11877 for (i = 0; i < MAX_PLAYERS; i++)
11878 effective_action[i] = stored_player[i].effective_action;
11880 GameActions_SP(effective_action, warp_mode);
11882 for (i = 0; i < MAX_PLAYERS; i++)
11884 if (stored_player[i].force_dropping)
11885 stored_player[i].action |= KEY_BUTTON_DROP;
11887 stored_player[i].force_dropping = FALSE;
11891 void GameActions_MM_Main(void)
11893 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11895 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11898 void GameActions_RND_Main(void)
11903 void GameActions_RND(void)
11905 static struct MouseActionInfo mouse_action_last = { 0 };
11906 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11907 int magic_wall_x = 0, magic_wall_y = 0;
11908 int i, x, y, element, graphic, last_gfx_frame;
11910 InitPlayfieldScanModeVars();
11912 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11914 SCAN_PLAYFIELD(x, y)
11916 ChangeCount[x][y] = 0;
11917 ChangeEvent[x][y] = -1;
11921 if (game.set_centered_player)
11923 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11925 // switching to "all players" only possible if all players fit to screen
11926 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11928 game.centered_player_nr_next = game.centered_player_nr;
11929 game.set_centered_player = FALSE;
11932 // do not switch focus to non-existing (or non-active) player
11933 if (game.centered_player_nr_next >= 0 &&
11934 !stored_player[game.centered_player_nr_next].active)
11936 game.centered_player_nr_next = game.centered_player_nr;
11937 game.set_centered_player = FALSE;
11941 if (game.set_centered_player &&
11942 ScreenMovPos == 0) // screen currently aligned at tile position
11946 if (game.centered_player_nr_next == -1)
11948 setScreenCenteredToAllPlayers(&sx, &sy);
11952 sx = stored_player[game.centered_player_nr_next].jx;
11953 sy = stored_player[game.centered_player_nr_next].jy;
11956 game.centered_player_nr = game.centered_player_nr_next;
11957 game.set_centered_player = FALSE;
11959 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11960 DrawGameDoorValues();
11963 // check single step mode (set flag and clear again if any player is active)
11964 game.enter_single_step_mode =
11965 (tape.single_step && tape.recording && !tape.pausing);
11967 for (i = 0; i < MAX_PLAYERS; i++)
11969 int actual_player_action = stored_player[i].effective_action;
11972 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11973 - rnd_equinox_tetrachloride 048
11974 - rnd_equinox_tetrachloride_ii 096
11975 - rnd_emanuel_schmieg 002
11976 - doctor_sloan_ww 001, 020
11978 if (stored_player[i].MovPos == 0)
11979 CheckGravityMovement(&stored_player[i]);
11982 // overwrite programmed action with tape action
11983 if (stored_player[i].programmed_action)
11984 actual_player_action = stored_player[i].programmed_action;
11986 PlayerActions(&stored_player[i], actual_player_action);
11988 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11991 // single step pause mode may already have been toggled by "ScrollPlayer()"
11992 if (game.enter_single_step_mode && !tape.pausing)
11993 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11995 ScrollScreen(NULL, SCROLL_GO_ON);
11997 /* for backwards compatibility, the following code emulates a fixed bug that
11998 occured when pushing elements (causing elements that just made their last
11999 pushing step to already (if possible) make their first falling step in the
12000 same game frame, which is bad); this code is also needed to use the famous
12001 "spring push bug" which is used in older levels and might be wanted to be
12002 used also in newer levels, but in this case the buggy pushing code is only
12003 affecting the "spring" element and no other elements */
12005 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12007 for (i = 0; i < MAX_PLAYERS; i++)
12009 struct PlayerInfo *player = &stored_player[i];
12010 int x = player->jx;
12011 int y = player->jy;
12013 if (player->active && player->is_pushing && player->is_moving &&
12015 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12016 Tile[x][y] == EL_SPRING))
12018 ContinueMoving(x, y);
12020 // continue moving after pushing (this is actually a bug)
12021 if (!IS_MOVING(x, y))
12022 Stop[x][y] = FALSE;
12027 SCAN_PLAYFIELD(x, y)
12029 Last[x][y] = Tile[x][y];
12031 ChangeCount[x][y] = 0;
12032 ChangeEvent[x][y] = -1;
12034 // this must be handled before main playfield loop
12035 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12038 if (MovDelay[x][y] <= 0)
12042 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12045 if (MovDelay[x][y] <= 0)
12047 int element = Store[x][y];
12048 int move_direction = MovDir[x][y];
12049 int player_index_bit = Store2[x][y];
12055 TEST_DrawLevelField(x, y);
12057 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12062 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12064 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12066 Debug("game:playing:GameActions_RND", "This should never happen!");
12068 ChangePage[x][y] = -1;
12072 Stop[x][y] = FALSE;
12073 if (WasJustMoving[x][y] > 0)
12074 WasJustMoving[x][y]--;
12075 if (WasJustFalling[x][y] > 0)
12076 WasJustFalling[x][y]--;
12077 if (CheckCollision[x][y] > 0)
12078 CheckCollision[x][y]--;
12079 if (CheckImpact[x][y] > 0)
12080 CheckImpact[x][y]--;
12084 /* reset finished pushing action (not done in ContinueMoving() to allow
12085 continuous pushing animation for elements with zero push delay) */
12086 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12088 ResetGfxAnimation(x, y);
12089 TEST_DrawLevelField(x, y);
12093 if (IS_BLOCKED(x, y))
12097 Blocked2Moving(x, y, &oldx, &oldy);
12098 if (!IS_MOVING(oldx, oldy))
12100 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12101 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12102 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12103 Debug("game:playing:GameActions_RND", "This should never happen!");
12109 if (mouse_action.button)
12111 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12113 x = mouse_action.lx;
12114 y = mouse_action.ly;
12115 element = Tile[x][y];
12119 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12120 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12123 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12124 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12127 SCAN_PLAYFIELD(x, y)
12129 element = Tile[x][y];
12130 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12131 last_gfx_frame = GfxFrame[x][y];
12133 ResetGfxFrame(x, y);
12135 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12136 DrawLevelGraphicAnimation(x, y, graphic);
12138 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12139 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12140 ResetRandomAnimationValue(x, y);
12142 SetRandomAnimationValue(x, y);
12144 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12146 if (IS_INACTIVE(element))
12148 if (IS_ANIMATED(graphic))
12149 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12154 // this may take place after moving, so 'element' may have changed
12155 if (IS_CHANGING(x, y) &&
12156 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12158 int page = element_info[element].event_page_nr[CE_DELAY];
12160 HandleElementChange(x, y, page);
12162 element = Tile[x][y];
12163 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12166 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12170 element = Tile[x][y];
12171 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12173 if (IS_ANIMATED(graphic) &&
12174 !IS_MOVING(x, y) &&
12176 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12178 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12179 TEST_DrawTwinkleOnField(x, y);
12181 else if (element == EL_ACID)
12184 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12186 else if ((element == EL_EXIT_OPEN ||
12187 element == EL_EM_EXIT_OPEN ||
12188 element == EL_SP_EXIT_OPEN ||
12189 element == EL_STEEL_EXIT_OPEN ||
12190 element == EL_EM_STEEL_EXIT_OPEN ||
12191 element == EL_SP_TERMINAL ||
12192 element == EL_SP_TERMINAL_ACTIVE ||
12193 element == EL_EXTRA_TIME ||
12194 element == EL_SHIELD_NORMAL ||
12195 element == EL_SHIELD_DEADLY) &&
12196 IS_ANIMATED(graphic))
12197 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12198 else if (IS_MOVING(x, y))
12199 ContinueMoving(x, y);
12200 else if (IS_ACTIVE_BOMB(element))
12201 CheckDynamite(x, y);
12202 else if (element == EL_AMOEBA_GROWING)
12203 AmoebaGrowing(x, y);
12204 else if (element == EL_AMOEBA_SHRINKING)
12205 AmoebaShrinking(x, y);
12207 #if !USE_NEW_AMOEBA_CODE
12208 else if (IS_AMOEBALIVE(element))
12209 AmoebaReproduce(x, y);
12212 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12214 else if (element == EL_EXIT_CLOSED)
12216 else if (element == EL_EM_EXIT_CLOSED)
12218 else if (element == EL_STEEL_EXIT_CLOSED)
12219 CheckExitSteel(x, y);
12220 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12221 CheckExitSteelEM(x, y);
12222 else if (element == EL_SP_EXIT_CLOSED)
12224 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12225 element == EL_EXPANDABLE_STEELWALL_GROWING)
12226 MauerWaechst(x, y);
12227 else if (element == EL_EXPANDABLE_WALL ||
12228 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12229 element == EL_EXPANDABLE_WALL_VERTICAL ||
12230 element == EL_EXPANDABLE_WALL_ANY ||
12231 element == EL_BD_EXPANDABLE_WALL)
12232 MauerAbleger(x, y);
12233 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12234 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12235 element == EL_EXPANDABLE_STEELWALL_ANY)
12236 MauerAblegerStahl(x, y);
12237 else if (element == EL_FLAMES)
12238 CheckForDragon(x, y);
12239 else if (element == EL_EXPLOSION)
12240 ; // drawing of correct explosion animation is handled separately
12241 else if (element == EL_ELEMENT_SNAPPING ||
12242 element == EL_DIAGONAL_SHRINKING ||
12243 element == EL_DIAGONAL_GROWING)
12245 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12247 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12249 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12250 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12252 if (IS_BELT_ACTIVE(element))
12253 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12255 if (game.magic_wall_active)
12257 int jx = local_player->jx, jy = local_player->jy;
12259 // play the element sound at the position nearest to the player
12260 if ((element == EL_MAGIC_WALL_FULL ||
12261 element == EL_MAGIC_WALL_ACTIVE ||
12262 element == EL_MAGIC_WALL_EMPTYING ||
12263 element == EL_BD_MAGIC_WALL_FULL ||
12264 element == EL_BD_MAGIC_WALL_ACTIVE ||
12265 element == EL_BD_MAGIC_WALL_EMPTYING ||
12266 element == EL_DC_MAGIC_WALL_FULL ||
12267 element == EL_DC_MAGIC_WALL_ACTIVE ||
12268 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12269 ABS(x - jx) + ABS(y - jy) <
12270 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12278 #if USE_NEW_AMOEBA_CODE
12279 // new experimental amoeba growth stuff
12280 if (!(FrameCounter % 8))
12282 static unsigned int random = 1684108901;
12284 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12286 x = RND(lev_fieldx);
12287 y = RND(lev_fieldy);
12288 element = Tile[x][y];
12290 if (!IS_PLAYER(x,y) &&
12291 (element == EL_EMPTY ||
12292 CAN_GROW_INTO(element) ||
12293 element == EL_QUICKSAND_EMPTY ||
12294 element == EL_QUICKSAND_FAST_EMPTY ||
12295 element == EL_ACID_SPLASH_LEFT ||
12296 element == EL_ACID_SPLASH_RIGHT))
12298 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12299 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12300 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12301 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12302 Tile[x][y] = EL_AMOEBA_DROP;
12305 random = random * 129 + 1;
12310 game.explosions_delayed = FALSE;
12312 SCAN_PLAYFIELD(x, y)
12314 element = Tile[x][y];
12316 if (ExplodeField[x][y])
12317 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12318 else if (element == EL_EXPLOSION)
12319 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12321 ExplodeField[x][y] = EX_TYPE_NONE;
12324 game.explosions_delayed = TRUE;
12326 if (game.magic_wall_active)
12328 if (!(game.magic_wall_time_left % 4))
12330 int element = Tile[magic_wall_x][magic_wall_y];
12332 if (element == EL_BD_MAGIC_WALL_FULL ||
12333 element == EL_BD_MAGIC_WALL_ACTIVE ||
12334 element == EL_BD_MAGIC_WALL_EMPTYING)
12335 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12336 else if (element == EL_DC_MAGIC_WALL_FULL ||
12337 element == EL_DC_MAGIC_WALL_ACTIVE ||
12338 element == EL_DC_MAGIC_WALL_EMPTYING)
12339 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12341 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12344 if (game.magic_wall_time_left > 0)
12346 game.magic_wall_time_left--;
12348 if (!game.magic_wall_time_left)
12350 SCAN_PLAYFIELD(x, y)
12352 element = Tile[x][y];
12354 if (element == EL_MAGIC_WALL_ACTIVE ||
12355 element == EL_MAGIC_WALL_FULL)
12357 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12358 TEST_DrawLevelField(x, y);
12360 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12361 element == EL_BD_MAGIC_WALL_FULL)
12363 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12364 TEST_DrawLevelField(x, y);
12366 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12367 element == EL_DC_MAGIC_WALL_FULL)
12369 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12370 TEST_DrawLevelField(x, y);
12374 game.magic_wall_active = FALSE;
12379 if (game.light_time_left > 0)
12381 game.light_time_left--;
12383 if (game.light_time_left == 0)
12384 RedrawAllLightSwitchesAndInvisibleElements();
12387 if (game.timegate_time_left > 0)
12389 game.timegate_time_left--;
12391 if (game.timegate_time_left == 0)
12392 CloseAllOpenTimegates();
12395 if (game.lenses_time_left > 0)
12397 game.lenses_time_left--;
12399 if (game.lenses_time_left == 0)
12400 RedrawAllInvisibleElementsForLenses();
12403 if (game.magnify_time_left > 0)
12405 game.magnify_time_left--;
12407 if (game.magnify_time_left == 0)
12408 RedrawAllInvisibleElementsForMagnifier();
12411 for (i = 0; i < MAX_PLAYERS; i++)
12413 struct PlayerInfo *player = &stored_player[i];
12415 if (SHIELD_ON(player))
12417 if (player->shield_deadly_time_left)
12418 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12419 else if (player->shield_normal_time_left)
12420 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12424 #if USE_DELAYED_GFX_REDRAW
12425 SCAN_PLAYFIELD(x, y)
12427 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12429 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12430 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12432 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12433 DrawLevelField(x, y);
12435 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12436 DrawLevelFieldCrumbled(x, y);
12438 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12439 DrawLevelFieldCrumbledNeighbours(x, y);
12441 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12442 DrawTwinkleOnField(x, y);
12445 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12450 PlayAllPlayersSound();
12452 for (i = 0; i < MAX_PLAYERS; i++)
12454 struct PlayerInfo *player = &stored_player[i];
12456 if (player->show_envelope != 0 && (!player->active ||
12457 player->MovPos == 0))
12459 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12461 player->show_envelope = 0;
12465 // use random number generator in every frame to make it less predictable
12466 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12469 mouse_action_last = mouse_action;
12472 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12474 int min_x = x, min_y = y, max_x = x, max_y = y;
12475 int scr_fieldx = getScreenFieldSizeX();
12476 int scr_fieldy = getScreenFieldSizeY();
12479 for (i = 0; i < MAX_PLAYERS; i++)
12481 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12483 if (!stored_player[i].active || &stored_player[i] == player)
12486 min_x = MIN(min_x, jx);
12487 min_y = MIN(min_y, jy);
12488 max_x = MAX(max_x, jx);
12489 max_y = MAX(max_y, jy);
12492 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12495 static boolean AllPlayersInVisibleScreen(void)
12499 for (i = 0; i < MAX_PLAYERS; i++)
12501 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12503 if (!stored_player[i].active)
12506 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12513 void ScrollLevel(int dx, int dy)
12515 int scroll_offset = 2 * TILEX_VAR;
12518 BlitBitmap(drawto_field, drawto_field,
12519 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12520 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12521 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12522 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12523 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12524 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12528 x = (dx == 1 ? BX1 : BX2);
12529 for (y = BY1; y <= BY2; y++)
12530 DrawScreenField(x, y);
12535 y = (dy == 1 ? BY1 : BY2);
12536 for (x = BX1; x <= BX2; x++)
12537 DrawScreenField(x, y);
12540 redraw_mask |= REDRAW_FIELD;
12543 static boolean canFallDown(struct PlayerInfo *player)
12545 int jx = player->jx, jy = player->jy;
12547 return (IN_LEV_FIELD(jx, jy + 1) &&
12548 (IS_FREE(jx, jy + 1) ||
12549 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12550 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12551 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12554 static boolean canPassField(int x, int y, int move_dir)
12556 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12557 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12558 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12559 int nextx = x + dx;
12560 int nexty = y + dy;
12561 int element = Tile[x][y];
12563 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12564 !CAN_MOVE(element) &&
12565 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12566 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12567 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12570 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12572 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12573 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12574 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12578 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12579 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12580 (IS_DIGGABLE(Tile[newx][newy]) ||
12581 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12582 canPassField(newx, newy, move_dir)));
12585 static void CheckGravityMovement(struct PlayerInfo *player)
12587 if (player->gravity && !player->programmed_action)
12589 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12590 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12591 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12592 int jx = player->jx, jy = player->jy;
12593 boolean player_is_moving_to_valid_field =
12594 (!player_is_snapping &&
12595 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12596 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12597 boolean player_can_fall_down = canFallDown(player);
12599 if (player_can_fall_down &&
12600 !player_is_moving_to_valid_field)
12601 player->programmed_action = MV_DOWN;
12605 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12607 return CheckGravityMovement(player);
12609 if (player->gravity && !player->programmed_action)
12611 int jx = player->jx, jy = player->jy;
12612 boolean field_under_player_is_free =
12613 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12614 boolean player_is_standing_on_valid_field =
12615 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12616 (IS_WALKABLE(Tile[jx][jy]) &&
12617 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12619 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12620 player->programmed_action = MV_DOWN;
12625 MovePlayerOneStep()
12626 -----------------------------------------------------------------------------
12627 dx, dy: direction (non-diagonal) to try to move the player to
12628 real_dx, real_dy: direction as read from input device (can be diagonal)
12631 boolean MovePlayerOneStep(struct PlayerInfo *player,
12632 int dx, int dy, int real_dx, int real_dy)
12634 int jx = player->jx, jy = player->jy;
12635 int new_jx = jx + dx, new_jy = jy + dy;
12637 boolean player_can_move = !player->cannot_move;
12639 if (!player->active || (!dx && !dy))
12640 return MP_NO_ACTION;
12642 player->MovDir = (dx < 0 ? MV_LEFT :
12643 dx > 0 ? MV_RIGHT :
12645 dy > 0 ? MV_DOWN : MV_NONE);
12647 if (!IN_LEV_FIELD(new_jx, new_jy))
12648 return MP_NO_ACTION;
12650 if (!player_can_move)
12652 if (player->MovPos == 0)
12654 player->is_moving = FALSE;
12655 player->is_digging = FALSE;
12656 player->is_collecting = FALSE;
12657 player->is_snapping = FALSE;
12658 player->is_pushing = FALSE;
12662 if (!network.enabled && game.centered_player_nr == -1 &&
12663 !AllPlayersInSight(player, new_jx, new_jy))
12664 return MP_NO_ACTION;
12666 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12667 if (can_move != MP_MOVING)
12670 // check if DigField() has caused relocation of the player
12671 if (player->jx != jx || player->jy != jy)
12672 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12674 StorePlayer[jx][jy] = 0;
12675 player->last_jx = jx;
12676 player->last_jy = jy;
12677 player->jx = new_jx;
12678 player->jy = new_jy;
12679 StorePlayer[new_jx][new_jy] = player->element_nr;
12681 if (player->move_delay_value_next != -1)
12683 player->move_delay_value = player->move_delay_value_next;
12684 player->move_delay_value_next = -1;
12688 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12690 player->step_counter++;
12692 PlayerVisit[jx][jy] = FrameCounter;
12694 player->is_moving = TRUE;
12697 // should better be called in MovePlayer(), but this breaks some tapes
12698 ScrollPlayer(player, SCROLL_INIT);
12704 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12706 int jx = player->jx, jy = player->jy;
12707 int old_jx = jx, old_jy = jy;
12708 int moved = MP_NO_ACTION;
12710 if (!player->active)
12715 if (player->MovPos == 0)
12717 player->is_moving = FALSE;
12718 player->is_digging = FALSE;
12719 player->is_collecting = FALSE;
12720 player->is_snapping = FALSE;
12721 player->is_pushing = FALSE;
12727 if (player->move_delay > 0)
12730 player->move_delay = -1; // set to "uninitialized" value
12732 // store if player is automatically moved to next field
12733 player->is_auto_moving = (player->programmed_action != MV_NONE);
12735 // remove the last programmed player action
12736 player->programmed_action = 0;
12738 if (player->MovPos)
12740 // should only happen if pre-1.2 tape recordings are played
12741 // this is only for backward compatibility
12743 int original_move_delay_value = player->move_delay_value;
12746 Debug("game:playing:MovePlayer",
12747 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12751 // scroll remaining steps with finest movement resolution
12752 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12754 while (player->MovPos)
12756 ScrollPlayer(player, SCROLL_GO_ON);
12757 ScrollScreen(NULL, SCROLL_GO_ON);
12759 AdvanceFrameAndPlayerCounters(player->index_nr);
12762 BackToFront_WithFrameDelay(0);
12765 player->move_delay_value = original_move_delay_value;
12768 player->is_active = FALSE;
12770 if (player->last_move_dir & MV_HORIZONTAL)
12772 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12773 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12777 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12778 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12781 if (!moved && !player->is_active)
12783 player->is_moving = FALSE;
12784 player->is_digging = FALSE;
12785 player->is_collecting = FALSE;
12786 player->is_snapping = FALSE;
12787 player->is_pushing = FALSE;
12793 if (moved & MP_MOVING && !ScreenMovPos &&
12794 (player->index_nr == game.centered_player_nr ||
12795 game.centered_player_nr == -1))
12797 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12799 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12801 // actual player has left the screen -- scroll in that direction
12802 if (jx != old_jx) // player has moved horizontally
12803 scroll_x += (jx - old_jx);
12804 else // player has moved vertically
12805 scroll_y += (jy - old_jy);
12809 int offset_raw = game.scroll_delay_value;
12811 if (jx != old_jx) // player has moved horizontally
12813 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12814 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12815 int new_scroll_x = jx - MIDPOSX + offset_x;
12817 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12818 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12819 scroll_x = new_scroll_x;
12821 // don't scroll over playfield boundaries
12822 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12824 // don't scroll more than one field at a time
12825 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12827 // don't scroll against the player's moving direction
12828 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12829 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12830 scroll_x = old_scroll_x;
12832 else // player has moved vertically
12834 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12835 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12836 int new_scroll_y = jy - MIDPOSY + offset_y;
12838 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12839 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12840 scroll_y = new_scroll_y;
12842 // don't scroll over playfield boundaries
12843 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12845 // don't scroll more than one field at a time
12846 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12848 // don't scroll against the player's moving direction
12849 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12850 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12851 scroll_y = old_scroll_y;
12855 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12857 if (!network.enabled && game.centered_player_nr == -1 &&
12858 !AllPlayersInVisibleScreen())
12860 scroll_x = old_scroll_x;
12861 scroll_y = old_scroll_y;
12865 ScrollScreen(player, SCROLL_INIT);
12866 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12871 player->StepFrame = 0;
12873 if (moved & MP_MOVING)
12875 if (old_jx != jx && old_jy == jy)
12876 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12877 else if (old_jx == jx && old_jy != jy)
12878 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12880 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12882 player->last_move_dir = player->MovDir;
12883 player->is_moving = TRUE;
12884 player->is_snapping = FALSE;
12885 player->is_switching = FALSE;
12886 player->is_dropping = FALSE;
12887 player->is_dropping_pressed = FALSE;
12888 player->drop_pressed_delay = 0;
12891 // should better be called here than above, but this breaks some tapes
12892 ScrollPlayer(player, SCROLL_INIT);
12897 CheckGravityMovementWhenNotMoving(player);
12899 player->is_moving = FALSE;
12901 /* at this point, the player is allowed to move, but cannot move right now
12902 (e.g. because of something blocking the way) -- ensure that the player
12903 is also allowed to move in the next frame (in old versions before 3.1.1,
12904 the player was forced to wait again for eight frames before next try) */
12906 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12907 player->move_delay = 0; // allow direct movement in the next frame
12910 if (player->move_delay == -1) // not yet initialized by DigField()
12911 player->move_delay = player->move_delay_value;
12913 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12915 TestIfPlayerTouchesBadThing(jx, jy);
12916 TestIfPlayerTouchesCustomElement(jx, jy);
12919 if (!player->active)
12920 RemovePlayer(player);
12925 void ScrollPlayer(struct PlayerInfo *player, int mode)
12927 int jx = player->jx, jy = player->jy;
12928 int last_jx = player->last_jx, last_jy = player->last_jy;
12929 int move_stepsize = TILEX / player->move_delay_value;
12931 if (!player->active)
12934 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12937 if (mode == SCROLL_INIT)
12939 player->actual_frame_counter = FrameCounter;
12940 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12942 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12943 Tile[last_jx][last_jy] == EL_EMPTY)
12945 int last_field_block_delay = 0; // start with no blocking at all
12946 int block_delay_adjustment = player->block_delay_adjustment;
12948 // if player blocks last field, add delay for exactly one move
12949 if (player->block_last_field)
12951 last_field_block_delay += player->move_delay_value;
12953 // when blocking enabled, prevent moving up despite gravity
12954 if (player->gravity && player->MovDir == MV_UP)
12955 block_delay_adjustment = -1;
12958 // add block delay adjustment (also possible when not blocking)
12959 last_field_block_delay += block_delay_adjustment;
12961 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12962 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12965 if (player->MovPos != 0) // player has not yet reached destination
12968 else if (!FrameReached(&player->actual_frame_counter, 1))
12971 if (player->MovPos != 0)
12973 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12974 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12976 // before DrawPlayer() to draw correct player graphic for this case
12977 if (player->MovPos == 0)
12978 CheckGravityMovement(player);
12981 if (player->MovPos == 0) // player reached destination field
12983 if (player->move_delay_reset_counter > 0)
12985 player->move_delay_reset_counter--;
12987 if (player->move_delay_reset_counter == 0)
12989 // continue with normal speed after quickly moving through gate
12990 HALVE_PLAYER_SPEED(player);
12992 // be able to make the next move without delay
12993 player->move_delay = 0;
12997 player->last_jx = jx;
12998 player->last_jy = jy;
13000 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13001 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13002 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13003 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13004 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13005 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13006 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13007 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13009 ExitPlayer(player);
13011 if (game.players_still_needed == 0 &&
13012 (game.friends_still_needed == 0 ||
13013 IS_SP_ELEMENT(Tile[jx][jy])))
13017 // this breaks one level: "machine", level 000
13019 int move_direction = player->MovDir;
13020 int enter_side = MV_DIR_OPPOSITE(move_direction);
13021 int leave_side = move_direction;
13022 int old_jx = last_jx;
13023 int old_jy = last_jy;
13024 int old_element = Tile[old_jx][old_jy];
13025 int new_element = Tile[jx][jy];
13027 if (IS_CUSTOM_ELEMENT(old_element))
13028 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13030 player->index_bit, leave_side);
13032 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13033 CE_PLAYER_LEAVES_X,
13034 player->index_bit, leave_side);
13036 if (IS_CUSTOM_ELEMENT(new_element))
13037 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13038 player->index_bit, enter_side);
13040 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13041 CE_PLAYER_ENTERS_X,
13042 player->index_bit, enter_side);
13044 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13045 CE_MOVE_OF_X, move_direction);
13048 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13050 TestIfPlayerTouchesBadThing(jx, jy);
13051 TestIfPlayerTouchesCustomElement(jx, jy);
13053 /* needed because pushed element has not yet reached its destination,
13054 so it would trigger a change event at its previous field location */
13055 if (!player->is_pushing)
13056 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13058 if (level.finish_dig_collect &&
13059 (player->is_digging || player->is_collecting))
13061 int last_element = player->last_removed_element;
13062 int move_direction = player->MovDir;
13063 int enter_side = MV_DIR_OPPOSITE(move_direction);
13064 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13065 CE_PLAYER_COLLECTS_X);
13067 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13068 player->index_bit, enter_side);
13070 player->last_removed_element = EL_UNDEFINED;
13073 if (!player->active)
13074 RemovePlayer(player);
13077 if (!game.LevelSolved && level.use_step_counter)
13087 if (TimeLeft <= 10 && setup.time_limit)
13088 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13090 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13092 DisplayGameControlValues();
13094 if (!TimeLeft && setup.time_limit)
13095 for (i = 0; i < MAX_PLAYERS; i++)
13096 KillPlayer(&stored_player[i]);
13098 else if (game.no_time_limit && !game.all_players_gone)
13100 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13102 DisplayGameControlValues();
13106 if (tape.single_step && tape.recording && !tape.pausing &&
13107 !player->programmed_action)
13108 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13110 if (!player->programmed_action)
13111 CheckSaveEngineSnapshot(player);
13115 void ScrollScreen(struct PlayerInfo *player, int mode)
13117 static unsigned int screen_frame_counter = 0;
13119 if (mode == SCROLL_INIT)
13121 // set scrolling step size according to actual player's moving speed
13122 ScrollStepSize = TILEX / player->move_delay_value;
13124 screen_frame_counter = FrameCounter;
13125 ScreenMovDir = player->MovDir;
13126 ScreenMovPos = player->MovPos;
13127 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13130 else if (!FrameReached(&screen_frame_counter, 1))
13135 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13136 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13137 redraw_mask |= REDRAW_FIELD;
13140 ScreenMovDir = MV_NONE;
13143 void TestIfPlayerTouchesCustomElement(int x, int y)
13145 static int xy[4][2] =
13152 static int trigger_sides[4][2] =
13154 // center side border side
13155 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13156 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13157 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13158 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13160 static int touch_dir[4] =
13162 MV_LEFT | MV_RIGHT,
13167 int center_element = Tile[x][y]; // should always be non-moving!
13170 for (i = 0; i < NUM_DIRECTIONS; i++)
13172 int xx = x + xy[i][0];
13173 int yy = y + xy[i][1];
13174 int center_side = trigger_sides[i][0];
13175 int border_side = trigger_sides[i][1];
13176 int border_element;
13178 if (!IN_LEV_FIELD(xx, yy))
13181 if (IS_PLAYER(x, y)) // player found at center element
13183 struct PlayerInfo *player = PLAYERINFO(x, y);
13185 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13186 border_element = Tile[xx][yy]; // may be moving!
13187 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13188 border_element = Tile[xx][yy];
13189 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13190 border_element = MovingOrBlocked2Element(xx, yy);
13192 continue; // center and border element do not touch
13194 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13195 player->index_bit, border_side);
13196 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13197 CE_PLAYER_TOUCHES_X,
13198 player->index_bit, border_side);
13201 /* use player element that is initially defined in the level playfield,
13202 not the player element that corresponds to the runtime player number
13203 (example: a level that contains EL_PLAYER_3 as the only player would
13204 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13205 int player_element = PLAYERINFO(x, y)->initial_element;
13207 CheckElementChangeBySide(xx, yy, border_element, player_element,
13208 CE_TOUCHING_X, border_side);
13211 else if (IS_PLAYER(xx, yy)) // player found at border element
13213 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13215 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13217 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13218 continue; // center and border element do not touch
13221 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13222 player->index_bit, center_side);
13223 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13224 CE_PLAYER_TOUCHES_X,
13225 player->index_bit, center_side);
13228 /* use player element that is initially defined in the level playfield,
13229 not the player element that corresponds to the runtime player number
13230 (example: a level that contains EL_PLAYER_3 as the only player would
13231 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13232 int player_element = PLAYERINFO(xx, yy)->initial_element;
13234 CheckElementChangeBySide(x, y, center_element, player_element,
13235 CE_TOUCHING_X, center_side);
13243 void TestIfElementTouchesCustomElement(int x, int y)
13245 static int xy[4][2] =
13252 static int trigger_sides[4][2] =
13254 // center side border side
13255 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13256 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13257 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13258 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13260 static int touch_dir[4] =
13262 MV_LEFT | MV_RIGHT,
13267 boolean change_center_element = FALSE;
13268 int center_element = Tile[x][y]; // should always be non-moving!
13269 int border_element_old[NUM_DIRECTIONS];
13272 for (i = 0; i < NUM_DIRECTIONS; i++)
13274 int xx = x + xy[i][0];
13275 int yy = y + xy[i][1];
13276 int border_element;
13278 border_element_old[i] = -1;
13280 if (!IN_LEV_FIELD(xx, yy))
13283 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13284 border_element = Tile[xx][yy]; // may be moving!
13285 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13286 border_element = Tile[xx][yy];
13287 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13288 border_element = MovingOrBlocked2Element(xx, yy);
13290 continue; // center and border element do not touch
13292 border_element_old[i] = border_element;
13295 for (i = 0; i < NUM_DIRECTIONS; i++)
13297 int xx = x + xy[i][0];
13298 int yy = y + xy[i][1];
13299 int center_side = trigger_sides[i][0];
13300 int border_element = border_element_old[i];
13302 if (border_element == -1)
13305 // check for change of border element
13306 CheckElementChangeBySide(xx, yy, border_element, center_element,
13307 CE_TOUCHING_X, center_side);
13309 // (center element cannot be player, so we dont have to check this here)
13312 for (i = 0; i < NUM_DIRECTIONS; i++)
13314 int xx = x + xy[i][0];
13315 int yy = y + xy[i][1];
13316 int border_side = trigger_sides[i][1];
13317 int border_element = border_element_old[i];
13319 if (border_element == -1)
13322 // check for change of center element (but change it only once)
13323 if (!change_center_element)
13324 change_center_element =
13325 CheckElementChangeBySide(x, y, center_element, border_element,
13326 CE_TOUCHING_X, border_side);
13328 if (IS_PLAYER(xx, yy))
13330 /* use player element that is initially defined in the level playfield,
13331 not the player element that corresponds to the runtime player number
13332 (example: a level that contains EL_PLAYER_3 as the only player would
13333 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13334 int player_element = PLAYERINFO(xx, yy)->initial_element;
13336 CheckElementChangeBySide(x, y, center_element, player_element,
13337 CE_TOUCHING_X, border_side);
13342 void TestIfElementHitsCustomElement(int x, int y, int direction)
13344 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13345 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13346 int hitx = x + dx, hity = y + dy;
13347 int hitting_element = Tile[x][y];
13348 int touched_element;
13350 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13353 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13354 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13356 if (IN_LEV_FIELD(hitx, hity))
13358 int opposite_direction = MV_DIR_OPPOSITE(direction);
13359 int hitting_side = direction;
13360 int touched_side = opposite_direction;
13361 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13362 MovDir[hitx][hity] != direction ||
13363 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13369 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13370 CE_HITTING_X, touched_side);
13372 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13373 CE_HIT_BY_X, hitting_side);
13375 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13376 CE_HIT_BY_SOMETHING, opposite_direction);
13378 if (IS_PLAYER(hitx, hity))
13380 /* use player element that is initially defined in the level playfield,
13381 not the player element that corresponds to the runtime player number
13382 (example: a level that contains EL_PLAYER_3 as the only player would
13383 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13384 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13386 CheckElementChangeBySide(x, y, hitting_element, player_element,
13387 CE_HITTING_X, touched_side);
13392 // "hitting something" is also true when hitting the playfield border
13393 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13394 CE_HITTING_SOMETHING, direction);
13397 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13399 int i, kill_x = -1, kill_y = -1;
13401 int bad_element = -1;
13402 static int test_xy[4][2] =
13409 static int test_dir[4] =
13417 for (i = 0; i < NUM_DIRECTIONS; i++)
13419 int test_x, test_y, test_move_dir, test_element;
13421 test_x = good_x + test_xy[i][0];
13422 test_y = good_y + test_xy[i][1];
13424 if (!IN_LEV_FIELD(test_x, test_y))
13428 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13430 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13432 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13433 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13435 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13436 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13440 bad_element = test_element;
13446 if (kill_x != -1 || kill_y != -1)
13448 if (IS_PLAYER(good_x, good_y))
13450 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13452 if (player->shield_deadly_time_left > 0 &&
13453 !IS_INDESTRUCTIBLE(bad_element))
13454 Bang(kill_x, kill_y);
13455 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13456 KillPlayer(player);
13459 Bang(good_x, good_y);
13463 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13465 int i, kill_x = -1, kill_y = -1;
13466 int bad_element = Tile[bad_x][bad_y];
13467 static int test_xy[4][2] =
13474 static int touch_dir[4] =
13476 MV_LEFT | MV_RIGHT,
13481 static int test_dir[4] =
13489 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13492 for (i = 0; i < NUM_DIRECTIONS; i++)
13494 int test_x, test_y, test_move_dir, test_element;
13496 test_x = bad_x + test_xy[i][0];
13497 test_y = bad_y + test_xy[i][1];
13499 if (!IN_LEV_FIELD(test_x, test_y))
13503 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13505 test_element = Tile[test_x][test_y];
13507 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13508 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13510 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13511 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13513 // good thing is player or penguin that does not move away
13514 if (IS_PLAYER(test_x, test_y))
13516 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13518 if (bad_element == EL_ROBOT && player->is_moving)
13519 continue; // robot does not kill player if he is moving
13521 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13523 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13524 continue; // center and border element do not touch
13532 else if (test_element == EL_PENGUIN)
13542 if (kill_x != -1 || kill_y != -1)
13544 if (IS_PLAYER(kill_x, kill_y))
13546 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13548 if (player->shield_deadly_time_left > 0 &&
13549 !IS_INDESTRUCTIBLE(bad_element))
13550 Bang(bad_x, bad_y);
13551 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13552 KillPlayer(player);
13555 Bang(kill_x, kill_y);
13559 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13561 int bad_element = Tile[bad_x][bad_y];
13562 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13563 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13564 int test_x = bad_x + dx, test_y = bad_y + dy;
13565 int test_move_dir, test_element;
13566 int kill_x = -1, kill_y = -1;
13568 if (!IN_LEV_FIELD(test_x, test_y))
13572 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13574 test_element = Tile[test_x][test_y];
13576 if (test_move_dir != bad_move_dir)
13578 // good thing can be player or penguin that does not move away
13579 if (IS_PLAYER(test_x, test_y))
13581 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13583 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13584 player as being hit when he is moving towards the bad thing, because
13585 the "get hit by" condition would be lost after the player stops) */
13586 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13587 return; // player moves away from bad thing
13592 else if (test_element == EL_PENGUIN)
13599 if (kill_x != -1 || kill_y != -1)
13601 if (IS_PLAYER(kill_x, kill_y))
13603 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13605 if (player->shield_deadly_time_left > 0 &&
13606 !IS_INDESTRUCTIBLE(bad_element))
13607 Bang(bad_x, bad_y);
13608 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13609 KillPlayer(player);
13612 Bang(kill_x, kill_y);
13616 void TestIfPlayerTouchesBadThing(int x, int y)
13618 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13621 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13623 TestIfGoodThingHitsBadThing(x, y, move_dir);
13626 void TestIfBadThingTouchesPlayer(int x, int y)
13628 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13631 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13633 TestIfBadThingHitsGoodThing(x, y, move_dir);
13636 void TestIfFriendTouchesBadThing(int x, int y)
13638 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13641 void TestIfBadThingTouchesFriend(int x, int y)
13643 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13646 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13648 int i, kill_x = bad_x, kill_y = bad_y;
13649 static int xy[4][2] =
13657 for (i = 0; i < NUM_DIRECTIONS; i++)
13661 x = bad_x + xy[i][0];
13662 y = bad_y + xy[i][1];
13663 if (!IN_LEV_FIELD(x, y))
13666 element = Tile[x][y];
13667 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13668 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13676 if (kill_x != bad_x || kill_y != bad_y)
13677 Bang(bad_x, bad_y);
13680 void KillPlayer(struct PlayerInfo *player)
13682 int jx = player->jx, jy = player->jy;
13684 if (!player->active)
13688 Debug("game:playing:KillPlayer",
13689 "0: killed == %d, active == %d, reanimated == %d",
13690 player->killed, player->active, player->reanimated);
13693 /* the following code was introduced to prevent an infinite loop when calling
13695 -> CheckTriggeredElementChangeExt()
13696 -> ExecuteCustomElementAction()
13698 -> (infinitely repeating the above sequence of function calls)
13699 which occurs when killing the player while having a CE with the setting
13700 "kill player X when explosion of <player X>"; the solution using a new
13701 field "player->killed" was chosen for backwards compatibility, although
13702 clever use of the fields "player->active" etc. would probably also work */
13704 if (player->killed)
13708 player->killed = TRUE;
13710 // remove accessible field at the player's position
13711 Tile[jx][jy] = EL_EMPTY;
13713 // deactivate shield (else Bang()/Explode() would not work right)
13714 player->shield_normal_time_left = 0;
13715 player->shield_deadly_time_left = 0;
13718 Debug("game:playing:KillPlayer",
13719 "1: killed == %d, active == %d, reanimated == %d",
13720 player->killed, player->active, player->reanimated);
13726 Debug("game:playing:KillPlayer",
13727 "2: killed == %d, active == %d, reanimated == %d",
13728 player->killed, player->active, player->reanimated);
13731 if (player->reanimated) // killed player may have been reanimated
13732 player->killed = player->reanimated = FALSE;
13734 BuryPlayer(player);
13737 static void KillPlayerUnlessEnemyProtected(int x, int y)
13739 if (!PLAYER_ENEMY_PROTECTED(x, y))
13740 KillPlayer(PLAYERINFO(x, y));
13743 static void KillPlayerUnlessExplosionProtected(int x, int y)
13745 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13746 KillPlayer(PLAYERINFO(x, y));
13749 void BuryPlayer(struct PlayerInfo *player)
13751 int jx = player->jx, jy = player->jy;
13753 if (!player->active)
13756 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13757 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13759 RemovePlayer(player);
13761 player->buried = TRUE;
13763 if (game.all_players_gone)
13764 game.GameOver = TRUE;
13767 void RemovePlayer(struct PlayerInfo *player)
13769 int jx = player->jx, jy = player->jy;
13770 int i, found = FALSE;
13772 player->present = FALSE;
13773 player->active = FALSE;
13775 // required for some CE actions (even if the player is not active anymore)
13776 player->MovPos = 0;
13778 if (!ExplodeField[jx][jy])
13779 StorePlayer[jx][jy] = 0;
13781 if (player->is_moving)
13782 TEST_DrawLevelField(player->last_jx, player->last_jy);
13784 for (i = 0; i < MAX_PLAYERS; i++)
13785 if (stored_player[i].active)
13790 game.all_players_gone = TRUE;
13791 game.GameOver = TRUE;
13794 game.exit_x = game.robot_wheel_x = jx;
13795 game.exit_y = game.robot_wheel_y = jy;
13798 void ExitPlayer(struct PlayerInfo *player)
13800 DrawPlayer(player); // needed here only to cleanup last field
13801 RemovePlayer(player);
13803 if (game.players_still_needed > 0)
13804 game.players_still_needed--;
13807 static void SetFieldForSnapping(int x, int y, int element, int direction,
13808 int player_index_bit)
13810 struct ElementInfo *ei = &element_info[element];
13811 int direction_bit = MV_DIR_TO_BIT(direction);
13812 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13813 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13814 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13816 Tile[x][y] = EL_ELEMENT_SNAPPING;
13817 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13818 MovDir[x][y] = direction;
13819 Store[x][y] = element;
13820 Store2[x][y] = player_index_bit;
13822 ResetGfxAnimation(x, y);
13824 GfxElement[x][y] = element;
13825 GfxAction[x][y] = action;
13826 GfxDir[x][y] = direction;
13827 GfxFrame[x][y] = -1;
13830 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
13831 int player_index_bit)
13833 TestIfElementTouchesCustomElement(x, y); // for empty space
13835 if (level.finish_dig_collect)
13837 int dig_side = MV_DIR_OPPOSITE(direction);
13839 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13840 player_index_bit, dig_side);
13845 =============================================================================
13846 checkDiagonalPushing()
13847 -----------------------------------------------------------------------------
13848 check if diagonal input device direction results in pushing of object
13849 (by checking if the alternative direction is walkable, diggable, ...)
13850 =============================================================================
13853 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13854 int x, int y, int real_dx, int real_dy)
13856 int jx, jy, dx, dy, xx, yy;
13858 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13861 // diagonal direction: check alternative direction
13866 xx = jx + (dx == 0 ? real_dx : 0);
13867 yy = jy + (dy == 0 ? real_dy : 0);
13869 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13873 =============================================================================
13875 -----------------------------------------------------------------------------
13876 x, y: field next to player (non-diagonal) to try to dig to
13877 real_dx, real_dy: direction as read from input device (can be diagonal)
13878 =============================================================================
13881 static int DigField(struct PlayerInfo *player,
13882 int oldx, int oldy, int x, int y,
13883 int real_dx, int real_dy, int mode)
13885 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13886 boolean player_was_pushing = player->is_pushing;
13887 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13888 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13889 int jx = oldx, jy = oldy;
13890 int dx = x - jx, dy = y - jy;
13891 int nextx = x + dx, nexty = y + dy;
13892 int move_direction = (dx == -1 ? MV_LEFT :
13893 dx == +1 ? MV_RIGHT :
13895 dy == +1 ? MV_DOWN : MV_NONE);
13896 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13897 int dig_side = MV_DIR_OPPOSITE(move_direction);
13898 int old_element = Tile[jx][jy];
13899 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13902 if (is_player) // function can also be called by EL_PENGUIN
13904 if (player->MovPos == 0)
13906 player->is_digging = FALSE;
13907 player->is_collecting = FALSE;
13910 if (player->MovPos == 0) // last pushing move finished
13911 player->is_pushing = FALSE;
13913 if (mode == DF_NO_PUSH) // player just stopped pushing
13915 player->is_switching = FALSE;
13916 player->push_delay = -1;
13918 return MP_NO_ACTION;
13922 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13923 old_element = Back[jx][jy];
13925 // in case of element dropped at player position, check background
13926 else if (Back[jx][jy] != EL_EMPTY &&
13927 game.engine_version >= VERSION_IDENT(2,2,0,0))
13928 old_element = Back[jx][jy];
13930 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13931 return MP_NO_ACTION; // field has no opening in this direction
13933 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13934 return MP_NO_ACTION; // field has no opening in this direction
13936 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13940 Tile[jx][jy] = player->artwork_element;
13941 InitMovingField(jx, jy, MV_DOWN);
13942 Store[jx][jy] = EL_ACID;
13943 ContinueMoving(jx, jy);
13944 BuryPlayer(player);
13946 return MP_DONT_RUN_INTO;
13949 if (player_can_move && DONT_RUN_INTO(element))
13951 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13953 return MP_DONT_RUN_INTO;
13956 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13957 return MP_NO_ACTION;
13959 collect_count = element_info[element].collect_count_initial;
13961 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13962 return MP_NO_ACTION;
13964 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13965 player_can_move = player_can_move_or_snap;
13967 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13968 game.engine_version >= VERSION_IDENT(2,2,0,0))
13970 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13971 player->index_bit, dig_side);
13972 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13973 player->index_bit, dig_side);
13975 if (element == EL_DC_LANDMINE)
13978 if (Tile[x][y] != element) // field changed by snapping
13981 return MP_NO_ACTION;
13984 if (player->gravity && is_player && !player->is_auto_moving &&
13985 canFallDown(player) && move_direction != MV_DOWN &&
13986 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13987 return MP_NO_ACTION; // player cannot walk here due to gravity
13989 if (player_can_move &&
13990 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13992 int sound_element = SND_ELEMENT(element);
13993 int sound_action = ACTION_WALKING;
13995 if (IS_RND_GATE(element))
13997 if (!player->key[RND_GATE_NR(element)])
13998 return MP_NO_ACTION;
14000 else if (IS_RND_GATE_GRAY(element))
14002 if (!player->key[RND_GATE_GRAY_NR(element)])
14003 return MP_NO_ACTION;
14005 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14007 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14008 return MP_NO_ACTION;
14010 else if (element == EL_EXIT_OPEN ||
14011 element == EL_EM_EXIT_OPEN ||
14012 element == EL_EM_EXIT_OPENING ||
14013 element == EL_STEEL_EXIT_OPEN ||
14014 element == EL_EM_STEEL_EXIT_OPEN ||
14015 element == EL_EM_STEEL_EXIT_OPENING ||
14016 element == EL_SP_EXIT_OPEN ||
14017 element == EL_SP_EXIT_OPENING)
14019 sound_action = ACTION_PASSING; // player is passing exit
14021 else if (element == EL_EMPTY)
14023 sound_action = ACTION_MOVING; // nothing to walk on
14026 // play sound from background or player, whatever is available
14027 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14028 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14030 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14032 else if (player_can_move &&
14033 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14035 if (!ACCESS_FROM(element, opposite_direction))
14036 return MP_NO_ACTION; // field not accessible from this direction
14038 if (CAN_MOVE(element)) // only fixed elements can be passed!
14039 return MP_NO_ACTION;
14041 if (IS_EM_GATE(element))
14043 if (!player->key[EM_GATE_NR(element)])
14044 return MP_NO_ACTION;
14046 else if (IS_EM_GATE_GRAY(element))
14048 if (!player->key[EM_GATE_GRAY_NR(element)])
14049 return MP_NO_ACTION;
14051 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14053 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14054 return MP_NO_ACTION;
14056 else if (IS_EMC_GATE(element))
14058 if (!player->key[EMC_GATE_NR(element)])
14059 return MP_NO_ACTION;
14061 else if (IS_EMC_GATE_GRAY(element))
14063 if (!player->key[EMC_GATE_GRAY_NR(element)])
14064 return MP_NO_ACTION;
14066 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14068 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14069 return MP_NO_ACTION;
14071 else if (element == EL_DC_GATE_WHITE ||
14072 element == EL_DC_GATE_WHITE_GRAY ||
14073 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14075 if (player->num_white_keys == 0)
14076 return MP_NO_ACTION;
14078 player->num_white_keys--;
14080 else if (IS_SP_PORT(element))
14082 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14083 element == EL_SP_GRAVITY_PORT_RIGHT ||
14084 element == EL_SP_GRAVITY_PORT_UP ||
14085 element == EL_SP_GRAVITY_PORT_DOWN)
14086 player->gravity = !player->gravity;
14087 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14088 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14089 element == EL_SP_GRAVITY_ON_PORT_UP ||
14090 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14091 player->gravity = TRUE;
14092 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14093 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14094 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14095 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14096 player->gravity = FALSE;
14099 // automatically move to the next field with double speed
14100 player->programmed_action = move_direction;
14102 if (player->move_delay_reset_counter == 0)
14104 player->move_delay_reset_counter = 2; // two double speed steps
14106 DOUBLE_PLAYER_SPEED(player);
14109 PlayLevelSoundAction(x, y, ACTION_PASSING);
14111 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14115 if (mode != DF_SNAP)
14117 GfxElement[x][y] = GFX_ELEMENT(element);
14118 player->is_digging = TRUE;
14121 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14123 // use old behaviour for old levels (digging)
14124 if (!level.finish_dig_collect)
14126 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14127 player->index_bit, dig_side);
14129 // if digging triggered player relocation, finish digging tile
14130 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14131 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14134 if (mode == DF_SNAP)
14136 if (level.block_snap_field)
14137 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14139 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14141 // use old behaviour for old levels (snapping)
14142 if (!level.finish_dig_collect)
14143 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14144 player->index_bit, dig_side);
14147 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14151 if (is_player && mode != DF_SNAP)
14153 GfxElement[x][y] = element;
14154 player->is_collecting = TRUE;
14157 if (element == EL_SPEED_PILL)
14159 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14161 else if (element == EL_EXTRA_TIME && level.time > 0)
14163 TimeLeft += level.extra_time;
14165 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14167 DisplayGameControlValues();
14169 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14171 player->shield_normal_time_left += level.shield_normal_time;
14172 if (element == EL_SHIELD_DEADLY)
14173 player->shield_deadly_time_left += level.shield_deadly_time;
14175 else if (element == EL_DYNAMITE ||
14176 element == EL_EM_DYNAMITE ||
14177 element == EL_SP_DISK_RED)
14179 if (player->inventory_size < MAX_INVENTORY_SIZE)
14180 player->inventory_element[player->inventory_size++] = element;
14182 DrawGameDoorValues();
14184 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14186 player->dynabomb_count++;
14187 player->dynabombs_left++;
14189 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14191 player->dynabomb_size++;
14193 else if (element == EL_DYNABOMB_INCREASE_POWER)
14195 player->dynabomb_xl = TRUE;
14197 else if (IS_KEY(element))
14199 player->key[KEY_NR(element)] = TRUE;
14201 DrawGameDoorValues();
14203 else if (element == EL_DC_KEY_WHITE)
14205 player->num_white_keys++;
14207 // display white keys?
14208 // DrawGameDoorValues();
14210 else if (IS_ENVELOPE(element))
14212 player->show_envelope = element;
14214 else if (element == EL_EMC_LENSES)
14216 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14218 RedrawAllInvisibleElementsForLenses();
14220 else if (element == EL_EMC_MAGNIFIER)
14222 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14224 RedrawAllInvisibleElementsForMagnifier();
14226 else if (IS_DROPPABLE(element) ||
14227 IS_THROWABLE(element)) // can be collected and dropped
14231 if (collect_count == 0)
14232 player->inventory_infinite_element = element;
14234 for (i = 0; i < collect_count; i++)
14235 if (player->inventory_size < MAX_INVENTORY_SIZE)
14236 player->inventory_element[player->inventory_size++] = element;
14238 DrawGameDoorValues();
14240 else if (collect_count > 0)
14242 game.gems_still_needed -= collect_count;
14243 if (game.gems_still_needed < 0)
14244 game.gems_still_needed = 0;
14246 game.snapshot.collected_item = TRUE;
14248 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14250 DisplayGameControlValues();
14253 RaiseScoreElement(element);
14254 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14256 // use old behaviour for old levels (collecting)
14257 if (!level.finish_dig_collect && is_player)
14259 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14260 player->index_bit, dig_side);
14262 // if collecting triggered player relocation, finish collecting tile
14263 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14264 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14267 if (mode == DF_SNAP)
14269 if (level.block_snap_field)
14270 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14272 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14274 // use old behaviour for old levels (snapping)
14275 if (!level.finish_dig_collect)
14276 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14277 player->index_bit, dig_side);
14280 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14282 if (mode == DF_SNAP && element != EL_BD_ROCK)
14283 return MP_NO_ACTION;
14285 if (CAN_FALL(element) && dy)
14286 return MP_NO_ACTION;
14288 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14289 !(element == EL_SPRING && level.use_spring_bug))
14290 return MP_NO_ACTION;
14292 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14293 ((move_direction & MV_VERTICAL &&
14294 ((element_info[element].move_pattern & MV_LEFT &&
14295 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14296 (element_info[element].move_pattern & MV_RIGHT &&
14297 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14298 (move_direction & MV_HORIZONTAL &&
14299 ((element_info[element].move_pattern & MV_UP &&
14300 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14301 (element_info[element].move_pattern & MV_DOWN &&
14302 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14303 return MP_NO_ACTION;
14305 // do not push elements already moving away faster than player
14306 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14307 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14308 return MP_NO_ACTION;
14310 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14312 if (player->push_delay_value == -1 || !player_was_pushing)
14313 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14315 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14317 if (player->push_delay_value == -1)
14318 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14320 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14322 if (!player->is_pushing)
14323 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14326 player->is_pushing = TRUE;
14327 player->is_active = TRUE;
14329 if (!(IN_LEV_FIELD(nextx, nexty) &&
14330 (IS_FREE(nextx, nexty) ||
14331 (IS_SB_ELEMENT(element) &&
14332 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14333 (IS_CUSTOM_ELEMENT(element) &&
14334 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14335 return MP_NO_ACTION;
14337 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14338 return MP_NO_ACTION;
14340 if (player->push_delay == -1) // new pushing; restart delay
14341 player->push_delay = 0;
14343 if (player->push_delay < player->push_delay_value &&
14344 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14345 element != EL_SPRING && element != EL_BALLOON)
14347 // make sure that there is no move delay before next try to push
14348 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14349 player->move_delay = 0;
14351 return MP_NO_ACTION;
14354 if (IS_CUSTOM_ELEMENT(element) &&
14355 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14357 if (!DigFieldByCE(nextx, nexty, element))
14358 return MP_NO_ACTION;
14361 if (IS_SB_ELEMENT(element))
14363 boolean sokoban_task_solved = FALSE;
14365 if (element == EL_SOKOBAN_FIELD_FULL)
14367 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14369 IncrementSokobanFieldsNeeded();
14370 IncrementSokobanObjectsNeeded();
14373 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14375 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14377 DecrementSokobanFieldsNeeded();
14378 DecrementSokobanObjectsNeeded();
14380 // sokoban object was pushed from empty field to sokoban field
14381 if (Back[x][y] == EL_EMPTY)
14382 sokoban_task_solved = TRUE;
14385 Tile[x][y] = EL_SOKOBAN_OBJECT;
14387 if (Back[x][y] == Back[nextx][nexty])
14388 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14389 else if (Back[x][y] != 0)
14390 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14393 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14396 if (sokoban_task_solved &&
14397 game.sokoban_fields_still_needed == 0 &&
14398 game.sokoban_objects_still_needed == 0 &&
14399 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14401 game.players_still_needed = 0;
14405 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14409 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14411 InitMovingField(x, y, move_direction);
14412 GfxAction[x][y] = ACTION_PUSHING;
14414 if (mode == DF_SNAP)
14415 ContinueMoving(x, y);
14417 MovPos[x][y] = (dx != 0 ? dx : dy);
14419 Pushed[x][y] = TRUE;
14420 Pushed[nextx][nexty] = TRUE;
14422 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14423 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14425 player->push_delay_value = -1; // get new value later
14427 // check for element change _after_ element has been pushed
14428 if (game.use_change_when_pushing_bug)
14430 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14431 player->index_bit, dig_side);
14432 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14433 player->index_bit, dig_side);
14436 else if (IS_SWITCHABLE(element))
14438 if (PLAYER_SWITCHING(player, x, y))
14440 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14441 player->index_bit, dig_side);
14446 player->is_switching = TRUE;
14447 player->switch_x = x;
14448 player->switch_y = y;
14450 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14452 if (element == EL_ROBOT_WHEEL)
14454 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14456 game.robot_wheel_x = x;
14457 game.robot_wheel_y = y;
14458 game.robot_wheel_active = TRUE;
14460 TEST_DrawLevelField(x, y);
14462 else if (element == EL_SP_TERMINAL)
14466 SCAN_PLAYFIELD(xx, yy)
14468 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14472 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14474 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14476 ResetGfxAnimation(xx, yy);
14477 TEST_DrawLevelField(xx, yy);
14481 else if (IS_BELT_SWITCH(element))
14483 ToggleBeltSwitch(x, y);
14485 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14486 element == EL_SWITCHGATE_SWITCH_DOWN ||
14487 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14488 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14490 ToggleSwitchgateSwitch(x, y);
14492 else if (element == EL_LIGHT_SWITCH ||
14493 element == EL_LIGHT_SWITCH_ACTIVE)
14495 ToggleLightSwitch(x, y);
14497 else if (element == EL_TIMEGATE_SWITCH ||
14498 element == EL_DC_TIMEGATE_SWITCH)
14500 ActivateTimegateSwitch(x, y);
14502 else if (element == EL_BALLOON_SWITCH_LEFT ||
14503 element == EL_BALLOON_SWITCH_RIGHT ||
14504 element == EL_BALLOON_SWITCH_UP ||
14505 element == EL_BALLOON_SWITCH_DOWN ||
14506 element == EL_BALLOON_SWITCH_NONE ||
14507 element == EL_BALLOON_SWITCH_ANY)
14509 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14510 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14511 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14512 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14513 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14516 else if (element == EL_LAMP)
14518 Tile[x][y] = EL_LAMP_ACTIVE;
14519 game.lights_still_needed--;
14521 ResetGfxAnimation(x, y);
14522 TEST_DrawLevelField(x, y);
14524 else if (element == EL_TIME_ORB_FULL)
14526 Tile[x][y] = EL_TIME_ORB_EMPTY;
14528 if (level.time > 0 || level.use_time_orb_bug)
14530 TimeLeft += level.time_orb_time;
14531 game.no_time_limit = FALSE;
14533 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14535 DisplayGameControlValues();
14538 ResetGfxAnimation(x, y);
14539 TEST_DrawLevelField(x, y);
14541 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14542 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14546 game.ball_active = !game.ball_active;
14548 SCAN_PLAYFIELD(xx, yy)
14550 int e = Tile[xx][yy];
14552 if (game.ball_active)
14554 if (e == EL_EMC_MAGIC_BALL)
14555 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14556 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14557 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14561 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14562 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14563 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14564 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14569 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14570 player->index_bit, dig_side);
14572 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14573 player->index_bit, dig_side);
14575 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14576 player->index_bit, dig_side);
14582 if (!PLAYER_SWITCHING(player, x, y))
14584 player->is_switching = TRUE;
14585 player->switch_x = x;
14586 player->switch_y = y;
14588 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14589 player->index_bit, dig_side);
14590 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14591 player->index_bit, dig_side);
14593 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14594 player->index_bit, dig_side);
14595 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14596 player->index_bit, dig_side);
14599 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14600 player->index_bit, dig_side);
14601 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14602 player->index_bit, dig_side);
14604 return MP_NO_ACTION;
14607 player->push_delay = -1;
14609 if (is_player) // function can also be called by EL_PENGUIN
14611 if (Tile[x][y] != element) // really digged/collected something
14613 player->is_collecting = !player->is_digging;
14614 player->is_active = TRUE;
14616 player->last_removed_element = element;
14623 static boolean DigFieldByCE(int x, int y, int digging_element)
14625 int element = Tile[x][y];
14627 if (!IS_FREE(x, y))
14629 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14630 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14633 // no element can dig solid indestructible elements
14634 if (IS_INDESTRUCTIBLE(element) &&
14635 !IS_DIGGABLE(element) &&
14636 !IS_COLLECTIBLE(element))
14639 if (AmoebaNr[x][y] &&
14640 (element == EL_AMOEBA_FULL ||
14641 element == EL_BD_AMOEBA ||
14642 element == EL_AMOEBA_GROWING))
14644 AmoebaCnt[AmoebaNr[x][y]]--;
14645 AmoebaCnt2[AmoebaNr[x][y]]--;
14648 if (IS_MOVING(x, y))
14649 RemoveMovingField(x, y);
14653 TEST_DrawLevelField(x, y);
14656 // if digged element was about to explode, prevent the explosion
14657 ExplodeField[x][y] = EX_TYPE_NONE;
14659 PlayLevelSoundAction(x, y, action);
14662 Store[x][y] = EL_EMPTY;
14664 // this makes it possible to leave the removed element again
14665 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14666 Store[x][y] = element;
14671 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14673 int jx = player->jx, jy = player->jy;
14674 int x = jx + dx, y = jy + dy;
14675 int snap_direction = (dx == -1 ? MV_LEFT :
14676 dx == +1 ? MV_RIGHT :
14678 dy == +1 ? MV_DOWN : MV_NONE);
14679 boolean can_continue_snapping = (level.continuous_snapping &&
14680 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14682 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14685 if (!player->active || !IN_LEV_FIELD(x, y))
14693 if (player->MovPos == 0)
14694 player->is_pushing = FALSE;
14696 player->is_snapping = FALSE;
14698 if (player->MovPos == 0)
14700 player->is_moving = FALSE;
14701 player->is_digging = FALSE;
14702 player->is_collecting = FALSE;
14708 // prevent snapping with already pressed snap key when not allowed
14709 if (player->is_snapping && !can_continue_snapping)
14712 player->MovDir = snap_direction;
14714 if (player->MovPos == 0)
14716 player->is_moving = FALSE;
14717 player->is_digging = FALSE;
14718 player->is_collecting = FALSE;
14721 player->is_dropping = FALSE;
14722 player->is_dropping_pressed = FALSE;
14723 player->drop_pressed_delay = 0;
14725 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14728 player->is_snapping = TRUE;
14729 player->is_active = TRUE;
14731 if (player->MovPos == 0)
14733 player->is_moving = FALSE;
14734 player->is_digging = FALSE;
14735 player->is_collecting = FALSE;
14738 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14739 TEST_DrawLevelField(player->last_jx, player->last_jy);
14741 TEST_DrawLevelField(x, y);
14746 static boolean DropElement(struct PlayerInfo *player)
14748 int old_element, new_element;
14749 int dropx = player->jx, dropy = player->jy;
14750 int drop_direction = player->MovDir;
14751 int drop_side = drop_direction;
14752 int drop_element = get_next_dropped_element(player);
14754 /* do not drop an element on top of another element; when holding drop key
14755 pressed without moving, dropped element must move away before the next
14756 element can be dropped (this is especially important if the next element
14757 is dynamite, which can be placed on background for historical reasons) */
14758 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14761 if (IS_THROWABLE(drop_element))
14763 dropx += GET_DX_FROM_DIR(drop_direction);
14764 dropy += GET_DY_FROM_DIR(drop_direction);
14766 if (!IN_LEV_FIELD(dropx, dropy))
14770 old_element = Tile[dropx][dropy]; // old element at dropping position
14771 new_element = drop_element; // default: no change when dropping
14773 // check if player is active, not moving and ready to drop
14774 if (!player->active || player->MovPos || player->drop_delay > 0)
14777 // check if player has anything that can be dropped
14778 if (new_element == EL_UNDEFINED)
14781 // only set if player has anything that can be dropped
14782 player->is_dropping_pressed = TRUE;
14784 // check if drop key was pressed long enough for EM style dynamite
14785 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14788 // check if anything can be dropped at the current position
14789 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14792 // collected custom elements can only be dropped on empty fields
14793 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14796 if (old_element != EL_EMPTY)
14797 Back[dropx][dropy] = old_element; // store old element on this field
14799 ResetGfxAnimation(dropx, dropy);
14800 ResetRandomAnimationValue(dropx, dropy);
14802 if (player->inventory_size > 0 ||
14803 player->inventory_infinite_element != EL_UNDEFINED)
14805 if (player->inventory_size > 0)
14807 player->inventory_size--;
14809 DrawGameDoorValues();
14811 if (new_element == EL_DYNAMITE)
14812 new_element = EL_DYNAMITE_ACTIVE;
14813 else if (new_element == EL_EM_DYNAMITE)
14814 new_element = EL_EM_DYNAMITE_ACTIVE;
14815 else if (new_element == EL_SP_DISK_RED)
14816 new_element = EL_SP_DISK_RED_ACTIVE;
14819 Tile[dropx][dropy] = new_element;
14821 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14822 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14823 el2img(Tile[dropx][dropy]), 0);
14825 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14827 // needed if previous element just changed to "empty" in the last frame
14828 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14830 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14831 player->index_bit, drop_side);
14832 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14834 player->index_bit, drop_side);
14836 TestIfElementTouchesCustomElement(dropx, dropy);
14838 else // player is dropping a dyna bomb
14840 player->dynabombs_left--;
14842 Tile[dropx][dropy] = new_element;
14844 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14845 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14846 el2img(Tile[dropx][dropy]), 0);
14848 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14851 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14852 InitField_WithBug1(dropx, dropy, FALSE);
14854 new_element = Tile[dropx][dropy]; // element might have changed
14856 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14857 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14859 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14860 MovDir[dropx][dropy] = drop_direction;
14862 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14864 // do not cause impact style collision by dropping elements that can fall
14865 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14868 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14869 player->is_dropping = TRUE;
14871 player->drop_pressed_delay = 0;
14872 player->is_dropping_pressed = FALSE;
14874 player->drop_x = dropx;
14875 player->drop_y = dropy;
14880 // ----------------------------------------------------------------------------
14881 // game sound playing functions
14882 // ----------------------------------------------------------------------------
14884 static int *loop_sound_frame = NULL;
14885 static int *loop_sound_volume = NULL;
14887 void InitPlayLevelSound(void)
14889 int num_sounds = getSoundListSize();
14891 checked_free(loop_sound_frame);
14892 checked_free(loop_sound_volume);
14894 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14895 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14898 static void PlayLevelSound(int x, int y, int nr)
14900 int sx = SCREENX(x), sy = SCREENY(y);
14901 int volume, stereo_position;
14902 int max_distance = 8;
14903 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14905 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14906 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14909 if (!IN_LEV_FIELD(x, y) ||
14910 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14911 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14914 volume = SOUND_MAX_VOLUME;
14916 if (!IN_SCR_FIELD(sx, sy))
14918 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14919 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14921 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14924 stereo_position = (SOUND_MAX_LEFT +
14925 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14926 (SCR_FIELDX + 2 * max_distance));
14928 if (IS_LOOP_SOUND(nr))
14930 /* This assures that quieter loop sounds do not overwrite louder ones,
14931 while restarting sound volume comparison with each new game frame. */
14933 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14936 loop_sound_volume[nr] = volume;
14937 loop_sound_frame[nr] = FrameCounter;
14940 PlaySoundExt(nr, volume, stereo_position, type);
14943 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14945 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14946 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14947 y < LEVELY(BY1) ? LEVELY(BY1) :
14948 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14952 static void PlayLevelSoundAction(int x, int y, int action)
14954 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14957 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14959 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14961 if (sound_effect != SND_UNDEFINED)
14962 PlayLevelSound(x, y, sound_effect);
14965 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14968 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14970 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14971 PlayLevelSound(x, y, sound_effect);
14974 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14976 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14978 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14979 PlayLevelSound(x, y, sound_effect);
14982 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14984 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14986 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14987 StopSound(sound_effect);
14990 static int getLevelMusicNr(void)
14992 if (levelset.music[level_nr] != MUS_UNDEFINED)
14993 return levelset.music[level_nr]; // from config file
14995 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14998 static void FadeLevelSounds(void)
15003 static void FadeLevelMusic(void)
15005 int music_nr = getLevelMusicNr();
15006 char *curr_music = getCurrentlyPlayingMusicFilename();
15007 char *next_music = getMusicInfoEntryFilename(music_nr);
15009 if (!strEqual(curr_music, next_music))
15013 void FadeLevelSoundsAndMusic(void)
15019 static void PlayLevelMusic(void)
15021 int music_nr = getLevelMusicNr();
15022 char *curr_music = getCurrentlyPlayingMusicFilename();
15023 char *next_music = getMusicInfoEntryFilename(music_nr);
15025 if (!strEqual(curr_music, next_music))
15026 PlayMusicLoop(music_nr);
15029 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15031 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15033 int x = xx - offset;
15034 int y = yy - offset;
15039 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15043 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15047 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15051 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15055 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15059 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15063 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15066 case SOUND_android_clone:
15067 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15070 case SOUND_android_move:
15071 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15075 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15079 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15083 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15086 case SOUND_eater_eat:
15087 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15091 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15094 case SOUND_collect:
15095 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15098 case SOUND_diamond:
15099 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15103 // !!! CHECK THIS !!!
15105 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15107 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15111 case SOUND_wonderfall:
15112 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15116 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15120 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15124 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15128 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15132 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15136 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15140 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15144 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15147 case SOUND_exit_open:
15148 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15151 case SOUND_exit_leave:
15152 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15155 case SOUND_dynamite:
15156 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15160 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15164 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15168 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15172 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15176 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15180 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15184 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15189 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15191 int element = map_element_SP_to_RND(element_sp);
15192 int action = map_action_SP_to_RND(action_sp);
15193 int offset = (setup.sp_show_border_elements ? 0 : 1);
15194 int x = xx - offset;
15195 int y = yy - offset;
15197 PlayLevelSoundElementAction(x, y, element, action);
15200 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15202 int element = map_element_MM_to_RND(element_mm);
15203 int action = map_action_MM_to_RND(action_mm);
15205 int x = xx - offset;
15206 int y = yy - offset;
15208 if (!IS_MM_ELEMENT(element))
15209 element = EL_MM_DEFAULT;
15211 PlayLevelSoundElementAction(x, y, element, action);
15214 void PlaySound_MM(int sound_mm)
15216 int sound = map_sound_MM_to_RND(sound_mm);
15218 if (sound == SND_UNDEFINED)
15224 void PlaySoundLoop_MM(int sound_mm)
15226 int sound = map_sound_MM_to_RND(sound_mm);
15228 if (sound == SND_UNDEFINED)
15231 PlaySoundLoop(sound);
15234 void StopSound_MM(int sound_mm)
15236 int sound = map_sound_MM_to_RND(sound_mm);
15238 if (sound == SND_UNDEFINED)
15244 void RaiseScore(int value)
15246 game.score += value;
15248 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15250 DisplayGameControlValues();
15253 void RaiseScoreElement(int element)
15258 case EL_BD_DIAMOND:
15259 case EL_EMERALD_YELLOW:
15260 case EL_EMERALD_RED:
15261 case EL_EMERALD_PURPLE:
15262 case EL_SP_INFOTRON:
15263 RaiseScore(level.score[SC_EMERALD]);
15266 RaiseScore(level.score[SC_DIAMOND]);
15269 RaiseScore(level.score[SC_CRYSTAL]);
15272 RaiseScore(level.score[SC_PEARL]);
15275 case EL_BD_BUTTERFLY:
15276 case EL_SP_ELECTRON:
15277 RaiseScore(level.score[SC_BUG]);
15280 case EL_BD_FIREFLY:
15281 case EL_SP_SNIKSNAK:
15282 RaiseScore(level.score[SC_SPACESHIP]);
15285 case EL_DARK_YAMYAM:
15286 RaiseScore(level.score[SC_YAMYAM]);
15289 RaiseScore(level.score[SC_ROBOT]);
15292 RaiseScore(level.score[SC_PACMAN]);
15295 RaiseScore(level.score[SC_NUT]);
15298 case EL_EM_DYNAMITE:
15299 case EL_SP_DISK_RED:
15300 case EL_DYNABOMB_INCREASE_NUMBER:
15301 case EL_DYNABOMB_INCREASE_SIZE:
15302 case EL_DYNABOMB_INCREASE_POWER:
15303 RaiseScore(level.score[SC_DYNAMITE]);
15305 case EL_SHIELD_NORMAL:
15306 case EL_SHIELD_DEADLY:
15307 RaiseScore(level.score[SC_SHIELD]);
15309 case EL_EXTRA_TIME:
15310 RaiseScore(level.extra_time_score);
15324 case EL_DC_KEY_WHITE:
15325 RaiseScore(level.score[SC_KEY]);
15328 RaiseScore(element_info[element].collect_score);
15333 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15335 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15337 // closing door required in case of envelope style request dialogs
15340 // prevent short reactivation of overlay buttons while closing door
15341 SetOverlayActive(FALSE);
15343 CloseDoor(DOOR_CLOSE_1);
15346 if (network.enabled)
15347 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15351 FadeSkipNextFadeIn();
15353 SetGameStatus(GAME_MODE_MAIN);
15358 else // continue playing the game
15360 if (tape.playing && tape.deactivate_display)
15361 TapeDeactivateDisplayOff(TRUE);
15363 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15365 if (tape.playing && tape.deactivate_display)
15366 TapeDeactivateDisplayOn();
15370 void RequestQuitGame(boolean ask_if_really_quit)
15372 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15373 boolean skip_request = game.all_players_gone || quick_quit;
15375 RequestQuitGameExt(skip_request, quick_quit,
15376 "Do you really want to quit the game?");
15379 void RequestRestartGame(char *message)
15381 game.restart_game_message = NULL;
15383 boolean has_started_game = hasStartedNetworkGame();
15384 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15386 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15388 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15392 // needed in case of envelope request to close game panel
15393 CloseDoor(DOOR_CLOSE_1);
15395 SetGameStatus(GAME_MODE_MAIN);
15401 void CheckGameOver(void)
15403 static boolean last_game_over = FALSE;
15404 static int game_over_delay = 0;
15405 int game_over_delay_value = 50;
15406 boolean game_over = checkGameFailed();
15408 // do not handle game over if request dialog is already active
15409 if (game.request_active)
15412 // do not ask to play again if game was never actually played
15413 if (!game.GamePlayed)
15418 last_game_over = FALSE;
15419 game_over_delay = game_over_delay_value;
15424 if (game_over_delay > 0)
15431 if (last_game_over != game_over)
15432 game.restart_game_message = (hasStartedNetworkGame() ?
15433 "Game over! Play it again?" :
15436 last_game_over = game_over;
15439 boolean checkGameSolved(void)
15441 // set for all game engines if level was solved
15442 return game.LevelSolved_GameEnd;
15445 boolean checkGameFailed(void)
15447 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15448 return (game_em.game_over && !game_em.level_solved);
15449 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15450 return (game_sp.game_over && !game_sp.level_solved);
15451 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15452 return (game_mm.game_over && !game_mm.level_solved);
15453 else // GAME_ENGINE_TYPE_RND
15454 return (game.GameOver && !game.LevelSolved);
15457 boolean checkGameEnded(void)
15459 return (checkGameSolved() || checkGameFailed());
15463 // ----------------------------------------------------------------------------
15464 // random generator functions
15465 // ----------------------------------------------------------------------------
15467 unsigned int InitEngineRandom_RND(int seed)
15469 game.num_random_calls = 0;
15471 return InitEngineRandom(seed);
15474 unsigned int RND(int max)
15478 game.num_random_calls++;
15480 return GetEngineRandom(max);
15487 // ----------------------------------------------------------------------------
15488 // game engine snapshot handling functions
15489 // ----------------------------------------------------------------------------
15491 struct EngineSnapshotInfo
15493 // runtime values for custom element collect score
15494 int collect_score[NUM_CUSTOM_ELEMENTS];
15496 // runtime values for group element choice position
15497 int choice_pos[NUM_GROUP_ELEMENTS];
15499 // runtime values for belt position animations
15500 int belt_graphic[4][NUM_BELT_PARTS];
15501 int belt_anim_mode[4][NUM_BELT_PARTS];
15504 static struct EngineSnapshotInfo engine_snapshot_rnd;
15505 static char *snapshot_level_identifier = NULL;
15506 static int snapshot_level_nr = -1;
15508 static void SaveEngineSnapshotValues_RND(void)
15510 static int belt_base_active_element[4] =
15512 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15513 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15514 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15515 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15519 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15521 int element = EL_CUSTOM_START + i;
15523 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15526 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15528 int element = EL_GROUP_START + i;
15530 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15533 for (i = 0; i < 4; i++)
15535 for (j = 0; j < NUM_BELT_PARTS; j++)
15537 int element = belt_base_active_element[i] + j;
15538 int graphic = el2img(element);
15539 int anim_mode = graphic_info[graphic].anim_mode;
15541 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15542 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15547 static void LoadEngineSnapshotValues_RND(void)
15549 unsigned int num_random_calls = game.num_random_calls;
15552 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15554 int element = EL_CUSTOM_START + i;
15556 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15559 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15561 int element = EL_GROUP_START + i;
15563 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15566 for (i = 0; i < 4; i++)
15568 for (j = 0; j < NUM_BELT_PARTS; j++)
15570 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15571 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15573 graphic_info[graphic].anim_mode = anim_mode;
15577 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15579 InitRND(tape.random_seed);
15580 for (i = 0; i < num_random_calls; i++)
15584 if (game.num_random_calls != num_random_calls)
15586 Error("number of random calls out of sync");
15587 Error("number of random calls should be %d", num_random_calls);
15588 Error("number of random calls is %d", game.num_random_calls);
15590 Fail("this should not happen -- please debug");
15594 void FreeEngineSnapshotSingle(void)
15596 FreeSnapshotSingle();
15598 setString(&snapshot_level_identifier, NULL);
15599 snapshot_level_nr = -1;
15602 void FreeEngineSnapshotList(void)
15604 FreeSnapshotList();
15607 static ListNode *SaveEngineSnapshotBuffers(void)
15609 ListNode *buffers = NULL;
15611 // copy some special values to a structure better suited for the snapshot
15613 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15614 SaveEngineSnapshotValues_RND();
15615 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15616 SaveEngineSnapshotValues_EM();
15617 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15618 SaveEngineSnapshotValues_SP(&buffers);
15619 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15620 SaveEngineSnapshotValues_MM(&buffers);
15622 // save values stored in special snapshot structure
15624 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15625 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15626 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15627 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15628 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15629 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15630 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15631 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15633 // save further RND engine values
15635 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15636 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15637 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15639 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15640 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15641 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15642 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15643 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15645 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15646 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15647 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15649 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15651 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15652 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15654 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15655 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15656 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15657 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15658 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15659 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15660 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15661 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15662 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15663 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15664 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15665 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15666 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15667 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15668 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15669 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15670 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15671 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15673 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15674 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15676 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15677 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15678 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15680 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15681 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15683 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15684 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15685 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15686 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15687 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15689 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15690 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15693 ListNode *node = engine_snapshot_list_rnd;
15696 while (node != NULL)
15698 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15703 Debug("game:playing:SaveEngineSnapshotBuffers",
15704 "size of engine snapshot: %d bytes", num_bytes);
15710 void SaveEngineSnapshotSingle(void)
15712 ListNode *buffers = SaveEngineSnapshotBuffers();
15714 // finally save all snapshot buffers to single snapshot
15715 SaveSnapshotSingle(buffers);
15717 // save level identification information
15718 setString(&snapshot_level_identifier, leveldir_current->identifier);
15719 snapshot_level_nr = level_nr;
15722 boolean CheckSaveEngineSnapshotToList(void)
15724 boolean save_snapshot =
15725 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15726 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15727 game.snapshot.changed_action) ||
15728 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15729 game.snapshot.collected_item));
15731 game.snapshot.changed_action = FALSE;
15732 game.snapshot.collected_item = FALSE;
15733 game.snapshot.save_snapshot = save_snapshot;
15735 return save_snapshot;
15738 void SaveEngineSnapshotToList(void)
15740 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15744 ListNode *buffers = SaveEngineSnapshotBuffers();
15746 // finally save all snapshot buffers to snapshot list
15747 SaveSnapshotToList(buffers);
15750 void SaveEngineSnapshotToListInitial(void)
15752 FreeEngineSnapshotList();
15754 SaveEngineSnapshotToList();
15757 static void LoadEngineSnapshotValues(void)
15759 // restore special values from snapshot structure
15761 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15762 LoadEngineSnapshotValues_RND();
15763 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15764 LoadEngineSnapshotValues_EM();
15765 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15766 LoadEngineSnapshotValues_SP();
15767 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15768 LoadEngineSnapshotValues_MM();
15771 void LoadEngineSnapshotSingle(void)
15773 LoadSnapshotSingle();
15775 LoadEngineSnapshotValues();
15778 static void LoadEngineSnapshot_Undo(int steps)
15780 LoadSnapshotFromList_Older(steps);
15782 LoadEngineSnapshotValues();
15785 static void LoadEngineSnapshot_Redo(int steps)
15787 LoadSnapshotFromList_Newer(steps);
15789 LoadEngineSnapshotValues();
15792 boolean CheckEngineSnapshotSingle(void)
15794 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15795 snapshot_level_nr == level_nr);
15798 boolean CheckEngineSnapshotList(void)
15800 return CheckSnapshotList();
15804 // ---------- new game button stuff -------------------------------------------
15811 boolean *setup_value;
15812 boolean allowed_on_tape;
15813 boolean is_touch_button;
15815 } gamebutton_info[NUM_GAME_BUTTONS] =
15818 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15819 GAME_CTRL_ID_STOP, NULL,
15820 TRUE, FALSE, "stop game"
15823 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15824 GAME_CTRL_ID_PAUSE, NULL,
15825 TRUE, FALSE, "pause game"
15828 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15829 GAME_CTRL_ID_PLAY, NULL,
15830 TRUE, FALSE, "play game"
15833 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15834 GAME_CTRL_ID_UNDO, NULL,
15835 TRUE, FALSE, "undo step"
15838 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15839 GAME_CTRL_ID_REDO, NULL,
15840 TRUE, FALSE, "redo step"
15843 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15844 GAME_CTRL_ID_SAVE, NULL,
15845 TRUE, FALSE, "save game"
15848 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15849 GAME_CTRL_ID_PAUSE2, NULL,
15850 TRUE, FALSE, "pause game"
15853 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15854 GAME_CTRL_ID_LOAD, NULL,
15855 TRUE, FALSE, "load game"
15858 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15859 GAME_CTRL_ID_PANEL_STOP, NULL,
15860 FALSE, FALSE, "stop game"
15863 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15864 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15865 FALSE, FALSE, "pause game"
15868 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15869 GAME_CTRL_ID_PANEL_PLAY, NULL,
15870 FALSE, FALSE, "play game"
15873 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15874 GAME_CTRL_ID_TOUCH_STOP, NULL,
15875 FALSE, TRUE, "stop game"
15878 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15879 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15880 FALSE, TRUE, "pause game"
15883 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15884 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15885 TRUE, FALSE, "background music on/off"
15888 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15889 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15890 TRUE, FALSE, "sound loops on/off"
15893 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15894 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15895 TRUE, FALSE, "normal sounds on/off"
15898 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15899 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15900 FALSE, FALSE, "background music on/off"
15903 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15904 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15905 FALSE, FALSE, "sound loops on/off"
15908 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15909 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15910 FALSE, FALSE, "normal sounds on/off"
15914 void CreateGameButtons(void)
15918 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15920 int graphic = gamebutton_info[i].graphic;
15921 struct GraphicInfo *gfx = &graphic_info[graphic];
15922 struct XY *pos = gamebutton_info[i].pos;
15923 struct GadgetInfo *gi;
15926 unsigned int event_mask;
15927 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15928 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15929 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15930 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15931 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15932 int gd_x = gfx->src_x;
15933 int gd_y = gfx->src_y;
15934 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15935 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15936 int gd_xa = gfx->src_x + gfx->active_xoffset;
15937 int gd_ya = gfx->src_y + gfx->active_yoffset;
15938 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15939 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15940 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15941 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15944 if (gfx->bitmap == NULL)
15946 game_gadget[id] = NULL;
15951 if (id == GAME_CTRL_ID_STOP ||
15952 id == GAME_CTRL_ID_PANEL_STOP ||
15953 id == GAME_CTRL_ID_TOUCH_STOP ||
15954 id == GAME_CTRL_ID_PLAY ||
15955 id == GAME_CTRL_ID_PANEL_PLAY ||
15956 id == GAME_CTRL_ID_SAVE ||
15957 id == GAME_CTRL_ID_LOAD)
15959 button_type = GD_TYPE_NORMAL_BUTTON;
15961 event_mask = GD_EVENT_RELEASED;
15963 else if (id == GAME_CTRL_ID_UNDO ||
15964 id == GAME_CTRL_ID_REDO)
15966 button_type = GD_TYPE_NORMAL_BUTTON;
15968 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15972 button_type = GD_TYPE_CHECK_BUTTON;
15973 checked = (gamebutton_info[i].setup_value != NULL ?
15974 *gamebutton_info[i].setup_value : FALSE);
15975 event_mask = GD_EVENT_PRESSED;
15978 gi = CreateGadget(GDI_CUSTOM_ID, id,
15979 GDI_IMAGE_ID, graphic,
15980 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15983 GDI_WIDTH, gfx->width,
15984 GDI_HEIGHT, gfx->height,
15985 GDI_TYPE, button_type,
15986 GDI_STATE, GD_BUTTON_UNPRESSED,
15987 GDI_CHECKED, checked,
15988 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15989 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15990 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15991 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15992 GDI_DIRECT_DRAW, FALSE,
15993 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15994 GDI_EVENT_MASK, event_mask,
15995 GDI_CALLBACK_ACTION, HandleGameButtons,
15999 Fail("cannot create gadget");
16001 game_gadget[id] = gi;
16005 void FreeGameButtons(void)
16009 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16010 FreeGadget(game_gadget[i]);
16013 static void UnmapGameButtonsAtSamePosition(int id)
16017 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16019 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16020 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16021 UnmapGadget(game_gadget[i]);
16024 static void UnmapGameButtonsAtSamePosition_All(void)
16026 if (setup.show_snapshot_buttons)
16028 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16029 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16030 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16034 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16035 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16036 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16038 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16039 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16040 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16044 static void MapGameButtonsAtSamePosition(int id)
16048 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16050 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16051 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16052 MapGadget(game_gadget[i]);
16054 UnmapGameButtonsAtSamePosition_All();
16057 void MapUndoRedoButtons(void)
16059 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16060 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16062 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16063 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16066 void UnmapUndoRedoButtons(void)
16068 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16069 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16071 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16072 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16075 void ModifyPauseButtons(void)
16079 GAME_CTRL_ID_PAUSE,
16080 GAME_CTRL_ID_PAUSE2,
16081 GAME_CTRL_ID_PANEL_PAUSE,
16082 GAME_CTRL_ID_TOUCH_PAUSE,
16087 for (i = 0; ids[i] > -1; i++)
16088 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16091 static void MapGameButtonsExt(boolean on_tape)
16095 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16096 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16097 i != GAME_CTRL_ID_UNDO &&
16098 i != GAME_CTRL_ID_REDO)
16099 MapGadget(game_gadget[i]);
16101 UnmapGameButtonsAtSamePosition_All();
16103 RedrawGameButtons();
16106 static void UnmapGameButtonsExt(boolean on_tape)
16110 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16111 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16112 UnmapGadget(game_gadget[i]);
16115 static void RedrawGameButtonsExt(boolean on_tape)
16119 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16120 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16121 RedrawGadget(game_gadget[i]);
16124 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16129 gi->checked = state;
16132 static void RedrawSoundButtonGadget(int id)
16134 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16135 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16136 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16137 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16138 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16139 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16142 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16143 RedrawGadget(game_gadget[id2]);
16146 void MapGameButtons(void)
16148 MapGameButtonsExt(FALSE);
16151 void UnmapGameButtons(void)
16153 UnmapGameButtonsExt(FALSE);
16156 void RedrawGameButtons(void)
16158 RedrawGameButtonsExt(FALSE);
16161 void MapGameButtonsOnTape(void)
16163 MapGameButtonsExt(TRUE);
16166 void UnmapGameButtonsOnTape(void)
16168 UnmapGameButtonsExt(TRUE);
16171 void RedrawGameButtonsOnTape(void)
16173 RedrawGameButtonsExt(TRUE);
16176 static void GameUndoRedoExt(void)
16178 ClearPlayerAction();
16180 tape.pausing = TRUE;
16183 UpdateAndDisplayGameControlValues();
16185 DrawCompleteVideoDisplay();
16186 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16187 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16188 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16193 static void GameUndo(int steps)
16195 if (!CheckEngineSnapshotList())
16198 LoadEngineSnapshot_Undo(steps);
16203 static void GameRedo(int steps)
16205 if (!CheckEngineSnapshotList())
16208 LoadEngineSnapshot_Redo(steps);
16213 static void HandleGameButtonsExt(int id, int button)
16215 static boolean game_undo_executed = FALSE;
16216 int steps = BUTTON_STEPSIZE(button);
16217 boolean handle_game_buttons =
16218 (game_status == GAME_MODE_PLAYING ||
16219 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16221 if (!handle_game_buttons)
16226 case GAME_CTRL_ID_STOP:
16227 case GAME_CTRL_ID_PANEL_STOP:
16228 case GAME_CTRL_ID_TOUCH_STOP:
16229 if (game_status == GAME_MODE_MAIN)
16235 RequestQuitGame(TRUE);
16239 case GAME_CTRL_ID_PAUSE:
16240 case GAME_CTRL_ID_PAUSE2:
16241 case GAME_CTRL_ID_PANEL_PAUSE:
16242 case GAME_CTRL_ID_TOUCH_PAUSE:
16243 if (network.enabled && game_status == GAME_MODE_PLAYING)
16246 SendToServer_ContinuePlaying();
16248 SendToServer_PausePlaying();
16251 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16253 game_undo_executed = FALSE;
16257 case GAME_CTRL_ID_PLAY:
16258 case GAME_CTRL_ID_PANEL_PLAY:
16259 if (game_status == GAME_MODE_MAIN)
16261 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16263 else if (tape.pausing)
16265 if (network.enabled)
16266 SendToServer_ContinuePlaying();
16268 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16272 case GAME_CTRL_ID_UNDO:
16273 // Important: When using "save snapshot when collecting an item" mode,
16274 // load last (current) snapshot for first "undo" after pressing "pause"
16275 // (else the last-but-one snapshot would be loaded, because the snapshot
16276 // pointer already points to the last snapshot when pressing "pause",
16277 // which is fine for "every step/move" mode, but not for "every collect")
16278 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16279 !game_undo_executed)
16282 game_undo_executed = TRUE;
16287 case GAME_CTRL_ID_REDO:
16291 case GAME_CTRL_ID_SAVE:
16295 case GAME_CTRL_ID_LOAD:
16299 case SOUND_CTRL_ID_MUSIC:
16300 case SOUND_CTRL_ID_PANEL_MUSIC:
16301 if (setup.sound_music)
16303 setup.sound_music = FALSE;
16307 else if (audio.music_available)
16309 setup.sound = setup.sound_music = TRUE;
16311 SetAudioMode(setup.sound);
16313 if (game_status == GAME_MODE_PLAYING)
16317 RedrawSoundButtonGadget(id);
16321 case SOUND_CTRL_ID_LOOPS:
16322 case SOUND_CTRL_ID_PANEL_LOOPS:
16323 if (setup.sound_loops)
16324 setup.sound_loops = FALSE;
16325 else if (audio.loops_available)
16327 setup.sound = setup.sound_loops = TRUE;
16329 SetAudioMode(setup.sound);
16332 RedrawSoundButtonGadget(id);
16336 case SOUND_CTRL_ID_SIMPLE:
16337 case SOUND_CTRL_ID_PANEL_SIMPLE:
16338 if (setup.sound_simple)
16339 setup.sound_simple = FALSE;
16340 else if (audio.sound_available)
16342 setup.sound = setup.sound_simple = TRUE;
16344 SetAudioMode(setup.sound);
16347 RedrawSoundButtonGadget(id);
16356 static void HandleGameButtons(struct GadgetInfo *gi)
16358 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16361 void HandleSoundButtonKeys(Key key)
16363 if (key == setup.shortcut.sound_simple)
16364 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16365 else if (key == setup.shortcut.sound_loops)
16366 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16367 else if (key == setup.shortcut.sound_music)
16368 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);