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 float score, score_final; // needed for time score < 10 for 10 seconds
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;
4729 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4730 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4732 if (!game.LevelSolved_GameWon)
4736 // do not start end game actions before the player stops moving (to exit)
4737 if (local_player->active && local_player->MovPos)
4740 game.LevelSolved_GameWon = TRUE;
4741 game.LevelSolved_SaveTape = tape.recording;
4742 game.LevelSolved_SaveScore = !tape.playing;
4746 LevelStats_incSolved(level_nr);
4748 SaveLevelSetup_SeriesInfo();
4751 if (tape.auto_play) // tape might already be stopped here
4752 tape.auto_play_level_solved = TRUE;
4756 game_over_delay_1 = 0;
4757 game_over_delay_2 = 0;
4758 game_over_delay_3 = game_over_delay_value_3;
4760 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4761 score = score_final = game.score_final;
4762 health = health_final = game.health_final;
4766 int time_frames = 0;
4771 time_frames = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4773 else if (game.no_time_limit && TimePlayed < 999)
4776 time_frames = (999 - TimePlayed) * FRAMES_PER_SECOND - TimeFrames;
4779 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4781 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4783 game_over_delay_1 = game_over_delay_value_1;
4785 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4788 score_final += health * time_score;
4790 game_over_delay_2 = game_over_delay_value_2;
4793 game.score_final = score_final;
4794 game.health_final = health_final;
4797 if (level_editor_test_game)
4800 score = score_final;
4802 game.LevelSolved_CountingTime = time;
4803 game.LevelSolved_CountingScore = score;
4805 game_panel_controls[GAME_PANEL_TIME].value = time;
4806 game_panel_controls[GAME_PANEL_SCORE].value = score;
4808 DisplayGameControlValues();
4811 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4813 // check if last player has left the level
4814 if (game.exit_x >= 0 &&
4817 int x = game.exit_x;
4818 int y = game.exit_y;
4819 int element = Tile[x][y];
4821 // close exit door after last player
4822 if ((game.all_players_gone &&
4823 (element == EL_EXIT_OPEN ||
4824 element == EL_SP_EXIT_OPEN ||
4825 element == EL_STEEL_EXIT_OPEN)) ||
4826 element == EL_EM_EXIT_OPEN ||
4827 element == EL_EM_STEEL_EXIT_OPEN)
4831 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4832 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4833 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4834 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4835 EL_EM_STEEL_EXIT_CLOSING);
4837 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4840 // player disappears
4841 DrawLevelField(x, y);
4844 for (i = 0; i < MAX_PLAYERS; i++)
4846 struct PlayerInfo *player = &stored_player[i];
4848 if (player->present)
4850 RemovePlayer(player);
4852 // player disappears
4853 DrawLevelField(player->jx, player->jy);
4858 PlaySound(SND_GAME_WINNING);
4861 if (game_over_delay_1 > 0)
4863 game_over_delay_1--;
4868 if (time != time_final)
4870 int time_to_go = ABS(time_final - time);
4871 int time_count_dir = (time < time_final ? +1 : -1);
4873 if (time_to_go < time_count_steps)
4874 time_count_steps = 1;
4876 time += time_count_steps * time_count_dir;
4877 score += time_count_steps * time_score;
4879 // set final score to correct rounding differences after counting score
4880 if (time == time_final)
4881 score = score_final;
4883 game.LevelSolved_CountingTime = time;
4884 game.LevelSolved_CountingScore = score;
4886 game_panel_controls[GAME_PANEL_TIME].value = time;
4887 game_panel_controls[GAME_PANEL_SCORE].value = score;
4889 DisplayGameControlValues();
4891 if (time == time_final)
4892 StopSound(SND_GAME_LEVELTIME_BONUS);
4893 else if (setup.sound_loops)
4894 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4896 PlaySound(SND_GAME_LEVELTIME_BONUS);
4901 if (game_over_delay_2 > 0)
4903 game_over_delay_2--;
4908 if (health != health_final)
4910 int health_count_dir = (health < health_final ? +1 : -1);
4912 health += health_count_dir;
4913 score += time_score;
4915 game.LevelSolved_CountingHealth = health;
4916 game.LevelSolved_CountingScore = score;
4918 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4919 game_panel_controls[GAME_PANEL_SCORE].value = score;
4921 DisplayGameControlValues();
4923 if (health == health_final)
4924 StopSound(SND_GAME_LEVELTIME_BONUS);
4925 else if (setup.sound_loops)
4926 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4928 PlaySound(SND_GAME_LEVELTIME_BONUS);
4933 game.panel.active = FALSE;
4935 if (game_over_delay_3 > 0)
4937 game_over_delay_3--;
4947 // used instead of "level_nr" (needed for network games)
4948 int last_level_nr = levelset.level_nr;
4951 game.LevelSolved_GameEnd = TRUE;
4953 if (game.LevelSolved_SaveTape)
4955 // make sure that request dialog to save tape does not open door again
4956 if (!global.use_envelope_request)
4957 CloseDoor(DOOR_CLOSE_1);
4959 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4962 // if no tape is to be saved, close both doors simultaneously
4963 CloseDoor(DOOR_CLOSE_ALL);
4965 if (level_editor_test_game)
4967 SetGameStatus(GAME_MODE_MAIN);
4974 if (!game.LevelSolved_SaveScore)
4976 SetGameStatus(GAME_MODE_MAIN);
4983 if (level_nr == leveldir_current->handicap_level)
4985 leveldir_current->handicap_level++;
4987 SaveLevelSetup_SeriesInfo();
4990 if (setup.increment_levels &&
4991 level_nr < leveldir_current->last_level &&
4994 level_nr++; // advance to next level
4995 TapeErase(); // start with empty tape
4997 if (setup.auto_play_next_level)
4999 LoadLevel(level_nr);
5001 SaveLevelSetup_SeriesInfo();
5005 hi_pos = NewHiScore(last_level_nr);
5007 if (hi_pos >= 0 && !setup.skip_scores_after_game)
5009 SetGameStatus(GAME_MODE_SCORES);
5011 DrawHallOfFame(last_level_nr, hi_pos);
5013 else if (setup.auto_play_next_level && setup.increment_levels &&
5014 last_level_nr < leveldir_current->last_level &&
5017 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5021 SetGameStatus(GAME_MODE_MAIN);
5027 int NewHiScore(int level_nr)
5031 boolean one_score_entry_per_name = !program.many_scores_per_name;
5033 LoadScore(level_nr);
5035 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
5036 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
5039 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
5041 if (game.score_final > highscore[k].Score)
5043 // player has made it to the hall of fame
5045 if (k < MAX_SCORE_ENTRIES - 1)
5047 int m = MAX_SCORE_ENTRIES - 1;
5049 if (one_score_entry_per_name)
5051 for (l = k; l < MAX_SCORE_ENTRIES; l++)
5052 if (strEqual(setup.player_name, highscore[l].Name))
5055 if (m == k) // player's new highscore overwrites his old one
5059 for (l = m; l > k; l--)
5061 strcpy(highscore[l].Name, highscore[l - 1].Name);
5062 highscore[l].Score = highscore[l - 1].Score;
5068 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
5069 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
5070 highscore[k].Score = game.score_final;
5075 else if (one_score_entry_per_name &&
5076 !strncmp(setup.player_name, highscore[k].Name,
5077 MAX_PLAYER_NAME_LEN))
5078 break; // player already there with a higher score
5082 SaveScore(level_nr);
5087 static int getElementMoveStepsizeExt(int x, int y, int direction)
5089 int element = Tile[x][y];
5090 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5091 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5092 int horiz_move = (dx != 0);
5093 int sign = (horiz_move ? dx : dy);
5094 int step = sign * element_info[element].move_stepsize;
5096 // special values for move stepsize for spring and things on conveyor belt
5099 if (CAN_FALL(element) &&
5100 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5101 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5102 else if (element == EL_SPRING)
5103 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5109 static int getElementMoveStepsize(int x, int y)
5111 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5114 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5116 if (player->GfxAction != action || player->GfxDir != dir)
5118 player->GfxAction = action;
5119 player->GfxDir = dir;
5121 player->StepFrame = 0;
5125 static void ResetGfxFrame(int x, int y)
5127 // profiling showed that "autotest" spends 10~20% of its time in this function
5128 if (DrawingDeactivatedField())
5131 int element = Tile[x][y];
5132 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5134 if (graphic_info[graphic].anim_global_sync)
5135 GfxFrame[x][y] = FrameCounter;
5136 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5137 GfxFrame[x][y] = CustomValue[x][y];
5138 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5139 GfxFrame[x][y] = element_info[element].collect_score;
5140 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5141 GfxFrame[x][y] = ChangeDelay[x][y];
5144 static void ResetGfxAnimation(int x, int y)
5146 GfxAction[x][y] = ACTION_DEFAULT;
5147 GfxDir[x][y] = MovDir[x][y];
5150 ResetGfxFrame(x, y);
5153 static void ResetRandomAnimationValue(int x, int y)
5155 GfxRandom[x][y] = INIT_GFX_RANDOM();
5158 static void InitMovingField(int x, int y, int direction)
5160 int element = Tile[x][y];
5161 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5162 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5165 boolean is_moving_before, is_moving_after;
5167 // check if element was/is moving or being moved before/after mode change
5168 is_moving_before = (WasJustMoving[x][y] != 0);
5169 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5171 // reset animation only for moving elements which change direction of moving
5172 // or which just started or stopped moving
5173 // (else CEs with property "can move" / "not moving" are reset each frame)
5174 if (is_moving_before != is_moving_after ||
5175 direction != MovDir[x][y])
5176 ResetGfxAnimation(x, y);
5178 MovDir[x][y] = direction;
5179 GfxDir[x][y] = direction;
5181 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5182 direction == MV_DOWN && CAN_FALL(element) ?
5183 ACTION_FALLING : ACTION_MOVING);
5185 // this is needed for CEs with property "can move" / "not moving"
5187 if (is_moving_after)
5189 if (Tile[newx][newy] == EL_EMPTY)
5190 Tile[newx][newy] = EL_BLOCKED;
5192 MovDir[newx][newy] = MovDir[x][y];
5194 CustomValue[newx][newy] = CustomValue[x][y];
5196 GfxFrame[newx][newy] = GfxFrame[x][y];
5197 GfxRandom[newx][newy] = GfxRandom[x][y];
5198 GfxAction[newx][newy] = GfxAction[x][y];
5199 GfxDir[newx][newy] = GfxDir[x][y];
5203 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5205 int direction = MovDir[x][y];
5206 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5207 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5213 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5215 int oldx = x, oldy = y;
5216 int direction = MovDir[x][y];
5218 if (direction == MV_LEFT)
5220 else if (direction == MV_RIGHT)
5222 else if (direction == MV_UP)
5224 else if (direction == MV_DOWN)
5227 *comes_from_x = oldx;
5228 *comes_from_y = oldy;
5231 static int MovingOrBlocked2Element(int x, int y)
5233 int element = Tile[x][y];
5235 if (element == EL_BLOCKED)
5239 Blocked2Moving(x, y, &oldx, &oldy);
5240 return Tile[oldx][oldy];
5246 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5248 // like MovingOrBlocked2Element(), but if element is moving
5249 // and (x,y) is the field the moving element is just leaving,
5250 // return EL_BLOCKED instead of the element value
5251 int element = Tile[x][y];
5253 if (IS_MOVING(x, y))
5255 if (element == EL_BLOCKED)
5259 Blocked2Moving(x, y, &oldx, &oldy);
5260 return Tile[oldx][oldy];
5269 static void RemoveField(int x, int y)
5271 Tile[x][y] = EL_EMPTY;
5277 CustomValue[x][y] = 0;
5280 ChangeDelay[x][y] = 0;
5281 ChangePage[x][y] = -1;
5282 Pushed[x][y] = FALSE;
5284 GfxElement[x][y] = EL_UNDEFINED;
5285 GfxAction[x][y] = ACTION_DEFAULT;
5286 GfxDir[x][y] = MV_NONE;
5289 static void RemoveMovingField(int x, int y)
5291 int oldx = x, oldy = y, newx = x, newy = y;
5292 int element = Tile[x][y];
5293 int next_element = EL_UNDEFINED;
5295 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5298 if (IS_MOVING(x, y))
5300 Moving2Blocked(x, y, &newx, &newy);
5302 if (Tile[newx][newy] != EL_BLOCKED)
5304 // element is moving, but target field is not free (blocked), but
5305 // already occupied by something different (example: acid pool);
5306 // in this case, only remove the moving field, but not the target
5308 RemoveField(oldx, oldy);
5310 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5312 TEST_DrawLevelField(oldx, oldy);
5317 else if (element == EL_BLOCKED)
5319 Blocked2Moving(x, y, &oldx, &oldy);
5320 if (!IS_MOVING(oldx, oldy))
5324 if (element == EL_BLOCKED &&
5325 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5326 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5327 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5328 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5329 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5330 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5331 next_element = get_next_element(Tile[oldx][oldy]);
5333 RemoveField(oldx, oldy);
5334 RemoveField(newx, newy);
5336 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5338 if (next_element != EL_UNDEFINED)
5339 Tile[oldx][oldy] = next_element;
5341 TEST_DrawLevelField(oldx, oldy);
5342 TEST_DrawLevelField(newx, newy);
5345 void DrawDynamite(int x, int y)
5347 int sx = SCREENX(x), sy = SCREENY(y);
5348 int graphic = el2img(Tile[x][y]);
5351 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5354 if (IS_WALKABLE_INSIDE(Back[x][y]))
5358 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5359 else if (Store[x][y])
5360 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5362 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5364 if (Back[x][y] || Store[x][y])
5365 DrawGraphicThruMask(sx, sy, graphic, frame);
5367 DrawGraphic(sx, sy, graphic, frame);
5370 static void CheckDynamite(int x, int y)
5372 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5376 if (MovDelay[x][y] != 0)
5379 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5385 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5390 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5392 boolean num_checked_players = 0;
5395 for (i = 0; i < MAX_PLAYERS; i++)
5397 if (stored_player[i].active)
5399 int sx = stored_player[i].jx;
5400 int sy = stored_player[i].jy;
5402 if (num_checked_players == 0)
5409 *sx1 = MIN(*sx1, sx);
5410 *sy1 = MIN(*sy1, sy);
5411 *sx2 = MAX(*sx2, sx);
5412 *sy2 = MAX(*sy2, sy);
5415 num_checked_players++;
5420 static boolean checkIfAllPlayersFitToScreen_RND(void)
5422 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5424 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5426 return (sx2 - sx1 < SCR_FIELDX &&
5427 sy2 - sy1 < SCR_FIELDY);
5430 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5432 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5434 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5436 *sx = (sx1 + sx2) / 2;
5437 *sy = (sy1 + sy2) / 2;
5440 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5441 boolean center_screen, boolean quick_relocation)
5443 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5444 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5445 boolean no_delay = (tape.warp_forward);
5446 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5447 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5448 int new_scroll_x, new_scroll_y;
5450 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5452 // case 1: quick relocation inside visible screen (without scrolling)
5459 if (!level.shifted_relocation || center_screen)
5461 // relocation _with_ centering of screen
5463 new_scroll_x = SCROLL_POSITION_X(x);
5464 new_scroll_y = SCROLL_POSITION_Y(y);
5468 // relocation _without_ centering of screen
5470 int center_scroll_x = SCROLL_POSITION_X(old_x);
5471 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5472 int offset_x = x + (scroll_x - center_scroll_x);
5473 int offset_y = y + (scroll_y - center_scroll_y);
5475 // for new screen position, apply previous offset to center position
5476 new_scroll_x = SCROLL_POSITION_X(offset_x);
5477 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5480 if (quick_relocation)
5482 // case 2: quick relocation (redraw without visible scrolling)
5484 scroll_x = new_scroll_x;
5485 scroll_y = new_scroll_y;
5492 // case 3: visible relocation (with scrolling to new position)
5494 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5496 SetVideoFrameDelay(wait_delay_value);
5498 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5500 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5501 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5503 if (dx == 0 && dy == 0) // no scrolling needed at all
5509 // set values for horizontal/vertical screen scrolling (half tile size)
5510 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5511 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5512 int pos_x = dx * TILEX / 2;
5513 int pos_y = dy * TILEY / 2;
5514 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5515 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5517 ScrollLevel(dx, dy);
5520 // scroll in two steps of half tile size to make things smoother
5521 BlitScreenToBitmapExt_RND(window, fx, fy);
5523 // scroll second step to align at full tile size
5524 BlitScreenToBitmap(window);
5530 SetVideoFrameDelay(frame_delay_value_old);
5533 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5535 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5536 int player_nr = GET_PLAYER_NR(el_player);
5537 struct PlayerInfo *player = &stored_player[player_nr];
5538 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5539 boolean no_delay = (tape.warp_forward);
5540 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5541 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5542 int old_jx = player->jx;
5543 int old_jy = player->jy;
5544 int old_element = Tile[old_jx][old_jy];
5545 int element = Tile[jx][jy];
5546 boolean player_relocated = (old_jx != jx || old_jy != jy);
5548 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5549 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5550 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5551 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5552 int leave_side_horiz = move_dir_horiz;
5553 int leave_side_vert = move_dir_vert;
5554 int enter_side = enter_side_horiz | enter_side_vert;
5555 int leave_side = leave_side_horiz | leave_side_vert;
5557 if (player->buried) // do not reanimate dead player
5560 if (!player_relocated) // no need to relocate the player
5563 if (IS_PLAYER(jx, jy)) // player already placed at new position
5565 RemoveField(jx, jy); // temporarily remove newly placed player
5566 DrawLevelField(jx, jy);
5569 if (player->present)
5571 while (player->MovPos)
5573 ScrollPlayer(player, SCROLL_GO_ON);
5574 ScrollScreen(NULL, SCROLL_GO_ON);
5576 AdvanceFrameAndPlayerCounters(player->index_nr);
5580 BackToFront_WithFrameDelay(wait_delay_value);
5583 DrawPlayer(player); // needed here only to cleanup last field
5584 DrawLevelField(player->jx, player->jy); // remove player graphic
5586 player->is_moving = FALSE;
5589 if (IS_CUSTOM_ELEMENT(old_element))
5590 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5592 player->index_bit, leave_side);
5594 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5596 player->index_bit, leave_side);
5598 Tile[jx][jy] = el_player;
5599 InitPlayerField(jx, jy, el_player, TRUE);
5601 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5602 possible that the relocation target field did not contain a player element,
5603 but a walkable element, to which the new player was relocated -- in this
5604 case, restore that (already initialized!) element on the player field */
5605 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5607 Tile[jx][jy] = element; // restore previously existing element
5610 // only visually relocate centered player
5611 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5612 FALSE, level.instant_relocation);
5614 TestIfPlayerTouchesBadThing(jx, jy);
5615 TestIfPlayerTouchesCustomElement(jx, jy);
5617 if (IS_CUSTOM_ELEMENT(element))
5618 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5619 player->index_bit, enter_side);
5621 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5622 player->index_bit, enter_side);
5624 if (player->is_switching)
5626 /* ensure that relocation while still switching an element does not cause
5627 a new element to be treated as also switched directly after relocation
5628 (this is important for teleporter switches that teleport the player to
5629 a place where another teleporter switch is in the same direction, which
5630 would then incorrectly be treated as immediately switched before the
5631 direction key that caused the switch was released) */
5633 player->switch_x += jx - old_jx;
5634 player->switch_y += jy - old_jy;
5638 static void Explode(int ex, int ey, int phase, int mode)
5644 // !!! eliminate this variable !!!
5645 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5647 if (game.explosions_delayed)
5649 ExplodeField[ex][ey] = mode;
5653 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5655 int center_element = Tile[ex][ey];
5656 int artwork_element, explosion_element; // set these values later
5658 // remove things displayed in background while burning dynamite
5659 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5662 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5664 // put moving element to center field (and let it explode there)
5665 center_element = MovingOrBlocked2Element(ex, ey);
5666 RemoveMovingField(ex, ey);
5667 Tile[ex][ey] = center_element;
5670 // now "center_element" is finally determined -- set related values now
5671 artwork_element = center_element; // for custom player artwork
5672 explosion_element = center_element; // for custom player artwork
5674 if (IS_PLAYER(ex, ey))
5676 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5678 artwork_element = stored_player[player_nr].artwork_element;
5680 if (level.use_explosion_element[player_nr])
5682 explosion_element = level.explosion_element[player_nr];
5683 artwork_element = explosion_element;
5687 if (mode == EX_TYPE_NORMAL ||
5688 mode == EX_TYPE_CENTER ||
5689 mode == EX_TYPE_CROSS)
5690 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5692 last_phase = element_info[explosion_element].explosion_delay + 1;
5694 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5696 int xx = x - ex + 1;
5697 int yy = y - ey + 1;
5700 if (!IN_LEV_FIELD(x, y) ||
5701 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5702 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5705 element = Tile[x][y];
5707 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5709 element = MovingOrBlocked2Element(x, y);
5711 if (!IS_EXPLOSION_PROOF(element))
5712 RemoveMovingField(x, y);
5715 // indestructible elements can only explode in center (but not flames)
5716 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5717 mode == EX_TYPE_BORDER)) ||
5718 element == EL_FLAMES)
5721 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5722 behaviour, for example when touching a yamyam that explodes to rocks
5723 with active deadly shield, a rock is created under the player !!! */
5724 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5726 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5727 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5728 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5730 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5733 if (IS_ACTIVE_BOMB(element))
5735 // re-activate things under the bomb like gate or penguin
5736 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5743 // save walkable background elements while explosion on same tile
5744 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5745 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5746 Back[x][y] = element;
5748 // ignite explodable elements reached by other explosion
5749 if (element == EL_EXPLOSION)
5750 element = Store2[x][y];
5752 if (AmoebaNr[x][y] &&
5753 (element == EL_AMOEBA_FULL ||
5754 element == EL_BD_AMOEBA ||
5755 element == EL_AMOEBA_GROWING))
5757 AmoebaCnt[AmoebaNr[x][y]]--;
5758 AmoebaCnt2[AmoebaNr[x][y]]--;
5763 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5765 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5767 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5769 if (PLAYERINFO(ex, ey)->use_murphy)
5770 Store[x][y] = EL_EMPTY;
5773 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5774 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5775 else if (ELEM_IS_PLAYER(center_element))
5776 Store[x][y] = EL_EMPTY;
5777 else if (center_element == EL_YAMYAM)
5778 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5779 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5780 Store[x][y] = element_info[center_element].content.e[xx][yy];
5782 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5783 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5784 // otherwise) -- FIX THIS !!!
5785 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5786 Store[x][y] = element_info[element].content.e[1][1];
5788 else if (!CAN_EXPLODE(element))
5789 Store[x][y] = element_info[element].content.e[1][1];
5792 Store[x][y] = EL_EMPTY;
5794 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5795 center_element == EL_AMOEBA_TO_DIAMOND)
5796 Store2[x][y] = element;
5798 Tile[x][y] = EL_EXPLOSION;
5799 GfxElement[x][y] = artwork_element;
5801 ExplodePhase[x][y] = 1;
5802 ExplodeDelay[x][y] = last_phase;
5807 if (center_element == EL_YAMYAM)
5808 game.yamyam_content_nr =
5809 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5821 GfxFrame[x][y] = 0; // restart explosion animation
5823 last_phase = ExplodeDelay[x][y];
5825 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5827 // this can happen if the player leaves an explosion just in time
5828 if (GfxElement[x][y] == EL_UNDEFINED)
5829 GfxElement[x][y] = EL_EMPTY;
5831 border_element = Store2[x][y];
5832 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5833 border_element = StorePlayer[x][y];
5835 if (phase == element_info[border_element].ignition_delay ||
5836 phase == last_phase)
5838 boolean border_explosion = FALSE;
5840 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5841 !PLAYER_EXPLOSION_PROTECTED(x, y))
5843 KillPlayerUnlessExplosionProtected(x, y);
5844 border_explosion = TRUE;
5846 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5848 Tile[x][y] = Store2[x][y];
5851 border_explosion = TRUE;
5853 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5855 AmoebaToDiamond(x, y);
5857 border_explosion = TRUE;
5860 // if an element just explodes due to another explosion (chain-reaction),
5861 // do not immediately end the new explosion when it was the last frame of
5862 // the explosion (as it would be done in the following "if"-statement!)
5863 if (border_explosion && phase == last_phase)
5867 if (phase == last_phase)
5871 element = Tile[x][y] = Store[x][y];
5872 Store[x][y] = Store2[x][y] = 0;
5873 GfxElement[x][y] = EL_UNDEFINED;
5875 // player can escape from explosions and might therefore be still alive
5876 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5877 element <= EL_PLAYER_IS_EXPLODING_4)
5879 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5880 int explosion_element = EL_PLAYER_1 + player_nr;
5881 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5882 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5884 if (level.use_explosion_element[player_nr])
5885 explosion_element = level.explosion_element[player_nr];
5887 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5888 element_info[explosion_element].content.e[xx][yy]);
5891 // restore probably existing indestructible background element
5892 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5893 element = Tile[x][y] = Back[x][y];
5896 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5897 GfxDir[x][y] = MV_NONE;
5898 ChangeDelay[x][y] = 0;
5899 ChangePage[x][y] = -1;
5901 CustomValue[x][y] = 0;
5903 InitField_WithBug2(x, y, FALSE);
5905 TEST_DrawLevelField(x, y);
5907 TestIfElementTouchesCustomElement(x, y);
5909 if (GFX_CRUMBLED(element))
5910 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5912 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5913 StorePlayer[x][y] = 0;
5915 if (ELEM_IS_PLAYER(element))
5916 RelocatePlayer(x, y, element);
5918 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5920 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5921 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5924 TEST_DrawLevelFieldCrumbled(x, y);
5926 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5928 DrawLevelElement(x, y, Back[x][y]);
5929 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5931 else if (IS_WALKABLE_UNDER(Back[x][y]))
5933 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5934 DrawLevelElementThruMask(x, y, Back[x][y]);
5936 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5937 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5941 static void DynaExplode(int ex, int ey)
5944 int dynabomb_element = Tile[ex][ey];
5945 int dynabomb_size = 1;
5946 boolean dynabomb_xl = FALSE;
5947 struct PlayerInfo *player;
5948 static int xy[4][2] =
5956 if (IS_ACTIVE_BOMB(dynabomb_element))
5958 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5959 dynabomb_size = player->dynabomb_size;
5960 dynabomb_xl = player->dynabomb_xl;
5961 player->dynabombs_left++;
5964 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5966 for (i = 0; i < NUM_DIRECTIONS; i++)
5968 for (j = 1; j <= dynabomb_size; j++)
5970 int x = ex + j * xy[i][0];
5971 int y = ey + j * xy[i][1];
5974 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5977 element = Tile[x][y];
5979 // do not restart explosions of fields with active bombs
5980 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5983 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5985 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5986 !IS_DIGGABLE(element) && !dynabomb_xl)
5992 void Bang(int x, int y)
5994 int element = MovingOrBlocked2Element(x, y);
5995 int explosion_type = EX_TYPE_NORMAL;
5997 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5999 struct PlayerInfo *player = PLAYERINFO(x, y);
6001 element = Tile[x][y] = player->initial_element;
6003 if (level.use_explosion_element[player->index_nr])
6005 int explosion_element = level.explosion_element[player->index_nr];
6007 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6008 explosion_type = EX_TYPE_CROSS;
6009 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6010 explosion_type = EX_TYPE_CENTER;
6018 case EL_BD_BUTTERFLY:
6021 case EL_DARK_YAMYAM:
6025 RaiseScoreElement(element);
6028 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6029 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6030 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6031 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6032 case EL_DYNABOMB_INCREASE_NUMBER:
6033 case EL_DYNABOMB_INCREASE_SIZE:
6034 case EL_DYNABOMB_INCREASE_POWER:
6035 explosion_type = EX_TYPE_DYNA;
6038 case EL_DC_LANDMINE:
6039 explosion_type = EX_TYPE_CENTER;
6044 case EL_LAMP_ACTIVE:
6045 case EL_AMOEBA_TO_DIAMOND:
6046 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6047 explosion_type = EX_TYPE_CENTER;
6051 if (element_info[element].explosion_type == EXPLODES_CROSS)
6052 explosion_type = EX_TYPE_CROSS;
6053 else if (element_info[element].explosion_type == EXPLODES_1X1)
6054 explosion_type = EX_TYPE_CENTER;
6058 if (explosion_type == EX_TYPE_DYNA)
6061 Explode(x, y, EX_PHASE_START, explosion_type);
6063 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6066 static void SplashAcid(int x, int y)
6068 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6069 (!IN_LEV_FIELD(x - 1, y - 2) ||
6070 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6071 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6073 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6074 (!IN_LEV_FIELD(x + 1, y - 2) ||
6075 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6076 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6078 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6081 static void InitBeltMovement(void)
6083 static int belt_base_element[4] =
6085 EL_CONVEYOR_BELT_1_LEFT,
6086 EL_CONVEYOR_BELT_2_LEFT,
6087 EL_CONVEYOR_BELT_3_LEFT,
6088 EL_CONVEYOR_BELT_4_LEFT
6090 static int belt_base_active_element[4] =
6092 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6093 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6094 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6095 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6100 // set frame order for belt animation graphic according to belt direction
6101 for (i = 0; i < NUM_BELTS; i++)
6105 for (j = 0; j < NUM_BELT_PARTS; j++)
6107 int element = belt_base_active_element[belt_nr] + j;
6108 int graphic_1 = el2img(element);
6109 int graphic_2 = el2panelimg(element);
6111 if (game.belt_dir[i] == MV_LEFT)
6113 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6114 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6118 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6119 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6124 SCAN_PLAYFIELD(x, y)
6126 int element = Tile[x][y];
6128 for (i = 0; i < NUM_BELTS; i++)
6130 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6132 int e_belt_nr = getBeltNrFromBeltElement(element);
6135 if (e_belt_nr == belt_nr)
6137 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6139 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6146 static void ToggleBeltSwitch(int x, int y)
6148 static int belt_base_element[4] =
6150 EL_CONVEYOR_BELT_1_LEFT,
6151 EL_CONVEYOR_BELT_2_LEFT,
6152 EL_CONVEYOR_BELT_3_LEFT,
6153 EL_CONVEYOR_BELT_4_LEFT
6155 static int belt_base_active_element[4] =
6157 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6158 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6159 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6160 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6162 static int belt_base_switch_element[4] =
6164 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6165 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6166 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6167 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6169 static int belt_move_dir[4] =
6177 int element = Tile[x][y];
6178 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6179 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6180 int belt_dir = belt_move_dir[belt_dir_nr];
6183 if (!IS_BELT_SWITCH(element))
6186 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6187 game.belt_dir[belt_nr] = belt_dir;
6189 if (belt_dir_nr == 3)
6192 // set frame order for belt animation graphic according to belt direction
6193 for (i = 0; i < NUM_BELT_PARTS; i++)
6195 int element = belt_base_active_element[belt_nr] + i;
6196 int graphic_1 = el2img(element);
6197 int graphic_2 = el2panelimg(element);
6199 if (belt_dir == MV_LEFT)
6201 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6202 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6206 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6207 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6211 SCAN_PLAYFIELD(xx, yy)
6213 int element = Tile[xx][yy];
6215 if (IS_BELT_SWITCH(element))
6217 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6219 if (e_belt_nr == belt_nr)
6221 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6222 TEST_DrawLevelField(xx, yy);
6225 else if (IS_BELT(element) && belt_dir != MV_NONE)
6227 int e_belt_nr = getBeltNrFromBeltElement(element);
6229 if (e_belt_nr == belt_nr)
6231 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6233 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6234 TEST_DrawLevelField(xx, yy);
6237 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6239 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6241 if (e_belt_nr == belt_nr)
6243 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6245 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6246 TEST_DrawLevelField(xx, yy);
6252 static void ToggleSwitchgateSwitch(int x, int y)
6256 game.switchgate_pos = !game.switchgate_pos;
6258 SCAN_PLAYFIELD(xx, yy)
6260 int element = Tile[xx][yy];
6262 if (element == EL_SWITCHGATE_SWITCH_UP)
6264 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6265 TEST_DrawLevelField(xx, yy);
6267 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6269 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6270 TEST_DrawLevelField(xx, yy);
6272 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6274 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6275 TEST_DrawLevelField(xx, yy);
6277 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6279 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6280 TEST_DrawLevelField(xx, yy);
6282 else if (element == EL_SWITCHGATE_OPEN ||
6283 element == EL_SWITCHGATE_OPENING)
6285 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6287 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6289 else if (element == EL_SWITCHGATE_CLOSED ||
6290 element == EL_SWITCHGATE_CLOSING)
6292 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6294 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6299 static int getInvisibleActiveFromInvisibleElement(int element)
6301 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6302 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6303 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6307 static int getInvisibleFromInvisibleActiveElement(int element)
6309 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6310 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6311 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6315 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6319 SCAN_PLAYFIELD(x, y)
6321 int element = Tile[x][y];
6323 if (element == EL_LIGHT_SWITCH &&
6324 game.light_time_left > 0)
6326 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6327 TEST_DrawLevelField(x, y);
6329 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6330 game.light_time_left == 0)
6332 Tile[x][y] = EL_LIGHT_SWITCH;
6333 TEST_DrawLevelField(x, y);
6335 else if (element == EL_EMC_DRIPPER &&
6336 game.light_time_left > 0)
6338 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6339 TEST_DrawLevelField(x, y);
6341 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6342 game.light_time_left == 0)
6344 Tile[x][y] = EL_EMC_DRIPPER;
6345 TEST_DrawLevelField(x, y);
6347 else if (element == EL_INVISIBLE_STEELWALL ||
6348 element == EL_INVISIBLE_WALL ||
6349 element == EL_INVISIBLE_SAND)
6351 if (game.light_time_left > 0)
6352 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6354 TEST_DrawLevelField(x, y);
6356 // uncrumble neighbour fields, if needed
6357 if (element == EL_INVISIBLE_SAND)
6358 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6360 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6361 element == EL_INVISIBLE_WALL_ACTIVE ||
6362 element == EL_INVISIBLE_SAND_ACTIVE)
6364 if (game.light_time_left == 0)
6365 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6367 TEST_DrawLevelField(x, y);
6369 // re-crumble neighbour fields, if needed
6370 if (element == EL_INVISIBLE_SAND)
6371 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6376 static void RedrawAllInvisibleElementsForLenses(void)
6380 SCAN_PLAYFIELD(x, y)
6382 int element = Tile[x][y];
6384 if (element == EL_EMC_DRIPPER &&
6385 game.lenses_time_left > 0)
6387 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6388 TEST_DrawLevelField(x, y);
6390 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6391 game.lenses_time_left == 0)
6393 Tile[x][y] = EL_EMC_DRIPPER;
6394 TEST_DrawLevelField(x, y);
6396 else if (element == EL_INVISIBLE_STEELWALL ||
6397 element == EL_INVISIBLE_WALL ||
6398 element == EL_INVISIBLE_SAND)
6400 if (game.lenses_time_left > 0)
6401 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6403 TEST_DrawLevelField(x, y);
6405 // uncrumble neighbour fields, if needed
6406 if (element == EL_INVISIBLE_SAND)
6407 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6409 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6410 element == EL_INVISIBLE_WALL_ACTIVE ||
6411 element == EL_INVISIBLE_SAND_ACTIVE)
6413 if (game.lenses_time_left == 0)
6414 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6416 TEST_DrawLevelField(x, y);
6418 // re-crumble neighbour fields, if needed
6419 if (element == EL_INVISIBLE_SAND)
6420 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6425 static void RedrawAllInvisibleElementsForMagnifier(void)
6429 SCAN_PLAYFIELD(x, y)
6431 int element = Tile[x][y];
6433 if (element == EL_EMC_FAKE_GRASS &&
6434 game.magnify_time_left > 0)
6436 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6437 TEST_DrawLevelField(x, y);
6439 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6440 game.magnify_time_left == 0)
6442 Tile[x][y] = EL_EMC_FAKE_GRASS;
6443 TEST_DrawLevelField(x, y);
6445 else if (IS_GATE_GRAY(element) &&
6446 game.magnify_time_left > 0)
6448 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6449 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6450 IS_EM_GATE_GRAY(element) ?
6451 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6452 IS_EMC_GATE_GRAY(element) ?
6453 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6454 IS_DC_GATE_GRAY(element) ?
6455 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6457 TEST_DrawLevelField(x, y);
6459 else if (IS_GATE_GRAY_ACTIVE(element) &&
6460 game.magnify_time_left == 0)
6462 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6463 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6464 IS_EM_GATE_GRAY_ACTIVE(element) ?
6465 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6466 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6467 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6468 IS_DC_GATE_GRAY_ACTIVE(element) ?
6469 EL_DC_GATE_WHITE_GRAY :
6471 TEST_DrawLevelField(x, y);
6476 static void ToggleLightSwitch(int x, int y)
6478 int element = Tile[x][y];
6480 game.light_time_left =
6481 (element == EL_LIGHT_SWITCH ?
6482 level.time_light * FRAMES_PER_SECOND : 0);
6484 RedrawAllLightSwitchesAndInvisibleElements();
6487 static void ActivateTimegateSwitch(int x, int y)
6491 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6493 SCAN_PLAYFIELD(xx, yy)
6495 int element = Tile[xx][yy];
6497 if (element == EL_TIMEGATE_CLOSED ||
6498 element == EL_TIMEGATE_CLOSING)
6500 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6501 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6505 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6507 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6508 TEST_DrawLevelField(xx, yy);
6514 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6515 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6518 static void Impact(int x, int y)
6520 boolean last_line = (y == lev_fieldy - 1);
6521 boolean object_hit = FALSE;
6522 boolean impact = (last_line || object_hit);
6523 int element = Tile[x][y];
6524 int smashed = EL_STEELWALL;
6526 if (!last_line) // check if element below was hit
6528 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6531 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6532 MovDir[x][y + 1] != MV_DOWN ||
6533 MovPos[x][y + 1] <= TILEY / 2));
6535 // do not smash moving elements that left the smashed field in time
6536 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6537 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6540 #if USE_QUICKSAND_IMPACT_BUGFIX
6541 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6543 RemoveMovingField(x, y + 1);
6544 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6545 Tile[x][y + 2] = EL_ROCK;
6546 TEST_DrawLevelField(x, y + 2);
6551 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6553 RemoveMovingField(x, y + 1);
6554 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6555 Tile[x][y + 2] = EL_ROCK;
6556 TEST_DrawLevelField(x, y + 2);
6563 smashed = MovingOrBlocked2Element(x, y + 1);
6565 impact = (last_line || object_hit);
6568 if (!last_line && smashed == EL_ACID) // element falls into acid
6570 SplashAcid(x, y + 1);
6574 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6575 // only reset graphic animation if graphic really changes after impact
6577 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6579 ResetGfxAnimation(x, y);
6580 TEST_DrawLevelField(x, y);
6583 if (impact && CAN_EXPLODE_IMPACT(element))
6588 else if (impact && element == EL_PEARL &&
6589 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6591 ResetGfxAnimation(x, y);
6593 Tile[x][y] = EL_PEARL_BREAKING;
6594 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6597 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6599 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6604 if (impact && element == EL_AMOEBA_DROP)
6606 if (object_hit && IS_PLAYER(x, y + 1))
6607 KillPlayerUnlessEnemyProtected(x, y + 1);
6608 else if (object_hit && smashed == EL_PENGUIN)
6612 Tile[x][y] = EL_AMOEBA_GROWING;
6613 Store[x][y] = EL_AMOEBA_WET;
6615 ResetRandomAnimationValue(x, y);
6620 if (object_hit) // check which object was hit
6622 if ((CAN_PASS_MAGIC_WALL(element) &&
6623 (smashed == EL_MAGIC_WALL ||
6624 smashed == EL_BD_MAGIC_WALL)) ||
6625 (CAN_PASS_DC_MAGIC_WALL(element) &&
6626 smashed == EL_DC_MAGIC_WALL))
6629 int activated_magic_wall =
6630 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6631 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6632 EL_DC_MAGIC_WALL_ACTIVE);
6634 // activate magic wall / mill
6635 SCAN_PLAYFIELD(xx, yy)
6637 if (Tile[xx][yy] == smashed)
6638 Tile[xx][yy] = activated_magic_wall;
6641 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6642 game.magic_wall_active = TRUE;
6644 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6645 SND_MAGIC_WALL_ACTIVATING :
6646 smashed == EL_BD_MAGIC_WALL ?
6647 SND_BD_MAGIC_WALL_ACTIVATING :
6648 SND_DC_MAGIC_WALL_ACTIVATING));
6651 if (IS_PLAYER(x, y + 1))
6653 if (CAN_SMASH_PLAYER(element))
6655 KillPlayerUnlessEnemyProtected(x, y + 1);
6659 else if (smashed == EL_PENGUIN)
6661 if (CAN_SMASH_PLAYER(element))
6667 else if (element == EL_BD_DIAMOND)
6669 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6675 else if (((element == EL_SP_INFOTRON ||
6676 element == EL_SP_ZONK) &&
6677 (smashed == EL_SP_SNIKSNAK ||
6678 smashed == EL_SP_ELECTRON ||
6679 smashed == EL_SP_DISK_ORANGE)) ||
6680 (element == EL_SP_INFOTRON &&
6681 smashed == EL_SP_DISK_YELLOW))
6686 else if (CAN_SMASH_EVERYTHING(element))
6688 if (IS_CLASSIC_ENEMY(smashed) ||
6689 CAN_EXPLODE_SMASHED(smashed))
6694 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6696 if (smashed == EL_LAMP ||
6697 smashed == EL_LAMP_ACTIVE)
6702 else if (smashed == EL_NUT)
6704 Tile[x][y + 1] = EL_NUT_BREAKING;
6705 PlayLevelSound(x, y, SND_NUT_BREAKING);
6706 RaiseScoreElement(EL_NUT);
6709 else if (smashed == EL_PEARL)
6711 ResetGfxAnimation(x, y);
6713 Tile[x][y + 1] = EL_PEARL_BREAKING;
6714 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6717 else if (smashed == EL_DIAMOND)
6719 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6720 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6723 else if (IS_BELT_SWITCH(smashed))
6725 ToggleBeltSwitch(x, y + 1);
6727 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6728 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6729 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6730 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6732 ToggleSwitchgateSwitch(x, y + 1);
6734 else if (smashed == EL_LIGHT_SWITCH ||
6735 smashed == EL_LIGHT_SWITCH_ACTIVE)
6737 ToggleLightSwitch(x, y + 1);
6741 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6743 CheckElementChangeBySide(x, y + 1, smashed, element,
6744 CE_SWITCHED, CH_SIDE_TOP);
6745 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6751 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6756 // play sound of magic wall / mill
6758 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6759 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6760 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6762 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6763 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6764 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6765 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6766 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6767 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6772 // play sound of object that hits the ground
6773 if (last_line || object_hit)
6774 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6777 static void TurnRoundExt(int x, int y)
6789 { 0, 0 }, { 0, 0 }, { 0, 0 },
6794 int left, right, back;
6798 { MV_DOWN, MV_UP, MV_RIGHT },
6799 { MV_UP, MV_DOWN, MV_LEFT },
6801 { MV_LEFT, MV_RIGHT, MV_DOWN },
6805 { MV_RIGHT, MV_LEFT, MV_UP }
6808 int element = Tile[x][y];
6809 int move_pattern = element_info[element].move_pattern;
6811 int old_move_dir = MovDir[x][y];
6812 int left_dir = turn[old_move_dir].left;
6813 int right_dir = turn[old_move_dir].right;
6814 int back_dir = turn[old_move_dir].back;
6816 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6817 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6818 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6819 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6821 int left_x = x + left_dx, left_y = y + left_dy;
6822 int right_x = x + right_dx, right_y = y + right_dy;
6823 int move_x = x + move_dx, move_y = y + move_dy;
6827 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6829 TestIfBadThingTouchesOtherBadThing(x, y);
6831 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6832 MovDir[x][y] = right_dir;
6833 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6834 MovDir[x][y] = left_dir;
6836 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6838 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6841 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6843 TestIfBadThingTouchesOtherBadThing(x, y);
6845 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6846 MovDir[x][y] = left_dir;
6847 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6848 MovDir[x][y] = right_dir;
6850 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6852 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6855 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6857 TestIfBadThingTouchesOtherBadThing(x, y);
6859 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6860 MovDir[x][y] = left_dir;
6861 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6862 MovDir[x][y] = right_dir;
6864 if (MovDir[x][y] != old_move_dir)
6867 else if (element == EL_YAMYAM)
6869 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6870 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6872 if (can_turn_left && can_turn_right)
6873 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6874 else if (can_turn_left)
6875 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6876 else if (can_turn_right)
6877 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6879 MovDir[x][y] = back_dir;
6881 MovDelay[x][y] = 16 + 16 * RND(3);
6883 else if (element == EL_DARK_YAMYAM)
6885 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6887 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6890 if (can_turn_left && can_turn_right)
6891 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6892 else if (can_turn_left)
6893 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6894 else if (can_turn_right)
6895 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6897 MovDir[x][y] = back_dir;
6899 MovDelay[x][y] = 16 + 16 * RND(3);
6901 else if (element == EL_PACMAN)
6903 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6904 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6906 if (can_turn_left && can_turn_right)
6907 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6908 else if (can_turn_left)
6909 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6910 else if (can_turn_right)
6911 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6913 MovDir[x][y] = back_dir;
6915 MovDelay[x][y] = 6 + RND(40);
6917 else if (element == EL_PIG)
6919 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6920 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6921 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6922 boolean should_turn_left, should_turn_right, should_move_on;
6924 int rnd = RND(rnd_value);
6926 should_turn_left = (can_turn_left &&
6928 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6929 y + back_dy + left_dy)));
6930 should_turn_right = (can_turn_right &&
6932 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6933 y + back_dy + right_dy)));
6934 should_move_on = (can_move_on &&
6937 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6938 y + move_dy + left_dy) ||
6939 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6940 y + move_dy + right_dy)));
6942 if (should_turn_left || should_turn_right || should_move_on)
6944 if (should_turn_left && should_turn_right && should_move_on)
6945 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6946 rnd < 2 * rnd_value / 3 ? right_dir :
6948 else if (should_turn_left && should_turn_right)
6949 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6950 else if (should_turn_left && should_move_on)
6951 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6952 else if (should_turn_right && should_move_on)
6953 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6954 else if (should_turn_left)
6955 MovDir[x][y] = left_dir;
6956 else if (should_turn_right)
6957 MovDir[x][y] = right_dir;
6958 else if (should_move_on)
6959 MovDir[x][y] = old_move_dir;
6961 else if (can_move_on && rnd > rnd_value / 8)
6962 MovDir[x][y] = old_move_dir;
6963 else if (can_turn_left && can_turn_right)
6964 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6965 else if (can_turn_left && rnd > rnd_value / 8)
6966 MovDir[x][y] = left_dir;
6967 else if (can_turn_right && rnd > rnd_value/8)
6968 MovDir[x][y] = right_dir;
6970 MovDir[x][y] = back_dir;
6972 xx = x + move_xy[MovDir[x][y]].dx;
6973 yy = y + move_xy[MovDir[x][y]].dy;
6975 if (!IN_LEV_FIELD(xx, yy) ||
6976 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6977 MovDir[x][y] = old_move_dir;
6981 else if (element == EL_DRAGON)
6983 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6984 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6985 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6987 int rnd = RND(rnd_value);
6989 if (can_move_on && rnd > rnd_value / 8)
6990 MovDir[x][y] = old_move_dir;
6991 else if (can_turn_left && can_turn_right)
6992 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6993 else if (can_turn_left && rnd > rnd_value / 8)
6994 MovDir[x][y] = left_dir;
6995 else if (can_turn_right && rnd > rnd_value / 8)
6996 MovDir[x][y] = right_dir;
6998 MovDir[x][y] = back_dir;
7000 xx = x + move_xy[MovDir[x][y]].dx;
7001 yy = y + move_xy[MovDir[x][y]].dy;
7003 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7004 MovDir[x][y] = old_move_dir;
7008 else if (element == EL_MOLE)
7010 boolean can_move_on =
7011 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7012 IS_AMOEBOID(Tile[move_x][move_y]) ||
7013 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7016 boolean can_turn_left =
7017 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7018 IS_AMOEBOID(Tile[left_x][left_y])));
7020 boolean can_turn_right =
7021 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7022 IS_AMOEBOID(Tile[right_x][right_y])));
7024 if (can_turn_left && can_turn_right)
7025 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7026 else if (can_turn_left)
7027 MovDir[x][y] = left_dir;
7029 MovDir[x][y] = right_dir;
7032 if (MovDir[x][y] != old_move_dir)
7035 else if (element == EL_BALLOON)
7037 MovDir[x][y] = game.wind_direction;
7040 else if (element == EL_SPRING)
7042 if (MovDir[x][y] & MV_HORIZONTAL)
7044 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7045 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7047 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7048 ResetGfxAnimation(move_x, move_y);
7049 TEST_DrawLevelField(move_x, move_y);
7051 MovDir[x][y] = back_dir;
7053 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7054 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7055 MovDir[x][y] = MV_NONE;
7060 else if (element == EL_ROBOT ||
7061 element == EL_SATELLITE ||
7062 element == EL_PENGUIN ||
7063 element == EL_EMC_ANDROID)
7065 int attr_x = -1, attr_y = -1;
7067 if (game.all_players_gone)
7069 attr_x = game.exit_x;
7070 attr_y = game.exit_y;
7076 for (i = 0; i < MAX_PLAYERS; i++)
7078 struct PlayerInfo *player = &stored_player[i];
7079 int jx = player->jx, jy = player->jy;
7081 if (!player->active)
7085 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7093 if (element == EL_ROBOT &&
7094 game.robot_wheel_x >= 0 &&
7095 game.robot_wheel_y >= 0 &&
7096 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7097 game.engine_version < VERSION_IDENT(3,1,0,0)))
7099 attr_x = game.robot_wheel_x;
7100 attr_y = game.robot_wheel_y;
7103 if (element == EL_PENGUIN)
7106 static int xy[4][2] =
7114 for (i = 0; i < NUM_DIRECTIONS; i++)
7116 int ex = x + xy[i][0];
7117 int ey = y + xy[i][1];
7119 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7120 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7121 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7122 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7131 MovDir[x][y] = MV_NONE;
7133 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7134 else if (attr_x > x)
7135 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7137 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7138 else if (attr_y > y)
7139 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7141 if (element == EL_ROBOT)
7145 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7146 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7147 Moving2Blocked(x, y, &newx, &newy);
7149 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7150 MovDelay[x][y] = 8 + 8 * !RND(3);
7152 MovDelay[x][y] = 16;
7154 else if (element == EL_PENGUIN)
7160 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7162 boolean first_horiz = RND(2);
7163 int new_move_dir = MovDir[x][y];
7166 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7167 Moving2Blocked(x, y, &newx, &newy);
7169 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7173 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7174 Moving2Blocked(x, y, &newx, &newy);
7176 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7179 MovDir[x][y] = old_move_dir;
7183 else if (element == EL_SATELLITE)
7189 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7191 boolean first_horiz = RND(2);
7192 int new_move_dir = MovDir[x][y];
7195 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7196 Moving2Blocked(x, y, &newx, &newy);
7198 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7202 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7203 Moving2Blocked(x, y, &newx, &newy);
7205 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7208 MovDir[x][y] = old_move_dir;
7212 else if (element == EL_EMC_ANDROID)
7214 static int check_pos[16] =
7216 -1, // 0 => (invalid)
7219 -1, // 3 => (invalid)
7221 0, // 5 => MV_LEFT | MV_UP
7222 2, // 6 => MV_RIGHT | MV_UP
7223 -1, // 7 => (invalid)
7225 6, // 9 => MV_LEFT | MV_DOWN
7226 4, // 10 => MV_RIGHT | MV_DOWN
7227 -1, // 11 => (invalid)
7228 -1, // 12 => (invalid)
7229 -1, // 13 => (invalid)
7230 -1, // 14 => (invalid)
7231 -1, // 15 => (invalid)
7239 { -1, -1, MV_LEFT | MV_UP },
7241 { +1, -1, MV_RIGHT | MV_UP },
7242 { +1, 0, MV_RIGHT },
7243 { +1, +1, MV_RIGHT | MV_DOWN },
7245 { -1, +1, MV_LEFT | MV_DOWN },
7248 int start_pos, check_order;
7249 boolean can_clone = FALSE;
7252 // check if there is any free field around current position
7253 for (i = 0; i < 8; i++)
7255 int newx = x + check_xy[i].dx;
7256 int newy = y + check_xy[i].dy;
7258 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7266 if (can_clone) // randomly find an element to clone
7270 start_pos = check_pos[RND(8)];
7271 check_order = (RND(2) ? -1 : +1);
7273 for (i = 0; i < 8; i++)
7275 int pos_raw = start_pos + i * check_order;
7276 int pos = (pos_raw + 8) % 8;
7277 int newx = x + check_xy[pos].dx;
7278 int newy = y + check_xy[pos].dy;
7280 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7282 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7283 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7285 Store[x][y] = Tile[newx][newy];
7294 if (can_clone) // randomly find a direction to move
7298 start_pos = check_pos[RND(8)];
7299 check_order = (RND(2) ? -1 : +1);
7301 for (i = 0; i < 8; i++)
7303 int pos_raw = start_pos + i * check_order;
7304 int pos = (pos_raw + 8) % 8;
7305 int newx = x + check_xy[pos].dx;
7306 int newy = y + check_xy[pos].dy;
7307 int new_move_dir = check_xy[pos].dir;
7309 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7311 MovDir[x][y] = new_move_dir;
7312 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7321 if (can_clone) // cloning and moving successful
7324 // cannot clone -- try to move towards player
7326 start_pos = check_pos[MovDir[x][y] & 0x0f];
7327 check_order = (RND(2) ? -1 : +1);
7329 for (i = 0; i < 3; i++)
7331 // first check start_pos, then previous/next or (next/previous) pos
7332 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7333 int pos = (pos_raw + 8) % 8;
7334 int newx = x + check_xy[pos].dx;
7335 int newy = y + check_xy[pos].dy;
7336 int new_move_dir = check_xy[pos].dir;
7338 if (IS_PLAYER(newx, newy))
7341 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7343 MovDir[x][y] = new_move_dir;
7344 MovDelay[x][y] = level.android_move_time * 8 + 1;
7351 else if (move_pattern == MV_TURNING_LEFT ||
7352 move_pattern == MV_TURNING_RIGHT ||
7353 move_pattern == MV_TURNING_LEFT_RIGHT ||
7354 move_pattern == MV_TURNING_RIGHT_LEFT ||
7355 move_pattern == MV_TURNING_RANDOM ||
7356 move_pattern == MV_ALL_DIRECTIONS)
7358 boolean can_turn_left =
7359 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7360 boolean can_turn_right =
7361 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7363 if (element_info[element].move_stepsize == 0) // "not moving"
7366 if (move_pattern == MV_TURNING_LEFT)
7367 MovDir[x][y] = left_dir;
7368 else if (move_pattern == MV_TURNING_RIGHT)
7369 MovDir[x][y] = right_dir;
7370 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7371 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7372 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7373 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7374 else if (move_pattern == MV_TURNING_RANDOM)
7375 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7376 can_turn_right && !can_turn_left ? right_dir :
7377 RND(2) ? left_dir : right_dir);
7378 else if (can_turn_left && can_turn_right)
7379 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7380 else if (can_turn_left)
7381 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7382 else if (can_turn_right)
7383 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7385 MovDir[x][y] = back_dir;
7387 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7389 else if (move_pattern == MV_HORIZONTAL ||
7390 move_pattern == MV_VERTICAL)
7392 if (move_pattern & old_move_dir)
7393 MovDir[x][y] = back_dir;
7394 else if (move_pattern == MV_HORIZONTAL)
7395 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7396 else if (move_pattern == MV_VERTICAL)
7397 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7399 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7401 else if (move_pattern & MV_ANY_DIRECTION)
7403 MovDir[x][y] = move_pattern;
7404 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7406 else if (move_pattern & MV_WIND_DIRECTION)
7408 MovDir[x][y] = game.wind_direction;
7409 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7411 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7413 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7414 MovDir[x][y] = left_dir;
7415 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7416 MovDir[x][y] = right_dir;
7418 if (MovDir[x][y] != old_move_dir)
7419 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7421 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7423 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7424 MovDir[x][y] = right_dir;
7425 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7426 MovDir[x][y] = left_dir;
7428 if (MovDir[x][y] != old_move_dir)
7429 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7431 else if (move_pattern == MV_TOWARDS_PLAYER ||
7432 move_pattern == MV_AWAY_FROM_PLAYER)
7434 int attr_x = -1, attr_y = -1;
7436 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7438 if (game.all_players_gone)
7440 attr_x = game.exit_x;
7441 attr_y = game.exit_y;
7447 for (i = 0; i < MAX_PLAYERS; i++)
7449 struct PlayerInfo *player = &stored_player[i];
7450 int jx = player->jx, jy = player->jy;
7452 if (!player->active)
7456 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7464 MovDir[x][y] = MV_NONE;
7466 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7467 else if (attr_x > x)
7468 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7470 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7471 else if (attr_y > y)
7472 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7474 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7476 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7478 boolean first_horiz = RND(2);
7479 int new_move_dir = MovDir[x][y];
7481 if (element_info[element].move_stepsize == 0) // "not moving"
7483 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7484 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7490 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7491 Moving2Blocked(x, y, &newx, &newy);
7493 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7497 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7498 Moving2Blocked(x, y, &newx, &newy);
7500 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7503 MovDir[x][y] = old_move_dir;
7506 else if (move_pattern == MV_WHEN_PUSHED ||
7507 move_pattern == MV_WHEN_DROPPED)
7509 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7510 MovDir[x][y] = MV_NONE;
7514 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7516 static int test_xy[7][2] =
7526 static int test_dir[7] =
7536 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7537 int move_preference = -1000000; // start with very low preference
7538 int new_move_dir = MV_NONE;
7539 int start_test = RND(4);
7542 for (i = 0; i < NUM_DIRECTIONS; i++)
7544 int move_dir = test_dir[start_test + i];
7545 int move_dir_preference;
7547 xx = x + test_xy[start_test + i][0];
7548 yy = y + test_xy[start_test + i][1];
7550 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7551 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7553 new_move_dir = move_dir;
7558 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7561 move_dir_preference = -1 * RunnerVisit[xx][yy];
7562 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7563 move_dir_preference = PlayerVisit[xx][yy];
7565 if (move_dir_preference > move_preference)
7567 // prefer field that has not been visited for the longest time
7568 move_preference = move_dir_preference;
7569 new_move_dir = move_dir;
7571 else if (move_dir_preference == move_preference &&
7572 move_dir == old_move_dir)
7574 // prefer last direction when all directions are preferred equally
7575 move_preference = move_dir_preference;
7576 new_move_dir = move_dir;
7580 MovDir[x][y] = new_move_dir;
7581 if (old_move_dir != new_move_dir)
7582 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7586 static void TurnRound(int x, int y)
7588 int direction = MovDir[x][y];
7592 GfxDir[x][y] = MovDir[x][y];
7594 if (direction != MovDir[x][y])
7598 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7600 ResetGfxFrame(x, y);
7603 static boolean JustBeingPushed(int x, int y)
7607 for (i = 0; i < MAX_PLAYERS; i++)
7609 struct PlayerInfo *player = &stored_player[i];
7611 if (player->active && player->is_pushing && player->MovPos)
7613 int next_jx = player->jx + (player->jx - player->last_jx);
7614 int next_jy = player->jy + (player->jy - player->last_jy);
7616 if (x == next_jx && y == next_jy)
7624 static void StartMoving(int x, int y)
7626 boolean started_moving = FALSE; // some elements can fall _and_ move
7627 int element = Tile[x][y];
7632 if (MovDelay[x][y] == 0)
7633 GfxAction[x][y] = ACTION_DEFAULT;
7635 if (CAN_FALL(element) && y < lev_fieldy - 1)
7637 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7638 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7639 if (JustBeingPushed(x, y))
7642 if (element == EL_QUICKSAND_FULL)
7644 if (IS_FREE(x, y + 1))
7646 InitMovingField(x, y, MV_DOWN);
7647 started_moving = TRUE;
7649 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7650 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7651 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7652 Store[x][y] = EL_ROCK;
7654 Store[x][y] = EL_ROCK;
7657 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7659 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7661 if (!MovDelay[x][y])
7663 MovDelay[x][y] = TILEY + 1;
7665 ResetGfxAnimation(x, y);
7666 ResetGfxAnimation(x, y + 1);
7671 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7672 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7679 Tile[x][y] = EL_QUICKSAND_EMPTY;
7680 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7681 Store[x][y + 1] = Store[x][y];
7684 PlayLevelSoundAction(x, y, ACTION_FILLING);
7686 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7688 if (!MovDelay[x][y])
7690 MovDelay[x][y] = TILEY + 1;
7692 ResetGfxAnimation(x, y);
7693 ResetGfxAnimation(x, y + 1);
7698 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7699 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7706 Tile[x][y] = EL_QUICKSAND_EMPTY;
7707 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7708 Store[x][y + 1] = Store[x][y];
7711 PlayLevelSoundAction(x, y, ACTION_FILLING);
7714 else if (element == EL_QUICKSAND_FAST_FULL)
7716 if (IS_FREE(x, y + 1))
7718 InitMovingField(x, y, MV_DOWN);
7719 started_moving = TRUE;
7721 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7722 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7723 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7724 Store[x][y] = EL_ROCK;
7726 Store[x][y] = EL_ROCK;
7729 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7731 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7733 if (!MovDelay[x][y])
7735 MovDelay[x][y] = TILEY + 1;
7737 ResetGfxAnimation(x, y);
7738 ResetGfxAnimation(x, y + 1);
7743 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7744 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7751 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7752 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7753 Store[x][y + 1] = Store[x][y];
7756 PlayLevelSoundAction(x, y, ACTION_FILLING);
7758 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7760 if (!MovDelay[x][y])
7762 MovDelay[x][y] = TILEY + 1;
7764 ResetGfxAnimation(x, y);
7765 ResetGfxAnimation(x, y + 1);
7770 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7771 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7778 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7779 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7780 Store[x][y + 1] = Store[x][y];
7783 PlayLevelSoundAction(x, y, ACTION_FILLING);
7786 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7787 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7789 InitMovingField(x, y, MV_DOWN);
7790 started_moving = TRUE;
7792 Tile[x][y] = EL_QUICKSAND_FILLING;
7793 Store[x][y] = element;
7795 PlayLevelSoundAction(x, y, ACTION_FILLING);
7797 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7798 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7800 InitMovingField(x, y, MV_DOWN);
7801 started_moving = TRUE;
7803 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7804 Store[x][y] = element;
7806 PlayLevelSoundAction(x, y, ACTION_FILLING);
7808 else if (element == EL_MAGIC_WALL_FULL)
7810 if (IS_FREE(x, y + 1))
7812 InitMovingField(x, y, MV_DOWN);
7813 started_moving = TRUE;
7815 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7816 Store[x][y] = EL_CHANGED(Store[x][y]);
7818 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7820 if (!MovDelay[x][y])
7821 MovDelay[x][y] = TILEY / 4 + 1;
7830 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7831 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7832 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7836 else if (element == EL_BD_MAGIC_WALL_FULL)
7838 if (IS_FREE(x, y + 1))
7840 InitMovingField(x, y, MV_DOWN);
7841 started_moving = TRUE;
7843 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7844 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7846 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7848 if (!MovDelay[x][y])
7849 MovDelay[x][y] = TILEY / 4 + 1;
7858 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7859 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7860 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7864 else if (element == EL_DC_MAGIC_WALL_FULL)
7866 if (IS_FREE(x, y + 1))
7868 InitMovingField(x, y, MV_DOWN);
7869 started_moving = TRUE;
7871 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7872 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7874 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7876 if (!MovDelay[x][y])
7877 MovDelay[x][y] = TILEY / 4 + 1;
7886 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7887 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7888 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7892 else if ((CAN_PASS_MAGIC_WALL(element) &&
7893 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7894 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7895 (CAN_PASS_DC_MAGIC_WALL(element) &&
7896 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7899 InitMovingField(x, y, MV_DOWN);
7900 started_moving = TRUE;
7903 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7904 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7905 EL_DC_MAGIC_WALL_FILLING);
7906 Store[x][y] = element;
7908 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7910 SplashAcid(x, y + 1);
7912 InitMovingField(x, y, MV_DOWN);
7913 started_moving = TRUE;
7915 Store[x][y] = EL_ACID;
7918 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7919 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7920 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7921 CAN_FALL(element) && WasJustFalling[x][y] &&
7922 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7924 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7925 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7926 (Tile[x][y + 1] == EL_BLOCKED)))
7928 /* this is needed for a special case not covered by calling "Impact()"
7929 from "ContinueMoving()": if an element moves to a tile directly below
7930 another element which was just falling on that tile (which was empty
7931 in the previous frame), the falling element above would just stop
7932 instead of smashing the element below (in previous version, the above
7933 element was just checked for "moving" instead of "falling", resulting
7934 in incorrect smashes caused by horizontal movement of the above
7935 element; also, the case of the player being the element to smash was
7936 simply not covered here... :-/ ) */
7938 CheckCollision[x][y] = 0;
7939 CheckImpact[x][y] = 0;
7943 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7945 if (MovDir[x][y] == MV_NONE)
7947 InitMovingField(x, y, MV_DOWN);
7948 started_moving = TRUE;
7951 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7953 if (WasJustFalling[x][y]) // prevent animation from being restarted
7954 MovDir[x][y] = MV_DOWN;
7956 InitMovingField(x, y, MV_DOWN);
7957 started_moving = TRUE;
7959 else if (element == EL_AMOEBA_DROP)
7961 Tile[x][y] = EL_AMOEBA_GROWING;
7962 Store[x][y] = EL_AMOEBA_WET;
7964 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7965 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7966 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7967 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7969 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7970 (IS_FREE(x - 1, y + 1) ||
7971 Tile[x - 1][y + 1] == EL_ACID));
7972 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7973 (IS_FREE(x + 1, y + 1) ||
7974 Tile[x + 1][y + 1] == EL_ACID));
7975 boolean can_fall_any = (can_fall_left || can_fall_right);
7976 boolean can_fall_both = (can_fall_left && can_fall_right);
7977 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7979 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7981 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7982 can_fall_right = FALSE;
7983 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7984 can_fall_left = FALSE;
7985 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7986 can_fall_right = FALSE;
7987 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7988 can_fall_left = FALSE;
7990 can_fall_any = (can_fall_left || can_fall_right);
7991 can_fall_both = FALSE;
7996 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7997 can_fall_right = FALSE; // slip down on left side
7999 can_fall_left = !(can_fall_right = RND(2));
8001 can_fall_both = FALSE;
8006 // if not determined otherwise, prefer left side for slipping down
8007 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8008 started_moving = TRUE;
8011 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8013 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8014 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8015 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8016 int belt_dir = game.belt_dir[belt_nr];
8018 if ((belt_dir == MV_LEFT && left_is_free) ||
8019 (belt_dir == MV_RIGHT && right_is_free))
8021 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8023 InitMovingField(x, y, belt_dir);
8024 started_moving = TRUE;
8026 Pushed[x][y] = TRUE;
8027 Pushed[nextx][y] = TRUE;
8029 GfxAction[x][y] = ACTION_DEFAULT;
8033 MovDir[x][y] = 0; // if element was moving, stop it
8038 // not "else if" because of elements that can fall and move (EL_SPRING)
8039 if (CAN_MOVE(element) && !started_moving)
8041 int move_pattern = element_info[element].move_pattern;
8044 Moving2Blocked(x, y, &newx, &newy);
8046 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8049 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8050 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8052 WasJustMoving[x][y] = 0;
8053 CheckCollision[x][y] = 0;
8055 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8057 if (Tile[x][y] != element) // element has changed
8061 if (!MovDelay[x][y]) // start new movement phase
8063 // all objects that can change their move direction after each step
8064 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8066 if (element != EL_YAMYAM &&
8067 element != EL_DARK_YAMYAM &&
8068 element != EL_PACMAN &&
8069 !(move_pattern & MV_ANY_DIRECTION) &&
8070 move_pattern != MV_TURNING_LEFT &&
8071 move_pattern != MV_TURNING_RIGHT &&
8072 move_pattern != MV_TURNING_LEFT_RIGHT &&
8073 move_pattern != MV_TURNING_RIGHT_LEFT &&
8074 move_pattern != MV_TURNING_RANDOM)
8078 if (MovDelay[x][y] && (element == EL_BUG ||
8079 element == EL_SPACESHIP ||
8080 element == EL_SP_SNIKSNAK ||
8081 element == EL_SP_ELECTRON ||
8082 element == EL_MOLE))
8083 TEST_DrawLevelField(x, y);
8087 if (MovDelay[x][y]) // wait some time before next movement
8091 if (element == EL_ROBOT ||
8092 element == EL_YAMYAM ||
8093 element == EL_DARK_YAMYAM)
8095 DrawLevelElementAnimationIfNeeded(x, y, element);
8096 PlayLevelSoundAction(x, y, ACTION_WAITING);
8098 else if (element == EL_SP_ELECTRON)
8099 DrawLevelElementAnimationIfNeeded(x, y, element);
8100 else if (element == EL_DRAGON)
8103 int dir = MovDir[x][y];
8104 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8105 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8106 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8107 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8108 dir == MV_UP ? IMG_FLAMES_1_UP :
8109 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8110 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8112 GfxAction[x][y] = ACTION_ATTACKING;
8114 if (IS_PLAYER(x, y))
8115 DrawPlayerField(x, y);
8117 TEST_DrawLevelField(x, y);
8119 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8121 for (i = 1; i <= 3; i++)
8123 int xx = x + i * dx;
8124 int yy = y + i * dy;
8125 int sx = SCREENX(xx);
8126 int sy = SCREENY(yy);
8127 int flame_graphic = graphic + (i - 1);
8129 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8134 int flamed = MovingOrBlocked2Element(xx, yy);
8136 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8139 RemoveMovingField(xx, yy);
8141 ChangeDelay[xx][yy] = 0;
8143 Tile[xx][yy] = EL_FLAMES;
8145 if (IN_SCR_FIELD(sx, sy))
8147 TEST_DrawLevelFieldCrumbled(xx, yy);
8148 DrawGraphic(sx, sy, flame_graphic, frame);
8153 if (Tile[xx][yy] == EL_FLAMES)
8154 Tile[xx][yy] = EL_EMPTY;
8155 TEST_DrawLevelField(xx, yy);
8160 if (MovDelay[x][y]) // element still has to wait some time
8162 PlayLevelSoundAction(x, y, ACTION_WAITING);
8168 // now make next step
8170 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8172 if (DONT_COLLIDE_WITH(element) &&
8173 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8174 !PLAYER_ENEMY_PROTECTED(newx, newy))
8176 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8181 else if (CAN_MOVE_INTO_ACID(element) &&
8182 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8183 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8184 (MovDir[x][y] == MV_DOWN ||
8185 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8187 SplashAcid(newx, newy);
8188 Store[x][y] = EL_ACID;
8190 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8192 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8193 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8194 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8195 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8198 TEST_DrawLevelField(x, y);
8200 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8201 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8202 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8204 game.friends_still_needed--;
8205 if (!game.friends_still_needed &&
8207 game.all_players_gone)
8212 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8214 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8215 TEST_DrawLevelField(newx, newy);
8217 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8219 else if (!IS_FREE(newx, newy))
8221 GfxAction[x][y] = ACTION_WAITING;
8223 if (IS_PLAYER(x, y))
8224 DrawPlayerField(x, y);
8226 TEST_DrawLevelField(x, y);
8231 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8233 if (IS_FOOD_PIG(Tile[newx][newy]))
8235 if (IS_MOVING(newx, newy))
8236 RemoveMovingField(newx, newy);
8239 Tile[newx][newy] = EL_EMPTY;
8240 TEST_DrawLevelField(newx, newy);
8243 PlayLevelSound(x, y, SND_PIG_DIGGING);
8245 else if (!IS_FREE(newx, newy))
8247 if (IS_PLAYER(x, y))
8248 DrawPlayerField(x, y);
8250 TEST_DrawLevelField(x, y);
8255 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8257 if (Store[x][y] != EL_EMPTY)
8259 boolean can_clone = FALSE;
8262 // check if element to clone is still there
8263 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8265 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8273 // cannot clone or target field not free anymore -- do not clone
8274 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8275 Store[x][y] = EL_EMPTY;
8278 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8280 if (IS_MV_DIAGONAL(MovDir[x][y]))
8282 int diagonal_move_dir = MovDir[x][y];
8283 int stored = Store[x][y];
8284 int change_delay = 8;
8287 // android is moving diagonally
8289 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8291 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8292 GfxElement[x][y] = EL_EMC_ANDROID;
8293 GfxAction[x][y] = ACTION_SHRINKING;
8294 GfxDir[x][y] = diagonal_move_dir;
8295 ChangeDelay[x][y] = change_delay;
8297 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8300 DrawLevelGraphicAnimation(x, y, graphic);
8301 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8303 if (Tile[newx][newy] == EL_ACID)
8305 SplashAcid(newx, newy);
8310 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8312 Store[newx][newy] = EL_EMC_ANDROID;
8313 GfxElement[newx][newy] = EL_EMC_ANDROID;
8314 GfxAction[newx][newy] = ACTION_GROWING;
8315 GfxDir[newx][newy] = diagonal_move_dir;
8316 ChangeDelay[newx][newy] = change_delay;
8318 graphic = el_act_dir2img(GfxElement[newx][newy],
8319 GfxAction[newx][newy], GfxDir[newx][newy]);
8321 DrawLevelGraphicAnimation(newx, newy, graphic);
8322 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8328 Tile[newx][newy] = EL_EMPTY;
8329 TEST_DrawLevelField(newx, newy);
8331 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8334 else if (!IS_FREE(newx, newy))
8339 else if (IS_CUSTOM_ELEMENT(element) &&
8340 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8342 if (!DigFieldByCE(newx, newy, element))
8345 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8347 RunnerVisit[x][y] = FrameCounter;
8348 PlayerVisit[x][y] /= 8; // expire player visit path
8351 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8353 if (!IS_FREE(newx, newy))
8355 if (IS_PLAYER(x, y))
8356 DrawPlayerField(x, y);
8358 TEST_DrawLevelField(x, y);
8364 boolean wanna_flame = !RND(10);
8365 int dx = newx - x, dy = newy - y;
8366 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8367 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8368 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8369 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8370 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8371 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8374 IS_CLASSIC_ENEMY(element1) ||
8375 IS_CLASSIC_ENEMY(element2)) &&
8376 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8377 element1 != EL_FLAMES && element2 != EL_FLAMES)
8379 ResetGfxAnimation(x, y);
8380 GfxAction[x][y] = ACTION_ATTACKING;
8382 if (IS_PLAYER(x, y))
8383 DrawPlayerField(x, y);
8385 TEST_DrawLevelField(x, y);
8387 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8389 MovDelay[x][y] = 50;
8391 Tile[newx][newy] = EL_FLAMES;
8392 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8393 Tile[newx1][newy1] = EL_FLAMES;
8394 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8395 Tile[newx2][newy2] = EL_FLAMES;
8401 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8402 Tile[newx][newy] == EL_DIAMOND)
8404 if (IS_MOVING(newx, newy))
8405 RemoveMovingField(newx, newy);
8408 Tile[newx][newy] = EL_EMPTY;
8409 TEST_DrawLevelField(newx, newy);
8412 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8414 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8415 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8417 if (AmoebaNr[newx][newy])
8419 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8420 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8421 Tile[newx][newy] == EL_BD_AMOEBA)
8422 AmoebaCnt[AmoebaNr[newx][newy]]--;
8425 if (IS_MOVING(newx, newy))
8427 RemoveMovingField(newx, newy);
8431 Tile[newx][newy] = EL_EMPTY;
8432 TEST_DrawLevelField(newx, newy);
8435 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8437 else if ((element == EL_PACMAN || element == EL_MOLE)
8438 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8440 if (AmoebaNr[newx][newy])
8442 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8443 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8444 Tile[newx][newy] == EL_BD_AMOEBA)
8445 AmoebaCnt[AmoebaNr[newx][newy]]--;
8448 if (element == EL_MOLE)
8450 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8451 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8453 ResetGfxAnimation(x, y);
8454 GfxAction[x][y] = ACTION_DIGGING;
8455 TEST_DrawLevelField(x, y);
8457 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8459 return; // wait for shrinking amoeba
8461 else // element == EL_PACMAN
8463 Tile[newx][newy] = EL_EMPTY;
8464 TEST_DrawLevelField(newx, newy);
8465 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8468 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8469 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8470 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8472 // wait for shrinking amoeba to completely disappear
8475 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8477 // object was running against a wall
8481 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8482 DrawLevelElementAnimation(x, y, element);
8484 if (DONT_TOUCH(element))
8485 TestIfBadThingTouchesPlayer(x, y);
8490 InitMovingField(x, y, MovDir[x][y]);
8492 PlayLevelSoundAction(x, y, ACTION_MOVING);
8496 ContinueMoving(x, y);
8499 void ContinueMoving(int x, int y)
8501 int element = Tile[x][y];
8502 struct ElementInfo *ei = &element_info[element];
8503 int direction = MovDir[x][y];
8504 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8505 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8506 int newx = x + dx, newy = y + dy;
8507 int stored = Store[x][y];
8508 int stored_new = Store[newx][newy];
8509 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8510 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8511 boolean last_line = (newy == lev_fieldy - 1);
8513 MovPos[x][y] += getElementMoveStepsize(x, y);
8515 if (pushed_by_player) // special case: moving object pushed by player
8516 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8518 if (ABS(MovPos[x][y]) < TILEX)
8520 TEST_DrawLevelField(x, y);
8522 return; // element is still moving
8525 // element reached destination field
8527 Tile[x][y] = EL_EMPTY;
8528 Tile[newx][newy] = element;
8529 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8531 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8533 element = Tile[newx][newy] = EL_ACID;
8535 else if (element == EL_MOLE)
8537 Tile[x][y] = EL_SAND;
8539 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8541 else if (element == EL_QUICKSAND_FILLING)
8543 element = Tile[newx][newy] = get_next_element(element);
8544 Store[newx][newy] = Store[x][y];
8546 else if (element == EL_QUICKSAND_EMPTYING)
8548 Tile[x][y] = get_next_element(element);
8549 element = Tile[newx][newy] = Store[x][y];
8551 else if (element == EL_QUICKSAND_FAST_FILLING)
8553 element = Tile[newx][newy] = get_next_element(element);
8554 Store[newx][newy] = Store[x][y];
8556 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8558 Tile[x][y] = get_next_element(element);
8559 element = Tile[newx][newy] = Store[x][y];
8561 else if (element == EL_MAGIC_WALL_FILLING)
8563 element = Tile[newx][newy] = get_next_element(element);
8564 if (!game.magic_wall_active)
8565 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8566 Store[newx][newy] = Store[x][y];
8568 else if (element == EL_MAGIC_WALL_EMPTYING)
8570 Tile[x][y] = get_next_element(element);
8571 if (!game.magic_wall_active)
8572 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8573 element = Tile[newx][newy] = Store[x][y];
8575 InitField(newx, newy, FALSE);
8577 else if (element == EL_BD_MAGIC_WALL_FILLING)
8579 element = Tile[newx][newy] = get_next_element(element);
8580 if (!game.magic_wall_active)
8581 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8582 Store[newx][newy] = Store[x][y];
8584 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8586 Tile[x][y] = get_next_element(element);
8587 if (!game.magic_wall_active)
8588 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8589 element = Tile[newx][newy] = Store[x][y];
8591 InitField(newx, newy, FALSE);
8593 else if (element == EL_DC_MAGIC_WALL_FILLING)
8595 element = Tile[newx][newy] = get_next_element(element);
8596 if (!game.magic_wall_active)
8597 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8598 Store[newx][newy] = Store[x][y];
8600 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8602 Tile[x][y] = get_next_element(element);
8603 if (!game.magic_wall_active)
8604 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8605 element = Tile[newx][newy] = Store[x][y];
8607 InitField(newx, newy, FALSE);
8609 else if (element == EL_AMOEBA_DROPPING)
8611 Tile[x][y] = get_next_element(element);
8612 element = Tile[newx][newy] = Store[x][y];
8614 else if (element == EL_SOKOBAN_OBJECT)
8617 Tile[x][y] = Back[x][y];
8619 if (Back[newx][newy])
8620 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8622 Back[x][y] = Back[newx][newy] = 0;
8625 Store[x][y] = EL_EMPTY;
8630 MovDelay[newx][newy] = 0;
8632 if (CAN_CHANGE_OR_HAS_ACTION(element))
8634 // copy element change control values to new field
8635 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8636 ChangePage[newx][newy] = ChangePage[x][y];
8637 ChangeCount[newx][newy] = ChangeCount[x][y];
8638 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8641 CustomValue[newx][newy] = CustomValue[x][y];
8643 ChangeDelay[x][y] = 0;
8644 ChangePage[x][y] = -1;
8645 ChangeCount[x][y] = 0;
8646 ChangeEvent[x][y] = -1;
8648 CustomValue[x][y] = 0;
8650 // copy animation control values to new field
8651 GfxFrame[newx][newy] = GfxFrame[x][y];
8652 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8653 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8654 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8656 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8658 // some elements can leave other elements behind after moving
8659 if (ei->move_leave_element != EL_EMPTY &&
8660 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8661 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8663 int move_leave_element = ei->move_leave_element;
8665 // this makes it possible to leave the removed element again
8666 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8667 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8669 Tile[x][y] = move_leave_element;
8671 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8672 MovDir[x][y] = direction;
8674 InitField(x, y, FALSE);
8676 if (GFX_CRUMBLED(Tile[x][y]))
8677 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8679 if (ELEM_IS_PLAYER(move_leave_element))
8680 RelocatePlayer(x, y, move_leave_element);
8683 // do this after checking for left-behind element
8684 ResetGfxAnimation(x, y); // reset animation values for old field
8686 if (!CAN_MOVE(element) ||
8687 (CAN_FALL(element) && direction == MV_DOWN &&
8688 (element == EL_SPRING ||
8689 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8690 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8691 GfxDir[x][y] = MovDir[newx][newy] = 0;
8693 TEST_DrawLevelField(x, y);
8694 TEST_DrawLevelField(newx, newy);
8696 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8698 // prevent pushed element from moving on in pushed direction
8699 if (pushed_by_player && CAN_MOVE(element) &&
8700 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8701 !(element_info[element].move_pattern & direction))
8702 TurnRound(newx, newy);
8704 // prevent elements on conveyor belt from moving on in last direction
8705 if (pushed_by_conveyor && CAN_FALL(element) &&
8706 direction & MV_HORIZONTAL)
8707 MovDir[newx][newy] = 0;
8709 if (!pushed_by_player)
8711 int nextx = newx + dx, nexty = newy + dy;
8712 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8714 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8716 if (CAN_FALL(element) && direction == MV_DOWN)
8717 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8719 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8720 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8722 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8723 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8726 if (DONT_TOUCH(element)) // object may be nasty to player or others
8728 TestIfBadThingTouchesPlayer(newx, newy);
8729 TestIfBadThingTouchesFriend(newx, newy);
8731 if (!IS_CUSTOM_ELEMENT(element))
8732 TestIfBadThingTouchesOtherBadThing(newx, newy);
8734 else if (element == EL_PENGUIN)
8735 TestIfFriendTouchesBadThing(newx, newy);
8737 if (DONT_GET_HIT_BY(element))
8739 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8742 // give the player one last chance (one more frame) to move away
8743 if (CAN_FALL(element) && direction == MV_DOWN &&
8744 (last_line || (!IS_FREE(x, newy + 1) &&
8745 (!IS_PLAYER(x, newy + 1) ||
8746 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8749 if (pushed_by_player && !game.use_change_when_pushing_bug)
8751 int push_side = MV_DIR_OPPOSITE(direction);
8752 struct PlayerInfo *player = PLAYERINFO(x, y);
8754 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8755 player->index_bit, push_side);
8756 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8757 player->index_bit, push_side);
8760 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8761 MovDelay[newx][newy] = 1;
8763 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8765 TestIfElementTouchesCustomElement(x, y); // empty or new element
8766 TestIfElementHitsCustomElement(newx, newy, direction);
8767 TestIfPlayerTouchesCustomElement(newx, newy);
8768 TestIfElementTouchesCustomElement(newx, newy);
8770 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8771 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8772 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8773 MV_DIR_OPPOSITE(direction));
8776 int AmoebaNeighbourNr(int ax, int ay)
8779 int element = Tile[ax][ay];
8781 static int xy[4][2] =
8789 for (i = 0; i < NUM_DIRECTIONS; i++)
8791 int x = ax + xy[i][0];
8792 int y = ay + xy[i][1];
8794 if (!IN_LEV_FIELD(x, y))
8797 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8798 group_nr = AmoebaNr[x][y];
8804 static void AmoebaMerge(int ax, int ay)
8806 int i, x, y, xx, yy;
8807 int new_group_nr = AmoebaNr[ax][ay];
8808 static int xy[4][2] =
8816 if (new_group_nr == 0)
8819 for (i = 0; i < NUM_DIRECTIONS; i++)
8824 if (!IN_LEV_FIELD(x, y))
8827 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8828 Tile[x][y] == EL_BD_AMOEBA ||
8829 Tile[x][y] == EL_AMOEBA_DEAD) &&
8830 AmoebaNr[x][y] != new_group_nr)
8832 int old_group_nr = AmoebaNr[x][y];
8834 if (old_group_nr == 0)
8837 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8838 AmoebaCnt[old_group_nr] = 0;
8839 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8840 AmoebaCnt2[old_group_nr] = 0;
8842 SCAN_PLAYFIELD(xx, yy)
8844 if (AmoebaNr[xx][yy] == old_group_nr)
8845 AmoebaNr[xx][yy] = new_group_nr;
8851 void AmoebaToDiamond(int ax, int ay)
8855 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8857 int group_nr = AmoebaNr[ax][ay];
8862 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8863 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8869 SCAN_PLAYFIELD(x, y)
8871 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8874 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8878 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8879 SND_AMOEBA_TURNING_TO_GEM :
8880 SND_AMOEBA_TURNING_TO_ROCK));
8885 static int xy[4][2] =
8893 for (i = 0; i < NUM_DIRECTIONS; i++)
8898 if (!IN_LEV_FIELD(x, y))
8901 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8903 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8904 SND_AMOEBA_TURNING_TO_GEM :
8905 SND_AMOEBA_TURNING_TO_ROCK));
8912 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8915 int group_nr = AmoebaNr[ax][ay];
8916 boolean done = FALSE;
8921 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8922 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8928 SCAN_PLAYFIELD(x, y)
8930 if (AmoebaNr[x][y] == group_nr &&
8931 (Tile[x][y] == EL_AMOEBA_DEAD ||
8932 Tile[x][y] == EL_BD_AMOEBA ||
8933 Tile[x][y] == EL_AMOEBA_GROWING))
8936 Tile[x][y] = new_element;
8937 InitField(x, y, FALSE);
8938 TEST_DrawLevelField(x, y);
8944 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8945 SND_BD_AMOEBA_TURNING_TO_ROCK :
8946 SND_BD_AMOEBA_TURNING_TO_GEM));
8949 static void AmoebaGrowing(int x, int y)
8951 static unsigned int sound_delay = 0;
8952 static unsigned int sound_delay_value = 0;
8954 if (!MovDelay[x][y]) // start new growing cycle
8958 if (DelayReached(&sound_delay, sound_delay_value))
8960 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8961 sound_delay_value = 30;
8965 if (MovDelay[x][y]) // wait some time before growing bigger
8968 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8970 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8971 6 - MovDelay[x][y]);
8973 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8976 if (!MovDelay[x][y])
8978 Tile[x][y] = Store[x][y];
8980 TEST_DrawLevelField(x, y);
8985 static void AmoebaShrinking(int x, int y)
8987 static unsigned int sound_delay = 0;
8988 static unsigned int sound_delay_value = 0;
8990 if (!MovDelay[x][y]) // start new shrinking cycle
8994 if (DelayReached(&sound_delay, sound_delay_value))
8995 sound_delay_value = 30;
8998 if (MovDelay[x][y]) // wait some time before shrinking
9001 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9003 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9004 6 - MovDelay[x][y]);
9006 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9009 if (!MovDelay[x][y])
9011 Tile[x][y] = EL_EMPTY;
9012 TEST_DrawLevelField(x, y);
9014 // don't let mole enter this field in this cycle;
9015 // (give priority to objects falling to this field from above)
9021 static void AmoebaReproduce(int ax, int ay)
9024 int element = Tile[ax][ay];
9025 int graphic = el2img(element);
9026 int newax = ax, neway = ay;
9027 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9028 static int xy[4][2] =
9036 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9038 Tile[ax][ay] = EL_AMOEBA_DEAD;
9039 TEST_DrawLevelField(ax, ay);
9043 if (IS_ANIMATED(graphic))
9044 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9046 if (!MovDelay[ax][ay]) // start making new amoeba field
9047 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9049 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9052 if (MovDelay[ax][ay])
9056 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9059 int x = ax + xy[start][0];
9060 int y = ay + xy[start][1];
9062 if (!IN_LEV_FIELD(x, y))
9065 if (IS_FREE(x, y) ||
9066 CAN_GROW_INTO(Tile[x][y]) ||
9067 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9068 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9074 if (newax == ax && neway == ay)
9077 else // normal or "filled" (BD style) amoeba
9080 boolean waiting_for_player = FALSE;
9082 for (i = 0; i < NUM_DIRECTIONS; i++)
9084 int j = (start + i) % 4;
9085 int x = ax + xy[j][0];
9086 int y = ay + xy[j][1];
9088 if (!IN_LEV_FIELD(x, y))
9091 if (IS_FREE(x, y) ||
9092 CAN_GROW_INTO(Tile[x][y]) ||
9093 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9094 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9100 else if (IS_PLAYER(x, y))
9101 waiting_for_player = TRUE;
9104 if (newax == ax && neway == ay) // amoeba cannot grow
9106 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9108 Tile[ax][ay] = EL_AMOEBA_DEAD;
9109 TEST_DrawLevelField(ax, ay);
9110 AmoebaCnt[AmoebaNr[ax][ay]]--;
9112 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9114 if (element == EL_AMOEBA_FULL)
9115 AmoebaToDiamond(ax, ay);
9116 else if (element == EL_BD_AMOEBA)
9117 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9122 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9124 // amoeba gets larger by growing in some direction
9126 int new_group_nr = AmoebaNr[ax][ay];
9129 if (new_group_nr == 0)
9131 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9133 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9139 AmoebaNr[newax][neway] = new_group_nr;
9140 AmoebaCnt[new_group_nr]++;
9141 AmoebaCnt2[new_group_nr]++;
9143 // if amoeba touches other amoeba(s) after growing, unify them
9144 AmoebaMerge(newax, neway);
9146 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9148 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9154 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9155 (neway == lev_fieldy - 1 && newax != ax))
9157 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9158 Store[newax][neway] = element;
9160 else if (neway == ay || element == EL_EMC_DRIPPER)
9162 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9164 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9168 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9169 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9170 Store[ax][ay] = EL_AMOEBA_DROP;
9171 ContinueMoving(ax, ay);
9175 TEST_DrawLevelField(newax, neway);
9178 static void Life(int ax, int ay)
9182 int element = Tile[ax][ay];
9183 int graphic = el2img(element);
9184 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9186 boolean changed = FALSE;
9188 if (IS_ANIMATED(graphic))
9189 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9194 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9195 MovDelay[ax][ay] = life_time;
9197 if (MovDelay[ax][ay]) // wait some time before next cycle
9200 if (MovDelay[ax][ay])
9204 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9206 int xx = ax+x1, yy = ay+y1;
9207 int old_element = Tile[xx][yy];
9208 int num_neighbours = 0;
9210 if (!IN_LEV_FIELD(xx, yy))
9213 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9215 int x = xx+x2, y = yy+y2;
9217 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9220 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9221 boolean is_neighbour = FALSE;
9223 if (level.use_life_bugs)
9225 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9226 (IS_FREE(x, y) && Stop[x][y]));
9229 (Last[x][y] == element || is_player_cell);
9235 boolean is_free = FALSE;
9237 if (level.use_life_bugs)
9238 is_free = (IS_FREE(xx, yy));
9240 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9242 if (xx == ax && yy == ay) // field in the middle
9244 if (num_neighbours < life_parameter[0] ||
9245 num_neighbours > life_parameter[1])
9247 Tile[xx][yy] = EL_EMPTY;
9248 if (Tile[xx][yy] != old_element)
9249 TEST_DrawLevelField(xx, yy);
9250 Stop[xx][yy] = TRUE;
9254 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9255 { // free border field
9256 if (num_neighbours >= life_parameter[2] &&
9257 num_neighbours <= life_parameter[3])
9259 Tile[xx][yy] = element;
9260 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9261 if (Tile[xx][yy] != old_element)
9262 TEST_DrawLevelField(xx, yy);
9263 Stop[xx][yy] = TRUE;
9270 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9271 SND_GAME_OF_LIFE_GROWING);
9274 static void InitRobotWheel(int x, int y)
9276 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9279 static void RunRobotWheel(int x, int y)
9281 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9284 static void StopRobotWheel(int x, int y)
9286 if (game.robot_wheel_x == x &&
9287 game.robot_wheel_y == y)
9289 game.robot_wheel_x = -1;
9290 game.robot_wheel_y = -1;
9291 game.robot_wheel_active = FALSE;
9295 static void InitTimegateWheel(int x, int y)
9297 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9300 static void RunTimegateWheel(int x, int y)
9302 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9305 static void InitMagicBallDelay(int x, int y)
9307 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9310 static void ActivateMagicBall(int bx, int by)
9314 if (level.ball_random)
9316 int pos_border = RND(8); // select one of the eight border elements
9317 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9318 int xx = pos_content % 3;
9319 int yy = pos_content / 3;
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 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9331 int xx = x - bx + 1;
9332 int yy = y - by + 1;
9334 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9335 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9339 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9342 static void CheckExit(int x, int y)
9344 if (game.gems_still_needed > 0 ||
9345 game.sokoban_fields_still_needed > 0 ||
9346 game.sokoban_objects_still_needed > 0 ||
9347 game.lights_still_needed > 0)
9349 int element = Tile[x][y];
9350 int graphic = el2img(element);
9352 if (IS_ANIMATED(graphic))
9353 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9358 // do not re-open exit door closed after last player
9359 if (game.all_players_gone)
9362 Tile[x][y] = EL_EXIT_OPENING;
9364 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9367 static void CheckExitEM(int x, int y)
9369 if (game.gems_still_needed > 0 ||
9370 game.sokoban_fields_still_needed > 0 ||
9371 game.sokoban_objects_still_needed > 0 ||
9372 game.lights_still_needed > 0)
9374 int element = Tile[x][y];
9375 int graphic = el2img(element);
9377 if (IS_ANIMATED(graphic))
9378 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9383 // do not re-open exit door closed after last player
9384 if (game.all_players_gone)
9387 Tile[x][y] = EL_EM_EXIT_OPENING;
9389 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9392 static void CheckExitSteel(int x, int y)
9394 if (game.gems_still_needed > 0 ||
9395 game.sokoban_fields_still_needed > 0 ||
9396 game.sokoban_objects_still_needed > 0 ||
9397 game.lights_still_needed > 0)
9399 int element = Tile[x][y];
9400 int graphic = el2img(element);
9402 if (IS_ANIMATED(graphic))
9403 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9408 // do not re-open exit door closed after last player
9409 if (game.all_players_gone)
9412 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9414 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9417 static void CheckExitSteelEM(int x, int y)
9419 if (game.gems_still_needed > 0 ||
9420 game.sokoban_fields_still_needed > 0 ||
9421 game.sokoban_objects_still_needed > 0 ||
9422 game.lights_still_needed > 0)
9424 int element = Tile[x][y];
9425 int graphic = el2img(element);
9427 if (IS_ANIMATED(graphic))
9428 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9433 // do not re-open exit door closed after last player
9434 if (game.all_players_gone)
9437 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9439 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9442 static void CheckExitSP(int x, int y)
9444 if (game.gems_still_needed > 0)
9446 int element = Tile[x][y];
9447 int graphic = el2img(element);
9449 if (IS_ANIMATED(graphic))
9450 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9455 // do not re-open exit door closed after last player
9456 if (game.all_players_gone)
9459 Tile[x][y] = EL_SP_EXIT_OPENING;
9461 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9464 static void CloseAllOpenTimegates(void)
9468 SCAN_PLAYFIELD(x, y)
9470 int element = Tile[x][y];
9472 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9474 Tile[x][y] = EL_TIMEGATE_CLOSING;
9476 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9481 static void DrawTwinkleOnField(int x, int y)
9483 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9486 if (Tile[x][y] == EL_BD_DIAMOND)
9489 if (MovDelay[x][y] == 0) // next animation frame
9490 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9492 if (MovDelay[x][y] != 0) // wait some time before next frame
9496 DrawLevelElementAnimation(x, y, Tile[x][y]);
9498 if (MovDelay[x][y] != 0)
9500 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9501 10 - MovDelay[x][y]);
9503 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9508 static void MauerWaechst(int x, int y)
9512 if (!MovDelay[x][y]) // next animation frame
9513 MovDelay[x][y] = 3 * delay;
9515 if (MovDelay[x][y]) // wait some time before next frame
9519 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9521 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9522 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9524 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9527 if (!MovDelay[x][y])
9529 if (MovDir[x][y] == MV_LEFT)
9531 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9532 TEST_DrawLevelField(x - 1, y);
9534 else if (MovDir[x][y] == MV_RIGHT)
9536 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9537 TEST_DrawLevelField(x + 1, y);
9539 else if (MovDir[x][y] == MV_UP)
9541 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9542 TEST_DrawLevelField(x, y - 1);
9546 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9547 TEST_DrawLevelField(x, y + 1);
9550 Tile[x][y] = Store[x][y];
9552 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9553 TEST_DrawLevelField(x, y);
9558 static void MauerAbleger(int ax, int ay)
9560 int element = Tile[ax][ay];
9561 int graphic = el2img(element);
9562 boolean oben_frei = FALSE, unten_frei = FALSE;
9563 boolean links_frei = FALSE, rechts_frei = FALSE;
9564 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9565 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9566 boolean new_wall = FALSE;
9568 if (IS_ANIMATED(graphic))
9569 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9571 if (!MovDelay[ax][ay]) // start building new wall
9572 MovDelay[ax][ay] = 6;
9574 if (MovDelay[ax][ay]) // wait some time before building new wall
9577 if (MovDelay[ax][ay])
9581 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9583 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9585 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9587 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9590 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9591 element == EL_EXPANDABLE_WALL_ANY)
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_UP;
9598 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9599 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9600 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9605 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9606 Store[ax][ay+1] = element;
9607 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9608 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9609 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9610 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9615 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9616 element == EL_EXPANDABLE_WALL_ANY ||
9617 element == EL_EXPANDABLE_WALL ||
9618 element == EL_BD_EXPANDABLE_WALL)
9622 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9623 Store[ax-1][ay] = element;
9624 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9625 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9626 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9627 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9633 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9634 Store[ax+1][ay] = element;
9635 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9636 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9637 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9638 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9643 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9644 TEST_DrawLevelField(ax, ay);
9646 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9648 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9649 unten_massiv = TRUE;
9650 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9651 links_massiv = TRUE;
9652 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9653 rechts_massiv = TRUE;
9655 if (((oben_massiv && unten_massiv) ||
9656 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9657 element == EL_EXPANDABLE_WALL) &&
9658 ((links_massiv && rechts_massiv) ||
9659 element == EL_EXPANDABLE_WALL_VERTICAL))
9660 Tile[ax][ay] = EL_WALL;
9663 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9666 static void MauerAblegerStahl(int ax, int ay)
9668 int element = Tile[ax][ay];
9669 int graphic = el2img(element);
9670 boolean oben_frei = FALSE, unten_frei = FALSE;
9671 boolean links_frei = FALSE, rechts_frei = FALSE;
9672 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9673 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9674 boolean new_wall = FALSE;
9676 if (IS_ANIMATED(graphic))
9677 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9679 if (!MovDelay[ax][ay]) // start building new wall
9680 MovDelay[ax][ay] = 6;
9682 if (MovDelay[ax][ay]) // wait some time before building new wall
9685 if (MovDelay[ax][ay])
9689 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9691 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9693 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9695 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9698 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9699 element == EL_EXPANDABLE_STEELWALL_ANY)
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_UP;
9706 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9707 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9708 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9713 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9714 Store[ax][ay+1] = element;
9715 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9716 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9717 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9718 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9723 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9724 element == EL_EXPANDABLE_STEELWALL_ANY)
9728 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9729 Store[ax-1][ay] = element;
9730 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9731 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9732 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9733 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9739 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9740 Store[ax+1][ay] = element;
9741 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9742 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9743 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9744 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9749 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9751 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9752 unten_massiv = TRUE;
9753 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9754 links_massiv = TRUE;
9755 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9756 rechts_massiv = TRUE;
9758 if (((oben_massiv && unten_massiv) ||
9759 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9760 ((links_massiv && rechts_massiv) ||
9761 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9762 Tile[ax][ay] = EL_STEELWALL;
9765 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9768 static void CheckForDragon(int x, int y)
9771 boolean dragon_found = FALSE;
9772 static int xy[4][2] =
9780 for (i = 0; i < NUM_DIRECTIONS; i++)
9782 for (j = 0; j < 4; j++)
9784 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9786 if (IN_LEV_FIELD(xx, yy) &&
9787 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9789 if (Tile[xx][yy] == EL_DRAGON)
9790 dragon_found = TRUE;
9799 for (i = 0; i < NUM_DIRECTIONS; i++)
9801 for (j = 0; j < 3; j++)
9803 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9805 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9807 Tile[xx][yy] = EL_EMPTY;
9808 TEST_DrawLevelField(xx, yy);
9817 static void InitBuggyBase(int x, int y)
9819 int element = Tile[x][y];
9820 int activating_delay = FRAMES_PER_SECOND / 4;
9823 (element == EL_SP_BUGGY_BASE ?
9824 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9825 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9827 element == EL_SP_BUGGY_BASE_ACTIVE ?
9828 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9831 static void WarnBuggyBase(int x, int y)
9834 static int xy[4][2] =
9842 for (i = 0; i < NUM_DIRECTIONS; i++)
9844 int xx = x + xy[i][0];
9845 int yy = y + xy[i][1];
9847 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9849 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9856 static void InitTrap(int x, int y)
9858 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9861 static void ActivateTrap(int x, int y)
9863 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9866 static void ChangeActiveTrap(int x, int y)
9868 int graphic = IMG_TRAP_ACTIVE;
9870 // if new animation frame was drawn, correct crumbled sand border
9871 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9872 TEST_DrawLevelFieldCrumbled(x, y);
9875 static int getSpecialActionElement(int element, int number, int base_element)
9877 return (element != EL_EMPTY ? element :
9878 number != -1 ? base_element + number - 1 :
9882 static int getModifiedActionNumber(int value_old, int operator, int operand,
9883 int value_min, int value_max)
9885 int value_new = (operator == CA_MODE_SET ? operand :
9886 operator == CA_MODE_ADD ? value_old + operand :
9887 operator == CA_MODE_SUBTRACT ? value_old - operand :
9888 operator == CA_MODE_MULTIPLY ? value_old * operand :
9889 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9890 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9893 return (value_new < value_min ? value_min :
9894 value_new > value_max ? value_max :
9898 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9900 struct ElementInfo *ei = &element_info[element];
9901 struct ElementChangeInfo *change = &ei->change_page[page];
9902 int target_element = change->target_element;
9903 int action_type = change->action_type;
9904 int action_mode = change->action_mode;
9905 int action_arg = change->action_arg;
9906 int action_element = change->action_element;
9909 if (!change->has_action)
9912 // ---------- determine action paramater values -----------------------------
9914 int level_time_value =
9915 (level.time > 0 ? TimeLeft :
9918 int action_arg_element_raw =
9919 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9920 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9921 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9922 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9923 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9924 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9925 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9927 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9929 int action_arg_direction =
9930 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9931 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9932 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9933 change->actual_trigger_side :
9934 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9935 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9938 int action_arg_number_min =
9939 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9942 int action_arg_number_max =
9943 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9944 action_type == CA_SET_LEVEL_GEMS ? 999 :
9945 action_type == CA_SET_LEVEL_TIME ? 9999 :
9946 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9947 action_type == CA_SET_CE_VALUE ? 9999 :
9948 action_type == CA_SET_CE_SCORE ? 9999 :
9951 int action_arg_number_reset =
9952 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9953 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9954 action_type == CA_SET_LEVEL_TIME ? level.time :
9955 action_type == CA_SET_LEVEL_SCORE ? 0 :
9956 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9957 action_type == CA_SET_CE_SCORE ? 0 :
9960 int action_arg_number =
9961 (action_arg <= CA_ARG_MAX ? action_arg :
9962 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9963 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9964 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9965 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9966 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9967 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9968 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9969 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9970 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9971 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9972 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9973 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9974 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9975 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9976 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9977 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9978 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9979 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9980 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9981 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9982 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9985 int action_arg_number_old =
9986 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9987 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9988 action_type == CA_SET_LEVEL_SCORE ? game.score :
9989 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9990 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9993 int action_arg_number_new =
9994 getModifiedActionNumber(action_arg_number_old,
9995 action_mode, action_arg_number,
9996 action_arg_number_min, action_arg_number_max);
9998 int trigger_player_bits =
9999 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10000 change->actual_trigger_player_bits : change->trigger_player);
10002 int action_arg_player_bits =
10003 (action_arg >= CA_ARG_PLAYER_1 &&
10004 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10005 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10006 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10009 // ---------- execute action -----------------------------------------------
10011 switch (action_type)
10018 // ---------- level actions ----------------------------------------------
10020 case CA_RESTART_LEVEL:
10022 game.restart_level = TRUE;
10027 case CA_SHOW_ENVELOPE:
10029 int element = getSpecialActionElement(action_arg_element,
10030 action_arg_number, EL_ENVELOPE_1);
10032 if (IS_ENVELOPE(element))
10033 local_player->show_envelope = element;
10038 case CA_SET_LEVEL_TIME:
10040 if (level.time > 0) // only modify limited time value
10042 TimeLeft = action_arg_number_new;
10044 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10046 DisplayGameControlValues();
10048 if (!TimeLeft && setup.time_limit)
10049 for (i = 0; i < MAX_PLAYERS; i++)
10050 KillPlayer(&stored_player[i]);
10056 case CA_SET_LEVEL_SCORE:
10058 game.score = action_arg_number_new;
10060 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10062 DisplayGameControlValues();
10067 case CA_SET_LEVEL_GEMS:
10069 game.gems_still_needed = action_arg_number_new;
10071 game.snapshot.collected_item = TRUE;
10073 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10075 DisplayGameControlValues();
10080 case CA_SET_LEVEL_WIND:
10082 game.wind_direction = action_arg_direction;
10087 case CA_SET_LEVEL_RANDOM_SEED:
10089 // ensure that setting a new random seed while playing is predictable
10090 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10095 // ---------- player actions ---------------------------------------------
10097 case CA_MOVE_PLAYER:
10098 case CA_MOVE_PLAYER_NEW:
10100 // automatically move to the next field in specified direction
10101 for (i = 0; i < MAX_PLAYERS; i++)
10102 if (trigger_player_bits & (1 << i))
10103 if (action_type == CA_MOVE_PLAYER ||
10104 stored_player[i].MovPos == 0)
10105 stored_player[i].programmed_action = action_arg_direction;
10110 case CA_EXIT_PLAYER:
10112 for (i = 0; i < MAX_PLAYERS; i++)
10113 if (action_arg_player_bits & (1 << i))
10114 ExitPlayer(&stored_player[i]);
10116 if (game.players_still_needed == 0)
10122 case CA_KILL_PLAYER:
10124 for (i = 0; i < MAX_PLAYERS; i++)
10125 if (action_arg_player_bits & (1 << i))
10126 KillPlayer(&stored_player[i]);
10131 case CA_SET_PLAYER_KEYS:
10133 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10134 int element = getSpecialActionElement(action_arg_element,
10135 action_arg_number, EL_KEY_1);
10137 if (IS_KEY(element))
10139 for (i = 0; i < MAX_PLAYERS; i++)
10141 if (trigger_player_bits & (1 << i))
10143 stored_player[i].key[KEY_NR(element)] = key_state;
10145 DrawGameDoorValues();
10153 case CA_SET_PLAYER_SPEED:
10155 for (i = 0; i < MAX_PLAYERS; i++)
10157 if (trigger_player_bits & (1 << i))
10159 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10161 if (action_arg == CA_ARG_SPEED_FASTER &&
10162 stored_player[i].cannot_move)
10164 action_arg_number = STEPSIZE_VERY_SLOW;
10166 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10167 action_arg == CA_ARG_SPEED_FASTER)
10169 action_arg_number = 2;
10170 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10173 else if (action_arg == CA_ARG_NUMBER_RESET)
10175 action_arg_number = level.initial_player_stepsize[i];
10179 getModifiedActionNumber(move_stepsize,
10182 action_arg_number_min,
10183 action_arg_number_max);
10185 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10192 case CA_SET_PLAYER_SHIELD:
10194 for (i = 0; i < MAX_PLAYERS; i++)
10196 if (trigger_player_bits & (1 << i))
10198 if (action_arg == CA_ARG_SHIELD_OFF)
10200 stored_player[i].shield_normal_time_left = 0;
10201 stored_player[i].shield_deadly_time_left = 0;
10203 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10205 stored_player[i].shield_normal_time_left = 999999;
10207 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10209 stored_player[i].shield_normal_time_left = 999999;
10210 stored_player[i].shield_deadly_time_left = 999999;
10218 case CA_SET_PLAYER_GRAVITY:
10220 for (i = 0; i < MAX_PLAYERS; i++)
10222 if (trigger_player_bits & (1 << i))
10224 stored_player[i].gravity =
10225 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10226 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10227 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10228 stored_player[i].gravity);
10235 case CA_SET_PLAYER_ARTWORK:
10237 for (i = 0; i < MAX_PLAYERS; i++)
10239 if (trigger_player_bits & (1 << i))
10241 int artwork_element = action_arg_element;
10243 if (action_arg == CA_ARG_ELEMENT_RESET)
10245 (level.use_artwork_element[i] ? level.artwork_element[i] :
10246 stored_player[i].element_nr);
10248 if (stored_player[i].artwork_element != artwork_element)
10249 stored_player[i].Frame = 0;
10251 stored_player[i].artwork_element = artwork_element;
10253 SetPlayerWaiting(&stored_player[i], FALSE);
10255 // set number of special actions for bored and sleeping animation
10256 stored_player[i].num_special_action_bored =
10257 get_num_special_action(artwork_element,
10258 ACTION_BORING_1, ACTION_BORING_LAST);
10259 stored_player[i].num_special_action_sleeping =
10260 get_num_special_action(artwork_element,
10261 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10268 case CA_SET_PLAYER_INVENTORY:
10270 for (i = 0; i < MAX_PLAYERS; i++)
10272 struct PlayerInfo *player = &stored_player[i];
10275 if (trigger_player_bits & (1 << i))
10277 int inventory_element = action_arg_element;
10279 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10280 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10281 action_arg == CA_ARG_ELEMENT_ACTION)
10283 int element = inventory_element;
10284 int collect_count = element_info[element].collect_count_initial;
10286 if (!IS_CUSTOM_ELEMENT(element))
10289 if (collect_count == 0)
10290 player->inventory_infinite_element = element;
10292 for (k = 0; k < collect_count; k++)
10293 if (player->inventory_size < MAX_INVENTORY_SIZE)
10294 player->inventory_element[player->inventory_size++] =
10297 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10298 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10299 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10301 if (player->inventory_infinite_element != EL_UNDEFINED &&
10302 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10303 action_arg_element_raw))
10304 player->inventory_infinite_element = EL_UNDEFINED;
10306 for (k = 0, j = 0; j < player->inventory_size; j++)
10308 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10309 action_arg_element_raw))
10310 player->inventory_element[k++] = player->inventory_element[j];
10313 player->inventory_size = k;
10315 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10317 if (player->inventory_size > 0)
10319 for (j = 0; j < player->inventory_size - 1; j++)
10320 player->inventory_element[j] = player->inventory_element[j + 1];
10322 player->inventory_size--;
10325 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10327 if (player->inventory_size > 0)
10328 player->inventory_size--;
10330 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10332 player->inventory_infinite_element = EL_UNDEFINED;
10333 player->inventory_size = 0;
10335 else if (action_arg == CA_ARG_INVENTORY_RESET)
10337 player->inventory_infinite_element = EL_UNDEFINED;
10338 player->inventory_size = 0;
10340 if (level.use_initial_inventory[i])
10342 for (j = 0; j < level.initial_inventory_size[i]; j++)
10344 int element = level.initial_inventory_content[i][j];
10345 int collect_count = element_info[element].collect_count_initial;
10347 if (!IS_CUSTOM_ELEMENT(element))
10350 if (collect_count == 0)
10351 player->inventory_infinite_element = element;
10353 for (k = 0; k < collect_count; k++)
10354 if (player->inventory_size < MAX_INVENTORY_SIZE)
10355 player->inventory_element[player->inventory_size++] =
10366 // ---------- CE actions -------------------------------------------------
10368 case CA_SET_CE_VALUE:
10370 int last_ce_value = CustomValue[x][y];
10372 CustomValue[x][y] = action_arg_number_new;
10374 if (CustomValue[x][y] != last_ce_value)
10376 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10377 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10379 if (CustomValue[x][y] == 0)
10381 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10382 ChangeCount[x][y] = 0; // allow at least one more change
10384 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10385 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10392 case CA_SET_CE_SCORE:
10394 int last_ce_score = ei->collect_score;
10396 ei->collect_score = action_arg_number_new;
10398 if (ei->collect_score != last_ce_score)
10400 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10401 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10403 if (ei->collect_score == 0)
10407 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10408 ChangeCount[x][y] = 0; // allow at least one more change
10410 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10411 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10414 This is a very special case that seems to be a mixture between
10415 CheckElementChange() and CheckTriggeredElementChange(): while
10416 the first one only affects single elements that are triggered
10417 directly, the second one affects multiple elements in the playfield
10418 that are triggered indirectly by another element. This is a third
10419 case: Changing the CE score always affects multiple identical CEs,
10420 so every affected CE must be checked, not only the single CE for
10421 which the CE score was changed in the first place (as every instance
10422 of that CE shares the same CE score, and therefore also can change)!
10424 SCAN_PLAYFIELD(xx, yy)
10426 if (Tile[xx][yy] == element)
10427 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10428 CE_SCORE_GETS_ZERO);
10436 case CA_SET_CE_ARTWORK:
10438 int artwork_element = action_arg_element;
10439 boolean reset_frame = FALSE;
10442 if (action_arg == CA_ARG_ELEMENT_RESET)
10443 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10446 if (ei->gfx_element != artwork_element)
10447 reset_frame = TRUE;
10449 ei->gfx_element = artwork_element;
10451 SCAN_PLAYFIELD(xx, yy)
10453 if (Tile[xx][yy] == element)
10457 ResetGfxAnimation(xx, yy);
10458 ResetRandomAnimationValue(xx, yy);
10461 TEST_DrawLevelField(xx, yy);
10468 // ---------- engine actions ---------------------------------------------
10470 case CA_SET_ENGINE_SCAN_MODE:
10472 InitPlayfieldScanMode(action_arg);
10482 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10484 int old_element = Tile[x][y];
10485 int new_element = GetElementFromGroupElement(element);
10486 int previous_move_direction = MovDir[x][y];
10487 int last_ce_value = CustomValue[x][y];
10488 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10489 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10490 boolean add_player_onto_element = (new_element_is_player &&
10491 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10492 IS_WALKABLE(old_element));
10494 if (!add_player_onto_element)
10496 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10497 RemoveMovingField(x, y);
10501 Tile[x][y] = new_element;
10503 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10504 MovDir[x][y] = previous_move_direction;
10506 if (element_info[new_element].use_last_ce_value)
10507 CustomValue[x][y] = last_ce_value;
10509 InitField_WithBug1(x, y, FALSE);
10511 new_element = Tile[x][y]; // element may have changed
10513 ResetGfxAnimation(x, y);
10514 ResetRandomAnimationValue(x, y);
10516 TEST_DrawLevelField(x, y);
10518 if (GFX_CRUMBLED(new_element))
10519 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10522 // check if element under the player changes from accessible to unaccessible
10523 // (needed for special case of dropping element which then changes)
10524 // (must be checked after creating new element for walkable group elements)
10525 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10526 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10533 // "ChangeCount" not set yet to allow "entered by player" change one time
10534 if (new_element_is_player)
10535 RelocatePlayer(x, y, new_element);
10538 ChangeCount[x][y]++; // count number of changes in the same frame
10540 TestIfBadThingTouchesPlayer(x, y);
10541 TestIfPlayerTouchesCustomElement(x, y);
10542 TestIfElementTouchesCustomElement(x, y);
10545 static void CreateField(int x, int y, int element)
10547 CreateFieldExt(x, y, element, FALSE);
10550 static void CreateElementFromChange(int x, int y, int element)
10552 element = GET_VALID_RUNTIME_ELEMENT(element);
10554 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10556 int old_element = Tile[x][y];
10558 // prevent changed element from moving in same engine frame
10559 // unless both old and new element can either fall or move
10560 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10561 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10565 CreateFieldExt(x, y, element, TRUE);
10568 static boolean ChangeElement(int x, int y, int element, int page)
10570 struct ElementInfo *ei = &element_info[element];
10571 struct ElementChangeInfo *change = &ei->change_page[page];
10572 int ce_value = CustomValue[x][y];
10573 int ce_score = ei->collect_score;
10574 int target_element;
10575 int old_element = Tile[x][y];
10577 // always use default change event to prevent running into a loop
10578 if (ChangeEvent[x][y] == -1)
10579 ChangeEvent[x][y] = CE_DELAY;
10581 if (ChangeEvent[x][y] == CE_DELAY)
10583 // reset actual trigger element, trigger player and action element
10584 change->actual_trigger_element = EL_EMPTY;
10585 change->actual_trigger_player = EL_EMPTY;
10586 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10587 change->actual_trigger_side = CH_SIDE_NONE;
10588 change->actual_trigger_ce_value = 0;
10589 change->actual_trigger_ce_score = 0;
10592 // do not change elements more than a specified maximum number of changes
10593 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10596 ChangeCount[x][y]++; // count number of changes in the same frame
10598 if (change->explode)
10605 if (change->use_target_content)
10607 boolean complete_replace = TRUE;
10608 boolean can_replace[3][3];
10611 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10614 boolean is_walkable;
10615 boolean is_diggable;
10616 boolean is_collectible;
10617 boolean is_removable;
10618 boolean is_destructible;
10619 int ex = x + xx - 1;
10620 int ey = y + yy - 1;
10621 int content_element = change->target_content.e[xx][yy];
10624 can_replace[xx][yy] = TRUE;
10626 if (ex == x && ey == y) // do not check changing element itself
10629 if (content_element == EL_EMPTY_SPACE)
10631 can_replace[xx][yy] = FALSE; // do not replace border with space
10636 if (!IN_LEV_FIELD(ex, ey))
10638 can_replace[xx][yy] = FALSE;
10639 complete_replace = FALSE;
10646 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10647 e = MovingOrBlocked2Element(ex, ey);
10649 is_empty = (IS_FREE(ex, ey) ||
10650 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10652 is_walkable = (is_empty || IS_WALKABLE(e));
10653 is_diggable = (is_empty || IS_DIGGABLE(e));
10654 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10655 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10656 is_removable = (is_diggable || is_collectible);
10658 can_replace[xx][yy] =
10659 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10660 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10661 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10662 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10663 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10664 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10665 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10667 if (!can_replace[xx][yy])
10668 complete_replace = FALSE;
10671 if (!change->only_if_complete || complete_replace)
10673 boolean something_has_changed = FALSE;
10675 if (change->only_if_complete && change->use_random_replace &&
10676 RND(100) < change->random_percentage)
10679 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10681 int ex = x + xx - 1;
10682 int ey = y + yy - 1;
10683 int content_element;
10685 if (can_replace[xx][yy] && (!change->use_random_replace ||
10686 RND(100) < change->random_percentage))
10688 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10689 RemoveMovingField(ex, ey);
10691 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10693 content_element = change->target_content.e[xx][yy];
10694 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10695 ce_value, ce_score);
10697 CreateElementFromChange(ex, ey, target_element);
10699 something_has_changed = TRUE;
10701 // for symmetry reasons, freeze newly created border elements
10702 if (ex != x || ey != y)
10703 Stop[ex][ey] = TRUE; // no more moving in this frame
10707 if (something_has_changed)
10709 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10710 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10716 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10717 ce_value, ce_score);
10719 if (element == EL_DIAGONAL_GROWING ||
10720 element == EL_DIAGONAL_SHRINKING)
10722 target_element = Store[x][y];
10724 Store[x][y] = EL_EMPTY;
10727 CreateElementFromChange(x, y, target_element);
10729 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10730 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10733 // this uses direct change before indirect change
10734 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10739 static void HandleElementChange(int x, int y, int page)
10741 int element = MovingOrBlocked2Element(x, y);
10742 struct ElementInfo *ei = &element_info[element];
10743 struct ElementChangeInfo *change = &ei->change_page[page];
10744 boolean handle_action_before_change = FALSE;
10747 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10748 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10750 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10751 x, y, element, element_info[element].token_name);
10752 Debug("game:playing:HandleElementChange", "This should never happen!");
10756 // this can happen with classic bombs on walkable, changing elements
10757 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10762 if (ChangeDelay[x][y] == 0) // initialize element change
10764 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10766 if (change->can_change)
10768 // !!! not clear why graphic animation should be reset at all here !!!
10769 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10770 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10773 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10775 When using an animation frame delay of 1 (this only happens with
10776 "sp_zonk.moving.left/right" in the classic graphics), the default
10777 (non-moving) animation shows wrong animation frames (while the
10778 moving animation, like "sp_zonk.moving.left/right", is correct,
10779 so this graphical bug never shows up with the classic graphics).
10780 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10781 be drawn instead of the correct frames 0,1,2,3. This is caused by
10782 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10783 an element change: First when the change delay ("ChangeDelay[][]")
10784 counter has reached zero after decrementing, then a second time in
10785 the next frame (after "GfxFrame[][]" was already incremented) when
10786 "ChangeDelay[][]" is reset to the initial delay value again.
10788 This causes frame 0 to be drawn twice, while the last frame won't
10789 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10791 As some animations may already be cleverly designed around this bug
10792 (at least the "Snake Bite" snake tail animation does this), it cannot
10793 simply be fixed here without breaking such existing animations.
10794 Unfortunately, it cannot easily be detected if a graphics set was
10795 designed "before" or "after" the bug was fixed. As a workaround,
10796 a new graphics set option "game.graphics_engine_version" was added
10797 to be able to specify the game's major release version for which the
10798 graphics set was designed, which can then be used to decide if the
10799 bugfix should be used (version 4 and above) or not (version 3 or
10800 below, or if no version was specified at all, as with old sets).
10802 (The wrong/fixed animation frames can be tested with the test level set
10803 "test_gfxframe" and level "000", which contains a specially prepared
10804 custom element at level position (x/y) == (11/9) which uses the zonk
10805 animation mentioned above. Using "game.graphics_engine_version: 4"
10806 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10807 This can also be seen from the debug output for this test element.)
10810 // when a custom element is about to change (for example by change delay),
10811 // do not reset graphic animation when the custom element is moving
10812 if (game.graphics_engine_version < 4 &&
10815 ResetGfxAnimation(x, y);
10816 ResetRandomAnimationValue(x, y);
10819 if (change->pre_change_function)
10820 change->pre_change_function(x, y);
10824 ChangeDelay[x][y]--;
10826 if (ChangeDelay[x][y] != 0) // continue element change
10828 if (change->can_change)
10830 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10832 if (IS_ANIMATED(graphic))
10833 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10835 if (change->change_function)
10836 change->change_function(x, y);
10839 else // finish element change
10841 if (ChangePage[x][y] != -1) // remember page from delayed change
10843 page = ChangePage[x][y];
10844 ChangePage[x][y] = -1;
10846 change = &ei->change_page[page];
10849 if (IS_MOVING(x, y)) // never change a running system ;-)
10851 ChangeDelay[x][y] = 1; // try change after next move step
10852 ChangePage[x][y] = page; // remember page to use for change
10857 // special case: set new level random seed before changing element
10858 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10859 handle_action_before_change = TRUE;
10861 if (change->has_action && handle_action_before_change)
10862 ExecuteCustomElementAction(x, y, element, page);
10864 if (change->can_change)
10866 if (ChangeElement(x, y, element, page))
10868 if (change->post_change_function)
10869 change->post_change_function(x, y);
10873 if (change->has_action && !handle_action_before_change)
10874 ExecuteCustomElementAction(x, y, element, page);
10878 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10879 int trigger_element,
10881 int trigger_player,
10885 boolean change_done_any = FALSE;
10886 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10889 if (!(trigger_events[trigger_element][trigger_event]))
10892 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10894 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10896 int element = EL_CUSTOM_START + i;
10897 boolean change_done = FALSE;
10900 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10901 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10904 for (p = 0; p < element_info[element].num_change_pages; p++)
10906 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10908 if (change->can_change_or_has_action &&
10909 change->has_event[trigger_event] &&
10910 change->trigger_side & trigger_side &&
10911 change->trigger_player & trigger_player &&
10912 change->trigger_page & trigger_page_bits &&
10913 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10915 change->actual_trigger_element = trigger_element;
10916 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10917 change->actual_trigger_player_bits = trigger_player;
10918 change->actual_trigger_side = trigger_side;
10919 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10920 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10922 if ((change->can_change && !change_done) || change->has_action)
10926 SCAN_PLAYFIELD(x, y)
10928 if (Tile[x][y] == element)
10930 if (change->can_change && !change_done)
10932 // if element already changed in this frame, not only prevent
10933 // another element change (checked in ChangeElement()), but
10934 // also prevent additional element actions for this element
10936 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10937 !level.use_action_after_change_bug)
10940 ChangeDelay[x][y] = 1;
10941 ChangeEvent[x][y] = trigger_event;
10943 HandleElementChange(x, y, p);
10945 else if (change->has_action)
10947 // if element already changed in this frame, not only prevent
10948 // another element change (checked in ChangeElement()), but
10949 // also prevent additional element actions for this element
10951 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10952 !level.use_action_after_change_bug)
10955 ExecuteCustomElementAction(x, y, element, p);
10956 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10961 if (change->can_change)
10963 change_done = TRUE;
10964 change_done_any = TRUE;
10971 RECURSION_LOOP_DETECTION_END();
10973 return change_done_any;
10976 static boolean CheckElementChangeExt(int x, int y,
10978 int trigger_element,
10980 int trigger_player,
10983 boolean change_done = FALSE;
10986 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10987 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10990 if (Tile[x][y] == EL_BLOCKED)
10992 Blocked2Moving(x, y, &x, &y);
10993 element = Tile[x][y];
10996 // check if element has already changed or is about to change after moving
10997 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10998 Tile[x][y] != element) ||
11000 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11001 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11002 ChangePage[x][y] != -1)))
11005 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11007 for (p = 0; p < element_info[element].num_change_pages; p++)
11009 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11011 /* check trigger element for all events where the element that is checked
11012 for changing interacts with a directly adjacent element -- this is
11013 different to element changes that affect other elements to change on the
11014 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11015 boolean check_trigger_element =
11016 (trigger_event == CE_TOUCHING_X ||
11017 trigger_event == CE_HITTING_X ||
11018 trigger_event == CE_HIT_BY_X ||
11019 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11021 if (change->can_change_or_has_action &&
11022 change->has_event[trigger_event] &&
11023 change->trigger_side & trigger_side &&
11024 change->trigger_player & trigger_player &&
11025 (!check_trigger_element ||
11026 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11028 change->actual_trigger_element = trigger_element;
11029 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11030 change->actual_trigger_player_bits = trigger_player;
11031 change->actual_trigger_side = trigger_side;
11032 change->actual_trigger_ce_value = CustomValue[x][y];
11033 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11035 // special case: trigger element not at (x,y) position for some events
11036 if (check_trigger_element)
11048 { 0, 0 }, { 0, 0 }, { 0, 0 },
11052 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11053 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11055 change->actual_trigger_ce_value = CustomValue[xx][yy];
11056 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11059 if (change->can_change && !change_done)
11061 ChangeDelay[x][y] = 1;
11062 ChangeEvent[x][y] = trigger_event;
11064 HandleElementChange(x, y, p);
11066 change_done = TRUE;
11068 else if (change->has_action)
11070 ExecuteCustomElementAction(x, y, element, p);
11071 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11076 RECURSION_LOOP_DETECTION_END();
11078 return change_done;
11081 static void PlayPlayerSound(struct PlayerInfo *player)
11083 int jx = player->jx, jy = player->jy;
11084 int sound_element = player->artwork_element;
11085 int last_action = player->last_action_waiting;
11086 int action = player->action_waiting;
11088 if (player->is_waiting)
11090 if (action != last_action)
11091 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11093 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11097 if (action != last_action)
11098 StopSound(element_info[sound_element].sound[last_action]);
11100 if (last_action == ACTION_SLEEPING)
11101 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11105 static void PlayAllPlayersSound(void)
11109 for (i = 0; i < MAX_PLAYERS; i++)
11110 if (stored_player[i].active)
11111 PlayPlayerSound(&stored_player[i]);
11114 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11116 boolean last_waiting = player->is_waiting;
11117 int move_dir = player->MovDir;
11119 player->dir_waiting = move_dir;
11120 player->last_action_waiting = player->action_waiting;
11124 if (!last_waiting) // not waiting -> waiting
11126 player->is_waiting = TRUE;
11128 player->frame_counter_bored =
11130 game.player_boring_delay_fixed +
11131 GetSimpleRandom(game.player_boring_delay_random);
11132 player->frame_counter_sleeping =
11134 game.player_sleeping_delay_fixed +
11135 GetSimpleRandom(game.player_sleeping_delay_random);
11137 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11140 if (game.player_sleeping_delay_fixed +
11141 game.player_sleeping_delay_random > 0 &&
11142 player->anim_delay_counter == 0 &&
11143 player->post_delay_counter == 0 &&
11144 FrameCounter >= player->frame_counter_sleeping)
11145 player->is_sleeping = TRUE;
11146 else if (game.player_boring_delay_fixed +
11147 game.player_boring_delay_random > 0 &&
11148 FrameCounter >= player->frame_counter_bored)
11149 player->is_bored = TRUE;
11151 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11152 player->is_bored ? ACTION_BORING :
11155 if (player->is_sleeping && player->use_murphy)
11157 // special case for sleeping Murphy when leaning against non-free tile
11159 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11160 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11161 !IS_MOVING(player->jx - 1, player->jy)))
11162 move_dir = MV_LEFT;
11163 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11164 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11165 !IS_MOVING(player->jx + 1, player->jy)))
11166 move_dir = MV_RIGHT;
11168 player->is_sleeping = FALSE;
11170 player->dir_waiting = move_dir;
11173 if (player->is_sleeping)
11175 if (player->num_special_action_sleeping > 0)
11177 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11179 int last_special_action = player->special_action_sleeping;
11180 int num_special_action = player->num_special_action_sleeping;
11181 int special_action =
11182 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11183 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11184 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11185 last_special_action + 1 : ACTION_SLEEPING);
11186 int special_graphic =
11187 el_act_dir2img(player->artwork_element, special_action, move_dir);
11189 player->anim_delay_counter =
11190 graphic_info[special_graphic].anim_delay_fixed +
11191 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11192 player->post_delay_counter =
11193 graphic_info[special_graphic].post_delay_fixed +
11194 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11196 player->special_action_sleeping = special_action;
11199 if (player->anim_delay_counter > 0)
11201 player->action_waiting = player->special_action_sleeping;
11202 player->anim_delay_counter--;
11204 else if (player->post_delay_counter > 0)
11206 player->post_delay_counter--;
11210 else if (player->is_bored)
11212 if (player->num_special_action_bored > 0)
11214 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11216 int special_action =
11217 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11218 int special_graphic =
11219 el_act_dir2img(player->artwork_element, special_action, move_dir);
11221 player->anim_delay_counter =
11222 graphic_info[special_graphic].anim_delay_fixed +
11223 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11224 player->post_delay_counter =
11225 graphic_info[special_graphic].post_delay_fixed +
11226 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11228 player->special_action_bored = special_action;
11231 if (player->anim_delay_counter > 0)
11233 player->action_waiting = player->special_action_bored;
11234 player->anim_delay_counter--;
11236 else if (player->post_delay_counter > 0)
11238 player->post_delay_counter--;
11243 else if (last_waiting) // waiting -> not waiting
11245 player->is_waiting = FALSE;
11246 player->is_bored = FALSE;
11247 player->is_sleeping = FALSE;
11249 player->frame_counter_bored = -1;
11250 player->frame_counter_sleeping = -1;
11252 player->anim_delay_counter = 0;
11253 player->post_delay_counter = 0;
11255 player->dir_waiting = player->MovDir;
11256 player->action_waiting = ACTION_DEFAULT;
11258 player->special_action_bored = ACTION_DEFAULT;
11259 player->special_action_sleeping = ACTION_DEFAULT;
11263 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11265 if ((!player->is_moving && player->was_moving) ||
11266 (player->MovPos == 0 && player->was_moving) ||
11267 (player->is_snapping && !player->was_snapping) ||
11268 (player->is_dropping && !player->was_dropping))
11270 if (!CheckSaveEngineSnapshotToList())
11273 player->was_moving = FALSE;
11274 player->was_snapping = TRUE;
11275 player->was_dropping = TRUE;
11279 if (player->is_moving)
11280 player->was_moving = TRUE;
11282 if (!player->is_snapping)
11283 player->was_snapping = FALSE;
11285 if (!player->is_dropping)
11286 player->was_dropping = FALSE;
11289 static struct MouseActionInfo mouse_action_last = { 0 };
11290 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11291 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11294 CheckSaveEngineSnapshotToList();
11296 mouse_action_last = mouse_action;
11299 static void CheckSingleStepMode(struct PlayerInfo *player)
11301 if (tape.single_step && tape.recording && !tape.pausing)
11303 // as it is called "single step mode", just return to pause mode when the
11304 // player stopped moving after one tile (or never starts moving at all)
11305 // (reverse logic needed here in case single step mode used in team mode)
11306 if (player->is_moving ||
11307 player->is_pushing ||
11308 player->is_dropping_pressed ||
11309 player->effective_mouse_action.button)
11310 game.enter_single_step_mode = FALSE;
11313 CheckSaveEngineSnapshot(player);
11316 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11318 int left = player_action & JOY_LEFT;
11319 int right = player_action & JOY_RIGHT;
11320 int up = player_action & JOY_UP;
11321 int down = player_action & JOY_DOWN;
11322 int button1 = player_action & JOY_BUTTON_1;
11323 int button2 = player_action & JOY_BUTTON_2;
11324 int dx = (left ? -1 : right ? 1 : 0);
11325 int dy = (up ? -1 : down ? 1 : 0);
11327 if (!player->active || tape.pausing)
11333 SnapField(player, dx, dy);
11337 DropElement(player);
11339 MovePlayer(player, dx, dy);
11342 CheckSingleStepMode(player);
11344 SetPlayerWaiting(player, FALSE);
11346 return player_action;
11350 // no actions for this player (no input at player's configured device)
11352 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11353 SnapField(player, 0, 0);
11354 CheckGravityMovementWhenNotMoving(player);
11356 if (player->MovPos == 0)
11357 SetPlayerWaiting(player, TRUE);
11359 if (player->MovPos == 0) // needed for tape.playing
11360 player->is_moving = FALSE;
11362 player->is_dropping = FALSE;
11363 player->is_dropping_pressed = FALSE;
11364 player->drop_pressed_delay = 0;
11366 CheckSingleStepMode(player);
11372 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11375 if (!tape.use_mouse_actions)
11378 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11379 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11380 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11383 static void SetTapeActionFromMouseAction(byte *tape_action,
11384 struct MouseActionInfo *mouse_action)
11386 if (!tape.use_mouse_actions)
11389 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11390 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11391 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11394 static void CheckLevelSolved(void)
11396 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11398 if (game_em.level_solved &&
11399 !game_em.game_over) // game won
11403 game_em.game_over = TRUE;
11405 game.all_players_gone = TRUE;
11408 if (game_em.game_over) // game lost
11409 game.all_players_gone = TRUE;
11411 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11413 if (game_sp.level_solved &&
11414 !game_sp.game_over) // game won
11418 game_sp.game_over = TRUE;
11420 game.all_players_gone = TRUE;
11423 if (game_sp.game_over) // game lost
11424 game.all_players_gone = TRUE;
11426 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11428 if (game_mm.level_solved &&
11429 !game_mm.game_over) // game won
11433 game_mm.game_over = TRUE;
11435 game.all_players_gone = TRUE;
11438 if (game_mm.game_over) // game lost
11439 game.all_players_gone = TRUE;
11443 static void CheckLevelTime(void)
11447 if (TimeFrames >= FRAMES_PER_SECOND)
11452 for (i = 0; i < MAX_PLAYERS; i++)
11454 struct PlayerInfo *player = &stored_player[i];
11456 if (SHIELD_ON(player))
11458 player->shield_normal_time_left--;
11460 if (player->shield_deadly_time_left > 0)
11461 player->shield_deadly_time_left--;
11465 if (!game.LevelSolved && !level.use_step_counter)
11473 if (TimeLeft <= 10 && setup.time_limit)
11474 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11476 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11477 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11479 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11481 if (!TimeLeft && setup.time_limit)
11483 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11484 game_em.lev->killed_out_of_time = TRUE;
11486 for (i = 0; i < MAX_PLAYERS; i++)
11487 KillPlayer(&stored_player[i]);
11490 else if (game.no_time_limit && !game.all_players_gone)
11492 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11495 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11498 if (tape.recording || tape.playing)
11499 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11502 if (tape.recording || tape.playing)
11503 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11505 UpdateAndDisplayGameControlValues();
11508 void AdvanceFrameAndPlayerCounters(int player_nr)
11512 // advance frame counters (global frame counter and time frame counter)
11516 // advance player counters (counters for move delay, move animation etc.)
11517 for (i = 0; i < MAX_PLAYERS; i++)
11519 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11520 int move_delay_value = stored_player[i].move_delay_value;
11521 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11523 if (!advance_player_counters) // not all players may be affected
11526 if (move_frames == 0) // less than one move per game frame
11528 int stepsize = TILEX / move_delay_value;
11529 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11530 int count = (stored_player[i].is_moving ?
11531 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11533 if (count % delay == 0)
11537 stored_player[i].Frame += move_frames;
11539 if (stored_player[i].MovPos != 0)
11540 stored_player[i].StepFrame += move_frames;
11542 if (stored_player[i].move_delay > 0)
11543 stored_player[i].move_delay--;
11545 // due to bugs in previous versions, counter must count up, not down
11546 if (stored_player[i].push_delay != -1)
11547 stored_player[i].push_delay++;
11549 if (stored_player[i].drop_delay > 0)
11550 stored_player[i].drop_delay--;
11552 if (stored_player[i].is_dropping_pressed)
11553 stored_player[i].drop_pressed_delay++;
11557 void StartGameActions(boolean init_network_game, boolean record_tape,
11560 unsigned int new_random_seed = InitRND(random_seed);
11563 TapeStartRecording(new_random_seed);
11565 if (init_network_game)
11567 SendToServer_LevelFile();
11568 SendToServer_StartPlaying();
11576 static void GameActionsExt(void)
11579 static unsigned int game_frame_delay = 0;
11581 unsigned int game_frame_delay_value;
11582 byte *recorded_player_action;
11583 byte summarized_player_action = 0;
11584 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11587 // detect endless loops, caused by custom element programming
11588 if (recursion_loop_detected && recursion_loop_depth == 0)
11590 char *message = getStringCat3("Internal Error! Element ",
11591 EL_NAME(recursion_loop_element),
11592 " caused endless loop! Quit the game?");
11594 Warn("element '%s' caused endless loop in game engine",
11595 EL_NAME(recursion_loop_element));
11597 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11599 recursion_loop_detected = FALSE; // if game should be continued
11606 if (game.restart_level)
11607 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11609 CheckLevelSolved();
11611 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11614 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11617 if (game_status != GAME_MODE_PLAYING) // status might have changed
11620 game_frame_delay_value =
11621 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11623 if (tape.playing && tape.warp_forward && !tape.pausing)
11624 game_frame_delay_value = 0;
11626 SetVideoFrameDelay(game_frame_delay_value);
11628 // (de)activate virtual buttons depending on current game status
11629 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11631 if (game.all_players_gone) // if no players there to be controlled anymore
11632 SetOverlayActive(FALSE);
11633 else if (!tape.playing) // if game continues after tape stopped playing
11634 SetOverlayActive(TRUE);
11639 // ---------- main game synchronization point ----------
11641 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11643 Debug("game:playing:skip", "skip == %d", skip);
11646 // ---------- main game synchronization point ----------
11648 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11652 if (network_playing && !network_player_action_received)
11654 // try to get network player actions in time
11656 // last chance to get network player actions without main loop delay
11657 HandleNetworking();
11659 // game was quit by network peer
11660 if (game_status != GAME_MODE_PLAYING)
11663 // check if network player actions still missing and game still running
11664 if (!network_player_action_received && !checkGameEnded())
11665 return; // failed to get network player actions in time
11667 // do not yet reset "network_player_action_received" (for tape.pausing)
11673 // at this point we know that we really continue executing the game
11675 network_player_action_received = FALSE;
11677 // when playing tape, read previously recorded player input from tape data
11678 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11680 local_player->effective_mouse_action = local_player->mouse_action;
11682 if (recorded_player_action != NULL)
11683 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11684 recorded_player_action);
11686 // TapePlayAction() may return NULL when toggling to "pause before death"
11690 if (tape.set_centered_player)
11692 game.centered_player_nr_next = tape.centered_player_nr_next;
11693 game.set_centered_player = TRUE;
11696 for (i = 0; i < MAX_PLAYERS; i++)
11698 summarized_player_action |= stored_player[i].action;
11700 if (!network_playing && (game.team_mode || tape.playing))
11701 stored_player[i].effective_action = stored_player[i].action;
11704 if (network_playing && !checkGameEnded())
11705 SendToServer_MovePlayer(summarized_player_action);
11707 // summarize all actions at local players mapped input device position
11708 // (this allows using different input devices in single player mode)
11709 if (!network.enabled && !game.team_mode)
11710 stored_player[map_player_action[local_player->index_nr]].effective_action =
11711 summarized_player_action;
11713 // summarize all actions at centered player in local team mode
11714 if (tape.recording &&
11715 setup.team_mode && !network.enabled &&
11716 setup.input_on_focus &&
11717 game.centered_player_nr != -1)
11719 for (i = 0; i < MAX_PLAYERS; i++)
11720 stored_player[map_player_action[i]].effective_action =
11721 (i == game.centered_player_nr ? summarized_player_action : 0);
11724 if (recorded_player_action != NULL)
11725 for (i = 0; i < MAX_PLAYERS; i++)
11726 stored_player[i].effective_action = recorded_player_action[i];
11728 for (i = 0; i < MAX_PLAYERS; i++)
11730 tape_action[i] = stored_player[i].effective_action;
11732 /* (this may happen in the RND game engine if a player was not present on
11733 the playfield on level start, but appeared later from a custom element */
11734 if (setup.team_mode &&
11737 !tape.player_participates[i])
11738 tape.player_participates[i] = TRUE;
11741 SetTapeActionFromMouseAction(tape_action,
11742 &local_player->effective_mouse_action);
11744 // only record actions from input devices, but not programmed actions
11745 if (tape.recording)
11746 TapeRecordAction(tape_action);
11748 // remember if game was played (especially after tape stopped playing)
11749 if (!tape.playing && summarized_player_action)
11750 game.GamePlayed = TRUE;
11752 #if USE_NEW_PLAYER_ASSIGNMENTS
11753 // !!! also map player actions in single player mode !!!
11754 // if (game.team_mode)
11757 byte mapped_action[MAX_PLAYERS];
11759 #if DEBUG_PLAYER_ACTIONS
11760 for (i = 0; i < MAX_PLAYERS; i++)
11761 DebugContinued("", "%d, ", stored_player[i].effective_action);
11764 for (i = 0; i < MAX_PLAYERS; i++)
11765 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11767 for (i = 0; i < MAX_PLAYERS; i++)
11768 stored_player[i].effective_action = mapped_action[i];
11770 #if DEBUG_PLAYER_ACTIONS
11771 DebugContinued("", "=> ");
11772 for (i = 0; i < MAX_PLAYERS; i++)
11773 DebugContinued("", "%d, ", stored_player[i].effective_action);
11774 DebugContinued("game:playing:player", "\n");
11777 #if DEBUG_PLAYER_ACTIONS
11780 for (i = 0; i < MAX_PLAYERS; i++)
11781 DebugContinued("", "%d, ", stored_player[i].effective_action);
11782 DebugContinued("game:playing:player", "\n");
11787 for (i = 0; i < MAX_PLAYERS; i++)
11789 // allow engine snapshot in case of changed movement attempt
11790 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11791 (stored_player[i].effective_action & KEY_MOTION))
11792 game.snapshot.changed_action = TRUE;
11794 // allow engine snapshot in case of snapping/dropping attempt
11795 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11796 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11797 game.snapshot.changed_action = TRUE;
11799 game.snapshot.last_action[i] = stored_player[i].effective_action;
11802 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11804 GameActions_EM_Main();
11806 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11808 GameActions_SP_Main();
11810 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11812 GameActions_MM_Main();
11816 GameActions_RND_Main();
11819 BlitScreenToBitmap(backbuffer);
11821 CheckLevelSolved();
11824 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11826 if (global.show_frames_per_second)
11828 static unsigned int fps_counter = 0;
11829 static int fps_frames = 0;
11830 unsigned int fps_delay_ms = Counter() - fps_counter;
11834 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11836 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11839 fps_counter = Counter();
11841 // always draw FPS to screen after FPS value was updated
11842 redraw_mask |= REDRAW_FPS;
11845 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11846 if (GetDrawDeactivationMask() == REDRAW_NONE)
11847 redraw_mask |= REDRAW_FPS;
11851 static void GameActions_CheckSaveEngineSnapshot(void)
11853 if (!game.snapshot.save_snapshot)
11856 // clear flag for saving snapshot _before_ saving snapshot
11857 game.snapshot.save_snapshot = FALSE;
11859 SaveEngineSnapshotToList();
11862 void GameActions(void)
11866 GameActions_CheckSaveEngineSnapshot();
11869 void GameActions_EM_Main(void)
11871 byte effective_action[MAX_PLAYERS];
11872 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11875 for (i = 0; i < MAX_PLAYERS; i++)
11876 effective_action[i] = stored_player[i].effective_action;
11878 GameActions_EM(effective_action, warp_mode);
11881 void GameActions_SP_Main(void)
11883 byte effective_action[MAX_PLAYERS];
11884 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11887 for (i = 0; i < MAX_PLAYERS; i++)
11888 effective_action[i] = stored_player[i].effective_action;
11890 GameActions_SP(effective_action, warp_mode);
11892 for (i = 0; i < MAX_PLAYERS; i++)
11894 if (stored_player[i].force_dropping)
11895 stored_player[i].action |= KEY_BUTTON_DROP;
11897 stored_player[i].force_dropping = FALSE;
11901 void GameActions_MM_Main(void)
11903 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11905 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11908 void GameActions_RND_Main(void)
11913 void GameActions_RND(void)
11915 static struct MouseActionInfo mouse_action_last = { 0 };
11916 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11917 int magic_wall_x = 0, magic_wall_y = 0;
11918 int i, x, y, element, graphic, last_gfx_frame;
11920 InitPlayfieldScanModeVars();
11922 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11924 SCAN_PLAYFIELD(x, y)
11926 ChangeCount[x][y] = 0;
11927 ChangeEvent[x][y] = -1;
11931 if (game.set_centered_player)
11933 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11935 // switching to "all players" only possible if all players fit to screen
11936 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11938 game.centered_player_nr_next = game.centered_player_nr;
11939 game.set_centered_player = FALSE;
11942 // do not switch focus to non-existing (or non-active) player
11943 if (game.centered_player_nr_next >= 0 &&
11944 !stored_player[game.centered_player_nr_next].active)
11946 game.centered_player_nr_next = game.centered_player_nr;
11947 game.set_centered_player = FALSE;
11951 if (game.set_centered_player &&
11952 ScreenMovPos == 0) // screen currently aligned at tile position
11956 if (game.centered_player_nr_next == -1)
11958 setScreenCenteredToAllPlayers(&sx, &sy);
11962 sx = stored_player[game.centered_player_nr_next].jx;
11963 sy = stored_player[game.centered_player_nr_next].jy;
11966 game.centered_player_nr = game.centered_player_nr_next;
11967 game.set_centered_player = FALSE;
11969 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11970 DrawGameDoorValues();
11973 // check single step mode (set flag and clear again if any player is active)
11974 game.enter_single_step_mode =
11975 (tape.single_step && tape.recording && !tape.pausing);
11977 for (i = 0; i < MAX_PLAYERS; i++)
11979 int actual_player_action = stored_player[i].effective_action;
11982 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11983 - rnd_equinox_tetrachloride 048
11984 - rnd_equinox_tetrachloride_ii 096
11985 - rnd_emanuel_schmieg 002
11986 - doctor_sloan_ww 001, 020
11988 if (stored_player[i].MovPos == 0)
11989 CheckGravityMovement(&stored_player[i]);
11992 // overwrite programmed action with tape action
11993 if (stored_player[i].programmed_action)
11994 actual_player_action = stored_player[i].programmed_action;
11996 PlayerActions(&stored_player[i], actual_player_action);
11998 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12001 // single step pause mode may already have been toggled by "ScrollPlayer()"
12002 if (game.enter_single_step_mode && !tape.pausing)
12003 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12005 ScrollScreen(NULL, SCROLL_GO_ON);
12007 /* for backwards compatibility, the following code emulates a fixed bug that
12008 occured when pushing elements (causing elements that just made their last
12009 pushing step to already (if possible) make their first falling step in the
12010 same game frame, which is bad); this code is also needed to use the famous
12011 "spring push bug" which is used in older levels and might be wanted to be
12012 used also in newer levels, but in this case the buggy pushing code is only
12013 affecting the "spring" element and no other elements */
12015 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12017 for (i = 0; i < MAX_PLAYERS; i++)
12019 struct PlayerInfo *player = &stored_player[i];
12020 int x = player->jx;
12021 int y = player->jy;
12023 if (player->active && player->is_pushing && player->is_moving &&
12025 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12026 Tile[x][y] == EL_SPRING))
12028 ContinueMoving(x, y);
12030 // continue moving after pushing (this is actually a bug)
12031 if (!IS_MOVING(x, y))
12032 Stop[x][y] = FALSE;
12037 SCAN_PLAYFIELD(x, y)
12039 Last[x][y] = Tile[x][y];
12041 ChangeCount[x][y] = 0;
12042 ChangeEvent[x][y] = -1;
12044 // this must be handled before main playfield loop
12045 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12048 if (MovDelay[x][y] <= 0)
12052 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12055 if (MovDelay[x][y] <= 0)
12057 int element = Store[x][y];
12058 int move_direction = MovDir[x][y];
12059 int player_index_bit = Store2[x][y];
12065 TEST_DrawLevelField(x, y);
12067 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12072 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12074 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12076 Debug("game:playing:GameActions_RND", "This should never happen!");
12078 ChangePage[x][y] = -1;
12082 Stop[x][y] = FALSE;
12083 if (WasJustMoving[x][y] > 0)
12084 WasJustMoving[x][y]--;
12085 if (WasJustFalling[x][y] > 0)
12086 WasJustFalling[x][y]--;
12087 if (CheckCollision[x][y] > 0)
12088 CheckCollision[x][y]--;
12089 if (CheckImpact[x][y] > 0)
12090 CheckImpact[x][y]--;
12094 /* reset finished pushing action (not done in ContinueMoving() to allow
12095 continuous pushing animation for elements with zero push delay) */
12096 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12098 ResetGfxAnimation(x, y);
12099 TEST_DrawLevelField(x, y);
12103 if (IS_BLOCKED(x, y))
12107 Blocked2Moving(x, y, &oldx, &oldy);
12108 if (!IS_MOVING(oldx, oldy))
12110 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12111 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12112 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12113 Debug("game:playing:GameActions_RND", "This should never happen!");
12119 if (mouse_action.button)
12121 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12123 x = mouse_action.lx;
12124 y = mouse_action.ly;
12125 element = Tile[x][y];
12129 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12130 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12133 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12134 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12137 SCAN_PLAYFIELD(x, y)
12139 element = Tile[x][y];
12140 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12141 last_gfx_frame = GfxFrame[x][y];
12143 ResetGfxFrame(x, y);
12145 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12146 DrawLevelGraphicAnimation(x, y, graphic);
12148 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12149 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12150 ResetRandomAnimationValue(x, y);
12152 SetRandomAnimationValue(x, y);
12154 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12156 if (IS_INACTIVE(element))
12158 if (IS_ANIMATED(graphic))
12159 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12164 // this may take place after moving, so 'element' may have changed
12165 if (IS_CHANGING(x, y) &&
12166 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12168 int page = element_info[element].event_page_nr[CE_DELAY];
12170 HandleElementChange(x, y, page);
12172 element = Tile[x][y];
12173 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12176 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12180 element = Tile[x][y];
12181 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12183 if (IS_ANIMATED(graphic) &&
12184 !IS_MOVING(x, y) &&
12186 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12188 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12189 TEST_DrawTwinkleOnField(x, y);
12191 else if (element == EL_ACID)
12194 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12196 else if ((element == EL_EXIT_OPEN ||
12197 element == EL_EM_EXIT_OPEN ||
12198 element == EL_SP_EXIT_OPEN ||
12199 element == EL_STEEL_EXIT_OPEN ||
12200 element == EL_EM_STEEL_EXIT_OPEN ||
12201 element == EL_SP_TERMINAL ||
12202 element == EL_SP_TERMINAL_ACTIVE ||
12203 element == EL_EXTRA_TIME ||
12204 element == EL_SHIELD_NORMAL ||
12205 element == EL_SHIELD_DEADLY) &&
12206 IS_ANIMATED(graphic))
12207 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12208 else if (IS_MOVING(x, y))
12209 ContinueMoving(x, y);
12210 else if (IS_ACTIVE_BOMB(element))
12211 CheckDynamite(x, y);
12212 else if (element == EL_AMOEBA_GROWING)
12213 AmoebaGrowing(x, y);
12214 else if (element == EL_AMOEBA_SHRINKING)
12215 AmoebaShrinking(x, y);
12217 #if !USE_NEW_AMOEBA_CODE
12218 else if (IS_AMOEBALIVE(element))
12219 AmoebaReproduce(x, y);
12222 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12224 else if (element == EL_EXIT_CLOSED)
12226 else if (element == EL_EM_EXIT_CLOSED)
12228 else if (element == EL_STEEL_EXIT_CLOSED)
12229 CheckExitSteel(x, y);
12230 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12231 CheckExitSteelEM(x, y);
12232 else if (element == EL_SP_EXIT_CLOSED)
12234 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12235 element == EL_EXPANDABLE_STEELWALL_GROWING)
12236 MauerWaechst(x, y);
12237 else if (element == EL_EXPANDABLE_WALL ||
12238 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12239 element == EL_EXPANDABLE_WALL_VERTICAL ||
12240 element == EL_EXPANDABLE_WALL_ANY ||
12241 element == EL_BD_EXPANDABLE_WALL)
12242 MauerAbleger(x, y);
12243 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12244 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12245 element == EL_EXPANDABLE_STEELWALL_ANY)
12246 MauerAblegerStahl(x, y);
12247 else if (element == EL_FLAMES)
12248 CheckForDragon(x, y);
12249 else if (element == EL_EXPLOSION)
12250 ; // drawing of correct explosion animation is handled separately
12251 else if (element == EL_ELEMENT_SNAPPING ||
12252 element == EL_DIAGONAL_SHRINKING ||
12253 element == EL_DIAGONAL_GROWING)
12255 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12257 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12259 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12260 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12262 if (IS_BELT_ACTIVE(element))
12263 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12265 if (game.magic_wall_active)
12267 int jx = local_player->jx, jy = local_player->jy;
12269 // play the element sound at the position nearest to the player
12270 if ((element == EL_MAGIC_WALL_FULL ||
12271 element == EL_MAGIC_WALL_ACTIVE ||
12272 element == EL_MAGIC_WALL_EMPTYING ||
12273 element == EL_BD_MAGIC_WALL_FULL ||
12274 element == EL_BD_MAGIC_WALL_ACTIVE ||
12275 element == EL_BD_MAGIC_WALL_EMPTYING ||
12276 element == EL_DC_MAGIC_WALL_FULL ||
12277 element == EL_DC_MAGIC_WALL_ACTIVE ||
12278 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12279 ABS(x - jx) + ABS(y - jy) <
12280 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12288 #if USE_NEW_AMOEBA_CODE
12289 // new experimental amoeba growth stuff
12290 if (!(FrameCounter % 8))
12292 static unsigned int random = 1684108901;
12294 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12296 x = RND(lev_fieldx);
12297 y = RND(lev_fieldy);
12298 element = Tile[x][y];
12300 if (!IS_PLAYER(x,y) &&
12301 (element == EL_EMPTY ||
12302 CAN_GROW_INTO(element) ||
12303 element == EL_QUICKSAND_EMPTY ||
12304 element == EL_QUICKSAND_FAST_EMPTY ||
12305 element == EL_ACID_SPLASH_LEFT ||
12306 element == EL_ACID_SPLASH_RIGHT))
12308 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12309 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12310 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12311 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12312 Tile[x][y] = EL_AMOEBA_DROP;
12315 random = random * 129 + 1;
12320 game.explosions_delayed = FALSE;
12322 SCAN_PLAYFIELD(x, y)
12324 element = Tile[x][y];
12326 if (ExplodeField[x][y])
12327 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12328 else if (element == EL_EXPLOSION)
12329 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12331 ExplodeField[x][y] = EX_TYPE_NONE;
12334 game.explosions_delayed = TRUE;
12336 if (game.magic_wall_active)
12338 if (!(game.magic_wall_time_left % 4))
12340 int element = Tile[magic_wall_x][magic_wall_y];
12342 if (element == EL_BD_MAGIC_WALL_FULL ||
12343 element == EL_BD_MAGIC_WALL_ACTIVE ||
12344 element == EL_BD_MAGIC_WALL_EMPTYING)
12345 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12346 else if (element == EL_DC_MAGIC_WALL_FULL ||
12347 element == EL_DC_MAGIC_WALL_ACTIVE ||
12348 element == EL_DC_MAGIC_WALL_EMPTYING)
12349 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12351 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12354 if (game.magic_wall_time_left > 0)
12356 game.magic_wall_time_left--;
12358 if (!game.magic_wall_time_left)
12360 SCAN_PLAYFIELD(x, y)
12362 element = Tile[x][y];
12364 if (element == EL_MAGIC_WALL_ACTIVE ||
12365 element == EL_MAGIC_WALL_FULL)
12367 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12368 TEST_DrawLevelField(x, y);
12370 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12371 element == EL_BD_MAGIC_WALL_FULL)
12373 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12374 TEST_DrawLevelField(x, y);
12376 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12377 element == EL_DC_MAGIC_WALL_FULL)
12379 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12380 TEST_DrawLevelField(x, y);
12384 game.magic_wall_active = FALSE;
12389 if (game.light_time_left > 0)
12391 game.light_time_left--;
12393 if (game.light_time_left == 0)
12394 RedrawAllLightSwitchesAndInvisibleElements();
12397 if (game.timegate_time_left > 0)
12399 game.timegate_time_left--;
12401 if (game.timegate_time_left == 0)
12402 CloseAllOpenTimegates();
12405 if (game.lenses_time_left > 0)
12407 game.lenses_time_left--;
12409 if (game.lenses_time_left == 0)
12410 RedrawAllInvisibleElementsForLenses();
12413 if (game.magnify_time_left > 0)
12415 game.magnify_time_left--;
12417 if (game.magnify_time_left == 0)
12418 RedrawAllInvisibleElementsForMagnifier();
12421 for (i = 0; i < MAX_PLAYERS; i++)
12423 struct PlayerInfo *player = &stored_player[i];
12425 if (SHIELD_ON(player))
12427 if (player->shield_deadly_time_left)
12428 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12429 else if (player->shield_normal_time_left)
12430 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12434 #if USE_DELAYED_GFX_REDRAW
12435 SCAN_PLAYFIELD(x, y)
12437 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12439 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12440 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12442 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12443 DrawLevelField(x, y);
12445 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12446 DrawLevelFieldCrumbled(x, y);
12448 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12449 DrawLevelFieldCrumbledNeighbours(x, y);
12451 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12452 DrawTwinkleOnField(x, y);
12455 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12460 PlayAllPlayersSound();
12462 for (i = 0; i < MAX_PLAYERS; i++)
12464 struct PlayerInfo *player = &stored_player[i];
12466 if (player->show_envelope != 0 && (!player->active ||
12467 player->MovPos == 0))
12469 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12471 player->show_envelope = 0;
12475 // use random number generator in every frame to make it less predictable
12476 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12479 mouse_action_last = mouse_action;
12482 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12484 int min_x = x, min_y = y, max_x = x, max_y = y;
12485 int scr_fieldx = getScreenFieldSizeX();
12486 int scr_fieldy = getScreenFieldSizeY();
12489 for (i = 0; i < MAX_PLAYERS; i++)
12491 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12493 if (!stored_player[i].active || &stored_player[i] == player)
12496 min_x = MIN(min_x, jx);
12497 min_y = MIN(min_y, jy);
12498 max_x = MAX(max_x, jx);
12499 max_y = MAX(max_y, jy);
12502 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12505 static boolean AllPlayersInVisibleScreen(void)
12509 for (i = 0; i < MAX_PLAYERS; i++)
12511 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12513 if (!stored_player[i].active)
12516 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12523 void ScrollLevel(int dx, int dy)
12525 int scroll_offset = 2 * TILEX_VAR;
12528 BlitBitmap(drawto_field, drawto_field,
12529 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12530 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12531 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12532 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12533 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12534 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12538 x = (dx == 1 ? BX1 : BX2);
12539 for (y = BY1; y <= BY2; y++)
12540 DrawScreenField(x, y);
12545 y = (dy == 1 ? BY1 : BY2);
12546 for (x = BX1; x <= BX2; x++)
12547 DrawScreenField(x, y);
12550 redraw_mask |= REDRAW_FIELD;
12553 static boolean canFallDown(struct PlayerInfo *player)
12555 int jx = player->jx, jy = player->jy;
12557 return (IN_LEV_FIELD(jx, jy + 1) &&
12558 (IS_FREE(jx, jy + 1) ||
12559 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12560 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12561 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12564 static boolean canPassField(int x, int y, int move_dir)
12566 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12567 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12568 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12569 int nextx = x + dx;
12570 int nexty = y + dy;
12571 int element = Tile[x][y];
12573 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12574 !CAN_MOVE(element) &&
12575 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12576 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12577 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12580 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12582 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12583 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12584 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12588 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12589 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12590 (IS_DIGGABLE(Tile[newx][newy]) ||
12591 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12592 canPassField(newx, newy, move_dir)));
12595 static void CheckGravityMovement(struct PlayerInfo *player)
12597 if (player->gravity && !player->programmed_action)
12599 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12600 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12601 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12602 int jx = player->jx, jy = player->jy;
12603 boolean player_is_moving_to_valid_field =
12604 (!player_is_snapping &&
12605 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12606 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12607 boolean player_can_fall_down = canFallDown(player);
12609 if (player_can_fall_down &&
12610 !player_is_moving_to_valid_field)
12611 player->programmed_action = MV_DOWN;
12615 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12617 return CheckGravityMovement(player);
12619 if (player->gravity && !player->programmed_action)
12621 int jx = player->jx, jy = player->jy;
12622 boolean field_under_player_is_free =
12623 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12624 boolean player_is_standing_on_valid_field =
12625 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12626 (IS_WALKABLE(Tile[jx][jy]) &&
12627 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12629 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12630 player->programmed_action = MV_DOWN;
12635 MovePlayerOneStep()
12636 -----------------------------------------------------------------------------
12637 dx, dy: direction (non-diagonal) to try to move the player to
12638 real_dx, real_dy: direction as read from input device (can be diagonal)
12641 boolean MovePlayerOneStep(struct PlayerInfo *player,
12642 int dx, int dy, int real_dx, int real_dy)
12644 int jx = player->jx, jy = player->jy;
12645 int new_jx = jx + dx, new_jy = jy + dy;
12647 boolean player_can_move = !player->cannot_move;
12649 if (!player->active || (!dx && !dy))
12650 return MP_NO_ACTION;
12652 player->MovDir = (dx < 0 ? MV_LEFT :
12653 dx > 0 ? MV_RIGHT :
12655 dy > 0 ? MV_DOWN : MV_NONE);
12657 if (!IN_LEV_FIELD(new_jx, new_jy))
12658 return MP_NO_ACTION;
12660 if (!player_can_move)
12662 if (player->MovPos == 0)
12664 player->is_moving = FALSE;
12665 player->is_digging = FALSE;
12666 player->is_collecting = FALSE;
12667 player->is_snapping = FALSE;
12668 player->is_pushing = FALSE;
12672 if (!network.enabled && game.centered_player_nr == -1 &&
12673 !AllPlayersInSight(player, new_jx, new_jy))
12674 return MP_NO_ACTION;
12676 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12677 if (can_move != MP_MOVING)
12680 // check if DigField() has caused relocation of the player
12681 if (player->jx != jx || player->jy != jy)
12682 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12684 StorePlayer[jx][jy] = 0;
12685 player->last_jx = jx;
12686 player->last_jy = jy;
12687 player->jx = new_jx;
12688 player->jy = new_jy;
12689 StorePlayer[new_jx][new_jy] = player->element_nr;
12691 if (player->move_delay_value_next != -1)
12693 player->move_delay_value = player->move_delay_value_next;
12694 player->move_delay_value_next = -1;
12698 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12700 player->step_counter++;
12702 PlayerVisit[jx][jy] = FrameCounter;
12704 player->is_moving = TRUE;
12707 // should better be called in MovePlayer(), but this breaks some tapes
12708 ScrollPlayer(player, SCROLL_INIT);
12714 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12716 int jx = player->jx, jy = player->jy;
12717 int old_jx = jx, old_jy = jy;
12718 int moved = MP_NO_ACTION;
12720 if (!player->active)
12725 if (player->MovPos == 0)
12727 player->is_moving = FALSE;
12728 player->is_digging = FALSE;
12729 player->is_collecting = FALSE;
12730 player->is_snapping = FALSE;
12731 player->is_pushing = FALSE;
12737 if (player->move_delay > 0)
12740 player->move_delay = -1; // set to "uninitialized" value
12742 // store if player is automatically moved to next field
12743 player->is_auto_moving = (player->programmed_action != MV_NONE);
12745 // remove the last programmed player action
12746 player->programmed_action = 0;
12748 if (player->MovPos)
12750 // should only happen if pre-1.2 tape recordings are played
12751 // this is only for backward compatibility
12753 int original_move_delay_value = player->move_delay_value;
12756 Debug("game:playing:MovePlayer",
12757 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12761 // scroll remaining steps with finest movement resolution
12762 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12764 while (player->MovPos)
12766 ScrollPlayer(player, SCROLL_GO_ON);
12767 ScrollScreen(NULL, SCROLL_GO_ON);
12769 AdvanceFrameAndPlayerCounters(player->index_nr);
12772 BackToFront_WithFrameDelay(0);
12775 player->move_delay_value = original_move_delay_value;
12778 player->is_active = FALSE;
12780 if (player->last_move_dir & MV_HORIZONTAL)
12782 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12783 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12787 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12788 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12791 if (!moved && !player->is_active)
12793 player->is_moving = FALSE;
12794 player->is_digging = FALSE;
12795 player->is_collecting = FALSE;
12796 player->is_snapping = FALSE;
12797 player->is_pushing = FALSE;
12803 if (moved & MP_MOVING && !ScreenMovPos &&
12804 (player->index_nr == game.centered_player_nr ||
12805 game.centered_player_nr == -1))
12807 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12809 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12811 // actual player has left the screen -- scroll in that direction
12812 if (jx != old_jx) // player has moved horizontally
12813 scroll_x += (jx - old_jx);
12814 else // player has moved vertically
12815 scroll_y += (jy - old_jy);
12819 int offset_raw = game.scroll_delay_value;
12821 if (jx != old_jx) // player has moved horizontally
12823 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12824 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12825 int new_scroll_x = jx - MIDPOSX + offset_x;
12827 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12828 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12829 scroll_x = new_scroll_x;
12831 // don't scroll over playfield boundaries
12832 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12834 // don't scroll more than one field at a time
12835 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12837 // don't scroll against the player's moving direction
12838 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12839 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12840 scroll_x = old_scroll_x;
12842 else // player has moved vertically
12844 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12845 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12846 int new_scroll_y = jy - MIDPOSY + offset_y;
12848 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12849 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12850 scroll_y = new_scroll_y;
12852 // don't scroll over playfield boundaries
12853 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12855 // don't scroll more than one field at a time
12856 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12858 // don't scroll against the player's moving direction
12859 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12860 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12861 scroll_y = old_scroll_y;
12865 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12867 if (!network.enabled && game.centered_player_nr == -1 &&
12868 !AllPlayersInVisibleScreen())
12870 scroll_x = old_scroll_x;
12871 scroll_y = old_scroll_y;
12875 ScrollScreen(player, SCROLL_INIT);
12876 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12881 player->StepFrame = 0;
12883 if (moved & MP_MOVING)
12885 if (old_jx != jx && old_jy == jy)
12886 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12887 else if (old_jx == jx && old_jy != jy)
12888 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12890 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12892 player->last_move_dir = player->MovDir;
12893 player->is_moving = TRUE;
12894 player->is_snapping = FALSE;
12895 player->is_switching = FALSE;
12896 player->is_dropping = FALSE;
12897 player->is_dropping_pressed = FALSE;
12898 player->drop_pressed_delay = 0;
12901 // should better be called here than above, but this breaks some tapes
12902 ScrollPlayer(player, SCROLL_INIT);
12907 CheckGravityMovementWhenNotMoving(player);
12909 player->is_moving = FALSE;
12911 /* at this point, the player is allowed to move, but cannot move right now
12912 (e.g. because of something blocking the way) -- ensure that the player
12913 is also allowed to move in the next frame (in old versions before 3.1.1,
12914 the player was forced to wait again for eight frames before next try) */
12916 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12917 player->move_delay = 0; // allow direct movement in the next frame
12920 if (player->move_delay == -1) // not yet initialized by DigField()
12921 player->move_delay = player->move_delay_value;
12923 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12925 TestIfPlayerTouchesBadThing(jx, jy);
12926 TestIfPlayerTouchesCustomElement(jx, jy);
12929 if (!player->active)
12930 RemovePlayer(player);
12935 void ScrollPlayer(struct PlayerInfo *player, int mode)
12937 int jx = player->jx, jy = player->jy;
12938 int last_jx = player->last_jx, last_jy = player->last_jy;
12939 int move_stepsize = TILEX / player->move_delay_value;
12941 if (!player->active)
12944 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12947 if (mode == SCROLL_INIT)
12949 player->actual_frame_counter = FrameCounter;
12950 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12952 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12953 Tile[last_jx][last_jy] == EL_EMPTY)
12955 int last_field_block_delay = 0; // start with no blocking at all
12956 int block_delay_adjustment = player->block_delay_adjustment;
12958 // if player blocks last field, add delay for exactly one move
12959 if (player->block_last_field)
12961 last_field_block_delay += player->move_delay_value;
12963 // when blocking enabled, prevent moving up despite gravity
12964 if (player->gravity && player->MovDir == MV_UP)
12965 block_delay_adjustment = -1;
12968 // add block delay adjustment (also possible when not blocking)
12969 last_field_block_delay += block_delay_adjustment;
12971 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12972 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12975 if (player->MovPos != 0) // player has not yet reached destination
12978 else if (!FrameReached(&player->actual_frame_counter, 1))
12981 if (player->MovPos != 0)
12983 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12984 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12986 // before DrawPlayer() to draw correct player graphic for this case
12987 if (player->MovPos == 0)
12988 CheckGravityMovement(player);
12991 if (player->MovPos == 0) // player reached destination field
12993 if (player->move_delay_reset_counter > 0)
12995 player->move_delay_reset_counter--;
12997 if (player->move_delay_reset_counter == 0)
12999 // continue with normal speed after quickly moving through gate
13000 HALVE_PLAYER_SPEED(player);
13002 // be able to make the next move without delay
13003 player->move_delay = 0;
13007 player->last_jx = jx;
13008 player->last_jy = jy;
13010 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13011 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13012 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13013 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13014 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13015 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13016 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13017 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13019 ExitPlayer(player);
13021 if (game.players_still_needed == 0 &&
13022 (game.friends_still_needed == 0 ||
13023 IS_SP_ELEMENT(Tile[jx][jy])))
13027 // this breaks one level: "machine", level 000
13029 int move_direction = player->MovDir;
13030 int enter_side = MV_DIR_OPPOSITE(move_direction);
13031 int leave_side = move_direction;
13032 int old_jx = last_jx;
13033 int old_jy = last_jy;
13034 int old_element = Tile[old_jx][old_jy];
13035 int new_element = Tile[jx][jy];
13037 if (IS_CUSTOM_ELEMENT(old_element))
13038 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13040 player->index_bit, leave_side);
13042 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13043 CE_PLAYER_LEAVES_X,
13044 player->index_bit, leave_side);
13046 if (IS_CUSTOM_ELEMENT(new_element))
13047 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13048 player->index_bit, enter_side);
13050 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13051 CE_PLAYER_ENTERS_X,
13052 player->index_bit, enter_side);
13054 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13055 CE_MOVE_OF_X, move_direction);
13058 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13060 TestIfPlayerTouchesBadThing(jx, jy);
13061 TestIfPlayerTouchesCustomElement(jx, jy);
13063 /* needed because pushed element has not yet reached its destination,
13064 so it would trigger a change event at its previous field location */
13065 if (!player->is_pushing)
13066 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13068 if (level.finish_dig_collect &&
13069 (player->is_digging || player->is_collecting))
13071 int last_element = player->last_removed_element;
13072 int move_direction = player->MovDir;
13073 int enter_side = MV_DIR_OPPOSITE(move_direction);
13074 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13075 CE_PLAYER_COLLECTS_X);
13077 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13078 player->index_bit, enter_side);
13080 player->last_removed_element = EL_UNDEFINED;
13083 if (!player->active)
13084 RemovePlayer(player);
13087 if (!game.LevelSolved && level.use_step_counter)
13097 if (TimeLeft <= 10 && setup.time_limit)
13098 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13100 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13102 DisplayGameControlValues();
13104 if (!TimeLeft && setup.time_limit)
13105 for (i = 0; i < MAX_PLAYERS; i++)
13106 KillPlayer(&stored_player[i]);
13108 else if (game.no_time_limit && !game.all_players_gone)
13110 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13112 DisplayGameControlValues();
13116 if (tape.single_step && tape.recording && !tape.pausing &&
13117 !player->programmed_action)
13118 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13120 if (!player->programmed_action)
13121 CheckSaveEngineSnapshot(player);
13125 void ScrollScreen(struct PlayerInfo *player, int mode)
13127 static unsigned int screen_frame_counter = 0;
13129 if (mode == SCROLL_INIT)
13131 // set scrolling step size according to actual player's moving speed
13132 ScrollStepSize = TILEX / player->move_delay_value;
13134 screen_frame_counter = FrameCounter;
13135 ScreenMovDir = player->MovDir;
13136 ScreenMovPos = player->MovPos;
13137 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13140 else if (!FrameReached(&screen_frame_counter, 1))
13145 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13146 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13147 redraw_mask |= REDRAW_FIELD;
13150 ScreenMovDir = MV_NONE;
13153 void TestIfPlayerTouchesCustomElement(int x, int y)
13155 static int xy[4][2] =
13162 static int trigger_sides[4][2] =
13164 // center side border side
13165 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13166 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13167 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13168 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13170 static int touch_dir[4] =
13172 MV_LEFT | MV_RIGHT,
13177 int center_element = Tile[x][y]; // should always be non-moving!
13180 for (i = 0; i < NUM_DIRECTIONS; i++)
13182 int xx = x + xy[i][0];
13183 int yy = y + xy[i][1];
13184 int center_side = trigger_sides[i][0];
13185 int border_side = trigger_sides[i][1];
13186 int border_element;
13188 if (!IN_LEV_FIELD(xx, yy))
13191 if (IS_PLAYER(x, y)) // player found at center element
13193 struct PlayerInfo *player = PLAYERINFO(x, y);
13195 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13196 border_element = Tile[xx][yy]; // may be moving!
13197 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13198 border_element = Tile[xx][yy];
13199 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13200 border_element = MovingOrBlocked2Element(xx, yy);
13202 continue; // center and border element do not touch
13204 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13205 player->index_bit, border_side);
13206 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13207 CE_PLAYER_TOUCHES_X,
13208 player->index_bit, border_side);
13211 /* use player element that is initially defined in the level playfield,
13212 not the player element that corresponds to the runtime player number
13213 (example: a level that contains EL_PLAYER_3 as the only player would
13214 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13215 int player_element = PLAYERINFO(x, y)->initial_element;
13217 CheckElementChangeBySide(xx, yy, border_element, player_element,
13218 CE_TOUCHING_X, border_side);
13221 else if (IS_PLAYER(xx, yy)) // player found at border element
13223 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13225 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13227 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13228 continue; // center and border element do not touch
13231 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13232 player->index_bit, center_side);
13233 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13234 CE_PLAYER_TOUCHES_X,
13235 player->index_bit, center_side);
13238 /* use player element that is initially defined in the level playfield,
13239 not the player element that corresponds to the runtime player number
13240 (example: a level that contains EL_PLAYER_3 as the only player would
13241 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13242 int player_element = PLAYERINFO(xx, yy)->initial_element;
13244 CheckElementChangeBySide(x, y, center_element, player_element,
13245 CE_TOUCHING_X, center_side);
13253 void TestIfElementTouchesCustomElement(int x, int y)
13255 static int xy[4][2] =
13262 static int trigger_sides[4][2] =
13264 // center side border side
13265 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13266 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13267 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13268 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13270 static int touch_dir[4] =
13272 MV_LEFT | MV_RIGHT,
13277 boolean change_center_element = FALSE;
13278 int center_element = Tile[x][y]; // should always be non-moving!
13279 int border_element_old[NUM_DIRECTIONS];
13282 for (i = 0; i < NUM_DIRECTIONS; i++)
13284 int xx = x + xy[i][0];
13285 int yy = y + xy[i][1];
13286 int border_element;
13288 border_element_old[i] = -1;
13290 if (!IN_LEV_FIELD(xx, yy))
13293 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13294 border_element = Tile[xx][yy]; // may be moving!
13295 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13296 border_element = Tile[xx][yy];
13297 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13298 border_element = MovingOrBlocked2Element(xx, yy);
13300 continue; // center and border element do not touch
13302 border_element_old[i] = border_element;
13305 for (i = 0; i < NUM_DIRECTIONS; i++)
13307 int xx = x + xy[i][0];
13308 int yy = y + xy[i][1];
13309 int center_side = trigger_sides[i][0];
13310 int border_element = border_element_old[i];
13312 if (border_element == -1)
13315 // check for change of border element
13316 CheckElementChangeBySide(xx, yy, border_element, center_element,
13317 CE_TOUCHING_X, center_side);
13319 // (center element cannot be player, so we dont have to check this here)
13322 for (i = 0; i < NUM_DIRECTIONS; i++)
13324 int xx = x + xy[i][0];
13325 int yy = y + xy[i][1];
13326 int border_side = trigger_sides[i][1];
13327 int border_element = border_element_old[i];
13329 if (border_element == -1)
13332 // check for change of center element (but change it only once)
13333 if (!change_center_element)
13334 change_center_element =
13335 CheckElementChangeBySide(x, y, center_element, border_element,
13336 CE_TOUCHING_X, border_side);
13338 if (IS_PLAYER(xx, yy))
13340 /* use player element that is initially defined in the level playfield,
13341 not the player element that corresponds to the runtime player number
13342 (example: a level that contains EL_PLAYER_3 as the only player would
13343 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13344 int player_element = PLAYERINFO(xx, yy)->initial_element;
13346 CheckElementChangeBySide(x, y, center_element, player_element,
13347 CE_TOUCHING_X, border_side);
13352 void TestIfElementHitsCustomElement(int x, int y, int direction)
13354 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13355 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13356 int hitx = x + dx, hity = y + dy;
13357 int hitting_element = Tile[x][y];
13358 int touched_element;
13360 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13363 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13364 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13366 if (IN_LEV_FIELD(hitx, hity))
13368 int opposite_direction = MV_DIR_OPPOSITE(direction);
13369 int hitting_side = direction;
13370 int touched_side = opposite_direction;
13371 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13372 MovDir[hitx][hity] != direction ||
13373 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13379 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13380 CE_HITTING_X, touched_side);
13382 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13383 CE_HIT_BY_X, hitting_side);
13385 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13386 CE_HIT_BY_SOMETHING, opposite_direction);
13388 if (IS_PLAYER(hitx, hity))
13390 /* use player element that is initially defined in the level playfield,
13391 not the player element that corresponds to the runtime player number
13392 (example: a level that contains EL_PLAYER_3 as the only player would
13393 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13394 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13396 CheckElementChangeBySide(x, y, hitting_element, player_element,
13397 CE_HITTING_X, touched_side);
13402 // "hitting something" is also true when hitting the playfield border
13403 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13404 CE_HITTING_SOMETHING, direction);
13407 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13409 int i, kill_x = -1, kill_y = -1;
13411 int bad_element = -1;
13412 static int test_xy[4][2] =
13419 static int test_dir[4] =
13427 for (i = 0; i < NUM_DIRECTIONS; i++)
13429 int test_x, test_y, test_move_dir, test_element;
13431 test_x = good_x + test_xy[i][0];
13432 test_y = good_y + test_xy[i][1];
13434 if (!IN_LEV_FIELD(test_x, test_y))
13438 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13440 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13442 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13443 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13445 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13446 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13450 bad_element = test_element;
13456 if (kill_x != -1 || kill_y != -1)
13458 if (IS_PLAYER(good_x, good_y))
13460 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13462 if (player->shield_deadly_time_left > 0 &&
13463 !IS_INDESTRUCTIBLE(bad_element))
13464 Bang(kill_x, kill_y);
13465 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13466 KillPlayer(player);
13469 Bang(good_x, good_y);
13473 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13475 int i, kill_x = -1, kill_y = -1;
13476 int bad_element = Tile[bad_x][bad_y];
13477 static int test_xy[4][2] =
13484 static int touch_dir[4] =
13486 MV_LEFT | MV_RIGHT,
13491 static int test_dir[4] =
13499 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13502 for (i = 0; i < NUM_DIRECTIONS; i++)
13504 int test_x, test_y, test_move_dir, test_element;
13506 test_x = bad_x + test_xy[i][0];
13507 test_y = bad_y + test_xy[i][1];
13509 if (!IN_LEV_FIELD(test_x, test_y))
13513 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13515 test_element = Tile[test_x][test_y];
13517 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13518 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13520 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13521 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13523 // good thing is player or penguin that does not move away
13524 if (IS_PLAYER(test_x, test_y))
13526 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13528 if (bad_element == EL_ROBOT && player->is_moving)
13529 continue; // robot does not kill player if he is moving
13531 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13533 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13534 continue; // center and border element do not touch
13542 else if (test_element == EL_PENGUIN)
13552 if (kill_x != -1 || kill_y != -1)
13554 if (IS_PLAYER(kill_x, kill_y))
13556 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13558 if (player->shield_deadly_time_left > 0 &&
13559 !IS_INDESTRUCTIBLE(bad_element))
13560 Bang(bad_x, bad_y);
13561 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13562 KillPlayer(player);
13565 Bang(kill_x, kill_y);
13569 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13571 int bad_element = Tile[bad_x][bad_y];
13572 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13573 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13574 int test_x = bad_x + dx, test_y = bad_y + dy;
13575 int test_move_dir, test_element;
13576 int kill_x = -1, kill_y = -1;
13578 if (!IN_LEV_FIELD(test_x, test_y))
13582 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13584 test_element = Tile[test_x][test_y];
13586 if (test_move_dir != bad_move_dir)
13588 // good thing can be player or penguin that does not move away
13589 if (IS_PLAYER(test_x, test_y))
13591 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13593 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13594 player as being hit when he is moving towards the bad thing, because
13595 the "get hit by" condition would be lost after the player stops) */
13596 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13597 return; // player moves away from bad thing
13602 else if (test_element == EL_PENGUIN)
13609 if (kill_x != -1 || kill_y != -1)
13611 if (IS_PLAYER(kill_x, kill_y))
13613 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13615 if (player->shield_deadly_time_left > 0 &&
13616 !IS_INDESTRUCTIBLE(bad_element))
13617 Bang(bad_x, bad_y);
13618 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13619 KillPlayer(player);
13622 Bang(kill_x, kill_y);
13626 void TestIfPlayerTouchesBadThing(int x, int y)
13628 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13631 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13633 TestIfGoodThingHitsBadThing(x, y, move_dir);
13636 void TestIfBadThingTouchesPlayer(int x, int y)
13638 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13641 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13643 TestIfBadThingHitsGoodThing(x, y, move_dir);
13646 void TestIfFriendTouchesBadThing(int x, int y)
13648 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13651 void TestIfBadThingTouchesFriend(int x, int y)
13653 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13656 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13658 int i, kill_x = bad_x, kill_y = bad_y;
13659 static int xy[4][2] =
13667 for (i = 0; i < NUM_DIRECTIONS; i++)
13671 x = bad_x + xy[i][0];
13672 y = bad_y + xy[i][1];
13673 if (!IN_LEV_FIELD(x, y))
13676 element = Tile[x][y];
13677 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13678 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13686 if (kill_x != bad_x || kill_y != bad_y)
13687 Bang(bad_x, bad_y);
13690 void KillPlayer(struct PlayerInfo *player)
13692 int jx = player->jx, jy = player->jy;
13694 if (!player->active)
13698 Debug("game:playing:KillPlayer",
13699 "0: killed == %d, active == %d, reanimated == %d",
13700 player->killed, player->active, player->reanimated);
13703 /* the following code was introduced to prevent an infinite loop when calling
13705 -> CheckTriggeredElementChangeExt()
13706 -> ExecuteCustomElementAction()
13708 -> (infinitely repeating the above sequence of function calls)
13709 which occurs when killing the player while having a CE with the setting
13710 "kill player X when explosion of <player X>"; the solution using a new
13711 field "player->killed" was chosen for backwards compatibility, although
13712 clever use of the fields "player->active" etc. would probably also work */
13714 if (player->killed)
13718 player->killed = TRUE;
13720 // remove accessible field at the player's position
13721 Tile[jx][jy] = EL_EMPTY;
13723 // deactivate shield (else Bang()/Explode() would not work right)
13724 player->shield_normal_time_left = 0;
13725 player->shield_deadly_time_left = 0;
13728 Debug("game:playing:KillPlayer",
13729 "1: killed == %d, active == %d, reanimated == %d",
13730 player->killed, player->active, player->reanimated);
13736 Debug("game:playing:KillPlayer",
13737 "2: killed == %d, active == %d, reanimated == %d",
13738 player->killed, player->active, player->reanimated);
13741 if (player->reanimated) // killed player may have been reanimated
13742 player->killed = player->reanimated = FALSE;
13744 BuryPlayer(player);
13747 static void KillPlayerUnlessEnemyProtected(int x, int y)
13749 if (!PLAYER_ENEMY_PROTECTED(x, y))
13750 KillPlayer(PLAYERINFO(x, y));
13753 static void KillPlayerUnlessExplosionProtected(int x, int y)
13755 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13756 KillPlayer(PLAYERINFO(x, y));
13759 void BuryPlayer(struct PlayerInfo *player)
13761 int jx = player->jx, jy = player->jy;
13763 if (!player->active)
13766 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13767 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13769 RemovePlayer(player);
13771 player->buried = TRUE;
13773 if (game.all_players_gone)
13774 game.GameOver = TRUE;
13777 void RemovePlayer(struct PlayerInfo *player)
13779 int jx = player->jx, jy = player->jy;
13780 int i, found = FALSE;
13782 player->present = FALSE;
13783 player->active = FALSE;
13785 // required for some CE actions (even if the player is not active anymore)
13786 player->MovPos = 0;
13788 if (!ExplodeField[jx][jy])
13789 StorePlayer[jx][jy] = 0;
13791 if (player->is_moving)
13792 TEST_DrawLevelField(player->last_jx, player->last_jy);
13794 for (i = 0; i < MAX_PLAYERS; i++)
13795 if (stored_player[i].active)
13800 game.all_players_gone = TRUE;
13801 game.GameOver = TRUE;
13804 game.exit_x = game.robot_wheel_x = jx;
13805 game.exit_y = game.robot_wheel_y = jy;
13808 void ExitPlayer(struct PlayerInfo *player)
13810 DrawPlayer(player); // needed here only to cleanup last field
13811 RemovePlayer(player);
13813 if (game.players_still_needed > 0)
13814 game.players_still_needed--;
13817 static void SetFieldForSnapping(int x, int y, int element, int direction,
13818 int player_index_bit)
13820 struct ElementInfo *ei = &element_info[element];
13821 int direction_bit = MV_DIR_TO_BIT(direction);
13822 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13823 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13824 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13826 Tile[x][y] = EL_ELEMENT_SNAPPING;
13827 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13828 MovDir[x][y] = direction;
13829 Store[x][y] = element;
13830 Store2[x][y] = player_index_bit;
13832 ResetGfxAnimation(x, y);
13834 GfxElement[x][y] = element;
13835 GfxAction[x][y] = action;
13836 GfxDir[x][y] = direction;
13837 GfxFrame[x][y] = -1;
13840 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
13841 int player_index_bit)
13843 TestIfElementTouchesCustomElement(x, y); // for empty space
13845 if (level.finish_dig_collect)
13847 int dig_side = MV_DIR_OPPOSITE(direction);
13849 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13850 player_index_bit, dig_side);
13855 =============================================================================
13856 checkDiagonalPushing()
13857 -----------------------------------------------------------------------------
13858 check if diagonal input device direction results in pushing of object
13859 (by checking if the alternative direction is walkable, diggable, ...)
13860 =============================================================================
13863 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13864 int x, int y, int real_dx, int real_dy)
13866 int jx, jy, dx, dy, xx, yy;
13868 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13871 // diagonal direction: check alternative direction
13876 xx = jx + (dx == 0 ? real_dx : 0);
13877 yy = jy + (dy == 0 ? real_dy : 0);
13879 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13883 =============================================================================
13885 -----------------------------------------------------------------------------
13886 x, y: field next to player (non-diagonal) to try to dig to
13887 real_dx, real_dy: direction as read from input device (can be diagonal)
13888 =============================================================================
13891 static int DigField(struct PlayerInfo *player,
13892 int oldx, int oldy, int x, int y,
13893 int real_dx, int real_dy, int mode)
13895 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13896 boolean player_was_pushing = player->is_pushing;
13897 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13898 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13899 int jx = oldx, jy = oldy;
13900 int dx = x - jx, dy = y - jy;
13901 int nextx = x + dx, nexty = y + dy;
13902 int move_direction = (dx == -1 ? MV_LEFT :
13903 dx == +1 ? MV_RIGHT :
13905 dy == +1 ? MV_DOWN : MV_NONE);
13906 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13907 int dig_side = MV_DIR_OPPOSITE(move_direction);
13908 int old_element = Tile[jx][jy];
13909 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13912 if (is_player) // function can also be called by EL_PENGUIN
13914 if (player->MovPos == 0)
13916 player->is_digging = FALSE;
13917 player->is_collecting = FALSE;
13920 if (player->MovPos == 0) // last pushing move finished
13921 player->is_pushing = FALSE;
13923 if (mode == DF_NO_PUSH) // player just stopped pushing
13925 player->is_switching = FALSE;
13926 player->push_delay = -1;
13928 return MP_NO_ACTION;
13932 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13933 old_element = Back[jx][jy];
13935 // in case of element dropped at player position, check background
13936 else if (Back[jx][jy] != EL_EMPTY &&
13937 game.engine_version >= VERSION_IDENT(2,2,0,0))
13938 old_element = Back[jx][jy];
13940 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13941 return MP_NO_ACTION; // field has no opening in this direction
13943 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13944 return MP_NO_ACTION; // field has no opening in this direction
13946 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13950 Tile[jx][jy] = player->artwork_element;
13951 InitMovingField(jx, jy, MV_DOWN);
13952 Store[jx][jy] = EL_ACID;
13953 ContinueMoving(jx, jy);
13954 BuryPlayer(player);
13956 return MP_DONT_RUN_INTO;
13959 if (player_can_move && DONT_RUN_INTO(element))
13961 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13963 return MP_DONT_RUN_INTO;
13966 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13967 return MP_NO_ACTION;
13969 collect_count = element_info[element].collect_count_initial;
13971 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13972 return MP_NO_ACTION;
13974 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13975 player_can_move = player_can_move_or_snap;
13977 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13978 game.engine_version >= VERSION_IDENT(2,2,0,0))
13980 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13981 player->index_bit, dig_side);
13982 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13983 player->index_bit, dig_side);
13985 if (element == EL_DC_LANDMINE)
13988 if (Tile[x][y] != element) // field changed by snapping
13991 return MP_NO_ACTION;
13994 if (player->gravity && is_player && !player->is_auto_moving &&
13995 canFallDown(player) && move_direction != MV_DOWN &&
13996 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13997 return MP_NO_ACTION; // player cannot walk here due to gravity
13999 if (player_can_move &&
14000 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14002 int sound_element = SND_ELEMENT(element);
14003 int sound_action = ACTION_WALKING;
14005 if (IS_RND_GATE(element))
14007 if (!player->key[RND_GATE_NR(element)])
14008 return MP_NO_ACTION;
14010 else if (IS_RND_GATE_GRAY(element))
14012 if (!player->key[RND_GATE_GRAY_NR(element)])
14013 return MP_NO_ACTION;
14015 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14017 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14018 return MP_NO_ACTION;
14020 else if (element == EL_EXIT_OPEN ||
14021 element == EL_EM_EXIT_OPEN ||
14022 element == EL_EM_EXIT_OPENING ||
14023 element == EL_STEEL_EXIT_OPEN ||
14024 element == EL_EM_STEEL_EXIT_OPEN ||
14025 element == EL_EM_STEEL_EXIT_OPENING ||
14026 element == EL_SP_EXIT_OPEN ||
14027 element == EL_SP_EXIT_OPENING)
14029 sound_action = ACTION_PASSING; // player is passing exit
14031 else if (element == EL_EMPTY)
14033 sound_action = ACTION_MOVING; // nothing to walk on
14036 // play sound from background or player, whatever is available
14037 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14038 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14040 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14042 else if (player_can_move &&
14043 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14045 if (!ACCESS_FROM(element, opposite_direction))
14046 return MP_NO_ACTION; // field not accessible from this direction
14048 if (CAN_MOVE(element)) // only fixed elements can be passed!
14049 return MP_NO_ACTION;
14051 if (IS_EM_GATE(element))
14053 if (!player->key[EM_GATE_NR(element)])
14054 return MP_NO_ACTION;
14056 else if (IS_EM_GATE_GRAY(element))
14058 if (!player->key[EM_GATE_GRAY_NR(element)])
14059 return MP_NO_ACTION;
14061 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14063 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14064 return MP_NO_ACTION;
14066 else if (IS_EMC_GATE(element))
14068 if (!player->key[EMC_GATE_NR(element)])
14069 return MP_NO_ACTION;
14071 else if (IS_EMC_GATE_GRAY(element))
14073 if (!player->key[EMC_GATE_GRAY_NR(element)])
14074 return MP_NO_ACTION;
14076 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14078 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14079 return MP_NO_ACTION;
14081 else if (element == EL_DC_GATE_WHITE ||
14082 element == EL_DC_GATE_WHITE_GRAY ||
14083 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14085 if (player->num_white_keys == 0)
14086 return MP_NO_ACTION;
14088 player->num_white_keys--;
14090 else if (IS_SP_PORT(element))
14092 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14093 element == EL_SP_GRAVITY_PORT_RIGHT ||
14094 element == EL_SP_GRAVITY_PORT_UP ||
14095 element == EL_SP_GRAVITY_PORT_DOWN)
14096 player->gravity = !player->gravity;
14097 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14098 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14099 element == EL_SP_GRAVITY_ON_PORT_UP ||
14100 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14101 player->gravity = TRUE;
14102 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14103 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14104 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14105 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14106 player->gravity = FALSE;
14109 // automatically move to the next field with double speed
14110 player->programmed_action = move_direction;
14112 if (player->move_delay_reset_counter == 0)
14114 player->move_delay_reset_counter = 2; // two double speed steps
14116 DOUBLE_PLAYER_SPEED(player);
14119 PlayLevelSoundAction(x, y, ACTION_PASSING);
14121 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14125 if (mode != DF_SNAP)
14127 GfxElement[x][y] = GFX_ELEMENT(element);
14128 player->is_digging = TRUE;
14131 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14133 // use old behaviour for old levels (digging)
14134 if (!level.finish_dig_collect)
14136 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14137 player->index_bit, dig_side);
14139 // if digging triggered player relocation, finish digging tile
14140 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14141 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14144 if (mode == DF_SNAP)
14146 if (level.block_snap_field)
14147 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14149 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14151 // use old behaviour for old levels (snapping)
14152 if (!level.finish_dig_collect)
14153 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14154 player->index_bit, dig_side);
14157 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14161 if (is_player && mode != DF_SNAP)
14163 GfxElement[x][y] = element;
14164 player->is_collecting = TRUE;
14167 if (element == EL_SPEED_PILL)
14169 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14171 else if (element == EL_EXTRA_TIME && level.time > 0)
14173 TimeLeft += level.extra_time;
14175 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14177 DisplayGameControlValues();
14179 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14181 player->shield_normal_time_left += level.shield_normal_time;
14182 if (element == EL_SHIELD_DEADLY)
14183 player->shield_deadly_time_left += level.shield_deadly_time;
14185 else if (element == EL_DYNAMITE ||
14186 element == EL_EM_DYNAMITE ||
14187 element == EL_SP_DISK_RED)
14189 if (player->inventory_size < MAX_INVENTORY_SIZE)
14190 player->inventory_element[player->inventory_size++] = element;
14192 DrawGameDoorValues();
14194 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14196 player->dynabomb_count++;
14197 player->dynabombs_left++;
14199 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14201 player->dynabomb_size++;
14203 else if (element == EL_DYNABOMB_INCREASE_POWER)
14205 player->dynabomb_xl = TRUE;
14207 else if (IS_KEY(element))
14209 player->key[KEY_NR(element)] = TRUE;
14211 DrawGameDoorValues();
14213 else if (element == EL_DC_KEY_WHITE)
14215 player->num_white_keys++;
14217 // display white keys?
14218 // DrawGameDoorValues();
14220 else if (IS_ENVELOPE(element))
14222 player->show_envelope = element;
14224 else if (element == EL_EMC_LENSES)
14226 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14228 RedrawAllInvisibleElementsForLenses();
14230 else if (element == EL_EMC_MAGNIFIER)
14232 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14234 RedrawAllInvisibleElementsForMagnifier();
14236 else if (IS_DROPPABLE(element) ||
14237 IS_THROWABLE(element)) // can be collected and dropped
14241 if (collect_count == 0)
14242 player->inventory_infinite_element = element;
14244 for (i = 0; i < collect_count; i++)
14245 if (player->inventory_size < MAX_INVENTORY_SIZE)
14246 player->inventory_element[player->inventory_size++] = element;
14248 DrawGameDoorValues();
14250 else if (collect_count > 0)
14252 game.gems_still_needed -= collect_count;
14253 if (game.gems_still_needed < 0)
14254 game.gems_still_needed = 0;
14256 game.snapshot.collected_item = TRUE;
14258 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14260 DisplayGameControlValues();
14263 RaiseScoreElement(element);
14264 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14266 // use old behaviour for old levels (collecting)
14267 if (!level.finish_dig_collect && is_player)
14269 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14270 player->index_bit, dig_side);
14272 // if collecting triggered player relocation, finish collecting tile
14273 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14274 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14277 if (mode == DF_SNAP)
14279 if (level.block_snap_field)
14280 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14282 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14284 // use old behaviour for old levels (snapping)
14285 if (!level.finish_dig_collect)
14286 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14287 player->index_bit, dig_side);
14290 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14292 if (mode == DF_SNAP && element != EL_BD_ROCK)
14293 return MP_NO_ACTION;
14295 if (CAN_FALL(element) && dy)
14296 return MP_NO_ACTION;
14298 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14299 !(element == EL_SPRING && level.use_spring_bug))
14300 return MP_NO_ACTION;
14302 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14303 ((move_direction & MV_VERTICAL &&
14304 ((element_info[element].move_pattern & MV_LEFT &&
14305 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14306 (element_info[element].move_pattern & MV_RIGHT &&
14307 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14308 (move_direction & MV_HORIZONTAL &&
14309 ((element_info[element].move_pattern & MV_UP &&
14310 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14311 (element_info[element].move_pattern & MV_DOWN &&
14312 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14313 return MP_NO_ACTION;
14315 // do not push elements already moving away faster than player
14316 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14317 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14318 return MP_NO_ACTION;
14320 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14322 if (player->push_delay_value == -1 || !player_was_pushing)
14323 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14325 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14327 if (player->push_delay_value == -1)
14328 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14330 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14332 if (!player->is_pushing)
14333 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14336 player->is_pushing = TRUE;
14337 player->is_active = TRUE;
14339 if (!(IN_LEV_FIELD(nextx, nexty) &&
14340 (IS_FREE(nextx, nexty) ||
14341 (IS_SB_ELEMENT(element) &&
14342 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14343 (IS_CUSTOM_ELEMENT(element) &&
14344 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14345 return MP_NO_ACTION;
14347 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14348 return MP_NO_ACTION;
14350 if (player->push_delay == -1) // new pushing; restart delay
14351 player->push_delay = 0;
14353 if (player->push_delay < player->push_delay_value &&
14354 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14355 element != EL_SPRING && element != EL_BALLOON)
14357 // make sure that there is no move delay before next try to push
14358 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14359 player->move_delay = 0;
14361 return MP_NO_ACTION;
14364 if (IS_CUSTOM_ELEMENT(element) &&
14365 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14367 if (!DigFieldByCE(nextx, nexty, element))
14368 return MP_NO_ACTION;
14371 if (IS_SB_ELEMENT(element))
14373 boolean sokoban_task_solved = FALSE;
14375 if (element == EL_SOKOBAN_FIELD_FULL)
14377 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14379 IncrementSokobanFieldsNeeded();
14380 IncrementSokobanObjectsNeeded();
14383 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14385 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14387 DecrementSokobanFieldsNeeded();
14388 DecrementSokobanObjectsNeeded();
14390 // sokoban object was pushed from empty field to sokoban field
14391 if (Back[x][y] == EL_EMPTY)
14392 sokoban_task_solved = TRUE;
14395 Tile[x][y] = EL_SOKOBAN_OBJECT;
14397 if (Back[x][y] == Back[nextx][nexty])
14398 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14399 else if (Back[x][y] != 0)
14400 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14403 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14406 if (sokoban_task_solved &&
14407 game.sokoban_fields_still_needed == 0 &&
14408 game.sokoban_objects_still_needed == 0 &&
14409 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14411 game.players_still_needed = 0;
14415 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14419 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14421 InitMovingField(x, y, move_direction);
14422 GfxAction[x][y] = ACTION_PUSHING;
14424 if (mode == DF_SNAP)
14425 ContinueMoving(x, y);
14427 MovPos[x][y] = (dx != 0 ? dx : dy);
14429 Pushed[x][y] = TRUE;
14430 Pushed[nextx][nexty] = TRUE;
14432 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14433 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14435 player->push_delay_value = -1; // get new value later
14437 // check for element change _after_ element has been pushed
14438 if (game.use_change_when_pushing_bug)
14440 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14441 player->index_bit, dig_side);
14442 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14443 player->index_bit, dig_side);
14446 else if (IS_SWITCHABLE(element))
14448 if (PLAYER_SWITCHING(player, x, y))
14450 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14451 player->index_bit, dig_side);
14456 player->is_switching = TRUE;
14457 player->switch_x = x;
14458 player->switch_y = y;
14460 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14462 if (element == EL_ROBOT_WHEEL)
14464 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14466 game.robot_wheel_x = x;
14467 game.robot_wheel_y = y;
14468 game.robot_wheel_active = TRUE;
14470 TEST_DrawLevelField(x, y);
14472 else if (element == EL_SP_TERMINAL)
14476 SCAN_PLAYFIELD(xx, yy)
14478 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14482 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14484 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14486 ResetGfxAnimation(xx, yy);
14487 TEST_DrawLevelField(xx, yy);
14491 else if (IS_BELT_SWITCH(element))
14493 ToggleBeltSwitch(x, y);
14495 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14496 element == EL_SWITCHGATE_SWITCH_DOWN ||
14497 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14498 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14500 ToggleSwitchgateSwitch(x, y);
14502 else if (element == EL_LIGHT_SWITCH ||
14503 element == EL_LIGHT_SWITCH_ACTIVE)
14505 ToggleLightSwitch(x, y);
14507 else if (element == EL_TIMEGATE_SWITCH ||
14508 element == EL_DC_TIMEGATE_SWITCH)
14510 ActivateTimegateSwitch(x, y);
14512 else if (element == EL_BALLOON_SWITCH_LEFT ||
14513 element == EL_BALLOON_SWITCH_RIGHT ||
14514 element == EL_BALLOON_SWITCH_UP ||
14515 element == EL_BALLOON_SWITCH_DOWN ||
14516 element == EL_BALLOON_SWITCH_NONE ||
14517 element == EL_BALLOON_SWITCH_ANY)
14519 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14520 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14521 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14522 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14523 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14526 else if (element == EL_LAMP)
14528 Tile[x][y] = EL_LAMP_ACTIVE;
14529 game.lights_still_needed--;
14531 ResetGfxAnimation(x, y);
14532 TEST_DrawLevelField(x, y);
14534 else if (element == EL_TIME_ORB_FULL)
14536 Tile[x][y] = EL_TIME_ORB_EMPTY;
14538 if (level.time > 0 || level.use_time_orb_bug)
14540 TimeLeft += level.time_orb_time;
14541 game.no_time_limit = FALSE;
14543 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14545 DisplayGameControlValues();
14548 ResetGfxAnimation(x, y);
14549 TEST_DrawLevelField(x, y);
14551 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14552 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14556 game.ball_active = !game.ball_active;
14558 SCAN_PLAYFIELD(xx, yy)
14560 int e = Tile[xx][yy];
14562 if (game.ball_active)
14564 if (e == EL_EMC_MAGIC_BALL)
14565 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14566 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14567 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14571 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14572 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14573 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14574 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14579 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14580 player->index_bit, dig_side);
14582 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14583 player->index_bit, dig_side);
14585 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14586 player->index_bit, dig_side);
14592 if (!PLAYER_SWITCHING(player, x, y))
14594 player->is_switching = TRUE;
14595 player->switch_x = x;
14596 player->switch_y = y;
14598 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14599 player->index_bit, dig_side);
14600 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14601 player->index_bit, dig_side);
14603 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14604 player->index_bit, dig_side);
14605 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14606 player->index_bit, dig_side);
14609 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14610 player->index_bit, dig_side);
14611 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14612 player->index_bit, dig_side);
14614 return MP_NO_ACTION;
14617 player->push_delay = -1;
14619 if (is_player) // function can also be called by EL_PENGUIN
14621 if (Tile[x][y] != element) // really digged/collected something
14623 player->is_collecting = !player->is_digging;
14624 player->is_active = TRUE;
14626 player->last_removed_element = element;
14633 static boolean DigFieldByCE(int x, int y, int digging_element)
14635 int element = Tile[x][y];
14637 if (!IS_FREE(x, y))
14639 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14640 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14643 // no element can dig solid indestructible elements
14644 if (IS_INDESTRUCTIBLE(element) &&
14645 !IS_DIGGABLE(element) &&
14646 !IS_COLLECTIBLE(element))
14649 if (AmoebaNr[x][y] &&
14650 (element == EL_AMOEBA_FULL ||
14651 element == EL_BD_AMOEBA ||
14652 element == EL_AMOEBA_GROWING))
14654 AmoebaCnt[AmoebaNr[x][y]]--;
14655 AmoebaCnt2[AmoebaNr[x][y]]--;
14658 if (IS_MOVING(x, y))
14659 RemoveMovingField(x, y);
14663 TEST_DrawLevelField(x, y);
14666 // if digged element was about to explode, prevent the explosion
14667 ExplodeField[x][y] = EX_TYPE_NONE;
14669 PlayLevelSoundAction(x, y, action);
14672 Store[x][y] = EL_EMPTY;
14674 // this makes it possible to leave the removed element again
14675 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14676 Store[x][y] = element;
14681 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14683 int jx = player->jx, jy = player->jy;
14684 int x = jx + dx, y = jy + dy;
14685 int snap_direction = (dx == -1 ? MV_LEFT :
14686 dx == +1 ? MV_RIGHT :
14688 dy == +1 ? MV_DOWN : MV_NONE);
14689 boolean can_continue_snapping = (level.continuous_snapping &&
14690 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14692 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14695 if (!player->active || !IN_LEV_FIELD(x, y))
14703 if (player->MovPos == 0)
14704 player->is_pushing = FALSE;
14706 player->is_snapping = FALSE;
14708 if (player->MovPos == 0)
14710 player->is_moving = FALSE;
14711 player->is_digging = FALSE;
14712 player->is_collecting = FALSE;
14718 // prevent snapping with already pressed snap key when not allowed
14719 if (player->is_snapping && !can_continue_snapping)
14722 player->MovDir = snap_direction;
14724 if (player->MovPos == 0)
14726 player->is_moving = FALSE;
14727 player->is_digging = FALSE;
14728 player->is_collecting = FALSE;
14731 player->is_dropping = FALSE;
14732 player->is_dropping_pressed = FALSE;
14733 player->drop_pressed_delay = 0;
14735 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14738 player->is_snapping = TRUE;
14739 player->is_active = TRUE;
14741 if (player->MovPos == 0)
14743 player->is_moving = FALSE;
14744 player->is_digging = FALSE;
14745 player->is_collecting = FALSE;
14748 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14749 TEST_DrawLevelField(player->last_jx, player->last_jy);
14751 TEST_DrawLevelField(x, y);
14756 static boolean DropElement(struct PlayerInfo *player)
14758 int old_element, new_element;
14759 int dropx = player->jx, dropy = player->jy;
14760 int drop_direction = player->MovDir;
14761 int drop_side = drop_direction;
14762 int drop_element = get_next_dropped_element(player);
14764 /* do not drop an element on top of another element; when holding drop key
14765 pressed without moving, dropped element must move away before the next
14766 element can be dropped (this is especially important if the next element
14767 is dynamite, which can be placed on background for historical reasons) */
14768 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14771 if (IS_THROWABLE(drop_element))
14773 dropx += GET_DX_FROM_DIR(drop_direction);
14774 dropy += GET_DY_FROM_DIR(drop_direction);
14776 if (!IN_LEV_FIELD(dropx, dropy))
14780 old_element = Tile[dropx][dropy]; // old element at dropping position
14781 new_element = drop_element; // default: no change when dropping
14783 // check if player is active, not moving and ready to drop
14784 if (!player->active || player->MovPos || player->drop_delay > 0)
14787 // check if player has anything that can be dropped
14788 if (new_element == EL_UNDEFINED)
14791 // only set if player has anything that can be dropped
14792 player->is_dropping_pressed = TRUE;
14794 // check if drop key was pressed long enough for EM style dynamite
14795 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14798 // check if anything can be dropped at the current position
14799 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14802 // collected custom elements can only be dropped on empty fields
14803 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14806 if (old_element != EL_EMPTY)
14807 Back[dropx][dropy] = old_element; // store old element on this field
14809 ResetGfxAnimation(dropx, dropy);
14810 ResetRandomAnimationValue(dropx, dropy);
14812 if (player->inventory_size > 0 ||
14813 player->inventory_infinite_element != EL_UNDEFINED)
14815 if (player->inventory_size > 0)
14817 player->inventory_size--;
14819 DrawGameDoorValues();
14821 if (new_element == EL_DYNAMITE)
14822 new_element = EL_DYNAMITE_ACTIVE;
14823 else if (new_element == EL_EM_DYNAMITE)
14824 new_element = EL_EM_DYNAMITE_ACTIVE;
14825 else if (new_element == EL_SP_DISK_RED)
14826 new_element = EL_SP_DISK_RED_ACTIVE;
14829 Tile[dropx][dropy] = new_element;
14831 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14832 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14833 el2img(Tile[dropx][dropy]), 0);
14835 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14837 // needed if previous element just changed to "empty" in the last frame
14838 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14840 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14841 player->index_bit, drop_side);
14842 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14844 player->index_bit, drop_side);
14846 TestIfElementTouchesCustomElement(dropx, dropy);
14848 else // player is dropping a dyna bomb
14850 player->dynabombs_left--;
14852 Tile[dropx][dropy] = new_element;
14854 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14855 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14856 el2img(Tile[dropx][dropy]), 0);
14858 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14861 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14862 InitField_WithBug1(dropx, dropy, FALSE);
14864 new_element = Tile[dropx][dropy]; // element might have changed
14866 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14867 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14869 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14870 MovDir[dropx][dropy] = drop_direction;
14872 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14874 // do not cause impact style collision by dropping elements that can fall
14875 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14878 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14879 player->is_dropping = TRUE;
14881 player->drop_pressed_delay = 0;
14882 player->is_dropping_pressed = FALSE;
14884 player->drop_x = dropx;
14885 player->drop_y = dropy;
14890 // ----------------------------------------------------------------------------
14891 // game sound playing functions
14892 // ----------------------------------------------------------------------------
14894 static int *loop_sound_frame = NULL;
14895 static int *loop_sound_volume = NULL;
14897 void InitPlayLevelSound(void)
14899 int num_sounds = getSoundListSize();
14901 checked_free(loop_sound_frame);
14902 checked_free(loop_sound_volume);
14904 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14905 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14908 static void PlayLevelSound(int x, int y, int nr)
14910 int sx = SCREENX(x), sy = SCREENY(y);
14911 int volume, stereo_position;
14912 int max_distance = 8;
14913 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14915 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14916 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14919 if (!IN_LEV_FIELD(x, y) ||
14920 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14921 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14924 volume = SOUND_MAX_VOLUME;
14926 if (!IN_SCR_FIELD(sx, sy))
14928 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14929 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14931 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14934 stereo_position = (SOUND_MAX_LEFT +
14935 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14936 (SCR_FIELDX + 2 * max_distance));
14938 if (IS_LOOP_SOUND(nr))
14940 /* This assures that quieter loop sounds do not overwrite louder ones,
14941 while restarting sound volume comparison with each new game frame. */
14943 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14946 loop_sound_volume[nr] = volume;
14947 loop_sound_frame[nr] = FrameCounter;
14950 PlaySoundExt(nr, volume, stereo_position, type);
14953 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14955 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14956 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14957 y < LEVELY(BY1) ? LEVELY(BY1) :
14958 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14962 static void PlayLevelSoundAction(int x, int y, int action)
14964 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14967 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14969 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14971 if (sound_effect != SND_UNDEFINED)
14972 PlayLevelSound(x, y, sound_effect);
14975 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14978 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14980 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14981 PlayLevelSound(x, y, sound_effect);
14984 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14986 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14988 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14989 PlayLevelSound(x, y, sound_effect);
14992 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14994 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14996 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14997 StopSound(sound_effect);
15000 static int getLevelMusicNr(void)
15002 if (levelset.music[level_nr] != MUS_UNDEFINED)
15003 return levelset.music[level_nr]; // from config file
15005 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15008 static void FadeLevelSounds(void)
15013 static void FadeLevelMusic(void)
15015 int music_nr = getLevelMusicNr();
15016 char *curr_music = getCurrentlyPlayingMusicFilename();
15017 char *next_music = getMusicInfoEntryFilename(music_nr);
15019 if (!strEqual(curr_music, next_music))
15023 void FadeLevelSoundsAndMusic(void)
15029 static void PlayLevelMusic(void)
15031 int music_nr = getLevelMusicNr();
15032 char *curr_music = getCurrentlyPlayingMusicFilename();
15033 char *next_music = getMusicInfoEntryFilename(music_nr);
15035 if (!strEqual(curr_music, next_music))
15036 PlayMusicLoop(music_nr);
15039 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15041 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15043 int x = xx - offset;
15044 int y = yy - offset;
15049 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15053 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15057 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15061 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15065 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15069 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15073 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15076 case SOUND_android_clone:
15077 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15080 case SOUND_android_move:
15081 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15085 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15089 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15093 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15096 case SOUND_eater_eat:
15097 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15101 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15104 case SOUND_collect:
15105 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15108 case SOUND_diamond:
15109 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15113 // !!! CHECK THIS !!!
15115 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15117 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15121 case SOUND_wonderfall:
15122 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15126 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15130 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15134 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15138 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15142 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15146 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15150 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15154 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15157 case SOUND_exit_open:
15158 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15161 case SOUND_exit_leave:
15162 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15165 case SOUND_dynamite:
15166 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15170 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15174 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15178 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15182 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15186 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15190 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15194 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15199 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15201 int element = map_element_SP_to_RND(element_sp);
15202 int action = map_action_SP_to_RND(action_sp);
15203 int offset = (setup.sp_show_border_elements ? 0 : 1);
15204 int x = xx - offset;
15205 int y = yy - offset;
15207 PlayLevelSoundElementAction(x, y, element, action);
15210 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15212 int element = map_element_MM_to_RND(element_mm);
15213 int action = map_action_MM_to_RND(action_mm);
15215 int x = xx - offset;
15216 int y = yy - offset;
15218 if (!IS_MM_ELEMENT(element))
15219 element = EL_MM_DEFAULT;
15221 PlayLevelSoundElementAction(x, y, element, action);
15224 void PlaySound_MM(int sound_mm)
15226 int sound = map_sound_MM_to_RND(sound_mm);
15228 if (sound == SND_UNDEFINED)
15234 void PlaySoundLoop_MM(int sound_mm)
15236 int sound = map_sound_MM_to_RND(sound_mm);
15238 if (sound == SND_UNDEFINED)
15241 PlaySoundLoop(sound);
15244 void StopSound_MM(int sound_mm)
15246 int sound = map_sound_MM_to_RND(sound_mm);
15248 if (sound == SND_UNDEFINED)
15254 void RaiseScore(int value)
15256 game.score += value;
15258 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15260 DisplayGameControlValues();
15263 void RaiseScoreElement(int element)
15268 case EL_BD_DIAMOND:
15269 case EL_EMERALD_YELLOW:
15270 case EL_EMERALD_RED:
15271 case EL_EMERALD_PURPLE:
15272 case EL_SP_INFOTRON:
15273 RaiseScore(level.score[SC_EMERALD]);
15276 RaiseScore(level.score[SC_DIAMOND]);
15279 RaiseScore(level.score[SC_CRYSTAL]);
15282 RaiseScore(level.score[SC_PEARL]);
15285 case EL_BD_BUTTERFLY:
15286 case EL_SP_ELECTRON:
15287 RaiseScore(level.score[SC_BUG]);
15290 case EL_BD_FIREFLY:
15291 case EL_SP_SNIKSNAK:
15292 RaiseScore(level.score[SC_SPACESHIP]);
15295 case EL_DARK_YAMYAM:
15296 RaiseScore(level.score[SC_YAMYAM]);
15299 RaiseScore(level.score[SC_ROBOT]);
15302 RaiseScore(level.score[SC_PACMAN]);
15305 RaiseScore(level.score[SC_NUT]);
15308 case EL_EM_DYNAMITE:
15309 case EL_SP_DISK_RED:
15310 case EL_DYNABOMB_INCREASE_NUMBER:
15311 case EL_DYNABOMB_INCREASE_SIZE:
15312 case EL_DYNABOMB_INCREASE_POWER:
15313 RaiseScore(level.score[SC_DYNAMITE]);
15315 case EL_SHIELD_NORMAL:
15316 case EL_SHIELD_DEADLY:
15317 RaiseScore(level.score[SC_SHIELD]);
15319 case EL_EXTRA_TIME:
15320 RaiseScore(level.extra_time_score);
15334 case EL_DC_KEY_WHITE:
15335 RaiseScore(level.score[SC_KEY]);
15338 RaiseScore(element_info[element].collect_score);
15343 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15345 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15347 // closing door required in case of envelope style request dialogs
15350 // prevent short reactivation of overlay buttons while closing door
15351 SetOverlayActive(FALSE);
15353 CloseDoor(DOOR_CLOSE_1);
15356 if (network.enabled)
15357 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15361 FadeSkipNextFadeIn();
15363 SetGameStatus(GAME_MODE_MAIN);
15368 else // continue playing the game
15370 if (tape.playing && tape.deactivate_display)
15371 TapeDeactivateDisplayOff(TRUE);
15373 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15375 if (tape.playing && tape.deactivate_display)
15376 TapeDeactivateDisplayOn();
15380 void RequestQuitGame(boolean ask_if_really_quit)
15382 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15383 boolean skip_request = game.all_players_gone || quick_quit;
15385 RequestQuitGameExt(skip_request, quick_quit,
15386 "Do you really want to quit the game?");
15389 void RequestRestartGame(char *message)
15391 game.restart_game_message = NULL;
15393 boolean has_started_game = hasStartedNetworkGame();
15394 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15396 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15398 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15402 // needed in case of envelope request to close game panel
15403 CloseDoor(DOOR_CLOSE_1);
15405 SetGameStatus(GAME_MODE_MAIN);
15411 void CheckGameOver(void)
15413 static boolean last_game_over = FALSE;
15414 static int game_over_delay = 0;
15415 int game_over_delay_value = 50;
15416 boolean game_over = checkGameFailed();
15418 // do not handle game over if request dialog is already active
15419 if (game.request_active)
15422 // do not ask to play again if game was never actually played
15423 if (!game.GamePlayed)
15428 last_game_over = FALSE;
15429 game_over_delay = game_over_delay_value;
15434 if (game_over_delay > 0)
15441 if (last_game_over != game_over)
15442 game.restart_game_message = (hasStartedNetworkGame() ?
15443 "Game over! Play it again?" :
15446 last_game_over = game_over;
15449 boolean checkGameSolved(void)
15451 // set for all game engines if level was solved
15452 return game.LevelSolved_GameEnd;
15455 boolean checkGameFailed(void)
15457 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15458 return (game_em.game_over && !game_em.level_solved);
15459 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15460 return (game_sp.game_over && !game_sp.level_solved);
15461 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15462 return (game_mm.game_over && !game_mm.level_solved);
15463 else // GAME_ENGINE_TYPE_RND
15464 return (game.GameOver && !game.LevelSolved);
15467 boolean checkGameEnded(void)
15469 return (checkGameSolved() || checkGameFailed());
15473 // ----------------------------------------------------------------------------
15474 // random generator functions
15475 // ----------------------------------------------------------------------------
15477 unsigned int InitEngineRandom_RND(int seed)
15479 game.num_random_calls = 0;
15481 return InitEngineRandom(seed);
15484 unsigned int RND(int max)
15488 game.num_random_calls++;
15490 return GetEngineRandom(max);
15497 // ----------------------------------------------------------------------------
15498 // game engine snapshot handling functions
15499 // ----------------------------------------------------------------------------
15501 struct EngineSnapshotInfo
15503 // runtime values for custom element collect score
15504 int collect_score[NUM_CUSTOM_ELEMENTS];
15506 // runtime values for group element choice position
15507 int choice_pos[NUM_GROUP_ELEMENTS];
15509 // runtime values for belt position animations
15510 int belt_graphic[4][NUM_BELT_PARTS];
15511 int belt_anim_mode[4][NUM_BELT_PARTS];
15514 static struct EngineSnapshotInfo engine_snapshot_rnd;
15515 static char *snapshot_level_identifier = NULL;
15516 static int snapshot_level_nr = -1;
15518 static void SaveEngineSnapshotValues_RND(void)
15520 static int belt_base_active_element[4] =
15522 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15523 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15524 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15525 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15529 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15531 int element = EL_CUSTOM_START + i;
15533 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15536 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15538 int element = EL_GROUP_START + i;
15540 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15543 for (i = 0; i < 4; i++)
15545 for (j = 0; j < NUM_BELT_PARTS; j++)
15547 int element = belt_base_active_element[i] + j;
15548 int graphic = el2img(element);
15549 int anim_mode = graphic_info[graphic].anim_mode;
15551 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15552 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15557 static void LoadEngineSnapshotValues_RND(void)
15559 unsigned int num_random_calls = game.num_random_calls;
15562 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15564 int element = EL_CUSTOM_START + i;
15566 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15569 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15571 int element = EL_GROUP_START + i;
15573 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15576 for (i = 0; i < 4; i++)
15578 for (j = 0; j < NUM_BELT_PARTS; j++)
15580 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15581 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15583 graphic_info[graphic].anim_mode = anim_mode;
15587 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15589 InitRND(tape.random_seed);
15590 for (i = 0; i < num_random_calls; i++)
15594 if (game.num_random_calls != num_random_calls)
15596 Error("number of random calls out of sync");
15597 Error("number of random calls should be %d", num_random_calls);
15598 Error("number of random calls is %d", game.num_random_calls);
15600 Fail("this should not happen -- please debug");
15604 void FreeEngineSnapshotSingle(void)
15606 FreeSnapshotSingle();
15608 setString(&snapshot_level_identifier, NULL);
15609 snapshot_level_nr = -1;
15612 void FreeEngineSnapshotList(void)
15614 FreeSnapshotList();
15617 static ListNode *SaveEngineSnapshotBuffers(void)
15619 ListNode *buffers = NULL;
15621 // copy some special values to a structure better suited for the snapshot
15623 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15624 SaveEngineSnapshotValues_RND();
15625 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15626 SaveEngineSnapshotValues_EM();
15627 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15628 SaveEngineSnapshotValues_SP(&buffers);
15629 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15630 SaveEngineSnapshotValues_MM(&buffers);
15632 // save values stored in special snapshot structure
15634 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15635 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15636 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15637 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15638 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15639 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15640 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15641 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15643 // save further RND engine values
15645 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15646 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15647 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15649 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15650 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15651 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15652 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15653 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15655 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15656 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15657 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15659 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15661 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15662 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15664 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15665 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15666 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15667 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15668 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15669 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15670 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15671 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15672 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15673 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15674 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15675 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15676 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15677 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15678 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15679 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15680 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15681 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15683 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15684 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15686 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15687 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15688 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15690 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15691 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15693 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15694 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15695 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15696 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15697 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15699 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15700 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15703 ListNode *node = engine_snapshot_list_rnd;
15706 while (node != NULL)
15708 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15713 Debug("game:playing:SaveEngineSnapshotBuffers",
15714 "size of engine snapshot: %d bytes", num_bytes);
15720 void SaveEngineSnapshotSingle(void)
15722 ListNode *buffers = SaveEngineSnapshotBuffers();
15724 // finally save all snapshot buffers to single snapshot
15725 SaveSnapshotSingle(buffers);
15727 // save level identification information
15728 setString(&snapshot_level_identifier, leveldir_current->identifier);
15729 snapshot_level_nr = level_nr;
15732 boolean CheckSaveEngineSnapshotToList(void)
15734 boolean save_snapshot =
15735 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15736 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15737 game.snapshot.changed_action) ||
15738 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15739 game.snapshot.collected_item));
15741 game.snapshot.changed_action = FALSE;
15742 game.snapshot.collected_item = FALSE;
15743 game.snapshot.save_snapshot = save_snapshot;
15745 return save_snapshot;
15748 void SaveEngineSnapshotToList(void)
15750 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15754 ListNode *buffers = SaveEngineSnapshotBuffers();
15756 // finally save all snapshot buffers to snapshot list
15757 SaveSnapshotToList(buffers);
15760 void SaveEngineSnapshotToListInitial(void)
15762 FreeEngineSnapshotList();
15764 SaveEngineSnapshotToList();
15767 static void LoadEngineSnapshotValues(void)
15769 // restore special values from snapshot structure
15771 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15772 LoadEngineSnapshotValues_RND();
15773 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15774 LoadEngineSnapshotValues_EM();
15775 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15776 LoadEngineSnapshotValues_SP();
15777 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15778 LoadEngineSnapshotValues_MM();
15781 void LoadEngineSnapshotSingle(void)
15783 LoadSnapshotSingle();
15785 LoadEngineSnapshotValues();
15788 static void LoadEngineSnapshot_Undo(int steps)
15790 LoadSnapshotFromList_Older(steps);
15792 LoadEngineSnapshotValues();
15795 static void LoadEngineSnapshot_Redo(int steps)
15797 LoadSnapshotFromList_Newer(steps);
15799 LoadEngineSnapshotValues();
15802 boolean CheckEngineSnapshotSingle(void)
15804 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15805 snapshot_level_nr == level_nr);
15808 boolean CheckEngineSnapshotList(void)
15810 return CheckSnapshotList();
15814 // ---------- new game button stuff -------------------------------------------
15821 boolean *setup_value;
15822 boolean allowed_on_tape;
15823 boolean is_touch_button;
15825 } gamebutton_info[NUM_GAME_BUTTONS] =
15828 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15829 GAME_CTRL_ID_STOP, NULL,
15830 TRUE, FALSE, "stop game"
15833 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15834 GAME_CTRL_ID_PAUSE, NULL,
15835 TRUE, FALSE, "pause game"
15838 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15839 GAME_CTRL_ID_PLAY, NULL,
15840 TRUE, FALSE, "play game"
15843 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15844 GAME_CTRL_ID_UNDO, NULL,
15845 TRUE, FALSE, "undo step"
15848 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15849 GAME_CTRL_ID_REDO, NULL,
15850 TRUE, FALSE, "redo step"
15853 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15854 GAME_CTRL_ID_SAVE, NULL,
15855 TRUE, FALSE, "save game"
15858 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15859 GAME_CTRL_ID_PAUSE2, NULL,
15860 TRUE, FALSE, "pause game"
15863 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15864 GAME_CTRL_ID_LOAD, NULL,
15865 TRUE, FALSE, "load game"
15868 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15869 GAME_CTRL_ID_PANEL_STOP, NULL,
15870 FALSE, FALSE, "stop game"
15873 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15874 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15875 FALSE, FALSE, "pause game"
15878 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15879 GAME_CTRL_ID_PANEL_PLAY, NULL,
15880 FALSE, FALSE, "play game"
15883 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15884 GAME_CTRL_ID_TOUCH_STOP, NULL,
15885 FALSE, TRUE, "stop game"
15888 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15889 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15890 FALSE, TRUE, "pause game"
15893 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15894 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15895 TRUE, FALSE, "background music on/off"
15898 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15899 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15900 TRUE, FALSE, "sound loops on/off"
15903 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15904 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15905 TRUE, FALSE, "normal sounds on/off"
15908 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15909 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15910 FALSE, FALSE, "background music on/off"
15913 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15914 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15915 FALSE, FALSE, "sound loops on/off"
15918 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15919 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15920 FALSE, FALSE, "normal sounds on/off"
15924 void CreateGameButtons(void)
15928 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15930 int graphic = gamebutton_info[i].graphic;
15931 struct GraphicInfo *gfx = &graphic_info[graphic];
15932 struct XY *pos = gamebutton_info[i].pos;
15933 struct GadgetInfo *gi;
15936 unsigned int event_mask;
15937 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15938 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15939 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15940 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15941 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15942 int gd_x = gfx->src_x;
15943 int gd_y = gfx->src_y;
15944 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15945 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15946 int gd_xa = gfx->src_x + gfx->active_xoffset;
15947 int gd_ya = gfx->src_y + gfx->active_yoffset;
15948 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15949 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15950 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15951 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15954 if (gfx->bitmap == NULL)
15956 game_gadget[id] = NULL;
15961 if (id == GAME_CTRL_ID_STOP ||
15962 id == GAME_CTRL_ID_PANEL_STOP ||
15963 id == GAME_CTRL_ID_TOUCH_STOP ||
15964 id == GAME_CTRL_ID_PLAY ||
15965 id == GAME_CTRL_ID_PANEL_PLAY ||
15966 id == GAME_CTRL_ID_SAVE ||
15967 id == GAME_CTRL_ID_LOAD)
15969 button_type = GD_TYPE_NORMAL_BUTTON;
15971 event_mask = GD_EVENT_RELEASED;
15973 else if (id == GAME_CTRL_ID_UNDO ||
15974 id == GAME_CTRL_ID_REDO)
15976 button_type = GD_TYPE_NORMAL_BUTTON;
15978 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15982 button_type = GD_TYPE_CHECK_BUTTON;
15983 checked = (gamebutton_info[i].setup_value != NULL ?
15984 *gamebutton_info[i].setup_value : FALSE);
15985 event_mask = GD_EVENT_PRESSED;
15988 gi = CreateGadget(GDI_CUSTOM_ID, id,
15989 GDI_IMAGE_ID, graphic,
15990 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15993 GDI_WIDTH, gfx->width,
15994 GDI_HEIGHT, gfx->height,
15995 GDI_TYPE, button_type,
15996 GDI_STATE, GD_BUTTON_UNPRESSED,
15997 GDI_CHECKED, checked,
15998 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15999 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16000 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16001 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16002 GDI_DIRECT_DRAW, FALSE,
16003 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16004 GDI_EVENT_MASK, event_mask,
16005 GDI_CALLBACK_ACTION, HandleGameButtons,
16009 Fail("cannot create gadget");
16011 game_gadget[id] = gi;
16015 void FreeGameButtons(void)
16019 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16020 FreeGadget(game_gadget[i]);
16023 static void UnmapGameButtonsAtSamePosition(int id)
16027 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16029 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16030 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16031 UnmapGadget(game_gadget[i]);
16034 static void UnmapGameButtonsAtSamePosition_All(void)
16036 if (setup.show_snapshot_buttons)
16038 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16039 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16040 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16044 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16045 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16046 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16048 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16049 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16050 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16054 static void MapGameButtonsAtSamePosition(int id)
16058 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16060 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16061 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16062 MapGadget(game_gadget[i]);
16064 UnmapGameButtonsAtSamePosition_All();
16067 void MapUndoRedoButtons(void)
16069 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16070 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16072 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16073 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16076 void UnmapUndoRedoButtons(void)
16078 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16079 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16081 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16082 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16085 void ModifyPauseButtons(void)
16089 GAME_CTRL_ID_PAUSE,
16090 GAME_CTRL_ID_PAUSE2,
16091 GAME_CTRL_ID_PANEL_PAUSE,
16092 GAME_CTRL_ID_TOUCH_PAUSE,
16097 for (i = 0; ids[i] > -1; i++)
16098 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16101 static void MapGameButtonsExt(boolean on_tape)
16105 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16106 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16107 i != GAME_CTRL_ID_UNDO &&
16108 i != GAME_CTRL_ID_REDO)
16109 MapGadget(game_gadget[i]);
16111 UnmapGameButtonsAtSamePosition_All();
16113 RedrawGameButtons();
16116 static void UnmapGameButtonsExt(boolean on_tape)
16120 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16121 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16122 UnmapGadget(game_gadget[i]);
16125 static void RedrawGameButtonsExt(boolean on_tape)
16129 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16130 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16131 RedrawGadget(game_gadget[i]);
16134 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16139 gi->checked = state;
16142 static void RedrawSoundButtonGadget(int id)
16144 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16145 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16146 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16147 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16148 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16149 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16152 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16153 RedrawGadget(game_gadget[id2]);
16156 void MapGameButtons(void)
16158 MapGameButtonsExt(FALSE);
16161 void UnmapGameButtons(void)
16163 UnmapGameButtonsExt(FALSE);
16166 void RedrawGameButtons(void)
16168 RedrawGameButtonsExt(FALSE);
16171 void MapGameButtonsOnTape(void)
16173 MapGameButtonsExt(TRUE);
16176 void UnmapGameButtonsOnTape(void)
16178 UnmapGameButtonsExt(TRUE);
16181 void RedrawGameButtonsOnTape(void)
16183 RedrawGameButtonsExt(TRUE);
16186 static void GameUndoRedoExt(void)
16188 ClearPlayerAction();
16190 tape.pausing = TRUE;
16193 UpdateAndDisplayGameControlValues();
16195 DrawCompleteVideoDisplay();
16196 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16197 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16198 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16203 static void GameUndo(int steps)
16205 if (!CheckEngineSnapshotList())
16208 LoadEngineSnapshot_Undo(steps);
16213 static void GameRedo(int steps)
16215 if (!CheckEngineSnapshotList())
16218 LoadEngineSnapshot_Redo(steps);
16223 static void HandleGameButtonsExt(int id, int button)
16225 static boolean game_undo_executed = FALSE;
16226 int steps = BUTTON_STEPSIZE(button);
16227 boolean handle_game_buttons =
16228 (game_status == GAME_MODE_PLAYING ||
16229 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16231 if (!handle_game_buttons)
16236 case GAME_CTRL_ID_STOP:
16237 case GAME_CTRL_ID_PANEL_STOP:
16238 case GAME_CTRL_ID_TOUCH_STOP:
16239 if (game_status == GAME_MODE_MAIN)
16245 RequestQuitGame(TRUE);
16249 case GAME_CTRL_ID_PAUSE:
16250 case GAME_CTRL_ID_PAUSE2:
16251 case GAME_CTRL_ID_PANEL_PAUSE:
16252 case GAME_CTRL_ID_TOUCH_PAUSE:
16253 if (network.enabled && game_status == GAME_MODE_PLAYING)
16256 SendToServer_ContinuePlaying();
16258 SendToServer_PausePlaying();
16261 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16263 game_undo_executed = FALSE;
16267 case GAME_CTRL_ID_PLAY:
16268 case GAME_CTRL_ID_PANEL_PLAY:
16269 if (game_status == GAME_MODE_MAIN)
16271 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16273 else if (tape.pausing)
16275 if (network.enabled)
16276 SendToServer_ContinuePlaying();
16278 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16282 case GAME_CTRL_ID_UNDO:
16283 // Important: When using "save snapshot when collecting an item" mode,
16284 // load last (current) snapshot for first "undo" after pressing "pause"
16285 // (else the last-but-one snapshot would be loaded, because the snapshot
16286 // pointer already points to the last snapshot when pressing "pause",
16287 // which is fine for "every step/move" mode, but not for "every collect")
16288 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16289 !game_undo_executed)
16292 game_undo_executed = TRUE;
16297 case GAME_CTRL_ID_REDO:
16301 case GAME_CTRL_ID_SAVE:
16305 case GAME_CTRL_ID_LOAD:
16309 case SOUND_CTRL_ID_MUSIC:
16310 case SOUND_CTRL_ID_PANEL_MUSIC:
16311 if (setup.sound_music)
16313 setup.sound_music = FALSE;
16317 else if (audio.music_available)
16319 setup.sound = setup.sound_music = TRUE;
16321 SetAudioMode(setup.sound);
16323 if (game_status == GAME_MODE_PLAYING)
16327 RedrawSoundButtonGadget(id);
16331 case SOUND_CTRL_ID_LOOPS:
16332 case SOUND_CTRL_ID_PANEL_LOOPS:
16333 if (setup.sound_loops)
16334 setup.sound_loops = FALSE;
16335 else if (audio.loops_available)
16337 setup.sound = setup.sound_loops = TRUE;
16339 SetAudioMode(setup.sound);
16342 RedrawSoundButtonGadget(id);
16346 case SOUND_CTRL_ID_SIMPLE:
16347 case SOUND_CTRL_ID_PANEL_SIMPLE:
16348 if (setup.sound_simple)
16349 setup.sound_simple = FALSE;
16350 else if (audio.sound_available)
16352 setup.sound = setup.sound_simple = TRUE;
16354 SetAudioMode(setup.sound);
16357 RedrawSoundButtonGadget(id);
16366 static void HandleGameButtons(struct GadgetInfo *gi)
16368 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16371 void HandleSoundButtonKeys(Key key)
16373 if (key == setup.shortcut.sound_simple)
16374 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16375 else if (key == setup.shortcut.sound_loops)
16376 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16377 else if (key == setup.shortcut.sound_music)
16378 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);