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();
2872 void UpdateGameDoorValues(void)
2874 UpdateGameControlValues();
2877 void DrawGameDoorValues(void)
2879 DisplayGameControlValues();
2883 // ============================================================================
2885 // ----------------------------------------------------------------------------
2886 // initialize game engine due to level / tape version number
2887 // ============================================================================
2889 static void InitGameEngine(void)
2891 int i, j, k, l, x, y;
2893 // set game engine from tape file when re-playing, else from level file
2894 game.engine_version = (tape.playing ? tape.engine_version :
2895 level.game_version);
2897 // set single or multi-player game mode (needed for re-playing tapes)
2898 game.team_mode = setup.team_mode;
2902 int num_players = 0;
2904 for (i = 0; i < MAX_PLAYERS; i++)
2905 if (tape.player_participates[i])
2908 // multi-player tapes contain input data for more than one player
2909 game.team_mode = (num_players > 1);
2913 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2914 level.game_version);
2915 Debug("game:init:level", " tape.file_version == %06d",
2917 Debug("game:init:level", " tape.game_version == %06d",
2919 Debug("game:init:level", " tape.engine_version == %06d",
2920 tape.engine_version);
2921 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2922 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2925 // --------------------------------------------------------------------------
2926 // set flags for bugs and changes according to active game engine version
2927 // --------------------------------------------------------------------------
2931 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2933 Bug was introduced in version:
2936 Bug was fixed in version:
2940 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2941 but the property "can fall" was missing, which caused some levels to be
2942 unsolvable. This was fixed in version 4.2.0.0.
2944 Affected levels/tapes:
2945 An example for a tape that was fixed by this bugfix is tape 029 from the
2946 level set "rnd_sam_bateman".
2947 The wrong behaviour will still be used for all levels or tapes that were
2948 created/recorded with it. An example for this is tape 023 from the level
2949 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2952 boolean use_amoeba_dropping_cannot_fall_bug =
2953 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2954 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2956 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2957 tape.game_version < VERSION_IDENT(4,2,0,0)));
2960 Summary of bugfix/change:
2961 Fixed move speed of elements entering or leaving magic wall.
2963 Fixed/changed in version:
2967 Before 2.0.1, move speed of elements entering or leaving magic wall was
2968 twice as fast as it is now.
2969 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2971 Affected levels/tapes:
2972 The first condition is generally needed for all levels/tapes before version
2973 2.0.1, which might use the old behaviour before it was changed; known tapes
2974 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2975 The second condition is an exception from the above case and is needed for
2976 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2977 above, but before it was known that this change would break tapes like the
2978 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2979 although the engine version while recording maybe was before 2.0.1. There
2980 are a lot of tapes that are affected by this exception, like tape 006 from
2981 the level set "rnd_conor_mancone".
2984 boolean use_old_move_stepsize_for_magic_wall =
2985 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2987 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2988 tape.game_version < VERSION_IDENT(4,2,0,0)));
2991 Summary of bugfix/change:
2992 Fixed handling for custom elements that change when pushed by the player.
2994 Fixed/changed in version:
2998 Before 3.1.0, custom elements that "change when pushing" changed directly
2999 after the player started pushing them (until then handled in "DigField()").
3000 Since 3.1.0, these custom elements are not changed until the "pushing"
3001 move of the element is finished (now handled in "ContinueMoving()").
3003 Affected levels/tapes:
3004 The first condition is generally needed for all levels/tapes before version
3005 3.1.0, which might use the old behaviour before it was changed; known tapes
3006 that are affected are some tapes from the level set "Walpurgis Gardens" by
3008 The second condition is an exception from the above case and is needed for
3009 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3010 above (including some development versions of 3.1.0), but before it was
3011 known that this change would break tapes like the above and was fixed in
3012 3.1.1, so that the changed behaviour was active although the engine version
3013 while recording maybe was before 3.1.0. There is at least one tape that is
3014 affected by this exception, which is the tape for the one-level set "Bug
3015 Machine" by Juergen Bonhagen.
3018 game.use_change_when_pushing_bug =
3019 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3021 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3022 tape.game_version < VERSION_IDENT(3,1,1,0)));
3025 Summary of bugfix/change:
3026 Fixed handling for blocking the field the player leaves when moving.
3028 Fixed/changed in version:
3032 Before 3.1.1, when "block last field when moving" was enabled, the field
3033 the player is leaving when moving was blocked for the time of the move,
3034 and was directly unblocked afterwards. This resulted in the last field
3035 being blocked for exactly one less than the number of frames of one player
3036 move. Additionally, even when blocking was disabled, the last field was
3037 blocked for exactly one frame.
3038 Since 3.1.1, due to changes in player movement handling, the last field
3039 is not blocked at all when blocking is disabled. When blocking is enabled,
3040 the last field is blocked for exactly the number of frames of one player
3041 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3042 last field is blocked for exactly one more than the number of frames of
3045 Affected levels/tapes:
3046 (!!! yet to be determined -- probably many !!!)
3049 game.use_block_last_field_bug =
3050 (game.engine_version < VERSION_IDENT(3,1,1,0));
3052 /* various special flags and settings for native Emerald Mine game engine */
3054 game_em.use_single_button =
3055 (game.engine_version > VERSION_IDENT(4,0,0,2));
3057 game_em.use_snap_key_bug =
3058 (game.engine_version < VERSION_IDENT(4,0,1,0));
3060 game_em.use_random_bug =
3061 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3063 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3065 game_em.use_old_explosions = use_old_em_engine;
3066 game_em.use_old_android = use_old_em_engine;
3067 game_em.use_old_push_elements = use_old_em_engine;
3068 game_em.use_old_push_into_acid = use_old_em_engine;
3070 game_em.use_wrap_around = !use_old_em_engine;
3072 // --------------------------------------------------------------------------
3074 // set maximal allowed number of custom element changes per game frame
3075 game.max_num_changes_per_frame = 1;
3077 // default scan direction: scan playfield from top/left to bottom/right
3078 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3080 // dynamically adjust element properties according to game engine version
3081 InitElementPropertiesEngine(game.engine_version);
3083 // ---------- initialize special element properties -------------------------
3085 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3086 if (use_amoeba_dropping_cannot_fall_bug)
3087 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3089 // ---------- initialize player's initial move delay ------------------------
3091 // dynamically adjust player properties according to level information
3092 for (i = 0; i < MAX_PLAYERS; i++)
3093 game.initial_move_delay_value[i] =
3094 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3096 // dynamically adjust player properties according to game engine version
3097 for (i = 0; i < MAX_PLAYERS; i++)
3098 game.initial_move_delay[i] =
3099 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3100 game.initial_move_delay_value[i] : 0);
3102 // ---------- initialize player's initial push delay ------------------------
3104 // dynamically adjust player properties according to game engine version
3105 game.initial_push_delay_value =
3106 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3108 // ---------- initialize changing elements ----------------------------------
3110 // initialize changing elements information
3111 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3113 struct ElementInfo *ei = &element_info[i];
3115 // this pointer might have been changed in the level editor
3116 ei->change = &ei->change_page[0];
3118 if (!IS_CUSTOM_ELEMENT(i))
3120 ei->change->target_element = EL_EMPTY_SPACE;
3121 ei->change->delay_fixed = 0;
3122 ei->change->delay_random = 0;
3123 ei->change->delay_frames = 1;
3126 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3128 ei->has_change_event[j] = FALSE;
3130 ei->event_page_nr[j] = 0;
3131 ei->event_page[j] = &ei->change_page[0];
3135 // add changing elements from pre-defined list
3136 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3138 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3139 struct ElementInfo *ei = &element_info[ch_delay->element];
3141 ei->change->target_element = ch_delay->target_element;
3142 ei->change->delay_fixed = ch_delay->change_delay;
3144 ei->change->pre_change_function = ch_delay->pre_change_function;
3145 ei->change->change_function = ch_delay->change_function;
3146 ei->change->post_change_function = ch_delay->post_change_function;
3148 ei->change->can_change = TRUE;
3149 ei->change->can_change_or_has_action = TRUE;
3151 ei->has_change_event[CE_DELAY] = TRUE;
3153 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3154 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3157 // ---------- initialize internal run-time variables ------------------------
3159 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3161 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3163 for (j = 0; j < ei->num_change_pages; j++)
3165 ei->change_page[j].can_change_or_has_action =
3166 (ei->change_page[j].can_change |
3167 ei->change_page[j].has_action);
3171 // add change events from custom element configuration
3172 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3174 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3176 for (j = 0; j < ei->num_change_pages; j++)
3178 if (!ei->change_page[j].can_change_or_has_action)
3181 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3183 // only add event page for the first page found with this event
3184 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3186 ei->has_change_event[k] = TRUE;
3188 ei->event_page_nr[k] = j;
3189 ei->event_page[k] = &ei->change_page[j];
3195 // ---------- initialize reference elements in change conditions ------------
3197 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3199 int element = EL_CUSTOM_START + i;
3200 struct ElementInfo *ei = &element_info[element];
3202 for (j = 0; j < ei->num_change_pages; j++)
3204 int trigger_element = ei->change_page[j].initial_trigger_element;
3206 if (trigger_element >= EL_PREV_CE_8 &&
3207 trigger_element <= EL_NEXT_CE_8)
3208 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3210 ei->change_page[j].trigger_element = trigger_element;
3214 // ---------- initialize run-time trigger player and element ----------------
3216 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3218 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3220 for (j = 0; j < ei->num_change_pages; j++)
3222 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3223 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3224 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3225 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3226 ei->change_page[j].actual_trigger_ce_value = 0;
3227 ei->change_page[j].actual_trigger_ce_score = 0;
3231 // ---------- initialize trigger events -------------------------------------
3233 // initialize trigger events information
3234 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3235 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3236 trigger_events[i][j] = FALSE;
3238 // add trigger events from element change event properties
3239 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3241 struct ElementInfo *ei = &element_info[i];
3243 for (j = 0; j < ei->num_change_pages; j++)
3245 if (!ei->change_page[j].can_change_or_has_action)
3248 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3250 int trigger_element = ei->change_page[j].trigger_element;
3252 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3254 if (ei->change_page[j].has_event[k])
3256 if (IS_GROUP_ELEMENT(trigger_element))
3258 struct ElementGroupInfo *group =
3259 element_info[trigger_element].group;
3261 for (l = 0; l < group->num_elements_resolved; l++)
3262 trigger_events[group->element_resolved[l]][k] = TRUE;
3264 else if (trigger_element == EL_ANY_ELEMENT)
3265 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3266 trigger_events[l][k] = TRUE;
3268 trigger_events[trigger_element][k] = TRUE;
3275 // ---------- initialize push delay -----------------------------------------
3277 // initialize push delay values to default
3278 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3280 if (!IS_CUSTOM_ELEMENT(i))
3282 // set default push delay values (corrected since version 3.0.7-1)
3283 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3285 element_info[i].push_delay_fixed = 2;
3286 element_info[i].push_delay_random = 8;
3290 element_info[i].push_delay_fixed = 8;
3291 element_info[i].push_delay_random = 8;
3296 // set push delay value for certain elements from pre-defined list
3297 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3299 int e = push_delay_list[i].element;
3301 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3302 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3305 // set push delay value for Supaplex elements for newer engine versions
3306 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3308 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3310 if (IS_SP_ELEMENT(i))
3312 // set SP push delay to just enough to push under a falling zonk
3313 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3315 element_info[i].push_delay_fixed = delay;
3316 element_info[i].push_delay_random = 0;
3321 // ---------- initialize move stepsize --------------------------------------
3323 // initialize move stepsize values to default
3324 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3325 if (!IS_CUSTOM_ELEMENT(i))
3326 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3328 // set move stepsize value for certain elements from pre-defined list
3329 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3331 int e = move_stepsize_list[i].element;
3333 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3335 // set move stepsize value for certain elements for older engine versions
3336 if (use_old_move_stepsize_for_magic_wall)
3338 if (e == EL_MAGIC_WALL_FILLING ||
3339 e == EL_MAGIC_WALL_EMPTYING ||
3340 e == EL_BD_MAGIC_WALL_FILLING ||
3341 e == EL_BD_MAGIC_WALL_EMPTYING)
3342 element_info[e].move_stepsize *= 2;
3346 // ---------- initialize collect score --------------------------------------
3348 // initialize collect score values for custom elements from initial value
3349 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3350 if (IS_CUSTOM_ELEMENT(i))
3351 element_info[i].collect_score = element_info[i].collect_score_initial;
3353 // ---------- initialize collect count --------------------------------------
3355 // initialize collect count values for non-custom elements
3356 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3357 if (!IS_CUSTOM_ELEMENT(i))
3358 element_info[i].collect_count_initial = 0;
3360 // add collect count values for all elements from pre-defined list
3361 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3362 element_info[collect_count_list[i].element].collect_count_initial =
3363 collect_count_list[i].count;
3365 // ---------- initialize access direction -----------------------------------
3367 // initialize access direction values to default (access from every side)
3368 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3369 if (!IS_CUSTOM_ELEMENT(i))
3370 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3372 // set access direction value for certain elements from pre-defined list
3373 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3374 element_info[access_direction_list[i].element].access_direction =
3375 access_direction_list[i].direction;
3377 // ---------- initialize explosion content ----------------------------------
3378 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3380 if (IS_CUSTOM_ELEMENT(i))
3383 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3385 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3387 element_info[i].content.e[x][y] =
3388 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3389 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3390 i == EL_PLAYER_3 ? EL_EMERALD :
3391 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3392 i == EL_MOLE ? EL_EMERALD_RED :
3393 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3394 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3395 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3396 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3397 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3398 i == EL_WALL_EMERALD ? EL_EMERALD :
3399 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3400 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3401 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3402 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3403 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3404 i == EL_WALL_PEARL ? EL_PEARL :
3405 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3410 // ---------- initialize recursion detection --------------------------------
3411 recursion_loop_depth = 0;
3412 recursion_loop_detected = FALSE;
3413 recursion_loop_element = EL_UNDEFINED;
3415 // ---------- initialize graphics engine ------------------------------------
3416 game.scroll_delay_value =
3417 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3418 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3419 !setup.forced_scroll_delay ? 0 :
3420 setup.scroll_delay ? setup.scroll_delay_value : 0);
3421 game.scroll_delay_value =
3422 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3424 // ---------- initialize game engine snapshots ------------------------------
3425 for (i = 0; i < MAX_PLAYERS; i++)
3426 game.snapshot.last_action[i] = 0;
3427 game.snapshot.changed_action = FALSE;
3428 game.snapshot.collected_item = FALSE;
3429 game.snapshot.mode =
3430 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3431 SNAPSHOT_MODE_EVERY_STEP :
3432 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3433 SNAPSHOT_MODE_EVERY_MOVE :
3434 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3435 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3436 game.snapshot.save_snapshot = FALSE;
3438 // ---------- initialize level time for Supaplex engine ---------------------
3439 // Supaplex levels with time limit currently unsupported -- should be added
3440 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3443 // ---------- initialize flags for handling game actions --------------------
3445 // set flags for game actions to default values
3446 game.use_key_actions = TRUE;
3447 game.use_mouse_actions = FALSE;
3449 // when using Mirror Magic game engine, handle mouse events only
3450 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3452 game.use_key_actions = FALSE;
3453 game.use_mouse_actions = TRUE;
3456 // check for custom elements with mouse click events
3457 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3459 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3461 int element = EL_CUSTOM_START + i;
3463 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3464 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3465 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3466 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3467 game.use_mouse_actions = TRUE;
3472 static int get_num_special_action(int element, int action_first,
3475 int num_special_action = 0;
3478 for (i = action_first; i <= action_last; i++)
3480 boolean found = FALSE;
3482 for (j = 0; j < NUM_DIRECTIONS; j++)
3483 if (el_act_dir2img(element, i, j) !=
3484 el_act_dir2img(element, ACTION_DEFAULT, j))
3488 num_special_action++;
3493 return num_special_action;
3497 // ============================================================================
3499 // ----------------------------------------------------------------------------
3500 // initialize and start new game
3501 // ============================================================================
3503 #if DEBUG_INIT_PLAYER
3504 static void DebugPrintPlayerStatus(char *message)
3511 Debug("game:init:player", "%s:", message);
3513 for (i = 0; i < MAX_PLAYERS; i++)
3515 struct PlayerInfo *player = &stored_player[i];
3517 Debug("game:init:player",
3518 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3522 player->connected_locally,
3523 player->connected_network,
3525 (local_player == player ? " (local player)" : ""));
3532 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3533 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3534 int fade_mask = REDRAW_FIELD;
3536 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3537 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3538 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3539 int initial_move_dir = MV_DOWN;
3542 // required here to update video display before fading (FIX THIS)
3543 DrawMaskedBorder(REDRAW_DOOR_2);
3545 if (!game.restart_level)
3546 CloseDoor(DOOR_CLOSE_1);
3548 SetGameStatus(GAME_MODE_PLAYING);
3550 if (level_editor_test_game)
3551 FadeSkipNextFadeOut();
3553 FadeSetEnterScreen();
3556 fade_mask = REDRAW_ALL;
3558 FadeLevelSoundsAndMusic();
3560 ExpireSoundLoops(TRUE);
3564 if (level_editor_test_game)
3565 FadeSkipNextFadeIn();
3567 // needed if different viewport properties defined for playing
3568 ChangeViewportPropertiesIfNeeded();
3572 DrawCompleteVideoDisplay();
3574 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3577 InitGameControlValues();
3581 // initialize tape actions from game when recording tape
3582 tape.use_key_actions = game.use_key_actions;
3583 tape.use_mouse_actions = game.use_mouse_actions;
3585 // initialize visible playfield size when recording tape (for team mode)
3586 tape.scr_fieldx = SCR_FIELDX;
3587 tape.scr_fieldy = SCR_FIELDY;
3590 // don't play tapes over network
3591 network_playing = (network.enabled && !tape.playing);
3593 for (i = 0; i < MAX_PLAYERS; i++)
3595 struct PlayerInfo *player = &stored_player[i];
3597 player->index_nr = i;
3598 player->index_bit = (1 << i);
3599 player->element_nr = EL_PLAYER_1 + i;
3601 player->present = FALSE;
3602 player->active = FALSE;
3603 player->mapped = FALSE;
3605 player->killed = FALSE;
3606 player->reanimated = FALSE;
3607 player->buried = FALSE;
3610 player->effective_action = 0;
3611 player->programmed_action = 0;
3612 player->snap_action = 0;
3614 player->mouse_action.lx = 0;
3615 player->mouse_action.ly = 0;
3616 player->mouse_action.button = 0;
3617 player->mouse_action.button_hint = 0;
3619 player->effective_mouse_action.lx = 0;
3620 player->effective_mouse_action.ly = 0;
3621 player->effective_mouse_action.button = 0;
3622 player->effective_mouse_action.button_hint = 0;
3624 for (j = 0; j < MAX_NUM_KEYS; j++)
3625 player->key[j] = FALSE;
3627 player->num_white_keys = 0;
3629 player->dynabomb_count = 0;
3630 player->dynabomb_size = 1;
3631 player->dynabombs_left = 0;
3632 player->dynabomb_xl = FALSE;
3634 player->MovDir = initial_move_dir;
3637 player->GfxDir = initial_move_dir;
3638 player->GfxAction = ACTION_DEFAULT;
3640 player->StepFrame = 0;
3642 player->initial_element = player->element_nr;
3643 player->artwork_element =
3644 (level.use_artwork_element[i] ? level.artwork_element[i] :
3645 player->element_nr);
3646 player->use_murphy = FALSE;
3648 player->block_last_field = FALSE; // initialized in InitPlayerField()
3649 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3651 player->gravity = level.initial_player_gravity[i];
3653 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3655 player->actual_frame_counter = 0;
3657 player->step_counter = 0;
3659 player->last_move_dir = initial_move_dir;
3661 player->is_active = FALSE;
3663 player->is_waiting = FALSE;
3664 player->is_moving = FALSE;
3665 player->is_auto_moving = FALSE;
3666 player->is_digging = FALSE;
3667 player->is_snapping = FALSE;
3668 player->is_collecting = FALSE;
3669 player->is_pushing = FALSE;
3670 player->is_switching = FALSE;
3671 player->is_dropping = FALSE;
3672 player->is_dropping_pressed = FALSE;
3674 player->is_bored = FALSE;
3675 player->is_sleeping = FALSE;
3677 player->was_waiting = TRUE;
3678 player->was_moving = FALSE;
3679 player->was_snapping = FALSE;
3680 player->was_dropping = FALSE;
3682 player->force_dropping = FALSE;
3684 player->frame_counter_bored = -1;
3685 player->frame_counter_sleeping = -1;
3687 player->anim_delay_counter = 0;
3688 player->post_delay_counter = 0;
3690 player->dir_waiting = initial_move_dir;
3691 player->action_waiting = ACTION_DEFAULT;
3692 player->last_action_waiting = ACTION_DEFAULT;
3693 player->special_action_bored = ACTION_DEFAULT;
3694 player->special_action_sleeping = ACTION_DEFAULT;
3696 player->switch_x = -1;
3697 player->switch_y = -1;
3699 player->drop_x = -1;
3700 player->drop_y = -1;
3702 player->show_envelope = 0;
3704 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3706 player->push_delay = -1; // initialized when pushing starts
3707 player->push_delay_value = game.initial_push_delay_value;
3709 player->drop_delay = 0;
3710 player->drop_pressed_delay = 0;
3712 player->last_jx = -1;
3713 player->last_jy = -1;
3717 player->shield_normal_time_left = 0;
3718 player->shield_deadly_time_left = 0;
3720 player->last_removed_element = EL_UNDEFINED;
3722 player->inventory_infinite_element = EL_UNDEFINED;
3723 player->inventory_size = 0;
3725 if (level.use_initial_inventory[i])
3727 for (j = 0; j < level.initial_inventory_size[i]; j++)
3729 int element = level.initial_inventory_content[i][j];
3730 int collect_count = element_info[element].collect_count_initial;
3733 if (!IS_CUSTOM_ELEMENT(element))
3736 if (collect_count == 0)
3737 player->inventory_infinite_element = element;
3739 for (k = 0; k < collect_count; k++)
3740 if (player->inventory_size < MAX_INVENTORY_SIZE)
3741 player->inventory_element[player->inventory_size++] = element;
3745 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3746 SnapField(player, 0, 0);
3748 map_player_action[i] = i;
3751 network_player_action_received = FALSE;
3753 // initial null action
3754 if (network_playing)
3755 SendToServer_MovePlayer(MV_NONE);
3760 TimeLeft = level.time;
3763 ScreenMovDir = MV_NONE;
3767 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3769 game.robot_wheel_x = -1;
3770 game.robot_wheel_y = -1;
3775 game.all_players_gone = FALSE;
3777 game.LevelSolved = FALSE;
3778 game.GameOver = FALSE;
3780 game.GamePlayed = !tape.playing;
3782 game.LevelSolved_GameWon = FALSE;
3783 game.LevelSolved_GameEnd = FALSE;
3784 game.LevelSolved_SaveTape = FALSE;
3785 game.LevelSolved_SaveScore = FALSE;
3787 game.LevelSolved_CountingTime = 0;
3788 game.LevelSolved_CountingScore = 0;
3789 game.LevelSolved_CountingHealth = 0;
3791 game.panel.active = TRUE;
3793 game.no_time_limit = (level.time == 0);
3795 game.yamyam_content_nr = 0;
3796 game.robot_wheel_active = FALSE;
3797 game.magic_wall_active = FALSE;
3798 game.magic_wall_time_left = 0;
3799 game.light_time_left = 0;
3800 game.timegate_time_left = 0;
3801 game.switchgate_pos = 0;
3802 game.wind_direction = level.wind_direction_initial;
3805 game.score_final = 0;
3807 game.health = MAX_HEALTH;
3808 game.health_final = MAX_HEALTH;
3810 game.gems_still_needed = level.gems_needed;
3811 game.sokoban_fields_still_needed = 0;
3812 game.sokoban_objects_still_needed = 0;
3813 game.lights_still_needed = 0;
3814 game.players_still_needed = 0;
3815 game.friends_still_needed = 0;
3817 game.lenses_time_left = 0;
3818 game.magnify_time_left = 0;
3820 game.ball_active = level.ball_active_initial;
3821 game.ball_content_nr = 0;
3823 game.explosions_delayed = TRUE;
3825 game.envelope_active = FALSE;
3827 for (i = 0; i < NUM_BELTS; i++)
3829 game.belt_dir[i] = MV_NONE;
3830 game.belt_dir_nr[i] = 3; // not moving, next moving left
3833 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3834 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3836 #if DEBUG_INIT_PLAYER
3837 DebugPrintPlayerStatus("Player status at level initialization");
3840 SCAN_PLAYFIELD(x, y)
3842 Tile[x][y] = Last[x][y] = level.field[x][y];
3843 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3844 ChangeDelay[x][y] = 0;
3845 ChangePage[x][y] = -1;
3846 CustomValue[x][y] = 0; // initialized in InitField()
3847 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3849 WasJustMoving[x][y] = 0;
3850 WasJustFalling[x][y] = 0;
3851 CheckCollision[x][y] = 0;
3852 CheckImpact[x][y] = 0;
3854 Pushed[x][y] = FALSE;
3856 ChangeCount[x][y] = 0;
3857 ChangeEvent[x][y] = -1;
3859 ExplodePhase[x][y] = 0;
3860 ExplodeDelay[x][y] = 0;
3861 ExplodeField[x][y] = EX_TYPE_NONE;
3863 RunnerVisit[x][y] = 0;
3864 PlayerVisit[x][y] = 0;
3867 GfxRandom[x][y] = INIT_GFX_RANDOM();
3868 GfxElement[x][y] = EL_UNDEFINED;
3869 GfxAction[x][y] = ACTION_DEFAULT;
3870 GfxDir[x][y] = MV_NONE;
3871 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3874 SCAN_PLAYFIELD(x, y)
3876 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3878 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3880 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3883 InitField(x, y, TRUE);
3885 ResetGfxAnimation(x, y);
3890 for (i = 0; i < MAX_PLAYERS; i++)
3892 struct PlayerInfo *player = &stored_player[i];
3894 // set number of special actions for bored and sleeping animation
3895 player->num_special_action_bored =
3896 get_num_special_action(player->artwork_element,
3897 ACTION_BORING_1, ACTION_BORING_LAST);
3898 player->num_special_action_sleeping =
3899 get_num_special_action(player->artwork_element,
3900 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3903 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3904 emulate_sb ? EMU_SOKOBAN :
3905 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3907 // initialize type of slippery elements
3908 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3910 if (!IS_CUSTOM_ELEMENT(i))
3912 // default: elements slip down either to the left or right randomly
3913 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3915 // SP style elements prefer to slip down on the left side
3916 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3917 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3919 // BD style elements prefer to slip down on the left side
3920 if (game.emulation == EMU_BOULDERDASH)
3921 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3925 // initialize explosion and ignition delay
3926 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3928 if (!IS_CUSTOM_ELEMENT(i))
3931 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3932 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3933 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3934 int last_phase = (num_phase + 1) * delay;
3935 int half_phase = (num_phase / 2) * delay;
3937 element_info[i].explosion_delay = last_phase - 1;
3938 element_info[i].ignition_delay = half_phase;
3940 if (i == EL_BLACK_ORB)
3941 element_info[i].ignition_delay = 1;
3945 // correct non-moving belts to start moving left
3946 for (i = 0; i < NUM_BELTS; i++)
3947 if (game.belt_dir[i] == MV_NONE)
3948 game.belt_dir_nr[i] = 3; // not moving, next moving left
3950 #if USE_NEW_PLAYER_ASSIGNMENTS
3951 // use preferred player also in local single-player mode
3952 if (!network.enabled && !game.team_mode)
3954 int new_index_nr = setup.network_player_nr;
3956 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3958 for (i = 0; i < MAX_PLAYERS; i++)
3959 stored_player[i].connected_locally = FALSE;
3961 stored_player[new_index_nr].connected_locally = TRUE;
3965 for (i = 0; i < MAX_PLAYERS; i++)
3967 stored_player[i].connected = FALSE;
3969 // in network game mode, the local player might not be the first player
3970 if (stored_player[i].connected_locally)
3971 local_player = &stored_player[i];
3974 if (!network.enabled)
3975 local_player->connected = TRUE;
3979 for (i = 0; i < MAX_PLAYERS; i++)
3980 stored_player[i].connected = tape.player_participates[i];
3982 else if (network.enabled)
3984 // add team mode players connected over the network (needed for correct
3985 // assignment of player figures from level to locally playing players)
3987 for (i = 0; i < MAX_PLAYERS; i++)
3988 if (stored_player[i].connected_network)
3989 stored_player[i].connected = TRUE;
3991 else if (game.team_mode)
3993 // try to guess locally connected team mode players (needed for correct
3994 // assignment of player figures from level to locally playing players)
3996 for (i = 0; i < MAX_PLAYERS; i++)
3997 if (setup.input[i].use_joystick ||
3998 setup.input[i].key.left != KSYM_UNDEFINED)
3999 stored_player[i].connected = TRUE;
4002 #if DEBUG_INIT_PLAYER
4003 DebugPrintPlayerStatus("Player status after level initialization");
4006 #if DEBUG_INIT_PLAYER
4007 Debug("game:init:player", "Reassigning players ...");
4010 // check if any connected player was not found in playfield
4011 for (i = 0; i < MAX_PLAYERS; i++)
4013 struct PlayerInfo *player = &stored_player[i];
4015 if (player->connected && !player->present)
4017 struct PlayerInfo *field_player = NULL;
4019 #if DEBUG_INIT_PLAYER
4020 Debug("game:init:player",
4021 "- looking for field player for player %d ...", i + 1);
4024 // assign first free player found that is present in the playfield
4026 // first try: look for unmapped playfield player that is not connected
4027 for (j = 0; j < MAX_PLAYERS; j++)
4028 if (field_player == NULL &&
4029 stored_player[j].present &&
4030 !stored_player[j].mapped &&
4031 !stored_player[j].connected)
4032 field_player = &stored_player[j];
4034 // second try: look for *any* unmapped playfield player
4035 for (j = 0; j < MAX_PLAYERS; j++)
4036 if (field_player == NULL &&
4037 stored_player[j].present &&
4038 !stored_player[j].mapped)
4039 field_player = &stored_player[j];
4041 if (field_player != NULL)
4043 int jx = field_player->jx, jy = field_player->jy;
4045 #if DEBUG_INIT_PLAYER
4046 Debug("game:init:player", "- found player %d",
4047 field_player->index_nr + 1);
4050 player->present = FALSE;
4051 player->active = FALSE;
4053 field_player->present = TRUE;
4054 field_player->active = TRUE;
4057 player->initial_element = field_player->initial_element;
4058 player->artwork_element = field_player->artwork_element;
4060 player->block_last_field = field_player->block_last_field;
4061 player->block_delay_adjustment = field_player->block_delay_adjustment;
4064 StorePlayer[jx][jy] = field_player->element_nr;
4066 field_player->jx = field_player->last_jx = jx;
4067 field_player->jy = field_player->last_jy = jy;
4069 if (local_player == player)
4070 local_player = field_player;
4072 map_player_action[field_player->index_nr] = i;
4074 field_player->mapped = TRUE;
4076 #if DEBUG_INIT_PLAYER
4077 Debug("game:init:player", "- map_player_action[%d] == %d",
4078 field_player->index_nr + 1, i + 1);
4083 if (player->connected && player->present)
4084 player->mapped = TRUE;
4087 #if DEBUG_INIT_PLAYER
4088 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4093 // check if any connected player was not found in playfield
4094 for (i = 0; i < MAX_PLAYERS; i++)
4096 struct PlayerInfo *player = &stored_player[i];
4098 if (player->connected && !player->present)
4100 for (j = 0; j < MAX_PLAYERS; j++)
4102 struct PlayerInfo *field_player = &stored_player[j];
4103 int jx = field_player->jx, jy = field_player->jy;
4105 // assign first free player found that is present in the playfield
4106 if (field_player->present && !field_player->connected)
4108 player->present = TRUE;
4109 player->active = TRUE;
4111 field_player->present = FALSE;
4112 field_player->active = FALSE;
4114 player->initial_element = field_player->initial_element;
4115 player->artwork_element = field_player->artwork_element;
4117 player->block_last_field = field_player->block_last_field;
4118 player->block_delay_adjustment = field_player->block_delay_adjustment;
4120 StorePlayer[jx][jy] = player->element_nr;
4122 player->jx = player->last_jx = jx;
4123 player->jy = player->last_jy = jy;
4133 Debug("game:init:player", "local_player->present == %d",
4134 local_player->present);
4137 // set focus to local player for network games, else to all players
4138 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4139 game.centered_player_nr_next = game.centered_player_nr;
4140 game.set_centered_player = FALSE;
4141 game.set_centered_player_wrap = FALSE;
4143 if (network_playing && tape.recording)
4145 // store client dependent player focus when recording network games
4146 tape.centered_player_nr_next = game.centered_player_nr_next;
4147 tape.set_centered_player = TRUE;
4152 // when playing a tape, eliminate all players who do not participate
4154 #if USE_NEW_PLAYER_ASSIGNMENTS
4156 if (!game.team_mode)
4158 for (i = 0; i < MAX_PLAYERS; i++)
4160 if (stored_player[i].active &&
4161 !tape.player_participates[map_player_action[i]])
4163 struct PlayerInfo *player = &stored_player[i];
4164 int jx = player->jx, jy = player->jy;
4166 #if DEBUG_INIT_PLAYER
4167 Debug("game:init:player", "Removing player %d at (%d, %d)",
4171 player->active = FALSE;
4172 StorePlayer[jx][jy] = 0;
4173 Tile[jx][jy] = EL_EMPTY;
4180 for (i = 0; i < MAX_PLAYERS; i++)
4182 if (stored_player[i].active &&
4183 !tape.player_participates[i])
4185 struct PlayerInfo *player = &stored_player[i];
4186 int jx = player->jx, jy = player->jy;
4188 player->active = FALSE;
4189 StorePlayer[jx][jy] = 0;
4190 Tile[jx][jy] = EL_EMPTY;
4195 else if (!network.enabled && !game.team_mode) // && !tape.playing
4197 // when in single player mode, eliminate all but the local player
4199 for (i = 0; i < MAX_PLAYERS; i++)
4201 struct PlayerInfo *player = &stored_player[i];
4203 if (player->active && player != local_player)
4205 int jx = player->jx, jy = player->jy;
4207 player->active = FALSE;
4208 player->present = FALSE;
4210 StorePlayer[jx][jy] = 0;
4211 Tile[jx][jy] = EL_EMPTY;
4216 for (i = 0; i < MAX_PLAYERS; i++)
4217 if (stored_player[i].active)
4218 game.players_still_needed++;
4220 if (level.solved_by_one_player)
4221 game.players_still_needed = 1;
4223 // when recording the game, store which players take part in the game
4226 #if USE_NEW_PLAYER_ASSIGNMENTS
4227 for (i = 0; i < MAX_PLAYERS; i++)
4228 if (stored_player[i].connected)
4229 tape.player_participates[i] = TRUE;
4231 for (i = 0; i < MAX_PLAYERS; i++)
4232 if (stored_player[i].active)
4233 tape.player_participates[i] = TRUE;
4237 #if DEBUG_INIT_PLAYER
4238 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4241 if (BorderElement == EL_EMPTY)
4244 SBX_Right = lev_fieldx - SCR_FIELDX;
4246 SBY_Lower = lev_fieldy - SCR_FIELDY;
4251 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4253 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4256 if (full_lev_fieldx <= SCR_FIELDX)
4257 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4258 if (full_lev_fieldy <= SCR_FIELDY)
4259 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4261 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4263 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4266 // if local player not found, look for custom element that might create
4267 // the player (make some assumptions about the right custom element)
4268 if (!local_player->present)
4270 int start_x = 0, start_y = 0;
4271 int found_rating = 0;
4272 int found_element = EL_UNDEFINED;
4273 int player_nr = local_player->index_nr;
4275 SCAN_PLAYFIELD(x, y)
4277 int element = Tile[x][y];
4282 if (level.use_start_element[player_nr] &&
4283 level.start_element[player_nr] == element &&
4290 found_element = element;
4293 if (!IS_CUSTOM_ELEMENT(element))
4296 if (CAN_CHANGE(element))
4298 for (i = 0; i < element_info[element].num_change_pages; i++)
4300 // check for player created from custom element as single target
4301 content = element_info[element].change_page[i].target_element;
4302 is_player = ELEM_IS_PLAYER(content);
4304 if (is_player && (found_rating < 3 ||
4305 (found_rating == 3 && element < found_element)))
4311 found_element = element;
4316 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4318 // check for player created from custom element as explosion content
4319 content = element_info[element].content.e[xx][yy];
4320 is_player = ELEM_IS_PLAYER(content);
4322 if (is_player && (found_rating < 2 ||
4323 (found_rating == 2 && element < found_element)))
4325 start_x = x + xx - 1;
4326 start_y = y + yy - 1;
4329 found_element = element;
4332 if (!CAN_CHANGE(element))
4335 for (i = 0; i < element_info[element].num_change_pages; i++)
4337 // check for player created from custom element as extended target
4339 element_info[element].change_page[i].target_content.e[xx][yy];
4341 is_player = ELEM_IS_PLAYER(content);
4343 if (is_player && (found_rating < 1 ||
4344 (found_rating == 1 && element < found_element)))
4346 start_x = x + xx - 1;
4347 start_y = y + yy - 1;
4350 found_element = element;
4356 scroll_x = SCROLL_POSITION_X(start_x);
4357 scroll_y = SCROLL_POSITION_Y(start_y);
4361 scroll_x = SCROLL_POSITION_X(local_player->jx);
4362 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4365 // !!! FIX THIS (START) !!!
4366 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4368 InitGameEngine_EM();
4370 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4372 InitGameEngine_SP();
4374 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4376 InitGameEngine_MM();
4380 DrawLevel(REDRAW_FIELD);
4383 // after drawing the level, correct some elements
4384 if (game.timegate_time_left == 0)
4385 CloseAllOpenTimegates();
4388 // blit playfield from scroll buffer to normal back buffer for fading in
4389 BlitScreenToBitmap(backbuffer);
4390 // !!! FIX THIS (END) !!!
4392 DrawMaskedBorder(fade_mask);
4397 // full screen redraw is required at this point in the following cases:
4398 // - special editor door undrawn when game was started from level editor
4399 // - drawing area (playfield) was changed and has to be removed completely
4400 redraw_mask = REDRAW_ALL;
4404 if (!game.restart_level)
4406 // copy default game door content to main double buffer
4408 // !!! CHECK AGAIN !!!
4409 SetPanelBackground();
4410 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4411 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4414 SetPanelBackground();
4415 SetDrawBackgroundMask(REDRAW_DOOR_1);
4417 UpdateAndDisplayGameControlValues();
4419 if (!game.restart_level)
4425 CreateGameButtons();
4430 // copy actual game door content to door double buffer for OpenDoor()
4431 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4433 OpenDoor(DOOR_OPEN_ALL);
4435 KeyboardAutoRepeatOffUnlessAutoplay();
4437 #if DEBUG_INIT_PLAYER
4438 DebugPrintPlayerStatus("Player status (final)");
4447 if (!game.restart_level && !tape.playing)
4449 LevelStats_incPlayed(level_nr);
4451 SaveLevelSetup_SeriesInfo();
4454 game.restart_level = FALSE;
4455 game.restart_game_message = NULL;
4457 game.request_active = FALSE;
4458 game.request_active_or_moving = FALSE;
4460 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4461 InitGameActions_MM();
4463 SaveEngineSnapshotToListInitial();
4465 if (!game.restart_level)
4467 PlaySound(SND_GAME_STARTING);
4469 if (setup.sound_music)
4474 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4475 int actual_player_x, int actual_player_y)
4477 // this is used for non-R'n'D game engines to update certain engine values
4479 // needed to determine if sounds are played within the visible screen area
4480 scroll_x = actual_scroll_x;
4481 scroll_y = actual_scroll_y;
4483 // needed to get player position for "follow finger" playing input method
4484 local_player->jx = actual_player_x;
4485 local_player->jy = actual_player_y;
4488 void InitMovDir(int x, int y)
4490 int i, element = Tile[x][y];
4491 static int xy[4][2] =
4498 static int direction[3][4] =
4500 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4501 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4502 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4511 Tile[x][y] = EL_BUG;
4512 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4515 case EL_SPACESHIP_RIGHT:
4516 case EL_SPACESHIP_UP:
4517 case EL_SPACESHIP_LEFT:
4518 case EL_SPACESHIP_DOWN:
4519 Tile[x][y] = EL_SPACESHIP;
4520 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4523 case EL_BD_BUTTERFLY_RIGHT:
4524 case EL_BD_BUTTERFLY_UP:
4525 case EL_BD_BUTTERFLY_LEFT:
4526 case EL_BD_BUTTERFLY_DOWN:
4527 Tile[x][y] = EL_BD_BUTTERFLY;
4528 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4531 case EL_BD_FIREFLY_RIGHT:
4532 case EL_BD_FIREFLY_UP:
4533 case EL_BD_FIREFLY_LEFT:
4534 case EL_BD_FIREFLY_DOWN:
4535 Tile[x][y] = EL_BD_FIREFLY;
4536 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4539 case EL_PACMAN_RIGHT:
4541 case EL_PACMAN_LEFT:
4542 case EL_PACMAN_DOWN:
4543 Tile[x][y] = EL_PACMAN;
4544 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4547 case EL_YAMYAM_LEFT:
4548 case EL_YAMYAM_RIGHT:
4550 case EL_YAMYAM_DOWN:
4551 Tile[x][y] = EL_YAMYAM;
4552 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4555 case EL_SP_SNIKSNAK:
4556 MovDir[x][y] = MV_UP;
4559 case EL_SP_ELECTRON:
4560 MovDir[x][y] = MV_LEFT;
4567 Tile[x][y] = EL_MOLE;
4568 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4571 case EL_SPRING_LEFT:
4572 case EL_SPRING_RIGHT:
4573 Tile[x][y] = EL_SPRING;
4574 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4578 if (IS_CUSTOM_ELEMENT(element))
4580 struct ElementInfo *ei = &element_info[element];
4581 int move_direction_initial = ei->move_direction_initial;
4582 int move_pattern = ei->move_pattern;
4584 if (move_direction_initial == MV_START_PREVIOUS)
4586 if (MovDir[x][y] != MV_NONE)
4589 move_direction_initial = MV_START_AUTOMATIC;
4592 if (move_direction_initial == MV_START_RANDOM)
4593 MovDir[x][y] = 1 << RND(4);
4594 else if (move_direction_initial & MV_ANY_DIRECTION)
4595 MovDir[x][y] = move_direction_initial;
4596 else if (move_pattern == MV_ALL_DIRECTIONS ||
4597 move_pattern == MV_TURNING_LEFT ||
4598 move_pattern == MV_TURNING_RIGHT ||
4599 move_pattern == MV_TURNING_LEFT_RIGHT ||
4600 move_pattern == MV_TURNING_RIGHT_LEFT ||
4601 move_pattern == MV_TURNING_RANDOM)
4602 MovDir[x][y] = 1 << RND(4);
4603 else if (move_pattern == MV_HORIZONTAL)
4604 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4605 else if (move_pattern == MV_VERTICAL)
4606 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4607 else if (move_pattern & MV_ANY_DIRECTION)
4608 MovDir[x][y] = element_info[element].move_pattern;
4609 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4610 move_pattern == MV_ALONG_RIGHT_SIDE)
4612 // use random direction as default start direction
4613 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4614 MovDir[x][y] = 1 << RND(4);
4616 for (i = 0; i < NUM_DIRECTIONS; i++)
4618 int x1 = x + xy[i][0];
4619 int y1 = y + xy[i][1];
4621 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4623 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4624 MovDir[x][y] = direction[0][i];
4626 MovDir[x][y] = direction[1][i];
4635 MovDir[x][y] = 1 << RND(4);
4637 if (element != EL_BUG &&
4638 element != EL_SPACESHIP &&
4639 element != EL_BD_BUTTERFLY &&
4640 element != EL_BD_FIREFLY)
4643 for (i = 0; i < NUM_DIRECTIONS; i++)
4645 int x1 = x + xy[i][0];
4646 int y1 = y + xy[i][1];
4648 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4650 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4652 MovDir[x][y] = direction[0][i];
4655 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4656 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4658 MovDir[x][y] = direction[1][i];
4667 GfxDir[x][y] = MovDir[x][y];
4670 void InitAmoebaNr(int x, int y)
4673 int group_nr = AmoebaNeighbourNr(x, y);
4677 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4679 if (AmoebaCnt[i] == 0)
4687 AmoebaNr[x][y] = group_nr;
4688 AmoebaCnt[group_nr]++;
4689 AmoebaCnt2[group_nr]++;
4692 static void LevelSolved(void)
4694 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4695 game.players_still_needed > 0)
4698 game.LevelSolved = TRUE;
4699 game.GameOver = TRUE;
4701 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4702 game_em.lev->score :
4703 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4706 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4707 MM_HEALTH(game_mm.laser_overload_value) :
4710 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4711 game.LevelSolved_CountingScore = game.score_final;
4712 game.LevelSolved_CountingHealth = game.health_final;
4717 static int time_count_steps;
4718 static int time, time_final;
4719 static float score, score_final; // needed for time score < 10 for 10 seconds
4720 static int health, health_final;
4721 static int game_over_delay_1 = 0;
4722 static int game_over_delay_2 = 0;
4723 static int game_over_delay_3 = 0;
4724 int game_over_delay_value_1 = 50;
4725 int game_over_delay_value_2 = 25;
4726 int game_over_delay_value_3 = 50;
4727 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4728 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4730 if (!game.LevelSolved_GameWon)
4734 // do not start end game actions before the player stops moving (to exit)
4735 if (local_player->active && local_player->MovPos)
4738 game.LevelSolved_GameWon = TRUE;
4739 game.LevelSolved_SaveTape = tape.recording;
4740 game.LevelSolved_SaveScore = !tape.playing;
4744 LevelStats_incSolved(level_nr);
4746 SaveLevelSetup_SeriesInfo();
4749 if (tape.auto_play) // tape might already be stopped here
4750 tape.auto_play_level_solved = TRUE;
4754 game_over_delay_1 = 0;
4755 game_over_delay_2 = 0;
4756 game_over_delay_3 = game_over_delay_value_3;
4758 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4759 score = score_final = game.score_final;
4760 health = health_final = game.health_final;
4764 int time_frames = 0;
4769 time_frames = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4771 else if (game.no_time_limit && TimePlayed < 999)
4774 time_frames = (999 - TimePlayed) * FRAMES_PER_SECOND - TimeFrames;
4777 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4779 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4781 game_over_delay_1 = game_over_delay_value_1;
4783 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4786 score_final += health * time_score;
4788 game_over_delay_2 = game_over_delay_value_2;
4791 game.score_final = score_final;
4792 game.health_final = health_final;
4795 if (level_editor_test_game)
4798 score = score_final;
4800 game.LevelSolved_CountingTime = time;
4801 game.LevelSolved_CountingScore = score;
4803 game_panel_controls[GAME_PANEL_TIME].value = time;
4804 game_panel_controls[GAME_PANEL_SCORE].value = score;
4806 DisplayGameControlValues();
4809 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4811 // check if last player has left the level
4812 if (game.exit_x >= 0 &&
4815 int x = game.exit_x;
4816 int y = game.exit_y;
4817 int element = Tile[x][y];
4819 // close exit door after last player
4820 if ((game.all_players_gone &&
4821 (element == EL_EXIT_OPEN ||
4822 element == EL_SP_EXIT_OPEN ||
4823 element == EL_STEEL_EXIT_OPEN)) ||
4824 element == EL_EM_EXIT_OPEN ||
4825 element == EL_EM_STEEL_EXIT_OPEN)
4829 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4830 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4831 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4832 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4833 EL_EM_STEEL_EXIT_CLOSING);
4835 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4838 // player disappears
4839 DrawLevelField(x, y);
4842 for (i = 0; i < MAX_PLAYERS; i++)
4844 struct PlayerInfo *player = &stored_player[i];
4846 if (player->present)
4848 RemovePlayer(player);
4850 // player disappears
4851 DrawLevelField(player->jx, player->jy);
4856 PlaySound(SND_GAME_WINNING);
4859 if (game_over_delay_1 > 0)
4861 game_over_delay_1--;
4866 if (time != time_final)
4868 int time_to_go = ABS(time_final - time);
4869 int time_count_dir = (time < time_final ? +1 : -1);
4871 if (time_to_go < time_count_steps)
4872 time_count_steps = 1;
4874 time += time_count_steps * time_count_dir;
4875 score += time_count_steps * time_score;
4877 // set final score to correct rounding differences after counting score
4878 if (time == time_final)
4879 score = score_final;
4881 game.LevelSolved_CountingTime = time;
4882 game.LevelSolved_CountingScore = score;
4884 game_panel_controls[GAME_PANEL_TIME].value = time;
4885 game_panel_controls[GAME_PANEL_SCORE].value = score;
4887 DisplayGameControlValues();
4889 if (time == time_final)
4890 StopSound(SND_GAME_LEVELTIME_BONUS);
4891 else if (setup.sound_loops)
4892 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4894 PlaySound(SND_GAME_LEVELTIME_BONUS);
4899 if (game_over_delay_2 > 0)
4901 game_over_delay_2--;
4906 if (health != health_final)
4908 int health_count_dir = (health < health_final ? +1 : -1);
4910 health += health_count_dir;
4911 score += time_score;
4913 game.LevelSolved_CountingHealth = health;
4914 game.LevelSolved_CountingScore = score;
4916 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4917 game_panel_controls[GAME_PANEL_SCORE].value = score;
4919 DisplayGameControlValues();
4921 if (health == health_final)
4922 StopSound(SND_GAME_LEVELTIME_BONUS);
4923 else if (setup.sound_loops)
4924 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4926 PlaySound(SND_GAME_LEVELTIME_BONUS);
4931 game.panel.active = FALSE;
4933 if (game_over_delay_3 > 0)
4935 game_over_delay_3--;
4945 // used instead of "level_nr" (needed for network games)
4946 int last_level_nr = levelset.level_nr;
4949 game.LevelSolved_GameEnd = TRUE;
4951 if (game.LevelSolved_SaveTape)
4953 // make sure that request dialog to save tape does not open door again
4954 if (!global.use_envelope_request)
4955 CloseDoor(DOOR_CLOSE_1);
4957 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4960 // if no tape is to be saved, close both doors simultaneously
4961 CloseDoor(DOOR_CLOSE_ALL);
4963 if (level_editor_test_game)
4965 SetGameStatus(GAME_MODE_MAIN);
4972 if (!game.LevelSolved_SaveScore)
4974 SetGameStatus(GAME_MODE_MAIN);
4981 if (level_nr == leveldir_current->handicap_level)
4983 leveldir_current->handicap_level++;
4985 SaveLevelSetup_SeriesInfo();
4988 if (setup.increment_levels &&
4989 level_nr < leveldir_current->last_level &&
4992 level_nr++; // advance to next level
4993 TapeErase(); // start with empty tape
4995 if (setup.auto_play_next_level)
4997 LoadLevel(level_nr);
4999 SaveLevelSetup_SeriesInfo();
5003 hi_pos = NewHiScore(last_level_nr);
5005 if (hi_pos >= 0 && !setup.skip_scores_after_game)
5007 SetGameStatus(GAME_MODE_SCORES);
5009 DrawHallOfFame(last_level_nr, hi_pos);
5011 else if (setup.auto_play_next_level && setup.increment_levels &&
5012 last_level_nr < leveldir_current->last_level &&
5015 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5019 SetGameStatus(GAME_MODE_MAIN);
5025 int NewHiScore(int level_nr)
5029 boolean one_score_entry_per_name = !program.many_scores_per_name;
5031 LoadScore(level_nr);
5033 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
5034 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
5037 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
5039 if (game.score_final > highscore[k].Score)
5041 // player has made it to the hall of fame
5043 if (k < MAX_SCORE_ENTRIES - 1)
5045 int m = MAX_SCORE_ENTRIES - 1;
5047 if (one_score_entry_per_name)
5049 for (l = k; l < MAX_SCORE_ENTRIES; l++)
5050 if (strEqual(setup.player_name, highscore[l].Name))
5053 if (m == k) // player's new highscore overwrites his old one
5057 for (l = m; l > k; l--)
5059 strcpy(highscore[l].Name, highscore[l - 1].Name);
5060 highscore[l].Score = highscore[l - 1].Score;
5066 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
5067 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
5068 highscore[k].Score = game.score_final;
5073 else if (one_score_entry_per_name &&
5074 !strncmp(setup.player_name, highscore[k].Name,
5075 MAX_PLAYER_NAME_LEN))
5076 break; // player already there with a higher score
5080 SaveScore(level_nr);
5085 static int getElementMoveStepsizeExt(int x, int y, int direction)
5087 int element = Tile[x][y];
5088 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5089 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5090 int horiz_move = (dx != 0);
5091 int sign = (horiz_move ? dx : dy);
5092 int step = sign * element_info[element].move_stepsize;
5094 // special values for move stepsize for spring and things on conveyor belt
5097 if (CAN_FALL(element) &&
5098 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5099 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5100 else if (element == EL_SPRING)
5101 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5107 static int getElementMoveStepsize(int x, int y)
5109 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5112 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5114 if (player->GfxAction != action || player->GfxDir != dir)
5116 player->GfxAction = action;
5117 player->GfxDir = dir;
5119 player->StepFrame = 0;
5123 static void ResetGfxFrame(int x, int y)
5125 // profiling showed that "autotest" spends 10~20% of its time in this function
5126 if (DrawingDeactivatedField())
5129 int element = Tile[x][y];
5130 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5132 if (graphic_info[graphic].anim_global_sync)
5133 GfxFrame[x][y] = FrameCounter;
5134 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5135 GfxFrame[x][y] = CustomValue[x][y];
5136 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5137 GfxFrame[x][y] = element_info[element].collect_score;
5138 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5139 GfxFrame[x][y] = ChangeDelay[x][y];
5142 static void ResetGfxAnimation(int x, int y)
5144 GfxAction[x][y] = ACTION_DEFAULT;
5145 GfxDir[x][y] = MovDir[x][y];
5148 ResetGfxFrame(x, y);
5151 static void ResetRandomAnimationValue(int x, int y)
5153 GfxRandom[x][y] = INIT_GFX_RANDOM();
5156 static void InitMovingField(int x, int y, int direction)
5158 int element = Tile[x][y];
5159 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5160 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5163 boolean is_moving_before, is_moving_after;
5165 // check if element was/is moving or being moved before/after mode change
5166 is_moving_before = (WasJustMoving[x][y] != 0);
5167 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5169 // reset animation only for moving elements which change direction of moving
5170 // or which just started or stopped moving
5171 // (else CEs with property "can move" / "not moving" are reset each frame)
5172 if (is_moving_before != is_moving_after ||
5173 direction != MovDir[x][y])
5174 ResetGfxAnimation(x, y);
5176 MovDir[x][y] = direction;
5177 GfxDir[x][y] = direction;
5179 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5180 direction == MV_DOWN && CAN_FALL(element) ?
5181 ACTION_FALLING : ACTION_MOVING);
5183 // this is needed for CEs with property "can move" / "not moving"
5185 if (is_moving_after)
5187 if (Tile[newx][newy] == EL_EMPTY)
5188 Tile[newx][newy] = EL_BLOCKED;
5190 MovDir[newx][newy] = MovDir[x][y];
5192 CustomValue[newx][newy] = CustomValue[x][y];
5194 GfxFrame[newx][newy] = GfxFrame[x][y];
5195 GfxRandom[newx][newy] = GfxRandom[x][y];
5196 GfxAction[newx][newy] = GfxAction[x][y];
5197 GfxDir[newx][newy] = GfxDir[x][y];
5201 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5203 int direction = MovDir[x][y];
5204 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5205 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5211 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5213 int oldx = x, oldy = y;
5214 int direction = MovDir[x][y];
5216 if (direction == MV_LEFT)
5218 else if (direction == MV_RIGHT)
5220 else if (direction == MV_UP)
5222 else if (direction == MV_DOWN)
5225 *comes_from_x = oldx;
5226 *comes_from_y = oldy;
5229 static int MovingOrBlocked2Element(int x, int y)
5231 int element = Tile[x][y];
5233 if (element == EL_BLOCKED)
5237 Blocked2Moving(x, y, &oldx, &oldy);
5238 return Tile[oldx][oldy];
5244 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5246 // like MovingOrBlocked2Element(), but if element is moving
5247 // and (x,y) is the field the moving element is just leaving,
5248 // return EL_BLOCKED instead of the element value
5249 int element = Tile[x][y];
5251 if (IS_MOVING(x, y))
5253 if (element == EL_BLOCKED)
5257 Blocked2Moving(x, y, &oldx, &oldy);
5258 return Tile[oldx][oldy];
5267 static void RemoveField(int x, int y)
5269 Tile[x][y] = EL_EMPTY;
5275 CustomValue[x][y] = 0;
5278 ChangeDelay[x][y] = 0;
5279 ChangePage[x][y] = -1;
5280 Pushed[x][y] = FALSE;
5282 GfxElement[x][y] = EL_UNDEFINED;
5283 GfxAction[x][y] = ACTION_DEFAULT;
5284 GfxDir[x][y] = MV_NONE;
5287 static void RemoveMovingField(int x, int y)
5289 int oldx = x, oldy = y, newx = x, newy = y;
5290 int element = Tile[x][y];
5291 int next_element = EL_UNDEFINED;
5293 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5296 if (IS_MOVING(x, y))
5298 Moving2Blocked(x, y, &newx, &newy);
5300 if (Tile[newx][newy] != EL_BLOCKED)
5302 // element is moving, but target field is not free (blocked), but
5303 // already occupied by something different (example: acid pool);
5304 // in this case, only remove the moving field, but not the target
5306 RemoveField(oldx, oldy);
5308 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5310 TEST_DrawLevelField(oldx, oldy);
5315 else if (element == EL_BLOCKED)
5317 Blocked2Moving(x, y, &oldx, &oldy);
5318 if (!IS_MOVING(oldx, oldy))
5322 if (element == EL_BLOCKED &&
5323 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5324 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5325 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5326 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5327 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5328 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5329 next_element = get_next_element(Tile[oldx][oldy]);
5331 RemoveField(oldx, oldy);
5332 RemoveField(newx, newy);
5334 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5336 if (next_element != EL_UNDEFINED)
5337 Tile[oldx][oldy] = next_element;
5339 TEST_DrawLevelField(oldx, oldy);
5340 TEST_DrawLevelField(newx, newy);
5343 void DrawDynamite(int x, int y)
5345 int sx = SCREENX(x), sy = SCREENY(y);
5346 int graphic = el2img(Tile[x][y]);
5349 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5352 if (IS_WALKABLE_INSIDE(Back[x][y]))
5356 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5357 else if (Store[x][y])
5358 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5360 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5362 if (Back[x][y] || Store[x][y])
5363 DrawGraphicThruMask(sx, sy, graphic, frame);
5365 DrawGraphic(sx, sy, graphic, frame);
5368 static void CheckDynamite(int x, int y)
5370 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5374 if (MovDelay[x][y] != 0)
5377 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5383 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5388 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5390 boolean num_checked_players = 0;
5393 for (i = 0; i < MAX_PLAYERS; i++)
5395 if (stored_player[i].active)
5397 int sx = stored_player[i].jx;
5398 int sy = stored_player[i].jy;
5400 if (num_checked_players == 0)
5407 *sx1 = MIN(*sx1, sx);
5408 *sy1 = MIN(*sy1, sy);
5409 *sx2 = MAX(*sx2, sx);
5410 *sy2 = MAX(*sy2, sy);
5413 num_checked_players++;
5418 static boolean checkIfAllPlayersFitToScreen_RND(void)
5420 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5422 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5424 return (sx2 - sx1 < SCR_FIELDX &&
5425 sy2 - sy1 < SCR_FIELDY);
5428 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5430 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5432 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5434 *sx = (sx1 + sx2) / 2;
5435 *sy = (sy1 + sy2) / 2;
5438 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5439 boolean center_screen, boolean quick_relocation)
5441 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5442 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5443 boolean no_delay = (tape.warp_forward);
5444 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5445 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5446 int new_scroll_x, new_scroll_y;
5448 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5450 // case 1: quick relocation inside visible screen (without scrolling)
5457 if (!level.shifted_relocation || center_screen)
5459 // relocation _with_ centering of screen
5461 new_scroll_x = SCROLL_POSITION_X(x);
5462 new_scroll_y = SCROLL_POSITION_Y(y);
5466 // relocation _without_ centering of screen
5468 int center_scroll_x = SCROLL_POSITION_X(old_x);
5469 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5470 int offset_x = x + (scroll_x - center_scroll_x);
5471 int offset_y = y + (scroll_y - center_scroll_y);
5473 // for new screen position, apply previous offset to center position
5474 new_scroll_x = SCROLL_POSITION_X(offset_x);
5475 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5478 if (quick_relocation)
5480 // case 2: quick relocation (redraw without visible scrolling)
5482 scroll_x = new_scroll_x;
5483 scroll_y = new_scroll_y;
5490 // case 3: visible relocation (with scrolling to new position)
5492 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5494 SetVideoFrameDelay(wait_delay_value);
5496 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5498 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5499 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5501 if (dx == 0 && dy == 0) // no scrolling needed at all
5507 // set values for horizontal/vertical screen scrolling (half tile size)
5508 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5509 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5510 int pos_x = dx * TILEX / 2;
5511 int pos_y = dy * TILEY / 2;
5512 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5513 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5515 ScrollLevel(dx, dy);
5518 // scroll in two steps of half tile size to make things smoother
5519 BlitScreenToBitmapExt_RND(window, fx, fy);
5521 // scroll second step to align at full tile size
5522 BlitScreenToBitmap(window);
5528 SetVideoFrameDelay(frame_delay_value_old);
5531 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5533 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5534 int player_nr = GET_PLAYER_NR(el_player);
5535 struct PlayerInfo *player = &stored_player[player_nr];
5536 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5537 boolean no_delay = (tape.warp_forward);
5538 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5539 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5540 int old_jx = player->jx;
5541 int old_jy = player->jy;
5542 int old_element = Tile[old_jx][old_jy];
5543 int element = Tile[jx][jy];
5544 boolean player_relocated = (old_jx != jx || old_jy != jy);
5546 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5547 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5548 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5549 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5550 int leave_side_horiz = move_dir_horiz;
5551 int leave_side_vert = move_dir_vert;
5552 int enter_side = enter_side_horiz | enter_side_vert;
5553 int leave_side = leave_side_horiz | leave_side_vert;
5555 if (player->buried) // do not reanimate dead player
5558 if (!player_relocated) // no need to relocate the player
5561 if (IS_PLAYER(jx, jy)) // player already placed at new position
5563 RemoveField(jx, jy); // temporarily remove newly placed player
5564 DrawLevelField(jx, jy);
5567 if (player->present)
5569 while (player->MovPos)
5571 ScrollPlayer(player, SCROLL_GO_ON);
5572 ScrollScreen(NULL, SCROLL_GO_ON);
5574 AdvanceFrameAndPlayerCounters(player->index_nr);
5578 BackToFront_WithFrameDelay(wait_delay_value);
5581 DrawPlayer(player); // needed here only to cleanup last field
5582 DrawLevelField(player->jx, player->jy); // remove player graphic
5584 player->is_moving = FALSE;
5587 if (IS_CUSTOM_ELEMENT(old_element))
5588 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5590 player->index_bit, leave_side);
5592 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5594 player->index_bit, leave_side);
5596 Tile[jx][jy] = el_player;
5597 InitPlayerField(jx, jy, el_player, TRUE);
5599 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5600 possible that the relocation target field did not contain a player element,
5601 but a walkable element, to which the new player was relocated -- in this
5602 case, restore that (already initialized!) element on the player field */
5603 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5605 Tile[jx][jy] = element; // restore previously existing element
5608 // only visually relocate centered player
5609 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5610 FALSE, level.instant_relocation);
5612 TestIfPlayerTouchesBadThing(jx, jy);
5613 TestIfPlayerTouchesCustomElement(jx, jy);
5615 if (IS_CUSTOM_ELEMENT(element))
5616 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5617 player->index_bit, enter_side);
5619 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5620 player->index_bit, enter_side);
5622 if (player->is_switching)
5624 /* ensure that relocation while still switching an element does not cause
5625 a new element to be treated as also switched directly after relocation
5626 (this is important for teleporter switches that teleport the player to
5627 a place where another teleporter switch is in the same direction, which
5628 would then incorrectly be treated as immediately switched before the
5629 direction key that caused the switch was released) */
5631 player->switch_x += jx - old_jx;
5632 player->switch_y += jy - old_jy;
5636 static void Explode(int ex, int ey, int phase, int mode)
5642 // !!! eliminate this variable !!!
5643 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5645 if (game.explosions_delayed)
5647 ExplodeField[ex][ey] = mode;
5651 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5653 int center_element = Tile[ex][ey];
5654 int artwork_element, explosion_element; // set these values later
5656 // remove things displayed in background while burning dynamite
5657 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5660 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5662 // put moving element to center field (and let it explode there)
5663 center_element = MovingOrBlocked2Element(ex, ey);
5664 RemoveMovingField(ex, ey);
5665 Tile[ex][ey] = center_element;
5668 // now "center_element" is finally determined -- set related values now
5669 artwork_element = center_element; // for custom player artwork
5670 explosion_element = center_element; // for custom player artwork
5672 if (IS_PLAYER(ex, ey))
5674 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5676 artwork_element = stored_player[player_nr].artwork_element;
5678 if (level.use_explosion_element[player_nr])
5680 explosion_element = level.explosion_element[player_nr];
5681 artwork_element = explosion_element;
5685 if (mode == EX_TYPE_NORMAL ||
5686 mode == EX_TYPE_CENTER ||
5687 mode == EX_TYPE_CROSS)
5688 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5690 last_phase = element_info[explosion_element].explosion_delay + 1;
5692 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5694 int xx = x - ex + 1;
5695 int yy = y - ey + 1;
5698 if (!IN_LEV_FIELD(x, y) ||
5699 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5700 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5703 element = Tile[x][y];
5705 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5707 element = MovingOrBlocked2Element(x, y);
5709 if (!IS_EXPLOSION_PROOF(element))
5710 RemoveMovingField(x, y);
5713 // indestructible elements can only explode in center (but not flames)
5714 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5715 mode == EX_TYPE_BORDER)) ||
5716 element == EL_FLAMES)
5719 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5720 behaviour, for example when touching a yamyam that explodes to rocks
5721 with active deadly shield, a rock is created under the player !!! */
5722 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5724 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5725 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5726 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5728 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5731 if (IS_ACTIVE_BOMB(element))
5733 // re-activate things under the bomb like gate or penguin
5734 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5741 // save walkable background elements while explosion on same tile
5742 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5743 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5744 Back[x][y] = element;
5746 // ignite explodable elements reached by other explosion
5747 if (element == EL_EXPLOSION)
5748 element = Store2[x][y];
5750 if (AmoebaNr[x][y] &&
5751 (element == EL_AMOEBA_FULL ||
5752 element == EL_BD_AMOEBA ||
5753 element == EL_AMOEBA_GROWING))
5755 AmoebaCnt[AmoebaNr[x][y]]--;
5756 AmoebaCnt2[AmoebaNr[x][y]]--;
5761 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5763 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5765 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5767 if (PLAYERINFO(ex, ey)->use_murphy)
5768 Store[x][y] = EL_EMPTY;
5771 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5772 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5773 else if (ELEM_IS_PLAYER(center_element))
5774 Store[x][y] = EL_EMPTY;
5775 else if (center_element == EL_YAMYAM)
5776 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5777 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5778 Store[x][y] = element_info[center_element].content.e[xx][yy];
5780 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5781 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5782 // otherwise) -- FIX THIS !!!
5783 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5784 Store[x][y] = element_info[element].content.e[1][1];
5786 else if (!CAN_EXPLODE(element))
5787 Store[x][y] = element_info[element].content.e[1][1];
5790 Store[x][y] = EL_EMPTY;
5792 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5793 center_element == EL_AMOEBA_TO_DIAMOND)
5794 Store2[x][y] = element;
5796 Tile[x][y] = EL_EXPLOSION;
5797 GfxElement[x][y] = artwork_element;
5799 ExplodePhase[x][y] = 1;
5800 ExplodeDelay[x][y] = last_phase;
5805 if (center_element == EL_YAMYAM)
5806 game.yamyam_content_nr =
5807 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5819 GfxFrame[x][y] = 0; // restart explosion animation
5821 last_phase = ExplodeDelay[x][y];
5823 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5825 // this can happen if the player leaves an explosion just in time
5826 if (GfxElement[x][y] == EL_UNDEFINED)
5827 GfxElement[x][y] = EL_EMPTY;
5829 border_element = Store2[x][y];
5830 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5831 border_element = StorePlayer[x][y];
5833 if (phase == element_info[border_element].ignition_delay ||
5834 phase == last_phase)
5836 boolean border_explosion = FALSE;
5838 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5839 !PLAYER_EXPLOSION_PROTECTED(x, y))
5841 KillPlayerUnlessExplosionProtected(x, y);
5842 border_explosion = TRUE;
5844 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5846 Tile[x][y] = Store2[x][y];
5849 border_explosion = TRUE;
5851 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5853 AmoebaToDiamond(x, y);
5855 border_explosion = TRUE;
5858 // if an element just explodes due to another explosion (chain-reaction),
5859 // do not immediately end the new explosion when it was the last frame of
5860 // the explosion (as it would be done in the following "if"-statement!)
5861 if (border_explosion && phase == last_phase)
5865 if (phase == last_phase)
5869 element = Tile[x][y] = Store[x][y];
5870 Store[x][y] = Store2[x][y] = 0;
5871 GfxElement[x][y] = EL_UNDEFINED;
5873 // player can escape from explosions and might therefore be still alive
5874 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5875 element <= EL_PLAYER_IS_EXPLODING_4)
5877 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5878 int explosion_element = EL_PLAYER_1 + player_nr;
5879 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5880 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5882 if (level.use_explosion_element[player_nr])
5883 explosion_element = level.explosion_element[player_nr];
5885 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5886 element_info[explosion_element].content.e[xx][yy]);
5889 // restore probably existing indestructible background element
5890 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5891 element = Tile[x][y] = Back[x][y];
5894 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5895 GfxDir[x][y] = MV_NONE;
5896 ChangeDelay[x][y] = 0;
5897 ChangePage[x][y] = -1;
5899 CustomValue[x][y] = 0;
5901 InitField_WithBug2(x, y, FALSE);
5903 TEST_DrawLevelField(x, y);
5905 TestIfElementTouchesCustomElement(x, y);
5907 if (GFX_CRUMBLED(element))
5908 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5910 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5911 StorePlayer[x][y] = 0;
5913 if (ELEM_IS_PLAYER(element))
5914 RelocatePlayer(x, y, element);
5916 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5918 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5919 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5922 TEST_DrawLevelFieldCrumbled(x, y);
5924 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5926 DrawLevelElement(x, y, Back[x][y]);
5927 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5929 else if (IS_WALKABLE_UNDER(Back[x][y]))
5931 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5932 DrawLevelElementThruMask(x, y, Back[x][y]);
5934 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5935 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5939 static void DynaExplode(int ex, int ey)
5942 int dynabomb_element = Tile[ex][ey];
5943 int dynabomb_size = 1;
5944 boolean dynabomb_xl = FALSE;
5945 struct PlayerInfo *player;
5946 static int xy[4][2] =
5954 if (IS_ACTIVE_BOMB(dynabomb_element))
5956 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5957 dynabomb_size = player->dynabomb_size;
5958 dynabomb_xl = player->dynabomb_xl;
5959 player->dynabombs_left++;
5962 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5964 for (i = 0; i < NUM_DIRECTIONS; i++)
5966 for (j = 1; j <= dynabomb_size; j++)
5968 int x = ex + j * xy[i][0];
5969 int y = ey + j * xy[i][1];
5972 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5975 element = Tile[x][y];
5977 // do not restart explosions of fields with active bombs
5978 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5981 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5983 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5984 !IS_DIGGABLE(element) && !dynabomb_xl)
5990 void Bang(int x, int y)
5992 int element = MovingOrBlocked2Element(x, y);
5993 int explosion_type = EX_TYPE_NORMAL;
5995 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5997 struct PlayerInfo *player = PLAYERINFO(x, y);
5999 element = Tile[x][y] = player->initial_element;
6001 if (level.use_explosion_element[player->index_nr])
6003 int explosion_element = level.explosion_element[player->index_nr];
6005 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6006 explosion_type = EX_TYPE_CROSS;
6007 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6008 explosion_type = EX_TYPE_CENTER;
6016 case EL_BD_BUTTERFLY:
6019 case EL_DARK_YAMYAM:
6023 RaiseScoreElement(element);
6026 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6027 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6028 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6029 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6030 case EL_DYNABOMB_INCREASE_NUMBER:
6031 case EL_DYNABOMB_INCREASE_SIZE:
6032 case EL_DYNABOMB_INCREASE_POWER:
6033 explosion_type = EX_TYPE_DYNA;
6036 case EL_DC_LANDMINE:
6037 explosion_type = EX_TYPE_CENTER;
6042 case EL_LAMP_ACTIVE:
6043 case EL_AMOEBA_TO_DIAMOND:
6044 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6045 explosion_type = EX_TYPE_CENTER;
6049 if (element_info[element].explosion_type == EXPLODES_CROSS)
6050 explosion_type = EX_TYPE_CROSS;
6051 else if (element_info[element].explosion_type == EXPLODES_1X1)
6052 explosion_type = EX_TYPE_CENTER;
6056 if (explosion_type == EX_TYPE_DYNA)
6059 Explode(x, y, EX_PHASE_START, explosion_type);
6061 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6064 static void SplashAcid(int x, int y)
6066 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6067 (!IN_LEV_FIELD(x - 1, y - 2) ||
6068 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6069 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6071 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6072 (!IN_LEV_FIELD(x + 1, y - 2) ||
6073 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6074 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6076 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6079 static void InitBeltMovement(void)
6081 static int belt_base_element[4] =
6083 EL_CONVEYOR_BELT_1_LEFT,
6084 EL_CONVEYOR_BELT_2_LEFT,
6085 EL_CONVEYOR_BELT_3_LEFT,
6086 EL_CONVEYOR_BELT_4_LEFT
6088 static int belt_base_active_element[4] =
6090 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6091 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6092 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6093 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6098 // set frame order for belt animation graphic according to belt direction
6099 for (i = 0; i < NUM_BELTS; i++)
6103 for (j = 0; j < NUM_BELT_PARTS; j++)
6105 int element = belt_base_active_element[belt_nr] + j;
6106 int graphic_1 = el2img(element);
6107 int graphic_2 = el2panelimg(element);
6109 if (game.belt_dir[i] == MV_LEFT)
6111 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6112 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6116 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6117 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6122 SCAN_PLAYFIELD(x, y)
6124 int element = Tile[x][y];
6126 for (i = 0; i < NUM_BELTS; i++)
6128 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6130 int e_belt_nr = getBeltNrFromBeltElement(element);
6133 if (e_belt_nr == belt_nr)
6135 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6137 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6144 static void ToggleBeltSwitch(int x, int y)
6146 static int belt_base_element[4] =
6148 EL_CONVEYOR_BELT_1_LEFT,
6149 EL_CONVEYOR_BELT_2_LEFT,
6150 EL_CONVEYOR_BELT_3_LEFT,
6151 EL_CONVEYOR_BELT_4_LEFT
6153 static int belt_base_active_element[4] =
6155 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6156 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6157 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6158 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6160 static int belt_base_switch_element[4] =
6162 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6163 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6164 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6165 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6167 static int belt_move_dir[4] =
6175 int element = Tile[x][y];
6176 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6177 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6178 int belt_dir = belt_move_dir[belt_dir_nr];
6181 if (!IS_BELT_SWITCH(element))
6184 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6185 game.belt_dir[belt_nr] = belt_dir;
6187 if (belt_dir_nr == 3)
6190 // set frame order for belt animation graphic according to belt direction
6191 for (i = 0; i < NUM_BELT_PARTS; i++)
6193 int element = belt_base_active_element[belt_nr] + i;
6194 int graphic_1 = el2img(element);
6195 int graphic_2 = el2panelimg(element);
6197 if (belt_dir == MV_LEFT)
6199 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6200 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6204 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6205 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6209 SCAN_PLAYFIELD(xx, yy)
6211 int element = Tile[xx][yy];
6213 if (IS_BELT_SWITCH(element))
6215 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6217 if (e_belt_nr == belt_nr)
6219 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6220 TEST_DrawLevelField(xx, yy);
6223 else if (IS_BELT(element) && belt_dir != MV_NONE)
6225 int e_belt_nr = getBeltNrFromBeltElement(element);
6227 if (e_belt_nr == belt_nr)
6229 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6231 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6232 TEST_DrawLevelField(xx, yy);
6235 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6237 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6239 if (e_belt_nr == belt_nr)
6241 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6243 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6244 TEST_DrawLevelField(xx, yy);
6250 static void ToggleSwitchgateSwitch(int x, int y)
6254 game.switchgate_pos = !game.switchgate_pos;
6256 SCAN_PLAYFIELD(xx, yy)
6258 int element = Tile[xx][yy];
6260 if (element == EL_SWITCHGATE_SWITCH_UP)
6262 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6263 TEST_DrawLevelField(xx, yy);
6265 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6267 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6268 TEST_DrawLevelField(xx, yy);
6270 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6272 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6273 TEST_DrawLevelField(xx, yy);
6275 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6277 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6278 TEST_DrawLevelField(xx, yy);
6280 else if (element == EL_SWITCHGATE_OPEN ||
6281 element == EL_SWITCHGATE_OPENING)
6283 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6285 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6287 else if (element == EL_SWITCHGATE_CLOSED ||
6288 element == EL_SWITCHGATE_CLOSING)
6290 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6292 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6297 static int getInvisibleActiveFromInvisibleElement(int element)
6299 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6300 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6301 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6305 static int getInvisibleFromInvisibleActiveElement(int element)
6307 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6308 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6309 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6313 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6317 SCAN_PLAYFIELD(x, y)
6319 int element = Tile[x][y];
6321 if (element == EL_LIGHT_SWITCH &&
6322 game.light_time_left > 0)
6324 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6325 TEST_DrawLevelField(x, y);
6327 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6328 game.light_time_left == 0)
6330 Tile[x][y] = EL_LIGHT_SWITCH;
6331 TEST_DrawLevelField(x, y);
6333 else if (element == EL_EMC_DRIPPER &&
6334 game.light_time_left > 0)
6336 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6337 TEST_DrawLevelField(x, y);
6339 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6340 game.light_time_left == 0)
6342 Tile[x][y] = EL_EMC_DRIPPER;
6343 TEST_DrawLevelField(x, y);
6345 else if (element == EL_INVISIBLE_STEELWALL ||
6346 element == EL_INVISIBLE_WALL ||
6347 element == EL_INVISIBLE_SAND)
6349 if (game.light_time_left > 0)
6350 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6352 TEST_DrawLevelField(x, y);
6354 // uncrumble neighbour fields, if needed
6355 if (element == EL_INVISIBLE_SAND)
6356 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6358 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6359 element == EL_INVISIBLE_WALL_ACTIVE ||
6360 element == EL_INVISIBLE_SAND_ACTIVE)
6362 if (game.light_time_left == 0)
6363 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6365 TEST_DrawLevelField(x, y);
6367 // re-crumble neighbour fields, if needed
6368 if (element == EL_INVISIBLE_SAND)
6369 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6374 static void RedrawAllInvisibleElementsForLenses(void)
6378 SCAN_PLAYFIELD(x, y)
6380 int element = Tile[x][y];
6382 if (element == EL_EMC_DRIPPER &&
6383 game.lenses_time_left > 0)
6385 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6386 TEST_DrawLevelField(x, y);
6388 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6389 game.lenses_time_left == 0)
6391 Tile[x][y] = EL_EMC_DRIPPER;
6392 TEST_DrawLevelField(x, y);
6394 else if (element == EL_INVISIBLE_STEELWALL ||
6395 element == EL_INVISIBLE_WALL ||
6396 element == EL_INVISIBLE_SAND)
6398 if (game.lenses_time_left > 0)
6399 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6401 TEST_DrawLevelField(x, y);
6403 // uncrumble neighbour fields, if needed
6404 if (element == EL_INVISIBLE_SAND)
6405 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6407 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6408 element == EL_INVISIBLE_WALL_ACTIVE ||
6409 element == EL_INVISIBLE_SAND_ACTIVE)
6411 if (game.lenses_time_left == 0)
6412 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6414 TEST_DrawLevelField(x, y);
6416 // re-crumble neighbour fields, if needed
6417 if (element == EL_INVISIBLE_SAND)
6418 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6423 static void RedrawAllInvisibleElementsForMagnifier(void)
6427 SCAN_PLAYFIELD(x, y)
6429 int element = Tile[x][y];
6431 if (element == EL_EMC_FAKE_GRASS &&
6432 game.magnify_time_left > 0)
6434 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6435 TEST_DrawLevelField(x, y);
6437 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6438 game.magnify_time_left == 0)
6440 Tile[x][y] = EL_EMC_FAKE_GRASS;
6441 TEST_DrawLevelField(x, y);
6443 else if (IS_GATE_GRAY(element) &&
6444 game.magnify_time_left > 0)
6446 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6447 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6448 IS_EM_GATE_GRAY(element) ?
6449 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6450 IS_EMC_GATE_GRAY(element) ?
6451 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6452 IS_DC_GATE_GRAY(element) ?
6453 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6455 TEST_DrawLevelField(x, y);
6457 else if (IS_GATE_GRAY_ACTIVE(element) &&
6458 game.magnify_time_left == 0)
6460 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6461 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6462 IS_EM_GATE_GRAY_ACTIVE(element) ?
6463 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6464 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6465 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6466 IS_DC_GATE_GRAY_ACTIVE(element) ?
6467 EL_DC_GATE_WHITE_GRAY :
6469 TEST_DrawLevelField(x, y);
6474 static void ToggleLightSwitch(int x, int y)
6476 int element = Tile[x][y];
6478 game.light_time_left =
6479 (element == EL_LIGHT_SWITCH ?
6480 level.time_light * FRAMES_PER_SECOND : 0);
6482 RedrawAllLightSwitchesAndInvisibleElements();
6485 static void ActivateTimegateSwitch(int x, int y)
6489 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6491 SCAN_PLAYFIELD(xx, yy)
6493 int element = Tile[xx][yy];
6495 if (element == EL_TIMEGATE_CLOSED ||
6496 element == EL_TIMEGATE_CLOSING)
6498 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6499 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6503 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6505 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6506 TEST_DrawLevelField(xx, yy);
6512 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6513 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6516 static void Impact(int x, int y)
6518 boolean last_line = (y == lev_fieldy - 1);
6519 boolean object_hit = FALSE;
6520 boolean impact = (last_line || object_hit);
6521 int element = Tile[x][y];
6522 int smashed = EL_STEELWALL;
6524 if (!last_line) // check if element below was hit
6526 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6529 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6530 MovDir[x][y + 1] != MV_DOWN ||
6531 MovPos[x][y + 1] <= TILEY / 2));
6533 // do not smash moving elements that left the smashed field in time
6534 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6535 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6538 #if USE_QUICKSAND_IMPACT_BUGFIX
6539 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6541 RemoveMovingField(x, y + 1);
6542 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6543 Tile[x][y + 2] = EL_ROCK;
6544 TEST_DrawLevelField(x, y + 2);
6549 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6551 RemoveMovingField(x, y + 1);
6552 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6553 Tile[x][y + 2] = EL_ROCK;
6554 TEST_DrawLevelField(x, y + 2);
6561 smashed = MovingOrBlocked2Element(x, y + 1);
6563 impact = (last_line || object_hit);
6566 if (!last_line && smashed == EL_ACID) // element falls into acid
6568 SplashAcid(x, y + 1);
6572 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6573 // only reset graphic animation if graphic really changes after impact
6575 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6577 ResetGfxAnimation(x, y);
6578 TEST_DrawLevelField(x, y);
6581 if (impact && CAN_EXPLODE_IMPACT(element))
6586 else if (impact && element == EL_PEARL &&
6587 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6589 ResetGfxAnimation(x, y);
6591 Tile[x][y] = EL_PEARL_BREAKING;
6592 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6595 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6597 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6602 if (impact && element == EL_AMOEBA_DROP)
6604 if (object_hit && IS_PLAYER(x, y + 1))
6605 KillPlayerUnlessEnemyProtected(x, y + 1);
6606 else if (object_hit && smashed == EL_PENGUIN)
6610 Tile[x][y] = EL_AMOEBA_GROWING;
6611 Store[x][y] = EL_AMOEBA_WET;
6613 ResetRandomAnimationValue(x, y);
6618 if (object_hit) // check which object was hit
6620 if ((CAN_PASS_MAGIC_WALL(element) &&
6621 (smashed == EL_MAGIC_WALL ||
6622 smashed == EL_BD_MAGIC_WALL)) ||
6623 (CAN_PASS_DC_MAGIC_WALL(element) &&
6624 smashed == EL_DC_MAGIC_WALL))
6627 int activated_magic_wall =
6628 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6629 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6630 EL_DC_MAGIC_WALL_ACTIVE);
6632 // activate magic wall / mill
6633 SCAN_PLAYFIELD(xx, yy)
6635 if (Tile[xx][yy] == smashed)
6636 Tile[xx][yy] = activated_magic_wall;
6639 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6640 game.magic_wall_active = TRUE;
6642 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6643 SND_MAGIC_WALL_ACTIVATING :
6644 smashed == EL_BD_MAGIC_WALL ?
6645 SND_BD_MAGIC_WALL_ACTIVATING :
6646 SND_DC_MAGIC_WALL_ACTIVATING));
6649 if (IS_PLAYER(x, y + 1))
6651 if (CAN_SMASH_PLAYER(element))
6653 KillPlayerUnlessEnemyProtected(x, y + 1);
6657 else if (smashed == EL_PENGUIN)
6659 if (CAN_SMASH_PLAYER(element))
6665 else if (element == EL_BD_DIAMOND)
6667 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6673 else if (((element == EL_SP_INFOTRON ||
6674 element == EL_SP_ZONK) &&
6675 (smashed == EL_SP_SNIKSNAK ||
6676 smashed == EL_SP_ELECTRON ||
6677 smashed == EL_SP_DISK_ORANGE)) ||
6678 (element == EL_SP_INFOTRON &&
6679 smashed == EL_SP_DISK_YELLOW))
6684 else if (CAN_SMASH_EVERYTHING(element))
6686 if (IS_CLASSIC_ENEMY(smashed) ||
6687 CAN_EXPLODE_SMASHED(smashed))
6692 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6694 if (smashed == EL_LAMP ||
6695 smashed == EL_LAMP_ACTIVE)
6700 else if (smashed == EL_NUT)
6702 Tile[x][y + 1] = EL_NUT_BREAKING;
6703 PlayLevelSound(x, y, SND_NUT_BREAKING);
6704 RaiseScoreElement(EL_NUT);
6707 else if (smashed == EL_PEARL)
6709 ResetGfxAnimation(x, y);
6711 Tile[x][y + 1] = EL_PEARL_BREAKING;
6712 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6715 else if (smashed == EL_DIAMOND)
6717 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6718 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6721 else if (IS_BELT_SWITCH(smashed))
6723 ToggleBeltSwitch(x, y + 1);
6725 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6726 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6727 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6728 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6730 ToggleSwitchgateSwitch(x, y + 1);
6732 else if (smashed == EL_LIGHT_SWITCH ||
6733 smashed == EL_LIGHT_SWITCH_ACTIVE)
6735 ToggleLightSwitch(x, y + 1);
6739 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6741 CheckElementChangeBySide(x, y + 1, smashed, element,
6742 CE_SWITCHED, CH_SIDE_TOP);
6743 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6749 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6754 // play sound of magic wall / mill
6756 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6757 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6758 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6760 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6761 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6762 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6763 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6764 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6765 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6770 // play sound of object that hits the ground
6771 if (last_line || object_hit)
6772 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6775 static void TurnRoundExt(int x, int y)
6787 { 0, 0 }, { 0, 0 }, { 0, 0 },
6792 int left, right, back;
6796 { MV_DOWN, MV_UP, MV_RIGHT },
6797 { MV_UP, MV_DOWN, MV_LEFT },
6799 { MV_LEFT, MV_RIGHT, MV_DOWN },
6803 { MV_RIGHT, MV_LEFT, MV_UP }
6806 int element = Tile[x][y];
6807 int move_pattern = element_info[element].move_pattern;
6809 int old_move_dir = MovDir[x][y];
6810 int left_dir = turn[old_move_dir].left;
6811 int right_dir = turn[old_move_dir].right;
6812 int back_dir = turn[old_move_dir].back;
6814 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6815 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6816 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6817 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6819 int left_x = x + left_dx, left_y = y + left_dy;
6820 int right_x = x + right_dx, right_y = y + right_dy;
6821 int move_x = x + move_dx, move_y = y + move_dy;
6825 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6827 TestIfBadThingTouchesOtherBadThing(x, y);
6829 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6830 MovDir[x][y] = right_dir;
6831 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6832 MovDir[x][y] = left_dir;
6834 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6836 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6839 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6841 TestIfBadThingTouchesOtherBadThing(x, y);
6843 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6844 MovDir[x][y] = left_dir;
6845 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6846 MovDir[x][y] = right_dir;
6848 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6850 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6853 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6855 TestIfBadThingTouchesOtherBadThing(x, y);
6857 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6858 MovDir[x][y] = left_dir;
6859 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6860 MovDir[x][y] = right_dir;
6862 if (MovDir[x][y] != old_move_dir)
6865 else if (element == EL_YAMYAM)
6867 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6868 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6870 if (can_turn_left && can_turn_right)
6871 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6872 else if (can_turn_left)
6873 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6874 else if (can_turn_right)
6875 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6877 MovDir[x][y] = back_dir;
6879 MovDelay[x][y] = 16 + 16 * RND(3);
6881 else if (element == EL_DARK_YAMYAM)
6883 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6885 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6888 if (can_turn_left && can_turn_right)
6889 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6890 else if (can_turn_left)
6891 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6892 else if (can_turn_right)
6893 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6895 MovDir[x][y] = back_dir;
6897 MovDelay[x][y] = 16 + 16 * RND(3);
6899 else if (element == EL_PACMAN)
6901 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6902 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6904 if (can_turn_left && can_turn_right)
6905 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6906 else if (can_turn_left)
6907 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6908 else if (can_turn_right)
6909 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6911 MovDir[x][y] = back_dir;
6913 MovDelay[x][y] = 6 + RND(40);
6915 else if (element == EL_PIG)
6917 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6918 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6919 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6920 boolean should_turn_left, should_turn_right, should_move_on;
6922 int rnd = RND(rnd_value);
6924 should_turn_left = (can_turn_left &&
6926 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6927 y + back_dy + left_dy)));
6928 should_turn_right = (can_turn_right &&
6930 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6931 y + back_dy + right_dy)));
6932 should_move_on = (can_move_on &&
6935 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6936 y + move_dy + left_dy) ||
6937 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6938 y + move_dy + right_dy)));
6940 if (should_turn_left || should_turn_right || should_move_on)
6942 if (should_turn_left && should_turn_right && should_move_on)
6943 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6944 rnd < 2 * rnd_value / 3 ? right_dir :
6946 else if (should_turn_left && should_turn_right)
6947 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6948 else if (should_turn_left && should_move_on)
6949 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6950 else if (should_turn_right && should_move_on)
6951 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6952 else if (should_turn_left)
6953 MovDir[x][y] = left_dir;
6954 else if (should_turn_right)
6955 MovDir[x][y] = right_dir;
6956 else if (should_move_on)
6957 MovDir[x][y] = old_move_dir;
6959 else if (can_move_on && rnd > rnd_value / 8)
6960 MovDir[x][y] = old_move_dir;
6961 else if (can_turn_left && can_turn_right)
6962 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6963 else if (can_turn_left && rnd > rnd_value / 8)
6964 MovDir[x][y] = left_dir;
6965 else if (can_turn_right && rnd > rnd_value/8)
6966 MovDir[x][y] = right_dir;
6968 MovDir[x][y] = back_dir;
6970 xx = x + move_xy[MovDir[x][y]].dx;
6971 yy = y + move_xy[MovDir[x][y]].dy;
6973 if (!IN_LEV_FIELD(xx, yy) ||
6974 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6975 MovDir[x][y] = old_move_dir;
6979 else if (element == EL_DRAGON)
6981 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6982 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6983 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6985 int rnd = RND(rnd_value);
6987 if (can_move_on && rnd > rnd_value / 8)
6988 MovDir[x][y] = old_move_dir;
6989 else if (can_turn_left && can_turn_right)
6990 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6991 else if (can_turn_left && rnd > rnd_value / 8)
6992 MovDir[x][y] = left_dir;
6993 else if (can_turn_right && rnd > rnd_value / 8)
6994 MovDir[x][y] = right_dir;
6996 MovDir[x][y] = back_dir;
6998 xx = x + move_xy[MovDir[x][y]].dx;
6999 yy = y + move_xy[MovDir[x][y]].dy;
7001 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7002 MovDir[x][y] = old_move_dir;
7006 else if (element == EL_MOLE)
7008 boolean can_move_on =
7009 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7010 IS_AMOEBOID(Tile[move_x][move_y]) ||
7011 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7014 boolean can_turn_left =
7015 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7016 IS_AMOEBOID(Tile[left_x][left_y])));
7018 boolean can_turn_right =
7019 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7020 IS_AMOEBOID(Tile[right_x][right_y])));
7022 if (can_turn_left && can_turn_right)
7023 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7024 else if (can_turn_left)
7025 MovDir[x][y] = left_dir;
7027 MovDir[x][y] = right_dir;
7030 if (MovDir[x][y] != old_move_dir)
7033 else if (element == EL_BALLOON)
7035 MovDir[x][y] = game.wind_direction;
7038 else if (element == EL_SPRING)
7040 if (MovDir[x][y] & MV_HORIZONTAL)
7042 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7043 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7045 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7046 ResetGfxAnimation(move_x, move_y);
7047 TEST_DrawLevelField(move_x, move_y);
7049 MovDir[x][y] = back_dir;
7051 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7052 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7053 MovDir[x][y] = MV_NONE;
7058 else if (element == EL_ROBOT ||
7059 element == EL_SATELLITE ||
7060 element == EL_PENGUIN ||
7061 element == EL_EMC_ANDROID)
7063 int attr_x = -1, attr_y = -1;
7065 if (game.all_players_gone)
7067 attr_x = game.exit_x;
7068 attr_y = game.exit_y;
7074 for (i = 0; i < MAX_PLAYERS; i++)
7076 struct PlayerInfo *player = &stored_player[i];
7077 int jx = player->jx, jy = player->jy;
7079 if (!player->active)
7083 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7091 if (element == EL_ROBOT &&
7092 game.robot_wheel_x >= 0 &&
7093 game.robot_wheel_y >= 0 &&
7094 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7095 game.engine_version < VERSION_IDENT(3,1,0,0)))
7097 attr_x = game.robot_wheel_x;
7098 attr_y = game.robot_wheel_y;
7101 if (element == EL_PENGUIN)
7104 static int xy[4][2] =
7112 for (i = 0; i < NUM_DIRECTIONS; i++)
7114 int ex = x + xy[i][0];
7115 int ey = y + xy[i][1];
7117 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7118 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7119 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7120 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7129 MovDir[x][y] = MV_NONE;
7131 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7132 else if (attr_x > x)
7133 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7135 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7136 else if (attr_y > y)
7137 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7139 if (element == EL_ROBOT)
7143 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7144 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7145 Moving2Blocked(x, y, &newx, &newy);
7147 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7148 MovDelay[x][y] = 8 + 8 * !RND(3);
7150 MovDelay[x][y] = 16;
7152 else if (element == EL_PENGUIN)
7158 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7160 boolean first_horiz = RND(2);
7161 int new_move_dir = MovDir[x][y];
7164 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7165 Moving2Blocked(x, y, &newx, &newy);
7167 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7171 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7172 Moving2Blocked(x, y, &newx, &newy);
7174 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7177 MovDir[x][y] = old_move_dir;
7181 else if (element == EL_SATELLITE)
7187 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7189 boolean first_horiz = RND(2);
7190 int new_move_dir = MovDir[x][y];
7193 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7194 Moving2Blocked(x, y, &newx, &newy);
7196 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7200 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7201 Moving2Blocked(x, y, &newx, &newy);
7203 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7206 MovDir[x][y] = old_move_dir;
7210 else if (element == EL_EMC_ANDROID)
7212 static int check_pos[16] =
7214 -1, // 0 => (invalid)
7217 -1, // 3 => (invalid)
7219 0, // 5 => MV_LEFT | MV_UP
7220 2, // 6 => MV_RIGHT | MV_UP
7221 -1, // 7 => (invalid)
7223 6, // 9 => MV_LEFT | MV_DOWN
7224 4, // 10 => MV_RIGHT | MV_DOWN
7225 -1, // 11 => (invalid)
7226 -1, // 12 => (invalid)
7227 -1, // 13 => (invalid)
7228 -1, // 14 => (invalid)
7229 -1, // 15 => (invalid)
7237 { -1, -1, MV_LEFT | MV_UP },
7239 { +1, -1, MV_RIGHT | MV_UP },
7240 { +1, 0, MV_RIGHT },
7241 { +1, +1, MV_RIGHT | MV_DOWN },
7243 { -1, +1, MV_LEFT | MV_DOWN },
7246 int start_pos, check_order;
7247 boolean can_clone = FALSE;
7250 // check if there is any free field around current position
7251 for (i = 0; i < 8; i++)
7253 int newx = x + check_xy[i].dx;
7254 int newy = y + check_xy[i].dy;
7256 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7264 if (can_clone) // randomly find an element to clone
7268 start_pos = check_pos[RND(8)];
7269 check_order = (RND(2) ? -1 : +1);
7271 for (i = 0; i < 8; i++)
7273 int pos_raw = start_pos + i * check_order;
7274 int pos = (pos_raw + 8) % 8;
7275 int newx = x + check_xy[pos].dx;
7276 int newy = y + check_xy[pos].dy;
7278 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7280 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7281 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7283 Store[x][y] = Tile[newx][newy];
7292 if (can_clone) // randomly find a direction to move
7296 start_pos = check_pos[RND(8)];
7297 check_order = (RND(2) ? -1 : +1);
7299 for (i = 0; i < 8; i++)
7301 int pos_raw = start_pos + i * check_order;
7302 int pos = (pos_raw + 8) % 8;
7303 int newx = x + check_xy[pos].dx;
7304 int newy = y + check_xy[pos].dy;
7305 int new_move_dir = check_xy[pos].dir;
7307 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7309 MovDir[x][y] = new_move_dir;
7310 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7319 if (can_clone) // cloning and moving successful
7322 // cannot clone -- try to move towards player
7324 start_pos = check_pos[MovDir[x][y] & 0x0f];
7325 check_order = (RND(2) ? -1 : +1);
7327 for (i = 0; i < 3; i++)
7329 // first check start_pos, then previous/next or (next/previous) pos
7330 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7331 int pos = (pos_raw + 8) % 8;
7332 int newx = x + check_xy[pos].dx;
7333 int newy = y + check_xy[pos].dy;
7334 int new_move_dir = check_xy[pos].dir;
7336 if (IS_PLAYER(newx, newy))
7339 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7341 MovDir[x][y] = new_move_dir;
7342 MovDelay[x][y] = level.android_move_time * 8 + 1;
7349 else if (move_pattern == MV_TURNING_LEFT ||
7350 move_pattern == MV_TURNING_RIGHT ||
7351 move_pattern == MV_TURNING_LEFT_RIGHT ||
7352 move_pattern == MV_TURNING_RIGHT_LEFT ||
7353 move_pattern == MV_TURNING_RANDOM ||
7354 move_pattern == MV_ALL_DIRECTIONS)
7356 boolean can_turn_left =
7357 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7358 boolean can_turn_right =
7359 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7361 if (element_info[element].move_stepsize == 0) // "not moving"
7364 if (move_pattern == MV_TURNING_LEFT)
7365 MovDir[x][y] = left_dir;
7366 else if (move_pattern == MV_TURNING_RIGHT)
7367 MovDir[x][y] = right_dir;
7368 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7369 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7370 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7371 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7372 else if (move_pattern == MV_TURNING_RANDOM)
7373 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7374 can_turn_right && !can_turn_left ? right_dir :
7375 RND(2) ? left_dir : right_dir);
7376 else if (can_turn_left && can_turn_right)
7377 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7378 else if (can_turn_left)
7379 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7380 else if (can_turn_right)
7381 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7383 MovDir[x][y] = back_dir;
7385 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7387 else if (move_pattern == MV_HORIZONTAL ||
7388 move_pattern == MV_VERTICAL)
7390 if (move_pattern & old_move_dir)
7391 MovDir[x][y] = back_dir;
7392 else if (move_pattern == MV_HORIZONTAL)
7393 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7394 else if (move_pattern == MV_VERTICAL)
7395 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7397 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7399 else if (move_pattern & MV_ANY_DIRECTION)
7401 MovDir[x][y] = move_pattern;
7402 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7404 else if (move_pattern & MV_WIND_DIRECTION)
7406 MovDir[x][y] = game.wind_direction;
7407 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7409 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7411 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7412 MovDir[x][y] = left_dir;
7413 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7414 MovDir[x][y] = right_dir;
7416 if (MovDir[x][y] != old_move_dir)
7417 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7419 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7421 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7422 MovDir[x][y] = right_dir;
7423 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7424 MovDir[x][y] = left_dir;
7426 if (MovDir[x][y] != old_move_dir)
7427 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7429 else if (move_pattern == MV_TOWARDS_PLAYER ||
7430 move_pattern == MV_AWAY_FROM_PLAYER)
7432 int attr_x = -1, attr_y = -1;
7434 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7436 if (game.all_players_gone)
7438 attr_x = game.exit_x;
7439 attr_y = game.exit_y;
7445 for (i = 0; i < MAX_PLAYERS; i++)
7447 struct PlayerInfo *player = &stored_player[i];
7448 int jx = player->jx, jy = player->jy;
7450 if (!player->active)
7454 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7462 MovDir[x][y] = MV_NONE;
7464 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7465 else if (attr_x > x)
7466 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7468 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7469 else if (attr_y > y)
7470 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7472 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7474 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7476 boolean first_horiz = RND(2);
7477 int new_move_dir = MovDir[x][y];
7479 if (element_info[element].move_stepsize == 0) // "not moving"
7481 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7482 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7488 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7489 Moving2Blocked(x, y, &newx, &newy);
7491 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7495 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7496 Moving2Blocked(x, y, &newx, &newy);
7498 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7501 MovDir[x][y] = old_move_dir;
7504 else if (move_pattern == MV_WHEN_PUSHED ||
7505 move_pattern == MV_WHEN_DROPPED)
7507 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7508 MovDir[x][y] = MV_NONE;
7512 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7514 static int test_xy[7][2] =
7524 static int test_dir[7] =
7534 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7535 int move_preference = -1000000; // start with very low preference
7536 int new_move_dir = MV_NONE;
7537 int start_test = RND(4);
7540 for (i = 0; i < NUM_DIRECTIONS; i++)
7542 int move_dir = test_dir[start_test + i];
7543 int move_dir_preference;
7545 xx = x + test_xy[start_test + i][0];
7546 yy = y + test_xy[start_test + i][1];
7548 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7549 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7551 new_move_dir = move_dir;
7556 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7559 move_dir_preference = -1 * RunnerVisit[xx][yy];
7560 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7561 move_dir_preference = PlayerVisit[xx][yy];
7563 if (move_dir_preference > move_preference)
7565 // prefer field that has not been visited for the longest time
7566 move_preference = move_dir_preference;
7567 new_move_dir = move_dir;
7569 else if (move_dir_preference == move_preference &&
7570 move_dir == old_move_dir)
7572 // prefer last direction when all directions are preferred equally
7573 move_preference = move_dir_preference;
7574 new_move_dir = move_dir;
7578 MovDir[x][y] = new_move_dir;
7579 if (old_move_dir != new_move_dir)
7580 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7584 static void TurnRound(int x, int y)
7586 int direction = MovDir[x][y];
7590 GfxDir[x][y] = MovDir[x][y];
7592 if (direction != MovDir[x][y])
7596 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7598 ResetGfxFrame(x, y);
7601 static boolean JustBeingPushed(int x, int y)
7605 for (i = 0; i < MAX_PLAYERS; i++)
7607 struct PlayerInfo *player = &stored_player[i];
7609 if (player->active && player->is_pushing && player->MovPos)
7611 int next_jx = player->jx + (player->jx - player->last_jx);
7612 int next_jy = player->jy + (player->jy - player->last_jy);
7614 if (x == next_jx && y == next_jy)
7622 static void StartMoving(int x, int y)
7624 boolean started_moving = FALSE; // some elements can fall _and_ move
7625 int element = Tile[x][y];
7630 if (MovDelay[x][y] == 0)
7631 GfxAction[x][y] = ACTION_DEFAULT;
7633 if (CAN_FALL(element) && y < lev_fieldy - 1)
7635 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7636 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7637 if (JustBeingPushed(x, y))
7640 if (element == EL_QUICKSAND_FULL)
7642 if (IS_FREE(x, y + 1))
7644 InitMovingField(x, y, MV_DOWN);
7645 started_moving = TRUE;
7647 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7648 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7649 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7650 Store[x][y] = EL_ROCK;
7652 Store[x][y] = EL_ROCK;
7655 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7657 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7659 if (!MovDelay[x][y])
7661 MovDelay[x][y] = TILEY + 1;
7663 ResetGfxAnimation(x, y);
7664 ResetGfxAnimation(x, y + 1);
7669 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7670 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7677 Tile[x][y] = EL_QUICKSAND_EMPTY;
7678 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7679 Store[x][y + 1] = Store[x][y];
7682 PlayLevelSoundAction(x, y, ACTION_FILLING);
7684 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7686 if (!MovDelay[x][y])
7688 MovDelay[x][y] = TILEY + 1;
7690 ResetGfxAnimation(x, y);
7691 ResetGfxAnimation(x, y + 1);
7696 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7697 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7704 Tile[x][y] = EL_QUICKSAND_EMPTY;
7705 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7706 Store[x][y + 1] = Store[x][y];
7709 PlayLevelSoundAction(x, y, ACTION_FILLING);
7712 else if (element == EL_QUICKSAND_FAST_FULL)
7714 if (IS_FREE(x, y + 1))
7716 InitMovingField(x, y, MV_DOWN);
7717 started_moving = TRUE;
7719 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7720 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7721 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7722 Store[x][y] = EL_ROCK;
7724 Store[x][y] = EL_ROCK;
7727 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7729 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7731 if (!MovDelay[x][y])
7733 MovDelay[x][y] = TILEY + 1;
7735 ResetGfxAnimation(x, y);
7736 ResetGfxAnimation(x, y + 1);
7741 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7742 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7749 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7750 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7751 Store[x][y + 1] = Store[x][y];
7754 PlayLevelSoundAction(x, y, ACTION_FILLING);
7756 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7758 if (!MovDelay[x][y])
7760 MovDelay[x][y] = TILEY + 1;
7762 ResetGfxAnimation(x, y);
7763 ResetGfxAnimation(x, y + 1);
7768 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7769 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7776 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7777 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7778 Store[x][y + 1] = Store[x][y];
7781 PlayLevelSoundAction(x, y, ACTION_FILLING);
7784 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7785 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7787 InitMovingField(x, y, MV_DOWN);
7788 started_moving = TRUE;
7790 Tile[x][y] = EL_QUICKSAND_FILLING;
7791 Store[x][y] = element;
7793 PlayLevelSoundAction(x, y, ACTION_FILLING);
7795 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7796 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7798 InitMovingField(x, y, MV_DOWN);
7799 started_moving = TRUE;
7801 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7802 Store[x][y] = element;
7804 PlayLevelSoundAction(x, y, ACTION_FILLING);
7806 else if (element == EL_MAGIC_WALL_FULL)
7808 if (IS_FREE(x, y + 1))
7810 InitMovingField(x, y, MV_DOWN);
7811 started_moving = TRUE;
7813 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7814 Store[x][y] = EL_CHANGED(Store[x][y]);
7816 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7818 if (!MovDelay[x][y])
7819 MovDelay[x][y] = TILEY / 4 + 1;
7828 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7829 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7830 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7834 else if (element == EL_BD_MAGIC_WALL_FULL)
7836 if (IS_FREE(x, y + 1))
7838 InitMovingField(x, y, MV_DOWN);
7839 started_moving = TRUE;
7841 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7842 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7844 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7846 if (!MovDelay[x][y])
7847 MovDelay[x][y] = TILEY / 4 + 1;
7856 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7857 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7858 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7862 else if (element == EL_DC_MAGIC_WALL_FULL)
7864 if (IS_FREE(x, y + 1))
7866 InitMovingField(x, y, MV_DOWN);
7867 started_moving = TRUE;
7869 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7870 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7872 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7874 if (!MovDelay[x][y])
7875 MovDelay[x][y] = TILEY / 4 + 1;
7884 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7885 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7886 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7890 else if ((CAN_PASS_MAGIC_WALL(element) &&
7891 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7892 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7893 (CAN_PASS_DC_MAGIC_WALL(element) &&
7894 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7897 InitMovingField(x, y, MV_DOWN);
7898 started_moving = TRUE;
7901 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7902 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7903 EL_DC_MAGIC_WALL_FILLING);
7904 Store[x][y] = element;
7906 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7908 SplashAcid(x, y + 1);
7910 InitMovingField(x, y, MV_DOWN);
7911 started_moving = TRUE;
7913 Store[x][y] = EL_ACID;
7916 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7917 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7918 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7919 CAN_FALL(element) && WasJustFalling[x][y] &&
7920 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7922 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7923 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7924 (Tile[x][y + 1] == EL_BLOCKED)))
7926 /* this is needed for a special case not covered by calling "Impact()"
7927 from "ContinueMoving()": if an element moves to a tile directly below
7928 another element which was just falling on that tile (which was empty
7929 in the previous frame), the falling element above would just stop
7930 instead of smashing the element below (in previous version, the above
7931 element was just checked for "moving" instead of "falling", resulting
7932 in incorrect smashes caused by horizontal movement of the above
7933 element; also, the case of the player being the element to smash was
7934 simply not covered here... :-/ ) */
7936 CheckCollision[x][y] = 0;
7937 CheckImpact[x][y] = 0;
7941 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7943 if (MovDir[x][y] == MV_NONE)
7945 InitMovingField(x, y, MV_DOWN);
7946 started_moving = TRUE;
7949 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7951 if (WasJustFalling[x][y]) // prevent animation from being restarted
7952 MovDir[x][y] = MV_DOWN;
7954 InitMovingField(x, y, MV_DOWN);
7955 started_moving = TRUE;
7957 else if (element == EL_AMOEBA_DROP)
7959 Tile[x][y] = EL_AMOEBA_GROWING;
7960 Store[x][y] = EL_AMOEBA_WET;
7962 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7963 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7964 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7965 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7967 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7968 (IS_FREE(x - 1, y + 1) ||
7969 Tile[x - 1][y + 1] == EL_ACID));
7970 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7971 (IS_FREE(x + 1, y + 1) ||
7972 Tile[x + 1][y + 1] == EL_ACID));
7973 boolean can_fall_any = (can_fall_left || can_fall_right);
7974 boolean can_fall_both = (can_fall_left && can_fall_right);
7975 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7977 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7979 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7980 can_fall_right = FALSE;
7981 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7982 can_fall_left = FALSE;
7983 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7984 can_fall_right = FALSE;
7985 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7986 can_fall_left = FALSE;
7988 can_fall_any = (can_fall_left || can_fall_right);
7989 can_fall_both = FALSE;
7994 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7995 can_fall_right = FALSE; // slip down on left side
7997 can_fall_left = !(can_fall_right = RND(2));
7999 can_fall_both = FALSE;
8004 // if not determined otherwise, prefer left side for slipping down
8005 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8006 started_moving = TRUE;
8009 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8011 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8012 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8013 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8014 int belt_dir = game.belt_dir[belt_nr];
8016 if ((belt_dir == MV_LEFT && left_is_free) ||
8017 (belt_dir == MV_RIGHT && right_is_free))
8019 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8021 InitMovingField(x, y, belt_dir);
8022 started_moving = TRUE;
8024 Pushed[x][y] = TRUE;
8025 Pushed[nextx][y] = TRUE;
8027 GfxAction[x][y] = ACTION_DEFAULT;
8031 MovDir[x][y] = 0; // if element was moving, stop it
8036 // not "else if" because of elements that can fall and move (EL_SPRING)
8037 if (CAN_MOVE(element) && !started_moving)
8039 int move_pattern = element_info[element].move_pattern;
8042 Moving2Blocked(x, y, &newx, &newy);
8044 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8047 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8048 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8050 WasJustMoving[x][y] = 0;
8051 CheckCollision[x][y] = 0;
8053 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8055 if (Tile[x][y] != element) // element has changed
8059 if (!MovDelay[x][y]) // start new movement phase
8061 // all objects that can change their move direction after each step
8062 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8064 if (element != EL_YAMYAM &&
8065 element != EL_DARK_YAMYAM &&
8066 element != EL_PACMAN &&
8067 !(move_pattern & MV_ANY_DIRECTION) &&
8068 move_pattern != MV_TURNING_LEFT &&
8069 move_pattern != MV_TURNING_RIGHT &&
8070 move_pattern != MV_TURNING_LEFT_RIGHT &&
8071 move_pattern != MV_TURNING_RIGHT_LEFT &&
8072 move_pattern != MV_TURNING_RANDOM)
8076 if (MovDelay[x][y] && (element == EL_BUG ||
8077 element == EL_SPACESHIP ||
8078 element == EL_SP_SNIKSNAK ||
8079 element == EL_SP_ELECTRON ||
8080 element == EL_MOLE))
8081 TEST_DrawLevelField(x, y);
8085 if (MovDelay[x][y]) // wait some time before next movement
8089 if (element == EL_ROBOT ||
8090 element == EL_YAMYAM ||
8091 element == EL_DARK_YAMYAM)
8093 DrawLevelElementAnimationIfNeeded(x, y, element);
8094 PlayLevelSoundAction(x, y, ACTION_WAITING);
8096 else if (element == EL_SP_ELECTRON)
8097 DrawLevelElementAnimationIfNeeded(x, y, element);
8098 else if (element == EL_DRAGON)
8101 int dir = MovDir[x][y];
8102 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8103 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8104 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8105 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8106 dir == MV_UP ? IMG_FLAMES_1_UP :
8107 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8108 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8110 GfxAction[x][y] = ACTION_ATTACKING;
8112 if (IS_PLAYER(x, y))
8113 DrawPlayerField(x, y);
8115 TEST_DrawLevelField(x, y);
8117 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8119 for (i = 1; i <= 3; i++)
8121 int xx = x + i * dx;
8122 int yy = y + i * dy;
8123 int sx = SCREENX(xx);
8124 int sy = SCREENY(yy);
8125 int flame_graphic = graphic + (i - 1);
8127 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8132 int flamed = MovingOrBlocked2Element(xx, yy);
8134 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8137 RemoveMovingField(xx, yy);
8139 ChangeDelay[xx][yy] = 0;
8141 Tile[xx][yy] = EL_FLAMES;
8143 if (IN_SCR_FIELD(sx, sy))
8145 TEST_DrawLevelFieldCrumbled(xx, yy);
8146 DrawGraphic(sx, sy, flame_graphic, frame);
8151 if (Tile[xx][yy] == EL_FLAMES)
8152 Tile[xx][yy] = EL_EMPTY;
8153 TEST_DrawLevelField(xx, yy);
8158 if (MovDelay[x][y]) // element still has to wait some time
8160 PlayLevelSoundAction(x, y, ACTION_WAITING);
8166 // now make next step
8168 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8170 if (DONT_COLLIDE_WITH(element) &&
8171 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8172 !PLAYER_ENEMY_PROTECTED(newx, newy))
8174 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8179 else if (CAN_MOVE_INTO_ACID(element) &&
8180 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8181 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8182 (MovDir[x][y] == MV_DOWN ||
8183 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8185 SplashAcid(newx, newy);
8186 Store[x][y] = EL_ACID;
8188 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8190 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8191 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8192 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8193 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8196 TEST_DrawLevelField(x, y);
8198 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8199 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8200 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8202 game.friends_still_needed--;
8203 if (!game.friends_still_needed &&
8205 game.all_players_gone)
8210 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8212 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8213 TEST_DrawLevelField(newx, newy);
8215 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8217 else if (!IS_FREE(newx, newy))
8219 GfxAction[x][y] = ACTION_WAITING;
8221 if (IS_PLAYER(x, y))
8222 DrawPlayerField(x, y);
8224 TEST_DrawLevelField(x, y);
8229 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8231 if (IS_FOOD_PIG(Tile[newx][newy]))
8233 if (IS_MOVING(newx, newy))
8234 RemoveMovingField(newx, newy);
8237 Tile[newx][newy] = EL_EMPTY;
8238 TEST_DrawLevelField(newx, newy);
8241 PlayLevelSound(x, y, SND_PIG_DIGGING);
8243 else if (!IS_FREE(newx, newy))
8245 if (IS_PLAYER(x, y))
8246 DrawPlayerField(x, y);
8248 TEST_DrawLevelField(x, y);
8253 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8255 if (Store[x][y] != EL_EMPTY)
8257 boolean can_clone = FALSE;
8260 // check if element to clone is still there
8261 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8263 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8271 // cannot clone or target field not free anymore -- do not clone
8272 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8273 Store[x][y] = EL_EMPTY;
8276 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8278 if (IS_MV_DIAGONAL(MovDir[x][y]))
8280 int diagonal_move_dir = MovDir[x][y];
8281 int stored = Store[x][y];
8282 int change_delay = 8;
8285 // android is moving diagonally
8287 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8289 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8290 GfxElement[x][y] = EL_EMC_ANDROID;
8291 GfxAction[x][y] = ACTION_SHRINKING;
8292 GfxDir[x][y] = diagonal_move_dir;
8293 ChangeDelay[x][y] = change_delay;
8295 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8298 DrawLevelGraphicAnimation(x, y, graphic);
8299 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8301 if (Tile[newx][newy] == EL_ACID)
8303 SplashAcid(newx, newy);
8308 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8310 Store[newx][newy] = EL_EMC_ANDROID;
8311 GfxElement[newx][newy] = EL_EMC_ANDROID;
8312 GfxAction[newx][newy] = ACTION_GROWING;
8313 GfxDir[newx][newy] = diagonal_move_dir;
8314 ChangeDelay[newx][newy] = change_delay;
8316 graphic = el_act_dir2img(GfxElement[newx][newy],
8317 GfxAction[newx][newy], GfxDir[newx][newy]);
8319 DrawLevelGraphicAnimation(newx, newy, graphic);
8320 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8326 Tile[newx][newy] = EL_EMPTY;
8327 TEST_DrawLevelField(newx, newy);
8329 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8332 else if (!IS_FREE(newx, newy))
8337 else if (IS_CUSTOM_ELEMENT(element) &&
8338 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8340 if (!DigFieldByCE(newx, newy, element))
8343 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8345 RunnerVisit[x][y] = FrameCounter;
8346 PlayerVisit[x][y] /= 8; // expire player visit path
8349 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8351 if (!IS_FREE(newx, newy))
8353 if (IS_PLAYER(x, y))
8354 DrawPlayerField(x, y);
8356 TEST_DrawLevelField(x, y);
8362 boolean wanna_flame = !RND(10);
8363 int dx = newx - x, dy = newy - y;
8364 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8365 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8366 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8367 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8368 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8369 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8372 IS_CLASSIC_ENEMY(element1) ||
8373 IS_CLASSIC_ENEMY(element2)) &&
8374 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8375 element1 != EL_FLAMES && element2 != EL_FLAMES)
8377 ResetGfxAnimation(x, y);
8378 GfxAction[x][y] = ACTION_ATTACKING;
8380 if (IS_PLAYER(x, y))
8381 DrawPlayerField(x, y);
8383 TEST_DrawLevelField(x, y);
8385 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8387 MovDelay[x][y] = 50;
8389 Tile[newx][newy] = EL_FLAMES;
8390 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8391 Tile[newx1][newy1] = EL_FLAMES;
8392 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8393 Tile[newx2][newy2] = EL_FLAMES;
8399 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8400 Tile[newx][newy] == EL_DIAMOND)
8402 if (IS_MOVING(newx, newy))
8403 RemoveMovingField(newx, newy);
8406 Tile[newx][newy] = EL_EMPTY;
8407 TEST_DrawLevelField(newx, newy);
8410 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8412 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8413 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8415 if (AmoebaNr[newx][newy])
8417 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8418 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8419 Tile[newx][newy] == EL_BD_AMOEBA)
8420 AmoebaCnt[AmoebaNr[newx][newy]]--;
8423 if (IS_MOVING(newx, newy))
8425 RemoveMovingField(newx, newy);
8429 Tile[newx][newy] = EL_EMPTY;
8430 TEST_DrawLevelField(newx, newy);
8433 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8435 else if ((element == EL_PACMAN || element == EL_MOLE)
8436 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8438 if (AmoebaNr[newx][newy])
8440 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8441 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8442 Tile[newx][newy] == EL_BD_AMOEBA)
8443 AmoebaCnt[AmoebaNr[newx][newy]]--;
8446 if (element == EL_MOLE)
8448 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8449 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8451 ResetGfxAnimation(x, y);
8452 GfxAction[x][y] = ACTION_DIGGING;
8453 TEST_DrawLevelField(x, y);
8455 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8457 return; // wait for shrinking amoeba
8459 else // element == EL_PACMAN
8461 Tile[newx][newy] = EL_EMPTY;
8462 TEST_DrawLevelField(newx, newy);
8463 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8466 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8467 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8468 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8470 // wait for shrinking amoeba to completely disappear
8473 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8475 // object was running against a wall
8479 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8480 DrawLevelElementAnimation(x, y, element);
8482 if (DONT_TOUCH(element))
8483 TestIfBadThingTouchesPlayer(x, y);
8488 InitMovingField(x, y, MovDir[x][y]);
8490 PlayLevelSoundAction(x, y, ACTION_MOVING);
8494 ContinueMoving(x, y);
8497 void ContinueMoving(int x, int y)
8499 int element = Tile[x][y];
8500 struct ElementInfo *ei = &element_info[element];
8501 int direction = MovDir[x][y];
8502 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8503 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8504 int newx = x + dx, newy = y + dy;
8505 int stored = Store[x][y];
8506 int stored_new = Store[newx][newy];
8507 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8508 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8509 boolean last_line = (newy == lev_fieldy - 1);
8511 MovPos[x][y] += getElementMoveStepsize(x, y);
8513 if (pushed_by_player) // special case: moving object pushed by player
8514 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8516 if (ABS(MovPos[x][y]) < TILEX)
8518 TEST_DrawLevelField(x, y);
8520 return; // element is still moving
8523 // element reached destination field
8525 Tile[x][y] = EL_EMPTY;
8526 Tile[newx][newy] = element;
8527 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8529 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8531 element = Tile[newx][newy] = EL_ACID;
8533 else if (element == EL_MOLE)
8535 Tile[x][y] = EL_SAND;
8537 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8539 else if (element == EL_QUICKSAND_FILLING)
8541 element = Tile[newx][newy] = get_next_element(element);
8542 Store[newx][newy] = Store[x][y];
8544 else if (element == EL_QUICKSAND_EMPTYING)
8546 Tile[x][y] = get_next_element(element);
8547 element = Tile[newx][newy] = Store[x][y];
8549 else if (element == EL_QUICKSAND_FAST_FILLING)
8551 element = Tile[newx][newy] = get_next_element(element);
8552 Store[newx][newy] = Store[x][y];
8554 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8556 Tile[x][y] = get_next_element(element);
8557 element = Tile[newx][newy] = Store[x][y];
8559 else if (element == EL_MAGIC_WALL_FILLING)
8561 element = Tile[newx][newy] = get_next_element(element);
8562 if (!game.magic_wall_active)
8563 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8564 Store[newx][newy] = Store[x][y];
8566 else if (element == EL_MAGIC_WALL_EMPTYING)
8568 Tile[x][y] = get_next_element(element);
8569 if (!game.magic_wall_active)
8570 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8571 element = Tile[newx][newy] = Store[x][y];
8573 InitField(newx, newy, FALSE);
8575 else if (element == EL_BD_MAGIC_WALL_FILLING)
8577 element = Tile[newx][newy] = get_next_element(element);
8578 if (!game.magic_wall_active)
8579 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8580 Store[newx][newy] = Store[x][y];
8582 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8584 Tile[x][y] = get_next_element(element);
8585 if (!game.magic_wall_active)
8586 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8587 element = Tile[newx][newy] = Store[x][y];
8589 InitField(newx, newy, FALSE);
8591 else if (element == EL_DC_MAGIC_WALL_FILLING)
8593 element = Tile[newx][newy] = get_next_element(element);
8594 if (!game.magic_wall_active)
8595 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8596 Store[newx][newy] = Store[x][y];
8598 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8600 Tile[x][y] = get_next_element(element);
8601 if (!game.magic_wall_active)
8602 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8603 element = Tile[newx][newy] = Store[x][y];
8605 InitField(newx, newy, FALSE);
8607 else if (element == EL_AMOEBA_DROPPING)
8609 Tile[x][y] = get_next_element(element);
8610 element = Tile[newx][newy] = Store[x][y];
8612 else if (element == EL_SOKOBAN_OBJECT)
8615 Tile[x][y] = Back[x][y];
8617 if (Back[newx][newy])
8618 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8620 Back[x][y] = Back[newx][newy] = 0;
8623 Store[x][y] = EL_EMPTY;
8628 MovDelay[newx][newy] = 0;
8630 if (CAN_CHANGE_OR_HAS_ACTION(element))
8632 // copy element change control values to new field
8633 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8634 ChangePage[newx][newy] = ChangePage[x][y];
8635 ChangeCount[newx][newy] = ChangeCount[x][y];
8636 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8639 CustomValue[newx][newy] = CustomValue[x][y];
8641 ChangeDelay[x][y] = 0;
8642 ChangePage[x][y] = -1;
8643 ChangeCount[x][y] = 0;
8644 ChangeEvent[x][y] = -1;
8646 CustomValue[x][y] = 0;
8648 // copy animation control values to new field
8649 GfxFrame[newx][newy] = GfxFrame[x][y];
8650 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8651 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8652 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8654 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8656 // some elements can leave other elements behind after moving
8657 if (ei->move_leave_element != EL_EMPTY &&
8658 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8659 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8661 int move_leave_element = ei->move_leave_element;
8663 // this makes it possible to leave the removed element again
8664 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8665 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8667 Tile[x][y] = move_leave_element;
8669 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8670 MovDir[x][y] = direction;
8672 InitField(x, y, FALSE);
8674 if (GFX_CRUMBLED(Tile[x][y]))
8675 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8677 if (ELEM_IS_PLAYER(move_leave_element))
8678 RelocatePlayer(x, y, move_leave_element);
8681 // do this after checking for left-behind element
8682 ResetGfxAnimation(x, y); // reset animation values for old field
8684 if (!CAN_MOVE(element) ||
8685 (CAN_FALL(element) && direction == MV_DOWN &&
8686 (element == EL_SPRING ||
8687 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8688 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8689 GfxDir[x][y] = MovDir[newx][newy] = 0;
8691 TEST_DrawLevelField(x, y);
8692 TEST_DrawLevelField(newx, newy);
8694 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8696 // prevent pushed element from moving on in pushed direction
8697 if (pushed_by_player && CAN_MOVE(element) &&
8698 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8699 !(element_info[element].move_pattern & direction))
8700 TurnRound(newx, newy);
8702 // prevent elements on conveyor belt from moving on in last direction
8703 if (pushed_by_conveyor && CAN_FALL(element) &&
8704 direction & MV_HORIZONTAL)
8705 MovDir[newx][newy] = 0;
8707 if (!pushed_by_player)
8709 int nextx = newx + dx, nexty = newy + dy;
8710 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8712 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8714 if (CAN_FALL(element) && direction == MV_DOWN)
8715 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8717 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8718 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8720 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8721 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8724 if (DONT_TOUCH(element)) // object may be nasty to player or others
8726 TestIfBadThingTouchesPlayer(newx, newy);
8727 TestIfBadThingTouchesFriend(newx, newy);
8729 if (!IS_CUSTOM_ELEMENT(element))
8730 TestIfBadThingTouchesOtherBadThing(newx, newy);
8732 else if (element == EL_PENGUIN)
8733 TestIfFriendTouchesBadThing(newx, newy);
8735 if (DONT_GET_HIT_BY(element))
8737 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8740 // give the player one last chance (one more frame) to move away
8741 if (CAN_FALL(element) && direction == MV_DOWN &&
8742 (last_line || (!IS_FREE(x, newy + 1) &&
8743 (!IS_PLAYER(x, newy + 1) ||
8744 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8747 if (pushed_by_player && !game.use_change_when_pushing_bug)
8749 int push_side = MV_DIR_OPPOSITE(direction);
8750 struct PlayerInfo *player = PLAYERINFO(x, y);
8752 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8753 player->index_bit, push_side);
8754 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8755 player->index_bit, push_side);
8758 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8759 MovDelay[newx][newy] = 1;
8761 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8763 TestIfElementTouchesCustomElement(x, y); // empty or new element
8764 TestIfElementHitsCustomElement(newx, newy, direction);
8765 TestIfPlayerTouchesCustomElement(newx, newy);
8766 TestIfElementTouchesCustomElement(newx, newy);
8768 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8769 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8770 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8771 MV_DIR_OPPOSITE(direction));
8774 int AmoebaNeighbourNr(int ax, int ay)
8777 int element = Tile[ax][ay];
8779 static int xy[4][2] =
8787 for (i = 0; i < NUM_DIRECTIONS; i++)
8789 int x = ax + xy[i][0];
8790 int y = ay + xy[i][1];
8792 if (!IN_LEV_FIELD(x, y))
8795 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8796 group_nr = AmoebaNr[x][y];
8802 static void AmoebaMerge(int ax, int ay)
8804 int i, x, y, xx, yy;
8805 int new_group_nr = AmoebaNr[ax][ay];
8806 static int xy[4][2] =
8814 if (new_group_nr == 0)
8817 for (i = 0; i < NUM_DIRECTIONS; i++)
8822 if (!IN_LEV_FIELD(x, y))
8825 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8826 Tile[x][y] == EL_BD_AMOEBA ||
8827 Tile[x][y] == EL_AMOEBA_DEAD) &&
8828 AmoebaNr[x][y] != new_group_nr)
8830 int old_group_nr = AmoebaNr[x][y];
8832 if (old_group_nr == 0)
8835 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8836 AmoebaCnt[old_group_nr] = 0;
8837 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8838 AmoebaCnt2[old_group_nr] = 0;
8840 SCAN_PLAYFIELD(xx, yy)
8842 if (AmoebaNr[xx][yy] == old_group_nr)
8843 AmoebaNr[xx][yy] = new_group_nr;
8849 void AmoebaToDiamond(int ax, int ay)
8853 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8855 int group_nr = AmoebaNr[ax][ay];
8860 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8861 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8867 SCAN_PLAYFIELD(x, y)
8869 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8872 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8876 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8877 SND_AMOEBA_TURNING_TO_GEM :
8878 SND_AMOEBA_TURNING_TO_ROCK));
8883 static int xy[4][2] =
8891 for (i = 0; i < NUM_DIRECTIONS; i++)
8896 if (!IN_LEV_FIELD(x, y))
8899 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8901 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8902 SND_AMOEBA_TURNING_TO_GEM :
8903 SND_AMOEBA_TURNING_TO_ROCK));
8910 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8913 int group_nr = AmoebaNr[ax][ay];
8914 boolean done = FALSE;
8919 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8920 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8926 SCAN_PLAYFIELD(x, y)
8928 if (AmoebaNr[x][y] == group_nr &&
8929 (Tile[x][y] == EL_AMOEBA_DEAD ||
8930 Tile[x][y] == EL_BD_AMOEBA ||
8931 Tile[x][y] == EL_AMOEBA_GROWING))
8934 Tile[x][y] = new_element;
8935 InitField(x, y, FALSE);
8936 TEST_DrawLevelField(x, y);
8942 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8943 SND_BD_AMOEBA_TURNING_TO_ROCK :
8944 SND_BD_AMOEBA_TURNING_TO_GEM));
8947 static void AmoebaGrowing(int x, int y)
8949 static unsigned int sound_delay = 0;
8950 static unsigned int sound_delay_value = 0;
8952 if (!MovDelay[x][y]) // start new growing cycle
8956 if (DelayReached(&sound_delay, sound_delay_value))
8958 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8959 sound_delay_value = 30;
8963 if (MovDelay[x][y]) // wait some time before growing bigger
8966 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8968 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8969 6 - MovDelay[x][y]);
8971 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8974 if (!MovDelay[x][y])
8976 Tile[x][y] = Store[x][y];
8978 TEST_DrawLevelField(x, y);
8983 static void AmoebaShrinking(int x, int y)
8985 static unsigned int sound_delay = 0;
8986 static unsigned int sound_delay_value = 0;
8988 if (!MovDelay[x][y]) // start new shrinking cycle
8992 if (DelayReached(&sound_delay, sound_delay_value))
8993 sound_delay_value = 30;
8996 if (MovDelay[x][y]) // wait some time before shrinking
8999 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9001 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9002 6 - MovDelay[x][y]);
9004 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9007 if (!MovDelay[x][y])
9009 Tile[x][y] = EL_EMPTY;
9010 TEST_DrawLevelField(x, y);
9012 // don't let mole enter this field in this cycle;
9013 // (give priority to objects falling to this field from above)
9019 static void AmoebaReproduce(int ax, int ay)
9022 int element = Tile[ax][ay];
9023 int graphic = el2img(element);
9024 int newax = ax, neway = ay;
9025 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9026 static int xy[4][2] =
9034 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9036 Tile[ax][ay] = EL_AMOEBA_DEAD;
9037 TEST_DrawLevelField(ax, ay);
9041 if (IS_ANIMATED(graphic))
9042 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9044 if (!MovDelay[ax][ay]) // start making new amoeba field
9045 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9047 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9050 if (MovDelay[ax][ay])
9054 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9057 int x = ax + xy[start][0];
9058 int y = ay + xy[start][1];
9060 if (!IN_LEV_FIELD(x, y))
9063 if (IS_FREE(x, y) ||
9064 CAN_GROW_INTO(Tile[x][y]) ||
9065 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9066 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9072 if (newax == ax && neway == ay)
9075 else // normal or "filled" (BD style) amoeba
9078 boolean waiting_for_player = FALSE;
9080 for (i = 0; i < NUM_DIRECTIONS; i++)
9082 int j = (start + i) % 4;
9083 int x = ax + xy[j][0];
9084 int y = ay + xy[j][1];
9086 if (!IN_LEV_FIELD(x, y))
9089 if (IS_FREE(x, y) ||
9090 CAN_GROW_INTO(Tile[x][y]) ||
9091 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9092 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9098 else if (IS_PLAYER(x, y))
9099 waiting_for_player = TRUE;
9102 if (newax == ax && neway == ay) // amoeba cannot grow
9104 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9106 Tile[ax][ay] = EL_AMOEBA_DEAD;
9107 TEST_DrawLevelField(ax, ay);
9108 AmoebaCnt[AmoebaNr[ax][ay]]--;
9110 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9112 if (element == EL_AMOEBA_FULL)
9113 AmoebaToDiamond(ax, ay);
9114 else if (element == EL_BD_AMOEBA)
9115 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9120 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9122 // amoeba gets larger by growing in some direction
9124 int new_group_nr = AmoebaNr[ax][ay];
9127 if (new_group_nr == 0)
9129 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9131 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9137 AmoebaNr[newax][neway] = new_group_nr;
9138 AmoebaCnt[new_group_nr]++;
9139 AmoebaCnt2[new_group_nr]++;
9141 // if amoeba touches other amoeba(s) after growing, unify them
9142 AmoebaMerge(newax, neway);
9144 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9146 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9152 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9153 (neway == lev_fieldy - 1 && newax != ax))
9155 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9156 Store[newax][neway] = element;
9158 else if (neway == ay || element == EL_EMC_DRIPPER)
9160 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9162 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9166 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9167 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9168 Store[ax][ay] = EL_AMOEBA_DROP;
9169 ContinueMoving(ax, ay);
9173 TEST_DrawLevelField(newax, neway);
9176 static void Life(int ax, int ay)
9180 int element = Tile[ax][ay];
9181 int graphic = el2img(element);
9182 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9184 boolean changed = FALSE;
9186 if (IS_ANIMATED(graphic))
9187 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9192 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9193 MovDelay[ax][ay] = life_time;
9195 if (MovDelay[ax][ay]) // wait some time before next cycle
9198 if (MovDelay[ax][ay])
9202 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9204 int xx = ax+x1, yy = ay+y1;
9205 int old_element = Tile[xx][yy];
9206 int num_neighbours = 0;
9208 if (!IN_LEV_FIELD(xx, yy))
9211 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9213 int x = xx+x2, y = yy+y2;
9215 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9218 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9219 boolean is_neighbour = FALSE;
9221 if (level.use_life_bugs)
9223 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9224 (IS_FREE(x, y) && Stop[x][y]));
9227 (Last[x][y] == element || is_player_cell);
9233 boolean is_free = FALSE;
9235 if (level.use_life_bugs)
9236 is_free = (IS_FREE(xx, yy));
9238 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9240 if (xx == ax && yy == ay) // field in the middle
9242 if (num_neighbours < life_parameter[0] ||
9243 num_neighbours > life_parameter[1])
9245 Tile[xx][yy] = EL_EMPTY;
9246 if (Tile[xx][yy] != old_element)
9247 TEST_DrawLevelField(xx, yy);
9248 Stop[xx][yy] = TRUE;
9252 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9253 { // free border field
9254 if (num_neighbours >= life_parameter[2] &&
9255 num_neighbours <= life_parameter[3])
9257 Tile[xx][yy] = element;
9258 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9259 if (Tile[xx][yy] != old_element)
9260 TEST_DrawLevelField(xx, yy);
9261 Stop[xx][yy] = TRUE;
9268 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9269 SND_GAME_OF_LIFE_GROWING);
9272 static void InitRobotWheel(int x, int y)
9274 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9277 static void RunRobotWheel(int x, int y)
9279 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9282 static void StopRobotWheel(int x, int y)
9284 if (game.robot_wheel_x == x &&
9285 game.robot_wheel_y == y)
9287 game.robot_wheel_x = -1;
9288 game.robot_wheel_y = -1;
9289 game.robot_wheel_active = FALSE;
9293 static void InitTimegateWheel(int x, int y)
9295 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9298 static void RunTimegateWheel(int x, int y)
9300 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9303 static void InitMagicBallDelay(int x, int y)
9305 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9308 static void ActivateMagicBall(int bx, int by)
9312 if (level.ball_random)
9314 int pos_border = RND(8); // select one of the eight border elements
9315 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9316 int xx = pos_content % 3;
9317 int yy = pos_content / 3;
9322 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9323 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9327 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9329 int xx = x - bx + 1;
9330 int yy = y - by + 1;
9332 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9333 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9337 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9340 static void CheckExit(int x, int y)
9342 if (game.gems_still_needed > 0 ||
9343 game.sokoban_fields_still_needed > 0 ||
9344 game.sokoban_objects_still_needed > 0 ||
9345 game.lights_still_needed > 0)
9347 int element = Tile[x][y];
9348 int graphic = el2img(element);
9350 if (IS_ANIMATED(graphic))
9351 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9356 // do not re-open exit door closed after last player
9357 if (game.all_players_gone)
9360 Tile[x][y] = EL_EXIT_OPENING;
9362 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9365 static void CheckExitEM(int x, int y)
9367 if (game.gems_still_needed > 0 ||
9368 game.sokoban_fields_still_needed > 0 ||
9369 game.sokoban_objects_still_needed > 0 ||
9370 game.lights_still_needed > 0)
9372 int element = Tile[x][y];
9373 int graphic = el2img(element);
9375 if (IS_ANIMATED(graphic))
9376 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9381 // do not re-open exit door closed after last player
9382 if (game.all_players_gone)
9385 Tile[x][y] = EL_EM_EXIT_OPENING;
9387 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9390 static void CheckExitSteel(int x, int y)
9392 if (game.gems_still_needed > 0 ||
9393 game.sokoban_fields_still_needed > 0 ||
9394 game.sokoban_objects_still_needed > 0 ||
9395 game.lights_still_needed > 0)
9397 int element = Tile[x][y];
9398 int graphic = el2img(element);
9400 if (IS_ANIMATED(graphic))
9401 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9406 // do not re-open exit door closed after last player
9407 if (game.all_players_gone)
9410 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9412 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9415 static void CheckExitSteelEM(int x, int y)
9417 if (game.gems_still_needed > 0 ||
9418 game.sokoban_fields_still_needed > 0 ||
9419 game.sokoban_objects_still_needed > 0 ||
9420 game.lights_still_needed > 0)
9422 int element = Tile[x][y];
9423 int graphic = el2img(element);
9425 if (IS_ANIMATED(graphic))
9426 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9431 // do not re-open exit door closed after last player
9432 if (game.all_players_gone)
9435 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9437 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9440 static void CheckExitSP(int x, int y)
9442 if (game.gems_still_needed > 0)
9444 int element = Tile[x][y];
9445 int graphic = el2img(element);
9447 if (IS_ANIMATED(graphic))
9448 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9453 // do not re-open exit door closed after last player
9454 if (game.all_players_gone)
9457 Tile[x][y] = EL_SP_EXIT_OPENING;
9459 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9462 static void CloseAllOpenTimegates(void)
9466 SCAN_PLAYFIELD(x, y)
9468 int element = Tile[x][y];
9470 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9472 Tile[x][y] = EL_TIMEGATE_CLOSING;
9474 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9479 static void DrawTwinkleOnField(int x, int y)
9481 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9484 if (Tile[x][y] == EL_BD_DIAMOND)
9487 if (MovDelay[x][y] == 0) // next animation frame
9488 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9490 if (MovDelay[x][y] != 0) // wait some time before next frame
9494 DrawLevelElementAnimation(x, y, Tile[x][y]);
9496 if (MovDelay[x][y] != 0)
9498 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9499 10 - MovDelay[x][y]);
9501 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9506 static void MauerWaechst(int x, int y)
9510 if (!MovDelay[x][y]) // next animation frame
9511 MovDelay[x][y] = 3 * delay;
9513 if (MovDelay[x][y]) // wait some time before next frame
9517 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9519 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9520 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9522 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9525 if (!MovDelay[x][y])
9527 if (MovDir[x][y] == MV_LEFT)
9529 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9530 TEST_DrawLevelField(x - 1, y);
9532 else if (MovDir[x][y] == MV_RIGHT)
9534 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9535 TEST_DrawLevelField(x + 1, y);
9537 else if (MovDir[x][y] == MV_UP)
9539 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9540 TEST_DrawLevelField(x, y - 1);
9544 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9545 TEST_DrawLevelField(x, y + 1);
9548 Tile[x][y] = Store[x][y];
9550 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9551 TEST_DrawLevelField(x, y);
9556 static void MauerAbleger(int ax, int ay)
9558 int element = Tile[ax][ay];
9559 int graphic = el2img(element);
9560 boolean oben_frei = FALSE, unten_frei = FALSE;
9561 boolean links_frei = FALSE, rechts_frei = FALSE;
9562 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9563 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9564 boolean new_wall = FALSE;
9566 if (IS_ANIMATED(graphic))
9567 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9569 if (!MovDelay[ax][ay]) // start building new wall
9570 MovDelay[ax][ay] = 6;
9572 if (MovDelay[ax][ay]) // wait some time before building new wall
9575 if (MovDelay[ax][ay])
9579 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9581 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9583 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9585 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9588 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9589 element == EL_EXPANDABLE_WALL_ANY)
9593 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9594 Store[ax][ay-1] = element;
9595 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9596 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9597 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9598 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9603 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9604 Store[ax][ay+1] = element;
9605 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9606 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9607 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9608 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9613 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9614 element == EL_EXPANDABLE_WALL_ANY ||
9615 element == EL_EXPANDABLE_WALL ||
9616 element == EL_BD_EXPANDABLE_WALL)
9620 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9621 Store[ax-1][ay] = element;
9622 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9623 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9624 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9625 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9631 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9632 Store[ax+1][ay] = element;
9633 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9634 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9635 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9636 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9641 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9642 TEST_DrawLevelField(ax, ay);
9644 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9646 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9647 unten_massiv = TRUE;
9648 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9649 links_massiv = TRUE;
9650 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9651 rechts_massiv = TRUE;
9653 if (((oben_massiv && unten_massiv) ||
9654 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9655 element == EL_EXPANDABLE_WALL) &&
9656 ((links_massiv && rechts_massiv) ||
9657 element == EL_EXPANDABLE_WALL_VERTICAL))
9658 Tile[ax][ay] = EL_WALL;
9661 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9664 static void MauerAblegerStahl(int ax, int ay)
9666 int element = Tile[ax][ay];
9667 int graphic = el2img(element);
9668 boolean oben_frei = FALSE, unten_frei = FALSE;
9669 boolean links_frei = FALSE, rechts_frei = FALSE;
9670 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9671 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9672 boolean new_wall = FALSE;
9674 if (IS_ANIMATED(graphic))
9675 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9677 if (!MovDelay[ax][ay]) // start building new wall
9678 MovDelay[ax][ay] = 6;
9680 if (MovDelay[ax][ay]) // wait some time before building new wall
9683 if (MovDelay[ax][ay])
9687 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9689 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9691 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9693 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9696 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9697 element == EL_EXPANDABLE_STEELWALL_ANY)
9701 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9702 Store[ax][ay-1] = element;
9703 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9704 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9705 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9706 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9711 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9712 Store[ax][ay+1] = element;
9713 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9714 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9715 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9716 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9721 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9722 element == EL_EXPANDABLE_STEELWALL_ANY)
9726 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9727 Store[ax-1][ay] = element;
9728 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9729 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9730 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9731 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9737 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9738 Store[ax+1][ay] = element;
9739 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9740 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9741 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9742 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9747 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9749 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9750 unten_massiv = TRUE;
9751 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9752 links_massiv = TRUE;
9753 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9754 rechts_massiv = TRUE;
9756 if (((oben_massiv && unten_massiv) ||
9757 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9758 ((links_massiv && rechts_massiv) ||
9759 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9760 Tile[ax][ay] = EL_STEELWALL;
9763 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9766 static void CheckForDragon(int x, int y)
9769 boolean dragon_found = FALSE;
9770 static int xy[4][2] =
9778 for (i = 0; i < NUM_DIRECTIONS; i++)
9780 for (j = 0; j < 4; j++)
9782 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9784 if (IN_LEV_FIELD(xx, yy) &&
9785 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9787 if (Tile[xx][yy] == EL_DRAGON)
9788 dragon_found = TRUE;
9797 for (i = 0; i < NUM_DIRECTIONS; i++)
9799 for (j = 0; j < 3; j++)
9801 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9803 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9805 Tile[xx][yy] = EL_EMPTY;
9806 TEST_DrawLevelField(xx, yy);
9815 static void InitBuggyBase(int x, int y)
9817 int element = Tile[x][y];
9818 int activating_delay = FRAMES_PER_SECOND / 4;
9821 (element == EL_SP_BUGGY_BASE ?
9822 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9823 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9825 element == EL_SP_BUGGY_BASE_ACTIVE ?
9826 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9829 static void WarnBuggyBase(int x, int y)
9832 static int xy[4][2] =
9840 for (i = 0; i < NUM_DIRECTIONS; i++)
9842 int xx = x + xy[i][0];
9843 int yy = y + xy[i][1];
9845 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9847 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9854 static void InitTrap(int x, int y)
9856 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9859 static void ActivateTrap(int x, int y)
9861 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9864 static void ChangeActiveTrap(int x, int y)
9866 int graphic = IMG_TRAP_ACTIVE;
9868 // if new animation frame was drawn, correct crumbled sand border
9869 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9870 TEST_DrawLevelFieldCrumbled(x, y);
9873 static int getSpecialActionElement(int element, int number, int base_element)
9875 return (element != EL_EMPTY ? element :
9876 number != -1 ? base_element + number - 1 :
9880 static int getModifiedActionNumber(int value_old, int operator, int operand,
9881 int value_min, int value_max)
9883 int value_new = (operator == CA_MODE_SET ? operand :
9884 operator == CA_MODE_ADD ? value_old + operand :
9885 operator == CA_MODE_SUBTRACT ? value_old - operand :
9886 operator == CA_MODE_MULTIPLY ? value_old * operand :
9887 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9888 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9891 return (value_new < value_min ? value_min :
9892 value_new > value_max ? value_max :
9896 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9898 struct ElementInfo *ei = &element_info[element];
9899 struct ElementChangeInfo *change = &ei->change_page[page];
9900 int target_element = change->target_element;
9901 int action_type = change->action_type;
9902 int action_mode = change->action_mode;
9903 int action_arg = change->action_arg;
9904 int action_element = change->action_element;
9907 if (!change->has_action)
9910 // ---------- determine action paramater values -----------------------------
9912 int level_time_value =
9913 (level.time > 0 ? TimeLeft :
9916 int action_arg_element_raw =
9917 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9918 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9919 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9920 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9921 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9922 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9923 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9925 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9927 int action_arg_direction =
9928 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9929 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9930 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9931 change->actual_trigger_side :
9932 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9933 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9936 int action_arg_number_min =
9937 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9940 int action_arg_number_max =
9941 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9942 action_type == CA_SET_LEVEL_GEMS ? 999 :
9943 action_type == CA_SET_LEVEL_TIME ? 9999 :
9944 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9945 action_type == CA_SET_CE_VALUE ? 9999 :
9946 action_type == CA_SET_CE_SCORE ? 9999 :
9949 int action_arg_number_reset =
9950 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9951 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9952 action_type == CA_SET_LEVEL_TIME ? level.time :
9953 action_type == CA_SET_LEVEL_SCORE ? 0 :
9954 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9955 action_type == CA_SET_CE_SCORE ? 0 :
9958 int action_arg_number =
9959 (action_arg <= CA_ARG_MAX ? action_arg :
9960 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9961 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9962 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9963 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9964 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9965 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9966 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9967 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9968 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9969 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9970 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9971 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9972 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9973 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9974 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9975 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9976 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9977 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9978 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9979 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9980 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9983 int action_arg_number_old =
9984 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9985 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9986 action_type == CA_SET_LEVEL_SCORE ? game.score :
9987 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9988 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9991 int action_arg_number_new =
9992 getModifiedActionNumber(action_arg_number_old,
9993 action_mode, action_arg_number,
9994 action_arg_number_min, action_arg_number_max);
9996 int trigger_player_bits =
9997 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9998 change->actual_trigger_player_bits : change->trigger_player);
10000 int action_arg_player_bits =
10001 (action_arg >= CA_ARG_PLAYER_1 &&
10002 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10003 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10004 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10007 // ---------- execute action -----------------------------------------------
10009 switch (action_type)
10016 // ---------- level actions ----------------------------------------------
10018 case CA_RESTART_LEVEL:
10020 game.restart_level = TRUE;
10025 case CA_SHOW_ENVELOPE:
10027 int element = getSpecialActionElement(action_arg_element,
10028 action_arg_number, EL_ENVELOPE_1);
10030 if (IS_ENVELOPE(element))
10031 local_player->show_envelope = element;
10036 case CA_SET_LEVEL_TIME:
10038 if (level.time > 0) // only modify limited time value
10040 TimeLeft = action_arg_number_new;
10042 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10044 DisplayGameControlValues();
10046 if (!TimeLeft && setup.time_limit)
10047 for (i = 0; i < MAX_PLAYERS; i++)
10048 KillPlayer(&stored_player[i]);
10054 case CA_SET_LEVEL_SCORE:
10056 game.score = action_arg_number_new;
10058 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10060 DisplayGameControlValues();
10065 case CA_SET_LEVEL_GEMS:
10067 game.gems_still_needed = action_arg_number_new;
10069 game.snapshot.collected_item = TRUE;
10071 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10073 DisplayGameControlValues();
10078 case CA_SET_LEVEL_WIND:
10080 game.wind_direction = action_arg_direction;
10085 case CA_SET_LEVEL_RANDOM_SEED:
10087 // ensure that setting a new random seed while playing is predictable
10088 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10093 // ---------- player actions ---------------------------------------------
10095 case CA_MOVE_PLAYER:
10096 case CA_MOVE_PLAYER_NEW:
10098 // automatically move to the next field in specified direction
10099 for (i = 0; i < MAX_PLAYERS; i++)
10100 if (trigger_player_bits & (1 << i))
10101 if (action_type == CA_MOVE_PLAYER ||
10102 stored_player[i].MovPos == 0)
10103 stored_player[i].programmed_action = action_arg_direction;
10108 case CA_EXIT_PLAYER:
10110 for (i = 0; i < MAX_PLAYERS; i++)
10111 if (action_arg_player_bits & (1 << i))
10112 ExitPlayer(&stored_player[i]);
10114 if (game.players_still_needed == 0)
10120 case CA_KILL_PLAYER:
10122 for (i = 0; i < MAX_PLAYERS; i++)
10123 if (action_arg_player_bits & (1 << i))
10124 KillPlayer(&stored_player[i]);
10129 case CA_SET_PLAYER_KEYS:
10131 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10132 int element = getSpecialActionElement(action_arg_element,
10133 action_arg_number, EL_KEY_1);
10135 if (IS_KEY(element))
10137 for (i = 0; i < MAX_PLAYERS; i++)
10139 if (trigger_player_bits & (1 << i))
10141 stored_player[i].key[KEY_NR(element)] = key_state;
10143 DrawGameDoorValues();
10151 case CA_SET_PLAYER_SPEED:
10153 for (i = 0; i < MAX_PLAYERS; i++)
10155 if (trigger_player_bits & (1 << i))
10157 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10159 if (action_arg == CA_ARG_SPEED_FASTER &&
10160 stored_player[i].cannot_move)
10162 action_arg_number = STEPSIZE_VERY_SLOW;
10164 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10165 action_arg == CA_ARG_SPEED_FASTER)
10167 action_arg_number = 2;
10168 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10171 else if (action_arg == CA_ARG_NUMBER_RESET)
10173 action_arg_number = level.initial_player_stepsize[i];
10177 getModifiedActionNumber(move_stepsize,
10180 action_arg_number_min,
10181 action_arg_number_max);
10183 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10190 case CA_SET_PLAYER_SHIELD:
10192 for (i = 0; i < MAX_PLAYERS; i++)
10194 if (trigger_player_bits & (1 << i))
10196 if (action_arg == CA_ARG_SHIELD_OFF)
10198 stored_player[i].shield_normal_time_left = 0;
10199 stored_player[i].shield_deadly_time_left = 0;
10201 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10203 stored_player[i].shield_normal_time_left = 999999;
10205 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10207 stored_player[i].shield_normal_time_left = 999999;
10208 stored_player[i].shield_deadly_time_left = 999999;
10216 case CA_SET_PLAYER_GRAVITY:
10218 for (i = 0; i < MAX_PLAYERS; i++)
10220 if (trigger_player_bits & (1 << i))
10222 stored_player[i].gravity =
10223 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10224 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10225 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10226 stored_player[i].gravity);
10233 case CA_SET_PLAYER_ARTWORK:
10235 for (i = 0; i < MAX_PLAYERS; i++)
10237 if (trigger_player_bits & (1 << i))
10239 int artwork_element = action_arg_element;
10241 if (action_arg == CA_ARG_ELEMENT_RESET)
10243 (level.use_artwork_element[i] ? level.artwork_element[i] :
10244 stored_player[i].element_nr);
10246 if (stored_player[i].artwork_element != artwork_element)
10247 stored_player[i].Frame = 0;
10249 stored_player[i].artwork_element = artwork_element;
10251 SetPlayerWaiting(&stored_player[i], FALSE);
10253 // set number of special actions for bored and sleeping animation
10254 stored_player[i].num_special_action_bored =
10255 get_num_special_action(artwork_element,
10256 ACTION_BORING_1, ACTION_BORING_LAST);
10257 stored_player[i].num_special_action_sleeping =
10258 get_num_special_action(artwork_element,
10259 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10266 case CA_SET_PLAYER_INVENTORY:
10268 for (i = 0; i < MAX_PLAYERS; i++)
10270 struct PlayerInfo *player = &stored_player[i];
10273 if (trigger_player_bits & (1 << i))
10275 int inventory_element = action_arg_element;
10277 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10278 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10279 action_arg == CA_ARG_ELEMENT_ACTION)
10281 int element = inventory_element;
10282 int collect_count = element_info[element].collect_count_initial;
10284 if (!IS_CUSTOM_ELEMENT(element))
10287 if (collect_count == 0)
10288 player->inventory_infinite_element = element;
10290 for (k = 0; k < collect_count; k++)
10291 if (player->inventory_size < MAX_INVENTORY_SIZE)
10292 player->inventory_element[player->inventory_size++] =
10295 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10296 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10297 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10299 if (player->inventory_infinite_element != EL_UNDEFINED &&
10300 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10301 action_arg_element_raw))
10302 player->inventory_infinite_element = EL_UNDEFINED;
10304 for (k = 0, j = 0; j < player->inventory_size; j++)
10306 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10307 action_arg_element_raw))
10308 player->inventory_element[k++] = player->inventory_element[j];
10311 player->inventory_size = k;
10313 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10315 if (player->inventory_size > 0)
10317 for (j = 0; j < player->inventory_size - 1; j++)
10318 player->inventory_element[j] = player->inventory_element[j + 1];
10320 player->inventory_size--;
10323 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10325 if (player->inventory_size > 0)
10326 player->inventory_size--;
10328 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10330 player->inventory_infinite_element = EL_UNDEFINED;
10331 player->inventory_size = 0;
10333 else if (action_arg == CA_ARG_INVENTORY_RESET)
10335 player->inventory_infinite_element = EL_UNDEFINED;
10336 player->inventory_size = 0;
10338 if (level.use_initial_inventory[i])
10340 for (j = 0; j < level.initial_inventory_size[i]; j++)
10342 int element = level.initial_inventory_content[i][j];
10343 int collect_count = element_info[element].collect_count_initial;
10345 if (!IS_CUSTOM_ELEMENT(element))
10348 if (collect_count == 0)
10349 player->inventory_infinite_element = element;
10351 for (k = 0; k < collect_count; k++)
10352 if (player->inventory_size < MAX_INVENTORY_SIZE)
10353 player->inventory_element[player->inventory_size++] =
10364 // ---------- CE actions -------------------------------------------------
10366 case CA_SET_CE_VALUE:
10368 int last_ce_value = CustomValue[x][y];
10370 CustomValue[x][y] = action_arg_number_new;
10372 if (CustomValue[x][y] != last_ce_value)
10374 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10375 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10377 if (CustomValue[x][y] == 0)
10379 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10380 ChangeCount[x][y] = 0; // allow at least one more change
10382 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10383 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10390 case CA_SET_CE_SCORE:
10392 int last_ce_score = ei->collect_score;
10394 ei->collect_score = action_arg_number_new;
10396 if (ei->collect_score != last_ce_score)
10398 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10399 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10401 if (ei->collect_score == 0)
10405 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10406 ChangeCount[x][y] = 0; // allow at least one more change
10408 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10409 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10412 This is a very special case that seems to be a mixture between
10413 CheckElementChange() and CheckTriggeredElementChange(): while
10414 the first one only affects single elements that are triggered
10415 directly, the second one affects multiple elements in the playfield
10416 that are triggered indirectly by another element. This is a third
10417 case: Changing the CE score always affects multiple identical CEs,
10418 so every affected CE must be checked, not only the single CE for
10419 which the CE score was changed in the first place (as every instance
10420 of that CE shares the same CE score, and therefore also can change)!
10422 SCAN_PLAYFIELD(xx, yy)
10424 if (Tile[xx][yy] == element)
10425 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10426 CE_SCORE_GETS_ZERO);
10434 case CA_SET_CE_ARTWORK:
10436 int artwork_element = action_arg_element;
10437 boolean reset_frame = FALSE;
10440 if (action_arg == CA_ARG_ELEMENT_RESET)
10441 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10444 if (ei->gfx_element != artwork_element)
10445 reset_frame = TRUE;
10447 ei->gfx_element = artwork_element;
10449 SCAN_PLAYFIELD(xx, yy)
10451 if (Tile[xx][yy] == element)
10455 ResetGfxAnimation(xx, yy);
10456 ResetRandomAnimationValue(xx, yy);
10459 TEST_DrawLevelField(xx, yy);
10466 // ---------- engine actions ---------------------------------------------
10468 case CA_SET_ENGINE_SCAN_MODE:
10470 InitPlayfieldScanMode(action_arg);
10480 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10482 int old_element = Tile[x][y];
10483 int new_element = GetElementFromGroupElement(element);
10484 int previous_move_direction = MovDir[x][y];
10485 int last_ce_value = CustomValue[x][y];
10486 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10487 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10488 boolean add_player_onto_element = (new_element_is_player &&
10489 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10490 IS_WALKABLE(old_element));
10492 if (!add_player_onto_element)
10494 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10495 RemoveMovingField(x, y);
10499 Tile[x][y] = new_element;
10501 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10502 MovDir[x][y] = previous_move_direction;
10504 if (element_info[new_element].use_last_ce_value)
10505 CustomValue[x][y] = last_ce_value;
10507 InitField_WithBug1(x, y, FALSE);
10509 new_element = Tile[x][y]; // element may have changed
10511 ResetGfxAnimation(x, y);
10512 ResetRandomAnimationValue(x, y);
10514 TEST_DrawLevelField(x, y);
10516 if (GFX_CRUMBLED(new_element))
10517 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10520 // check if element under the player changes from accessible to unaccessible
10521 // (needed for special case of dropping element which then changes)
10522 // (must be checked after creating new element for walkable group elements)
10523 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10524 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10531 // "ChangeCount" not set yet to allow "entered by player" change one time
10532 if (new_element_is_player)
10533 RelocatePlayer(x, y, new_element);
10536 ChangeCount[x][y]++; // count number of changes in the same frame
10538 TestIfBadThingTouchesPlayer(x, y);
10539 TestIfPlayerTouchesCustomElement(x, y);
10540 TestIfElementTouchesCustomElement(x, y);
10543 static void CreateField(int x, int y, int element)
10545 CreateFieldExt(x, y, element, FALSE);
10548 static void CreateElementFromChange(int x, int y, int element)
10550 element = GET_VALID_RUNTIME_ELEMENT(element);
10552 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10554 int old_element = Tile[x][y];
10556 // prevent changed element from moving in same engine frame
10557 // unless both old and new element can either fall or move
10558 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10559 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10563 CreateFieldExt(x, y, element, TRUE);
10566 static boolean ChangeElement(int x, int y, int element, int page)
10568 struct ElementInfo *ei = &element_info[element];
10569 struct ElementChangeInfo *change = &ei->change_page[page];
10570 int ce_value = CustomValue[x][y];
10571 int ce_score = ei->collect_score;
10572 int target_element;
10573 int old_element = Tile[x][y];
10575 // always use default change event to prevent running into a loop
10576 if (ChangeEvent[x][y] == -1)
10577 ChangeEvent[x][y] = CE_DELAY;
10579 if (ChangeEvent[x][y] == CE_DELAY)
10581 // reset actual trigger element, trigger player and action element
10582 change->actual_trigger_element = EL_EMPTY;
10583 change->actual_trigger_player = EL_EMPTY;
10584 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10585 change->actual_trigger_side = CH_SIDE_NONE;
10586 change->actual_trigger_ce_value = 0;
10587 change->actual_trigger_ce_score = 0;
10590 // do not change elements more than a specified maximum number of changes
10591 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10594 ChangeCount[x][y]++; // count number of changes in the same frame
10596 if (change->explode)
10603 if (change->use_target_content)
10605 boolean complete_replace = TRUE;
10606 boolean can_replace[3][3];
10609 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10612 boolean is_walkable;
10613 boolean is_diggable;
10614 boolean is_collectible;
10615 boolean is_removable;
10616 boolean is_destructible;
10617 int ex = x + xx - 1;
10618 int ey = y + yy - 1;
10619 int content_element = change->target_content.e[xx][yy];
10622 can_replace[xx][yy] = TRUE;
10624 if (ex == x && ey == y) // do not check changing element itself
10627 if (content_element == EL_EMPTY_SPACE)
10629 can_replace[xx][yy] = FALSE; // do not replace border with space
10634 if (!IN_LEV_FIELD(ex, ey))
10636 can_replace[xx][yy] = FALSE;
10637 complete_replace = FALSE;
10644 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10645 e = MovingOrBlocked2Element(ex, ey);
10647 is_empty = (IS_FREE(ex, ey) ||
10648 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10650 is_walkable = (is_empty || IS_WALKABLE(e));
10651 is_diggable = (is_empty || IS_DIGGABLE(e));
10652 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10653 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10654 is_removable = (is_diggable || is_collectible);
10656 can_replace[xx][yy] =
10657 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10658 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10659 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10660 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10661 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10662 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10663 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10665 if (!can_replace[xx][yy])
10666 complete_replace = FALSE;
10669 if (!change->only_if_complete || complete_replace)
10671 boolean something_has_changed = FALSE;
10673 if (change->only_if_complete && change->use_random_replace &&
10674 RND(100) < change->random_percentage)
10677 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10679 int ex = x + xx - 1;
10680 int ey = y + yy - 1;
10681 int content_element;
10683 if (can_replace[xx][yy] && (!change->use_random_replace ||
10684 RND(100) < change->random_percentage))
10686 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10687 RemoveMovingField(ex, ey);
10689 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10691 content_element = change->target_content.e[xx][yy];
10692 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10693 ce_value, ce_score);
10695 CreateElementFromChange(ex, ey, target_element);
10697 something_has_changed = TRUE;
10699 // for symmetry reasons, freeze newly created border elements
10700 if (ex != x || ey != y)
10701 Stop[ex][ey] = TRUE; // no more moving in this frame
10705 if (something_has_changed)
10707 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10708 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10714 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10715 ce_value, ce_score);
10717 if (element == EL_DIAGONAL_GROWING ||
10718 element == EL_DIAGONAL_SHRINKING)
10720 target_element = Store[x][y];
10722 Store[x][y] = EL_EMPTY;
10725 CreateElementFromChange(x, y, target_element);
10727 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10728 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10731 // this uses direct change before indirect change
10732 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10737 static void HandleElementChange(int x, int y, int page)
10739 int element = MovingOrBlocked2Element(x, y);
10740 struct ElementInfo *ei = &element_info[element];
10741 struct ElementChangeInfo *change = &ei->change_page[page];
10742 boolean handle_action_before_change = FALSE;
10745 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10746 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10748 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10749 x, y, element, element_info[element].token_name);
10750 Debug("game:playing:HandleElementChange", "This should never happen!");
10754 // this can happen with classic bombs on walkable, changing elements
10755 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10760 if (ChangeDelay[x][y] == 0) // initialize element change
10762 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10764 if (change->can_change)
10766 // !!! not clear why graphic animation should be reset at all here !!!
10767 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10768 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10771 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10773 When using an animation frame delay of 1 (this only happens with
10774 "sp_zonk.moving.left/right" in the classic graphics), the default
10775 (non-moving) animation shows wrong animation frames (while the
10776 moving animation, like "sp_zonk.moving.left/right", is correct,
10777 so this graphical bug never shows up with the classic graphics).
10778 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10779 be drawn instead of the correct frames 0,1,2,3. This is caused by
10780 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10781 an element change: First when the change delay ("ChangeDelay[][]")
10782 counter has reached zero after decrementing, then a second time in
10783 the next frame (after "GfxFrame[][]" was already incremented) when
10784 "ChangeDelay[][]" is reset to the initial delay value again.
10786 This causes frame 0 to be drawn twice, while the last frame won't
10787 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10789 As some animations may already be cleverly designed around this bug
10790 (at least the "Snake Bite" snake tail animation does this), it cannot
10791 simply be fixed here without breaking such existing animations.
10792 Unfortunately, it cannot easily be detected if a graphics set was
10793 designed "before" or "after" the bug was fixed. As a workaround,
10794 a new graphics set option "game.graphics_engine_version" was added
10795 to be able to specify the game's major release version for which the
10796 graphics set was designed, which can then be used to decide if the
10797 bugfix should be used (version 4 and above) or not (version 3 or
10798 below, or if no version was specified at all, as with old sets).
10800 (The wrong/fixed animation frames can be tested with the test level set
10801 "test_gfxframe" and level "000", which contains a specially prepared
10802 custom element at level position (x/y) == (11/9) which uses the zonk
10803 animation mentioned above. Using "game.graphics_engine_version: 4"
10804 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10805 This can also be seen from the debug output for this test element.)
10808 // when a custom element is about to change (for example by change delay),
10809 // do not reset graphic animation when the custom element is moving
10810 if (game.graphics_engine_version < 4 &&
10813 ResetGfxAnimation(x, y);
10814 ResetRandomAnimationValue(x, y);
10817 if (change->pre_change_function)
10818 change->pre_change_function(x, y);
10822 ChangeDelay[x][y]--;
10824 if (ChangeDelay[x][y] != 0) // continue element change
10826 if (change->can_change)
10828 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10830 if (IS_ANIMATED(graphic))
10831 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10833 if (change->change_function)
10834 change->change_function(x, y);
10837 else // finish element change
10839 if (ChangePage[x][y] != -1) // remember page from delayed change
10841 page = ChangePage[x][y];
10842 ChangePage[x][y] = -1;
10844 change = &ei->change_page[page];
10847 if (IS_MOVING(x, y)) // never change a running system ;-)
10849 ChangeDelay[x][y] = 1; // try change after next move step
10850 ChangePage[x][y] = page; // remember page to use for change
10855 // special case: set new level random seed before changing element
10856 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10857 handle_action_before_change = TRUE;
10859 if (change->has_action && handle_action_before_change)
10860 ExecuteCustomElementAction(x, y, element, page);
10862 if (change->can_change)
10864 if (ChangeElement(x, y, element, page))
10866 if (change->post_change_function)
10867 change->post_change_function(x, y);
10871 if (change->has_action && !handle_action_before_change)
10872 ExecuteCustomElementAction(x, y, element, page);
10876 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10877 int trigger_element,
10879 int trigger_player,
10883 boolean change_done_any = FALSE;
10884 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10887 if (!(trigger_events[trigger_element][trigger_event]))
10890 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10892 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10894 int element = EL_CUSTOM_START + i;
10895 boolean change_done = FALSE;
10898 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10899 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10902 for (p = 0; p < element_info[element].num_change_pages; p++)
10904 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10906 if (change->can_change_or_has_action &&
10907 change->has_event[trigger_event] &&
10908 change->trigger_side & trigger_side &&
10909 change->trigger_player & trigger_player &&
10910 change->trigger_page & trigger_page_bits &&
10911 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10913 change->actual_trigger_element = trigger_element;
10914 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10915 change->actual_trigger_player_bits = trigger_player;
10916 change->actual_trigger_side = trigger_side;
10917 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10918 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10920 if ((change->can_change && !change_done) || change->has_action)
10924 SCAN_PLAYFIELD(x, y)
10926 if (Tile[x][y] == element)
10928 if (change->can_change && !change_done)
10930 // if element already changed in this frame, not only prevent
10931 // another element change (checked in ChangeElement()), but
10932 // also prevent additional element actions for this element
10934 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10935 !level.use_action_after_change_bug)
10938 ChangeDelay[x][y] = 1;
10939 ChangeEvent[x][y] = trigger_event;
10941 HandleElementChange(x, y, p);
10943 else if (change->has_action)
10945 // if element already changed in this frame, not only prevent
10946 // another element change (checked in ChangeElement()), but
10947 // also prevent additional element actions for this element
10949 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10950 !level.use_action_after_change_bug)
10953 ExecuteCustomElementAction(x, y, element, p);
10954 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10959 if (change->can_change)
10961 change_done = TRUE;
10962 change_done_any = TRUE;
10969 RECURSION_LOOP_DETECTION_END();
10971 return change_done_any;
10974 static boolean CheckElementChangeExt(int x, int y,
10976 int trigger_element,
10978 int trigger_player,
10981 boolean change_done = FALSE;
10984 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10985 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10988 if (Tile[x][y] == EL_BLOCKED)
10990 Blocked2Moving(x, y, &x, &y);
10991 element = Tile[x][y];
10994 // check if element has already changed or is about to change after moving
10995 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10996 Tile[x][y] != element) ||
10998 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10999 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11000 ChangePage[x][y] != -1)))
11003 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11005 for (p = 0; p < element_info[element].num_change_pages; p++)
11007 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11009 /* check trigger element for all events where the element that is checked
11010 for changing interacts with a directly adjacent element -- this is
11011 different to element changes that affect other elements to change on the
11012 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11013 boolean check_trigger_element =
11014 (trigger_event == CE_TOUCHING_X ||
11015 trigger_event == CE_HITTING_X ||
11016 trigger_event == CE_HIT_BY_X ||
11017 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11019 if (change->can_change_or_has_action &&
11020 change->has_event[trigger_event] &&
11021 change->trigger_side & trigger_side &&
11022 change->trigger_player & trigger_player &&
11023 (!check_trigger_element ||
11024 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11026 change->actual_trigger_element = trigger_element;
11027 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11028 change->actual_trigger_player_bits = trigger_player;
11029 change->actual_trigger_side = trigger_side;
11030 change->actual_trigger_ce_value = CustomValue[x][y];
11031 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11033 // special case: trigger element not at (x,y) position for some events
11034 if (check_trigger_element)
11046 { 0, 0 }, { 0, 0 }, { 0, 0 },
11050 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11051 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11053 change->actual_trigger_ce_value = CustomValue[xx][yy];
11054 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11057 if (change->can_change && !change_done)
11059 ChangeDelay[x][y] = 1;
11060 ChangeEvent[x][y] = trigger_event;
11062 HandleElementChange(x, y, p);
11064 change_done = TRUE;
11066 else if (change->has_action)
11068 ExecuteCustomElementAction(x, y, element, p);
11069 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11074 RECURSION_LOOP_DETECTION_END();
11076 return change_done;
11079 static void PlayPlayerSound(struct PlayerInfo *player)
11081 int jx = player->jx, jy = player->jy;
11082 int sound_element = player->artwork_element;
11083 int last_action = player->last_action_waiting;
11084 int action = player->action_waiting;
11086 if (player->is_waiting)
11088 if (action != last_action)
11089 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11091 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11095 if (action != last_action)
11096 StopSound(element_info[sound_element].sound[last_action]);
11098 if (last_action == ACTION_SLEEPING)
11099 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11103 static void PlayAllPlayersSound(void)
11107 for (i = 0; i < MAX_PLAYERS; i++)
11108 if (stored_player[i].active)
11109 PlayPlayerSound(&stored_player[i]);
11112 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11114 boolean last_waiting = player->is_waiting;
11115 int move_dir = player->MovDir;
11117 player->dir_waiting = move_dir;
11118 player->last_action_waiting = player->action_waiting;
11122 if (!last_waiting) // not waiting -> waiting
11124 player->is_waiting = TRUE;
11126 player->frame_counter_bored =
11128 game.player_boring_delay_fixed +
11129 GetSimpleRandom(game.player_boring_delay_random);
11130 player->frame_counter_sleeping =
11132 game.player_sleeping_delay_fixed +
11133 GetSimpleRandom(game.player_sleeping_delay_random);
11135 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11138 if (game.player_sleeping_delay_fixed +
11139 game.player_sleeping_delay_random > 0 &&
11140 player->anim_delay_counter == 0 &&
11141 player->post_delay_counter == 0 &&
11142 FrameCounter >= player->frame_counter_sleeping)
11143 player->is_sleeping = TRUE;
11144 else if (game.player_boring_delay_fixed +
11145 game.player_boring_delay_random > 0 &&
11146 FrameCounter >= player->frame_counter_bored)
11147 player->is_bored = TRUE;
11149 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11150 player->is_bored ? ACTION_BORING :
11153 if (player->is_sleeping && player->use_murphy)
11155 // special case for sleeping Murphy when leaning against non-free tile
11157 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11158 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11159 !IS_MOVING(player->jx - 1, player->jy)))
11160 move_dir = MV_LEFT;
11161 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11162 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11163 !IS_MOVING(player->jx + 1, player->jy)))
11164 move_dir = MV_RIGHT;
11166 player->is_sleeping = FALSE;
11168 player->dir_waiting = move_dir;
11171 if (player->is_sleeping)
11173 if (player->num_special_action_sleeping > 0)
11175 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11177 int last_special_action = player->special_action_sleeping;
11178 int num_special_action = player->num_special_action_sleeping;
11179 int special_action =
11180 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11181 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11182 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11183 last_special_action + 1 : ACTION_SLEEPING);
11184 int special_graphic =
11185 el_act_dir2img(player->artwork_element, special_action, move_dir);
11187 player->anim_delay_counter =
11188 graphic_info[special_graphic].anim_delay_fixed +
11189 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11190 player->post_delay_counter =
11191 graphic_info[special_graphic].post_delay_fixed +
11192 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11194 player->special_action_sleeping = special_action;
11197 if (player->anim_delay_counter > 0)
11199 player->action_waiting = player->special_action_sleeping;
11200 player->anim_delay_counter--;
11202 else if (player->post_delay_counter > 0)
11204 player->post_delay_counter--;
11208 else if (player->is_bored)
11210 if (player->num_special_action_bored > 0)
11212 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11214 int special_action =
11215 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11216 int special_graphic =
11217 el_act_dir2img(player->artwork_element, special_action, move_dir);
11219 player->anim_delay_counter =
11220 graphic_info[special_graphic].anim_delay_fixed +
11221 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11222 player->post_delay_counter =
11223 graphic_info[special_graphic].post_delay_fixed +
11224 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11226 player->special_action_bored = special_action;
11229 if (player->anim_delay_counter > 0)
11231 player->action_waiting = player->special_action_bored;
11232 player->anim_delay_counter--;
11234 else if (player->post_delay_counter > 0)
11236 player->post_delay_counter--;
11241 else if (last_waiting) // waiting -> not waiting
11243 player->is_waiting = FALSE;
11244 player->is_bored = FALSE;
11245 player->is_sleeping = FALSE;
11247 player->frame_counter_bored = -1;
11248 player->frame_counter_sleeping = -1;
11250 player->anim_delay_counter = 0;
11251 player->post_delay_counter = 0;
11253 player->dir_waiting = player->MovDir;
11254 player->action_waiting = ACTION_DEFAULT;
11256 player->special_action_bored = ACTION_DEFAULT;
11257 player->special_action_sleeping = ACTION_DEFAULT;
11261 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11263 if ((!player->is_moving && player->was_moving) ||
11264 (player->MovPos == 0 && player->was_moving) ||
11265 (player->is_snapping && !player->was_snapping) ||
11266 (player->is_dropping && !player->was_dropping))
11268 if (!CheckSaveEngineSnapshotToList())
11271 player->was_moving = FALSE;
11272 player->was_snapping = TRUE;
11273 player->was_dropping = TRUE;
11277 if (player->is_moving)
11278 player->was_moving = TRUE;
11280 if (!player->is_snapping)
11281 player->was_snapping = FALSE;
11283 if (!player->is_dropping)
11284 player->was_dropping = FALSE;
11287 static struct MouseActionInfo mouse_action_last = { 0 };
11288 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11289 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11292 CheckSaveEngineSnapshotToList();
11294 mouse_action_last = mouse_action;
11297 static void CheckSingleStepMode(struct PlayerInfo *player)
11299 if (tape.single_step && tape.recording && !tape.pausing)
11301 // as it is called "single step mode", just return to pause mode when the
11302 // player stopped moving after one tile (or never starts moving at all)
11303 // (reverse logic needed here in case single step mode used in team mode)
11304 if (player->is_moving ||
11305 player->is_pushing ||
11306 player->is_dropping_pressed ||
11307 player->effective_mouse_action.button)
11308 game.enter_single_step_mode = FALSE;
11311 CheckSaveEngineSnapshot(player);
11314 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11316 int left = player_action & JOY_LEFT;
11317 int right = player_action & JOY_RIGHT;
11318 int up = player_action & JOY_UP;
11319 int down = player_action & JOY_DOWN;
11320 int button1 = player_action & JOY_BUTTON_1;
11321 int button2 = player_action & JOY_BUTTON_2;
11322 int dx = (left ? -1 : right ? 1 : 0);
11323 int dy = (up ? -1 : down ? 1 : 0);
11325 if (!player->active || tape.pausing)
11331 SnapField(player, dx, dy);
11335 DropElement(player);
11337 MovePlayer(player, dx, dy);
11340 CheckSingleStepMode(player);
11342 SetPlayerWaiting(player, FALSE);
11344 return player_action;
11348 // no actions for this player (no input at player's configured device)
11350 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11351 SnapField(player, 0, 0);
11352 CheckGravityMovementWhenNotMoving(player);
11354 if (player->MovPos == 0)
11355 SetPlayerWaiting(player, TRUE);
11357 if (player->MovPos == 0) // needed for tape.playing
11358 player->is_moving = FALSE;
11360 player->is_dropping = FALSE;
11361 player->is_dropping_pressed = FALSE;
11362 player->drop_pressed_delay = 0;
11364 CheckSingleStepMode(player);
11370 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11373 if (!tape.use_mouse_actions)
11376 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11377 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11378 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11381 static void SetTapeActionFromMouseAction(byte *tape_action,
11382 struct MouseActionInfo *mouse_action)
11384 if (!tape.use_mouse_actions)
11387 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11388 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11389 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11392 static void CheckLevelSolved(void)
11394 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11396 if (game_em.level_solved &&
11397 !game_em.game_over) // game won
11401 game_em.game_over = TRUE;
11403 game.all_players_gone = TRUE;
11406 if (game_em.game_over) // game lost
11407 game.all_players_gone = TRUE;
11409 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11411 if (game_sp.level_solved &&
11412 !game_sp.game_over) // game won
11416 game_sp.game_over = TRUE;
11418 game.all_players_gone = TRUE;
11421 if (game_sp.game_over) // game lost
11422 game.all_players_gone = TRUE;
11424 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11426 if (game_mm.level_solved &&
11427 !game_mm.game_over) // game won
11431 game_mm.game_over = TRUE;
11433 game.all_players_gone = TRUE;
11436 if (game_mm.game_over) // game lost
11437 game.all_players_gone = TRUE;
11441 static void CheckLevelTime(void)
11445 if (TimeFrames >= FRAMES_PER_SECOND)
11450 for (i = 0; i < MAX_PLAYERS; i++)
11452 struct PlayerInfo *player = &stored_player[i];
11454 if (SHIELD_ON(player))
11456 player->shield_normal_time_left--;
11458 if (player->shield_deadly_time_left > 0)
11459 player->shield_deadly_time_left--;
11463 if (!game.LevelSolved && !level.use_step_counter)
11471 if (TimeLeft <= 10 && setup.time_limit)
11472 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11474 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11475 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11477 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11479 if (!TimeLeft && setup.time_limit)
11481 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11482 game_em.lev->killed_out_of_time = TRUE;
11484 for (i = 0; i < MAX_PLAYERS; i++)
11485 KillPlayer(&stored_player[i]);
11488 else if (game.no_time_limit && !game.all_players_gone)
11490 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11493 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11496 if (tape.recording || tape.playing)
11497 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11500 if (tape.recording || tape.playing)
11501 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11503 UpdateAndDisplayGameControlValues();
11506 void AdvanceFrameAndPlayerCounters(int player_nr)
11510 // advance frame counters (global frame counter and time frame counter)
11514 // advance player counters (counters for move delay, move animation etc.)
11515 for (i = 0; i < MAX_PLAYERS; i++)
11517 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11518 int move_delay_value = stored_player[i].move_delay_value;
11519 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11521 if (!advance_player_counters) // not all players may be affected
11524 if (move_frames == 0) // less than one move per game frame
11526 int stepsize = TILEX / move_delay_value;
11527 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11528 int count = (stored_player[i].is_moving ?
11529 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11531 if (count % delay == 0)
11535 stored_player[i].Frame += move_frames;
11537 if (stored_player[i].MovPos != 0)
11538 stored_player[i].StepFrame += move_frames;
11540 if (stored_player[i].move_delay > 0)
11541 stored_player[i].move_delay--;
11543 // due to bugs in previous versions, counter must count up, not down
11544 if (stored_player[i].push_delay != -1)
11545 stored_player[i].push_delay++;
11547 if (stored_player[i].drop_delay > 0)
11548 stored_player[i].drop_delay--;
11550 if (stored_player[i].is_dropping_pressed)
11551 stored_player[i].drop_pressed_delay++;
11555 void StartGameActions(boolean init_network_game, boolean record_tape,
11558 unsigned int new_random_seed = InitRND(random_seed);
11561 TapeStartRecording(new_random_seed);
11563 if (init_network_game)
11565 SendToServer_LevelFile();
11566 SendToServer_StartPlaying();
11574 static void GameActionsExt(void)
11577 static unsigned int game_frame_delay = 0;
11579 unsigned int game_frame_delay_value;
11580 byte *recorded_player_action;
11581 byte summarized_player_action = 0;
11582 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11585 // detect endless loops, caused by custom element programming
11586 if (recursion_loop_detected && recursion_loop_depth == 0)
11588 char *message = getStringCat3("Internal Error! Element ",
11589 EL_NAME(recursion_loop_element),
11590 " caused endless loop! Quit the game?");
11592 Warn("element '%s' caused endless loop in game engine",
11593 EL_NAME(recursion_loop_element));
11595 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11597 recursion_loop_detected = FALSE; // if game should be continued
11604 if (game.restart_level)
11605 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11607 CheckLevelSolved();
11609 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11612 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11615 if (game_status != GAME_MODE_PLAYING) // status might have changed
11618 game_frame_delay_value =
11619 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11621 if (tape.playing && tape.warp_forward && !tape.pausing)
11622 game_frame_delay_value = 0;
11624 SetVideoFrameDelay(game_frame_delay_value);
11626 // (de)activate virtual buttons depending on current game status
11627 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11629 if (game.all_players_gone) // if no players there to be controlled anymore
11630 SetOverlayActive(FALSE);
11631 else if (!tape.playing) // if game continues after tape stopped playing
11632 SetOverlayActive(TRUE);
11637 // ---------- main game synchronization point ----------
11639 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11641 Debug("game:playing:skip", "skip == %d", skip);
11644 // ---------- main game synchronization point ----------
11646 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11650 if (network_playing && !network_player_action_received)
11652 // try to get network player actions in time
11654 // last chance to get network player actions without main loop delay
11655 HandleNetworking();
11657 // game was quit by network peer
11658 if (game_status != GAME_MODE_PLAYING)
11661 // check if network player actions still missing and game still running
11662 if (!network_player_action_received && !checkGameEnded())
11663 return; // failed to get network player actions in time
11665 // do not yet reset "network_player_action_received" (for tape.pausing)
11671 // at this point we know that we really continue executing the game
11673 network_player_action_received = FALSE;
11675 // when playing tape, read previously recorded player input from tape data
11676 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11678 local_player->effective_mouse_action = local_player->mouse_action;
11680 if (recorded_player_action != NULL)
11681 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11682 recorded_player_action);
11684 // TapePlayAction() may return NULL when toggling to "pause before death"
11688 if (tape.set_centered_player)
11690 game.centered_player_nr_next = tape.centered_player_nr_next;
11691 game.set_centered_player = TRUE;
11694 for (i = 0; i < MAX_PLAYERS; i++)
11696 summarized_player_action |= stored_player[i].action;
11698 if (!network_playing && (game.team_mode || tape.playing))
11699 stored_player[i].effective_action = stored_player[i].action;
11702 if (network_playing && !checkGameEnded())
11703 SendToServer_MovePlayer(summarized_player_action);
11705 // summarize all actions at local players mapped input device position
11706 // (this allows using different input devices in single player mode)
11707 if (!network.enabled && !game.team_mode)
11708 stored_player[map_player_action[local_player->index_nr]].effective_action =
11709 summarized_player_action;
11711 // summarize all actions at centered player in local team mode
11712 if (tape.recording &&
11713 setup.team_mode && !network.enabled &&
11714 setup.input_on_focus &&
11715 game.centered_player_nr != -1)
11717 for (i = 0; i < MAX_PLAYERS; i++)
11718 stored_player[map_player_action[i]].effective_action =
11719 (i == game.centered_player_nr ? summarized_player_action : 0);
11722 if (recorded_player_action != NULL)
11723 for (i = 0; i < MAX_PLAYERS; i++)
11724 stored_player[i].effective_action = recorded_player_action[i];
11726 for (i = 0; i < MAX_PLAYERS; i++)
11728 tape_action[i] = stored_player[i].effective_action;
11730 /* (this may happen in the RND game engine if a player was not present on
11731 the playfield on level start, but appeared later from a custom element */
11732 if (setup.team_mode &&
11735 !tape.player_participates[i])
11736 tape.player_participates[i] = TRUE;
11739 SetTapeActionFromMouseAction(tape_action,
11740 &local_player->effective_mouse_action);
11742 // only record actions from input devices, but not programmed actions
11743 if (tape.recording)
11744 TapeRecordAction(tape_action);
11746 // remember if game was played (especially after tape stopped playing)
11747 if (!tape.playing && summarized_player_action)
11748 game.GamePlayed = TRUE;
11750 #if USE_NEW_PLAYER_ASSIGNMENTS
11751 // !!! also map player actions in single player mode !!!
11752 // if (game.team_mode)
11755 byte mapped_action[MAX_PLAYERS];
11757 #if DEBUG_PLAYER_ACTIONS
11758 for (i = 0; i < MAX_PLAYERS; i++)
11759 DebugContinued("", "%d, ", stored_player[i].effective_action);
11762 for (i = 0; i < MAX_PLAYERS; i++)
11763 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11765 for (i = 0; i < MAX_PLAYERS; i++)
11766 stored_player[i].effective_action = mapped_action[i];
11768 #if DEBUG_PLAYER_ACTIONS
11769 DebugContinued("", "=> ");
11770 for (i = 0; i < MAX_PLAYERS; i++)
11771 DebugContinued("", "%d, ", stored_player[i].effective_action);
11772 DebugContinued("game:playing:player", "\n");
11775 #if DEBUG_PLAYER_ACTIONS
11778 for (i = 0; i < MAX_PLAYERS; i++)
11779 DebugContinued("", "%d, ", stored_player[i].effective_action);
11780 DebugContinued("game:playing:player", "\n");
11785 for (i = 0; i < MAX_PLAYERS; i++)
11787 // allow engine snapshot in case of changed movement attempt
11788 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11789 (stored_player[i].effective_action & KEY_MOTION))
11790 game.snapshot.changed_action = TRUE;
11792 // allow engine snapshot in case of snapping/dropping attempt
11793 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11794 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11795 game.snapshot.changed_action = TRUE;
11797 game.snapshot.last_action[i] = stored_player[i].effective_action;
11800 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11802 GameActions_EM_Main();
11804 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11806 GameActions_SP_Main();
11808 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11810 GameActions_MM_Main();
11814 GameActions_RND_Main();
11817 BlitScreenToBitmap(backbuffer);
11819 CheckLevelSolved();
11822 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11824 if (global.show_frames_per_second)
11826 static unsigned int fps_counter = 0;
11827 static int fps_frames = 0;
11828 unsigned int fps_delay_ms = Counter() - fps_counter;
11832 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11834 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11837 fps_counter = Counter();
11839 // always draw FPS to screen after FPS value was updated
11840 redraw_mask |= REDRAW_FPS;
11843 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11844 if (GetDrawDeactivationMask() == REDRAW_NONE)
11845 redraw_mask |= REDRAW_FPS;
11849 static void GameActions_CheckSaveEngineSnapshot(void)
11851 if (!game.snapshot.save_snapshot)
11854 // clear flag for saving snapshot _before_ saving snapshot
11855 game.snapshot.save_snapshot = FALSE;
11857 SaveEngineSnapshotToList();
11860 void GameActions(void)
11864 GameActions_CheckSaveEngineSnapshot();
11867 void GameActions_EM_Main(void)
11869 byte effective_action[MAX_PLAYERS];
11870 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11873 for (i = 0; i < MAX_PLAYERS; i++)
11874 effective_action[i] = stored_player[i].effective_action;
11876 GameActions_EM(effective_action, warp_mode);
11879 void GameActions_SP_Main(void)
11881 byte effective_action[MAX_PLAYERS];
11882 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11885 for (i = 0; i < MAX_PLAYERS; i++)
11886 effective_action[i] = stored_player[i].effective_action;
11888 GameActions_SP(effective_action, warp_mode);
11890 for (i = 0; i < MAX_PLAYERS; i++)
11892 if (stored_player[i].force_dropping)
11893 stored_player[i].action |= KEY_BUTTON_DROP;
11895 stored_player[i].force_dropping = FALSE;
11899 void GameActions_MM_Main(void)
11901 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11903 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11906 void GameActions_RND_Main(void)
11911 void GameActions_RND(void)
11913 static struct MouseActionInfo mouse_action_last = { 0 };
11914 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11915 int magic_wall_x = 0, magic_wall_y = 0;
11916 int i, x, y, element, graphic, last_gfx_frame;
11918 InitPlayfieldScanModeVars();
11920 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11922 SCAN_PLAYFIELD(x, y)
11924 ChangeCount[x][y] = 0;
11925 ChangeEvent[x][y] = -1;
11929 if (game.set_centered_player)
11931 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11933 // switching to "all players" only possible if all players fit to screen
11934 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11936 game.centered_player_nr_next = game.centered_player_nr;
11937 game.set_centered_player = FALSE;
11940 // do not switch focus to non-existing (or non-active) player
11941 if (game.centered_player_nr_next >= 0 &&
11942 !stored_player[game.centered_player_nr_next].active)
11944 game.centered_player_nr_next = game.centered_player_nr;
11945 game.set_centered_player = FALSE;
11949 if (game.set_centered_player &&
11950 ScreenMovPos == 0) // screen currently aligned at tile position
11954 if (game.centered_player_nr_next == -1)
11956 setScreenCenteredToAllPlayers(&sx, &sy);
11960 sx = stored_player[game.centered_player_nr_next].jx;
11961 sy = stored_player[game.centered_player_nr_next].jy;
11964 game.centered_player_nr = game.centered_player_nr_next;
11965 game.set_centered_player = FALSE;
11967 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11968 DrawGameDoorValues();
11971 // check single step mode (set flag and clear again if any player is active)
11972 game.enter_single_step_mode =
11973 (tape.single_step && tape.recording && !tape.pausing);
11975 for (i = 0; i < MAX_PLAYERS; i++)
11977 int actual_player_action = stored_player[i].effective_action;
11980 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11981 - rnd_equinox_tetrachloride 048
11982 - rnd_equinox_tetrachloride_ii 096
11983 - rnd_emanuel_schmieg 002
11984 - doctor_sloan_ww 001, 020
11986 if (stored_player[i].MovPos == 0)
11987 CheckGravityMovement(&stored_player[i]);
11990 // overwrite programmed action with tape action
11991 if (stored_player[i].programmed_action)
11992 actual_player_action = stored_player[i].programmed_action;
11994 PlayerActions(&stored_player[i], actual_player_action);
11996 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11999 // single step pause mode may already have been toggled by "ScrollPlayer()"
12000 if (game.enter_single_step_mode && !tape.pausing)
12001 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12003 ScrollScreen(NULL, SCROLL_GO_ON);
12005 /* for backwards compatibility, the following code emulates a fixed bug that
12006 occured when pushing elements (causing elements that just made their last
12007 pushing step to already (if possible) make their first falling step in the
12008 same game frame, which is bad); this code is also needed to use the famous
12009 "spring push bug" which is used in older levels and might be wanted to be
12010 used also in newer levels, but in this case the buggy pushing code is only
12011 affecting the "spring" element and no other elements */
12013 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12015 for (i = 0; i < MAX_PLAYERS; i++)
12017 struct PlayerInfo *player = &stored_player[i];
12018 int x = player->jx;
12019 int y = player->jy;
12021 if (player->active && player->is_pushing && player->is_moving &&
12023 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12024 Tile[x][y] == EL_SPRING))
12026 ContinueMoving(x, y);
12028 // continue moving after pushing (this is actually a bug)
12029 if (!IS_MOVING(x, y))
12030 Stop[x][y] = FALSE;
12035 SCAN_PLAYFIELD(x, y)
12037 Last[x][y] = Tile[x][y];
12039 ChangeCount[x][y] = 0;
12040 ChangeEvent[x][y] = -1;
12042 // this must be handled before main playfield loop
12043 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12046 if (MovDelay[x][y] <= 0)
12050 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12053 if (MovDelay[x][y] <= 0)
12055 int element = Store[x][y];
12056 int move_direction = MovDir[x][y];
12057 int player_index_bit = Store2[x][y];
12063 TEST_DrawLevelField(x, y);
12065 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12070 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12072 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12074 Debug("game:playing:GameActions_RND", "This should never happen!");
12076 ChangePage[x][y] = -1;
12080 Stop[x][y] = FALSE;
12081 if (WasJustMoving[x][y] > 0)
12082 WasJustMoving[x][y]--;
12083 if (WasJustFalling[x][y] > 0)
12084 WasJustFalling[x][y]--;
12085 if (CheckCollision[x][y] > 0)
12086 CheckCollision[x][y]--;
12087 if (CheckImpact[x][y] > 0)
12088 CheckImpact[x][y]--;
12092 /* reset finished pushing action (not done in ContinueMoving() to allow
12093 continuous pushing animation for elements with zero push delay) */
12094 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12096 ResetGfxAnimation(x, y);
12097 TEST_DrawLevelField(x, y);
12101 if (IS_BLOCKED(x, y))
12105 Blocked2Moving(x, y, &oldx, &oldy);
12106 if (!IS_MOVING(oldx, oldy))
12108 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12109 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12110 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12111 Debug("game:playing:GameActions_RND", "This should never happen!");
12117 if (mouse_action.button)
12119 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12121 x = mouse_action.lx;
12122 y = mouse_action.ly;
12123 element = Tile[x][y];
12127 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12128 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12131 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12132 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12135 SCAN_PLAYFIELD(x, y)
12137 element = Tile[x][y];
12138 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12139 last_gfx_frame = GfxFrame[x][y];
12141 ResetGfxFrame(x, y);
12143 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12144 DrawLevelGraphicAnimation(x, y, graphic);
12146 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12147 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12148 ResetRandomAnimationValue(x, y);
12150 SetRandomAnimationValue(x, y);
12152 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12154 if (IS_INACTIVE(element))
12156 if (IS_ANIMATED(graphic))
12157 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12162 // this may take place after moving, so 'element' may have changed
12163 if (IS_CHANGING(x, y) &&
12164 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12166 int page = element_info[element].event_page_nr[CE_DELAY];
12168 HandleElementChange(x, y, page);
12170 element = Tile[x][y];
12171 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12174 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12178 element = Tile[x][y];
12179 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12181 if (IS_ANIMATED(graphic) &&
12182 !IS_MOVING(x, y) &&
12184 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12186 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12187 TEST_DrawTwinkleOnField(x, y);
12189 else if (element == EL_ACID)
12192 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12194 else if ((element == EL_EXIT_OPEN ||
12195 element == EL_EM_EXIT_OPEN ||
12196 element == EL_SP_EXIT_OPEN ||
12197 element == EL_STEEL_EXIT_OPEN ||
12198 element == EL_EM_STEEL_EXIT_OPEN ||
12199 element == EL_SP_TERMINAL ||
12200 element == EL_SP_TERMINAL_ACTIVE ||
12201 element == EL_EXTRA_TIME ||
12202 element == EL_SHIELD_NORMAL ||
12203 element == EL_SHIELD_DEADLY) &&
12204 IS_ANIMATED(graphic))
12205 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12206 else if (IS_MOVING(x, y))
12207 ContinueMoving(x, y);
12208 else if (IS_ACTIVE_BOMB(element))
12209 CheckDynamite(x, y);
12210 else if (element == EL_AMOEBA_GROWING)
12211 AmoebaGrowing(x, y);
12212 else if (element == EL_AMOEBA_SHRINKING)
12213 AmoebaShrinking(x, y);
12215 #if !USE_NEW_AMOEBA_CODE
12216 else if (IS_AMOEBALIVE(element))
12217 AmoebaReproduce(x, y);
12220 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12222 else if (element == EL_EXIT_CLOSED)
12224 else if (element == EL_EM_EXIT_CLOSED)
12226 else if (element == EL_STEEL_EXIT_CLOSED)
12227 CheckExitSteel(x, y);
12228 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12229 CheckExitSteelEM(x, y);
12230 else if (element == EL_SP_EXIT_CLOSED)
12232 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12233 element == EL_EXPANDABLE_STEELWALL_GROWING)
12234 MauerWaechst(x, y);
12235 else if (element == EL_EXPANDABLE_WALL ||
12236 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12237 element == EL_EXPANDABLE_WALL_VERTICAL ||
12238 element == EL_EXPANDABLE_WALL_ANY ||
12239 element == EL_BD_EXPANDABLE_WALL)
12240 MauerAbleger(x, y);
12241 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12242 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12243 element == EL_EXPANDABLE_STEELWALL_ANY)
12244 MauerAblegerStahl(x, y);
12245 else if (element == EL_FLAMES)
12246 CheckForDragon(x, y);
12247 else if (element == EL_EXPLOSION)
12248 ; // drawing of correct explosion animation is handled separately
12249 else if (element == EL_ELEMENT_SNAPPING ||
12250 element == EL_DIAGONAL_SHRINKING ||
12251 element == EL_DIAGONAL_GROWING)
12253 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12255 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12257 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12258 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12260 if (IS_BELT_ACTIVE(element))
12261 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12263 if (game.magic_wall_active)
12265 int jx = local_player->jx, jy = local_player->jy;
12267 // play the element sound at the position nearest to the player
12268 if ((element == EL_MAGIC_WALL_FULL ||
12269 element == EL_MAGIC_WALL_ACTIVE ||
12270 element == EL_MAGIC_WALL_EMPTYING ||
12271 element == EL_BD_MAGIC_WALL_FULL ||
12272 element == EL_BD_MAGIC_WALL_ACTIVE ||
12273 element == EL_BD_MAGIC_WALL_EMPTYING ||
12274 element == EL_DC_MAGIC_WALL_FULL ||
12275 element == EL_DC_MAGIC_WALL_ACTIVE ||
12276 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12277 ABS(x - jx) + ABS(y - jy) <
12278 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12286 #if USE_NEW_AMOEBA_CODE
12287 // new experimental amoeba growth stuff
12288 if (!(FrameCounter % 8))
12290 static unsigned int random = 1684108901;
12292 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12294 x = RND(lev_fieldx);
12295 y = RND(lev_fieldy);
12296 element = Tile[x][y];
12298 if (!IS_PLAYER(x,y) &&
12299 (element == EL_EMPTY ||
12300 CAN_GROW_INTO(element) ||
12301 element == EL_QUICKSAND_EMPTY ||
12302 element == EL_QUICKSAND_FAST_EMPTY ||
12303 element == EL_ACID_SPLASH_LEFT ||
12304 element == EL_ACID_SPLASH_RIGHT))
12306 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12307 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12308 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12309 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12310 Tile[x][y] = EL_AMOEBA_DROP;
12313 random = random * 129 + 1;
12318 game.explosions_delayed = FALSE;
12320 SCAN_PLAYFIELD(x, y)
12322 element = Tile[x][y];
12324 if (ExplodeField[x][y])
12325 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12326 else if (element == EL_EXPLOSION)
12327 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12329 ExplodeField[x][y] = EX_TYPE_NONE;
12332 game.explosions_delayed = TRUE;
12334 if (game.magic_wall_active)
12336 if (!(game.magic_wall_time_left % 4))
12338 int element = Tile[magic_wall_x][magic_wall_y];
12340 if (element == EL_BD_MAGIC_WALL_FULL ||
12341 element == EL_BD_MAGIC_WALL_ACTIVE ||
12342 element == EL_BD_MAGIC_WALL_EMPTYING)
12343 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12344 else if (element == EL_DC_MAGIC_WALL_FULL ||
12345 element == EL_DC_MAGIC_WALL_ACTIVE ||
12346 element == EL_DC_MAGIC_WALL_EMPTYING)
12347 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12349 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12352 if (game.magic_wall_time_left > 0)
12354 game.magic_wall_time_left--;
12356 if (!game.magic_wall_time_left)
12358 SCAN_PLAYFIELD(x, y)
12360 element = Tile[x][y];
12362 if (element == EL_MAGIC_WALL_ACTIVE ||
12363 element == EL_MAGIC_WALL_FULL)
12365 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12366 TEST_DrawLevelField(x, y);
12368 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12369 element == EL_BD_MAGIC_WALL_FULL)
12371 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12372 TEST_DrawLevelField(x, y);
12374 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12375 element == EL_DC_MAGIC_WALL_FULL)
12377 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12378 TEST_DrawLevelField(x, y);
12382 game.magic_wall_active = FALSE;
12387 if (game.light_time_left > 0)
12389 game.light_time_left--;
12391 if (game.light_time_left == 0)
12392 RedrawAllLightSwitchesAndInvisibleElements();
12395 if (game.timegate_time_left > 0)
12397 game.timegate_time_left--;
12399 if (game.timegate_time_left == 0)
12400 CloseAllOpenTimegates();
12403 if (game.lenses_time_left > 0)
12405 game.lenses_time_left--;
12407 if (game.lenses_time_left == 0)
12408 RedrawAllInvisibleElementsForLenses();
12411 if (game.magnify_time_left > 0)
12413 game.magnify_time_left--;
12415 if (game.magnify_time_left == 0)
12416 RedrawAllInvisibleElementsForMagnifier();
12419 for (i = 0; i < MAX_PLAYERS; i++)
12421 struct PlayerInfo *player = &stored_player[i];
12423 if (SHIELD_ON(player))
12425 if (player->shield_deadly_time_left)
12426 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12427 else if (player->shield_normal_time_left)
12428 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12432 #if USE_DELAYED_GFX_REDRAW
12433 SCAN_PLAYFIELD(x, y)
12435 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12437 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12438 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12440 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12441 DrawLevelField(x, y);
12443 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12444 DrawLevelFieldCrumbled(x, y);
12446 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12447 DrawLevelFieldCrumbledNeighbours(x, y);
12449 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12450 DrawTwinkleOnField(x, y);
12453 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12458 PlayAllPlayersSound();
12460 for (i = 0; i < MAX_PLAYERS; i++)
12462 struct PlayerInfo *player = &stored_player[i];
12464 if (player->show_envelope != 0 && (!player->active ||
12465 player->MovPos == 0))
12467 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12469 player->show_envelope = 0;
12473 // use random number generator in every frame to make it less predictable
12474 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12477 mouse_action_last = mouse_action;
12480 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12482 int min_x = x, min_y = y, max_x = x, max_y = y;
12483 int scr_fieldx = getScreenFieldSizeX();
12484 int scr_fieldy = getScreenFieldSizeY();
12487 for (i = 0; i < MAX_PLAYERS; i++)
12489 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12491 if (!stored_player[i].active || &stored_player[i] == player)
12494 min_x = MIN(min_x, jx);
12495 min_y = MIN(min_y, jy);
12496 max_x = MAX(max_x, jx);
12497 max_y = MAX(max_y, jy);
12500 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12503 static boolean AllPlayersInVisibleScreen(void)
12507 for (i = 0; i < MAX_PLAYERS; i++)
12509 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12511 if (!stored_player[i].active)
12514 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12521 void ScrollLevel(int dx, int dy)
12523 int scroll_offset = 2 * TILEX_VAR;
12526 BlitBitmap(drawto_field, drawto_field,
12527 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12528 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12529 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12530 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12531 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12532 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12536 x = (dx == 1 ? BX1 : BX2);
12537 for (y = BY1; y <= BY2; y++)
12538 DrawScreenField(x, y);
12543 y = (dy == 1 ? BY1 : BY2);
12544 for (x = BX1; x <= BX2; x++)
12545 DrawScreenField(x, y);
12548 redraw_mask |= REDRAW_FIELD;
12551 static boolean canFallDown(struct PlayerInfo *player)
12553 int jx = player->jx, jy = player->jy;
12555 return (IN_LEV_FIELD(jx, jy + 1) &&
12556 (IS_FREE(jx, jy + 1) ||
12557 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12558 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12559 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12562 static boolean canPassField(int x, int y, int move_dir)
12564 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12565 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12566 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12567 int nextx = x + dx;
12568 int nexty = y + dy;
12569 int element = Tile[x][y];
12571 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12572 !CAN_MOVE(element) &&
12573 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12574 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12575 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12578 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12580 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12581 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12582 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12586 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12587 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12588 (IS_DIGGABLE(Tile[newx][newy]) ||
12589 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12590 canPassField(newx, newy, move_dir)));
12593 static void CheckGravityMovement(struct PlayerInfo *player)
12595 if (player->gravity && !player->programmed_action)
12597 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12598 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12599 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12600 int jx = player->jx, jy = player->jy;
12601 boolean player_is_moving_to_valid_field =
12602 (!player_is_snapping &&
12603 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12604 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12605 boolean player_can_fall_down = canFallDown(player);
12607 if (player_can_fall_down &&
12608 !player_is_moving_to_valid_field)
12609 player->programmed_action = MV_DOWN;
12613 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12615 return CheckGravityMovement(player);
12617 if (player->gravity && !player->programmed_action)
12619 int jx = player->jx, jy = player->jy;
12620 boolean field_under_player_is_free =
12621 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12622 boolean player_is_standing_on_valid_field =
12623 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12624 (IS_WALKABLE(Tile[jx][jy]) &&
12625 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12627 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12628 player->programmed_action = MV_DOWN;
12633 MovePlayerOneStep()
12634 -----------------------------------------------------------------------------
12635 dx, dy: direction (non-diagonal) to try to move the player to
12636 real_dx, real_dy: direction as read from input device (can be diagonal)
12639 boolean MovePlayerOneStep(struct PlayerInfo *player,
12640 int dx, int dy, int real_dx, int real_dy)
12642 int jx = player->jx, jy = player->jy;
12643 int new_jx = jx + dx, new_jy = jy + dy;
12645 boolean player_can_move = !player->cannot_move;
12647 if (!player->active || (!dx && !dy))
12648 return MP_NO_ACTION;
12650 player->MovDir = (dx < 0 ? MV_LEFT :
12651 dx > 0 ? MV_RIGHT :
12653 dy > 0 ? MV_DOWN : MV_NONE);
12655 if (!IN_LEV_FIELD(new_jx, new_jy))
12656 return MP_NO_ACTION;
12658 if (!player_can_move)
12660 if (player->MovPos == 0)
12662 player->is_moving = FALSE;
12663 player->is_digging = FALSE;
12664 player->is_collecting = FALSE;
12665 player->is_snapping = FALSE;
12666 player->is_pushing = FALSE;
12670 if (!network.enabled && game.centered_player_nr == -1 &&
12671 !AllPlayersInSight(player, new_jx, new_jy))
12672 return MP_NO_ACTION;
12674 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12675 if (can_move != MP_MOVING)
12678 // check if DigField() has caused relocation of the player
12679 if (player->jx != jx || player->jy != jy)
12680 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12682 StorePlayer[jx][jy] = 0;
12683 player->last_jx = jx;
12684 player->last_jy = jy;
12685 player->jx = new_jx;
12686 player->jy = new_jy;
12687 StorePlayer[new_jx][new_jy] = player->element_nr;
12689 if (player->move_delay_value_next != -1)
12691 player->move_delay_value = player->move_delay_value_next;
12692 player->move_delay_value_next = -1;
12696 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12698 player->step_counter++;
12700 PlayerVisit[jx][jy] = FrameCounter;
12702 player->is_moving = TRUE;
12705 // should better be called in MovePlayer(), but this breaks some tapes
12706 ScrollPlayer(player, SCROLL_INIT);
12712 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12714 int jx = player->jx, jy = player->jy;
12715 int old_jx = jx, old_jy = jy;
12716 int moved = MP_NO_ACTION;
12718 if (!player->active)
12723 if (player->MovPos == 0)
12725 player->is_moving = FALSE;
12726 player->is_digging = FALSE;
12727 player->is_collecting = FALSE;
12728 player->is_snapping = FALSE;
12729 player->is_pushing = FALSE;
12735 if (player->move_delay > 0)
12738 player->move_delay = -1; // set to "uninitialized" value
12740 // store if player is automatically moved to next field
12741 player->is_auto_moving = (player->programmed_action != MV_NONE);
12743 // remove the last programmed player action
12744 player->programmed_action = 0;
12746 if (player->MovPos)
12748 // should only happen if pre-1.2 tape recordings are played
12749 // this is only for backward compatibility
12751 int original_move_delay_value = player->move_delay_value;
12754 Debug("game:playing:MovePlayer",
12755 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12759 // scroll remaining steps with finest movement resolution
12760 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12762 while (player->MovPos)
12764 ScrollPlayer(player, SCROLL_GO_ON);
12765 ScrollScreen(NULL, SCROLL_GO_ON);
12767 AdvanceFrameAndPlayerCounters(player->index_nr);
12770 BackToFront_WithFrameDelay(0);
12773 player->move_delay_value = original_move_delay_value;
12776 player->is_active = FALSE;
12778 if (player->last_move_dir & MV_HORIZONTAL)
12780 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12781 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12785 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12786 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12789 if (!moved && !player->is_active)
12791 player->is_moving = FALSE;
12792 player->is_digging = FALSE;
12793 player->is_collecting = FALSE;
12794 player->is_snapping = FALSE;
12795 player->is_pushing = FALSE;
12801 if (moved & MP_MOVING && !ScreenMovPos &&
12802 (player->index_nr == game.centered_player_nr ||
12803 game.centered_player_nr == -1))
12805 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12807 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12809 // actual player has left the screen -- scroll in that direction
12810 if (jx != old_jx) // player has moved horizontally
12811 scroll_x += (jx - old_jx);
12812 else // player has moved vertically
12813 scroll_y += (jy - old_jy);
12817 int offset_raw = game.scroll_delay_value;
12819 if (jx != old_jx) // player has moved horizontally
12821 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12822 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12823 int new_scroll_x = jx - MIDPOSX + offset_x;
12825 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12826 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12827 scroll_x = new_scroll_x;
12829 // don't scroll over playfield boundaries
12830 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12832 // don't scroll more than one field at a time
12833 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12835 // don't scroll against the player's moving direction
12836 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12837 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12838 scroll_x = old_scroll_x;
12840 else // player has moved vertically
12842 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12843 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12844 int new_scroll_y = jy - MIDPOSY + offset_y;
12846 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12847 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12848 scroll_y = new_scroll_y;
12850 // don't scroll over playfield boundaries
12851 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12853 // don't scroll more than one field at a time
12854 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12856 // don't scroll against the player's moving direction
12857 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12858 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12859 scroll_y = old_scroll_y;
12863 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12865 if (!network.enabled && game.centered_player_nr == -1 &&
12866 !AllPlayersInVisibleScreen())
12868 scroll_x = old_scroll_x;
12869 scroll_y = old_scroll_y;
12873 ScrollScreen(player, SCROLL_INIT);
12874 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12879 player->StepFrame = 0;
12881 if (moved & MP_MOVING)
12883 if (old_jx != jx && old_jy == jy)
12884 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12885 else if (old_jx == jx && old_jy != jy)
12886 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12888 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12890 player->last_move_dir = player->MovDir;
12891 player->is_moving = TRUE;
12892 player->is_snapping = FALSE;
12893 player->is_switching = FALSE;
12894 player->is_dropping = FALSE;
12895 player->is_dropping_pressed = FALSE;
12896 player->drop_pressed_delay = 0;
12899 // should better be called here than above, but this breaks some tapes
12900 ScrollPlayer(player, SCROLL_INIT);
12905 CheckGravityMovementWhenNotMoving(player);
12907 player->is_moving = FALSE;
12909 /* at this point, the player is allowed to move, but cannot move right now
12910 (e.g. because of something blocking the way) -- ensure that the player
12911 is also allowed to move in the next frame (in old versions before 3.1.1,
12912 the player was forced to wait again for eight frames before next try) */
12914 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12915 player->move_delay = 0; // allow direct movement in the next frame
12918 if (player->move_delay == -1) // not yet initialized by DigField()
12919 player->move_delay = player->move_delay_value;
12921 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12923 TestIfPlayerTouchesBadThing(jx, jy);
12924 TestIfPlayerTouchesCustomElement(jx, jy);
12927 if (!player->active)
12928 RemovePlayer(player);
12933 void ScrollPlayer(struct PlayerInfo *player, int mode)
12935 int jx = player->jx, jy = player->jy;
12936 int last_jx = player->last_jx, last_jy = player->last_jy;
12937 int move_stepsize = TILEX / player->move_delay_value;
12939 if (!player->active)
12942 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12945 if (mode == SCROLL_INIT)
12947 player->actual_frame_counter = FrameCounter;
12948 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12950 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12951 Tile[last_jx][last_jy] == EL_EMPTY)
12953 int last_field_block_delay = 0; // start with no blocking at all
12954 int block_delay_adjustment = player->block_delay_adjustment;
12956 // if player blocks last field, add delay for exactly one move
12957 if (player->block_last_field)
12959 last_field_block_delay += player->move_delay_value;
12961 // when blocking enabled, prevent moving up despite gravity
12962 if (player->gravity && player->MovDir == MV_UP)
12963 block_delay_adjustment = -1;
12966 // add block delay adjustment (also possible when not blocking)
12967 last_field_block_delay += block_delay_adjustment;
12969 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12970 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12973 if (player->MovPos != 0) // player has not yet reached destination
12976 else if (!FrameReached(&player->actual_frame_counter, 1))
12979 if (player->MovPos != 0)
12981 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12982 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12984 // before DrawPlayer() to draw correct player graphic for this case
12985 if (player->MovPos == 0)
12986 CheckGravityMovement(player);
12989 if (player->MovPos == 0) // player reached destination field
12991 if (player->move_delay_reset_counter > 0)
12993 player->move_delay_reset_counter--;
12995 if (player->move_delay_reset_counter == 0)
12997 // continue with normal speed after quickly moving through gate
12998 HALVE_PLAYER_SPEED(player);
13000 // be able to make the next move without delay
13001 player->move_delay = 0;
13005 player->last_jx = jx;
13006 player->last_jy = jy;
13008 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13009 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13010 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13011 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13012 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13013 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13014 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13015 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13017 ExitPlayer(player);
13019 if (game.players_still_needed == 0 &&
13020 (game.friends_still_needed == 0 ||
13021 IS_SP_ELEMENT(Tile[jx][jy])))
13025 // this breaks one level: "machine", level 000
13027 int move_direction = player->MovDir;
13028 int enter_side = MV_DIR_OPPOSITE(move_direction);
13029 int leave_side = move_direction;
13030 int old_jx = last_jx;
13031 int old_jy = last_jy;
13032 int old_element = Tile[old_jx][old_jy];
13033 int new_element = Tile[jx][jy];
13035 if (IS_CUSTOM_ELEMENT(old_element))
13036 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13038 player->index_bit, leave_side);
13040 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13041 CE_PLAYER_LEAVES_X,
13042 player->index_bit, leave_side);
13044 if (IS_CUSTOM_ELEMENT(new_element))
13045 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13046 player->index_bit, enter_side);
13048 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13049 CE_PLAYER_ENTERS_X,
13050 player->index_bit, enter_side);
13052 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13053 CE_MOVE_OF_X, move_direction);
13056 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13058 TestIfPlayerTouchesBadThing(jx, jy);
13059 TestIfPlayerTouchesCustomElement(jx, jy);
13061 /* needed because pushed element has not yet reached its destination,
13062 so it would trigger a change event at its previous field location */
13063 if (!player->is_pushing)
13064 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13066 if (level.finish_dig_collect &&
13067 (player->is_digging || player->is_collecting))
13069 int last_element = player->last_removed_element;
13070 int move_direction = player->MovDir;
13071 int enter_side = MV_DIR_OPPOSITE(move_direction);
13072 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13073 CE_PLAYER_COLLECTS_X);
13075 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13076 player->index_bit, enter_side);
13078 player->last_removed_element = EL_UNDEFINED;
13081 if (!player->active)
13082 RemovePlayer(player);
13085 if (!game.LevelSolved && level.use_step_counter)
13095 if (TimeLeft <= 10 && setup.time_limit)
13096 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13098 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13100 DisplayGameControlValues();
13102 if (!TimeLeft && setup.time_limit)
13103 for (i = 0; i < MAX_PLAYERS; i++)
13104 KillPlayer(&stored_player[i]);
13106 else if (game.no_time_limit && !game.all_players_gone)
13108 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13110 DisplayGameControlValues();
13114 if (tape.single_step && tape.recording && !tape.pausing &&
13115 !player->programmed_action)
13116 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13118 if (!player->programmed_action)
13119 CheckSaveEngineSnapshot(player);
13123 void ScrollScreen(struct PlayerInfo *player, int mode)
13125 static unsigned int screen_frame_counter = 0;
13127 if (mode == SCROLL_INIT)
13129 // set scrolling step size according to actual player's moving speed
13130 ScrollStepSize = TILEX / player->move_delay_value;
13132 screen_frame_counter = FrameCounter;
13133 ScreenMovDir = player->MovDir;
13134 ScreenMovPos = player->MovPos;
13135 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13138 else if (!FrameReached(&screen_frame_counter, 1))
13143 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13144 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13145 redraw_mask |= REDRAW_FIELD;
13148 ScreenMovDir = MV_NONE;
13151 void TestIfPlayerTouchesCustomElement(int x, int y)
13153 static int xy[4][2] =
13160 static int trigger_sides[4][2] =
13162 // center side border side
13163 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13164 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13165 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13166 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13168 static int touch_dir[4] =
13170 MV_LEFT | MV_RIGHT,
13175 int center_element = Tile[x][y]; // should always be non-moving!
13178 for (i = 0; i < NUM_DIRECTIONS; i++)
13180 int xx = x + xy[i][0];
13181 int yy = y + xy[i][1];
13182 int center_side = trigger_sides[i][0];
13183 int border_side = trigger_sides[i][1];
13184 int border_element;
13186 if (!IN_LEV_FIELD(xx, yy))
13189 if (IS_PLAYER(x, y)) // player found at center element
13191 struct PlayerInfo *player = PLAYERINFO(x, y);
13193 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13194 border_element = Tile[xx][yy]; // may be moving!
13195 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13196 border_element = Tile[xx][yy];
13197 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13198 border_element = MovingOrBlocked2Element(xx, yy);
13200 continue; // center and border element do not touch
13202 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13203 player->index_bit, border_side);
13204 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13205 CE_PLAYER_TOUCHES_X,
13206 player->index_bit, border_side);
13209 /* use player element that is initially defined in the level playfield,
13210 not the player element that corresponds to the runtime player number
13211 (example: a level that contains EL_PLAYER_3 as the only player would
13212 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13213 int player_element = PLAYERINFO(x, y)->initial_element;
13215 CheckElementChangeBySide(xx, yy, border_element, player_element,
13216 CE_TOUCHING_X, border_side);
13219 else if (IS_PLAYER(xx, yy)) // player found at border element
13221 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13223 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13225 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13226 continue; // center and border element do not touch
13229 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13230 player->index_bit, center_side);
13231 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13232 CE_PLAYER_TOUCHES_X,
13233 player->index_bit, center_side);
13236 /* use player element that is initially defined in the level playfield,
13237 not the player element that corresponds to the runtime player number
13238 (example: a level that contains EL_PLAYER_3 as the only player would
13239 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13240 int player_element = PLAYERINFO(xx, yy)->initial_element;
13242 CheckElementChangeBySide(x, y, center_element, player_element,
13243 CE_TOUCHING_X, center_side);
13251 void TestIfElementTouchesCustomElement(int x, int y)
13253 static int xy[4][2] =
13260 static int trigger_sides[4][2] =
13262 // center side border side
13263 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13264 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13265 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13266 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13268 static int touch_dir[4] =
13270 MV_LEFT | MV_RIGHT,
13275 boolean change_center_element = FALSE;
13276 int center_element = Tile[x][y]; // should always be non-moving!
13277 int border_element_old[NUM_DIRECTIONS];
13280 for (i = 0; i < NUM_DIRECTIONS; i++)
13282 int xx = x + xy[i][0];
13283 int yy = y + xy[i][1];
13284 int border_element;
13286 border_element_old[i] = -1;
13288 if (!IN_LEV_FIELD(xx, yy))
13291 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13292 border_element = Tile[xx][yy]; // may be moving!
13293 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13294 border_element = Tile[xx][yy];
13295 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13296 border_element = MovingOrBlocked2Element(xx, yy);
13298 continue; // center and border element do not touch
13300 border_element_old[i] = border_element;
13303 for (i = 0; i < NUM_DIRECTIONS; i++)
13305 int xx = x + xy[i][0];
13306 int yy = y + xy[i][1];
13307 int center_side = trigger_sides[i][0];
13308 int border_element = border_element_old[i];
13310 if (border_element == -1)
13313 // check for change of border element
13314 CheckElementChangeBySide(xx, yy, border_element, center_element,
13315 CE_TOUCHING_X, center_side);
13317 // (center element cannot be player, so we dont have to check this here)
13320 for (i = 0; i < NUM_DIRECTIONS; i++)
13322 int xx = x + xy[i][0];
13323 int yy = y + xy[i][1];
13324 int border_side = trigger_sides[i][1];
13325 int border_element = border_element_old[i];
13327 if (border_element == -1)
13330 // check for change of center element (but change it only once)
13331 if (!change_center_element)
13332 change_center_element =
13333 CheckElementChangeBySide(x, y, center_element, border_element,
13334 CE_TOUCHING_X, border_side);
13336 if (IS_PLAYER(xx, yy))
13338 /* use player element that is initially defined in the level playfield,
13339 not the player element that corresponds to the runtime player number
13340 (example: a level that contains EL_PLAYER_3 as the only player would
13341 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13342 int player_element = PLAYERINFO(xx, yy)->initial_element;
13344 CheckElementChangeBySide(x, y, center_element, player_element,
13345 CE_TOUCHING_X, border_side);
13350 void TestIfElementHitsCustomElement(int x, int y, int direction)
13352 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13353 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13354 int hitx = x + dx, hity = y + dy;
13355 int hitting_element = Tile[x][y];
13356 int touched_element;
13358 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13361 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13362 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13364 if (IN_LEV_FIELD(hitx, hity))
13366 int opposite_direction = MV_DIR_OPPOSITE(direction);
13367 int hitting_side = direction;
13368 int touched_side = opposite_direction;
13369 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13370 MovDir[hitx][hity] != direction ||
13371 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13377 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13378 CE_HITTING_X, touched_side);
13380 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13381 CE_HIT_BY_X, hitting_side);
13383 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13384 CE_HIT_BY_SOMETHING, opposite_direction);
13386 if (IS_PLAYER(hitx, hity))
13388 /* use player element that is initially defined in the level playfield,
13389 not the player element that corresponds to the runtime player number
13390 (example: a level that contains EL_PLAYER_3 as the only player would
13391 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13392 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13394 CheckElementChangeBySide(x, y, hitting_element, player_element,
13395 CE_HITTING_X, touched_side);
13400 // "hitting something" is also true when hitting the playfield border
13401 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13402 CE_HITTING_SOMETHING, direction);
13405 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13407 int i, kill_x = -1, kill_y = -1;
13409 int bad_element = -1;
13410 static int test_xy[4][2] =
13417 static int test_dir[4] =
13425 for (i = 0; i < NUM_DIRECTIONS; i++)
13427 int test_x, test_y, test_move_dir, test_element;
13429 test_x = good_x + test_xy[i][0];
13430 test_y = good_y + test_xy[i][1];
13432 if (!IN_LEV_FIELD(test_x, test_y))
13436 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13438 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13440 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13441 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13443 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13444 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13448 bad_element = test_element;
13454 if (kill_x != -1 || kill_y != -1)
13456 if (IS_PLAYER(good_x, good_y))
13458 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13460 if (player->shield_deadly_time_left > 0 &&
13461 !IS_INDESTRUCTIBLE(bad_element))
13462 Bang(kill_x, kill_y);
13463 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13464 KillPlayer(player);
13467 Bang(good_x, good_y);
13471 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13473 int i, kill_x = -1, kill_y = -1;
13474 int bad_element = Tile[bad_x][bad_y];
13475 static int test_xy[4][2] =
13482 static int touch_dir[4] =
13484 MV_LEFT | MV_RIGHT,
13489 static int test_dir[4] =
13497 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13500 for (i = 0; i < NUM_DIRECTIONS; i++)
13502 int test_x, test_y, test_move_dir, test_element;
13504 test_x = bad_x + test_xy[i][0];
13505 test_y = bad_y + test_xy[i][1];
13507 if (!IN_LEV_FIELD(test_x, test_y))
13511 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13513 test_element = Tile[test_x][test_y];
13515 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13516 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13518 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13519 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13521 // good thing is player or penguin that does not move away
13522 if (IS_PLAYER(test_x, test_y))
13524 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13526 if (bad_element == EL_ROBOT && player->is_moving)
13527 continue; // robot does not kill player if he is moving
13529 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13531 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13532 continue; // center and border element do not touch
13540 else if (test_element == EL_PENGUIN)
13550 if (kill_x != -1 || kill_y != -1)
13552 if (IS_PLAYER(kill_x, kill_y))
13554 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13556 if (player->shield_deadly_time_left > 0 &&
13557 !IS_INDESTRUCTIBLE(bad_element))
13558 Bang(bad_x, bad_y);
13559 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13560 KillPlayer(player);
13563 Bang(kill_x, kill_y);
13567 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13569 int bad_element = Tile[bad_x][bad_y];
13570 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13571 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13572 int test_x = bad_x + dx, test_y = bad_y + dy;
13573 int test_move_dir, test_element;
13574 int kill_x = -1, kill_y = -1;
13576 if (!IN_LEV_FIELD(test_x, test_y))
13580 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13582 test_element = Tile[test_x][test_y];
13584 if (test_move_dir != bad_move_dir)
13586 // good thing can be player or penguin that does not move away
13587 if (IS_PLAYER(test_x, test_y))
13589 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13591 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13592 player as being hit when he is moving towards the bad thing, because
13593 the "get hit by" condition would be lost after the player stops) */
13594 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13595 return; // player moves away from bad thing
13600 else if (test_element == EL_PENGUIN)
13607 if (kill_x != -1 || kill_y != -1)
13609 if (IS_PLAYER(kill_x, kill_y))
13611 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13613 if (player->shield_deadly_time_left > 0 &&
13614 !IS_INDESTRUCTIBLE(bad_element))
13615 Bang(bad_x, bad_y);
13616 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13617 KillPlayer(player);
13620 Bang(kill_x, kill_y);
13624 void TestIfPlayerTouchesBadThing(int x, int y)
13626 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13629 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13631 TestIfGoodThingHitsBadThing(x, y, move_dir);
13634 void TestIfBadThingTouchesPlayer(int x, int y)
13636 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13639 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13641 TestIfBadThingHitsGoodThing(x, y, move_dir);
13644 void TestIfFriendTouchesBadThing(int x, int y)
13646 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13649 void TestIfBadThingTouchesFriend(int x, int y)
13651 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13654 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13656 int i, kill_x = bad_x, kill_y = bad_y;
13657 static int xy[4][2] =
13665 for (i = 0; i < NUM_DIRECTIONS; i++)
13669 x = bad_x + xy[i][0];
13670 y = bad_y + xy[i][1];
13671 if (!IN_LEV_FIELD(x, y))
13674 element = Tile[x][y];
13675 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13676 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13684 if (kill_x != bad_x || kill_y != bad_y)
13685 Bang(bad_x, bad_y);
13688 void KillPlayer(struct PlayerInfo *player)
13690 int jx = player->jx, jy = player->jy;
13692 if (!player->active)
13696 Debug("game:playing:KillPlayer",
13697 "0: killed == %d, active == %d, reanimated == %d",
13698 player->killed, player->active, player->reanimated);
13701 /* the following code was introduced to prevent an infinite loop when calling
13703 -> CheckTriggeredElementChangeExt()
13704 -> ExecuteCustomElementAction()
13706 -> (infinitely repeating the above sequence of function calls)
13707 which occurs when killing the player while having a CE with the setting
13708 "kill player X when explosion of <player X>"; the solution using a new
13709 field "player->killed" was chosen for backwards compatibility, although
13710 clever use of the fields "player->active" etc. would probably also work */
13712 if (player->killed)
13716 player->killed = TRUE;
13718 // remove accessible field at the player's position
13719 Tile[jx][jy] = EL_EMPTY;
13721 // deactivate shield (else Bang()/Explode() would not work right)
13722 player->shield_normal_time_left = 0;
13723 player->shield_deadly_time_left = 0;
13726 Debug("game:playing:KillPlayer",
13727 "1: killed == %d, active == %d, reanimated == %d",
13728 player->killed, player->active, player->reanimated);
13734 Debug("game:playing:KillPlayer",
13735 "2: killed == %d, active == %d, reanimated == %d",
13736 player->killed, player->active, player->reanimated);
13739 if (player->reanimated) // killed player may have been reanimated
13740 player->killed = player->reanimated = FALSE;
13742 BuryPlayer(player);
13745 static void KillPlayerUnlessEnemyProtected(int x, int y)
13747 if (!PLAYER_ENEMY_PROTECTED(x, y))
13748 KillPlayer(PLAYERINFO(x, y));
13751 static void KillPlayerUnlessExplosionProtected(int x, int y)
13753 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13754 KillPlayer(PLAYERINFO(x, y));
13757 void BuryPlayer(struct PlayerInfo *player)
13759 int jx = player->jx, jy = player->jy;
13761 if (!player->active)
13764 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13765 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13767 RemovePlayer(player);
13769 player->buried = TRUE;
13771 if (game.all_players_gone)
13772 game.GameOver = TRUE;
13775 void RemovePlayer(struct PlayerInfo *player)
13777 int jx = player->jx, jy = player->jy;
13778 int i, found = FALSE;
13780 player->present = FALSE;
13781 player->active = FALSE;
13783 // required for some CE actions (even if the player is not active anymore)
13784 player->MovPos = 0;
13786 if (!ExplodeField[jx][jy])
13787 StorePlayer[jx][jy] = 0;
13789 if (player->is_moving)
13790 TEST_DrawLevelField(player->last_jx, player->last_jy);
13792 for (i = 0; i < MAX_PLAYERS; i++)
13793 if (stored_player[i].active)
13798 game.all_players_gone = TRUE;
13799 game.GameOver = TRUE;
13802 game.exit_x = game.robot_wheel_x = jx;
13803 game.exit_y = game.robot_wheel_y = jy;
13806 void ExitPlayer(struct PlayerInfo *player)
13808 DrawPlayer(player); // needed here only to cleanup last field
13809 RemovePlayer(player);
13811 if (game.players_still_needed > 0)
13812 game.players_still_needed--;
13815 static void SetFieldForSnapping(int x, int y, int element, int direction,
13816 int player_index_bit)
13818 struct ElementInfo *ei = &element_info[element];
13819 int direction_bit = MV_DIR_TO_BIT(direction);
13820 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13821 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13822 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13824 Tile[x][y] = EL_ELEMENT_SNAPPING;
13825 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13826 MovDir[x][y] = direction;
13827 Store[x][y] = element;
13828 Store2[x][y] = player_index_bit;
13830 ResetGfxAnimation(x, y);
13832 GfxElement[x][y] = element;
13833 GfxAction[x][y] = action;
13834 GfxDir[x][y] = direction;
13835 GfxFrame[x][y] = -1;
13838 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
13839 int player_index_bit)
13841 TestIfElementTouchesCustomElement(x, y); // for empty space
13843 if (level.finish_dig_collect)
13845 int dig_side = MV_DIR_OPPOSITE(direction);
13847 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13848 player_index_bit, dig_side);
13853 =============================================================================
13854 checkDiagonalPushing()
13855 -----------------------------------------------------------------------------
13856 check if diagonal input device direction results in pushing of object
13857 (by checking if the alternative direction is walkable, diggable, ...)
13858 =============================================================================
13861 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13862 int x, int y, int real_dx, int real_dy)
13864 int jx, jy, dx, dy, xx, yy;
13866 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13869 // diagonal direction: check alternative direction
13874 xx = jx + (dx == 0 ? real_dx : 0);
13875 yy = jy + (dy == 0 ? real_dy : 0);
13877 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13881 =============================================================================
13883 -----------------------------------------------------------------------------
13884 x, y: field next to player (non-diagonal) to try to dig to
13885 real_dx, real_dy: direction as read from input device (can be diagonal)
13886 =============================================================================
13889 static int DigField(struct PlayerInfo *player,
13890 int oldx, int oldy, int x, int y,
13891 int real_dx, int real_dy, int mode)
13893 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13894 boolean player_was_pushing = player->is_pushing;
13895 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13896 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13897 int jx = oldx, jy = oldy;
13898 int dx = x - jx, dy = y - jy;
13899 int nextx = x + dx, nexty = y + dy;
13900 int move_direction = (dx == -1 ? MV_LEFT :
13901 dx == +1 ? MV_RIGHT :
13903 dy == +1 ? MV_DOWN : MV_NONE);
13904 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13905 int dig_side = MV_DIR_OPPOSITE(move_direction);
13906 int old_element = Tile[jx][jy];
13907 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13910 if (is_player) // function can also be called by EL_PENGUIN
13912 if (player->MovPos == 0)
13914 player->is_digging = FALSE;
13915 player->is_collecting = FALSE;
13918 if (player->MovPos == 0) // last pushing move finished
13919 player->is_pushing = FALSE;
13921 if (mode == DF_NO_PUSH) // player just stopped pushing
13923 player->is_switching = FALSE;
13924 player->push_delay = -1;
13926 return MP_NO_ACTION;
13930 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13931 old_element = Back[jx][jy];
13933 // in case of element dropped at player position, check background
13934 else if (Back[jx][jy] != EL_EMPTY &&
13935 game.engine_version >= VERSION_IDENT(2,2,0,0))
13936 old_element = Back[jx][jy];
13938 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13939 return MP_NO_ACTION; // field has no opening in this direction
13941 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13942 return MP_NO_ACTION; // field has no opening in this direction
13944 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13948 Tile[jx][jy] = player->artwork_element;
13949 InitMovingField(jx, jy, MV_DOWN);
13950 Store[jx][jy] = EL_ACID;
13951 ContinueMoving(jx, jy);
13952 BuryPlayer(player);
13954 return MP_DONT_RUN_INTO;
13957 if (player_can_move && DONT_RUN_INTO(element))
13959 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13961 return MP_DONT_RUN_INTO;
13964 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13965 return MP_NO_ACTION;
13967 collect_count = element_info[element].collect_count_initial;
13969 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13970 return MP_NO_ACTION;
13972 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13973 player_can_move = player_can_move_or_snap;
13975 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13976 game.engine_version >= VERSION_IDENT(2,2,0,0))
13978 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13979 player->index_bit, dig_side);
13980 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13981 player->index_bit, dig_side);
13983 if (element == EL_DC_LANDMINE)
13986 if (Tile[x][y] != element) // field changed by snapping
13989 return MP_NO_ACTION;
13992 if (player->gravity && is_player && !player->is_auto_moving &&
13993 canFallDown(player) && move_direction != MV_DOWN &&
13994 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13995 return MP_NO_ACTION; // player cannot walk here due to gravity
13997 if (player_can_move &&
13998 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14000 int sound_element = SND_ELEMENT(element);
14001 int sound_action = ACTION_WALKING;
14003 if (IS_RND_GATE(element))
14005 if (!player->key[RND_GATE_NR(element)])
14006 return MP_NO_ACTION;
14008 else if (IS_RND_GATE_GRAY(element))
14010 if (!player->key[RND_GATE_GRAY_NR(element)])
14011 return MP_NO_ACTION;
14013 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14015 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14016 return MP_NO_ACTION;
14018 else if (element == EL_EXIT_OPEN ||
14019 element == EL_EM_EXIT_OPEN ||
14020 element == EL_EM_EXIT_OPENING ||
14021 element == EL_STEEL_EXIT_OPEN ||
14022 element == EL_EM_STEEL_EXIT_OPEN ||
14023 element == EL_EM_STEEL_EXIT_OPENING ||
14024 element == EL_SP_EXIT_OPEN ||
14025 element == EL_SP_EXIT_OPENING)
14027 sound_action = ACTION_PASSING; // player is passing exit
14029 else if (element == EL_EMPTY)
14031 sound_action = ACTION_MOVING; // nothing to walk on
14034 // play sound from background or player, whatever is available
14035 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14036 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14038 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14040 else if (player_can_move &&
14041 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14043 if (!ACCESS_FROM(element, opposite_direction))
14044 return MP_NO_ACTION; // field not accessible from this direction
14046 if (CAN_MOVE(element)) // only fixed elements can be passed!
14047 return MP_NO_ACTION;
14049 if (IS_EM_GATE(element))
14051 if (!player->key[EM_GATE_NR(element)])
14052 return MP_NO_ACTION;
14054 else if (IS_EM_GATE_GRAY(element))
14056 if (!player->key[EM_GATE_GRAY_NR(element)])
14057 return MP_NO_ACTION;
14059 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14061 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14062 return MP_NO_ACTION;
14064 else if (IS_EMC_GATE(element))
14066 if (!player->key[EMC_GATE_NR(element)])
14067 return MP_NO_ACTION;
14069 else if (IS_EMC_GATE_GRAY(element))
14071 if (!player->key[EMC_GATE_GRAY_NR(element)])
14072 return MP_NO_ACTION;
14074 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14076 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14077 return MP_NO_ACTION;
14079 else if (element == EL_DC_GATE_WHITE ||
14080 element == EL_DC_GATE_WHITE_GRAY ||
14081 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14083 if (player->num_white_keys == 0)
14084 return MP_NO_ACTION;
14086 player->num_white_keys--;
14088 else if (IS_SP_PORT(element))
14090 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14091 element == EL_SP_GRAVITY_PORT_RIGHT ||
14092 element == EL_SP_GRAVITY_PORT_UP ||
14093 element == EL_SP_GRAVITY_PORT_DOWN)
14094 player->gravity = !player->gravity;
14095 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14096 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14097 element == EL_SP_GRAVITY_ON_PORT_UP ||
14098 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14099 player->gravity = TRUE;
14100 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14101 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14102 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14103 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14104 player->gravity = FALSE;
14107 // automatically move to the next field with double speed
14108 player->programmed_action = move_direction;
14110 if (player->move_delay_reset_counter == 0)
14112 player->move_delay_reset_counter = 2; // two double speed steps
14114 DOUBLE_PLAYER_SPEED(player);
14117 PlayLevelSoundAction(x, y, ACTION_PASSING);
14119 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14123 if (mode != DF_SNAP)
14125 GfxElement[x][y] = GFX_ELEMENT(element);
14126 player->is_digging = TRUE;
14129 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14131 // use old behaviour for old levels (digging)
14132 if (!level.finish_dig_collect)
14134 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14135 player->index_bit, dig_side);
14137 // if digging triggered player relocation, finish digging tile
14138 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14139 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14142 if (mode == DF_SNAP)
14144 if (level.block_snap_field)
14145 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14147 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14149 // use old behaviour for old levels (snapping)
14150 if (!level.finish_dig_collect)
14151 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14152 player->index_bit, dig_side);
14155 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14159 if (is_player && mode != DF_SNAP)
14161 GfxElement[x][y] = element;
14162 player->is_collecting = TRUE;
14165 if (element == EL_SPEED_PILL)
14167 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14169 else if (element == EL_EXTRA_TIME && level.time > 0)
14171 TimeLeft += level.extra_time;
14173 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14175 DisplayGameControlValues();
14177 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14179 player->shield_normal_time_left += level.shield_normal_time;
14180 if (element == EL_SHIELD_DEADLY)
14181 player->shield_deadly_time_left += level.shield_deadly_time;
14183 else if (element == EL_DYNAMITE ||
14184 element == EL_EM_DYNAMITE ||
14185 element == EL_SP_DISK_RED)
14187 if (player->inventory_size < MAX_INVENTORY_SIZE)
14188 player->inventory_element[player->inventory_size++] = element;
14190 DrawGameDoorValues();
14192 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14194 player->dynabomb_count++;
14195 player->dynabombs_left++;
14197 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14199 player->dynabomb_size++;
14201 else if (element == EL_DYNABOMB_INCREASE_POWER)
14203 player->dynabomb_xl = TRUE;
14205 else if (IS_KEY(element))
14207 player->key[KEY_NR(element)] = TRUE;
14209 DrawGameDoorValues();
14211 else if (element == EL_DC_KEY_WHITE)
14213 player->num_white_keys++;
14215 // display white keys?
14216 // DrawGameDoorValues();
14218 else if (IS_ENVELOPE(element))
14220 player->show_envelope = element;
14222 else if (element == EL_EMC_LENSES)
14224 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14226 RedrawAllInvisibleElementsForLenses();
14228 else if (element == EL_EMC_MAGNIFIER)
14230 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14232 RedrawAllInvisibleElementsForMagnifier();
14234 else if (IS_DROPPABLE(element) ||
14235 IS_THROWABLE(element)) // can be collected and dropped
14239 if (collect_count == 0)
14240 player->inventory_infinite_element = element;
14242 for (i = 0; i < collect_count; i++)
14243 if (player->inventory_size < MAX_INVENTORY_SIZE)
14244 player->inventory_element[player->inventory_size++] = element;
14246 DrawGameDoorValues();
14248 else if (collect_count > 0)
14250 game.gems_still_needed -= collect_count;
14251 if (game.gems_still_needed < 0)
14252 game.gems_still_needed = 0;
14254 game.snapshot.collected_item = TRUE;
14256 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14258 DisplayGameControlValues();
14261 RaiseScoreElement(element);
14262 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14264 // use old behaviour for old levels (collecting)
14265 if (!level.finish_dig_collect && is_player)
14267 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14268 player->index_bit, dig_side);
14270 // if collecting triggered player relocation, finish collecting tile
14271 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14272 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14275 if (mode == DF_SNAP)
14277 if (level.block_snap_field)
14278 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14280 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14282 // use old behaviour for old levels (snapping)
14283 if (!level.finish_dig_collect)
14284 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14285 player->index_bit, dig_side);
14288 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14290 if (mode == DF_SNAP && element != EL_BD_ROCK)
14291 return MP_NO_ACTION;
14293 if (CAN_FALL(element) && dy)
14294 return MP_NO_ACTION;
14296 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14297 !(element == EL_SPRING && level.use_spring_bug))
14298 return MP_NO_ACTION;
14300 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14301 ((move_direction & MV_VERTICAL &&
14302 ((element_info[element].move_pattern & MV_LEFT &&
14303 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14304 (element_info[element].move_pattern & MV_RIGHT &&
14305 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14306 (move_direction & MV_HORIZONTAL &&
14307 ((element_info[element].move_pattern & MV_UP &&
14308 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14309 (element_info[element].move_pattern & MV_DOWN &&
14310 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14311 return MP_NO_ACTION;
14313 // do not push elements already moving away faster than player
14314 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14315 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14316 return MP_NO_ACTION;
14318 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14320 if (player->push_delay_value == -1 || !player_was_pushing)
14321 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14323 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14325 if (player->push_delay_value == -1)
14326 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14328 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14330 if (!player->is_pushing)
14331 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14334 player->is_pushing = TRUE;
14335 player->is_active = TRUE;
14337 if (!(IN_LEV_FIELD(nextx, nexty) &&
14338 (IS_FREE(nextx, nexty) ||
14339 (IS_SB_ELEMENT(element) &&
14340 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14341 (IS_CUSTOM_ELEMENT(element) &&
14342 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14343 return MP_NO_ACTION;
14345 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14346 return MP_NO_ACTION;
14348 if (player->push_delay == -1) // new pushing; restart delay
14349 player->push_delay = 0;
14351 if (player->push_delay < player->push_delay_value &&
14352 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14353 element != EL_SPRING && element != EL_BALLOON)
14355 // make sure that there is no move delay before next try to push
14356 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14357 player->move_delay = 0;
14359 return MP_NO_ACTION;
14362 if (IS_CUSTOM_ELEMENT(element) &&
14363 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14365 if (!DigFieldByCE(nextx, nexty, element))
14366 return MP_NO_ACTION;
14369 if (IS_SB_ELEMENT(element))
14371 boolean sokoban_task_solved = FALSE;
14373 if (element == EL_SOKOBAN_FIELD_FULL)
14375 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14377 IncrementSokobanFieldsNeeded();
14378 IncrementSokobanObjectsNeeded();
14381 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14383 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14385 DecrementSokobanFieldsNeeded();
14386 DecrementSokobanObjectsNeeded();
14388 // sokoban object was pushed from empty field to sokoban field
14389 if (Back[x][y] == EL_EMPTY)
14390 sokoban_task_solved = TRUE;
14393 Tile[x][y] = EL_SOKOBAN_OBJECT;
14395 if (Back[x][y] == Back[nextx][nexty])
14396 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14397 else if (Back[x][y] != 0)
14398 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14401 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14404 if (sokoban_task_solved &&
14405 game.sokoban_fields_still_needed == 0 &&
14406 game.sokoban_objects_still_needed == 0 &&
14407 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14409 game.players_still_needed = 0;
14413 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14417 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14419 InitMovingField(x, y, move_direction);
14420 GfxAction[x][y] = ACTION_PUSHING;
14422 if (mode == DF_SNAP)
14423 ContinueMoving(x, y);
14425 MovPos[x][y] = (dx != 0 ? dx : dy);
14427 Pushed[x][y] = TRUE;
14428 Pushed[nextx][nexty] = TRUE;
14430 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14431 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14433 player->push_delay_value = -1; // get new value later
14435 // check for element change _after_ element has been pushed
14436 if (game.use_change_when_pushing_bug)
14438 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14439 player->index_bit, dig_side);
14440 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14441 player->index_bit, dig_side);
14444 else if (IS_SWITCHABLE(element))
14446 if (PLAYER_SWITCHING(player, x, y))
14448 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14449 player->index_bit, dig_side);
14454 player->is_switching = TRUE;
14455 player->switch_x = x;
14456 player->switch_y = y;
14458 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14460 if (element == EL_ROBOT_WHEEL)
14462 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14464 game.robot_wheel_x = x;
14465 game.robot_wheel_y = y;
14466 game.robot_wheel_active = TRUE;
14468 TEST_DrawLevelField(x, y);
14470 else if (element == EL_SP_TERMINAL)
14474 SCAN_PLAYFIELD(xx, yy)
14476 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14480 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14482 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14484 ResetGfxAnimation(xx, yy);
14485 TEST_DrawLevelField(xx, yy);
14489 else if (IS_BELT_SWITCH(element))
14491 ToggleBeltSwitch(x, y);
14493 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14494 element == EL_SWITCHGATE_SWITCH_DOWN ||
14495 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14496 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14498 ToggleSwitchgateSwitch(x, y);
14500 else if (element == EL_LIGHT_SWITCH ||
14501 element == EL_LIGHT_SWITCH_ACTIVE)
14503 ToggleLightSwitch(x, y);
14505 else if (element == EL_TIMEGATE_SWITCH ||
14506 element == EL_DC_TIMEGATE_SWITCH)
14508 ActivateTimegateSwitch(x, y);
14510 else if (element == EL_BALLOON_SWITCH_LEFT ||
14511 element == EL_BALLOON_SWITCH_RIGHT ||
14512 element == EL_BALLOON_SWITCH_UP ||
14513 element == EL_BALLOON_SWITCH_DOWN ||
14514 element == EL_BALLOON_SWITCH_NONE ||
14515 element == EL_BALLOON_SWITCH_ANY)
14517 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14518 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14519 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14520 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14521 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14524 else if (element == EL_LAMP)
14526 Tile[x][y] = EL_LAMP_ACTIVE;
14527 game.lights_still_needed--;
14529 ResetGfxAnimation(x, y);
14530 TEST_DrawLevelField(x, y);
14532 else if (element == EL_TIME_ORB_FULL)
14534 Tile[x][y] = EL_TIME_ORB_EMPTY;
14536 if (level.time > 0 || level.use_time_orb_bug)
14538 TimeLeft += level.time_orb_time;
14539 game.no_time_limit = FALSE;
14541 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14543 DisplayGameControlValues();
14546 ResetGfxAnimation(x, y);
14547 TEST_DrawLevelField(x, y);
14549 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14550 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14554 game.ball_active = !game.ball_active;
14556 SCAN_PLAYFIELD(xx, yy)
14558 int e = Tile[xx][yy];
14560 if (game.ball_active)
14562 if (e == EL_EMC_MAGIC_BALL)
14563 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14564 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14565 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14569 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14570 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14571 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14572 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14577 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14578 player->index_bit, dig_side);
14580 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14581 player->index_bit, dig_side);
14583 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14584 player->index_bit, dig_side);
14590 if (!PLAYER_SWITCHING(player, x, y))
14592 player->is_switching = TRUE;
14593 player->switch_x = x;
14594 player->switch_y = y;
14596 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14597 player->index_bit, dig_side);
14598 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14599 player->index_bit, dig_side);
14601 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14602 player->index_bit, dig_side);
14603 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14604 player->index_bit, dig_side);
14607 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14608 player->index_bit, dig_side);
14609 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14610 player->index_bit, dig_side);
14612 return MP_NO_ACTION;
14615 player->push_delay = -1;
14617 if (is_player) // function can also be called by EL_PENGUIN
14619 if (Tile[x][y] != element) // really digged/collected something
14621 player->is_collecting = !player->is_digging;
14622 player->is_active = TRUE;
14624 player->last_removed_element = element;
14631 static boolean DigFieldByCE(int x, int y, int digging_element)
14633 int element = Tile[x][y];
14635 if (!IS_FREE(x, y))
14637 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14638 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14641 // no element can dig solid indestructible elements
14642 if (IS_INDESTRUCTIBLE(element) &&
14643 !IS_DIGGABLE(element) &&
14644 !IS_COLLECTIBLE(element))
14647 if (AmoebaNr[x][y] &&
14648 (element == EL_AMOEBA_FULL ||
14649 element == EL_BD_AMOEBA ||
14650 element == EL_AMOEBA_GROWING))
14652 AmoebaCnt[AmoebaNr[x][y]]--;
14653 AmoebaCnt2[AmoebaNr[x][y]]--;
14656 if (IS_MOVING(x, y))
14657 RemoveMovingField(x, y);
14661 TEST_DrawLevelField(x, y);
14664 // if digged element was about to explode, prevent the explosion
14665 ExplodeField[x][y] = EX_TYPE_NONE;
14667 PlayLevelSoundAction(x, y, action);
14670 Store[x][y] = EL_EMPTY;
14672 // this makes it possible to leave the removed element again
14673 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14674 Store[x][y] = element;
14679 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14681 int jx = player->jx, jy = player->jy;
14682 int x = jx + dx, y = jy + dy;
14683 int snap_direction = (dx == -1 ? MV_LEFT :
14684 dx == +1 ? MV_RIGHT :
14686 dy == +1 ? MV_DOWN : MV_NONE);
14687 boolean can_continue_snapping = (level.continuous_snapping &&
14688 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14690 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14693 if (!player->active || !IN_LEV_FIELD(x, y))
14701 if (player->MovPos == 0)
14702 player->is_pushing = FALSE;
14704 player->is_snapping = FALSE;
14706 if (player->MovPos == 0)
14708 player->is_moving = FALSE;
14709 player->is_digging = FALSE;
14710 player->is_collecting = FALSE;
14716 // prevent snapping with already pressed snap key when not allowed
14717 if (player->is_snapping && !can_continue_snapping)
14720 player->MovDir = snap_direction;
14722 if (player->MovPos == 0)
14724 player->is_moving = FALSE;
14725 player->is_digging = FALSE;
14726 player->is_collecting = FALSE;
14729 player->is_dropping = FALSE;
14730 player->is_dropping_pressed = FALSE;
14731 player->drop_pressed_delay = 0;
14733 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14736 player->is_snapping = TRUE;
14737 player->is_active = TRUE;
14739 if (player->MovPos == 0)
14741 player->is_moving = FALSE;
14742 player->is_digging = FALSE;
14743 player->is_collecting = FALSE;
14746 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14747 TEST_DrawLevelField(player->last_jx, player->last_jy);
14749 TEST_DrawLevelField(x, y);
14754 static boolean DropElement(struct PlayerInfo *player)
14756 int old_element, new_element;
14757 int dropx = player->jx, dropy = player->jy;
14758 int drop_direction = player->MovDir;
14759 int drop_side = drop_direction;
14760 int drop_element = get_next_dropped_element(player);
14762 /* do not drop an element on top of another element; when holding drop key
14763 pressed without moving, dropped element must move away before the next
14764 element can be dropped (this is especially important if the next element
14765 is dynamite, which can be placed on background for historical reasons) */
14766 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14769 if (IS_THROWABLE(drop_element))
14771 dropx += GET_DX_FROM_DIR(drop_direction);
14772 dropy += GET_DY_FROM_DIR(drop_direction);
14774 if (!IN_LEV_FIELD(dropx, dropy))
14778 old_element = Tile[dropx][dropy]; // old element at dropping position
14779 new_element = drop_element; // default: no change when dropping
14781 // check if player is active, not moving and ready to drop
14782 if (!player->active || player->MovPos || player->drop_delay > 0)
14785 // check if player has anything that can be dropped
14786 if (new_element == EL_UNDEFINED)
14789 // only set if player has anything that can be dropped
14790 player->is_dropping_pressed = TRUE;
14792 // check if drop key was pressed long enough for EM style dynamite
14793 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14796 // check if anything can be dropped at the current position
14797 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14800 // collected custom elements can only be dropped on empty fields
14801 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14804 if (old_element != EL_EMPTY)
14805 Back[dropx][dropy] = old_element; // store old element on this field
14807 ResetGfxAnimation(dropx, dropy);
14808 ResetRandomAnimationValue(dropx, dropy);
14810 if (player->inventory_size > 0 ||
14811 player->inventory_infinite_element != EL_UNDEFINED)
14813 if (player->inventory_size > 0)
14815 player->inventory_size--;
14817 DrawGameDoorValues();
14819 if (new_element == EL_DYNAMITE)
14820 new_element = EL_DYNAMITE_ACTIVE;
14821 else if (new_element == EL_EM_DYNAMITE)
14822 new_element = EL_EM_DYNAMITE_ACTIVE;
14823 else if (new_element == EL_SP_DISK_RED)
14824 new_element = EL_SP_DISK_RED_ACTIVE;
14827 Tile[dropx][dropy] = new_element;
14829 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14830 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14831 el2img(Tile[dropx][dropy]), 0);
14833 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14835 // needed if previous element just changed to "empty" in the last frame
14836 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14838 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14839 player->index_bit, drop_side);
14840 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14842 player->index_bit, drop_side);
14844 TestIfElementTouchesCustomElement(dropx, dropy);
14846 else // player is dropping a dyna bomb
14848 player->dynabombs_left--;
14850 Tile[dropx][dropy] = new_element;
14852 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14853 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14854 el2img(Tile[dropx][dropy]), 0);
14856 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14859 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14860 InitField_WithBug1(dropx, dropy, FALSE);
14862 new_element = Tile[dropx][dropy]; // element might have changed
14864 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14865 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14867 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14868 MovDir[dropx][dropy] = drop_direction;
14870 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14872 // do not cause impact style collision by dropping elements that can fall
14873 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14876 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14877 player->is_dropping = TRUE;
14879 player->drop_pressed_delay = 0;
14880 player->is_dropping_pressed = FALSE;
14882 player->drop_x = dropx;
14883 player->drop_y = dropy;
14888 // ----------------------------------------------------------------------------
14889 // game sound playing functions
14890 // ----------------------------------------------------------------------------
14892 static int *loop_sound_frame = NULL;
14893 static int *loop_sound_volume = NULL;
14895 void InitPlayLevelSound(void)
14897 int num_sounds = getSoundListSize();
14899 checked_free(loop_sound_frame);
14900 checked_free(loop_sound_volume);
14902 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14903 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14906 static void PlayLevelSound(int x, int y, int nr)
14908 int sx = SCREENX(x), sy = SCREENY(y);
14909 int volume, stereo_position;
14910 int max_distance = 8;
14911 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14913 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14914 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14917 if (!IN_LEV_FIELD(x, y) ||
14918 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14919 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14922 volume = SOUND_MAX_VOLUME;
14924 if (!IN_SCR_FIELD(sx, sy))
14926 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14927 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14929 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14932 stereo_position = (SOUND_MAX_LEFT +
14933 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14934 (SCR_FIELDX + 2 * max_distance));
14936 if (IS_LOOP_SOUND(nr))
14938 /* This assures that quieter loop sounds do not overwrite louder ones,
14939 while restarting sound volume comparison with each new game frame. */
14941 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14944 loop_sound_volume[nr] = volume;
14945 loop_sound_frame[nr] = FrameCounter;
14948 PlaySoundExt(nr, volume, stereo_position, type);
14951 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14953 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14954 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14955 y < LEVELY(BY1) ? LEVELY(BY1) :
14956 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14960 static void PlayLevelSoundAction(int x, int y, int action)
14962 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14965 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14967 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14969 if (sound_effect != SND_UNDEFINED)
14970 PlayLevelSound(x, y, sound_effect);
14973 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14976 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14978 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14979 PlayLevelSound(x, y, sound_effect);
14982 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14984 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14986 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14987 PlayLevelSound(x, y, sound_effect);
14990 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14992 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14994 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14995 StopSound(sound_effect);
14998 static int getLevelMusicNr(void)
15000 if (levelset.music[level_nr] != MUS_UNDEFINED)
15001 return levelset.music[level_nr]; // from config file
15003 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15006 static void FadeLevelSounds(void)
15011 static void FadeLevelMusic(void)
15013 int music_nr = getLevelMusicNr();
15014 char *curr_music = getCurrentlyPlayingMusicFilename();
15015 char *next_music = getMusicInfoEntryFilename(music_nr);
15017 if (!strEqual(curr_music, next_music))
15021 void FadeLevelSoundsAndMusic(void)
15027 static void PlayLevelMusic(void)
15029 int music_nr = getLevelMusicNr();
15030 char *curr_music = getCurrentlyPlayingMusicFilename();
15031 char *next_music = getMusicInfoEntryFilename(music_nr);
15033 if (!strEqual(curr_music, next_music))
15034 PlayMusicLoop(music_nr);
15037 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15039 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15041 int x = xx - offset;
15042 int y = yy - offset;
15047 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15051 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15055 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15059 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15063 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15067 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15071 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15074 case SOUND_android_clone:
15075 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15078 case SOUND_android_move:
15079 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15083 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15087 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15091 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15094 case SOUND_eater_eat:
15095 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15099 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15102 case SOUND_collect:
15103 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15106 case SOUND_diamond:
15107 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15111 // !!! CHECK THIS !!!
15113 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15115 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15119 case SOUND_wonderfall:
15120 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15124 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15128 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15132 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15136 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15140 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15144 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15148 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15152 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15155 case SOUND_exit_open:
15156 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15159 case SOUND_exit_leave:
15160 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15163 case SOUND_dynamite:
15164 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15168 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15172 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15176 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15180 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15184 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15188 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15192 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15197 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15199 int element = map_element_SP_to_RND(element_sp);
15200 int action = map_action_SP_to_RND(action_sp);
15201 int offset = (setup.sp_show_border_elements ? 0 : 1);
15202 int x = xx - offset;
15203 int y = yy - offset;
15205 PlayLevelSoundElementAction(x, y, element, action);
15208 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15210 int element = map_element_MM_to_RND(element_mm);
15211 int action = map_action_MM_to_RND(action_mm);
15213 int x = xx - offset;
15214 int y = yy - offset;
15216 if (!IS_MM_ELEMENT(element))
15217 element = EL_MM_DEFAULT;
15219 PlayLevelSoundElementAction(x, y, element, action);
15222 void PlaySound_MM(int sound_mm)
15224 int sound = map_sound_MM_to_RND(sound_mm);
15226 if (sound == SND_UNDEFINED)
15232 void PlaySoundLoop_MM(int sound_mm)
15234 int sound = map_sound_MM_to_RND(sound_mm);
15236 if (sound == SND_UNDEFINED)
15239 PlaySoundLoop(sound);
15242 void StopSound_MM(int sound_mm)
15244 int sound = map_sound_MM_to_RND(sound_mm);
15246 if (sound == SND_UNDEFINED)
15252 void RaiseScore(int value)
15254 game.score += value;
15256 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15258 DisplayGameControlValues();
15261 void RaiseScoreElement(int element)
15266 case EL_BD_DIAMOND:
15267 case EL_EMERALD_YELLOW:
15268 case EL_EMERALD_RED:
15269 case EL_EMERALD_PURPLE:
15270 case EL_SP_INFOTRON:
15271 RaiseScore(level.score[SC_EMERALD]);
15274 RaiseScore(level.score[SC_DIAMOND]);
15277 RaiseScore(level.score[SC_CRYSTAL]);
15280 RaiseScore(level.score[SC_PEARL]);
15283 case EL_BD_BUTTERFLY:
15284 case EL_SP_ELECTRON:
15285 RaiseScore(level.score[SC_BUG]);
15288 case EL_BD_FIREFLY:
15289 case EL_SP_SNIKSNAK:
15290 RaiseScore(level.score[SC_SPACESHIP]);
15293 case EL_DARK_YAMYAM:
15294 RaiseScore(level.score[SC_YAMYAM]);
15297 RaiseScore(level.score[SC_ROBOT]);
15300 RaiseScore(level.score[SC_PACMAN]);
15303 RaiseScore(level.score[SC_NUT]);
15306 case EL_EM_DYNAMITE:
15307 case EL_SP_DISK_RED:
15308 case EL_DYNABOMB_INCREASE_NUMBER:
15309 case EL_DYNABOMB_INCREASE_SIZE:
15310 case EL_DYNABOMB_INCREASE_POWER:
15311 RaiseScore(level.score[SC_DYNAMITE]);
15313 case EL_SHIELD_NORMAL:
15314 case EL_SHIELD_DEADLY:
15315 RaiseScore(level.score[SC_SHIELD]);
15317 case EL_EXTRA_TIME:
15318 RaiseScore(level.extra_time_score);
15332 case EL_DC_KEY_WHITE:
15333 RaiseScore(level.score[SC_KEY]);
15336 RaiseScore(element_info[element].collect_score);
15341 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15343 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15345 // closing door required in case of envelope style request dialogs
15348 // prevent short reactivation of overlay buttons while closing door
15349 SetOverlayActive(FALSE);
15351 CloseDoor(DOOR_CLOSE_1);
15354 if (network.enabled)
15355 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15359 FadeSkipNextFadeIn();
15361 SetGameStatus(GAME_MODE_MAIN);
15366 else // continue playing the game
15368 if (tape.playing && tape.deactivate_display)
15369 TapeDeactivateDisplayOff(TRUE);
15371 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15373 if (tape.playing && tape.deactivate_display)
15374 TapeDeactivateDisplayOn();
15378 void RequestQuitGame(boolean ask_if_really_quit)
15380 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15381 boolean skip_request = game.all_players_gone || quick_quit;
15383 RequestQuitGameExt(skip_request, quick_quit,
15384 "Do you really want to quit the game?");
15387 void RequestRestartGame(char *message)
15389 game.restart_game_message = NULL;
15391 boolean has_started_game = hasStartedNetworkGame();
15392 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15394 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15396 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15400 // needed in case of envelope request to close game panel
15401 CloseDoor(DOOR_CLOSE_1);
15403 SetGameStatus(GAME_MODE_MAIN);
15409 void CheckGameOver(void)
15411 static boolean last_game_over = FALSE;
15412 static int game_over_delay = 0;
15413 int game_over_delay_value = 50;
15414 boolean game_over = checkGameFailed();
15416 // do not handle game over if request dialog is already active
15417 if (game.request_active)
15420 // do not ask to play again if game was never actually played
15421 if (!game.GamePlayed)
15426 last_game_over = FALSE;
15427 game_over_delay = game_over_delay_value;
15432 if (game_over_delay > 0)
15439 if (last_game_over != game_over)
15440 game.restart_game_message = (hasStartedNetworkGame() ?
15441 "Game over! Play it again?" :
15444 last_game_over = game_over;
15447 boolean checkGameSolved(void)
15449 // set for all game engines if level was solved
15450 return game.LevelSolved_GameEnd;
15453 boolean checkGameFailed(void)
15455 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15456 return (game_em.game_over && !game_em.level_solved);
15457 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15458 return (game_sp.game_over && !game_sp.level_solved);
15459 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15460 return (game_mm.game_over && !game_mm.level_solved);
15461 else // GAME_ENGINE_TYPE_RND
15462 return (game.GameOver && !game.LevelSolved);
15465 boolean checkGameEnded(void)
15467 return (checkGameSolved() || checkGameFailed());
15471 // ----------------------------------------------------------------------------
15472 // random generator functions
15473 // ----------------------------------------------------------------------------
15475 unsigned int InitEngineRandom_RND(int seed)
15477 game.num_random_calls = 0;
15479 return InitEngineRandom(seed);
15482 unsigned int RND(int max)
15486 game.num_random_calls++;
15488 return GetEngineRandom(max);
15495 // ----------------------------------------------------------------------------
15496 // game engine snapshot handling functions
15497 // ----------------------------------------------------------------------------
15499 struct EngineSnapshotInfo
15501 // runtime values for custom element collect score
15502 int collect_score[NUM_CUSTOM_ELEMENTS];
15504 // runtime values for group element choice position
15505 int choice_pos[NUM_GROUP_ELEMENTS];
15507 // runtime values for belt position animations
15508 int belt_graphic[4][NUM_BELT_PARTS];
15509 int belt_anim_mode[4][NUM_BELT_PARTS];
15512 static struct EngineSnapshotInfo engine_snapshot_rnd;
15513 static char *snapshot_level_identifier = NULL;
15514 static int snapshot_level_nr = -1;
15516 static void SaveEngineSnapshotValues_RND(void)
15518 static int belt_base_active_element[4] =
15520 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15521 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15522 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15523 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15527 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15529 int element = EL_CUSTOM_START + i;
15531 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15534 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15536 int element = EL_GROUP_START + i;
15538 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15541 for (i = 0; i < 4; i++)
15543 for (j = 0; j < NUM_BELT_PARTS; j++)
15545 int element = belt_base_active_element[i] + j;
15546 int graphic = el2img(element);
15547 int anim_mode = graphic_info[graphic].anim_mode;
15549 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15550 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15555 static void LoadEngineSnapshotValues_RND(void)
15557 unsigned int num_random_calls = game.num_random_calls;
15560 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15562 int element = EL_CUSTOM_START + i;
15564 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15567 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15569 int element = EL_GROUP_START + i;
15571 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15574 for (i = 0; i < 4; i++)
15576 for (j = 0; j < NUM_BELT_PARTS; j++)
15578 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15579 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15581 graphic_info[graphic].anim_mode = anim_mode;
15585 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15587 InitRND(tape.random_seed);
15588 for (i = 0; i < num_random_calls; i++)
15592 if (game.num_random_calls != num_random_calls)
15594 Error("number of random calls out of sync");
15595 Error("number of random calls should be %d", num_random_calls);
15596 Error("number of random calls is %d", game.num_random_calls);
15598 Fail("this should not happen -- please debug");
15602 void FreeEngineSnapshotSingle(void)
15604 FreeSnapshotSingle();
15606 setString(&snapshot_level_identifier, NULL);
15607 snapshot_level_nr = -1;
15610 void FreeEngineSnapshotList(void)
15612 FreeSnapshotList();
15615 static ListNode *SaveEngineSnapshotBuffers(void)
15617 ListNode *buffers = NULL;
15619 // copy some special values to a structure better suited for the snapshot
15621 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15622 SaveEngineSnapshotValues_RND();
15623 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15624 SaveEngineSnapshotValues_EM();
15625 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15626 SaveEngineSnapshotValues_SP(&buffers);
15627 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15628 SaveEngineSnapshotValues_MM(&buffers);
15630 // save values stored in special snapshot structure
15632 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15633 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15634 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15635 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15636 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15637 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15638 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15639 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15641 // save further RND engine values
15643 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15644 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15645 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15647 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15648 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15649 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15650 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15651 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15653 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15654 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15655 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15657 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15659 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15660 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15662 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15663 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15664 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15665 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15666 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15667 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15668 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15669 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15670 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15671 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15672 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15673 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15674 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15675 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15676 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15677 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15678 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15679 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15681 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15682 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15684 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15685 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15686 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15688 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15689 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15691 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15692 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15693 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15694 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15695 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15697 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15698 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15701 ListNode *node = engine_snapshot_list_rnd;
15704 while (node != NULL)
15706 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15711 Debug("game:playing:SaveEngineSnapshotBuffers",
15712 "size of engine snapshot: %d bytes", num_bytes);
15718 void SaveEngineSnapshotSingle(void)
15720 ListNode *buffers = SaveEngineSnapshotBuffers();
15722 // finally save all snapshot buffers to single snapshot
15723 SaveSnapshotSingle(buffers);
15725 // save level identification information
15726 setString(&snapshot_level_identifier, leveldir_current->identifier);
15727 snapshot_level_nr = level_nr;
15730 boolean CheckSaveEngineSnapshotToList(void)
15732 boolean save_snapshot =
15733 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15734 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15735 game.snapshot.changed_action) ||
15736 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15737 game.snapshot.collected_item));
15739 game.snapshot.changed_action = FALSE;
15740 game.snapshot.collected_item = FALSE;
15741 game.snapshot.save_snapshot = save_snapshot;
15743 return save_snapshot;
15746 void SaveEngineSnapshotToList(void)
15748 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15752 ListNode *buffers = SaveEngineSnapshotBuffers();
15754 // finally save all snapshot buffers to snapshot list
15755 SaveSnapshotToList(buffers);
15758 void SaveEngineSnapshotToListInitial(void)
15760 FreeEngineSnapshotList();
15762 SaveEngineSnapshotToList();
15765 static void LoadEngineSnapshotValues(void)
15767 // restore special values from snapshot structure
15769 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15770 LoadEngineSnapshotValues_RND();
15771 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15772 LoadEngineSnapshotValues_EM();
15773 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15774 LoadEngineSnapshotValues_SP();
15775 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15776 LoadEngineSnapshotValues_MM();
15779 void LoadEngineSnapshotSingle(void)
15781 LoadSnapshotSingle();
15783 LoadEngineSnapshotValues();
15786 static void LoadEngineSnapshot_Undo(int steps)
15788 LoadSnapshotFromList_Older(steps);
15790 LoadEngineSnapshotValues();
15793 static void LoadEngineSnapshot_Redo(int steps)
15795 LoadSnapshotFromList_Newer(steps);
15797 LoadEngineSnapshotValues();
15800 boolean CheckEngineSnapshotSingle(void)
15802 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15803 snapshot_level_nr == level_nr);
15806 boolean CheckEngineSnapshotList(void)
15808 return CheckSnapshotList();
15812 // ---------- new game button stuff -------------------------------------------
15819 boolean *setup_value;
15820 boolean allowed_on_tape;
15821 boolean is_touch_button;
15823 } gamebutton_info[NUM_GAME_BUTTONS] =
15826 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15827 GAME_CTRL_ID_STOP, NULL,
15828 TRUE, FALSE, "stop game"
15831 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15832 GAME_CTRL_ID_PAUSE, NULL,
15833 TRUE, FALSE, "pause game"
15836 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15837 GAME_CTRL_ID_PLAY, NULL,
15838 TRUE, FALSE, "play game"
15841 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15842 GAME_CTRL_ID_UNDO, NULL,
15843 TRUE, FALSE, "undo step"
15846 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15847 GAME_CTRL_ID_REDO, NULL,
15848 TRUE, FALSE, "redo step"
15851 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15852 GAME_CTRL_ID_SAVE, NULL,
15853 TRUE, FALSE, "save game"
15856 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15857 GAME_CTRL_ID_PAUSE2, NULL,
15858 TRUE, FALSE, "pause game"
15861 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15862 GAME_CTRL_ID_LOAD, NULL,
15863 TRUE, FALSE, "load game"
15866 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15867 GAME_CTRL_ID_PANEL_STOP, NULL,
15868 FALSE, FALSE, "stop game"
15871 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15872 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15873 FALSE, FALSE, "pause game"
15876 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15877 GAME_CTRL_ID_PANEL_PLAY, NULL,
15878 FALSE, FALSE, "play game"
15881 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15882 GAME_CTRL_ID_TOUCH_STOP, NULL,
15883 FALSE, TRUE, "stop game"
15886 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15887 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15888 FALSE, TRUE, "pause game"
15891 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15892 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15893 TRUE, FALSE, "background music on/off"
15896 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15897 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15898 TRUE, FALSE, "sound loops on/off"
15901 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15902 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15903 TRUE, FALSE, "normal sounds on/off"
15906 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15907 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15908 FALSE, FALSE, "background music on/off"
15911 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15912 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15913 FALSE, FALSE, "sound loops on/off"
15916 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15917 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15918 FALSE, FALSE, "normal sounds on/off"
15922 void CreateGameButtons(void)
15926 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15928 int graphic = gamebutton_info[i].graphic;
15929 struct GraphicInfo *gfx = &graphic_info[graphic];
15930 struct XY *pos = gamebutton_info[i].pos;
15931 struct GadgetInfo *gi;
15934 unsigned int event_mask;
15935 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15936 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15937 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15938 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15939 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15940 int gd_x = gfx->src_x;
15941 int gd_y = gfx->src_y;
15942 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15943 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15944 int gd_xa = gfx->src_x + gfx->active_xoffset;
15945 int gd_ya = gfx->src_y + gfx->active_yoffset;
15946 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15947 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15948 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15949 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15952 if (gfx->bitmap == NULL)
15954 game_gadget[id] = NULL;
15959 if (id == GAME_CTRL_ID_STOP ||
15960 id == GAME_CTRL_ID_PANEL_STOP ||
15961 id == GAME_CTRL_ID_TOUCH_STOP ||
15962 id == GAME_CTRL_ID_PLAY ||
15963 id == GAME_CTRL_ID_PANEL_PLAY ||
15964 id == GAME_CTRL_ID_SAVE ||
15965 id == GAME_CTRL_ID_LOAD)
15967 button_type = GD_TYPE_NORMAL_BUTTON;
15969 event_mask = GD_EVENT_RELEASED;
15971 else if (id == GAME_CTRL_ID_UNDO ||
15972 id == GAME_CTRL_ID_REDO)
15974 button_type = GD_TYPE_NORMAL_BUTTON;
15976 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15980 button_type = GD_TYPE_CHECK_BUTTON;
15981 checked = (gamebutton_info[i].setup_value != NULL ?
15982 *gamebutton_info[i].setup_value : FALSE);
15983 event_mask = GD_EVENT_PRESSED;
15986 gi = CreateGadget(GDI_CUSTOM_ID, id,
15987 GDI_IMAGE_ID, graphic,
15988 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15991 GDI_WIDTH, gfx->width,
15992 GDI_HEIGHT, gfx->height,
15993 GDI_TYPE, button_type,
15994 GDI_STATE, GD_BUTTON_UNPRESSED,
15995 GDI_CHECKED, checked,
15996 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15997 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15998 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15999 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16000 GDI_DIRECT_DRAW, FALSE,
16001 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16002 GDI_EVENT_MASK, event_mask,
16003 GDI_CALLBACK_ACTION, HandleGameButtons,
16007 Fail("cannot create gadget");
16009 game_gadget[id] = gi;
16013 void FreeGameButtons(void)
16017 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16018 FreeGadget(game_gadget[i]);
16021 static void UnmapGameButtonsAtSamePosition(int id)
16025 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16027 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16028 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16029 UnmapGadget(game_gadget[i]);
16032 static void UnmapGameButtonsAtSamePosition_All(void)
16034 if (setup.show_snapshot_buttons)
16036 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16037 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16038 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16042 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16043 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16044 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16046 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16047 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16048 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16052 static void MapGameButtonsAtSamePosition(int id)
16056 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16058 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16059 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16060 MapGadget(game_gadget[i]);
16062 UnmapGameButtonsAtSamePosition_All();
16065 void MapUndoRedoButtons(void)
16067 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16068 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16070 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16071 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16074 void UnmapUndoRedoButtons(void)
16076 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16077 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16079 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16080 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16083 void ModifyPauseButtons(void)
16087 GAME_CTRL_ID_PAUSE,
16088 GAME_CTRL_ID_PAUSE2,
16089 GAME_CTRL_ID_PANEL_PAUSE,
16090 GAME_CTRL_ID_TOUCH_PAUSE,
16095 for (i = 0; ids[i] > -1; i++)
16096 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16099 static void MapGameButtonsExt(boolean on_tape)
16103 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16104 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16105 i != GAME_CTRL_ID_UNDO &&
16106 i != GAME_CTRL_ID_REDO)
16107 MapGadget(game_gadget[i]);
16109 UnmapGameButtonsAtSamePosition_All();
16111 RedrawGameButtons();
16114 static void UnmapGameButtonsExt(boolean on_tape)
16118 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16119 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16120 UnmapGadget(game_gadget[i]);
16123 static void RedrawGameButtonsExt(boolean on_tape)
16127 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16128 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16129 RedrawGadget(game_gadget[i]);
16132 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16137 gi->checked = state;
16140 static void RedrawSoundButtonGadget(int id)
16142 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16143 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16144 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16145 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16146 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16147 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16150 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16151 RedrawGadget(game_gadget[id2]);
16154 void MapGameButtons(void)
16156 MapGameButtonsExt(FALSE);
16159 void UnmapGameButtons(void)
16161 UnmapGameButtonsExt(FALSE);
16164 void RedrawGameButtons(void)
16166 RedrawGameButtonsExt(FALSE);
16169 void MapGameButtonsOnTape(void)
16171 MapGameButtonsExt(TRUE);
16174 void UnmapGameButtonsOnTape(void)
16176 UnmapGameButtonsExt(TRUE);
16179 void RedrawGameButtonsOnTape(void)
16181 RedrawGameButtonsExt(TRUE);
16184 static void GameUndoRedoExt(void)
16186 ClearPlayerAction();
16188 tape.pausing = TRUE;
16191 UpdateAndDisplayGameControlValues();
16193 DrawCompleteVideoDisplay();
16194 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16195 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16196 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16201 static void GameUndo(int steps)
16203 if (!CheckEngineSnapshotList())
16206 LoadEngineSnapshot_Undo(steps);
16211 static void GameRedo(int steps)
16213 if (!CheckEngineSnapshotList())
16216 LoadEngineSnapshot_Redo(steps);
16221 static void HandleGameButtonsExt(int id, int button)
16223 static boolean game_undo_executed = FALSE;
16224 int steps = BUTTON_STEPSIZE(button);
16225 boolean handle_game_buttons =
16226 (game_status == GAME_MODE_PLAYING ||
16227 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16229 if (!handle_game_buttons)
16234 case GAME_CTRL_ID_STOP:
16235 case GAME_CTRL_ID_PANEL_STOP:
16236 case GAME_CTRL_ID_TOUCH_STOP:
16237 if (game_status == GAME_MODE_MAIN)
16243 RequestQuitGame(TRUE);
16247 case GAME_CTRL_ID_PAUSE:
16248 case GAME_CTRL_ID_PAUSE2:
16249 case GAME_CTRL_ID_PANEL_PAUSE:
16250 case GAME_CTRL_ID_TOUCH_PAUSE:
16251 if (network.enabled && game_status == GAME_MODE_PLAYING)
16254 SendToServer_ContinuePlaying();
16256 SendToServer_PausePlaying();
16259 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16261 game_undo_executed = FALSE;
16265 case GAME_CTRL_ID_PLAY:
16266 case GAME_CTRL_ID_PANEL_PLAY:
16267 if (game_status == GAME_MODE_MAIN)
16269 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16271 else if (tape.pausing)
16273 if (network.enabled)
16274 SendToServer_ContinuePlaying();
16276 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16280 case GAME_CTRL_ID_UNDO:
16281 // Important: When using "save snapshot when collecting an item" mode,
16282 // load last (current) snapshot for first "undo" after pressing "pause"
16283 // (else the last-but-one snapshot would be loaded, because the snapshot
16284 // pointer already points to the last snapshot when pressing "pause",
16285 // which is fine for "every step/move" mode, but not for "every collect")
16286 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16287 !game_undo_executed)
16290 game_undo_executed = TRUE;
16295 case GAME_CTRL_ID_REDO:
16299 case GAME_CTRL_ID_SAVE:
16303 case GAME_CTRL_ID_LOAD:
16307 case SOUND_CTRL_ID_MUSIC:
16308 case SOUND_CTRL_ID_PANEL_MUSIC:
16309 if (setup.sound_music)
16311 setup.sound_music = FALSE;
16315 else if (audio.music_available)
16317 setup.sound = setup.sound_music = TRUE;
16319 SetAudioMode(setup.sound);
16321 if (game_status == GAME_MODE_PLAYING)
16325 RedrawSoundButtonGadget(id);
16329 case SOUND_CTRL_ID_LOOPS:
16330 case SOUND_CTRL_ID_PANEL_LOOPS:
16331 if (setup.sound_loops)
16332 setup.sound_loops = FALSE;
16333 else if (audio.loops_available)
16335 setup.sound = setup.sound_loops = TRUE;
16337 SetAudioMode(setup.sound);
16340 RedrawSoundButtonGadget(id);
16344 case SOUND_CTRL_ID_SIMPLE:
16345 case SOUND_CTRL_ID_PANEL_SIMPLE:
16346 if (setup.sound_simple)
16347 setup.sound_simple = FALSE;
16348 else if (audio.sound_available)
16350 setup.sound = setup.sound_simple = TRUE;
16352 SetAudioMode(setup.sound);
16355 RedrawSoundButtonGadget(id);
16364 static void HandleGameButtons(struct GadgetInfo *gi)
16366 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16369 void HandleSoundButtonKeys(Key key)
16371 if (key == setup.shortcut.sound_simple)
16372 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16373 else if (key == setup.shortcut.sound_loops)
16374 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16375 else if (key == setup.shortcut.sound_music)
16376 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);