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 || !setup.count_score_after_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 (setup.count_score_after_game)
4861 if (game_over_delay_1 > 0)
4863 game_over_delay_1--;
4868 if (time != time_final)
4870 int time_to_go = ABS(time_final - time);
4871 int time_count_dir = (time < time_final ? +1 : -1);
4873 if (time_to_go < time_count_steps)
4874 time_count_steps = 1;
4876 time += time_count_steps * time_count_dir;
4877 score += time_count_steps * time_score;
4879 // set final score to correct rounding differences after counting score
4880 if (time == time_final)
4881 score = score_final;
4883 game.LevelSolved_CountingTime = time;
4884 game.LevelSolved_CountingScore = score;
4886 game_panel_controls[GAME_PANEL_TIME].value = time;
4887 game_panel_controls[GAME_PANEL_SCORE].value = score;
4889 DisplayGameControlValues();
4891 if (time == time_final)
4892 StopSound(SND_GAME_LEVELTIME_BONUS);
4893 else if (setup.sound_loops)
4894 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4896 PlaySound(SND_GAME_LEVELTIME_BONUS);
4901 if (game_over_delay_2 > 0)
4903 game_over_delay_2--;
4908 if (health != health_final)
4910 int health_count_dir = (health < health_final ? +1 : -1);
4912 health += health_count_dir;
4913 score += time_score;
4915 game.LevelSolved_CountingHealth = health;
4916 game.LevelSolved_CountingScore = score;
4918 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4919 game_panel_controls[GAME_PANEL_SCORE].value = score;
4921 DisplayGameControlValues();
4923 if (health == health_final)
4924 StopSound(SND_GAME_LEVELTIME_BONUS);
4925 else if (setup.sound_loops)
4926 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4928 PlaySound(SND_GAME_LEVELTIME_BONUS);
4934 game.panel.active = FALSE;
4936 if (game_over_delay_3 > 0)
4938 game_over_delay_3--;
4948 // used instead of "level_nr" (needed for network games)
4949 int last_level_nr = levelset.level_nr;
4952 game.LevelSolved_GameEnd = TRUE;
4954 if (game.LevelSolved_SaveTape)
4956 // make sure that request dialog to save tape does not open door again
4957 if (!global.use_envelope_request)
4958 CloseDoor(DOOR_CLOSE_1);
4960 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4963 // if no tape is to be saved, close both doors simultaneously
4964 CloseDoor(DOOR_CLOSE_ALL);
4966 if (level_editor_test_game)
4968 SetGameStatus(GAME_MODE_MAIN);
4975 if (!game.LevelSolved_SaveScore)
4977 SetGameStatus(GAME_MODE_MAIN);
4984 if (level_nr == leveldir_current->handicap_level)
4986 leveldir_current->handicap_level++;
4988 SaveLevelSetup_SeriesInfo();
4991 if (setup.increment_levels &&
4992 level_nr < leveldir_current->last_level &&
4995 level_nr++; // advance to next level
4996 TapeErase(); // start with empty tape
4998 if (setup.auto_play_next_level)
5000 LoadLevel(level_nr);
5002 SaveLevelSetup_SeriesInfo();
5006 hi_pos = NewHiScore(last_level_nr);
5008 if (hi_pos >= 0 && setup.show_scores_after_game)
5010 SetGameStatus(GAME_MODE_SCORES);
5012 DrawHallOfFame(last_level_nr, hi_pos);
5014 else if (setup.auto_play_next_level && setup.increment_levels &&
5015 last_level_nr < leveldir_current->last_level &&
5018 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5022 SetGameStatus(GAME_MODE_MAIN);
5028 int NewHiScore(int level_nr)
5032 boolean one_score_entry_per_name = !program.many_scores_per_name;
5034 LoadScore(level_nr);
5036 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
5037 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
5040 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
5042 if (game.score_final > highscore[k].Score)
5044 // player has made it to the hall of fame
5046 if (k < MAX_SCORE_ENTRIES - 1)
5048 int m = MAX_SCORE_ENTRIES - 1;
5050 if (one_score_entry_per_name)
5052 for (l = k; l < MAX_SCORE_ENTRIES; l++)
5053 if (strEqual(setup.player_name, highscore[l].Name))
5056 if (m == k) // player's new highscore overwrites his old one
5060 for (l = m; l > k; l--)
5062 strcpy(highscore[l].Name, highscore[l - 1].Name);
5063 highscore[l].Score = highscore[l - 1].Score;
5069 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
5070 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
5071 highscore[k].Score = game.score_final;
5076 else if (one_score_entry_per_name &&
5077 !strncmp(setup.player_name, highscore[k].Name,
5078 MAX_PLAYER_NAME_LEN))
5079 break; // player already there with a higher score
5083 SaveScore(level_nr);
5088 static int getElementMoveStepsizeExt(int x, int y, int direction)
5090 int element = Tile[x][y];
5091 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5092 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5093 int horiz_move = (dx != 0);
5094 int sign = (horiz_move ? dx : dy);
5095 int step = sign * element_info[element].move_stepsize;
5097 // special values for move stepsize for spring and things on conveyor belt
5100 if (CAN_FALL(element) &&
5101 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5102 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5103 else if (element == EL_SPRING)
5104 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5110 static int getElementMoveStepsize(int x, int y)
5112 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5115 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5117 if (player->GfxAction != action || player->GfxDir != dir)
5119 player->GfxAction = action;
5120 player->GfxDir = dir;
5122 player->StepFrame = 0;
5126 static void ResetGfxFrame(int x, int y)
5128 // profiling showed that "autotest" spends 10~20% of its time in this function
5129 if (DrawingDeactivatedField())
5132 int element = Tile[x][y];
5133 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5135 if (graphic_info[graphic].anim_global_sync)
5136 GfxFrame[x][y] = FrameCounter;
5137 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5138 GfxFrame[x][y] = CustomValue[x][y];
5139 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5140 GfxFrame[x][y] = element_info[element].collect_score;
5141 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5142 GfxFrame[x][y] = ChangeDelay[x][y];
5145 static void ResetGfxAnimation(int x, int y)
5147 GfxAction[x][y] = ACTION_DEFAULT;
5148 GfxDir[x][y] = MovDir[x][y];
5151 ResetGfxFrame(x, y);
5154 static void ResetRandomAnimationValue(int x, int y)
5156 GfxRandom[x][y] = INIT_GFX_RANDOM();
5159 static void InitMovingField(int x, int y, int direction)
5161 int element = Tile[x][y];
5162 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5163 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5166 boolean is_moving_before, is_moving_after;
5168 // check if element was/is moving or being moved before/after mode change
5169 is_moving_before = (WasJustMoving[x][y] != 0);
5170 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5172 // reset animation only for moving elements which change direction of moving
5173 // or which just started or stopped moving
5174 // (else CEs with property "can move" / "not moving" are reset each frame)
5175 if (is_moving_before != is_moving_after ||
5176 direction != MovDir[x][y])
5177 ResetGfxAnimation(x, y);
5179 MovDir[x][y] = direction;
5180 GfxDir[x][y] = direction;
5182 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5183 direction == MV_DOWN && CAN_FALL(element) ?
5184 ACTION_FALLING : ACTION_MOVING);
5186 // this is needed for CEs with property "can move" / "not moving"
5188 if (is_moving_after)
5190 if (Tile[newx][newy] == EL_EMPTY)
5191 Tile[newx][newy] = EL_BLOCKED;
5193 MovDir[newx][newy] = MovDir[x][y];
5195 CustomValue[newx][newy] = CustomValue[x][y];
5197 GfxFrame[newx][newy] = GfxFrame[x][y];
5198 GfxRandom[newx][newy] = GfxRandom[x][y];
5199 GfxAction[newx][newy] = GfxAction[x][y];
5200 GfxDir[newx][newy] = GfxDir[x][y];
5204 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5206 int direction = MovDir[x][y];
5207 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5208 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5214 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5216 int oldx = x, oldy = y;
5217 int direction = MovDir[x][y];
5219 if (direction == MV_LEFT)
5221 else if (direction == MV_RIGHT)
5223 else if (direction == MV_UP)
5225 else if (direction == MV_DOWN)
5228 *comes_from_x = oldx;
5229 *comes_from_y = oldy;
5232 static int MovingOrBlocked2Element(int x, int y)
5234 int element = Tile[x][y];
5236 if (element == EL_BLOCKED)
5240 Blocked2Moving(x, y, &oldx, &oldy);
5241 return Tile[oldx][oldy];
5247 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5249 // like MovingOrBlocked2Element(), but if element is moving
5250 // and (x,y) is the field the moving element is just leaving,
5251 // return EL_BLOCKED instead of the element value
5252 int element = Tile[x][y];
5254 if (IS_MOVING(x, y))
5256 if (element == EL_BLOCKED)
5260 Blocked2Moving(x, y, &oldx, &oldy);
5261 return Tile[oldx][oldy];
5270 static void RemoveField(int x, int y)
5272 Tile[x][y] = EL_EMPTY;
5278 CustomValue[x][y] = 0;
5281 ChangeDelay[x][y] = 0;
5282 ChangePage[x][y] = -1;
5283 Pushed[x][y] = FALSE;
5285 GfxElement[x][y] = EL_UNDEFINED;
5286 GfxAction[x][y] = ACTION_DEFAULT;
5287 GfxDir[x][y] = MV_NONE;
5290 static void RemoveMovingField(int x, int y)
5292 int oldx = x, oldy = y, newx = x, newy = y;
5293 int element = Tile[x][y];
5294 int next_element = EL_UNDEFINED;
5296 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5299 if (IS_MOVING(x, y))
5301 Moving2Blocked(x, y, &newx, &newy);
5303 if (Tile[newx][newy] != EL_BLOCKED)
5305 // element is moving, but target field is not free (blocked), but
5306 // already occupied by something different (example: acid pool);
5307 // in this case, only remove the moving field, but not the target
5309 RemoveField(oldx, oldy);
5311 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5313 TEST_DrawLevelField(oldx, oldy);
5318 else if (element == EL_BLOCKED)
5320 Blocked2Moving(x, y, &oldx, &oldy);
5321 if (!IS_MOVING(oldx, oldy))
5325 if (element == EL_BLOCKED &&
5326 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5327 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5328 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5329 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5330 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5331 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5332 next_element = get_next_element(Tile[oldx][oldy]);
5334 RemoveField(oldx, oldy);
5335 RemoveField(newx, newy);
5337 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5339 if (next_element != EL_UNDEFINED)
5340 Tile[oldx][oldy] = next_element;
5342 TEST_DrawLevelField(oldx, oldy);
5343 TEST_DrawLevelField(newx, newy);
5346 void DrawDynamite(int x, int y)
5348 int sx = SCREENX(x), sy = SCREENY(y);
5349 int graphic = el2img(Tile[x][y]);
5352 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5355 if (IS_WALKABLE_INSIDE(Back[x][y]))
5359 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5360 else if (Store[x][y])
5361 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5363 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5365 if (Back[x][y] || Store[x][y])
5366 DrawGraphicThruMask(sx, sy, graphic, frame);
5368 DrawGraphic(sx, sy, graphic, frame);
5371 static void CheckDynamite(int x, int y)
5373 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5377 if (MovDelay[x][y] != 0)
5380 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5386 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5391 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5393 boolean num_checked_players = 0;
5396 for (i = 0; i < MAX_PLAYERS; i++)
5398 if (stored_player[i].active)
5400 int sx = stored_player[i].jx;
5401 int sy = stored_player[i].jy;
5403 if (num_checked_players == 0)
5410 *sx1 = MIN(*sx1, sx);
5411 *sy1 = MIN(*sy1, sy);
5412 *sx2 = MAX(*sx2, sx);
5413 *sy2 = MAX(*sy2, sy);
5416 num_checked_players++;
5421 static boolean checkIfAllPlayersFitToScreen_RND(void)
5423 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5425 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5427 return (sx2 - sx1 < SCR_FIELDX &&
5428 sy2 - sy1 < SCR_FIELDY);
5431 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5433 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5435 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5437 *sx = (sx1 + sx2) / 2;
5438 *sy = (sy1 + sy2) / 2;
5441 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5442 boolean center_screen, boolean quick_relocation)
5444 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5445 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5446 boolean no_delay = (tape.warp_forward);
5447 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5448 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5449 int new_scroll_x, new_scroll_y;
5451 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5453 // case 1: quick relocation inside visible screen (without scrolling)
5460 if (!level.shifted_relocation || center_screen)
5462 // relocation _with_ centering of screen
5464 new_scroll_x = SCROLL_POSITION_X(x);
5465 new_scroll_y = SCROLL_POSITION_Y(y);
5469 // relocation _without_ centering of screen
5471 int center_scroll_x = SCROLL_POSITION_X(old_x);
5472 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5473 int offset_x = x + (scroll_x - center_scroll_x);
5474 int offset_y = y + (scroll_y - center_scroll_y);
5476 // for new screen position, apply previous offset to center position
5477 new_scroll_x = SCROLL_POSITION_X(offset_x);
5478 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5481 if (quick_relocation)
5483 // case 2: quick relocation (redraw without visible scrolling)
5485 scroll_x = new_scroll_x;
5486 scroll_y = new_scroll_y;
5493 // case 3: visible relocation (with scrolling to new position)
5495 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5497 SetVideoFrameDelay(wait_delay_value);
5499 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5501 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5502 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5504 if (dx == 0 && dy == 0) // no scrolling needed at all
5510 // set values for horizontal/vertical screen scrolling (half tile size)
5511 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5512 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5513 int pos_x = dx * TILEX / 2;
5514 int pos_y = dy * TILEY / 2;
5515 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5516 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5518 ScrollLevel(dx, dy);
5521 // scroll in two steps of half tile size to make things smoother
5522 BlitScreenToBitmapExt_RND(window, fx, fy);
5524 // scroll second step to align at full tile size
5525 BlitScreenToBitmap(window);
5531 SetVideoFrameDelay(frame_delay_value_old);
5534 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5536 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5537 int player_nr = GET_PLAYER_NR(el_player);
5538 struct PlayerInfo *player = &stored_player[player_nr];
5539 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5540 boolean no_delay = (tape.warp_forward);
5541 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5542 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5543 int old_jx = player->jx;
5544 int old_jy = player->jy;
5545 int old_element = Tile[old_jx][old_jy];
5546 int element = Tile[jx][jy];
5547 boolean player_relocated = (old_jx != jx || old_jy != jy);
5549 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5550 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5551 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5552 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5553 int leave_side_horiz = move_dir_horiz;
5554 int leave_side_vert = move_dir_vert;
5555 int enter_side = enter_side_horiz | enter_side_vert;
5556 int leave_side = leave_side_horiz | leave_side_vert;
5558 if (player->buried) // do not reanimate dead player
5561 if (!player_relocated) // no need to relocate the player
5564 if (IS_PLAYER(jx, jy)) // player already placed at new position
5566 RemoveField(jx, jy); // temporarily remove newly placed player
5567 DrawLevelField(jx, jy);
5570 if (player->present)
5572 while (player->MovPos)
5574 ScrollPlayer(player, SCROLL_GO_ON);
5575 ScrollScreen(NULL, SCROLL_GO_ON);
5577 AdvanceFrameAndPlayerCounters(player->index_nr);
5581 BackToFront_WithFrameDelay(wait_delay_value);
5584 DrawPlayer(player); // needed here only to cleanup last field
5585 DrawLevelField(player->jx, player->jy); // remove player graphic
5587 player->is_moving = FALSE;
5590 if (IS_CUSTOM_ELEMENT(old_element))
5591 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5593 player->index_bit, leave_side);
5595 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5597 player->index_bit, leave_side);
5599 Tile[jx][jy] = el_player;
5600 InitPlayerField(jx, jy, el_player, TRUE);
5602 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5603 possible that the relocation target field did not contain a player element,
5604 but a walkable element, to which the new player was relocated -- in this
5605 case, restore that (already initialized!) element on the player field */
5606 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5608 Tile[jx][jy] = element; // restore previously existing element
5611 // only visually relocate centered player
5612 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5613 FALSE, level.instant_relocation);
5615 TestIfPlayerTouchesBadThing(jx, jy);
5616 TestIfPlayerTouchesCustomElement(jx, jy);
5618 if (IS_CUSTOM_ELEMENT(element))
5619 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5620 player->index_bit, enter_side);
5622 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5623 player->index_bit, enter_side);
5625 if (player->is_switching)
5627 /* ensure that relocation while still switching an element does not cause
5628 a new element to be treated as also switched directly after relocation
5629 (this is important for teleporter switches that teleport the player to
5630 a place where another teleporter switch is in the same direction, which
5631 would then incorrectly be treated as immediately switched before the
5632 direction key that caused the switch was released) */
5634 player->switch_x += jx - old_jx;
5635 player->switch_y += jy - old_jy;
5639 static void Explode(int ex, int ey, int phase, int mode)
5645 // !!! eliminate this variable !!!
5646 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5648 if (game.explosions_delayed)
5650 ExplodeField[ex][ey] = mode;
5654 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5656 int center_element = Tile[ex][ey];
5657 int artwork_element, explosion_element; // set these values later
5659 // remove things displayed in background while burning dynamite
5660 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5663 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5665 // put moving element to center field (and let it explode there)
5666 center_element = MovingOrBlocked2Element(ex, ey);
5667 RemoveMovingField(ex, ey);
5668 Tile[ex][ey] = center_element;
5671 // now "center_element" is finally determined -- set related values now
5672 artwork_element = center_element; // for custom player artwork
5673 explosion_element = center_element; // for custom player artwork
5675 if (IS_PLAYER(ex, ey))
5677 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5679 artwork_element = stored_player[player_nr].artwork_element;
5681 if (level.use_explosion_element[player_nr])
5683 explosion_element = level.explosion_element[player_nr];
5684 artwork_element = explosion_element;
5688 if (mode == EX_TYPE_NORMAL ||
5689 mode == EX_TYPE_CENTER ||
5690 mode == EX_TYPE_CROSS)
5691 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5693 last_phase = element_info[explosion_element].explosion_delay + 1;
5695 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5697 int xx = x - ex + 1;
5698 int yy = y - ey + 1;
5701 if (!IN_LEV_FIELD(x, y) ||
5702 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5703 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5706 element = Tile[x][y];
5708 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5710 element = MovingOrBlocked2Element(x, y);
5712 if (!IS_EXPLOSION_PROOF(element))
5713 RemoveMovingField(x, y);
5716 // indestructible elements can only explode in center (but not flames)
5717 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5718 mode == EX_TYPE_BORDER)) ||
5719 element == EL_FLAMES)
5722 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5723 behaviour, for example when touching a yamyam that explodes to rocks
5724 with active deadly shield, a rock is created under the player !!! */
5725 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5727 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5728 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5729 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5731 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5734 if (IS_ACTIVE_BOMB(element))
5736 // re-activate things under the bomb like gate or penguin
5737 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5744 // save walkable background elements while explosion on same tile
5745 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5746 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5747 Back[x][y] = element;
5749 // ignite explodable elements reached by other explosion
5750 if (element == EL_EXPLOSION)
5751 element = Store2[x][y];
5753 if (AmoebaNr[x][y] &&
5754 (element == EL_AMOEBA_FULL ||
5755 element == EL_BD_AMOEBA ||
5756 element == EL_AMOEBA_GROWING))
5758 AmoebaCnt[AmoebaNr[x][y]]--;
5759 AmoebaCnt2[AmoebaNr[x][y]]--;
5764 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5766 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5768 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5770 if (PLAYERINFO(ex, ey)->use_murphy)
5771 Store[x][y] = EL_EMPTY;
5774 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5775 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5776 else if (ELEM_IS_PLAYER(center_element))
5777 Store[x][y] = EL_EMPTY;
5778 else if (center_element == EL_YAMYAM)
5779 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5780 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5781 Store[x][y] = element_info[center_element].content.e[xx][yy];
5783 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5784 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5785 // otherwise) -- FIX THIS !!!
5786 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5787 Store[x][y] = element_info[element].content.e[1][1];
5789 else if (!CAN_EXPLODE(element))
5790 Store[x][y] = element_info[element].content.e[1][1];
5793 Store[x][y] = EL_EMPTY;
5795 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5796 center_element == EL_AMOEBA_TO_DIAMOND)
5797 Store2[x][y] = element;
5799 Tile[x][y] = EL_EXPLOSION;
5800 GfxElement[x][y] = artwork_element;
5802 ExplodePhase[x][y] = 1;
5803 ExplodeDelay[x][y] = last_phase;
5808 if (center_element == EL_YAMYAM)
5809 game.yamyam_content_nr =
5810 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5822 GfxFrame[x][y] = 0; // restart explosion animation
5824 last_phase = ExplodeDelay[x][y];
5826 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5828 // this can happen if the player leaves an explosion just in time
5829 if (GfxElement[x][y] == EL_UNDEFINED)
5830 GfxElement[x][y] = EL_EMPTY;
5832 border_element = Store2[x][y];
5833 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5834 border_element = StorePlayer[x][y];
5836 if (phase == element_info[border_element].ignition_delay ||
5837 phase == last_phase)
5839 boolean border_explosion = FALSE;
5841 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5842 !PLAYER_EXPLOSION_PROTECTED(x, y))
5844 KillPlayerUnlessExplosionProtected(x, y);
5845 border_explosion = TRUE;
5847 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5849 Tile[x][y] = Store2[x][y];
5852 border_explosion = TRUE;
5854 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5856 AmoebaToDiamond(x, y);
5858 border_explosion = TRUE;
5861 // if an element just explodes due to another explosion (chain-reaction),
5862 // do not immediately end the new explosion when it was the last frame of
5863 // the explosion (as it would be done in the following "if"-statement!)
5864 if (border_explosion && phase == last_phase)
5868 if (phase == last_phase)
5872 element = Tile[x][y] = Store[x][y];
5873 Store[x][y] = Store2[x][y] = 0;
5874 GfxElement[x][y] = EL_UNDEFINED;
5876 // player can escape from explosions and might therefore be still alive
5877 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5878 element <= EL_PLAYER_IS_EXPLODING_4)
5880 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5881 int explosion_element = EL_PLAYER_1 + player_nr;
5882 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5883 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5885 if (level.use_explosion_element[player_nr])
5886 explosion_element = level.explosion_element[player_nr];
5888 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5889 element_info[explosion_element].content.e[xx][yy]);
5892 // restore probably existing indestructible background element
5893 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5894 element = Tile[x][y] = Back[x][y];
5897 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5898 GfxDir[x][y] = MV_NONE;
5899 ChangeDelay[x][y] = 0;
5900 ChangePage[x][y] = -1;
5902 CustomValue[x][y] = 0;
5904 InitField_WithBug2(x, y, FALSE);
5906 TEST_DrawLevelField(x, y);
5908 TestIfElementTouchesCustomElement(x, y);
5910 if (GFX_CRUMBLED(element))
5911 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5913 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5914 StorePlayer[x][y] = 0;
5916 if (ELEM_IS_PLAYER(element))
5917 RelocatePlayer(x, y, element);
5919 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5921 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5922 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5925 TEST_DrawLevelFieldCrumbled(x, y);
5927 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5929 DrawLevelElement(x, y, Back[x][y]);
5930 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5932 else if (IS_WALKABLE_UNDER(Back[x][y]))
5934 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5935 DrawLevelElementThruMask(x, y, Back[x][y]);
5937 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5938 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5942 static void DynaExplode(int ex, int ey)
5945 int dynabomb_element = Tile[ex][ey];
5946 int dynabomb_size = 1;
5947 boolean dynabomb_xl = FALSE;
5948 struct PlayerInfo *player;
5949 static int xy[4][2] =
5957 if (IS_ACTIVE_BOMB(dynabomb_element))
5959 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5960 dynabomb_size = player->dynabomb_size;
5961 dynabomb_xl = player->dynabomb_xl;
5962 player->dynabombs_left++;
5965 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5967 for (i = 0; i < NUM_DIRECTIONS; i++)
5969 for (j = 1; j <= dynabomb_size; j++)
5971 int x = ex + j * xy[i][0];
5972 int y = ey + j * xy[i][1];
5975 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5978 element = Tile[x][y];
5980 // do not restart explosions of fields with active bombs
5981 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5984 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5986 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5987 !IS_DIGGABLE(element) && !dynabomb_xl)
5993 void Bang(int x, int y)
5995 int element = MovingOrBlocked2Element(x, y);
5996 int explosion_type = EX_TYPE_NORMAL;
5998 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6000 struct PlayerInfo *player = PLAYERINFO(x, y);
6002 element = Tile[x][y] = player->initial_element;
6004 if (level.use_explosion_element[player->index_nr])
6006 int explosion_element = level.explosion_element[player->index_nr];
6008 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6009 explosion_type = EX_TYPE_CROSS;
6010 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6011 explosion_type = EX_TYPE_CENTER;
6019 case EL_BD_BUTTERFLY:
6022 case EL_DARK_YAMYAM:
6026 RaiseScoreElement(element);
6029 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6030 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6031 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6032 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6033 case EL_DYNABOMB_INCREASE_NUMBER:
6034 case EL_DYNABOMB_INCREASE_SIZE:
6035 case EL_DYNABOMB_INCREASE_POWER:
6036 explosion_type = EX_TYPE_DYNA;
6039 case EL_DC_LANDMINE:
6040 explosion_type = EX_TYPE_CENTER;
6045 case EL_LAMP_ACTIVE:
6046 case EL_AMOEBA_TO_DIAMOND:
6047 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6048 explosion_type = EX_TYPE_CENTER;
6052 if (element_info[element].explosion_type == EXPLODES_CROSS)
6053 explosion_type = EX_TYPE_CROSS;
6054 else if (element_info[element].explosion_type == EXPLODES_1X1)
6055 explosion_type = EX_TYPE_CENTER;
6059 if (explosion_type == EX_TYPE_DYNA)
6062 Explode(x, y, EX_PHASE_START, explosion_type);
6064 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6067 static void SplashAcid(int x, int y)
6069 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6070 (!IN_LEV_FIELD(x - 1, y - 2) ||
6071 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6072 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6074 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6075 (!IN_LEV_FIELD(x + 1, y - 2) ||
6076 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6077 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6079 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6082 static void InitBeltMovement(void)
6084 static int belt_base_element[4] =
6086 EL_CONVEYOR_BELT_1_LEFT,
6087 EL_CONVEYOR_BELT_2_LEFT,
6088 EL_CONVEYOR_BELT_3_LEFT,
6089 EL_CONVEYOR_BELT_4_LEFT
6091 static int belt_base_active_element[4] =
6093 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6094 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6095 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6096 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6101 // set frame order for belt animation graphic according to belt direction
6102 for (i = 0; i < NUM_BELTS; i++)
6106 for (j = 0; j < NUM_BELT_PARTS; j++)
6108 int element = belt_base_active_element[belt_nr] + j;
6109 int graphic_1 = el2img(element);
6110 int graphic_2 = el2panelimg(element);
6112 if (game.belt_dir[i] == MV_LEFT)
6114 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6115 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6119 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6120 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6125 SCAN_PLAYFIELD(x, y)
6127 int element = Tile[x][y];
6129 for (i = 0; i < NUM_BELTS; i++)
6131 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6133 int e_belt_nr = getBeltNrFromBeltElement(element);
6136 if (e_belt_nr == belt_nr)
6138 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6140 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6147 static void ToggleBeltSwitch(int x, int y)
6149 static int belt_base_element[4] =
6151 EL_CONVEYOR_BELT_1_LEFT,
6152 EL_CONVEYOR_BELT_2_LEFT,
6153 EL_CONVEYOR_BELT_3_LEFT,
6154 EL_CONVEYOR_BELT_4_LEFT
6156 static int belt_base_active_element[4] =
6158 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6159 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6160 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6161 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6163 static int belt_base_switch_element[4] =
6165 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6166 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6167 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6168 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6170 static int belt_move_dir[4] =
6178 int element = Tile[x][y];
6179 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6180 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6181 int belt_dir = belt_move_dir[belt_dir_nr];
6184 if (!IS_BELT_SWITCH(element))
6187 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6188 game.belt_dir[belt_nr] = belt_dir;
6190 if (belt_dir_nr == 3)
6193 // set frame order for belt animation graphic according to belt direction
6194 for (i = 0; i < NUM_BELT_PARTS; i++)
6196 int element = belt_base_active_element[belt_nr] + i;
6197 int graphic_1 = el2img(element);
6198 int graphic_2 = el2panelimg(element);
6200 if (belt_dir == MV_LEFT)
6202 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6203 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6207 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6208 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6212 SCAN_PLAYFIELD(xx, yy)
6214 int element = Tile[xx][yy];
6216 if (IS_BELT_SWITCH(element))
6218 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6220 if (e_belt_nr == belt_nr)
6222 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6223 TEST_DrawLevelField(xx, yy);
6226 else if (IS_BELT(element) && belt_dir != MV_NONE)
6228 int e_belt_nr = getBeltNrFromBeltElement(element);
6230 if (e_belt_nr == belt_nr)
6232 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6234 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6235 TEST_DrawLevelField(xx, yy);
6238 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6240 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6242 if (e_belt_nr == belt_nr)
6244 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6246 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6247 TEST_DrawLevelField(xx, yy);
6253 static void ToggleSwitchgateSwitch(int x, int y)
6257 game.switchgate_pos = !game.switchgate_pos;
6259 SCAN_PLAYFIELD(xx, yy)
6261 int element = Tile[xx][yy];
6263 if (element == EL_SWITCHGATE_SWITCH_UP)
6265 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6266 TEST_DrawLevelField(xx, yy);
6268 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6270 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6271 TEST_DrawLevelField(xx, yy);
6273 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6275 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6276 TEST_DrawLevelField(xx, yy);
6278 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6280 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6281 TEST_DrawLevelField(xx, yy);
6283 else if (element == EL_SWITCHGATE_OPEN ||
6284 element == EL_SWITCHGATE_OPENING)
6286 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6288 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6290 else if (element == EL_SWITCHGATE_CLOSED ||
6291 element == EL_SWITCHGATE_CLOSING)
6293 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6295 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6300 static int getInvisibleActiveFromInvisibleElement(int element)
6302 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6303 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6304 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6308 static int getInvisibleFromInvisibleActiveElement(int element)
6310 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6311 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6312 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6316 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6320 SCAN_PLAYFIELD(x, y)
6322 int element = Tile[x][y];
6324 if (element == EL_LIGHT_SWITCH &&
6325 game.light_time_left > 0)
6327 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6328 TEST_DrawLevelField(x, y);
6330 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6331 game.light_time_left == 0)
6333 Tile[x][y] = EL_LIGHT_SWITCH;
6334 TEST_DrawLevelField(x, y);
6336 else if (element == EL_EMC_DRIPPER &&
6337 game.light_time_left > 0)
6339 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6340 TEST_DrawLevelField(x, y);
6342 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6343 game.light_time_left == 0)
6345 Tile[x][y] = EL_EMC_DRIPPER;
6346 TEST_DrawLevelField(x, y);
6348 else if (element == EL_INVISIBLE_STEELWALL ||
6349 element == EL_INVISIBLE_WALL ||
6350 element == EL_INVISIBLE_SAND)
6352 if (game.light_time_left > 0)
6353 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6355 TEST_DrawLevelField(x, y);
6357 // uncrumble neighbour fields, if needed
6358 if (element == EL_INVISIBLE_SAND)
6359 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6361 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6362 element == EL_INVISIBLE_WALL_ACTIVE ||
6363 element == EL_INVISIBLE_SAND_ACTIVE)
6365 if (game.light_time_left == 0)
6366 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6368 TEST_DrawLevelField(x, y);
6370 // re-crumble neighbour fields, if needed
6371 if (element == EL_INVISIBLE_SAND)
6372 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6377 static void RedrawAllInvisibleElementsForLenses(void)
6381 SCAN_PLAYFIELD(x, y)
6383 int element = Tile[x][y];
6385 if (element == EL_EMC_DRIPPER &&
6386 game.lenses_time_left > 0)
6388 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6389 TEST_DrawLevelField(x, y);
6391 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6392 game.lenses_time_left == 0)
6394 Tile[x][y] = EL_EMC_DRIPPER;
6395 TEST_DrawLevelField(x, y);
6397 else if (element == EL_INVISIBLE_STEELWALL ||
6398 element == EL_INVISIBLE_WALL ||
6399 element == EL_INVISIBLE_SAND)
6401 if (game.lenses_time_left > 0)
6402 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6404 TEST_DrawLevelField(x, y);
6406 // uncrumble neighbour fields, if needed
6407 if (element == EL_INVISIBLE_SAND)
6408 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6410 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6411 element == EL_INVISIBLE_WALL_ACTIVE ||
6412 element == EL_INVISIBLE_SAND_ACTIVE)
6414 if (game.lenses_time_left == 0)
6415 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6417 TEST_DrawLevelField(x, y);
6419 // re-crumble neighbour fields, if needed
6420 if (element == EL_INVISIBLE_SAND)
6421 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6426 static void RedrawAllInvisibleElementsForMagnifier(void)
6430 SCAN_PLAYFIELD(x, y)
6432 int element = Tile[x][y];
6434 if (element == EL_EMC_FAKE_GRASS &&
6435 game.magnify_time_left > 0)
6437 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6438 TEST_DrawLevelField(x, y);
6440 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6441 game.magnify_time_left == 0)
6443 Tile[x][y] = EL_EMC_FAKE_GRASS;
6444 TEST_DrawLevelField(x, y);
6446 else if (IS_GATE_GRAY(element) &&
6447 game.magnify_time_left > 0)
6449 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6450 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6451 IS_EM_GATE_GRAY(element) ?
6452 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6453 IS_EMC_GATE_GRAY(element) ?
6454 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6455 IS_DC_GATE_GRAY(element) ?
6456 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6458 TEST_DrawLevelField(x, y);
6460 else if (IS_GATE_GRAY_ACTIVE(element) &&
6461 game.magnify_time_left == 0)
6463 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6464 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6465 IS_EM_GATE_GRAY_ACTIVE(element) ?
6466 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6467 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6468 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6469 IS_DC_GATE_GRAY_ACTIVE(element) ?
6470 EL_DC_GATE_WHITE_GRAY :
6472 TEST_DrawLevelField(x, y);
6477 static void ToggleLightSwitch(int x, int y)
6479 int element = Tile[x][y];
6481 game.light_time_left =
6482 (element == EL_LIGHT_SWITCH ?
6483 level.time_light * FRAMES_PER_SECOND : 0);
6485 RedrawAllLightSwitchesAndInvisibleElements();
6488 static void ActivateTimegateSwitch(int x, int y)
6492 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6494 SCAN_PLAYFIELD(xx, yy)
6496 int element = Tile[xx][yy];
6498 if (element == EL_TIMEGATE_CLOSED ||
6499 element == EL_TIMEGATE_CLOSING)
6501 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6502 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6506 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6508 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6509 TEST_DrawLevelField(xx, yy);
6515 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6516 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6519 static void Impact(int x, int y)
6521 boolean last_line = (y == lev_fieldy - 1);
6522 boolean object_hit = FALSE;
6523 boolean impact = (last_line || object_hit);
6524 int element = Tile[x][y];
6525 int smashed = EL_STEELWALL;
6527 if (!last_line) // check if element below was hit
6529 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6532 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6533 MovDir[x][y + 1] != MV_DOWN ||
6534 MovPos[x][y + 1] <= TILEY / 2));
6536 // do not smash moving elements that left the smashed field in time
6537 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6538 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6541 #if USE_QUICKSAND_IMPACT_BUGFIX
6542 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6544 RemoveMovingField(x, y + 1);
6545 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6546 Tile[x][y + 2] = EL_ROCK;
6547 TEST_DrawLevelField(x, y + 2);
6552 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6554 RemoveMovingField(x, y + 1);
6555 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6556 Tile[x][y + 2] = EL_ROCK;
6557 TEST_DrawLevelField(x, y + 2);
6564 smashed = MovingOrBlocked2Element(x, y + 1);
6566 impact = (last_line || object_hit);
6569 if (!last_line && smashed == EL_ACID) // element falls into acid
6571 SplashAcid(x, y + 1);
6575 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6576 // only reset graphic animation if graphic really changes after impact
6578 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6580 ResetGfxAnimation(x, y);
6581 TEST_DrawLevelField(x, y);
6584 if (impact && CAN_EXPLODE_IMPACT(element))
6589 else if (impact && element == EL_PEARL &&
6590 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6592 ResetGfxAnimation(x, y);
6594 Tile[x][y] = EL_PEARL_BREAKING;
6595 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6598 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6600 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6605 if (impact && element == EL_AMOEBA_DROP)
6607 if (object_hit && IS_PLAYER(x, y + 1))
6608 KillPlayerUnlessEnemyProtected(x, y + 1);
6609 else if (object_hit && smashed == EL_PENGUIN)
6613 Tile[x][y] = EL_AMOEBA_GROWING;
6614 Store[x][y] = EL_AMOEBA_WET;
6616 ResetRandomAnimationValue(x, y);
6621 if (object_hit) // check which object was hit
6623 if ((CAN_PASS_MAGIC_WALL(element) &&
6624 (smashed == EL_MAGIC_WALL ||
6625 smashed == EL_BD_MAGIC_WALL)) ||
6626 (CAN_PASS_DC_MAGIC_WALL(element) &&
6627 smashed == EL_DC_MAGIC_WALL))
6630 int activated_magic_wall =
6631 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6632 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6633 EL_DC_MAGIC_WALL_ACTIVE);
6635 // activate magic wall / mill
6636 SCAN_PLAYFIELD(xx, yy)
6638 if (Tile[xx][yy] == smashed)
6639 Tile[xx][yy] = activated_magic_wall;
6642 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6643 game.magic_wall_active = TRUE;
6645 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6646 SND_MAGIC_WALL_ACTIVATING :
6647 smashed == EL_BD_MAGIC_WALL ?
6648 SND_BD_MAGIC_WALL_ACTIVATING :
6649 SND_DC_MAGIC_WALL_ACTIVATING));
6652 if (IS_PLAYER(x, y + 1))
6654 if (CAN_SMASH_PLAYER(element))
6656 KillPlayerUnlessEnemyProtected(x, y + 1);
6660 else if (smashed == EL_PENGUIN)
6662 if (CAN_SMASH_PLAYER(element))
6668 else if (element == EL_BD_DIAMOND)
6670 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6676 else if (((element == EL_SP_INFOTRON ||
6677 element == EL_SP_ZONK) &&
6678 (smashed == EL_SP_SNIKSNAK ||
6679 smashed == EL_SP_ELECTRON ||
6680 smashed == EL_SP_DISK_ORANGE)) ||
6681 (element == EL_SP_INFOTRON &&
6682 smashed == EL_SP_DISK_YELLOW))
6687 else if (CAN_SMASH_EVERYTHING(element))
6689 if (IS_CLASSIC_ENEMY(smashed) ||
6690 CAN_EXPLODE_SMASHED(smashed))
6695 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6697 if (smashed == EL_LAMP ||
6698 smashed == EL_LAMP_ACTIVE)
6703 else if (smashed == EL_NUT)
6705 Tile[x][y + 1] = EL_NUT_BREAKING;
6706 PlayLevelSound(x, y, SND_NUT_BREAKING);
6707 RaiseScoreElement(EL_NUT);
6710 else if (smashed == EL_PEARL)
6712 ResetGfxAnimation(x, y);
6714 Tile[x][y + 1] = EL_PEARL_BREAKING;
6715 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6718 else if (smashed == EL_DIAMOND)
6720 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6721 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6724 else if (IS_BELT_SWITCH(smashed))
6726 ToggleBeltSwitch(x, y + 1);
6728 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6729 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6730 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6731 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6733 ToggleSwitchgateSwitch(x, y + 1);
6735 else if (smashed == EL_LIGHT_SWITCH ||
6736 smashed == EL_LIGHT_SWITCH_ACTIVE)
6738 ToggleLightSwitch(x, y + 1);
6742 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6744 CheckElementChangeBySide(x, y + 1, smashed, element,
6745 CE_SWITCHED, CH_SIDE_TOP);
6746 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6752 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6757 // play sound of magic wall / mill
6759 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6760 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6761 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6763 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6764 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6765 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6766 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6767 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6768 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6773 // play sound of object that hits the ground
6774 if (last_line || object_hit)
6775 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6778 static void TurnRoundExt(int x, int y)
6790 { 0, 0 }, { 0, 0 }, { 0, 0 },
6795 int left, right, back;
6799 { MV_DOWN, MV_UP, MV_RIGHT },
6800 { MV_UP, MV_DOWN, MV_LEFT },
6802 { MV_LEFT, MV_RIGHT, MV_DOWN },
6806 { MV_RIGHT, MV_LEFT, MV_UP }
6809 int element = Tile[x][y];
6810 int move_pattern = element_info[element].move_pattern;
6812 int old_move_dir = MovDir[x][y];
6813 int left_dir = turn[old_move_dir].left;
6814 int right_dir = turn[old_move_dir].right;
6815 int back_dir = turn[old_move_dir].back;
6817 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6818 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6819 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6820 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6822 int left_x = x + left_dx, left_y = y + left_dy;
6823 int right_x = x + right_dx, right_y = y + right_dy;
6824 int move_x = x + move_dx, move_y = y + move_dy;
6828 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6830 TestIfBadThingTouchesOtherBadThing(x, y);
6832 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6833 MovDir[x][y] = right_dir;
6834 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6835 MovDir[x][y] = left_dir;
6837 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6839 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6842 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6844 TestIfBadThingTouchesOtherBadThing(x, y);
6846 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6847 MovDir[x][y] = left_dir;
6848 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6849 MovDir[x][y] = right_dir;
6851 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6853 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6856 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6858 TestIfBadThingTouchesOtherBadThing(x, y);
6860 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6861 MovDir[x][y] = left_dir;
6862 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6863 MovDir[x][y] = right_dir;
6865 if (MovDir[x][y] != old_move_dir)
6868 else if (element == EL_YAMYAM)
6870 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6871 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6873 if (can_turn_left && can_turn_right)
6874 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6875 else if (can_turn_left)
6876 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6877 else if (can_turn_right)
6878 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6880 MovDir[x][y] = back_dir;
6882 MovDelay[x][y] = 16 + 16 * RND(3);
6884 else if (element == EL_DARK_YAMYAM)
6886 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6888 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6891 if (can_turn_left && can_turn_right)
6892 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6893 else if (can_turn_left)
6894 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6895 else if (can_turn_right)
6896 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6898 MovDir[x][y] = back_dir;
6900 MovDelay[x][y] = 16 + 16 * RND(3);
6902 else if (element == EL_PACMAN)
6904 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6905 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6907 if (can_turn_left && can_turn_right)
6908 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6909 else if (can_turn_left)
6910 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6911 else if (can_turn_right)
6912 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6914 MovDir[x][y] = back_dir;
6916 MovDelay[x][y] = 6 + RND(40);
6918 else if (element == EL_PIG)
6920 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6921 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6922 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6923 boolean should_turn_left, should_turn_right, should_move_on;
6925 int rnd = RND(rnd_value);
6927 should_turn_left = (can_turn_left &&
6929 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6930 y + back_dy + left_dy)));
6931 should_turn_right = (can_turn_right &&
6933 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6934 y + back_dy + right_dy)));
6935 should_move_on = (can_move_on &&
6938 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6939 y + move_dy + left_dy) ||
6940 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6941 y + move_dy + right_dy)));
6943 if (should_turn_left || should_turn_right || should_move_on)
6945 if (should_turn_left && should_turn_right && should_move_on)
6946 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6947 rnd < 2 * rnd_value / 3 ? right_dir :
6949 else if (should_turn_left && should_turn_right)
6950 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6951 else if (should_turn_left && should_move_on)
6952 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6953 else if (should_turn_right && should_move_on)
6954 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6955 else if (should_turn_left)
6956 MovDir[x][y] = left_dir;
6957 else if (should_turn_right)
6958 MovDir[x][y] = right_dir;
6959 else if (should_move_on)
6960 MovDir[x][y] = old_move_dir;
6962 else if (can_move_on && rnd > rnd_value / 8)
6963 MovDir[x][y] = old_move_dir;
6964 else if (can_turn_left && can_turn_right)
6965 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6966 else if (can_turn_left && rnd > rnd_value / 8)
6967 MovDir[x][y] = left_dir;
6968 else if (can_turn_right && rnd > rnd_value/8)
6969 MovDir[x][y] = right_dir;
6971 MovDir[x][y] = back_dir;
6973 xx = x + move_xy[MovDir[x][y]].dx;
6974 yy = y + move_xy[MovDir[x][y]].dy;
6976 if (!IN_LEV_FIELD(xx, yy) ||
6977 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6978 MovDir[x][y] = old_move_dir;
6982 else if (element == EL_DRAGON)
6984 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6985 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6986 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6988 int rnd = RND(rnd_value);
6990 if (can_move_on && rnd > rnd_value / 8)
6991 MovDir[x][y] = old_move_dir;
6992 else if (can_turn_left && can_turn_right)
6993 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6994 else if (can_turn_left && rnd > rnd_value / 8)
6995 MovDir[x][y] = left_dir;
6996 else if (can_turn_right && rnd > rnd_value / 8)
6997 MovDir[x][y] = right_dir;
6999 MovDir[x][y] = back_dir;
7001 xx = x + move_xy[MovDir[x][y]].dx;
7002 yy = y + move_xy[MovDir[x][y]].dy;
7004 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7005 MovDir[x][y] = old_move_dir;
7009 else if (element == EL_MOLE)
7011 boolean can_move_on =
7012 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7013 IS_AMOEBOID(Tile[move_x][move_y]) ||
7014 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7017 boolean can_turn_left =
7018 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7019 IS_AMOEBOID(Tile[left_x][left_y])));
7021 boolean can_turn_right =
7022 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7023 IS_AMOEBOID(Tile[right_x][right_y])));
7025 if (can_turn_left && can_turn_right)
7026 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7027 else if (can_turn_left)
7028 MovDir[x][y] = left_dir;
7030 MovDir[x][y] = right_dir;
7033 if (MovDir[x][y] != old_move_dir)
7036 else if (element == EL_BALLOON)
7038 MovDir[x][y] = game.wind_direction;
7041 else if (element == EL_SPRING)
7043 if (MovDir[x][y] & MV_HORIZONTAL)
7045 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7046 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7048 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7049 ResetGfxAnimation(move_x, move_y);
7050 TEST_DrawLevelField(move_x, move_y);
7052 MovDir[x][y] = back_dir;
7054 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7055 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7056 MovDir[x][y] = MV_NONE;
7061 else if (element == EL_ROBOT ||
7062 element == EL_SATELLITE ||
7063 element == EL_PENGUIN ||
7064 element == EL_EMC_ANDROID)
7066 int attr_x = -1, attr_y = -1;
7068 if (game.all_players_gone)
7070 attr_x = game.exit_x;
7071 attr_y = game.exit_y;
7077 for (i = 0; i < MAX_PLAYERS; i++)
7079 struct PlayerInfo *player = &stored_player[i];
7080 int jx = player->jx, jy = player->jy;
7082 if (!player->active)
7086 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7094 if (element == EL_ROBOT &&
7095 game.robot_wheel_x >= 0 &&
7096 game.robot_wheel_y >= 0 &&
7097 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7098 game.engine_version < VERSION_IDENT(3,1,0,0)))
7100 attr_x = game.robot_wheel_x;
7101 attr_y = game.robot_wheel_y;
7104 if (element == EL_PENGUIN)
7107 static int xy[4][2] =
7115 for (i = 0; i < NUM_DIRECTIONS; i++)
7117 int ex = x + xy[i][0];
7118 int ey = y + xy[i][1];
7120 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7121 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7122 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7123 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7132 MovDir[x][y] = MV_NONE;
7134 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7135 else if (attr_x > x)
7136 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7138 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7139 else if (attr_y > y)
7140 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7142 if (element == EL_ROBOT)
7146 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7147 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7148 Moving2Blocked(x, y, &newx, &newy);
7150 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7151 MovDelay[x][y] = 8 + 8 * !RND(3);
7153 MovDelay[x][y] = 16;
7155 else if (element == EL_PENGUIN)
7161 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7163 boolean first_horiz = RND(2);
7164 int new_move_dir = MovDir[x][y];
7167 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7168 Moving2Blocked(x, y, &newx, &newy);
7170 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7174 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7175 Moving2Blocked(x, y, &newx, &newy);
7177 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7180 MovDir[x][y] = old_move_dir;
7184 else if (element == EL_SATELLITE)
7190 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7192 boolean first_horiz = RND(2);
7193 int new_move_dir = MovDir[x][y];
7196 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7197 Moving2Blocked(x, y, &newx, &newy);
7199 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7203 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7204 Moving2Blocked(x, y, &newx, &newy);
7206 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7209 MovDir[x][y] = old_move_dir;
7213 else if (element == EL_EMC_ANDROID)
7215 static int check_pos[16] =
7217 -1, // 0 => (invalid)
7220 -1, // 3 => (invalid)
7222 0, // 5 => MV_LEFT | MV_UP
7223 2, // 6 => MV_RIGHT | MV_UP
7224 -1, // 7 => (invalid)
7226 6, // 9 => MV_LEFT | MV_DOWN
7227 4, // 10 => MV_RIGHT | MV_DOWN
7228 -1, // 11 => (invalid)
7229 -1, // 12 => (invalid)
7230 -1, // 13 => (invalid)
7231 -1, // 14 => (invalid)
7232 -1, // 15 => (invalid)
7240 { -1, -1, MV_LEFT | MV_UP },
7242 { +1, -1, MV_RIGHT | MV_UP },
7243 { +1, 0, MV_RIGHT },
7244 { +1, +1, MV_RIGHT | MV_DOWN },
7246 { -1, +1, MV_LEFT | MV_DOWN },
7249 int start_pos, check_order;
7250 boolean can_clone = FALSE;
7253 // check if there is any free field around current position
7254 for (i = 0; i < 8; i++)
7256 int newx = x + check_xy[i].dx;
7257 int newy = y + check_xy[i].dy;
7259 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7267 if (can_clone) // randomly find an element to clone
7271 start_pos = check_pos[RND(8)];
7272 check_order = (RND(2) ? -1 : +1);
7274 for (i = 0; i < 8; i++)
7276 int pos_raw = start_pos + i * check_order;
7277 int pos = (pos_raw + 8) % 8;
7278 int newx = x + check_xy[pos].dx;
7279 int newy = y + check_xy[pos].dy;
7281 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7283 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7284 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7286 Store[x][y] = Tile[newx][newy];
7295 if (can_clone) // randomly find a direction to move
7299 start_pos = check_pos[RND(8)];
7300 check_order = (RND(2) ? -1 : +1);
7302 for (i = 0; i < 8; i++)
7304 int pos_raw = start_pos + i * check_order;
7305 int pos = (pos_raw + 8) % 8;
7306 int newx = x + check_xy[pos].dx;
7307 int newy = y + check_xy[pos].dy;
7308 int new_move_dir = check_xy[pos].dir;
7310 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7312 MovDir[x][y] = new_move_dir;
7313 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7322 if (can_clone) // cloning and moving successful
7325 // cannot clone -- try to move towards player
7327 start_pos = check_pos[MovDir[x][y] & 0x0f];
7328 check_order = (RND(2) ? -1 : +1);
7330 for (i = 0; i < 3; i++)
7332 // first check start_pos, then previous/next or (next/previous) pos
7333 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7334 int pos = (pos_raw + 8) % 8;
7335 int newx = x + check_xy[pos].dx;
7336 int newy = y + check_xy[pos].dy;
7337 int new_move_dir = check_xy[pos].dir;
7339 if (IS_PLAYER(newx, newy))
7342 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7344 MovDir[x][y] = new_move_dir;
7345 MovDelay[x][y] = level.android_move_time * 8 + 1;
7352 else if (move_pattern == MV_TURNING_LEFT ||
7353 move_pattern == MV_TURNING_RIGHT ||
7354 move_pattern == MV_TURNING_LEFT_RIGHT ||
7355 move_pattern == MV_TURNING_RIGHT_LEFT ||
7356 move_pattern == MV_TURNING_RANDOM ||
7357 move_pattern == MV_ALL_DIRECTIONS)
7359 boolean can_turn_left =
7360 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7361 boolean can_turn_right =
7362 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7364 if (element_info[element].move_stepsize == 0) // "not moving"
7367 if (move_pattern == MV_TURNING_LEFT)
7368 MovDir[x][y] = left_dir;
7369 else if (move_pattern == MV_TURNING_RIGHT)
7370 MovDir[x][y] = right_dir;
7371 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7372 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7373 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7374 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7375 else if (move_pattern == MV_TURNING_RANDOM)
7376 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7377 can_turn_right && !can_turn_left ? right_dir :
7378 RND(2) ? left_dir : right_dir);
7379 else if (can_turn_left && can_turn_right)
7380 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7381 else if (can_turn_left)
7382 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7383 else if (can_turn_right)
7384 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7386 MovDir[x][y] = back_dir;
7388 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7390 else if (move_pattern == MV_HORIZONTAL ||
7391 move_pattern == MV_VERTICAL)
7393 if (move_pattern & old_move_dir)
7394 MovDir[x][y] = back_dir;
7395 else if (move_pattern == MV_HORIZONTAL)
7396 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7397 else if (move_pattern == MV_VERTICAL)
7398 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7400 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7402 else if (move_pattern & MV_ANY_DIRECTION)
7404 MovDir[x][y] = move_pattern;
7405 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7407 else if (move_pattern & MV_WIND_DIRECTION)
7409 MovDir[x][y] = game.wind_direction;
7410 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7412 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7414 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7415 MovDir[x][y] = left_dir;
7416 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7417 MovDir[x][y] = right_dir;
7419 if (MovDir[x][y] != old_move_dir)
7420 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7422 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7424 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7425 MovDir[x][y] = right_dir;
7426 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7427 MovDir[x][y] = left_dir;
7429 if (MovDir[x][y] != old_move_dir)
7430 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7432 else if (move_pattern == MV_TOWARDS_PLAYER ||
7433 move_pattern == MV_AWAY_FROM_PLAYER)
7435 int attr_x = -1, attr_y = -1;
7437 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7439 if (game.all_players_gone)
7441 attr_x = game.exit_x;
7442 attr_y = game.exit_y;
7448 for (i = 0; i < MAX_PLAYERS; i++)
7450 struct PlayerInfo *player = &stored_player[i];
7451 int jx = player->jx, jy = player->jy;
7453 if (!player->active)
7457 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7465 MovDir[x][y] = MV_NONE;
7467 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7468 else if (attr_x > x)
7469 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7471 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7472 else if (attr_y > y)
7473 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7475 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7477 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7479 boolean first_horiz = RND(2);
7480 int new_move_dir = MovDir[x][y];
7482 if (element_info[element].move_stepsize == 0) // "not moving"
7484 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7485 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7491 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7492 Moving2Blocked(x, y, &newx, &newy);
7494 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7498 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7499 Moving2Blocked(x, y, &newx, &newy);
7501 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7504 MovDir[x][y] = old_move_dir;
7507 else if (move_pattern == MV_WHEN_PUSHED ||
7508 move_pattern == MV_WHEN_DROPPED)
7510 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7511 MovDir[x][y] = MV_NONE;
7515 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7517 static int test_xy[7][2] =
7527 static int test_dir[7] =
7537 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7538 int move_preference = -1000000; // start with very low preference
7539 int new_move_dir = MV_NONE;
7540 int start_test = RND(4);
7543 for (i = 0; i < NUM_DIRECTIONS; i++)
7545 int move_dir = test_dir[start_test + i];
7546 int move_dir_preference;
7548 xx = x + test_xy[start_test + i][0];
7549 yy = y + test_xy[start_test + i][1];
7551 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7552 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7554 new_move_dir = move_dir;
7559 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7562 move_dir_preference = -1 * RunnerVisit[xx][yy];
7563 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7564 move_dir_preference = PlayerVisit[xx][yy];
7566 if (move_dir_preference > move_preference)
7568 // prefer field that has not been visited for the longest time
7569 move_preference = move_dir_preference;
7570 new_move_dir = move_dir;
7572 else if (move_dir_preference == move_preference &&
7573 move_dir == old_move_dir)
7575 // prefer last direction when all directions are preferred equally
7576 move_preference = move_dir_preference;
7577 new_move_dir = move_dir;
7581 MovDir[x][y] = new_move_dir;
7582 if (old_move_dir != new_move_dir)
7583 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7587 static void TurnRound(int x, int y)
7589 int direction = MovDir[x][y];
7593 GfxDir[x][y] = MovDir[x][y];
7595 if (direction != MovDir[x][y])
7599 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7601 ResetGfxFrame(x, y);
7604 static boolean JustBeingPushed(int x, int y)
7608 for (i = 0; i < MAX_PLAYERS; i++)
7610 struct PlayerInfo *player = &stored_player[i];
7612 if (player->active && player->is_pushing && player->MovPos)
7614 int next_jx = player->jx + (player->jx - player->last_jx);
7615 int next_jy = player->jy + (player->jy - player->last_jy);
7617 if (x == next_jx && y == next_jy)
7625 static void StartMoving(int x, int y)
7627 boolean started_moving = FALSE; // some elements can fall _and_ move
7628 int element = Tile[x][y];
7633 if (MovDelay[x][y] == 0)
7634 GfxAction[x][y] = ACTION_DEFAULT;
7636 if (CAN_FALL(element) && y < lev_fieldy - 1)
7638 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7639 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7640 if (JustBeingPushed(x, y))
7643 if (element == EL_QUICKSAND_FULL)
7645 if (IS_FREE(x, y + 1))
7647 InitMovingField(x, y, MV_DOWN);
7648 started_moving = TRUE;
7650 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7651 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7652 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7653 Store[x][y] = EL_ROCK;
7655 Store[x][y] = EL_ROCK;
7658 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7660 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7662 if (!MovDelay[x][y])
7664 MovDelay[x][y] = TILEY + 1;
7666 ResetGfxAnimation(x, y);
7667 ResetGfxAnimation(x, y + 1);
7672 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7673 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7680 Tile[x][y] = EL_QUICKSAND_EMPTY;
7681 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7682 Store[x][y + 1] = Store[x][y];
7685 PlayLevelSoundAction(x, y, ACTION_FILLING);
7687 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7689 if (!MovDelay[x][y])
7691 MovDelay[x][y] = TILEY + 1;
7693 ResetGfxAnimation(x, y);
7694 ResetGfxAnimation(x, y + 1);
7699 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7700 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7707 Tile[x][y] = EL_QUICKSAND_EMPTY;
7708 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7709 Store[x][y + 1] = Store[x][y];
7712 PlayLevelSoundAction(x, y, ACTION_FILLING);
7715 else if (element == EL_QUICKSAND_FAST_FULL)
7717 if (IS_FREE(x, y + 1))
7719 InitMovingField(x, y, MV_DOWN);
7720 started_moving = TRUE;
7722 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7723 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7724 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7725 Store[x][y] = EL_ROCK;
7727 Store[x][y] = EL_ROCK;
7730 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7732 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7734 if (!MovDelay[x][y])
7736 MovDelay[x][y] = TILEY + 1;
7738 ResetGfxAnimation(x, y);
7739 ResetGfxAnimation(x, y + 1);
7744 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7745 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7752 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7753 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7754 Store[x][y + 1] = Store[x][y];
7757 PlayLevelSoundAction(x, y, ACTION_FILLING);
7759 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7761 if (!MovDelay[x][y])
7763 MovDelay[x][y] = TILEY + 1;
7765 ResetGfxAnimation(x, y);
7766 ResetGfxAnimation(x, y + 1);
7771 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7772 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7779 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7780 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7781 Store[x][y + 1] = Store[x][y];
7784 PlayLevelSoundAction(x, y, ACTION_FILLING);
7787 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7788 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7790 InitMovingField(x, y, MV_DOWN);
7791 started_moving = TRUE;
7793 Tile[x][y] = EL_QUICKSAND_FILLING;
7794 Store[x][y] = element;
7796 PlayLevelSoundAction(x, y, ACTION_FILLING);
7798 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7799 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7801 InitMovingField(x, y, MV_DOWN);
7802 started_moving = TRUE;
7804 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7805 Store[x][y] = element;
7807 PlayLevelSoundAction(x, y, ACTION_FILLING);
7809 else if (element == EL_MAGIC_WALL_FULL)
7811 if (IS_FREE(x, y + 1))
7813 InitMovingField(x, y, MV_DOWN);
7814 started_moving = TRUE;
7816 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7817 Store[x][y] = EL_CHANGED(Store[x][y]);
7819 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7821 if (!MovDelay[x][y])
7822 MovDelay[x][y] = TILEY / 4 + 1;
7831 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7832 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7833 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7837 else if (element == EL_BD_MAGIC_WALL_FULL)
7839 if (IS_FREE(x, y + 1))
7841 InitMovingField(x, y, MV_DOWN);
7842 started_moving = TRUE;
7844 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7845 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7847 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7849 if (!MovDelay[x][y])
7850 MovDelay[x][y] = TILEY / 4 + 1;
7859 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7860 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7861 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7865 else if (element == EL_DC_MAGIC_WALL_FULL)
7867 if (IS_FREE(x, y + 1))
7869 InitMovingField(x, y, MV_DOWN);
7870 started_moving = TRUE;
7872 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7873 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7875 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7877 if (!MovDelay[x][y])
7878 MovDelay[x][y] = TILEY / 4 + 1;
7887 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7888 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7889 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7893 else if ((CAN_PASS_MAGIC_WALL(element) &&
7894 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7895 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7896 (CAN_PASS_DC_MAGIC_WALL(element) &&
7897 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7900 InitMovingField(x, y, MV_DOWN);
7901 started_moving = TRUE;
7904 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7905 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7906 EL_DC_MAGIC_WALL_FILLING);
7907 Store[x][y] = element;
7909 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7911 SplashAcid(x, y + 1);
7913 InitMovingField(x, y, MV_DOWN);
7914 started_moving = TRUE;
7916 Store[x][y] = EL_ACID;
7919 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7920 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7921 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7922 CAN_FALL(element) && WasJustFalling[x][y] &&
7923 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7925 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7926 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7927 (Tile[x][y + 1] == EL_BLOCKED)))
7929 /* this is needed for a special case not covered by calling "Impact()"
7930 from "ContinueMoving()": if an element moves to a tile directly below
7931 another element which was just falling on that tile (which was empty
7932 in the previous frame), the falling element above would just stop
7933 instead of smashing the element below (in previous version, the above
7934 element was just checked for "moving" instead of "falling", resulting
7935 in incorrect smashes caused by horizontal movement of the above
7936 element; also, the case of the player being the element to smash was
7937 simply not covered here... :-/ ) */
7939 CheckCollision[x][y] = 0;
7940 CheckImpact[x][y] = 0;
7944 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7946 if (MovDir[x][y] == MV_NONE)
7948 InitMovingField(x, y, MV_DOWN);
7949 started_moving = TRUE;
7952 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7954 if (WasJustFalling[x][y]) // prevent animation from being restarted
7955 MovDir[x][y] = MV_DOWN;
7957 InitMovingField(x, y, MV_DOWN);
7958 started_moving = TRUE;
7960 else if (element == EL_AMOEBA_DROP)
7962 Tile[x][y] = EL_AMOEBA_GROWING;
7963 Store[x][y] = EL_AMOEBA_WET;
7965 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7966 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7967 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7968 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7970 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7971 (IS_FREE(x - 1, y + 1) ||
7972 Tile[x - 1][y + 1] == EL_ACID));
7973 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7974 (IS_FREE(x + 1, y + 1) ||
7975 Tile[x + 1][y + 1] == EL_ACID));
7976 boolean can_fall_any = (can_fall_left || can_fall_right);
7977 boolean can_fall_both = (can_fall_left && can_fall_right);
7978 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7980 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7982 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7983 can_fall_right = FALSE;
7984 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7985 can_fall_left = FALSE;
7986 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7987 can_fall_right = FALSE;
7988 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7989 can_fall_left = FALSE;
7991 can_fall_any = (can_fall_left || can_fall_right);
7992 can_fall_both = FALSE;
7997 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7998 can_fall_right = FALSE; // slip down on left side
8000 can_fall_left = !(can_fall_right = RND(2));
8002 can_fall_both = FALSE;
8007 // if not determined otherwise, prefer left side for slipping down
8008 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8009 started_moving = TRUE;
8012 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8014 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8015 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8016 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8017 int belt_dir = game.belt_dir[belt_nr];
8019 if ((belt_dir == MV_LEFT && left_is_free) ||
8020 (belt_dir == MV_RIGHT && right_is_free))
8022 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8024 InitMovingField(x, y, belt_dir);
8025 started_moving = TRUE;
8027 Pushed[x][y] = TRUE;
8028 Pushed[nextx][y] = TRUE;
8030 GfxAction[x][y] = ACTION_DEFAULT;
8034 MovDir[x][y] = 0; // if element was moving, stop it
8039 // not "else if" because of elements that can fall and move (EL_SPRING)
8040 if (CAN_MOVE(element) && !started_moving)
8042 int move_pattern = element_info[element].move_pattern;
8045 Moving2Blocked(x, y, &newx, &newy);
8047 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8050 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8051 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8053 WasJustMoving[x][y] = 0;
8054 CheckCollision[x][y] = 0;
8056 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8058 if (Tile[x][y] != element) // element has changed
8062 if (!MovDelay[x][y]) // start new movement phase
8064 // all objects that can change their move direction after each step
8065 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8067 if (element != EL_YAMYAM &&
8068 element != EL_DARK_YAMYAM &&
8069 element != EL_PACMAN &&
8070 !(move_pattern & MV_ANY_DIRECTION) &&
8071 move_pattern != MV_TURNING_LEFT &&
8072 move_pattern != MV_TURNING_RIGHT &&
8073 move_pattern != MV_TURNING_LEFT_RIGHT &&
8074 move_pattern != MV_TURNING_RIGHT_LEFT &&
8075 move_pattern != MV_TURNING_RANDOM)
8079 if (MovDelay[x][y] && (element == EL_BUG ||
8080 element == EL_SPACESHIP ||
8081 element == EL_SP_SNIKSNAK ||
8082 element == EL_SP_ELECTRON ||
8083 element == EL_MOLE))
8084 TEST_DrawLevelField(x, y);
8088 if (MovDelay[x][y]) // wait some time before next movement
8092 if (element == EL_ROBOT ||
8093 element == EL_YAMYAM ||
8094 element == EL_DARK_YAMYAM)
8096 DrawLevelElementAnimationIfNeeded(x, y, element);
8097 PlayLevelSoundAction(x, y, ACTION_WAITING);
8099 else if (element == EL_SP_ELECTRON)
8100 DrawLevelElementAnimationIfNeeded(x, y, element);
8101 else if (element == EL_DRAGON)
8104 int dir = MovDir[x][y];
8105 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8106 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8107 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8108 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8109 dir == MV_UP ? IMG_FLAMES_1_UP :
8110 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8111 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8113 GfxAction[x][y] = ACTION_ATTACKING;
8115 if (IS_PLAYER(x, y))
8116 DrawPlayerField(x, y);
8118 TEST_DrawLevelField(x, y);
8120 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8122 for (i = 1; i <= 3; i++)
8124 int xx = x + i * dx;
8125 int yy = y + i * dy;
8126 int sx = SCREENX(xx);
8127 int sy = SCREENY(yy);
8128 int flame_graphic = graphic + (i - 1);
8130 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8135 int flamed = MovingOrBlocked2Element(xx, yy);
8137 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8140 RemoveMovingField(xx, yy);
8142 ChangeDelay[xx][yy] = 0;
8144 Tile[xx][yy] = EL_FLAMES;
8146 if (IN_SCR_FIELD(sx, sy))
8148 TEST_DrawLevelFieldCrumbled(xx, yy);
8149 DrawGraphic(sx, sy, flame_graphic, frame);
8154 if (Tile[xx][yy] == EL_FLAMES)
8155 Tile[xx][yy] = EL_EMPTY;
8156 TEST_DrawLevelField(xx, yy);
8161 if (MovDelay[x][y]) // element still has to wait some time
8163 PlayLevelSoundAction(x, y, ACTION_WAITING);
8169 // now make next step
8171 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8173 if (DONT_COLLIDE_WITH(element) &&
8174 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8175 !PLAYER_ENEMY_PROTECTED(newx, newy))
8177 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8182 else if (CAN_MOVE_INTO_ACID(element) &&
8183 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8184 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8185 (MovDir[x][y] == MV_DOWN ||
8186 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8188 SplashAcid(newx, newy);
8189 Store[x][y] = EL_ACID;
8191 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8193 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8194 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8195 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8196 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8199 TEST_DrawLevelField(x, y);
8201 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8202 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8203 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8205 game.friends_still_needed--;
8206 if (!game.friends_still_needed &&
8208 game.all_players_gone)
8213 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8215 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8216 TEST_DrawLevelField(newx, newy);
8218 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8220 else if (!IS_FREE(newx, newy))
8222 GfxAction[x][y] = ACTION_WAITING;
8224 if (IS_PLAYER(x, y))
8225 DrawPlayerField(x, y);
8227 TEST_DrawLevelField(x, y);
8232 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8234 if (IS_FOOD_PIG(Tile[newx][newy]))
8236 if (IS_MOVING(newx, newy))
8237 RemoveMovingField(newx, newy);
8240 Tile[newx][newy] = EL_EMPTY;
8241 TEST_DrawLevelField(newx, newy);
8244 PlayLevelSound(x, y, SND_PIG_DIGGING);
8246 else if (!IS_FREE(newx, newy))
8248 if (IS_PLAYER(x, y))
8249 DrawPlayerField(x, y);
8251 TEST_DrawLevelField(x, y);
8256 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8258 if (Store[x][y] != EL_EMPTY)
8260 boolean can_clone = FALSE;
8263 // check if element to clone is still there
8264 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8266 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8274 // cannot clone or target field not free anymore -- do not clone
8275 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8276 Store[x][y] = EL_EMPTY;
8279 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8281 if (IS_MV_DIAGONAL(MovDir[x][y]))
8283 int diagonal_move_dir = MovDir[x][y];
8284 int stored = Store[x][y];
8285 int change_delay = 8;
8288 // android is moving diagonally
8290 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8292 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8293 GfxElement[x][y] = EL_EMC_ANDROID;
8294 GfxAction[x][y] = ACTION_SHRINKING;
8295 GfxDir[x][y] = diagonal_move_dir;
8296 ChangeDelay[x][y] = change_delay;
8298 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8301 DrawLevelGraphicAnimation(x, y, graphic);
8302 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8304 if (Tile[newx][newy] == EL_ACID)
8306 SplashAcid(newx, newy);
8311 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8313 Store[newx][newy] = EL_EMC_ANDROID;
8314 GfxElement[newx][newy] = EL_EMC_ANDROID;
8315 GfxAction[newx][newy] = ACTION_GROWING;
8316 GfxDir[newx][newy] = diagonal_move_dir;
8317 ChangeDelay[newx][newy] = change_delay;
8319 graphic = el_act_dir2img(GfxElement[newx][newy],
8320 GfxAction[newx][newy], GfxDir[newx][newy]);
8322 DrawLevelGraphicAnimation(newx, newy, graphic);
8323 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8329 Tile[newx][newy] = EL_EMPTY;
8330 TEST_DrawLevelField(newx, newy);
8332 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8335 else if (!IS_FREE(newx, newy))
8340 else if (IS_CUSTOM_ELEMENT(element) &&
8341 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8343 if (!DigFieldByCE(newx, newy, element))
8346 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8348 RunnerVisit[x][y] = FrameCounter;
8349 PlayerVisit[x][y] /= 8; // expire player visit path
8352 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8354 if (!IS_FREE(newx, newy))
8356 if (IS_PLAYER(x, y))
8357 DrawPlayerField(x, y);
8359 TEST_DrawLevelField(x, y);
8365 boolean wanna_flame = !RND(10);
8366 int dx = newx - x, dy = newy - y;
8367 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8368 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8369 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8370 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8371 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8372 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8375 IS_CLASSIC_ENEMY(element1) ||
8376 IS_CLASSIC_ENEMY(element2)) &&
8377 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8378 element1 != EL_FLAMES && element2 != EL_FLAMES)
8380 ResetGfxAnimation(x, y);
8381 GfxAction[x][y] = ACTION_ATTACKING;
8383 if (IS_PLAYER(x, y))
8384 DrawPlayerField(x, y);
8386 TEST_DrawLevelField(x, y);
8388 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8390 MovDelay[x][y] = 50;
8392 Tile[newx][newy] = EL_FLAMES;
8393 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8394 Tile[newx1][newy1] = EL_FLAMES;
8395 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8396 Tile[newx2][newy2] = EL_FLAMES;
8402 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8403 Tile[newx][newy] == EL_DIAMOND)
8405 if (IS_MOVING(newx, newy))
8406 RemoveMovingField(newx, newy);
8409 Tile[newx][newy] = EL_EMPTY;
8410 TEST_DrawLevelField(newx, newy);
8413 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8415 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8416 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8418 if (AmoebaNr[newx][newy])
8420 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8421 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8422 Tile[newx][newy] == EL_BD_AMOEBA)
8423 AmoebaCnt[AmoebaNr[newx][newy]]--;
8426 if (IS_MOVING(newx, newy))
8428 RemoveMovingField(newx, newy);
8432 Tile[newx][newy] = EL_EMPTY;
8433 TEST_DrawLevelField(newx, newy);
8436 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8438 else if ((element == EL_PACMAN || element == EL_MOLE)
8439 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8441 if (AmoebaNr[newx][newy])
8443 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8444 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8445 Tile[newx][newy] == EL_BD_AMOEBA)
8446 AmoebaCnt[AmoebaNr[newx][newy]]--;
8449 if (element == EL_MOLE)
8451 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8452 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8454 ResetGfxAnimation(x, y);
8455 GfxAction[x][y] = ACTION_DIGGING;
8456 TEST_DrawLevelField(x, y);
8458 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8460 return; // wait for shrinking amoeba
8462 else // element == EL_PACMAN
8464 Tile[newx][newy] = EL_EMPTY;
8465 TEST_DrawLevelField(newx, newy);
8466 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8469 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8470 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8471 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8473 // wait for shrinking amoeba to completely disappear
8476 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8478 // object was running against a wall
8482 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8483 DrawLevelElementAnimation(x, y, element);
8485 if (DONT_TOUCH(element))
8486 TestIfBadThingTouchesPlayer(x, y);
8491 InitMovingField(x, y, MovDir[x][y]);
8493 PlayLevelSoundAction(x, y, ACTION_MOVING);
8497 ContinueMoving(x, y);
8500 void ContinueMoving(int x, int y)
8502 int element = Tile[x][y];
8503 struct ElementInfo *ei = &element_info[element];
8504 int direction = MovDir[x][y];
8505 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8506 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8507 int newx = x + dx, newy = y + dy;
8508 int stored = Store[x][y];
8509 int stored_new = Store[newx][newy];
8510 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8511 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8512 boolean last_line = (newy == lev_fieldy - 1);
8514 MovPos[x][y] += getElementMoveStepsize(x, y);
8516 if (pushed_by_player) // special case: moving object pushed by player
8517 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8519 if (ABS(MovPos[x][y]) < TILEX)
8521 TEST_DrawLevelField(x, y);
8523 return; // element is still moving
8526 // element reached destination field
8528 Tile[x][y] = EL_EMPTY;
8529 Tile[newx][newy] = element;
8530 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8532 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8534 element = Tile[newx][newy] = EL_ACID;
8536 else if (element == EL_MOLE)
8538 Tile[x][y] = EL_SAND;
8540 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8542 else if (element == EL_QUICKSAND_FILLING)
8544 element = Tile[newx][newy] = get_next_element(element);
8545 Store[newx][newy] = Store[x][y];
8547 else if (element == EL_QUICKSAND_EMPTYING)
8549 Tile[x][y] = get_next_element(element);
8550 element = Tile[newx][newy] = Store[x][y];
8552 else if (element == EL_QUICKSAND_FAST_FILLING)
8554 element = Tile[newx][newy] = get_next_element(element);
8555 Store[newx][newy] = Store[x][y];
8557 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8559 Tile[x][y] = get_next_element(element);
8560 element = Tile[newx][newy] = Store[x][y];
8562 else if (element == EL_MAGIC_WALL_FILLING)
8564 element = Tile[newx][newy] = get_next_element(element);
8565 if (!game.magic_wall_active)
8566 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8567 Store[newx][newy] = Store[x][y];
8569 else if (element == EL_MAGIC_WALL_EMPTYING)
8571 Tile[x][y] = get_next_element(element);
8572 if (!game.magic_wall_active)
8573 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8574 element = Tile[newx][newy] = Store[x][y];
8576 InitField(newx, newy, FALSE);
8578 else if (element == EL_BD_MAGIC_WALL_FILLING)
8580 element = Tile[newx][newy] = get_next_element(element);
8581 if (!game.magic_wall_active)
8582 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8583 Store[newx][newy] = Store[x][y];
8585 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8587 Tile[x][y] = get_next_element(element);
8588 if (!game.magic_wall_active)
8589 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8590 element = Tile[newx][newy] = Store[x][y];
8592 InitField(newx, newy, FALSE);
8594 else if (element == EL_DC_MAGIC_WALL_FILLING)
8596 element = Tile[newx][newy] = get_next_element(element);
8597 if (!game.magic_wall_active)
8598 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8599 Store[newx][newy] = Store[x][y];
8601 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8603 Tile[x][y] = get_next_element(element);
8604 if (!game.magic_wall_active)
8605 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8606 element = Tile[newx][newy] = Store[x][y];
8608 InitField(newx, newy, FALSE);
8610 else if (element == EL_AMOEBA_DROPPING)
8612 Tile[x][y] = get_next_element(element);
8613 element = Tile[newx][newy] = Store[x][y];
8615 else if (element == EL_SOKOBAN_OBJECT)
8618 Tile[x][y] = Back[x][y];
8620 if (Back[newx][newy])
8621 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8623 Back[x][y] = Back[newx][newy] = 0;
8626 Store[x][y] = EL_EMPTY;
8631 MovDelay[newx][newy] = 0;
8633 if (CAN_CHANGE_OR_HAS_ACTION(element))
8635 // copy element change control values to new field
8636 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8637 ChangePage[newx][newy] = ChangePage[x][y];
8638 ChangeCount[newx][newy] = ChangeCount[x][y];
8639 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8642 CustomValue[newx][newy] = CustomValue[x][y];
8644 ChangeDelay[x][y] = 0;
8645 ChangePage[x][y] = -1;
8646 ChangeCount[x][y] = 0;
8647 ChangeEvent[x][y] = -1;
8649 CustomValue[x][y] = 0;
8651 // copy animation control values to new field
8652 GfxFrame[newx][newy] = GfxFrame[x][y];
8653 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8654 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8655 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8657 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8659 // some elements can leave other elements behind after moving
8660 if (ei->move_leave_element != EL_EMPTY &&
8661 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8662 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8664 int move_leave_element = ei->move_leave_element;
8666 // this makes it possible to leave the removed element again
8667 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8668 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8670 Tile[x][y] = move_leave_element;
8672 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8673 MovDir[x][y] = direction;
8675 InitField(x, y, FALSE);
8677 if (GFX_CRUMBLED(Tile[x][y]))
8678 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8680 if (ELEM_IS_PLAYER(move_leave_element))
8681 RelocatePlayer(x, y, move_leave_element);
8684 // do this after checking for left-behind element
8685 ResetGfxAnimation(x, y); // reset animation values for old field
8687 if (!CAN_MOVE(element) ||
8688 (CAN_FALL(element) && direction == MV_DOWN &&
8689 (element == EL_SPRING ||
8690 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8691 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8692 GfxDir[x][y] = MovDir[newx][newy] = 0;
8694 TEST_DrawLevelField(x, y);
8695 TEST_DrawLevelField(newx, newy);
8697 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8699 // prevent pushed element from moving on in pushed direction
8700 if (pushed_by_player && CAN_MOVE(element) &&
8701 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8702 !(element_info[element].move_pattern & direction))
8703 TurnRound(newx, newy);
8705 // prevent elements on conveyor belt from moving on in last direction
8706 if (pushed_by_conveyor && CAN_FALL(element) &&
8707 direction & MV_HORIZONTAL)
8708 MovDir[newx][newy] = 0;
8710 if (!pushed_by_player)
8712 int nextx = newx + dx, nexty = newy + dy;
8713 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8715 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8717 if (CAN_FALL(element) && direction == MV_DOWN)
8718 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8720 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8721 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8723 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8724 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8727 if (DONT_TOUCH(element)) // object may be nasty to player or others
8729 TestIfBadThingTouchesPlayer(newx, newy);
8730 TestIfBadThingTouchesFriend(newx, newy);
8732 if (!IS_CUSTOM_ELEMENT(element))
8733 TestIfBadThingTouchesOtherBadThing(newx, newy);
8735 else if (element == EL_PENGUIN)
8736 TestIfFriendTouchesBadThing(newx, newy);
8738 if (DONT_GET_HIT_BY(element))
8740 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8743 // give the player one last chance (one more frame) to move away
8744 if (CAN_FALL(element) && direction == MV_DOWN &&
8745 (last_line || (!IS_FREE(x, newy + 1) &&
8746 (!IS_PLAYER(x, newy + 1) ||
8747 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8750 if (pushed_by_player && !game.use_change_when_pushing_bug)
8752 int push_side = MV_DIR_OPPOSITE(direction);
8753 struct PlayerInfo *player = PLAYERINFO(x, y);
8755 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8756 player->index_bit, push_side);
8757 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8758 player->index_bit, push_side);
8761 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8762 MovDelay[newx][newy] = 1;
8764 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8766 TestIfElementTouchesCustomElement(x, y); // empty or new element
8767 TestIfElementHitsCustomElement(newx, newy, direction);
8768 TestIfPlayerTouchesCustomElement(newx, newy);
8769 TestIfElementTouchesCustomElement(newx, newy);
8771 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8772 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8773 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8774 MV_DIR_OPPOSITE(direction));
8777 int AmoebaNeighbourNr(int ax, int ay)
8780 int element = Tile[ax][ay];
8782 static int xy[4][2] =
8790 for (i = 0; i < NUM_DIRECTIONS; i++)
8792 int x = ax + xy[i][0];
8793 int y = ay + xy[i][1];
8795 if (!IN_LEV_FIELD(x, y))
8798 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8799 group_nr = AmoebaNr[x][y];
8805 static void AmoebaMerge(int ax, int ay)
8807 int i, x, y, xx, yy;
8808 int new_group_nr = AmoebaNr[ax][ay];
8809 static int xy[4][2] =
8817 if (new_group_nr == 0)
8820 for (i = 0; i < NUM_DIRECTIONS; i++)
8825 if (!IN_LEV_FIELD(x, y))
8828 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8829 Tile[x][y] == EL_BD_AMOEBA ||
8830 Tile[x][y] == EL_AMOEBA_DEAD) &&
8831 AmoebaNr[x][y] != new_group_nr)
8833 int old_group_nr = AmoebaNr[x][y];
8835 if (old_group_nr == 0)
8838 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8839 AmoebaCnt[old_group_nr] = 0;
8840 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8841 AmoebaCnt2[old_group_nr] = 0;
8843 SCAN_PLAYFIELD(xx, yy)
8845 if (AmoebaNr[xx][yy] == old_group_nr)
8846 AmoebaNr[xx][yy] = new_group_nr;
8852 void AmoebaToDiamond(int ax, int ay)
8856 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8858 int group_nr = AmoebaNr[ax][ay];
8863 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8864 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8870 SCAN_PLAYFIELD(x, y)
8872 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8875 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8879 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8880 SND_AMOEBA_TURNING_TO_GEM :
8881 SND_AMOEBA_TURNING_TO_ROCK));
8886 static int xy[4][2] =
8894 for (i = 0; i < NUM_DIRECTIONS; i++)
8899 if (!IN_LEV_FIELD(x, y))
8902 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8904 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8905 SND_AMOEBA_TURNING_TO_GEM :
8906 SND_AMOEBA_TURNING_TO_ROCK));
8913 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8916 int group_nr = AmoebaNr[ax][ay];
8917 boolean done = FALSE;
8922 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8923 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8929 SCAN_PLAYFIELD(x, y)
8931 if (AmoebaNr[x][y] == group_nr &&
8932 (Tile[x][y] == EL_AMOEBA_DEAD ||
8933 Tile[x][y] == EL_BD_AMOEBA ||
8934 Tile[x][y] == EL_AMOEBA_GROWING))
8937 Tile[x][y] = new_element;
8938 InitField(x, y, FALSE);
8939 TEST_DrawLevelField(x, y);
8945 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8946 SND_BD_AMOEBA_TURNING_TO_ROCK :
8947 SND_BD_AMOEBA_TURNING_TO_GEM));
8950 static void AmoebaGrowing(int x, int y)
8952 static unsigned int sound_delay = 0;
8953 static unsigned int sound_delay_value = 0;
8955 if (!MovDelay[x][y]) // start new growing cycle
8959 if (DelayReached(&sound_delay, sound_delay_value))
8961 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8962 sound_delay_value = 30;
8966 if (MovDelay[x][y]) // wait some time before growing bigger
8969 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8971 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8972 6 - MovDelay[x][y]);
8974 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8977 if (!MovDelay[x][y])
8979 Tile[x][y] = Store[x][y];
8981 TEST_DrawLevelField(x, y);
8986 static void AmoebaShrinking(int x, int y)
8988 static unsigned int sound_delay = 0;
8989 static unsigned int sound_delay_value = 0;
8991 if (!MovDelay[x][y]) // start new shrinking cycle
8995 if (DelayReached(&sound_delay, sound_delay_value))
8996 sound_delay_value = 30;
8999 if (MovDelay[x][y]) // wait some time before shrinking
9002 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9004 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9005 6 - MovDelay[x][y]);
9007 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9010 if (!MovDelay[x][y])
9012 Tile[x][y] = EL_EMPTY;
9013 TEST_DrawLevelField(x, y);
9015 // don't let mole enter this field in this cycle;
9016 // (give priority to objects falling to this field from above)
9022 static void AmoebaReproduce(int ax, int ay)
9025 int element = Tile[ax][ay];
9026 int graphic = el2img(element);
9027 int newax = ax, neway = ay;
9028 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9029 static int xy[4][2] =
9037 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9039 Tile[ax][ay] = EL_AMOEBA_DEAD;
9040 TEST_DrawLevelField(ax, ay);
9044 if (IS_ANIMATED(graphic))
9045 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9047 if (!MovDelay[ax][ay]) // start making new amoeba field
9048 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9050 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9053 if (MovDelay[ax][ay])
9057 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9060 int x = ax + xy[start][0];
9061 int y = ay + xy[start][1];
9063 if (!IN_LEV_FIELD(x, y))
9066 if (IS_FREE(x, y) ||
9067 CAN_GROW_INTO(Tile[x][y]) ||
9068 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9069 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9075 if (newax == ax && neway == ay)
9078 else // normal or "filled" (BD style) amoeba
9081 boolean waiting_for_player = FALSE;
9083 for (i = 0; i < NUM_DIRECTIONS; i++)
9085 int j = (start + i) % 4;
9086 int x = ax + xy[j][0];
9087 int y = ay + xy[j][1];
9089 if (!IN_LEV_FIELD(x, y))
9092 if (IS_FREE(x, y) ||
9093 CAN_GROW_INTO(Tile[x][y]) ||
9094 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9095 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9101 else if (IS_PLAYER(x, y))
9102 waiting_for_player = TRUE;
9105 if (newax == ax && neway == ay) // amoeba cannot grow
9107 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9109 Tile[ax][ay] = EL_AMOEBA_DEAD;
9110 TEST_DrawLevelField(ax, ay);
9111 AmoebaCnt[AmoebaNr[ax][ay]]--;
9113 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9115 if (element == EL_AMOEBA_FULL)
9116 AmoebaToDiamond(ax, ay);
9117 else if (element == EL_BD_AMOEBA)
9118 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9123 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9125 // amoeba gets larger by growing in some direction
9127 int new_group_nr = AmoebaNr[ax][ay];
9130 if (new_group_nr == 0)
9132 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9134 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9140 AmoebaNr[newax][neway] = new_group_nr;
9141 AmoebaCnt[new_group_nr]++;
9142 AmoebaCnt2[new_group_nr]++;
9144 // if amoeba touches other amoeba(s) after growing, unify them
9145 AmoebaMerge(newax, neway);
9147 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9149 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9155 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9156 (neway == lev_fieldy - 1 && newax != ax))
9158 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9159 Store[newax][neway] = element;
9161 else if (neway == ay || element == EL_EMC_DRIPPER)
9163 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9165 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9169 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9170 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9171 Store[ax][ay] = EL_AMOEBA_DROP;
9172 ContinueMoving(ax, ay);
9176 TEST_DrawLevelField(newax, neway);
9179 static void Life(int ax, int ay)
9183 int element = Tile[ax][ay];
9184 int graphic = el2img(element);
9185 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9187 boolean changed = FALSE;
9189 if (IS_ANIMATED(graphic))
9190 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9195 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9196 MovDelay[ax][ay] = life_time;
9198 if (MovDelay[ax][ay]) // wait some time before next cycle
9201 if (MovDelay[ax][ay])
9205 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9207 int xx = ax+x1, yy = ay+y1;
9208 int old_element = Tile[xx][yy];
9209 int num_neighbours = 0;
9211 if (!IN_LEV_FIELD(xx, yy))
9214 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9216 int x = xx+x2, y = yy+y2;
9218 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9221 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9222 boolean is_neighbour = FALSE;
9224 if (level.use_life_bugs)
9226 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9227 (IS_FREE(x, y) && Stop[x][y]));
9230 (Last[x][y] == element || is_player_cell);
9236 boolean is_free = FALSE;
9238 if (level.use_life_bugs)
9239 is_free = (IS_FREE(xx, yy));
9241 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9243 if (xx == ax && yy == ay) // field in the middle
9245 if (num_neighbours < life_parameter[0] ||
9246 num_neighbours > life_parameter[1])
9248 Tile[xx][yy] = EL_EMPTY;
9249 if (Tile[xx][yy] != old_element)
9250 TEST_DrawLevelField(xx, yy);
9251 Stop[xx][yy] = TRUE;
9255 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9256 { // free border field
9257 if (num_neighbours >= life_parameter[2] &&
9258 num_neighbours <= life_parameter[3])
9260 Tile[xx][yy] = element;
9261 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9262 if (Tile[xx][yy] != old_element)
9263 TEST_DrawLevelField(xx, yy);
9264 Stop[xx][yy] = TRUE;
9271 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9272 SND_GAME_OF_LIFE_GROWING);
9275 static void InitRobotWheel(int x, int y)
9277 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9280 static void RunRobotWheel(int x, int y)
9282 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9285 static void StopRobotWheel(int x, int y)
9287 if (game.robot_wheel_x == x &&
9288 game.robot_wheel_y == y)
9290 game.robot_wheel_x = -1;
9291 game.robot_wheel_y = -1;
9292 game.robot_wheel_active = FALSE;
9296 static void InitTimegateWheel(int x, int y)
9298 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9301 static void RunTimegateWheel(int x, int y)
9303 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9306 static void InitMagicBallDelay(int x, int y)
9308 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9311 static void ActivateMagicBall(int bx, int by)
9315 if (level.ball_random)
9317 int pos_border = RND(8); // select one of the eight border elements
9318 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9319 int xx = pos_content % 3;
9320 int yy = pos_content / 3;
9325 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9326 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9330 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9332 int xx = x - bx + 1;
9333 int yy = y - by + 1;
9335 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9336 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9340 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9343 static void CheckExit(int x, int y)
9345 if (game.gems_still_needed > 0 ||
9346 game.sokoban_fields_still_needed > 0 ||
9347 game.sokoban_objects_still_needed > 0 ||
9348 game.lights_still_needed > 0)
9350 int element = Tile[x][y];
9351 int graphic = el2img(element);
9353 if (IS_ANIMATED(graphic))
9354 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9359 // do not re-open exit door closed after last player
9360 if (game.all_players_gone)
9363 Tile[x][y] = EL_EXIT_OPENING;
9365 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9368 static void CheckExitEM(int x, int y)
9370 if (game.gems_still_needed > 0 ||
9371 game.sokoban_fields_still_needed > 0 ||
9372 game.sokoban_objects_still_needed > 0 ||
9373 game.lights_still_needed > 0)
9375 int element = Tile[x][y];
9376 int graphic = el2img(element);
9378 if (IS_ANIMATED(graphic))
9379 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9384 // do not re-open exit door closed after last player
9385 if (game.all_players_gone)
9388 Tile[x][y] = EL_EM_EXIT_OPENING;
9390 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9393 static void CheckExitSteel(int x, int y)
9395 if (game.gems_still_needed > 0 ||
9396 game.sokoban_fields_still_needed > 0 ||
9397 game.sokoban_objects_still_needed > 0 ||
9398 game.lights_still_needed > 0)
9400 int element = Tile[x][y];
9401 int graphic = el2img(element);
9403 if (IS_ANIMATED(graphic))
9404 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9409 // do not re-open exit door closed after last player
9410 if (game.all_players_gone)
9413 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9415 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9418 static void CheckExitSteelEM(int x, int y)
9420 if (game.gems_still_needed > 0 ||
9421 game.sokoban_fields_still_needed > 0 ||
9422 game.sokoban_objects_still_needed > 0 ||
9423 game.lights_still_needed > 0)
9425 int element = Tile[x][y];
9426 int graphic = el2img(element);
9428 if (IS_ANIMATED(graphic))
9429 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9434 // do not re-open exit door closed after last player
9435 if (game.all_players_gone)
9438 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9440 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9443 static void CheckExitSP(int x, int y)
9445 if (game.gems_still_needed > 0)
9447 int element = Tile[x][y];
9448 int graphic = el2img(element);
9450 if (IS_ANIMATED(graphic))
9451 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9456 // do not re-open exit door closed after last player
9457 if (game.all_players_gone)
9460 Tile[x][y] = EL_SP_EXIT_OPENING;
9462 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9465 static void CloseAllOpenTimegates(void)
9469 SCAN_PLAYFIELD(x, y)
9471 int element = Tile[x][y];
9473 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9475 Tile[x][y] = EL_TIMEGATE_CLOSING;
9477 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9482 static void DrawTwinkleOnField(int x, int y)
9484 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9487 if (Tile[x][y] == EL_BD_DIAMOND)
9490 if (MovDelay[x][y] == 0) // next animation frame
9491 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9493 if (MovDelay[x][y] != 0) // wait some time before next frame
9497 DrawLevelElementAnimation(x, y, Tile[x][y]);
9499 if (MovDelay[x][y] != 0)
9501 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9502 10 - MovDelay[x][y]);
9504 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9509 static void MauerWaechst(int x, int y)
9513 if (!MovDelay[x][y]) // next animation frame
9514 MovDelay[x][y] = 3 * delay;
9516 if (MovDelay[x][y]) // wait some time before next frame
9520 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9522 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9523 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9525 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9528 if (!MovDelay[x][y])
9530 if (MovDir[x][y] == MV_LEFT)
9532 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9533 TEST_DrawLevelField(x - 1, y);
9535 else if (MovDir[x][y] == MV_RIGHT)
9537 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9538 TEST_DrawLevelField(x + 1, y);
9540 else if (MovDir[x][y] == MV_UP)
9542 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9543 TEST_DrawLevelField(x, y - 1);
9547 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9548 TEST_DrawLevelField(x, y + 1);
9551 Tile[x][y] = Store[x][y];
9553 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9554 TEST_DrawLevelField(x, y);
9559 static void MauerAbleger(int ax, int ay)
9561 int element = Tile[ax][ay];
9562 int graphic = el2img(element);
9563 boolean oben_frei = FALSE, unten_frei = FALSE;
9564 boolean links_frei = FALSE, rechts_frei = FALSE;
9565 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9566 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9567 boolean new_wall = FALSE;
9569 if (IS_ANIMATED(graphic))
9570 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9572 if (!MovDelay[ax][ay]) // start building new wall
9573 MovDelay[ax][ay] = 6;
9575 if (MovDelay[ax][ay]) // wait some time before building new wall
9578 if (MovDelay[ax][ay])
9582 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9584 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9586 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9588 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9591 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9592 element == EL_EXPANDABLE_WALL_ANY)
9596 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9597 Store[ax][ay-1] = element;
9598 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9599 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9600 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9601 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9606 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9607 Store[ax][ay+1] = element;
9608 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9609 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9610 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9611 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9616 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9617 element == EL_EXPANDABLE_WALL_ANY ||
9618 element == EL_EXPANDABLE_WALL ||
9619 element == EL_BD_EXPANDABLE_WALL)
9623 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9624 Store[ax-1][ay] = element;
9625 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9626 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9627 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9628 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9634 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9635 Store[ax+1][ay] = element;
9636 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9637 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9638 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9639 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9644 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9645 TEST_DrawLevelField(ax, ay);
9647 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9649 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9650 unten_massiv = TRUE;
9651 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9652 links_massiv = TRUE;
9653 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9654 rechts_massiv = TRUE;
9656 if (((oben_massiv && unten_massiv) ||
9657 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9658 element == EL_EXPANDABLE_WALL) &&
9659 ((links_massiv && rechts_massiv) ||
9660 element == EL_EXPANDABLE_WALL_VERTICAL))
9661 Tile[ax][ay] = EL_WALL;
9664 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9667 static void MauerAblegerStahl(int ax, int ay)
9669 int element = Tile[ax][ay];
9670 int graphic = el2img(element);
9671 boolean oben_frei = FALSE, unten_frei = FALSE;
9672 boolean links_frei = FALSE, rechts_frei = FALSE;
9673 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9674 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9675 boolean new_wall = FALSE;
9677 if (IS_ANIMATED(graphic))
9678 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9680 if (!MovDelay[ax][ay]) // start building new wall
9681 MovDelay[ax][ay] = 6;
9683 if (MovDelay[ax][ay]) // wait some time before building new wall
9686 if (MovDelay[ax][ay])
9690 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9692 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9694 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9696 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9699 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9700 element == EL_EXPANDABLE_STEELWALL_ANY)
9704 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9705 Store[ax][ay-1] = element;
9706 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9707 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9708 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9709 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9714 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9715 Store[ax][ay+1] = element;
9716 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9717 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9718 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9719 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9724 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9725 element == EL_EXPANDABLE_STEELWALL_ANY)
9729 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9730 Store[ax-1][ay] = element;
9731 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9732 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9733 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9734 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9740 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9741 Store[ax+1][ay] = element;
9742 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9743 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9744 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9745 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9750 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9752 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9753 unten_massiv = TRUE;
9754 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9755 links_massiv = TRUE;
9756 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9757 rechts_massiv = TRUE;
9759 if (((oben_massiv && unten_massiv) ||
9760 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9761 ((links_massiv && rechts_massiv) ||
9762 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9763 Tile[ax][ay] = EL_STEELWALL;
9766 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9769 static void CheckForDragon(int x, int y)
9772 boolean dragon_found = FALSE;
9773 static int xy[4][2] =
9781 for (i = 0; i < NUM_DIRECTIONS; i++)
9783 for (j = 0; j < 4; j++)
9785 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9787 if (IN_LEV_FIELD(xx, yy) &&
9788 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9790 if (Tile[xx][yy] == EL_DRAGON)
9791 dragon_found = TRUE;
9800 for (i = 0; i < NUM_DIRECTIONS; i++)
9802 for (j = 0; j < 3; j++)
9804 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9806 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9808 Tile[xx][yy] = EL_EMPTY;
9809 TEST_DrawLevelField(xx, yy);
9818 static void InitBuggyBase(int x, int y)
9820 int element = Tile[x][y];
9821 int activating_delay = FRAMES_PER_SECOND / 4;
9824 (element == EL_SP_BUGGY_BASE ?
9825 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9826 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9828 element == EL_SP_BUGGY_BASE_ACTIVE ?
9829 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9832 static void WarnBuggyBase(int x, int y)
9835 static int xy[4][2] =
9843 for (i = 0; i < NUM_DIRECTIONS; i++)
9845 int xx = x + xy[i][0];
9846 int yy = y + xy[i][1];
9848 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9850 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9857 static void InitTrap(int x, int y)
9859 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9862 static void ActivateTrap(int x, int y)
9864 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9867 static void ChangeActiveTrap(int x, int y)
9869 int graphic = IMG_TRAP_ACTIVE;
9871 // if new animation frame was drawn, correct crumbled sand border
9872 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9873 TEST_DrawLevelFieldCrumbled(x, y);
9876 static int getSpecialActionElement(int element, int number, int base_element)
9878 return (element != EL_EMPTY ? element :
9879 number != -1 ? base_element + number - 1 :
9883 static int getModifiedActionNumber(int value_old, int operator, int operand,
9884 int value_min, int value_max)
9886 int value_new = (operator == CA_MODE_SET ? operand :
9887 operator == CA_MODE_ADD ? value_old + operand :
9888 operator == CA_MODE_SUBTRACT ? value_old - operand :
9889 operator == CA_MODE_MULTIPLY ? value_old * operand :
9890 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9891 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9894 return (value_new < value_min ? value_min :
9895 value_new > value_max ? value_max :
9899 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9901 struct ElementInfo *ei = &element_info[element];
9902 struct ElementChangeInfo *change = &ei->change_page[page];
9903 int target_element = change->target_element;
9904 int action_type = change->action_type;
9905 int action_mode = change->action_mode;
9906 int action_arg = change->action_arg;
9907 int action_element = change->action_element;
9910 if (!change->has_action)
9913 // ---------- determine action paramater values -----------------------------
9915 int level_time_value =
9916 (level.time > 0 ? TimeLeft :
9919 int action_arg_element_raw =
9920 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9921 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9922 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9923 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9924 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9925 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9926 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9928 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9930 int action_arg_direction =
9931 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9932 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9933 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9934 change->actual_trigger_side :
9935 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9936 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9939 int action_arg_number_min =
9940 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9943 int action_arg_number_max =
9944 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9945 action_type == CA_SET_LEVEL_GEMS ? 999 :
9946 action_type == CA_SET_LEVEL_TIME ? 9999 :
9947 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9948 action_type == CA_SET_CE_VALUE ? 9999 :
9949 action_type == CA_SET_CE_SCORE ? 9999 :
9952 int action_arg_number_reset =
9953 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9954 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9955 action_type == CA_SET_LEVEL_TIME ? level.time :
9956 action_type == CA_SET_LEVEL_SCORE ? 0 :
9957 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9958 action_type == CA_SET_CE_SCORE ? 0 :
9961 int action_arg_number =
9962 (action_arg <= CA_ARG_MAX ? action_arg :
9963 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9964 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9965 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9966 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9967 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9968 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9969 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9970 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9971 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9972 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9973 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9974 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9975 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9976 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9977 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9978 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9979 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9980 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9981 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9982 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9983 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9986 int action_arg_number_old =
9987 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9988 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9989 action_type == CA_SET_LEVEL_SCORE ? game.score :
9990 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9991 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9994 int action_arg_number_new =
9995 getModifiedActionNumber(action_arg_number_old,
9996 action_mode, action_arg_number,
9997 action_arg_number_min, action_arg_number_max);
9999 int trigger_player_bits =
10000 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10001 change->actual_trigger_player_bits : change->trigger_player);
10003 int action_arg_player_bits =
10004 (action_arg >= CA_ARG_PLAYER_1 &&
10005 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10006 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10007 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10010 // ---------- execute action -----------------------------------------------
10012 switch (action_type)
10019 // ---------- level actions ----------------------------------------------
10021 case CA_RESTART_LEVEL:
10023 game.restart_level = TRUE;
10028 case CA_SHOW_ENVELOPE:
10030 int element = getSpecialActionElement(action_arg_element,
10031 action_arg_number, EL_ENVELOPE_1);
10033 if (IS_ENVELOPE(element))
10034 local_player->show_envelope = element;
10039 case CA_SET_LEVEL_TIME:
10041 if (level.time > 0) // only modify limited time value
10043 TimeLeft = action_arg_number_new;
10045 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10047 DisplayGameControlValues();
10049 if (!TimeLeft && setup.time_limit)
10050 for (i = 0; i < MAX_PLAYERS; i++)
10051 KillPlayer(&stored_player[i]);
10057 case CA_SET_LEVEL_SCORE:
10059 game.score = action_arg_number_new;
10061 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10063 DisplayGameControlValues();
10068 case CA_SET_LEVEL_GEMS:
10070 game.gems_still_needed = action_arg_number_new;
10072 game.snapshot.collected_item = TRUE;
10074 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10076 DisplayGameControlValues();
10081 case CA_SET_LEVEL_WIND:
10083 game.wind_direction = action_arg_direction;
10088 case CA_SET_LEVEL_RANDOM_SEED:
10090 // ensure that setting a new random seed while playing is predictable
10091 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10096 // ---------- player actions ---------------------------------------------
10098 case CA_MOVE_PLAYER:
10099 case CA_MOVE_PLAYER_NEW:
10101 // automatically move to the next field in specified direction
10102 for (i = 0; i < MAX_PLAYERS; i++)
10103 if (trigger_player_bits & (1 << i))
10104 if (action_type == CA_MOVE_PLAYER ||
10105 stored_player[i].MovPos == 0)
10106 stored_player[i].programmed_action = action_arg_direction;
10111 case CA_EXIT_PLAYER:
10113 for (i = 0; i < MAX_PLAYERS; i++)
10114 if (action_arg_player_bits & (1 << i))
10115 ExitPlayer(&stored_player[i]);
10117 if (game.players_still_needed == 0)
10123 case CA_KILL_PLAYER:
10125 for (i = 0; i < MAX_PLAYERS; i++)
10126 if (action_arg_player_bits & (1 << i))
10127 KillPlayer(&stored_player[i]);
10132 case CA_SET_PLAYER_KEYS:
10134 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10135 int element = getSpecialActionElement(action_arg_element,
10136 action_arg_number, EL_KEY_1);
10138 if (IS_KEY(element))
10140 for (i = 0; i < MAX_PLAYERS; i++)
10142 if (trigger_player_bits & (1 << i))
10144 stored_player[i].key[KEY_NR(element)] = key_state;
10146 DrawGameDoorValues();
10154 case CA_SET_PLAYER_SPEED:
10156 for (i = 0; i < MAX_PLAYERS; i++)
10158 if (trigger_player_bits & (1 << i))
10160 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10162 if (action_arg == CA_ARG_SPEED_FASTER &&
10163 stored_player[i].cannot_move)
10165 action_arg_number = STEPSIZE_VERY_SLOW;
10167 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10168 action_arg == CA_ARG_SPEED_FASTER)
10170 action_arg_number = 2;
10171 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10174 else if (action_arg == CA_ARG_NUMBER_RESET)
10176 action_arg_number = level.initial_player_stepsize[i];
10180 getModifiedActionNumber(move_stepsize,
10183 action_arg_number_min,
10184 action_arg_number_max);
10186 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10193 case CA_SET_PLAYER_SHIELD:
10195 for (i = 0; i < MAX_PLAYERS; i++)
10197 if (trigger_player_bits & (1 << i))
10199 if (action_arg == CA_ARG_SHIELD_OFF)
10201 stored_player[i].shield_normal_time_left = 0;
10202 stored_player[i].shield_deadly_time_left = 0;
10204 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10206 stored_player[i].shield_normal_time_left = 999999;
10208 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10210 stored_player[i].shield_normal_time_left = 999999;
10211 stored_player[i].shield_deadly_time_left = 999999;
10219 case CA_SET_PLAYER_GRAVITY:
10221 for (i = 0; i < MAX_PLAYERS; i++)
10223 if (trigger_player_bits & (1 << i))
10225 stored_player[i].gravity =
10226 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10227 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10228 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10229 stored_player[i].gravity);
10236 case CA_SET_PLAYER_ARTWORK:
10238 for (i = 0; i < MAX_PLAYERS; i++)
10240 if (trigger_player_bits & (1 << i))
10242 int artwork_element = action_arg_element;
10244 if (action_arg == CA_ARG_ELEMENT_RESET)
10246 (level.use_artwork_element[i] ? level.artwork_element[i] :
10247 stored_player[i].element_nr);
10249 if (stored_player[i].artwork_element != artwork_element)
10250 stored_player[i].Frame = 0;
10252 stored_player[i].artwork_element = artwork_element;
10254 SetPlayerWaiting(&stored_player[i], FALSE);
10256 // set number of special actions for bored and sleeping animation
10257 stored_player[i].num_special_action_bored =
10258 get_num_special_action(artwork_element,
10259 ACTION_BORING_1, ACTION_BORING_LAST);
10260 stored_player[i].num_special_action_sleeping =
10261 get_num_special_action(artwork_element,
10262 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10269 case CA_SET_PLAYER_INVENTORY:
10271 for (i = 0; i < MAX_PLAYERS; i++)
10273 struct PlayerInfo *player = &stored_player[i];
10276 if (trigger_player_bits & (1 << i))
10278 int inventory_element = action_arg_element;
10280 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10281 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10282 action_arg == CA_ARG_ELEMENT_ACTION)
10284 int element = inventory_element;
10285 int collect_count = element_info[element].collect_count_initial;
10287 if (!IS_CUSTOM_ELEMENT(element))
10290 if (collect_count == 0)
10291 player->inventory_infinite_element = element;
10293 for (k = 0; k < collect_count; k++)
10294 if (player->inventory_size < MAX_INVENTORY_SIZE)
10295 player->inventory_element[player->inventory_size++] =
10298 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10299 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10300 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10302 if (player->inventory_infinite_element != EL_UNDEFINED &&
10303 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10304 action_arg_element_raw))
10305 player->inventory_infinite_element = EL_UNDEFINED;
10307 for (k = 0, j = 0; j < player->inventory_size; j++)
10309 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10310 action_arg_element_raw))
10311 player->inventory_element[k++] = player->inventory_element[j];
10314 player->inventory_size = k;
10316 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10318 if (player->inventory_size > 0)
10320 for (j = 0; j < player->inventory_size - 1; j++)
10321 player->inventory_element[j] = player->inventory_element[j + 1];
10323 player->inventory_size--;
10326 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10328 if (player->inventory_size > 0)
10329 player->inventory_size--;
10331 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10333 player->inventory_infinite_element = EL_UNDEFINED;
10334 player->inventory_size = 0;
10336 else if (action_arg == CA_ARG_INVENTORY_RESET)
10338 player->inventory_infinite_element = EL_UNDEFINED;
10339 player->inventory_size = 0;
10341 if (level.use_initial_inventory[i])
10343 for (j = 0; j < level.initial_inventory_size[i]; j++)
10345 int element = level.initial_inventory_content[i][j];
10346 int collect_count = element_info[element].collect_count_initial;
10348 if (!IS_CUSTOM_ELEMENT(element))
10351 if (collect_count == 0)
10352 player->inventory_infinite_element = element;
10354 for (k = 0; k < collect_count; k++)
10355 if (player->inventory_size < MAX_INVENTORY_SIZE)
10356 player->inventory_element[player->inventory_size++] =
10367 // ---------- CE actions -------------------------------------------------
10369 case CA_SET_CE_VALUE:
10371 int last_ce_value = CustomValue[x][y];
10373 CustomValue[x][y] = action_arg_number_new;
10375 if (CustomValue[x][y] != last_ce_value)
10377 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10378 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10380 if (CustomValue[x][y] == 0)
10382 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10383 ChangeCount[x][y] = 0; // allow at least one more change
10385 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10386 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10393 case CA_SET_CE_SCORE:
10395 int last_ce_score = ei->collect_score;
10397 ei->collect_score = action_arg_number_new;
10399 if (ei->collect_score != last_ce_score)
10401 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10402 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10404 if (ei->collect_score == 0)
10408 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10409 ChangeCount[x][y] = 0; // allow at least one more change
10411 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10412 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10415 This is a very special case that seems to be a mixture between
10416 CheckElementChange() and CheckTriggeredElementChange(): while
10417 the first one only affects single elements that are triggered
10418 directly, the second one affects multiple elements in the playfield
10419 that are triggered indirectly by another element. This is a third
10420 case: Changing the CE score always affects multiple identical CEs,
10421 so every affected CE must be checked, not only the single CE for
10422 which the CE score was changed in the first place (as every instance
10423 of that CE shares the same CE score, and therefore also can change)!
10425 SCAN_PLAYFIELD(xx, yy)
10427 if (Tile[xx][yy] == element)
10428 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10429 CE_SCORE_GETS_ZERO);
10437 case CA_SET_CE_ARTWORK:
10439 int artwork_element = action_arg_element;
10440 boolean reset_frame = FALSE;
10443 if (action_arg == CA_ARG_ELEMENT_RESET)
10444 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10447 if (ei->gfx_element != artwork_element)
10448 reset_frame = TRUE;
10450 ei->gfx_element = artwork_element;
10452 SCAN_PLAYFIELD(xx, yy)
10454 if (Tile[xx][yy] == element)
10458 ResetGfxAnimation(xx, yy);
10459 ResetRandomAnimationValue(xx, yy);
10462 TEST_DrawLevelField(xx, yy);
10469 // ---------- engine actions ---------------------------------------------
10471 case CA_SET_ENGINE_SCAN_MODE:
10473 InitPlayfieldScanMode(action_arg);
10483 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10485 int old_element = Tile[x][y];
10486 int new_element = GetElementFromGroupElement(element);
10487 int previous_move_direction = MovDir[x][y];
10488 int last_ce_value = CustomValue[x][y];
10489 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10490 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10491 boolean add_player_onto_element = (new_element_is_player &&
10492 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10493 IS_WALKABLE(old_element));
10495 if (!add_player_onto_element)
10497 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10498 RemoveMovingField(x, y);
10502 Tile[x][y] = new_element;
10504 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10505 MovDir[x][y] = previous_move_direction;
10507 if (element_info[new_element].use_last_ce_value)
10508 CustomValue[x][y] = last_ce_value;
10510 InitField_WithBug1(x, y, FALSE);
10512 new_element = Tile[x][y]; // element may have changed
10514 ResetGfxAnimation(x, y);
10515 ResetRandomAnimationValue(x, y);
10517 TEST_DrawLevelField(x, y);
10519 if (GFX_CRUMBLED(new_element))
10520 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10523 // check if element under the player changes from accessible to unaccessible
10524 // (needed for special case of dropping element which then changes)
10525 // (must be checked after creating new element for walkable group elements)
10526 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10527 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10534 // "ChangeCount" not set yet to allow "entered by player" change one time
10535 if (new_element_is_player)
10536 RelocatePlayer(x, y, new_element);
10539 ChangeCount[x][y]++; // count number of changes in the same frame
10541 TestIfBadThingTouchesPlayer(x, y);
10542 TestIfPlayerTouchesCustomElement(x, y);
10543 TestIfElementTouchesCustomElement(x, y);
10546 static void CreateField(int x, int y, int element)
10548 CreateFieldExt(x, y, element, FALSE);
10551 static void CreateElementFromChange(int x, int y, int element)
10553 element = GET_VALID_RUNTIME_ELEMENT(element);
10555 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10557 int old_element = Tile[x][y];
10559 // prevent changed element from moving in same engine frame
10560 // unless both old and new element can either fall or move
10561 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10562 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10566 CreateFieldExt(x, y, element, TRUE);
10569 static boolean ChangeElement(int x, int y, int element, int page)
10571 struct ElementInfo *ei = &element_info[element];
10572 struct ElementChangeInfo *change = &ei->change_page[page];
10573 int ce_value = CustomValue[x][y];
10574 int ce_score = ei->collect_score;
10575 int target_element;
10576 int old_element = Tile[x][y];
10578 // always use default change event to prevent running into a loop
10579 if (ChangeEvent[x][y] == -1)
10580 ChangeEvent[x][y] = CE_DELAY;
10582 if (ChangeEvent[x][y] == CE_DELAY)
10584 // reset actual trigger element, trigger player and action element
10585 change->actual_trigger_element = EL_EMPTY;
10586 change->actual_trigger_player = EL_EMPTY;
10587 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10588 change->actual_trigger_side = CH_SIDE_NONE;
10589 change->actual_trigger_ce_value = 0;
10590 change->actual_trigger_ce_score = 0;
10593 // do not change elements more than a specified maximum number of changes
10594 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10597 ChangeCount[x][y]++; // count number of changes in the same frame
10599 if (change->explode)
10606 if (change->use_target_content)
10608 boolean complete_replace = TRUE;
10609 boolean can_replace[3][3];
10612 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10615 boolean is_walkable;
10616 boolean is_diggable;
10617 boolean is_collectible;
10618 boolean is_removable;
10619 boolean is_destructible;
10620 int ex = x + xx - 1;
10621 int ey = y + yy - 1;
10622 int content_element = change->target_content.e[xx][yy];
10625 can_replace[xx][yy] = TRUE;
10627 if (ex == x && ey == y) // do not check changing element itself
10630 if (content_element == EL_EMPTY_SPACE)
10632 can_replace[xx][yy] = FALSE; // do not replace border with space
10637 if (!IN_LEV_FIELD(ex, ey))
10639 can_replace[xx][yy] = FALSE;
10640 complete_replace = FALSE;
10647 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10648 e = MovingOrBlocked2Element(ex, ey);
10650 is_empty = (IS_FREE(ex, ey) ||
10651 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10653 is_walkable = (is_empty || IS_WALKABLE(e));
10654 is_diggable = (is_empty || IS_DIGGABLE(e));
10655 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10656 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10657 is_removable = (is_diggable || is_collectible);
10659 can_replace[xx][yy] =
10660 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10661 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10662 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10663 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10664 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10665 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10666 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10668 if (!can_replace[xx][yy])
10669 complete_replace = FALSE;
10672 if (!change->only_if_complete || complete_replace)
10674 boolean something_has_changed = FALSE;
10676 if (change->only_if_complete && change->use_random_replace &&
10677 RND(100) < change->random_percentage)
10680 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10682 int ex = x + xx - 1;
10683 int ey = y + yy - 1;
10684 int content_element;
10686 if (can_replace[xx][yy] && (!change->use_random_replace ||
10687 RND(100) < change->random_percentage))
10689 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10690 RemoveMovingField(ex, ey);
10692 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10694 content_element = change->target_content.e[xx][yy];
10695 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10696 ce_value, ce_score);
10698 CreateElementFromChange(ex, ey, target_element);
10700 something_has_changed = TRUE;
10702 // for symmetry reasons, freeze newly created border elements
10703 if (ex != x || ey != y)
10704 Stop[ex][ey] = TRUE; // no more moving in this frame
10708 if (something_has_changed)
10710 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10711 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10717 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10718 ce_value, ce_score);
10720 if (element == EL_DIAGONAL_GROWING ||
10721 element == EL_DIAGONAL_SHRINKING)
10723 target_element = Store[x][y];
10725 Store[x][y] = EL_EMPTY;
10728 CreateElementFromChange(x, y, target_element);
10730 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10731 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10734 // this uses direct change before indirect change
10735 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10740 static void HandleElementChange(int x, int y, int page)
10742 int element = MovingOrBlocked2Element(x, y);
10743 struct ElementInfo *ei = &element_info[element];
10744 struct ElementChangeInfo *change = &ei->change_page[page];
10745 boolean handle_action_before_change = FALSE;
10748 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10749 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10751 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10752 x, y, element, element_info[element].token_name);
10753 Debug("game:playing:HandleElementChange", "This should never happen!");
10757 // this can happen with classic bombs on walkable, changing elements
10758 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10763 if (ChangeDelay[x][y] == 0) // initialize element change
10765 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10767 if (change->can_change)
10769 // !!! not clear why graphic animation should be reset at all here !!!
10770 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10771 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10774 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10776 When using an animation frame delay of 1 (this only happens with
10777 "sp_zonk.moving.left/right" in the classic graphics), the default
10778 (non-moving) animation shows wrong animation frames (while the
10779 moving animation, like "sp_zonk.moving.left/right", is correct,
10780 so this graphical bug never shows up with the classic graphics).
10781 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10782 be drawn instead of the correct frames 0,1,2,3. This is caused by
10783 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10784 an element change: First when the change delay ("ChangeDelay[][]")
10785 counter has reached zero after decrementing, then a second time in
10786 the next frame (after "GfxFrame[][]" was already incremented) when
10787 "ChangeDelay[][]" is reset to the initial delay value again.
10789 This causes frame 0 to be drawn twice, while the last frame won't
10790 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10792 As some animations may already be cleverly designed around this bug
10793 (at least the "Snake Bite" snake tail animation does this), it cannot
10794 simply be fixed here without breaking such existing animations.
10795 Unfortunately, it cannot easily be detected if a graphics set was
10796 designed "before" or "after" the bug was fixed. As a workaround,
10797 a new graphics set option "game.graphics_engine_version" was added
10798 to be able to specify the game's major release version for which the
10799 graphics set was designed, which can then be used to decide if the
10800 bugfix should be used (version 4 and above) or not (version 3 or
10801 below, or if no version was specified at all, as with old sets).
10803 (The wrong/fixed animation frames can be tested with the test level set
10804 "test_gfxframe" and level "000", which contains a specially prepared
10805 custom element at level position (x/y) == (11/9) which uses the zonk
10806 animation mentioned above. Using "game.graphics_engine_version: 4"
10807 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10808 This can also be seen from the debug output for this test element.)
10811 // when a custom element is about to change (for example by change delay),
10812 // do not reset graphic animation when the custom element is moving
10813 if (game.graphics_engine_version < 4 &&
10816 ResetGfxAnimation(x, y);
10817 ResetRandomAnimationValue(x, y);
10820 if (change->pre_change_function)
10821 change->pre_change_function(x, y);
10825 ChangeDelay[x][y]--;
10827 if (ChangeDelay[x][y] != 0) // continue element change
10829 if (change->can_change)
10831 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10833 if (IS_ANIMATED(graphic))
10834 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10836 if (change->change_function)
10837 change->change_function(x, y);
10840 else // finish element change
10842 if (ChangePage[x][y] != -1) // remember page from delayed change
10844 page = ChangePage[x][y];
10845 ChangePage[x][y] = -1;
10847 change = &ei->change_page[page];
10850 if (IS_MOVING(x, y)) // never change a running system ;-)
10852 ChangeDelay[x][y] = 1; // try change after next move step
10853 ChangePage[x][y] = page; // remember page to use for change
10858 // special case: set new level random seed before changing element
10859 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10860 handle_action_before_change = TRUE;
10862 if (change->has_action && handle_action_before_change)
10863 ExecuteCustomElementAction(x, y, element, page);
10865 if (change->can_change)
10867 if (ChangeElement(x, y, element, page))
10869 if (change->post_change_function)
10870 change->post_change_function(x, y);
10874 if (change->has_action && !handle_action_before_change)
10875 ExecuteCustomElementAction(x, y, element, page);
10879 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10880 int trigger_element,
10882 int trigger_player,
10886 boolean change_done_any = FALSE;
10887 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10890 if (!(trigger_events[trigger_element][trigger_event]))
10893 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10895 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10897 int element = EL_CUSTOM_START + i;
10898 boolean change_done = FALSE;
10901 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10902 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10905 for (p = 0; p < element_info[element].num_change_pages; p++)
10907 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10909 if (change->can_change_or_has_action &&
10910 change->has_event[trigger_event] &&
10911 change->trigger_side & trigger_side &&
10912 change->trigger_player & trigger_player &&
10913 change->trigger_page & trigger_page_bits &&
10914 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10916 change->actual_trigger_element = trigger_element;
10917 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10918 change->actual_trigger_player_bits = trigger_player;
10919 change->actual_trigger_side = trigger_side;
10920 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10921 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10923 if ((change->can_change && !change_done) || change->has_action)
10927 SCAN_PLAYFIELD(x, y)
10929 if (Tile[x][y] == element)
10931 if (change->can_change && !change_done)
10933 // if element already changed in this frame, not only prevent
10934 // another element change (checked in ChangeElement()), but
10935 // also prevent additional element actions for this element
10937 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10938 !level.use_action_after_change_bug)
10941 ChangeDelay[x][y] = 1;
10942 ChangeEvent[x][y] = trigger_event;
10944 HandleElementChange(x, y, p);
10946 else if (change->has_action)
10948 // if element already changed in this frame, not only prevent
10949 // another element change (checked in ChangeElement()), but
10950 // also prevent additional element actions for this element
10952 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10953 !level.use_action_after_change_bug)
10956 ExecuteCustomElementAction(x, y, element, p);
10957 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10962 if (change->can_change)
10964 change_done = TRUE;
10965 change_done_any = TRUE;
10972 RECURSION_LOOP_DETECTION_END();
10974 return change_done_any;
10977 static boolean CheckElementChangeExt(int x, int y,
10979 int trigger_element,
10981 int trigger_player,
10984 boolean change_done = FALSE;
10987 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10988 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10991 if (Tile[x][y] == EL_BLOCKED)
10993 Blocked2Moving(x, y, &x, &y);
10994 element = Tile[x][y];
10997 // check if element has already changed or is about to change after moving
10998 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10999 Tile[x][y] != element) ||
11001 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11002 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11003 ChangePage[x][y] != -1)))
11006 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11008 for (p = 0; p < element_info[element].num_change_pages; p++)
11010 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11012 /* check trigger element for all events where the element that is checked
11013 for changing interacts with a directly adjacent element -- this is
11014 different to element changes that affect other elements to change on the
11015 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11016 boolean check_trigger_element =
11017 (trigger_event == CE_TOUCHING_X ||
11018 trigger_event == CE_HITTING_X ||
11019 trigger_event == CE_HIT_BY_X ||
11020 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11022 if (change->can_change_or_has_action &&
11023 change->has_event[trigger_event] &&
11024 change->trigger_side & trigger_side &&
11025 change->trigger_player & trigger_player &&
11026 (!check_trigger_element ||
11027 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11029 change->actual_trigger_element = trigger_element;
11030 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11031 change->actual_trigger_player_bits = trigger_player;
11032 change->actual_trigger_side = trigger_side;
11033 change->actual_trigger_ce_value = CustomValue[x][y];
11034 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11036 // special case: trigger element not at (x,y) position for some events
11037 if (check_trigger_element)
11049 { 0, 0 }, { 0, 0 }, { 0, 0 },
11053 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11054 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11056 change->actual_trigger_ce_value = CustomValue[xx][yy];
11057 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11060 if (change->can_change && !change_done)
11062 ChangeDelay[x][y] = 1;
11063 ChangeEvent[x][y] = trigger_event;
11065 HandleElementChange(x, y, p);
11067 change_done = TRUE;
11069 else if (change->has_action)
11071 ExecuteCustomElementAction(x, y, element, p);
11072 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11077 RECURSION_LOOP_DETECTION_END();
11079 return change_done;
11082 static void PlayPlayerSound(struct PlayerInfo *player)
11084 int jx = player->jx, jy = player->jy;
11085 int sound_element = player->artwork_element;
11086 int last_action = player->last_action_waiting;
11087 int action = player->action_waiting;
11089 if (player->is_waiting)
11091 if (action != last_action)
11092 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11094 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11098 if (action != last_action)
11099 StopSound(element_info[sound_element].sound[last_action]);
11101 if (last_action == ACTION_SLEEPING)
11102 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11106 static void PlayAllPlayersSound(void)
11110 for (i = 0; i < MAX_PLAYERS; i++)
11111 if (stored_player[i].active)
11112 PlayPlayerSound(&stored_player[i]);
11115 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11117 boolean last_waiting = player->is_waiting;
11118 int move_dir = player->MovDir;
11120 player->dir_waiting = move_dir;
11121 player->last_action_waiting = player->action_waiting;
11125 if (!last_waiting) // not waiting -> waiting
11127 player->is_waiting = TRUE;
11129 player->frame_counter_bored =
11131 game.player_boring_delay_fixed +
11132 GetSimpleRandom(game.player_boring_delay_random);
11133 player->frame_counter_sleeping =
11135 game.player_sleeping_delay_fixed +
11136 GetSimpleRandom(game.player_sleeping_delay_random);
11138 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11141 if (game.player_sleeping_delay_fixed +
11142 game.player_sleeping_delay_random > 0 &&
11143 player->anim_delay_counter == 0 &&
11144 player->post_delay_counter == 0 &&
11145 FrameCounter >= player->frame_counter_sleeping)
11146 player->is_sleeping = TRUE;
11147 else if (game.player_boring_delay_fixed +
11148 game.player_boring_delay_random > 0 &&
11149 FrameCounter >= player->frame_counter_bored)
11150 player->is_bored = TRUE;
11152 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11153 player->is_bored ? ACTION_BORING :
11156 if (player->is_sleeping && player->use_murphy)
11158 // special case for sleeping Murphy when leaning against non-free tile
11160 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11161 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11162 !IS_MOVING(player->jx - 1, player->jy)))
11163 move_dir = MV_LEFT;
11164 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11165 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11166 !IS_MOVING(player->jx + 1, player->jy)))
11167 move_dir = MV_RIGHT;
11169 player->is_sleeping = FALSE;
11171 player->dir_waiting = move_dir;
11174 if (player->is_sleeping)
11176 if (player->num_special_action_sleeping > 0)
11178 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11180 int last_special_action = player->special_action_sleeping;
11181 int num_special_action = player->num_special_action_sleeping;
11182 int special_action =
11183 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11184 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11185 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11186 last_special_action + 1 : ACTION_SLEEPING);
11187 int special_graphic =
11188 el_act_dir2img(player->artwork_element, special_action, move_dir);
11190 player->anim_delay_counter =
11191 graphic_info[special_graphic].anim_delay_fixed +
11192 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11193 player->post_delay_counter =
11194 graphic_info[special_graphic].post_delay_fixed +
11195 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11197 player->special_action_sleeping = special_action;
11200 if (player->anim_delay_counter > 0)
11202 player->action_waiting = player->special_action_sleeping;
11203 player->anim_delay_counter--;
11205 else if (player->post_delay_counter > 0)
11207 player->post_delay_counter--;
11211 else if (player->is_bored)
11213 if (player->num_special_action_bored > 0)
11215 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11217 int special_action =
11218 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11219 int special_graphic =
11220 el_act_dir2img(player->artwork_element, special_action, move_dir);
11222 player->anim_delay_counter =
11223 graphic_info[special_graphic].anim_delay_fixed +
11224 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11225 player->post_delay_counter =
11226 graphic_info[special_graphic].post_delay_fixed +
11227 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11229 player->special_action_bored = special_action;
11232 if (player->anim_delay_counter > 0)
11234 player->action_waiting = player->special_action_bored;
11235 player->anim_delay_counter--;
11237 else if (player->post_delay_counter > 0)
11239 player->post_delay_counter--;
11244 else if (last_waiting) // waiting -> not waiting
11246 player->is_waiting = FALSE;
11247 player->is_bored = FALSE;
11248 player->is_sleeping = FALSE;
11250 player->frame_counter_bored = -1;
11251 player->frame_counter_sleeping = -1;
11253 player->anim_delay_counter = 0;
11254 player->post_delay_counter = 0;
11256 player->dir_waiting = player->MovDir;
11257 player->action_waiting = ACTION_DEFAULT;
11259 player->special_action_bored = ACTION_DEFAULT;
11260 player->special_action_sleeping = ACTION_DEFAULT;
11264 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11266 if ((!player->is_moving && player->was_moving) ||
11267 (player->MovPos == 0 && player->was_moving) ||
11268 (player->is_snapping && !player->was_snapping) ||
11269 (player->is_dropping && !player->was_dropping))
11271 if (!CheckSaveEngineSnapshotToList())
11274 player->was_moving = FALSE;
11275 player->was_snapping = TRUE;
11276 player->was_dropping = TRUE;
11280 if (player->is_moving)
11281 player->was_moving = TRUE;
11283 if (!player->is_snapping)
11284 player->was_snapping = FALSE;
11286 if (!player->is_dropping)
11287 player->was_dropping = FALSE;
11290 static struct MouseActionInfo mouse_action_last = { 0 };
11291 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11292 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11295 CheckSaveEngineSnapshotToList();
11297 mouse_action_last = mouse_action;
11300 static void CheckSingleStepMode(struct PlayerInfo *player)
11302 if (tape.single_step && tape.recording && !tape.pausing)
11304 // as it is called "single step mode", just return to pause mode when the
11305 // player stopped moving after one tile (or never starts moving at all)
11306 // (reverse logic needed here in case single step mode used in team mode)
11307 if (player->is_moving ||
11308 player->is_pushing ||
11309 player->is_dropping_pressed ||
11310 player->effective_mouse_action.button)
11311 game.enter_single_step_mode = FALSE;
11314 CheckSaveEngineSnapshot(player);
11317 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11319 int left = player_action & JOY_LEFT;
11320 int right = player_action & JOY_RIGHT;
11321 int up = player_action & JOY_UP;
11322 int down = player_action & JOY_DOWN;
11323 int button1 = player_action & JOY_BUTTON_1;
11324 int button2 = player_action & JOY_BUTTON_2;
11325 int dx = (left ? -1 : right ? 1 : 0);
11326 int dy = (up ? -1 : down ? 1 : 0);
11328 if (!player->active || tape.pausing)
11334 SnapField(player, dx, dy);
11338 DropElement(player);
11340 MovePlayer(player, dx, dy);
11343 CheckSingleStepMode(player);
11345 SetPlayerWaiting(player, FALSE);
11347 return player_action;
11351 // no actions for this player (no input at player's configured device)
11353 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11354 SnapField(player, 0, 0);
11355 CheckGravityMovementWhenNotMoving(player);
11357 if (player->MovPos == 0)
11358 SetPlayerWaiting(player, TRUE);
11360 if (player->MovPos == 0) // needed for tape.playing
11361 player->is_moving = FALSE;
11363 player->is_dropping = FALSE;
11364 player->is_dropping_pressed = FALSE;
11365 player->drop_pressed_delay = 0;
11367 CheckSingleStepMode(player);
11373 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11376 if (!tape.use_mouse_actions)
11379 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11380 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11381 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11384 static void SetTapeActionFromMouseAction(byte *tape_action,
11385 struct MouseActionInfo *mouse_action)
11387 if (!tape.use_mouse_actions)
11390 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11391 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11392 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11395 static void CheckLevelSolved(void)
11397 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11399 if (game_em.level_solved &&
11400 !game_em.game_over) // game won
11404 game_em.game_over = TRUE;
11406 game.all_players_gone = TRUE;
11409 if (game_em.game_over) // game lost
11410 game.all_players_gone = TRUE;
11412 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11414 if (game_sp.level_solved &&
11415 !game_sp.game_over) // game won
11419 game_sp.game_over = TRUE;
11421 game.all_players_gone = TRUE;
11424 if (game_sp.game_over) // game lost
11425 game.all_players_gone = TRUE;
11427 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11429 if (game_mm.level_solved &&
11430 !game_mm.game_over) // game won
11434 game_mm.game_over = TRUE;
11436 game.all_players_gone = TRUE;
11439 if (game_mm.game_over) // game lost
11440 game.all_players_gone = TRUE;
11444 static void CheckLevelTime(void)
11448 if (TimeFrames >= FRAMES_PER_SECOND)
11453 for (i = 0; i < MAX_PLAYERS; i++)
11455 struct PlayerInfo *player = &stored_player[i];
11457 if (SHIELD_ON(player))
11459 player->shield_normal_time_left--;
11461 if (player->shield_deadly_time_left > 0)
11462 player->shield_deadly_time_left--;
11466 if (!game.LevelSolved && !level.use_step_counter)
11474 if (TimeLeft <= 10 && setup.time_limit)
11475 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11477 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11478 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11480 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11482 if (!TimeLeft && setup.time_limit)
11484 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11485 game_em.lev->killed_out_of_time = TRUE;
11487 for (i = 0; i < MAX_PLAYERS; i++)
11488 KillPlayer(&stored_player[i]);
11491 else if (game.no_time_limit && !game.all_players_gone)
11493 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11496 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11499 if (tape.recording || tape.playing)
11500 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11503 if (tape.recording || tape.playing)
11504 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11506 UpdateAndDisplayGameControlValues();
11509 void AdvanceFrameAndPlayerCounters(int player_nr)
11513 // advance frame counters (global frame counter and time frame counter)
11517 // advance player counters (counters for move delay, move animation etc.)
11518 for (i = 0; i < MAX_PLAYERS; i++)
11520 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11521 int move_delay_value = stored_player[i].move_delay_value;
11522 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11524 if (!advance_player_counters) // not all players may be affected
11527 if (move_frames == 0) // less than one move per game frame
11529 int stepsize = TILEX / move_delay_value;
11530 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11531 int count = (stored_player[i].is_moving ?
11532 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11534 if (count % delay == 0)
11538 stored_player[i].Frame += move_frames;
11540 if (stored_player[i].MovPos != 0)
11541 stored_player[i].StepFrame += move_frames;
11543 if (stored_player[i].move_delay > 0)
11544 stored_player[i].move_delay--;
11546 // due to bugs in previous versions, counter must count up, not down
11547 if (stored_player[i].push_delay != -1)
11548 stored_player[i].push_delay++;
11550 if (stored_player[i].drop_delay > 0)
11551 stored_player[i].drop_delay--;
11553 if (stored_player[i].is_dropping_pressed)
11554 stored_player[i].drop_pressed_delay++;
11558 void StartGameActions(boolean init_network_game, boolean record_tape,
11561 unsigned int new_random_seed = InitRND(random_seed);
11564 TapeStartRecording(new_random_seed);
11566 if (init_network_game)
11568 SendToServer_LevelFile();
11569 SendToServer_StartPlaying();
11577 static void GameActionsExt(void)
11580 static unsigned int game_frame_delay = 0;
11582 unsigned int game_frame_delay_value;
11583 byte *recorded_player_action;
11584 byte summarized_player_action = 0;
11585 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11588 // detect endless loops, caused by custom element programming
11589 if (recursion_loop_detected && recursion_loop_depth == 0)
11591 char *message = getStringCat3("Internal Error! Element ",
11592 EL_NAME(recursion_loop_element),
11593 " caused endless loop! Quit the game?");
11595 Warn("element '%s' caused endless loop in game engine",
11596 EL_NAME(recursion_loop_element));
11598 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11600 recursion_loop_detected = FALSE; // if game should be continued
11607 if (game.restart_level)
11608 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11610 CheckLevelSolved();
11612 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11615 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11618 if (game_status != GAME_MODE_PLAYING) // status might have changed
11621 game_frame_delay_value =
11622 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11624 if (tape.playing && tape.warp_forward && !tape.pausing)
11625 game_frame_delay_value = 0;
11627 SetVideoFrameDelay(game_frame_delay_value);
11629 // (de)activate virtual buttons depending on current game status
11630 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11632 if (game.all_players_gone) // if no players there to be controlled anymore
11633 SetOverlayActive(FALSE);
11634 else if (!tape.playing) // if game continues after tape stopped playing
11635 SetOverlayActive(TRUE);
11640 // ---------- main game synchronization point ----------
11642 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11644 Debug("game:playing:skip", "skip == %d", skip);
11647 // ---------- main game synchronization point ----------
11649 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11653 if (network_playing && !network_player_action_received)
11655 // try to get network player actions in time
11657 // last chance to get network player actions without main loop delay
11658 HandleNetworking();
11660 // game was quit by network peer
11661 if (game_status != GAME_MODE_PLAYING)
11664 // check if network player actions still missing and game still running
11665 if (!network_player_action_received && !checkGameEnded())
11666 return; // failed to get network player actions in time
11668 // do not yet reset "network_player_action_received" (for tape.pausing)
11674 // at this point we know that we really continue executing the game
11676 network_player_action_received = FALSE;
11678 // when playing tape, read previously recorded player input from tape data
11679 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11681 local_player->effective_mouse_action = local_player->mouse_action;
11683 if (recorded_player_action != NULL)
11684 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11685 recorded_player_action);
11687 // TapePlayAction() may return NULL when toggling to "pause before death"
11691 if (tape.set_centered_player)
11693 game.centered_player_nr_next = tape.centered_player_nr_next;
11694 game.set_centered_player = TRUE;
11697 for (i = 0; i < MAX_PLAYERS; i++)
11699 summarized_player_action |= stored_player[i].action;
11701 if (!network_playing && (game.team_mode || tape.playing))
11702 stored_player[i].effective_action = stored_player[i].action;
11705 if (network_playing && !checkGameEnded())
11706 SendToServer_MovePlayer(summarized_player_action);
11708 // summarize all actions at local players mapped input device position
11709 // (this allows using different input devices in single player mode)
11710 if (!network.enabled && !game.team_mode)
11711 stored_player[map_player_action[local_player->index_nr]].effective_action =
11712 summarized_player_action;
11714 // summarize all actions at centered player in local team mode
11715 if (tape.recording &&
11716 setup.team_mode && !network.enabled &&
11717 setup.input_on_focus &&
11718 game.centered_player_nr != -1)
11720 for (i = 0; i < MAX_PLAYERS; i++)
11721 stored_player[map_player_action[i]].effective_action =
11722 (i == game.centered_player_nr ? summarized_player_action : 0);
11725 if (recorded_player_action != NULL)
11726 for (i = 0; i < MAX_PLAYERS; i++)
11727 stored_player[i].effective_action = recorded_player_action[i];
11729 for (i = 0; i < MAX_PLAYERS; i++)
11731 tape_action[i] = stored_player[i].effective_action;
11733 /* (this may happen in the RND game engine if a player was not present on
11734 the playfield on level start, but appeared later from a custom element */
11735 if (setup.team_mode &&
11738 !tape.player_participates[i])
11739 tape.player_participates[i] = TRUE;
11742 SetTapeActionFromMouseAction(tape_action,
11743 &local_player->effective_mouse_action);
11745 // only record actions from input devices, but not programmed actions
11746 if (tape.recording)
11747 TapeRecordAction(tape_action);
11749 // remember if game was played (especially after tape stopped playing)
11750 if (!tape.playing && summarized_player_action)
11751 game.GamePlayed = TRUE;
11753 #if USE_NEW_PLAYER_ASSIGNMENTS
11754 // !!! also map player actions in single player mode !!!
11755 // if (game.team_mode)
11758 byte mapped_action[MAX_PLAYERS];
11760 #if DEBUG_PLAYER_ACTIONS
11761 for (i = 0; i < MAX_PLAYERS; i++)
11762 DebugContinued("", "%d, ", stored_player[i].effective_action);
11765 for (i = 0; i < MAX_PLAYERS; i++)
11766 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11768 for (i = 0; i < MAX_PLAYERS; i++)
11769 stored_player[i].effective_action = mapped_action[i];
11771 #if DEBUG_PLAYER_ACTIONS
11772 DebugContinued("", "=> ");
11773 for (i = 0; i < MAX_PLAYERS; i++)
11774 DebugContinued("", "%d, ", stored_player[i].effective_action);
11775 DebugContinued("game:playing:player", "\n");
11778 #if DEBUG_PLAYER_ACTIONS
11781 for (i = 0; i < MAX_PLAYERS; i++)
11782 DebugContinued("", "%d, ", stored_player[i].effective_action);
11783 DebugContinued("game:playing:player", "\n");
11788 for (i = 0; i < MAX_PLAYERS; i++)
11790 // allow engine snapshot in case of changed movement attempt
11791 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11792 (stored_player[i].effective_action & KEY_MOTION))
11793 game.snapshot.changed_action = TRUE;
11795 // allow engine snapshot in case of snapping/dropping attempt
11796 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11797 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11798 game.snapshot.changed_action = TRUE;
11800 game.snapshot.last_action[i] = stored_player[i].effective_action;
11803 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11805 GameActions_EM_Main();
11807 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11809 GameActions_SP_Main();
11811 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11813 GameActions_MM_Main();
11817 GameActions_RND_Main();
11820 BlitScreenToBitmap(backbuffer);
11822 CheckLevelSolved();
11825 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11827 if (global.show_frames_per_second)
11829 static unsigned int fps_counter = 0;
11830 static int fps_frames = 0;
11831 unsigned int fps_delay_ms = Counter() - fps_counter;
11835 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11837 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11840 fps_counter = Counter();
11842 // always draw FPS to screen after FPS value was updated
11843 redraw_mask |= REDRAW_FPS;
11846 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11847 if (GetDrawDeactivationMask() == REDRAW_NONE)
11848 redraw_mask |= REDRAW_FPS;
11852 static void GameActions_CheckSaveEngineSnapshot(void)
11854 if (!game.snapshot.save_snapshot)
11857 // clear flag for saving snapshot _before_ saving snapshot
11858 game.snapshot.save_snapshot = FALSE;
11860 SaveEngineSnapshotToList();
11863 void GameActions(void)
11867 GameActions_CheckSaveEngineSnapshot();
11870 void GameActions_EM_Main(void)
11872 byte effective_action[MAX_PLAYERS];
11873 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11876 for (i = 0; i < MAX_PLAYERS; i++)
11877 effective_action[i] = stored_player[i].effective_action;
11879 GameActions_EM(effective_action, warp_mode);
11882 void GameActions_SP_Main(void)
11884 byte effective_action[MAX_PLAYERS];
11885 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11888 for (i = 0; i < MAX_PLAYERS; i++)
11889 effective_action[i] = stored_player[i].effective_action;
11891 GameActions_SP(effective_action, warp_mode);
11893 for (i = 0; i < MAX_PLAYERS; i++)
11895 if (stored_player[i].force_dropping)
11896 stored_player[i].action |= KEY_BUTTON_DROP;
11898 stored_player[i].force_dropping = FALSE;
11902 void GameActions_MM_Main(void)
11904 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11906 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11909 void GameActions_RND_Main(void)
11914 void GameActions_RND(void)
11916 static struct MouseActionInfo mouse_action_last = { 0 };
11917 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11918 int magic_wall_x = 0, magic_wall_y = 0;
11919 int i, x, y, element, graphic, last_gfx_frame;
11921 InitPlayfieldScanModeVars();
11923 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11925 SCAN_PLAYFIELD(x, y)
11927 ChangeCount[x][y] = 0;
11928 ChangeEvent[x][y] = -1;
11932 if (game.set_centered_player)
11934 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11936 // switching to "all players" only possible if all players fit to screen
11937 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11939 game.centered_player_nr_next = game.centered_player_nr;
11940 game.set_centered_player = FALSE;
11943 // do not switch focus to non-existing (or non-active) player
11944 if (game.centered_player_nr_next >= 0 &&
11945 !stored_player[game.centered_player_nr_next].active)
11947 game.centered_player_nr_next = game.centered_player_nr;
11948 game.set_centered_player = FALSE;
11952 if (game.set_centered_player &&
11953 ScreenMovPos == 0) // screen currently aligned at tile position
11957 if (game.centered_player_nr_next == -1)
11959 setScreenCenteredToAllPlayers(&sx, &sy);
11963 sx = stored_player[game.centered_player_nr_next].jx;
11964 sy = stored_player[game.centered_player_nr_next].jy;
11967 game.centered_player_nr = game.centered_player_nr_next;
11968 game.set_centered_player = FALSE;
11970 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11971 DrawGameDoorValues();
11974 // check single step mode (set flag and clear again if any player is active)
11975 game.enter_single_step_mode =
11976 (tape.single_step && tape.recording && !tape.pausing);
11978 for (i = 0; i < MAX_PLAYERS; i++)
11980 int actual_player_action = stored_player[i].effective_action;
11983 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11984 - rnd_equinox_tetrachloride 048
11985 - rnd_equinox_tetrachloride_ii 096
11986 - rnd_emanuel_schmieg 002
11987 - doctor_sloan_ww 001, 020
11989 if (stored_player[i].MovPos == 0)
11990 CheckGravityMovement(&stored_player[i]);
11993 // overwrite programmed action with tape action
11994 if (stored_player[i].programmed_action)
11995 actual_player_action = stored_player[i].programmed_action;
11997 PlayerActions(&stored_player[i], actual_player_action);
11999 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12002 // single step pause mode may already have been toggled by "ScrollPlayer()"
12003 if (game.enter_single_step_mode && !tape.pausing)
12004 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12006 ScrollScreen(NULL, SCROLL_GO_ON);
12008 /* for backwards compatibility, the following code emulates a fixed bug that
12009 occured when pushing elements (causing elements that just made their last
12010 pushing step to already (if possible) make their first falling step in the
12011 same game frame, which is bad); this code is also needed to use the famous
12012 "spring push bug" which is used in older levels and might be wanted to be
12013 used also in newer levels, but in this case the buggy pushing code is only
12014 affecting the "spring" element and no other elements */
12016 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12018 for (i = 0; i < MAX_PLAYERS; i++)
12020 struct PlayerInfo *player = &stored_player[i];
12021 int x = player->jx;
12022 int y = player->jy;
12024 if (player->active && player->is_pushing && player->is_moving &&
12026 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12027 Tile[x][y] == EL_SPRING))
12029 ContinueMoving(x, y);
12031 // continue moving after pushing (this is actually a bug)
12032 if (!IS_MOVING(x, y))
12033 Stop[x][y] = FALSE;
12038 SCAN_PLAYFIELD(x, y)
12040 Last[x][y] = Tile[x][y];
12042 ChangeCount[x][y] = 0;
12043 ChangeEvent[x][y] = -1;
12045 // this must be handled before main playfield loop
12046 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12049 if (MovDelay[x][y] <= 0)
12053 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12056 if (MovDelay[x][y] <= 0)
12058 int element = Store[x][y];
12059 int move_direction = MovDir[x][y];
12060 int player_index_bit = Store2[x][y];
12066 TEST_DrawLevelField(x, y);
12068 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12073 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12075 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12077 Debug("game:playing:GameActions_RND", "This should never happen!");
12079 ChangePage[x][y] = -1;
12083 Stop[x][y] = FALSE;
12084 if (WasJustMoving[x][y] > 0)
12085 WasJustMoving[x][y]--;
12086 if (WasJustFalling[x][y] > 0)
12087 WasJustFalling[x][y]--;
12088 if (CheckCollision[x][y] > 0)
12089 CheckCollision[x][y]--;
12090 if (CheckImpact[x][y] > 0)
12091 CheckImpact[x][y]--;
12095 /* reset finished pushing action (not done in ContinueMoving() to allow
12096 continuous pushing animation for elements with zero push delay) */
12097 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12099 ResetGfxAnimation(x, y);
12100 TEST_DrawLevelField(x, y);
12104 if (IS_BLOCKED(x, y))
12108 Blocked2Moving(x, y, &oldx, &oldy);
12109 if (!IS_MOVING(oldx, oldy))
12111 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12112 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12113 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12114 Debug("game:playing:GameActions_RND", "This should never happen!");
12120 if (mouse_action.button)
12122 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12124 x = mouse_action.lx;
12125 y = mouse_action.ly;
12126 element = Tile[x][y];
12130 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12131 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12134 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12135 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12138 SCAN_PLAYFIELD(x, y)
12140 element = Tile[x][y];
12141 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12142 last_gfx_frame = GfxFrame[x][y];
12144 ResetGfxFrame(x, y);
12146 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12147 DrawLevelGraphicAnimation(x, y, graphic);
12149 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12150 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12151 ResetRandomAnimationValue(x, y);
12153 SetRandomAnimationValue(x, y);
12155 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12157 if (IS_INACTIVE(element))
12159 if (IS_ANIMATED(graphic))
12160 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12165 // this may take place after moving, so 'element' may have changed
12166 if (IS_CHANGING(x, y) &&
12167 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12169 int page = element_info[element].event_page_nr[CE_DELAY];
12171 HandleElementChange(x, y, page);
12173 element = Tile[x][y];
12174 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12177 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12181 element = Tile[x][y];
12182 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12184 if (IS_ANIMATED(graphic) &&
12185 !IS_MOVING(x, y) &&
12187 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12189 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12190 TEST_DrawTwinkleOnField(x, y);
12192 else if (element == EL_ACID)
12195 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12197 else if ((element == EL_EXIT_OPEN ||
12198 element == EL_EM_EXIT_OPEN ||
12199 element == EL_SP_EXIT_OPEN ||
12200 element == EL_STEEL_EXIT_OPEN ||
12201 element == EL_EM_STEEL_EXIT_OPEN ||
12202 element == EL_SP_TERMINAL ||
12203 element == EL_SP_TERMINAL_ACTIVE ||
12204 element == EL_EXTRA_TIME ||
12205 element == EL_SHIELD_NORMAL ||
12206 element == EL_SHIELD_DEADLY) &&
12207 IS_ANIMATED(graphic))
12208 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12209 else if (IS_MOVING(x, y))
12210 ContinueMoving(x, y);
12211 else if (IS_ACTIVE_BOMB(element))
12212 CheckDynamite(x, y);
12213 else if (element == EL_AMOEBA_GROWING)
12214 AmoebaGrowing(x, y);
12215 else if (element == EL_AMOEBA_SHRINKING)
12216 AmoebaShrinking(x, y);
12218 #if !USE_NEW_AMOEBA_CODE
12219 else if (IS_AMOEBALIVE(element))
12220 AmoebaReproduce(x, y);
12223 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12225 else if (element == EL_EXIT_CLOSED)
12227 else if (element == EL_EM_EXIT_CLOSED)
12229 else if (element == EL_STEEL_EXIT_CLOSED)
12230 CheckExitSteel(x, y);
12231 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12232 CheckExitSteelEM(x, y);
12233 else if (element == EL_SP_EXIT_CLOSED)
12235 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12236 element == EL_EXPANDABLE_STEELWALL_GROWING)
12237 MauerWaechst(x, y);
12238 else if (element == EL_EXPANDABLE_WALL ||
12239 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12240 element == EL_EXPANDABLE_WALL_VERTICAL ||
12241 element == EL_EXPANDABLE_WALL_ANY ||
12242 element == EL_BD_EXPANDABLE_WALL)
12243 MauerAbleger(x, y);
12244 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12245 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12246 element == EL_EXPANDABLE_STEELWALL_ANY)
12247 MauerAblegerStahl(x, y);
12248 else if (element == EL_FLAMES)
12249 CheckForDragon(x, y);
12250 else if (element == EL_EXPLOSION)
12251 ; // drawing of correct explosion animation is handled separately
12252 else if (element == EL_ELEMENT_SNAPPING ||
12253 element == EL_DIAGONAL_SHRINKING ||
12254 element == EL_DIAGONAL_GROWING)
12256 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12258 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12260 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12261 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12263 if (IS_BELT_ACTIVE(element))
12264 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12266 if (game.magic_wall_active)
12268 int jx = local_player->jx, jy = local_player->jy;
12270 // play the element sound at the position nearest to the player
12271 if ((element == EL_MAGIC_WALL_FULL ||
12272 element == EL_MAGIC_WALL_ACTIVE ||
12273 element == EL_MAGIC_WALL_EMPTYING ||
12274 element == EL_BD_MAGIC_WALL_FULL ||
12275 element == EL_BD_MAGIC_WALL_ACTIVE ||
12276 element == EL_BD_MAGIC_WALL_EMPTYING ||
12277 element == EL_DC_MAGIC_WALL_FULL ||
12278 element == EL_DC_MAGIC_WALL_ACTIVE ||
12279 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12280 ABS(x - jx) + ABS(y - jy) <
12281 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12289 #if USE_NEW_AMOEBA_CODE
12290 // new experimental amoeba growth stuff
12291 if (!(FrameCounter % 8))
12293 static unsigned int random = 1684108901;
12295 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12297 x = RND(lev_fieldx);
12298 y = RND(lev_fieldy);
12299 element = Tile[x][y];
12301 if (!IS_PLAYER(x,y) &&
12302 (element == EL_EMPTY ||
12303 CAN_GROW_INTO(element) ||
12304 element == EL_QUICKSAND_EMPTY ||
12305 element == EL_QUICKSAND_FAST_EMPTY ||
12306 element == EL_ACID_SPLASH_LEFT ||
12307 element == EL_ACID_SPLASH_RIGHT))
12309 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12310 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12311 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12312 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12313 Tile[x][y] = EL_AMOEBA_DROP;
12316 random = random * 129 + 1;
12321 game.explosions_delayed = FALSE;
12323 SCAN_PLAYFIELD(x, y)
12325 element = Tile[x][y];
12327 if (ExplodeField[x][y])
12328 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12329 else if (element == EL_EXPLOSION)
12330 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12332 ExplodeField[x][y] = EX_TYPE_NONE;
12335 game.explosions_delayed = TRUE;
12337 if (game.magic_wall_active)
12339 if (!(game.magic_wall_time_left % 4))
12341 int element = Tile[magic_wall_x][magic_wall_y];
12343 if (element == EL_BD_MAGIC_WALL_FULL ||
12344 element == EL_BD_MAGIC_WALL_ACTIVE ||
12345 element == EL_BD_MAGIC_WALL_EMPTYING)
12346 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12347 else if (element == EL_DC_MAGIC_WALL_FULL ||
12348 element == EL_DC_MAGIC_WALL_ACTIVE ||
12349 element == EL_DC_MAGIC_WALL_EMPTYING)
12350 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12352 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12355 if (game.magic_wall_time_left > 0)
12357 game.magic_wall_time_left--;
12359 if (!game.magic_wall_time_left)
12361 SCAN_PLAYFIELD(x, y)
12363 element = Tile[x][y];
12365 if (element == EL_MAGIC_WALL_ACTIVE ||
12366 element == EL_MAGIC_WALL_FULL)
12368 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12369 TEST_DrawLevelField(x, y);
12371 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12372 element == EL_BD_MAGIC_WALL_FULL)
12374 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12375 TEST_DrawLevelField(x, y);
12377 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12378 element == EL_DC_MAGIC_WALL_FULL)
12380 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12381 TEST_DrawLevelField(x, y);
12385 game.magic_wall_active = FALSE;
12390 if (game.light_time_left > 0)
12392 game.light_time_left--;
12394 if (game.light_time_left == 0)
12395 RedrawAllLightSwitchesAndInvisibleElements();
12398 if (game.timegate_time_left > 0)
12400 game.timegate_time_left--;
12402 if (game.timegate_time_left == 0)
12403 CloseAllOpenTimegates();
12406 if (game.lenses_time_left > 0)
12408 game.lenses_time_left--;
12410 if (game.lenses_time_left == 0)
12411 RedrawAllInvisibleElementsForLenses();
12414 if (game.magnify_time_left > 0)
12416 game.magnify_time_left--;
12418 if (game.magnify_time_left == 0)
12419 RedrawAllInvisibleElementsForMagnifier();
12422 for (i = 0; i < MAX_PLAYERS; i++)
12424 struct PlayerInfo *player = &stored_player[i];
12426 if (SHIELD_ON(player))
12428 if (player->shield_deadly_time_left)
12429 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12430 else if (player->shield_normal_time_left)
12431 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12435 #if USE_DELAYED_GFX_REDRAW
12436 SCAN_PLAYFIELD(x, y)
12438 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12440 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12441 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12443 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12444 DrawLevelField(x, y);
12446 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12447 DrawLevelFieldCrumbled(x, y);
12449 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12450 DrawLevelFieldCrumbledNeighbours(x, y);
12452 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12453 DrawTwinkleOnField(x, y);
12456 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12461 PlayAllPlayersSound();
12463 for (i = 0; i < MAX_PLAYERS; i++)
12465 struct PlayerInfo *player = &stored_player[i];
12467 if (player->show_envelope != 0 && (!player->active ||
12468 player->MovPos == 0))
12470 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12472 player->show_envelope = 0;
12476 // use random number generator in every frame to make it less predictable
12477 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12480 mouse_action_last = mouse_action;
12483 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12485 int min_x = x, min_y = y, max_x = x, max_y = y;
12486 int scr_fieldx = getScreenFieldSizeX();
12487 int scr_fieldy = getScreenFieldSizeY();
12490 for (i = 0; i < MAX_PLAYERS; i++)
12492 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12494 if (!stored_player[i].active || &stored_player[i] == player)
12497 min_x = MIN(min_x, jx);
12498 min_y = MIN(min_y, jy);
12499 max_x = MAX(max_x, jx);
12500 max_y = MAX(max_y, jy);
12503 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12506 static boolean AllPlayersInVisibleScreen(void)
12510 for (i = 0; i < MAX_PLAYERS; i++)
12512 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12514 if (!stored_player[i].active)
12517 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12524 void ScrollLevel(int dx, int dy)
12526 int scroll_offset = 2 * TILEX_VAR;
12529 BlitBitmap(drawto_field, drawto_field,
12530 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12531 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12532 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12533 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12534 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12535 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12539 x = (dx == 1 ? BX1 : BX2);
12540 for (y = BY1; y <= BY2; y++)
12541 DrawScreenField(x, y);
12546 y = (dy == 1 ? BY1 : BY2);
12547 for (x = BX1; x <= BX2; x++)
12548 DrawScreenField(x, y);
12551 redraw_mask |= REDRAW_FIELD;
12554 static boolean canFallDown(struct PlayerInfo *player)
12556 int jx = player->jx, jy = player->jy;
12558 return (IN_LEV_FIELD(jx, jy + 1) &&
12559 (IS_FREE(jx, jy + 1) ||
12560 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12561 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12562 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12565 static boolean canPassField(int x, int y, int move_dir)
12567 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12568 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12569 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12570 int nextx = x + dx;
12571 int nexty = y + dy;
12572 int element = Tile[x][y];
12574 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12575 !CAN_MOVE(element) &&
12576 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12577 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12578 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12581 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12583 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12584 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12585 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12589 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12590 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12591 (IS_DIGGABLE(Tile[newx][newy]) ||
12592 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12593 canPassField(newx, newy, move_dir)));
12596 static void CheckGravityMovement(struct PlayerInfo *player)
12598 if (player->gravity && !player->programmed_action)
12600 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12601 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12602 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12603 int jx = player->jx, jy = player->jy;
12604 boolean player_is_moving_to_valid_field =
12605 (!player_is_snapping &&
12606 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12607 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12608 boolean player_can_fall_down = canFallDown(player);
12610 if (player_can_fall_down &&
12611 !player_is_moving_to_valid_field)
12612 player->programmed_action = MV_DOWN;
12616 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12618 return CheckGravityMovement(player);
12620 if (player->gravity && !player->programmed_action)
12622 int jx = player->jx, jy = player->jy;
12623 boolean field_under_player_is_free =
12624 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12625 boolean player_is_standing_on_valid_field =
12626 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12627 (IS_WALKABLE(Tile[jx][jy]) &&
12628 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12630 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12631 player->programmed_action = MV_DOWN;
12636 MovePlayerOneStep()
12637 -----------------------------------------------------------------------------
12638 dx, dy: direction (non-diagonal) to try to move the player to
12639 real_dx, real_dy: direction as read from input device (can be diagonal)
12642 boolean MovePlayerOneStep(struct PlayerInfo *player,
12643 int dx, int dy, int real_dx, int real_dy)
12645 int jx = player->jx, jy = player->jy;
12646 int new_jx = jx + dx, new_jy = jy + dy;
12648 boolean player_can_move = !player->cannot_move;
12650 if (!player->active || (!dx && !dy))
12651 return MP_NO_ACTION;
12653 player->MovDir = (dx < 0 ? MV_LEFT :
12654 dx > 0 ? MV_RIGHT :
12656 dy > 0 ? MV_DOWN : MV_NONE);
12658 if (!IN_LEV_FIELD(new_jx, new_jy))
12659 return MP_NO_ACTION;
12661 if (!player_can_move)
12663 if (player->MovPos == 0)
12665 player->is_moving = FALSE;
12666 player->is_digging = FALSE;
12667 player->is_collecting = FALSE;
12668 player->is_snapping = FALSE;
12669 player->is_pushing = FALSE;
12673 if (!network.enabled && game.centered_player_nr == -1 &&
12674 !AllPlayersInSight(player, new_jx, new_jy))
12675 return MP_NO_ACTION;
12677 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12678 if (can_move != MP_MOVING)
12681 // check if DigField() has caused relocation of the player
12682 if (player->jx != jx || player->jy != jy)
12683 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12685 StorePlayer[jx][jy] = 0;
12686 player->last_jx = jx;
12687 player->last_jy = jy;
12688 player->jx = new_jx;
12689 player->jy = new_jy;
12690 StorePlayer[new_jx][new_jy] = player->element_nr;
12692 if (player->move_delay_value_next != -1)
12694 player->move_delay_value = player->move_delay_value_next;
12695 player->move_delay_value_next = -1;
12699 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12701 player->step_counter++;
12703 PlayerVisit[jx][jy] = FrameCounter;
12705 player->is_moving = TRUE;
12708 // should better be called in MovePlayer(), but this breaks some tapes
12709 ScrollPlayer(player, SCROLL_INIT);
12715 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12717 int jx = player->jx, jy = player->jy;
12718 int old_jx = jx, old_jy = jy;
12719 int moved = MP_NO_ACTION;
12721 if (!player->active)
12726 if (player->MovPos == 0)
12728 player->is_moving = FALSE;
12729 player->is_digging = FALSE;
12730 player->is_collecting = FALSE;
12731 player->is_snapping = FALSE;
12732 player->is_pushing = FALSE;
12738 if (player->move_delay > 0)
12741 player->move_delay = -1; // set to "uninitialized" value
12743 // store if player is automatically moved to next field
12744 player->is_auto_moving = (player->programmed_action != MV_NONE);
12746 // remove the last programmed player action
12747 player->programmed_action = 0;
12749 if (player->MovPos)
12751 // should only happen if pre-1.2 tape recordings are played
12752 // this is only for backward compatibility
12754 int original_move_delay_value = player->move_delay_value;
12757 Debug("game:playing:MovePlayer",
12758 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12762 // scroll remaining steps with finest movement resolution
12763 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12765 while (player->MovPos)
12767 ScrollPlayer(player, SCROLL_GO_ON);
12768 ScrollScreen(NULL, SCROLL_GO_ON);
12770 AdvanceFrameAndPlayerCounters(player->index_nr);
12773 BackToFront_WithFrameDelay(0);
12776 player->move_delay_value = original_move_delay_value;
12779 player->is_active = FALSE;
12781 if (player->last_move_dir & MV_HORIZONTAL)
12783 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12784 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12788 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12789 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12792 if (!moved && !player->is_active)
12794 player->is_moving = FALSE;
12795 player->is_digging = FALSE;
12796 player->is_collecting = FALSE;
12797 player->is_snapping = FALSE;
12798 player->is_pushing = FALSE;
12804 if (moved & MP_MOVING && !ScreenMovPos &&
12805 (player->index_nr == game.centered_player_nr ||
12806 game.centered_player_nr == -1))
12808 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12810 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12812 // actual player has left the screen -- scroll in that direction
12813 if (jx != old_jx) // player has moved horizontally
12814 scroll_x += (jx - old_jx);
12815 else // player has moved vertically
12816 scroll_y += (jy - old_jy);
12820 int offset_raw = game.scroll_delay_value;
12822 if (jx != old_jx) // player has moved horizontally
12824 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12825 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12826 int new_scroll_x = jx - MIDPOSX + offset_x;
12828 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12829 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12830 scroll_x = new_scroll_x;
12832 // don't scroll over playfield boundaries
12833 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12835 // don't scroll more than one field at a time
12836 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12838 // don't scroll against the player's moving direction
12839 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12840 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12841 scroll_x = old_scroll_x;
12843 else // player has moved vertically
12845 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12846 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12847 int new_scroll_y = jy - MIDPOSY + offset_y;
12849 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12850 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12851 scroll_y = new_scroll_y;
12853 // don't scroll over playfield boundaries
12854 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12856 // don't scroll more than one field at a time
12857 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12859 // don't scroll against the player's moving direction
12860 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12861 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12862 scroll_y = old_scroll_y;
12866 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12868 if (!network.enabled && game.centered_player_nr == -1 &&
12869 !AllPlayersInVisibleScreen())
12871 scroll_x = old_scroll_x;
12872 scroll_y = old_scroll_y;
12876 ScrollScreen(player, SCROLL_INIT);
12877 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12882 player->StepFrame = 0;
12884 if (moved & MP_MOVING)
12886 if (old_jx != jx && old_jy == jy)
12887 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12888 else if (old_jx == jx && old_jy != jy)
12889 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12891 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12893 player->last_move_dir = player->MovDir;
12894 player->is_moving = TRUE;
12895 player->is_snapping = FALSE;
12896 player->is_switching = FALSE;
12897 player->is_dropping = FALSE;
12898 player->is_dropping_pressed = FALSE;
12899 player->drop_pressed_delay = 0;
12902 // should better be called here than above, but this breaks some tapes
12903 ScrollPlayer(player, SCROLL_INIT);
12908 CheckGravityMovementWhenNotMoving(player);
12910 player->is_moving = FALSE;
12912 /* at this point, the player is allowed to move, but cannot move right now
12913 (e.g. because of something blocking the way) -- ensure that the player
12914 is also allowed to move in the next frame (in old versions before 3.1.1,
12915 the player was forced to wait again for eight frames before next try) */
12917 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12918 player->move_delay = 0; // allow direct movement in the next frame
12921 if (player->move_delay == -1) // not yet initialized by DigField()
12922 player->move_delay = player->move_delay_value;
12924 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12926 TestIfPlayerTouchesBadThing(jx, jy);
12927 TestIfPlayerTouchesCustomElement(jx, jy);
12930 if (!player->active)
12931 RemovePlayer(player);
12936 void ScrollPlayer(struct PlayerInfo *player, int mode)
12938 int jx = player->jx, jy = player->jy;
12939 int last_jx = player->last_jx, last_jy = player->last_jy;
12940 int move_stepsize = TILEX / player->move_delay_value;
12942 if (!player->active)
12945 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12948 if (mode == SCROLL_INIT)
12950 player->actual_frame_counter = FrameCounter;
12951 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12953 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12954 Tile[last_jx][last_jy] == EL_EMPTY)
12956 int last_field_block_delay = 0; // start with no blocking at all
12957 int block_delay_adjustment = player->block_delay_adjustment;
12959 // if player blocks last field, add delay for exactly one move
12960 if (player->block_last_field)
12962 last_field_block_delay += player->move_delay_value;
12964 // when blocking enabled, prevent moving up despite gravity
12965 if (player->gravity && player->MovDir == MV_UP)
12966 block_delay_adjustment = -1;
12969 // add block delay adjustment (also possible when not blocking)
12970 last_field_block_delay += block_delay_adjustment;
12972 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12973 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12976 if (player->MovPos != 0) // player has not yet reached destination
12979 else if (!FrameReached(&player->actual_frame_counter, 1))
12982 if (player->MovPos != 0)
12984 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12985 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12987 // before DrawPlayer() to draw correct player graphic for this case
12988 if (player->MovPos == 0)
12989 CheckGravityMovement(player);
12992 if (player->MovPos == 0) // player reached destination field
12994 if (player->move_delay_reset_counter > 0)
12996 player->move_delay_reset_counter--;
12998 if (player->move_delay_reset_counter == 0)
13000 // continue with normal speed after quickly moving through gate
13001 HALVE_PLAYER_SPEED(player);
13003 // be able to make the next move without delay
13004 player->move_delay = 0;
13008 player->last_jx = jx;
13009 player->last_jy = jy;
13011 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13012 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13013 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13014 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13015 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13016 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13017 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13018 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13020 ExitPlayer(player);
13022 if (game.players_still_needed == 0 &&
13023 (game.friends_still_needed == 0 ||
13024 IS_SP_ELEMENT(Tile[jx][jy])))
13028 // this breaks one level: "machine", level 000
13030 int move_direction = player->MovDir;
13031 int enter_side = MV_DIR_OPPOSITE(move_direction);
13032 int leave_side = move_direction;
13033 int old_jx = last_jx;
13034 int old_jy = last_jy;
13035 int old_element = Tile[old_jx][old_jy];
13036 int new_element = Tile[jx][jy];
13038 if (IS_CUSTOM_ELEMENT(old_element))
13039 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13041 player->index_bit, leave_side);
13043 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13044 CE_PLAYER_LEAVES_X,
13045 player->index_bit, leave_side);
13047 if (IS_CUSTOM_ELEMENT(new_element))
13048 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13049 player->index_bit, enter_side);
13051 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13052 CE_PLAYER_ENTERS_X,
13053 player->index_bit, enter_side);
13055 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13056 CE_MOVE_OF_X, move_direction);
13059 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13061 TestIfPlayerTouchesBadThing(jx, jy);
13062 TestIfPlayerTouchesCustomElement(jx, jy);
13064 /* needed because pushed element has not yet reached its destination,
13065 so it would trigger a change event at its previous field location */
13066 if (!player->is_pushing)
13067 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13069 if (level.finish_dig_collect &&
13070 (player->is_digging || player->is_collecting))
13072 int last_element = player->last_removed_element;
13073 int move_direction = player->MovDir;
13074 int enter_side = MV_DIR_OPPOSITE(move_direction);
13075 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13076 CE_PLAYER_COLLECTS_X);
13078 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13079 player->index_bit, enter_side);
13081 player->last_removed_element = EL_UNDEFINED;
13084 if (!player->active)
13085 RemovePlayer(player);
13088 if (!game.LevelSolved && level.use_step_counter)
13098 if (TimeLeft <= 10 && setup.time_limit)
13099 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13101 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13103 DisplayGameControlValues();
13105 if (!TimeLeft && setup.time_limit)
13106 for (i = 0; i < MAX_PLAYERS; i++)
13107 KillPlayer(&stored_player[i]);
13109 else if (game.no_time_limit && !game.all_players_gone)
13111 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13113 DisplayGameControlValues();
13117 if (tape.single_step && tape.recording && !tape.pausing &&
13118 !player->programmed_action)
13119 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13121 if (!player->programmed_action)
13122 CheckSaveEngineSnapshot(player);
13126 void ScrollScreen(struct PlayerInfo *player, int mode)
13128 static unsigned int screen_frame_counter = 0;
13130 if (mode == SCROLL_INIT)
13132 // set scrolling step size according to actual player's moving speed
13133 ScrollStepSize = TILEX / player->move_delay_value;
13135 screen_frame_counter = FrameCounter;
13136 ScreenMovDir = player->MovDir;
13137 ScreenMovPos = player->MovPos;
13138 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13141 else if (!FrameReached(&screen_frame_counter, 1))
13146 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13147 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13148 redraw_mask |= REDRAW_FIELD;
13151 ScreenMovDir = MV_NONE;
13154 void TestIfPlayerTouchesCustomElement(int x, int y)
13156 static int xy[4][2] =
13163 static int trigger_sides[4][2] =
13165 // center side border side
13166 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13167 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13168 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13169 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13171 static int touch_dir[4] =
13173 MV_LEFT | MV_RIGHT,
13178 int center_element = Tile[x][y]; // should always be non-moving!
13181 for (i = 0; i < NUM_DIRECTIONS; i++)
13183 int xx = x + xy[i][0];
13184 int yy = y + xy[i][1];
13185 int center_side = trigger_sides[i][0];
13186 int border_side = trigger_sides[i][1];
13187 int border_element;
13189 if (!IN_LEV_FIELD(xx, yy))
13192 if (IS_PLAYER(x, y)) // player found at center element
13194 struct PlayerInfo *player = PLAYERINFO(x, y);
13196 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13197 border_element = Tile[xx][yy]; // may be moving!
13198 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13199 border_element = Tile[xx][yy];
13200 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13201 border_element = MovingOrBlocked2Element(xx, yy);
13203 continue; // center and border element do not touch
13205 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13206 player->index_bit, border_side);
13207 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13208 CE_PLAYER_TOUCHES_X,
13209 player->index_bit, border_side);
13212 /* use player element that is initially defined in the level playfield,
13213 not the player element that corresponds to the runtime player number
13214 (example: a level that contains EL_PLAYER_3 as the only player would
13215 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13216 int player_element = PLAYERINFO(x, y)->initial_element;
13218 CheckElementChangeBySide(xx, yy, border_element, player_element,
13219 CE_TOUCHING_X, border_side);
13222 else if (IS_PLAYER(xx, yy)) // player found at border element
13224 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13226 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13228 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13229 continue; // center and border element do not touch
13232 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13233 player->index_bit, center_side);
13234 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13235 CE_PLAYER_TOUCHES_X,
13236 player->index_bit, center_side);
13239 /* use player element that is initially defined in the level playfield,
13240 not the player element that corresponds to the runtime player number
13241 (example: a level that contains EL_PLAYER_3 as the only player would
13242 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13243 int player_element = PLAYERINFO(xx, yy)->initial_element;
13245 CheckElementChangeBySide(x, y, center_element, player_element,
13246 CE_TOUCHING_X, center_side);
13254 void TestIfElementTouchesCustomElement(int x, int y)
13256 static int xy[4][2] =
13263 static int trigger_sides[4][2] =
13265 // center side border side
13266 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13267 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13268 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13269 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13271 static int touch_dir[4] =
13273 MV_LEFT | MV_RIGHT,
13278 boolean change_center_element = FALSE;
13279 int center_element = Tile[x][y]; // should always be non-moving!
13280 int border_element_old[NUM_DIRECTIONS];
13283 for (i = 0; i < NUM_DIRECTIONS; i++)
13285 int xx = x + xy[i][0];
13286 int yy = y + xy[i][1];
13287 int border_element;
13289 border_element_old[i] = -1;
13291 if (!IN_LEV_FIELD(xx, yy))
13294 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13295 border_element = Tile[xx][yy]; // may be moving!
13296 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13297 border_element = Tile[xx][yy];
13298 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13299 border_element = MovingOrBlocked2Element(xx, yy);
13301 continue; // center and border element do not touch
13303 border_element_old[i] = border_element;
13306 for (i = 0; i < NUM_DIRECTIONS; i++)
13308 int xx = x + xy[i][0];
13309 int yy = y + xy[i][1];
13310 int center_side = trigger_sides[i][0];
13311 int border_element = border_element_old[i];
13313 if (border_element == -1)
13316 // check for change of border element
13317 CheckElementChangeBySide(xx, yy, border_element, center_element,
13318 CE_TOUCHING_X, center_side);
13320 // (center element cannot be player, so we dont have to check this here)
13323 for (i = 0; i < NUM_DIRECTIONS; i++)
13325 int xx = x + xy[i][0];
13326 int yy = y + xy[i][1];
13327 int border_side = trigger_sides[i][1];
13328 int border_element = border_element_old[i];
13330 if (border_element == -1)
13333 // check for change of center element (but change it only once)
13334 if (!change_center_element)
13335 change_center_element =
13336 CheckElementChangeBySide(x, y, center_element, border_element,
13337 CE_TOUCHING_X, border_side);
13339 if (IS_PLAYER(xx, yy))
13341 /* use player element that is initially defined in the level playfield,
13342 not the player element that corresponds to the runtime player number
13343 (example: a level that contains EL_PLAYER_3 as the only player would
13344 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13345 int player_element = PLAYERINFO(xx, yy)->initial_element;
13347 CheckElementChangeBySide(x, y, center_element, player_element,
13348 CE_TOUCHING_X, border_side);
13353 void TestIfElementHitsCustomElement(int x, int y, int direction)
13355 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13356 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13357 int hitx = x + dx, hity = y + dy;
13358 int hitting_element = Tile[x][y];
13359 int touched_element;
13361 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13364 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13365 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13367 if (IN_LEV_FIELD(hitx, hity))
13369 int opposite_direction = MV_DIR_OPPOSITE(direction);
13370 int hitting_side = direction;
13371 int touched_side = opposite_direction;
13372 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13373 MovDir[hitx][hity] != direction ||
13374 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13380 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13381 CE_HITTING_X, touched_side);
13383 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13384 CE_HIT_BY_X, hitting_side);
13386 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13387 CE_HIT_BY_SOMETHING, opposite_direction);
13389 if (IS_PLAYER(hitx, hity))
13391 /* use player element that is initially defined in the level playfield,
13392 not the player element that corresponds to the runtime player number
13393 (example: a level that contains EL_PLAYER_3 as the only player would
13394 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13395 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13397 CheckElementChangeBySide(x, y, hitting_element, player_element,
13398 CE_HITTING_X, touched_side);
13403 // "hitting something" is also true when hitting the playfield border
13404 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13405 CE_HITTING_SOMETHING, direction);
13408 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13410 int i, kill_x = -1, kill_y = -1;
13412 int bad_element = -1;
13413 static int test_xy[4][2] =
13420 static int test_dir[4] =
13428 for (i = 0; i < NUM_DIRECTIONS; i++)
13430 int test_x, test_y, test_move_dir, test_element;
13432 test_x = good_x + test_xy[i][0];
13433 test_y = good_y + test_xy[i][1];
13435 if (!IN_LEV_FIELD(test_x, test_y))
13439 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13441 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13443 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13444 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13446 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13447 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13451 bad_element = test_element;
13457 if (kill_x != -1 || kill_y != -1)
13459 if (IS_PLAYER(good_x, good_y))
13461 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13463 if (player->shield_deadly_time_left > 0 &&
13464 !IS_INDESTRUCTIBLE(bad_element))
13465 Bang(kill_x, kill_y);
13466 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13467 KillPlayer(player);
13470 Bang(good_x, good_y);
13474 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13476 int i, kill_x = -1, kill_y = -1;
13477 int bad_element = Tile[bad_x][bad_y];
13478 static int test_xy[4][2] =
13485 static int touch_dir[4] =
13487 MV_LEFT | MV_RIGHT,
13492 static int test_dir[4] =
13500 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13503 for (i = 0; i < NUM_DIRECTIONS; i++)
13505 int test_x, test_y, test_move_dir, test_element;
13507 test_x = bad_x + test_xy[i][0];
13508 test_y = bad_y + test_xy[i][1];
13510 if (!IN_LEV_FIELD(test_x, test_y))
13514 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13516 test_element = Tile[test_x][test_y];
13518 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13519 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13521 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13522 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13524 // good thing is player or penguin that does not move away
13525 if (IS_PLAYER(test_x, test_y))
13527 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13529 if (bad_element == EL_ROBOT && player->is_moving)
13530 continue; // robot does not kill player if he is moving
13532 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13534 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13535 continue; // center and border element do not touch
13543 else if (test_element == EL_PENGUIN)
13553 if (kill_x != -1 || kill_y != -1)
13555 if (IS_PLAYER(kill_x, kill_y))
13557 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13559 if (player->shield_deadly_time_left > 0 &&
13560 !IS_INDESTRUCTIBLE(bad_element))
13561 Bang(bad_x, bad_y);
13562 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13563 KillPlayer(player);
13566 Bang(kill_x, kill_y);
13570 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13572 int bad_element = Tile[bad_x][bad_y];
13573 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13574 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13575 int test_x = bad_x + dx, test_y = bad_y + dy;
13576 int test_move_dir, test_element;
13577 int kill_x = -1, kill_y = -1;
13579 if (!IN_LEV_FIELD(test_x, test_y))
13583 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13585 test_element = Tile[test_x][test_y];
13587 if (test_move_dir != bad_move_dir)
13589 // good thing can be player or penguin that does not move away
13590 if (IS_PLAYER(test_x, test_y))
13592 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13594 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13595 player as being hit when he is moving towards the bad thing, because
13596 the "get hit by" condition would be lost after the player stops) */
13597 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13598 return; // player moves away from bad thing
13603 else if (test_element == EL_PENGUIN)
13610 if (kill_x != -1 || kill_y != -1)
13612 if (IS_PLAYER(kill_x, kill_y))
13614 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13616 if (player->shield_deadly_time_left > 0 &&
13617 !IS_INDESTRUCTIBLE(bad_element))
13618 Bang(bad_x, bad_y);
13619 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13620 KillPlayer(player);
13623 Bang(kill_x, kill_y);
13627 void TestIfPlayerTouchesBadThing(int x, int y)
13629 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13632 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13634 TestIfGoodThingHitsBadThing(x, y, move_dir);
13637 void TestIfBadThingTouchesPlayer(int x, int y)
13639 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13642 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13644 TestIfBadThingHitsGoodThing(x, y, move_dir);
13647 void TestIfFriendTouchesBadThing(int x, int y)
13649 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13652 void TestIfBadThingTouchesFriend(int x, int y)
13654 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13657 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13659 int i, kill_x = bad_x, kill_y = bad_y;
13660 static int xy[4][2] =
13668 for (i = 0; i < NUM_DIRECTIONS; i++)
13672 x = bad_x + xy[i][0];
13673 y = bad_y + xy[i][1];
13674 if (!IN_LEV_FIELD(x, y))
13677 element = Tile[x][y];
13678 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13679 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13687 if (kill_x != bad_x || kill_y != bad_y)
13688 Bang(bad_x, bad_y);
13691 void KillPlayer(struct PlayerInfo *player)
13693 int jx = player->jx, jy = player->jy;
13695 if (!player->active)
13699 Debug("game:playing:KillPlayer",
13700 "0: killed == %d, active == %d, reanimated == %d",
13701 player->killed, player->active, player->reanimated);
13704 /* the following code was introduced to prevent an infinite loop when calling
13706 -> CheckTriggeredElementChangeExt()
13707 -> ExecuteCustomElementAction()
13709 -> (infinitely repeating the above sequence of function calls)
13710 which occurs when killing the player while having a CE with the setting
13711 "kill player X when explosion of <player X>"; the solution using a new
13712 field "player->killed" was chosen for backwards compatibility, although
13713 clever use of the fields "player->active" etc. would probably also work */
13715 if (player->killed)
13719 player->killed = TRUE;
13721 // remove accessible field at the player's position
13722 Tile[jx][jy] = EL_EMPTY;
13724 // deactivate shield (else Bang()/Explode() would not work right)
13725 player->shield_normal_time_left = 0;
13726 player->shield_deadly_time_left = 0;
13729 Debug("game:playing:KillPlayer",
13730 "1: killed == %d, active == %d, reanimated == %d",
13731 player->killed, player->active, player->reanimated);
13737 Debug("game:playing:KillPlayer",
13738 "2: killed == %d, active == %d, reanimated == %d",
13739 player->killed, player->active, player->reanimated);
13742 if (player->reanimated) // killed player may have been reanimated
13743 player->killed = player->reanimated = FALSE;
13745 BuryPlayer(player);
13748 static void KillPlayerUnlessEnemyProtected(int x, int y)
13750 if (!PLAYER_ENEMY_PROTECTED(x, y))
13751 KillPlayer(PLAYERINFO(x, y));
13754 static void KillPlayerUnlessExplosionProtected(int x, int y)
13756 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13757 KillPlayer(PLAYERINFO(x, y));
13760 void BuryPlayer(struct PlayerInfo *player)
13762 int jx = player->jx, jy = player->jy;
13764 if (!player->active)
13767 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13768 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13770 RemovePlayer(player);
13772 player->buried = TRUE;
13774 if (game.all_players_gone)
13775 game.GameOver = TRUE;
13778 void RemovePlayer(struct PlayerInfo *player)
13780 int jx = player->jx, jy = player->jy;
13781 int i, found = FALSE;
13783 player->present = FALSE;
13784 player->active = FALSE;
13786 // required for some CE actions (even if the player is not active anymore)
13787 player->MovPos = 0;
13789 if (!ExplodeField[jx][jy])
13790 StorePlayer[jx][jy] = 0;
13792 if (player->is_moving)
13793 TEST_DrawLevelField(player->last_jx, player->last_jy);
13795 for (i = 0; i < MAX_PLAYERS; i++)
13796 if (stored_player[i].active)
13801 game.all_players_gone = TRUE;
13802 game.GameOver = TRUE;
13805 game.exit_x = game.robot_wheel_x = jx;
13806 game.exit_y = game.robot_wheel_y = jy;
13809 void ExitPlayer(struct PlayerInfo *player)
13811 DrawPlayer(player); // needed here only to cleanup last field
13812 RemovePlayer(player);
13814 if (game.players_still_needed > 0)
13815 game.players_still_needed--;
13818 static void SetFieldForSnapping(int x, int y, int element, int direction,
13819 int player_index_bit)
13821 struct ElementInfo *ei = &element_info[element];
13822 int direction_bit = MV_DIR_TO_BIT(direction);
13823 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13824 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13825 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13827 Tile[x][y] = EL_ELEMENT_SNAPPING;
13828 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13829 MovDir[x][y] = direction;
13830 Store[x][y] = element;
13831 Store2[x][y] = player_index_bit;
13833 ResetGfxAnimation(x, y);
13835 GfxElement[x][y] = element;
13836 GfxAction[x][y] = action;
13837 GfxDir[x][y] = direction;
13838 GfxFrame[x][y] = -1;
13841 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
13842 int player_index_bit)
13844 TestIfElementTouchesCustomElement(x, y); // for empty space
13846 if (level.finish_dig_collect)
13848 int dig_side = MV_DIR_OPPOSITE(direction);
13850 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13851 player_index_bit, dig_side);
13856 =============================================================================
13857 checkDiagonalPushing()
13858 -----------------------------------------------------------------------------
13859 check if diagonal input device direction results in pushing of object
13860 (by checking if the alternative direction is walkable, diggable, ...)
13861 =============================================================================
13864 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13865 int x, int y, int real_dx, int real_dy)
13867 int jx, jy, dx, dy, xx, yy;
13869 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13872 // diagonal direction: check alternative direction
13877 xx = jx + (dx == 0 ? real_dx : 0);
13878 yy = jy + (dy == 0 ? real_dy : 0);
13880 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13884 =============================================================================
13886 -----------------------------------------------------------------------------
13887 x, y: field next to player (non-diagonal) to try to dig to
13888 real_dx, real_dy: direction as read from input device (can be diagonal)
13889 =============================================================================
13892 static int DigField(struct PlayerInfo *player,
13893 int oldx, int oldy, int x, int y,
13894 int real_dx, int real_dy, int mode)
13896 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13897 boolean player_was_pushing = player->is_pushing;
13898 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13899 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13900 int jx = oldx, jy = oldy;
13901 int dx = x - jx, dy = y - jy;
13902 int nextx = x + dx, nexty = y + dy;
13903 int move_direction = (dx == -1 ? MV_LEFT :
13904 dx == +1 ? MV_RIGHT :
13906 dy == +1 ? MV_DOWN : MV_NONE);
13907 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13908 int dig_side = MV_DIR_OPPOSITE(move_direction);
13909 int old_element = Tile[jx][jy];
13910 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13913 if (is_player) // function can also be called by EL_PENGUIN
13915 if (player->MovPos == 0)
13917 player->is_digging = FALSE;
13918 player->is_collecting = FALSE;
13921 if (player->MovPos == 0) // last pushing move finished
13922 player->is_pushing = FALSE;
13924 if (mode == DF_NO_PUSH) // player just stopped pushing
13926 player->is_switching = FALSE;
13927 player->push_delay = -1;
13929 return MP_NO_ACTION;
13933 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13934 old_element = Back[jx][jy];
13936 // in case of element dropped at player position, check background
13937 else if (Back[jx][jy] != EL_EMPTY &&
13938 game.engine_version >= VERSION_IDENT(2,2,0,0))
13939 old_element = Back[jx][jy];
13941 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13942 return MP_NO_ACTION; // field has no opening in this direction
13944 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13945 return MP_NO_ACTION; // field has no opening in this direction
13947 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13951 Tile[jx][jy] = player->artwork_element;
13952 InitMovingField(jx, jy, MV_DOWN);
13953 Store[jx][jy] = EL_ACID;
13954 ContinueMoving(jx, jy);
13955 BuryPlayer(player);
13957 return MP_DONT_RUN_INTO;
13960 if (player_can_move && DONT_RUN_INTO(element))
13962 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13964 return MP_DONT_RUN_INTO;
13967 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13968 return MP_NO_ACTION;
13970 collect_count = element_info[element].collect_count_initial;
13972 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13973 return MP_NO_ACTION;
13975 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13976 player_can_move = player_can_move_or_snap;
13978 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13979 game.engine_version >= VERSION_IDENT(2,2,0,0))
13981 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13982 player->index_bit, dig_side);
13983 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13984 player->index_bit, dig_side);
13986 if (element == EL_DC_LANDMINE)
13989 if (Tile[x][y] != element) // field changed by snapping
13992 return MP_NO_ACTION;
13995 if (player->gravity && is_player && !player->is_auto_moving &&
13996 canFallDown(player) && move_direction != MV_DOWN &&
13997 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13998 return MP_NO_ACTION; // player cannot walk here due to gravity
14000 if (player_can_move &&
14001 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14003 int sound_element = SND_ELEMENT(element);
14004 int sound_action = ACTION_WALKING;
14006 if (IS_RND_GATE(element))
14008 if (!player->key[RND_GATE_NR(element)])
14009 return MP_NO_ACTION;
14011 else if (IS_RND_GATE_GRAY(element))
14013 if (!player->key[RND_GATE_GRAY_NR(element)])
14014 return MP_NO_ACTION;
14016 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14018 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14019 return MP_NO_ACTION;
14021 else if (element == EL_EXIT_OPEN ||
14022 element == EL_EM_EXIT_OPEN ||
14023 element == EL_EM_EXIT_OPENING ||
14024 element == EL_STEEL_EXIT_OPEN ||
14025 element == EL_EM_STEEL_EXIT_OPEN ||
14026 element == EL_EM_STEEL_EXIT_OPENING ||
14027 element == EL_SP_EXIT_OPEN ||
14028 element == EL_SP_EXIT_OPENING)
14030 sound_action = ACTION_PASSING; // player is passing exit
14032 else if (element == EL_EMPTY)
14034 sound_action = ACTION_MOVING; // nothing to walk on
14037 // play sound from background or player, whatever is available
14038 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14039 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14041 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14043 else if (player_can_move &&
14044 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14046 if (!ACCESS_FROM(element, opposite_direction))
14047 return MP_NO_ACTION; // field not accessible from this direction
14049 if (CAN_MOVE(element)) // only fixed elements can be passed!
14050 return MP_NO_ACTION;
14052 if (IS_EM_GATE(element))
14054 if (!player->key[EM_GATE_NR(element)])
14055 return MP_NO_ACTION;
14057 else if (IS_EM_GATE_GRAY(element))
14059 if (!player->key[EM_GATE_GRAY_NR(element)])
14060 return MP_NO_ACTION;
14062 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14064 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14065 return MP_NO_ACTION;
14067 else if (IS_EMC_GATE(element))
14069 if (!player->key[EMC_GATE_NR(element)])
14070 return MP_NO_ACTION;
14072 else if (IS_EMC_GATE_GRAY(element))
14074 if (!player->key[EMC_GATE_GRAY_NR(element)])
14075 return MP_NO_ACTION;
14077 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14079 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14080 return MP_NO_ACTION;
14082 else if (element == EL_DC_GATE_WHITE ||
14083 element == EL_DC_GATE_WHITE_GRAY ||
14084 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14086 if (player->num_white_keys == 0)
14087 return MP_NO_ACTION;
14089 player->num_white_keys--;
14091 else if (IS_SP_PORT(element))
14093 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14094 element == EL_SP_GRAVITY_PORT_RIGHT ||
14095 element == EL_SP_GRAVITY_PORT_UP ||
14096 element == EL_SP_GRAVITY_PORT_DOWN)
14097 player->gravity = !player->gravity;
14098 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14099 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14100 element == EL_SP_GRAVITY_ON_PORT_UP ||
14101 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14102 player->gravity = TRUE;
14103 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14104 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14105 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14106 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14107 player->gravity = FALSE;
14110 // automatically move to the next field with double speed
14111 player->programmed_action = move_direction;
14113 if (player->move_delay_reset_counter == 0)
14115 player->move_delay_reset_counter = 2; // two double speed steps
14117 DOUBLE_PLAYER_SPEED(player);
14120 PlayLevelSoundAction(x, y, ACTION_PASSING);
14122 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14126 if (mode != DF_SNAP)
14128 GfxElement[x][y] = GFX_ELEMENT(element);
14129 player->is_digging = TRUE;
14132 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14134 // use old behaviour for old levels (digging)
14135 if (!level.finish_dig_collect)
14137 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14138 player->index_bit, dig_side);
14140 // if digging triggered player relocation, finish digging tile
14141 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14142 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14145 if (mode == DF_SNAP)
14147 if (level.block_snap_field)
14148 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14150 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14152 // use old behaviour for old levels (snapping)
14153 if (!level.finish_dig_collect)
14154 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14155 player->index_bit, dig_side);
14158 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14162 if (is_player && mode != DF_SNAP)
14164 GfxElement[x][y] = element;
14165 player->is_collecting = TRUE;
14168 if (element == EL_SPEED_PILL)
14170 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14172 else if (element == EL_EXTRA_TIME && level.time > 0)
14174 TimeLeft += level.extra_time;
14176 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14178 DisplayGameControlValues();
14180 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14182 player->shield_normal_time_left += level.shield_normal_time;
14183 if (element == EL_SHIELD_DEADLY)
14184 player->shield_deadly_time_left += level.shield_deadly_time;
14186 else if (element == EL_DYNAMITE ||
14187 element == EL_EM_DYNAMITE ||
14188 element == EL_SP_DISK_RED)
14190 if (player->inventory_size < MAX_INVENTORY_SIZE)
14191 player->inventory_element[player->inventory_size++] = element;
14193 DrawGameDoorValues();
14195 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14197 player->dynabomb_count++;
14198 player->dynabombs_left++;
14200 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14202 player->dynabomb_size++;
14204 else if (element == EL_DYNABOMB_INCREASE_POWER)
14206 player->dynabomb_xl = TRUE;
14208 else if (IS_KEY(element))
14210 player->key[KEY_NR(element)] = TRUE;
14212 DrawGameDoorValues();
14214 else if (element == EL_DC_KEY_WHITE)
14216 player->num_white_keys++;
14218 // display white keys?
14219 // DrawGameDoorValues();
14221 else if (IS_ENVELOPE(element))
14223 player->show_envelope = element;
14225 else if (element == EL_EMC_LENSES)
14227 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14229 RedrawAllInvisibleElementsForLenses();
14231 else if (element == EL_EMC_MAGNIFIER)
14233 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14235 RedrawAllInvisibleElementsForMagnifier();
14237 else if (IS_DROPPABLE(element) ||
14238 IS_THROWABLE(element)) // can be collected and dropped
14242 if (collect_count == 0)
14243 player->inventory_infinite_element = element;
14245 for (i = 0; i < collect_count; i++)
14246 if (player->inventory_size < MAX_INVENTORY_SIZE)
14247 player->inventory_element[player->inventory_size++] = element;
14249 DrawGameDoorValues();
14251 else if (collect_count > 0)
14253 game.gems_still_needed -= collect_count;
14254 if (game.gems_still_needed < 0)
14255 game.gems_still_needed = 0;
14257 game.snapshot.collected_item = TRUE;
14259 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14261 DisplayGameControlValues();
14264 RaiseScoreElement(element);
14265 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14267 // use old behaviour for old levels (collecting)
14268 if (!level.finish_dig_collect && is_player)
14270 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14271 player->index_bit, dig_side);
14273 // if collecting triggered player relocation, finish collecting tile
14274 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14275 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14278 if (mode == DF_SNAP)
14280 if (level.block_snap_field)
14281 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14283 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14285 // use old behaviour for old levels (snapping)
14286 if (!level.finish_dig_collect)
14287 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14288 player->index_bit, dig_side);
14291 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14293 if (mode == DF_SNAP && element != EL_BD_ROCK)
14294 return MP_NO_ACTION;
14296 if (CAN_FALL(element) && dy)
14297 return MP_NO_ACTION;
14299 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14300 !(element == EL_SPRING && level.use_spring_bug))
14301 return MP_NO_ACTION;
14303 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14304 ((move_direction & MV_VERTICAL &&
14305 ((element_info[element].move_pattern & MV_LEFT &&
14306 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14307 (element_info[element].move_pattern & MV_RIGHT &&
14308 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14309 (move_direction & MV_HORIZONTAL &&
14310 ((element_info[element].move_pattern & MV_UP &&
14311 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14312 (element_info[element].move_pattern & MV_DOWN &&
14313 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14314 return MP_NO_ACTION;
14316 // do not push elements already moving away faster than player
14317 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14318 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14319 return MP_NO_ACTION;
14321 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14323 if (player->push_delay_value == -1 || !player_was_pushing)
14324 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14326 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14328 if (player->push_delay_value == -1)
14329 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14331 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14333 if (!player->is_pushing)
14334 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14337 player->is_pushing = TRUE;
14338 player->is_active = TRUE;
14340 if (!(IN_LEV_FIELD(nextx, nexty) &&
14341 (IS_FREE(nextx, nexty) ||
14342 (IS_SB_ELEMENT(element) &&
14343 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14344 (IS_CUSTOM_ELEMENT(element) &&
14345 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14346 return MP_NO_ACTION;
14348 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14349 return MP_NO_ACTION;
14351 if (player->push_delay == -1) // new pushing; restart delay
14352 player->push_delay = 0;
14354 if (player->push_delay < player->push_delay_value &&
14355 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14356 element != EL_SPRING && element != EL_BALLOON)
14358 // make sure that there is no move delay before next try to push
14359 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14360 player->move_delay = 0;
14362 return MP_NO_ACTION;
14365 if (IS_CUSTOM_ELEMENT(element) &&
14366 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14368 if (!DigFieldByCE(nextx, nexty, element))
14369 return MP_NO_ACTION;
14372 if (IS_SB_ELEMENT(element))
14374 boolean sokoban_task_solved = FALSE;
14376 if (element == EL_SOKOBAN_FIELD_FULL)
14378 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14380 IncrementSokobanFieldsNeeded();
14381 IncrementSokobanObjectsNeeded();
14384 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14386 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14388 DecrementSokobanFieldsNeeded();
14389 DecrementSokobanObjectsNeeded();
14391 // sokoban object was pushed from empty field to sokoban field
14392 if (Back[x][y] == EL_EMPTY)
14393 sokoban_task_solved = TRUE;
14396 Tile[x][y] = EL_SOKOBAN_OBJECT;
14398 if (Back[x][y] == Back[nextx][nexty])
14399 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14400 else if (Back[x][y] != 0)
14401 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14404 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14407 if (sokoban_task_solved &&
14408 game.sokoban_fields_still_needed == 0 &&
14409 game.sokoban_objects_still_needed == 0 &&
14410 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14412 game.players_still_needed = 0;
14416 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14420 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14422 InitMovingField(x, y, move_direction);
14423 GfxAction[x][y] = ACTION_PUSHING;
14425 if (mode == DF_SNAP)
14426 ContinueMoving(x, y);
14428 MovPos[x][y] = (dx != 0 ? dx : dy);
14430 Pushed[x][y] = TRUE;
14431 Pushed[nextx][nexty] = TRUE;
14433 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14434 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14436 player->push_delay_value = -1; // get new value later
14438 // check for element change _after_ element has been pushed
14439 if (game.use_change_when_pushing_bug)
14441 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14442 player->index_bit, dig_side);
14443 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14444 player->index_bit, dig_side);
14447 else if (IS_SWITCHABLE(element))
14449 if (PLAYER_SWITCHING(player, x, y))
14451 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14452 player->index_bit, dig_side);
14457 player->is_switching = TRUE;
14458 player->switch_x = x;
14459 player->switch_y = y;
14461 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14463 if (element == EL_ROBOT_WHEEL)
14465 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14467 game.robot_wheel_x = x;
14468 game.robot_wheel_y = y;
14469 game.robot_wheel_active = TRUE;
14471 TEST_DrawLevelField(x, y);
14473 else if (element == EL_SP_TERMINAL)
14477 SCAN_PLAYFIELD(xx, yy)
14479 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14483 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14485 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14487 ResetGfxAnimation(xx, yy);
14488 TEST_DrawLevelField(xx, yy);
14492 else if (IS_BELT_SWITCH(element))
14494 ToggleBeltSwitch(x, y);
14496 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14497 element == EL_SWITCHGATE_SWITCH_DOWN ||
14498 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14499 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14501 ToggleSwitchgateSwitch(x, y);
14503 else if (element == EL_LIGHT_SWITCH ||
14504 element == EL_LIGHT_SWITCH_ACTIVE)
14506 ToggleLightSwitch(x, y);
14508 else if (element == EL_TIMEGATE_SWITCH ||
14509 element == EL_DC_TIMEGATE_SWITCH)
14511 ActivateTimegateSwitch(x, y);
14513 else if (element == EL_BALLOON_SWITCH_LEFT ||
14514 element == EL_BALLOON_SWITCH_RIGHT ||
14515 element == EL_BALLOON_SWITCH_UP ||
14516 element == EL_BALLOON_SWITCH_DOWN ||
14517 element == EL_BALLOON_SWITCH_NONE ||
14518 element == EL_BALLOON_SWITCH_ANY)
14520 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14521 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14522 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14523 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14524 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14527 else if (element == EL_LAMP)
14529 Tile[x][y] = EL_LAMP_ACTIVE;
14530 game.lights_still_needed--;
14532 ResetGfxAnimation(x, y);
14533 TEST_DrawLevelField(x, y);
14535 else if (element == EL_TIME_ORB_FULL)
14537 Tile[x][y] = EL_TIME_ORB_EMPTY;
14539 if (level.time > 0 || level.use_time_orb_bug)
14541 TimeLeft += level.time_orb_time;
14542 game.no_time_limit = FALSE;
14544 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14546 DisplayGameControlValues();
14549 ResetGfxAnimation(x, y);
14550 TEST_DrawLevelField(x, y);
14552 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14553 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14557 game.ball_active = !game.ball_active;
14559 SCAN_PLAYFIELD(xx, yy)
14561 int e = Tile[xx][yy];
14563 if (game.ball_active)
14565 if (e == EL_EMC_MAGIC_BALL)
14566 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14567 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14568 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14572 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14573 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14574 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14575 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14580 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14581 player->index_bit, dig_side);
14583 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14584 player->index_bit, dig_side);
14586 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14587 player->index_bit, dig_side);
14593 if (!PLAYER_SWITCHING(player, x, y))
14595 player->is_switching = TRUE;
14596 player->switch_x = x;
14597 player->switch_y = y;
14599 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14600 player->index_bit, dig_side);
14601 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14602 player->index_bit, dig_side);
14604 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14605 player->index_bit, dig_side);
14606 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14607 player->index_bit, dig_side);
14610 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14611 player->index_bit, dig_side);
14612 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14613 player->index_bit, dig_side);
14615 return MP_NO_ACTION;
14618 player->push_delay = -1;
14620 if (is_player) // function can also be called by EL_PENGUIN
14622 if (Tile[x][y] != element) // really digged/collected something
14624 player->is_collecting = !player->is_digging;
14625 player->is_active = TRUE;
14627 player->last_removed_element = element;
14634 static boolean DigFieldByCE(int x, int y, int digging_element)
14636 int element = Tile[x][y];
14638 if (!IS_FREE(x, y))
14640 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14641 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14644 // no element can dig solid indestructible elements
14645 if (IS_INDESTRUCTIBLE(element) &&
14646 !IS_DIGGABLE(element) &&
14647 !IS_COLLECTIBLE(element))
14650 if (AmoebaNr[x][y] &&
14651 (element == EL_AMOEBA_FULL ||
14652 element == EL_BD_AMOEBA ||
14653 element == EL_AMOEBA_GROWING))
14655 AmoebaCnt[AmoebaNr[x][y]]--;
14656 AmoebaCnt2[AmoebaNr[x][y]]--;
14659 if (IS_MOVING(x, y))
14660 RemoveMovingField(x, y);
14664 TEST_DrawLevelField(x, y);
14667 // if digged element was about to explode, prevent the explosion
14668 ExplodeField[x][y] = EX_TYPE_NONE;
14670 PlayLevelSoundAction(x, y, action);
14673 Store[x][y] = EL_EMPTY;
14675 // this makes it possible to leave the removed element again
14676 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14677 Store[x][y] = element;
14682 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14684 int jx = player->jx, jy = player->jy;
14685 int x = jx + dx, y = jy + dy;
14686 int snap_direction = (dx == -1 ? MV_LEFT :
14687 dx == +1 ? MV_RIGHT :
14689 dy == +1 ? MV_DOWN : MV_NONE);
14690 boolean can_continue_snapping = (level.continuous_snapping &&
14691 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14693 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14696 if (!player->active || !IN_LEV_FIELD(x, y))
14704 if (player->MovPos == 0)
14705 player->is_pushing = FALSE;
14707 player->is_snapping = FALSE;
14709 if (player->MovPos == 0)
14711 player->is_moving = FALSE;
14712 player->is_digging = FALSE;
14713 player->is_collecting = FALSE;
14719 // prevent snapping with already pressed snap key when not allowed
14720 if (player->is_snapping && !can_continue_snapping)
14723 player->MovDir = snap_direction;
14725 if (player->MovPos == 0)
14727 player->is_moving = FALSE;
14728 player->is_digging = FALSE;
14729 player->is_collecting = FALSE;
14732 player->is_dropping = FALSE;
14733 player->is_dropping_pressed = FALSE;
14734 player->drop_pressed_delay = 0;
14736 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14739 player->is_snapping = TRUE;
14740 player->is_active = TRUE;
14742 if (player->MovPos == 0)
14744 player->is_moving = FALSE;
14745 player->is_digging = FALSE;
14746 player->is_collecting = FALSE;
14749 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14750 TEST_DrawLevelField(player->last_jx, player->last_jy);
14752 TEST_DrawLevelField(x, y);
14757 static boolean DropElement(struct PlayerInfo *player)
14759 int old_element, new_element;
14760 int dropx = player->jx, dropy = player->jy;
14761 int drop_direction = player->MovDir;
14762 int drop_side = drop_direction;
14763 int drop_element = get_next_dropped_element(player);
14765 /* do not drop an element on top of another element; when holding drop key
14766 pressed without moving, dropped element must move away before the next
14767 element can be dropped (this is especially important if the next element
14768 is dynamite, which can be placed on background for historical reasons) */
14769 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14772 if (IS_THROWABLE(drop_element))
14774 dropx += GET_DX_FROM_DIR(drop_direction);
14775 dropy += GET_DY_FROM_DIR(drop_direction);
14777 if (!IN_LEV_FIELD(dropx, dropy))
14781 old_element = Tile[dropx][dropy]; // old element at dropping position
14782 new_element = drop_element; // default: no change when dropping
14784 // check if player is active, not moving and ready to drop
14785 if (!player->active || player->MovPos || player->drop_delay > 0)
14788 // check if player has anything that can be dropped
14789 if (new_element == EL_UNDEFINED)
14792 // only set if player has anything that can be dropped
14793 player->is_dropping_pressed = TRUE;
14795 // check if drop key was pressed long enough for EM style dynamite
14796 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14799 // check if anything can be dropped at the current position
14800 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14803 // collected custom elements can only be dropped on empty fields
14804 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14807 if (old_element != EL_EMPTY)
14808 Back[dropx][dropy] = old_element; // store old element on this field
14810 ResetGfxAnimation(dropx, dropy);
14811 ResetRandomAnimationValue(dropx, dropy);
14813 if (player->inventory_size > 0 ||
14814 player->inventory_infinite_element != EL_UNDEFINED)
14816 if (player->inventory_size > 0)
14818 player->inventory_size--;
14820 DrawGameDoorValues();
14822 if (new_element == EL_DYNAMITE)
14823 new_element = EL_DYNAMITE_ACTIVE;
14824 else if (new_element == EL_EM_DYNAMITE)
14825 new_element = EL_EM_DYNAMITE_ACTIVE;
14826 else if (new_element == EL_SP_DISK_RED)
14827 new_element = EL_SP_DISK_RED_ACTIVE;
14830 Tile[dropx][dropy] = new_element;
14832 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14833 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14834 el2img(Tile[dropx][dropy]), 0);
14836 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14838 // needed if previous element just changed to "empty" in the last frame
14839 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14841 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14842 player->index_bit, drop_side);
14843 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14845 player->index_bit, drop_side);
14847 TestIfElementTouchesCustomElement(dropx, dropy);
14849 else // player is dropping a dyna bomb
14851 player->dynabombs_left--;
14853 Tile[dropx][dropy] = new_element;
14855 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14856 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14857 el2img(Tile[dropx][dropy]), 0);
14859 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14862 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14863 InitField_WithBug1(dropx, dropy, FALSE);
14865 new_element = Tile[dropx][dropy]; // element might have changed
14867 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14868 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14870 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14871 MovDir[dropx][dropy] = drop_direction;
14873 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14875 // do not cause impact style collision by dropping elements that can fall
14876 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14879 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14880 player->is_dropping = TRUE;
14882 player->drop_pressed_delay = 0;
14883 player->is_dropping_pressed = FALSE;
14885 player->drop_x = dropx;
14886 player->drop_y = dropy;
14891 // ----------------------------------------------------------------------------
14892 // game sound playing functions
14893 // ----------------------------------------------------------------------------
14895 static int *loop_sound_frame = NULL;
14896 static int *loop_sound_volume = NULL;
14898 void InitPlayLevelSound(void)
14900 int num_sounds = getSoundListSize();
14902 checked_free(loop_sound_frame);
14903 checked_free(loop_sound_volume);
14905 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14906 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14909 static void PlayLevelSound(int x, int y, int nr)
14911 int sx = SCREENX(x), sy = SCREENY(y);
14912 int volume, stereo_position;
14913 int max_distance = 8;
14914 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14916 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14917 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14920 if (!IN_LEV_FIELD(x, y) ||
14921 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14922 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14925 volume = SOUND_MAX_VOLUME;
14927 if (!IN_SCR_FIELD(sx, sy))
14929 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14930 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14932 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14935 stereo_position = (SOUND_MAX_LEFT +
14936 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14937 (SCR_FIELDX + 2 * max_distance));
14939 if (IS_LOOP_SOUND(nr))
14941 /* This assures that quieter loop sounds do not overwrite louder ones,
14942 while restarting sound volume comparison with each new game frame. */
14944 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14947 loop_sound_volume[nr] = volume;
14948 loop_sound_frame[nr] = FrameCounter;
14951 PlaySoundExt(nr, volume, stereo_position, type);
14954 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14956 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14957 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14958 y < LEVELY(BY1) ? LEVELY(BY1) :
14959 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14963 static void PlayLevelSoundAction(int x, int y, int action)
14965 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14968 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14970 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14972 if (sound_effect != SND_UNDEFINED)
14973 PlayLevelSound(x, y, sound_effect);
14976 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14979 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14981 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14982 PlayLevelSound(x, y, sound_effect);
14985 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14987 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14989 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14990 PlayLevelSound(x, y, sound_effect);
14993 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14995 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14997 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14998 StopSound(sound_effect);
15001 static int getLevelMusicNr(void)
15003 if (levelset.music[level_nr] != MUS_UNDEFINED)
15004 return levelset.music[level_nr]; // from config file
15006 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15009 static void FadeLevelSounds(void)
15014 static void FadeLevelMusic(void)
15016 int music_nr = getLevelMusicNr();
15017 char *curr_music = getCurrentlyPlayingMusicFilename();
15018 char *next_music = getMusicInfoEntryFilename(music_nr);
15020 if (!strEqual(curr_music, next_music))
15024 void FadeLevelSoundsAndMusic(void)
15030 static void PlayLevelMusic(void)
15032 int music_nr = getLevelMusicNr();
15033 char *curr_music = getCurrentlyPlayingMusicFilename();
15034 char *next_music = getMusicInfoEntryFilename(music_nr);
15036 if (!strEqual(curr_music, next_music))
15037 PlayMusicLoop(music_nr);
15040 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15042 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15044 int x = xx - offset;
15045 int y = yy - offset;
15050 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15054 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15058 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15062 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15066 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15070 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15074 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15077 case SOUND_android_clone:
15078 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15081 case SOUND_android_move:
15082 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15086 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15090 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15094 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15097 case SOUND_eater_eat:
15098 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15102 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15105 case SOUND_collect:
15106 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15109 case SOUND_diamond:
15110 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15114 // !!! CHECK THIS !!!
15116 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15118 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15122 case SOUND_wonderfall:
15123 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15127 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15131 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15135 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15139 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15143 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15147 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15151 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15155 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15158 case SOUND_exit_open:
15159 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15162 case SOUND_exit_leave:
15163 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15166 case SOUND_dynamite:
15167 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15171 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15175 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15179 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15183 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15187 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15191 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15195 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15200 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15202 int element = map_element_SP_to_RND(element_sp);
15203 int action = map_action_SP_to_RND(action_sp);
15204 int offset = (setup.sp_show_border_elements ? 0 : 1);
15205 int x = xx - offset;
15206 int y = yy - offset;
15208 PlayLevelSoundElementAction(x, y, element, action);
15211 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15213 int element = map_element_MM_to_RND(element_mm);
15214 int action = map_action_MM_to_RND(action_mm);
15216 int x = xx - offset;
15217 int y = yy - offset;
15219 if (!IS_MM_ELEMENT(element))
15220 element = EL_MM_DEFAULT;
15222 PlayLevelSoundElementAction(x, y, element, action);
15225 void PlaySound_MM(int sound_mm)
15227 int sound = map_sound_MM_to_RND(sound_mm);
15229 if (sound == SND_UNDEFINED)
15235 void PlaySoundLoop_MM(int sound_mm)
15237 int sound = map_sound_MM_to_RND(sound_mm);
15239 if (sound == SND_UNDEFINED)
15242 PlaySoundLoop(sound);
15245 void StopSound_MM(int sound_mm)
15247 int sound = map_sound_MM_to_RND(sound_mm);
15249 if (sound == SND_UNDEFINED)
15255 void RaiseScore(int value)
15257 game.score += value;
15259 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15261 DisplayGameControlValues();
15264 void RaiseScoreElement(int element)
15269 case EL_BD_DIAMOND:
15270 case EL_EMERALD_YELLOW:
15271 case EL_EMERALD_RED:
15272 case EL_EMERALD_PURPLE:
15273 case EL_SP_INFOTRON:
15274 RaiseScore(level.score[SC_EMERALD]);
15277 RaiseScore(level.score[SC_DIAMOND]);
15280 RaiseScore(level.score[SC_CRYSTAL]);
15283 RaiseScore(level.score[SC_PEARL]);
15286 case EL_BD_BUTTERFLY:
15287 case EL_SP_ELECTRON:
15288 RaiseScore(level.score[SC_BUG]);
15291 case EL_BD_FIREFLY:
15292 case EL_SP_SNIKSNAK:
15293 RaiseScore(level.score[SC_SPACESHIP]);
15296 case EL_DARK_YAMYAM:
15297 RaiseScore(level.score[SC_YAMYAM]);
15300 RaiseScore(level.score[SC_ROBOT]);
15303 RaiseScore(level.score[SC_PACMAN]);
15306 RaiseScore(level.score[SC_NUT]);
15309 case EL_EM_DYNAMITE:
15310 case EL_SP_DISK_RED:
15311 case EL_DYNABOMB_INCREASE_NUMBER:
15312 case EL_DYNABOMB_INCREASE_SIZE:
15313 case EL_DYNABOMB_INCREASE_POWER:
15314 RaiseScore(level.score[SC_DYNAMITE]);
15316 case EL_SHIELD_NORMAL:
15317 case EL_SHIELD_DEADLY:
15318 RaiseScore(level.score[SC_SHIELD]);
15320 case EL_EXTRA_TIME:
15321 RaiseScore(level.extra_time_score);
15335 case EL_DC_KEY_WHITE:
15336 RaiseScore(level.score[SC_KEY]);
15339 RaiseScore(element_info[element].collect_score);
15344 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15346 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15348 // closing door required in case of envelope style request dialogs
15351 // prevent short reactivation of overlay buttons while closing door
15352 SetOverlayActive(FALSE);
15354 CloseDoor(DOOR_CLOSE_1);
15357 if (network.enabled)
15358 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15362 FadeSkipNextFadeIn();
15364 SetGameStatus(GAME_MODE_MAIN);
15369 else // continue playing the game
15371 if (tape.playing && tape.deactivate_display)
15372 TapeDeactivateDisplayOff(TRUE);
15374 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15376 if (tape.playing && tape.deactivate_display)
15377 TapeDeactivateDisplayOn();
15381 void RequestQuitGame(boolean ask_if_really_quit)
15383 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15384 boolean skip_request = game.all_players_gone || quick_quit;
15386 RequestQuitGameExt(skip_request, quick_quit,
15387 "Do you really want to quit the game?");
15390 void RequestRestartGame(char *message)
15392 game.restart_game_message = NULL;
15394 boolean has_started_game = hasStartedNetworkGame();
15395 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15397 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15399 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15403 // needed in case of envelope request to close game panel
15404 CloseDoor(DOOR_CLOSE_1);
15406 SetGameStatus(GAME_MODE_MAIN);
15412 void CheckGameOver(void)
15414 static boolean last_game_over = FALSE;
15415 static int game_over_delay = 0;
15416 int game_over_delay_value = 50;
15417 boolean game_over = checkGameFailed();
15419 // do not handle game over if request dialog is already active
15420 if (game.request_active)
15423 // do not ask to play again if game was never actually played
15424 if (!game.GamePlayed)
15429 last_game_over = FALSE;
15430 game_over_delay = game_over_delay_value;
15435 if (game_over_delay > 0)
15442 if (last_game_over != game_over)
15443 game.restart_game_message = (hasStartedNetworkGame() ?
15444 "Game over! Play it again?" :
15447 last_game_over = game_over;
15450 boolean checkGameSolved(void)
15452 // set for all game engines if level was solved
15453 return game.LevelSolved_GameEnd;
15456 boolean checkGameFailed(void)
15458 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15459 return (game_em.game_over && !game_em.level_solved);
15460 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15461 return (game_sp.game_over && !game_sp.level_solved);
15462 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15463 return (game_mm.game_over && !game_mm.level_solved);
15464 else // GAME_ENGINE_TYPE_RND
15465 return (game.GameOver && !game.LevelSolved);
15468 boolean checkGameEnded(void)
15470 return (checkGameSolved() || checkGameFailed());
15474 // ----------------------------------------------------------------------------
15475 // random generator functions
15476 // ----------------------------------------------------------------------------
15478 unsigned int InitEngineRandom_RND(int seed)
15480 game.num_random_calls = 0;
15482 return InitEngineRandom(seed);
15485 unsigned int RND(int max)
15489 game.num_random_calls++;
15491 return GetEngineRandom(max);
15498 // ----------------------------------------------------------------------------
15499 // game engine snapshot handling functions
15500 // ----------------------------------------------------------------------------
15502 struct EngineSnapshotInfo
15504 // runtime values for custom element collect score
15505 int collect_score[NUM_CUSTOM_ELEMENTS];
15507 // runtime values for group element choice position
15508 int choice_pos[NUM_GROUP_ELEMENTS];
15510 // runtime values for belt position animations
15511 int belt_graphic[4][NUM_BELT_PARTS];
15512 int belt_anim_mode[4][NUM_BELT_PARTS];
15515 static struct EngineSnapshotInfo engine_snapshot_rnd;
15516 static char *snapshot_level_identifier = NULL;
15517 static int snapshot_level_nr = -1;
15519 static void SaveEngineSnapshotValues_RND(void)
15521 static int belt_base_active_element[4] =
15523 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15524 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15525 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15526 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15530 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15532 int element = EL_CUSTOM_START + i;
15534 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15537 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15539 int element = EL_GROUP_START + i;
15541 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15544 for (i = 0; i < 4; i++)
15546 for (j = 0; j < NUM_BELT_PARTS; j++)
15548 int element = belt_base_active_element[i] + j;
15549 int graphic = el2img(element);
15550 int anim_mode = graphic_info[graphic].anim_mode;
15552 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15553 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15558 static void LoadEngineSnapshotValues_RND(void)
15560 unsigned int num_random_calls = game.num_random_calls;
15563 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15565 int element = EL_CUSTOM_START + i;
15567 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15570 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15572 int element = EL_GROUP_START + i;
15574 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15577 for (i = 0; i < 4; i++)
15579 for (j = 0; j < NUM_BELT_PARTS; j++)
15581 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15582 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15584 graphic_info[graphic].anim_mode = anim_mode;
15588 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15590 InitRND(tape.random_seed);
15591 for (i = 0; i < num_random_calls; i++)
15595 if (game.num_random_calls != num_random_calls)
15597 Error("number of random calls out of sync");
15598 Error("number of random calls should be %d", num_random_calls);
15599 Error("number of random calls is %d", game.num_random_calls);
15601 Fail("this should not happen -- please debug");
15605 void FreeEngineSnapshotSingle(void)
15607 FreeSnapshotSingle();
15609 setString(&snapshot_level_identifier, NULL);
15610 snapshot_level_nr = -1;
15613 void FreeEngineSnapshotList(void)
15615 FreeSnapshotList();
15618 static ListNode *SaveEngineSnapshotBuffers(void)
15620 ListNode *buffers = NULL;
15622 // copy some special values to a structure better suited for the snapshot
15624 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15625 SaveEngineSnapshotValues_RND();
15626 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15627 SaveEngineSnapshotValues_EM();
15628 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15629 SaveEngineSnapshotValues_SP(&buffers);
15630 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15631 SaveEngineSnapshotValues_MM(&buffers);
15633 // save values stored in special snapshot structure
15635 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15636 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15637 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15638 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15639 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15640 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15641 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15642 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15644 // save further RND engine values
15646 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15647 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15648 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15650 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15651 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15652 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15653 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15654 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15656 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15657 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15658 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15660 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15662 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15663 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15665 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15666 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15667 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15668 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15669 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15670 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15671 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15672 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15673 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15674 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15675 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15676 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15677 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15678 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15679 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15680 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15681 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15682 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15684 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15685 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15687 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15688 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15689 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15691 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15692 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15694 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15695 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15696 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15697 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15698 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15700 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15701 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15704 ListNode *node = engine_snapshot_list_rnd;
15707 while (node != NULL)
15709 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15714 Debug("game:playing:SaveEngineSnapshotBuffers",
15715 "size of engine snapshot: %d bytes", num_bytes);
15721 void SaveEngineSnapshotSingle(void)
15723 ListNode *buffers = SaveEngineSnapshotBuffers();
15725 // finally save all snapshot buffers to single snapshot
15726 SaveSnapshotSingle(buffers);
15728 // save level identification information
15729 setString(&snapshot_level_identifier, leveldir_current->identifier);
15730 snapshot_level_nr = level_nr;
15733 boolean CheckSaveEngineSnapshotToList(void)
15735 boolean save_snapshot =
15736 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15737 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15738 game.snapshot.changed_action) ||
15739 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15740 game.snapshot.collected_item));
15742 game.snapshot.changed_action = FALSE;
15743 game.snapshot.collected_item = FALSE;
15744 game.snapshot.save_snapshot = save_snapshot;
15746 return save_snapshot;
15749 void SaveEngineSnapshotToList(void)
15751 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15755 ListNode *buffers = SaveEngineSnapshotBuffers();
15757 // finally save all snapshot buffers to snapshot list
15758 SaveSnapshotToList(buffers);
15761 void SaveEngineSnapshotToListInitial(void)
15763 FreeEngineSnapshotList();
15765 SaveEngineSnapshotToList();
15768 static void LoadEngineSnapshotValues(void)
15770 // restore special values from snapshot structure
15772 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15773 LoadEngineSnapshotValues_RND();
15774 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15775 LoadEngineSnapshotValues_EM();
15776 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15777 LoadEngineSnapshotValues_SP();
15778 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15779 LoadEngineSnapshotValues_MM();
15782 void LoadEngineSnapshotSingle(void)
15784 LoadSnapshotSingle();
15786 LoadEngineSnapshotValues();
15789 static void LoadEngineSnapshot_Undo(int steps)
15791 LoadSnapshotFromList_Older(steps);
15793 LoadEngineSnapshotValues();
15796 static void LoadEngineSnapshot_Redo(int steps)
15798 LoadSnapshotFromList_Newer(steps);
15800 LoadEngineSnapshotValues();
15803 boolean CheckEngineSnapshotSingle(void)
15805 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15806 snapshot_level_nr == level_nr);
15809 boolean CheckEngineSnapshotList(void)
15811 return CheckSnapshotList();
15815 // ---------- new game button stuff -------------------------------------------
15822 boolean *setup_value;
15823 boolean allowed_on_tape;
15824 boolean is_touch_button;
15826 } gamebutton_info[NUM_GAME_BUTTONS] =
15829 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15830 GAME_CTRL_ID_STOP, NULL,
15831 TRUE, FALSE, "stop game"
15834 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15835 GAME_CTRL_ID_PAUSE, NULL,
15836 TRUE, FALSE, "pause game"
15839 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15840 GAME_CTRL_ID_PLAY, NULL,
15841 TRUE, FALSE, "play game"
15844 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15845 GAME_CTRL_ID_UNDO, NULL,
15846 TRUE, FALSE, "undo step"
15849 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15850 GAME_CTRL_ID_REDO, NULL,
15851 TRUE, FALSE, "redo step"
15854 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15855 GAME_CTRL_ID_SAVE, NULL,
15856 TRUE, FALSE, "save game"
15859 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15860 GAME_CTRL_ID_PAUSE2, NULL,
15861 TRUE, FALSE, "pause game"
15864 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15865 GAME_CTRL_ID_LOAD, NULL,
15866 TRUE, FALSE, "load game"
15869 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15870 GAME_CTRL_ID_PANEL_STOP, NULL,
15871 FALSE, FALSE, "stop game"
15874 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15875 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15876 FALSE, FALSE, "pause game"
15879 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15880 GAME_CTRL_ID_PANEL_PLAY, NULL,
15881 FALSE, FALSE, "play game"
15884 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15885 GAME_CTRL_ID_TOUCH_STOP, NULL,
15886 FALSE, TRUE, "stop game"
15889 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15890 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15891 FALSE, TRUE, "pause game"
15894 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15895 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15896 TRUE, FALSE, "background music on/off"
15899 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15900 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15901 TRUE, FALSE, "sound loops on/off"
15904 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15905 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15906 TRUE, FALSE, "normal sounds on/off"
15909 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15910 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15911 FALSE, FALSE, "background music on/off"
15914 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15915 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15916 FALSE, FALSE, "sound loops on/off"
15919 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15920 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15921 FALSE, FALSE, "normal sounds on/off"
15925 void CreateGameButtons(void)
15929 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15931 int graphic = gamebutton_info[i].graphic;
15932 struct GraphicInfo *gfx = &graphic_info[graphic];
15933 struct XY *pos = gamebutton_info[i].pos;
15934 struct GadgetInfo *gi;
15937 unsigned int event_mask;
15938 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15939 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15940 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15941 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15942 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15943 int gd_x = gfx->src_x;
15944 int gd_y = gfx->src_y;
15945 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15946 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15947 int gd_xa = gfx->src_x + gfx->active_xoffset;
15948 int gd_ya = gfx->src_y + gfx->active_yoffset;
15949 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15950 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15951 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15952 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15955 if (gfx->bitmap == NULL)
15957 game_gadget[id] = NULL;
15962 if (id == GAME_CTRL_ID_STOP ||
15963 id == GAME_CTRL_ID_PANEL_STOP ||
15964 id == GAME_CTRL_ID_TOUCH_STOP ||
15965 id == GAME_CTRL_ID_PLAY ||
15966 id == GAME_CTRL_ID_PANEL_PLAY ||
15967 id == GAME_CTRL_ID_SAVE ||
15968 id == GAME_CTRL_ID_LOAD)
15970 button_type = GD_TYPE_NORMAL_BUTTON;
15972 event_mask = GD_EVENT_RELEASED;
15974 else if (id == GAME_CTRL_ID_UNDO ||
15975 id == GAME_CTRL_ID_REDO)
15977 button_type = GD_TYPE_NORMAL_BUTTON;
15979 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15983 button_type = GD_TYPE_CHECK_BUTTON;
15984 checked = (gamebutton_info[i].setup_value != NULL ?
15985 *gamebutton_info[i].setup_value : FALSE);
15986 event_mask = GD_EVENT_PRESSED;
15989 gi = CreateGadget(GDI_CUSTOM_ID, id,
15990 GDI_IMAGE_ID, graphic,
15991 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15994 GDI_WIDTH, gfx->width,
15995 GDI_HEIGHT, gfx->height,
15996 GDI_TYPE, button_type,
15997 GDI_STATE, GD_BUTTON_UNPRESSED,
15998 GDI_CHECKED, checked,
15999 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16000 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16001 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16002 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16003 GDI_DIRECT_DRAW, FALSE,
16004 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16005 GDI_EVENT_MASK, event_mask,
16006 GDI_CALLBACK_ACTION, HandleGameButtons,
16010 Fail("cannot create gadget");
16012 game_gadget[id] = gi;
16016 void FreeGameButtons(void)
16020 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16021 FreeGadget(game_gadget[i]);
16024 static void UnmapGameButtonsAtSamePosition(int id)
16028 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16030 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16031 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16032 UnmapGadget(game_gadget[i]);
16035 static void UnmapGameButtonsAtSamePosition_All(void)
16037 if (setup.show_snapshot_buttons)
16039 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16040 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16041 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16045 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16046 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16047 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16049 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16050 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16051 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16055 static void MapGameButtonsAtSamePosition(int id)
16059 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16061 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16062 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16063 MapGadget(game_gadget[i]);
16065 UnmapGameButtonsAtSamePosition_All();
16068 void MapUndoRedoButtons(void)
16070 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16071 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16073 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16074 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16077 void UnmapUndoRedoButtons(void)
16079 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16080 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16082 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16083 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16086 void ModifyPauseButtons(void)
16090 GAME_CTRL_ID_PAUSE,
16091 GAME_CTRL_ID_PAUSE2,
16092 GAME_CTRL_ID_PANEL_PAUSE,
16093 GAME_CTRL_ID_TOUCH_PAUSE,
16098 for (i = 0; ids[i] > -1; i++)
16099 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16102 static void MapGameButtonsExt(boolean on_tape)
16106 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16107 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16108 i != GAME_CTRL_ID_UNDO &&
16109 i != GAME_CTRL_ID_REDO)
16110 MapGadget(game_gadget[i]);
16112 UnmapGameButtonsAtSamePosition_All();
16114 RedrawGameButtons();
16117 static void UnmapGameButtonsExt(boolean on_tape)
16121 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16122 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16123 UnmapGadget(game_gadget[i]);
16126 static void RedrawGameButtonsExt(boolean on_tape)
16130 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16131 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16132 RedrawGadget(game_gadget[i]);
16135 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16140 gi->checked = state;
16143 static void RedrawSoundButtonGadget(int id)
16145 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16146 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16147 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16148 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16149 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16150 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16153 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16154 RedrawGadget(game_gadget[id2]);
16157 void MapGameButtons(void)
16159 MapGameButtonsExt(FALSE);
16162 void UnmapGameButtons(void)
16164 UnmapGameButtonsExt(FALSE);
16167 void RedrawGameButtons(void)
16169 RedrawGameButtonsExt(FALSE);
16172 void MapGameButtonsOnTape(void)
16174 MapGameButtonsExt(TRUE);
16177 void UnmapGameButtonsOnTape(void)
16179 UnmapGameButtonsExt(TRUE);
16182 void RedrawGameButtonsOnTape(void)
16184 RedrawGameButtonsExt(TRUE);
16187 static void GameUndoRedoExt(void)
16189 ClearPlayerAction();
16191 tape.pausing = TRUE;
16194 UpdateAndDisplayGameControlValues();
16196 DrawCompleteVideoDisplay();
16197 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16198 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16199 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16204 static void GameUndo(int steps)
16206 if (!CheckEngineSnapshotList())
16209 LoadEngineSnapshot_Undo(steps);
16214 static void GameRedo(int steps)
16216 if (!CheckEngineSnapshotList())
16219 LoadEngineSnapshot_Redo(steps);
16224 static void HandleGameButtonsExt(int id, int button)
16226 static boolean game_undo_executed = FALSE;
16227 int steps = BUTTON_STEPSIZE(button);
16228 boolean handle_game_buttons =
16229 (game_status == GAME_MODE_PLAYING ||
16230 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16232 if (!handle_game_buttons)
16237 case GAME_CTRL_ID_STOP:
16238 case GAME_CTRL_ID_PANEL_STOP:
16239 case GAME_CTRL_ID_TOUCH_STOP:
16240 if (game_status == GAME_MODE_MAIN)
16246 RequestQuitGame(TRUE);
16250 case GAME_CTRL_ID_PAUSE:
16251 case GAME_CTRL_ID_PAUSE2:
16252 case GAME_CTRL_ID_PANEL_PAUSE:
16253 case GAME_CTRL_ID_TOUCH_PAUSE:
16254 if (network.enabled && game_status == GAME_MODE_PLAYING)
16257 SendToServer_ContinuePlaying();
16259 SendToServer_PausePlaying();
16262 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16264 game_undo_executed = FALSE;
16268 case GAME_CTRL_ID_PLAY:
16269 case GAME_CTRL_ID_PANEL_PLAY:
16270 if (game_status == GAME_MODE_MAIN)
16272 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16274 else if (tape.pausing)
16276 if (network.enabled)
16277 SendToServer_ContinuePlaying();
16279 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16283 case GAME_CTRL_ID_UNDO:
16284 // Important: When using "save snapshot when collecting an item" mode,
16285 // load last (current) snapshot for first "undo" after pressing "pause"
16286 // (else the last-but-one snapshot would be loaded, because the snapshot
16287 // pointer already points to the last snapshot when pressing "pause",
16288 // which is fine for "every step/move" mode, but not for "every collect")
16289 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16290 !game_undo_executed)
16293 game_undo_executed = TRUE;
16298 case GAME_CTRL_ID_REDO:
16302 case GAME_CTRL_ID_SAVE:
16306 case GAME_CTRL_ID_LOAD:
16310 case SOUND_CTRL_ID_MUSIC:
16311 case SOUND_CTRL_ID_PANEL_MUSIC:
16312 if (setup.sound_music)
16314 setup.sound_music = FALSE;
16318 else if (audio.music_available)
16320 setup.sound = setup.sound_music = TRUE;
16322 SetAudioMode(setup.sound);
16324 if (game_status == GAME_MODE_PLAYING)
16328 RedrawSoundButtonGadget(id);
16332 case SOUND_CTRL_ID_LOOPS:
16333 case SOUND_CTRL_ID_PANEL_LOOPS:
16334 if (setup.sound_loops)
16335 setup.sound_loops = FALSE;
16336 else if (audio.loops_available)
16338 setup.sound = setup.sound_loops = TRUE;
16340 SetAudioMode(setup.sound);
16343 RedrawSoundButtonGadget(id);
16347 case SOUND_CTRL_ID_SIMPLE:
16348 case SOUND_CTRL_ID_PANEL_SIMPLE:
16349 if (setup.sound_simple)
16350 setup.sound_simple = FALSE;
16351 else if (audio.sound_available)
16353 setup.sound = setup.sound_simple = TRUE;
16355 SetAudioMode(setup.sound);
16358 RedrawSoundButtonGadget(id);
16367 static void HandleGameButtons(struct GadgetInfo *gi)
16369 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16372 void HandleSoundButtonKeys(Key key)
16374 if (key == setup.shortcut.sound_simple)
16375 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16376 else if (key == setup.shortcut.sound_loops)
16377 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16378 else if (key == setup.shortcut.sound_music)
16379 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);